Quantitative Factor Investing Screener (Simulated Data)
Step 1: Set Your Factor Thresholds
Screener Results (Based on Simulated Stock Universe)
Stocks Found: ---
Average P/E of Results: ---
Average Market Cap of Results: --- (Billions $)
Screened Stocks:
| Ticker | Name | Sector | Price ($) | Mkt Cap (B$) | P/E | P/B | EPS Gr. (%) | ROE (%) | Beta | Div. Yield (%) |
|---|---|---|---|---|---|---|---|---|---|---|
| Define criteria and run screener to see results. | ||||||||||
Sector Distribution of Screened Stocks:
No stocks found to display sector distribution.
'; const newCanvas = document.createElement('canvas'); // Re-add canvas for future use newCanvas.id = canvasId; chartParent.appendChild(newCanvas); } } currentScreenerResults = { criteria, screenedStocks }; } if (runScreenerBtn) { runScreenerBtn.addEventListener('click', () => { if (configErrorEl) { configErrorEl.style.display = 'none'; configErrorEl.textContent = ''; } try { const criteria = getCriteria(); const rangePairs = [ ['peRatioMin', 'peRatioMax'], ['pbRatioMin', 'pbRatioMax'], ['epsGrowthMin', 'epsGrowthMax'], ['revGrowthMin', 'revGrowthMax'], ['mom6mMin', 'mom6mMax'], ['mom12mMin', 'mom12mMax'], ['roeMin', 'roeMax'], ['deRatioMin', 'deRatioMax'], ['marketCapMin', 'marketCapMax'], ['betaMin', 'betaMax'], ['divYieldMin', 'divYieldMax'] ]; for (const [minKey, maxKey] of rangePairs) { if (criteria[minKey] !== undefined && criteria[maxKey] !== undefined && criteria[minKey] > criteria[maxKey]) { // Get label text for better error message const labelText = document.querySelector(`label[for="${minKey}"]`)?.textContent.replace('Min:', '').trim() || minKey.replace('Min',''); throw new Error(`Min value for ${labelText} cannot be greater than its Max value.`); } } const screenedStocks = screenStocks(criteria); displayResults(screenedStocks, criteria); if (tabs && tabs[1]) tabs[1].click(); } catch (e) { if (configErrorEl) { configErrorEl.textContent = e.message || "Error processing criteria."; configErrorEl.style.display = 'block'; } else { console.error(e.message || "Error processing criteria."); } } }); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => { if (!currentScreenerResults || !currentScreenerResults.screenedStocks) { alert("Please run the screener first to generate results."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library could not be found.'); return; } const jsPDFConstructor = window.jspdf.jsPDF; let tempDoc; // Declare here try { tempDoc = new jsPDFConstructor(); } catch (e) { alert('Error initializing jsPDF. Library might not be loaded correctly.'); return; } if (typeof tempDoc.autoTable !== 'function') { alert('Error: jsPDF-AutoTable plugin could not be found.'); return; } const doc = new jsPDFConstructor('l', 'pt', 'a4'); const pageWidth = doc.internal.pageSize.getWidth(); const margin = 30; let yPos = margin; const rootStyles = getComputedStyle(document.documentElement); const primaryColorPDF = rootStyles.getPropertyValue('--primary-color').trim(); function checkNewPage(neededHeight){ if (yPos + neededHeight > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); yPos = margin; } } function addTitle(text){ checkNewPage(30); doc.setFontSize(18); doc.setTextColor(primaryColorPDF); doc.text(text, pageWidth / 2, yPos, { align: 'center' }); yPos += 30; } function addSectionTitle(text){ checkNewPage(25); doc.setFontSize(14); doc.setTextColor(primaryColorPDF); doc.text(text, margin, yPos); yPos += 20; } addTitle("Quantitative Factor Investing Screener Results"); addSectionTitle("Screening Criteria Used:"); const criteriaData = []; const crit = currentScreenerResults.criteria; Object.keys(crit).forEach(key => { if (key !== 'selectedSectors') { const labelEl = document.querySelector(`label[for="${key}"]`); const label = labelEl ? labelEl.textContent.replace(/Min:|Max:/, '').replace(':', '').trim() : key; criteriaData.push([label, crit[key]]); } }); if (crit.selectedSectors.length > 0) { criteriaData.push(["Selected Sectors", crit.selectedSectors.join(', ')]); } else { criteriaData.push(["Selected Sectors", "All Sectors"]); } checkNewPage(criteriaData.length * 15 + 30); doc.autoTable({ startY: yPos, head: [['Criterion', 'Value']], body: criteriaData, theme: 'striped', headStyles: { fillColor: primaryColorPDF, textColor: '#ffffff'}, margin: {left: margin, right: margin} }); yPos = doc.lastAutoTable.finalY + 20; addSectionTitle(`Screened Stocks (${currentScreenerResults.screenedStocks.length} found):`); const tableData = currentScreenerResults.screenedStocks.map(s => [ s.ticker, s.name, s.sector, s.price.toFixed(2), s.marketCap.toFixed(1), s.peRatio.toFixed(1), s.pbRatio.toFixed(1), s.epsGrowth.toFixed(1), s.roe.toFixed(1), s.beta.toFixed(2), s.dividendYield.toFixed(1) ]); checkNewPage(Math.min(15, tableData.length) * 15 + 30); doc.autoTable({ startY: yPos, head: [['Ticker', 'Name', 'Sector', 'Price ($)', 'Mkt Cap (B$)', 'P/E', 'P/B', 'EPS Gr.(%)', 'ROE (%)', 'Beta', 'Div.Y (%)']], body: tableData, theme: 'grid', headStyles: { fillColor: primaryColorPDF, textColor: '#ffffff', fontSize: 7.5}, // Reduced font size for header bodyStyles: { fontSize: 7 }, // Reduced font size for body margin: {left: margin, right: margin}, tableWidth: 'auto' }); yPos = doc.lastAutoTable.finalY + 20; if (sectorDistributionChartInstance && currentScreenerResults.screenedStocks.length > 0) { // Check for Chart.js before attempting to use it for PDF if (typeof window.Chart === 'undefined') { console.error('Chart.js library not loaded. Cannot include chart in PDF.'); checkNewPage(20); doc.text("Sector distribution chart could not be rendered (Chart.js not loaded).", margin, yPos); yPos+=20; } else { addSectionTitle("Sector Distribution of Screened Stocks:"); try { const chartImage = sectorDistributionChartInstance.toBase64Image(); const chartMaxHeight = 180; const chartMaxWidth = pageWidth / 2 - margin * 1.5; checkNewPage(chartMaxHeight + 20); const imgProps = doc.getImageProperties(chartImage); let imgWidth = imgProps.width; let imgHeight = imgProps.height; if (imgWidth > chartMaxWidth) { const r = chartMaxWidth / imgWidth; imgWidth = chartMaxWidth; imgHeight *= r; } if (imgHeight > chartMaxHeight) { const r = chartMaxHeight / imgHeight; imgHeight = chartMaxHeight; imgWidth *= r; } doc.addImage(chartImage, 'PNG', margin, yPos, imgWidth, imgHeight); yPos += imgHeight + 20; } catch (e) { console.error("Error adding chart to PDF:", e); checkNewPage(20); doc.text("Sector distribution chart could not be rendered in PDF.", margin, yPos); yPos+=20;} } } checkNewPage(20); doc.setFontSize(8); doc.setTextColor(150,150,150); doc.text(`Report generated on: ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`, margin, doc.internal.pageSize.getHeight() - (margin/2)); doc.text("Quantitative Factor Screener (Simulated Data)", pageWidth - margin, doc.internal.pageSize.getHeight() - (margin/2), { align: 'right' }); doc.save("Quantitative_Factor_Screener_Results.pdf"); }); } // Initialize if (tabs && tabContents && tabs.length > 0 && tabContents.length > 0) { if (tabs[0] && tabContents[0] && !tabs[0].classList.contains('active')) { tabs[0].classList.add('active'); tabContents[0].classList.add('active'); } currentQfsTabIndex = Array.from(tabs).findIndex(tab => tab.classList.contains('active')); if(currentQfsTabIndex === -1 && tabs[0]) { tabs[0].classList.add('active'); if(tabContents[0]) tabContents[0].classList.add('active'); currentQfsTabIndex = 0; } updateNavButtons(); } else { console.error("Quant Factor Screener: Tabs or tab content areas not found."); } });