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.

Input Data (for your chosen lookback period, e.g., last 12 months)

Asset Definitions

Returns Data (for the lookback period described above)

%
%
%

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(); });
Scroll to Top