Quantitative Multi-Factor Model Creator
Define Model Factors and Their Weights
Total Weight: 0%
Input Data for Each Asset
Factor Values:
Added Assets
No assets added yet.
Quantitative Multi-Factor Model Results
No results to display. Please define factors, add assets, and then click "Calculate Model".
No assets added yet.
'; return; } let tableHTML = '| Asset Name | '; const namedFactors = factors.filter(f => f.name && f.name.trim() !== ''); namedFactors.forEach(f => tableHTML += `${escapeHtml(f.name)} | `); tableHTML += 'Action |
|---|---|---|
| ${escapeHtml(asset.name)} | `; namedFactors.forEach(f => { const value = asset.factorValues[f.id]; tableHTML += `${value !== undefined && !isNaN(value) ? value : 'N/A'} | `; }); tableHTML += `
Factors & Weights:
No factors defined.
'; return; } let summaryHTML = 'Factors & Weights Used:
- ';
factors.forEach(f => {
if (f.name && f.name.trim()) {
summaryHTML += `
- ${escapeHtml(f.name)}: ${f.weight}% (${f.direction === 'high' ? 'Higher is better' : 'Lower is better'}) `; } }); summaryHTML += '
Please define factors on Tab 1 and add asset data on Tab 2.
'; downloadPdfButtonResults.style.display = 'none'; return; } if (!validateFactorWeights()){ alert('Factor weights issue. Please correct on Tab 1.'); switchTab('factorsTab'); downloadPdfButtonResults.style.display = 'none'; return; } const results = []; const namedFactors = factors.filter(f => f.name && f.name.trim() !== ''); const factorRawValues = {}; namedFactors.forEach(f => factorRawValues[f.id] = []); assets.forEach(asset => { namedFactors.forEach(f => { const val = asset.factorValues[f.id]; if (val !== undefined && !isNaN(val)) { factorRawValues[f.id].push(val); } }); }); const factorStats = {}; namedFactors.forEach(f => { const currentFactorValues = factorRawValues[f.id]; // Renamed for clarity within this scope if (currentFactorValues && currentFactorValues.length > 0) { factorStats[f.id] = { min: Math.min(...currentFactorValues), max: Math.max(...currentFactorValues) }; } else { factorStats[f.id] = { min: 0, max: 0 }; } }); assets.forEach(asset => { let compositeScore = 0; const assetResult = { id: asset.id, name: asset.name, factorValues: {}, normalizedScores: {}, weightedScores: {}, compositeScore: 0, rank: 0 }; namedFactors.forEach(f => { const rawValue = asset.factorValues[f.id]; assetResult.factorValues[f.id] = (rawValue !== undefined && !isNaN(rawValue)) ? rawValue : 'N/A'; let normalizedScore = 0; if (rawValue !== undefined && !isNaN(rawValue)) { const { min, max } = factorStats[f.id]; if (max > min) { if (f.direction === 'high') { normalizedScore = (rawValue - min) / (max - min); } else { normalizedScore = (max - rawValue) / (max - min); } } else if (max === min && factorRawValues[f.id] && factorRawValues[f.id].length > 0) { // *** FIXED LINE HERE *** normalizedScore = 0; } } normalizedScore = Math.max(0, Math.min(1, normalizedScore)); assetResult.normalizedScores[f.id] = normalizedScore; const weightedScore = normalizedScore * (f.weight / 100); assetResult.weightedScores[f.id] = weightedScore; compositeScore += weightedScore; }); assetResult.compositeScore = compositeScore; results.push(assetResult); }); results.sort((a, b) => b.compositeScore - a.compositeScore); results.forEach((res, index) => res.rank = index + 1); renderResultsTable(results, namedFactors); downloadPdfButtonResults.style.display = 'block'; }); function renderResultsTable(results, displayFactors) { if (!resultsTableContainer) return; if (results.length === 0) { resultsTableContainer.innerHTML = 'No results calculated or no assets to display.
'; return; } let tableHTML = `| Rank | Asset Name | `; displayFactors.forEach(f => tableHTML += `${escapeHtml(f.name)} (Raw) | `); displayFactors.forEach(f => tableHTML += `${escapeHtml(f.name)} (Wgt. Score) | `); tableHTML += `Composite Score |
|---|---|---|---|---|
| ${res.rank} | ${escapeHtml(res.name)} | `; displayFactors.forEach(f => { const rawVal = res.factorValues[f.id]; tableHTML += `${typeof rawVal === 'number' ? rawVal.toFixed(2) : rawVal} | `; }); displayFactors.forEach(f => tableHTML += `${res.weightedScores[f.id].toFixed(4)} | `); tableHTML += `${res.compositeScore.toFixed(4)} |