Dual Momentum Strategy Illustrator
Manually input past returns to see a simplified dual momentum allocation decision.
** VERY IMPORTANT - PLEASE READ **
- This tool is for educational and illustrative purposes ONLY. It demonstrates a simplified strategy based on YOUR manually entered data.
- It does **NOT** use live or historical market data, perform backtesting, or provide investment advice.
- Past performance is not indicative of future results. Momentum strategies can underperform and have specific risks.
- The asset names and return figures are for you to define based on your research for a particular lookback period.
- Always consult with a qualified financial advisor before making any investment decisions.
Dual Momentum Strategy Decision
Absolute Momentum Check:
${primaryName} Return (${this.formatPercent(primaryReturn)})
vs. Risk-Free Return (${this.formatPercent(riskFreeReturn)})
Result: ${absoluteMomentumPass ? 'PASS (Invest in Equities)' : 'FAIL (Switch to Safe Asset)'}
`;
if (absoluteMomentumPass) {
// 2. Relative Momentum Check
const relativeMomentumPrimaryWins = primaryReturn > alternativeReturn;
rationaleHtml += `
Relative Momentum Check (Equities chosen):
${primaryName} Return (${this.formatPercent(primaryReturn)})
vs. ${alternativeName} Return (${this.formatPercent(alternativeReturn)})
Result: Invest in ${relativeMomentumPrimaryWins ? primaryName : alternativeName}
`;
decisionAsset = relativeMomentumPrimaryWins ? primaryName : alternativeName;
decisionClass = 'dm-decision-equities';
} else {
decisionAsset = safeName;
decisionClass = 'dm-decision-safe';
}
this.resultsData = {
inputs: {
lookbackPeriodDesc: this.elements.lookbackPeriodDescInput.value,
primaryName, alternativeName, safeName,
primaryReturn, alternativeReturn, riskFreeReturn
},
decision: decisionAsset,
rationale: rationaleHtml, // Store HTML rationale for PDF. Could be structured data too.
decisionClass: decisionClass
};
this.elements.decisionDisplay.className = `dm-decision-box ${decisionClass}`;
this.elements.decisionDisplay.innerHTML = `
Strategy Suggests Allocating To:
${decisionAsset}
`;
this.elements.rationaleDisplay.innerHTML = rationaleHtml;
this.elements.resultsSection.style.display = 'block';
},
generatePDF: async function() {
if (!window.jspdf || !window.html2canvas || Object.keys(this.resultsData).length === 0) {
alert('Please determine the allocation first to generate a PDF.'); return;
}
await new Promise(resolve => setTimeout(resolve, 50));
const i = this.resultsData.inputs;
const d = this.resultsData.decision;
// Rationale for PDF needs to be plain text or simple HTML, not directly from DOM if complex CSS
let pdfRationale = `Absolute Momentum: ${i.primaryName} (${this.formatPercent(i.primaryReturn)}) vs. Risk-Free (${this.formatPercent(i.riskFreeReturn)}) -> ${(i.primaryReturn > i.riskFreeReturn) ? 'PASS' : 'FAIL'}. `;
if (i.primaryReturn > i.riskFreeReturn) {
pdfRationale += `Relative Momentum: ${i.primaryName} (${this.formatPercent(i.primaryReturn)}) vs. ${i.alternativeName} (${this.formatPercent(i.alternativeReturn)}) -> Invest in ${(i.primaryReturn > i.alternativeReturn) ? i.primaryName : i.alternativeName}.`;
} else {
pdfRationale += `Result: Switch to ${i.safeName}.`;
}
const pdfExportContainer = document.createElement('div');
pdfExportContainer.classList.add('dm-pdf-export-content');
pdfExportContainer.style.width = '800px';
let pdfHtml = `
Dual Momentum Strategy Analysis
`;
pdfHtml += `
Disclaimer: Illustrative, educational tool. Based on manual inputs. NOT investment advice. Consult professionals.
`;
pdfHtml += `
Inputs Summary
`;
pdfHtml += `
Lookback Period: ${i.lookbackPeriodDesc || 'N/A'}
`;
pdfHtml += `
Primary Risky Asset: ${i.primaryName}
`;
pdfHtml += `
Alternative Risky Asset: ${i.alternativeName}
`;
pdfHtml += `
Safe/Defensive Asset: ${i.safeName}
`;
pdfHtml += `
`;
pdfHtml += `
Return of ${i.primaryName}: ${this.formatPercent(i.primaryReturn).replace(' %','')}
`;
pdfHtml += `
Return of ${i.alternativeName}: ${this.formatPercent(i.alternativeReturn).replace(' %','')}
`;
pdfHtml += `
Return of Risk-Free Asset: ${this.formatPercent(i.riskFreeReturn).replace(' %','')}
`;
pdfHtml += `
`;
pdfHtml += `
Strategy Decision
`;
pdfHtml += `
Allocate to: ${d}
`;
pdfHtml += `
Rationale
${pdfRationale.replace(/
/g, ' ')}
`;
pdfExportContainer.innerHTML = pdfHtml;
document.body.appendChild(pdfExportContainer);
try {
const canvas = await html2canvas(pdfExportContainer, { scale: 1.5, useCORS: true, logging: false, windowWidth: pdfExportContainer.scrollWidth });
document.body.removeChild(pdfExportContainer);
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
let imgFinalWidth = pdfWidth - 40;
let imgFinalHeight = (imgProps.height * imgFinalWidth) / imgProps.width;
if (imgFinalHeight > pdfHeight - 40) {
imgFinalHeight = pdfHeight - 40;
imgFinalWidth = (imgProps.width * imgFinalHeight) / imgProps.height;
}
let currentY = 20;
let heightProcessed = 0;
const pageMargin = 20;
const pageHeightAvailable = pdfHeight - 2 * pageMargin;
while(heightProcessed < canvas.height) {
let pageSegmentHeightOnCanvas = Math.min(canvas.height - heightProcessed, (pageHeightAvailable / imgFinalHeight) * canvas.height );
if (pageSegmentHeightOnCanvas <=0) break;
const segmentCanvas = document.createElement('canvas');
segmentCanvas.width = canvas.width;
segmentCanvas.height = pageSegmentHeightOnCanvas;
const sctx = segmentCanvas.getContext('2d');
sctx.drawImage(canvas, 0, heightProcessed, canvas.width, pageSegmentHeightOnCanvas, 0, 0, canvas.width, pageSegmentHeightOnCanvas);
const segmentImgData = segmentCanvas.toDataURL('image/png');
const segmentImgPropsPdf = pdf.getImageProperties(segmentImgData);
let segmentRenderWidth = pdfWidth - 2 * pageMargin;
let segmentRenderHeight = (segmentImgPropsPdf.height * segmentRenderWidth) / segmentImgPropsPdf.width;
if(segmentRenderHeight > pageHeightAvailable) {
segmentRenderHeight = pageHeightAvailable;
segmentRenderWidth = (segmentImgPropsPdf.width * segmentRenderHeight) / segmentImgPropsPdf.height;
}
if(heightProcessed > 0) pdf.addPage();
pdf.addImage(segmentImgData, 'PNG', pageMargin, pageMargin, segmentRenderWidth, segmentRenderHeight);
heightProcessed += pageSegmentHeightOnCanvas;
}
pdf.save('Dual_Momentum_Analysis.pdf');
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating PDF.");
if (document.body.contains(pdfExportContainer)) document.body.removeChild(pdfExportContainer);
}
}
};
document.addEventListener('DOMContentLoaded', function() {
dmApp.init();
});