| |
| let returnChart = null; |
| let weightChart = null; |
| let concentrationChart = null; |
| let sectorData = null; |
| let selectedTickers = new Set(); |
| let colorMap = {}; |
|
|
| |
| if (typeof Chart !== 'undefined' && Chart.annotation) { |
| Chart.register(Chart.annotation); |
| } |
|
|
| |
| async function fetchTickersBySector() { |
| try { |
| const response = await fetch('/api/tickers_by_sector'); |
| return await response.json(); |
| } catch (error) { |
| console.error('Error fetching tickers:', error); |
| return []; |
| } |
| } |
|
|
| |
| function formatDate(date) { |
| return date.toISOString().split('T')[0]; |
| } |
|
|
| |
| function setActiveNavLink() { |
| const currentPath = window.location.pathname; |
| const navLinks = document.querySelectorAll('.nav-link'); |
| |
| navLinks.forEach(link => { |
| const linkPath = link.getAttribute('href'); |
| if (currentPath.endsWith(linkPath) || |
| (currentPath.endsWith('/') && linkPath === '/index.html') || |
| (currentPath.endsWith('/fullpage') && linkPath === '/index.html')) { |
| link.classList.add('active'); |
| } else { |
| link.classList.remove('active'); |
| } |
| }); |
| } |
|
|
| |
| function populateStockList(sectors) { |
| const stockListElement = document.getElementById('stockList'); |
| stockListElement.innerHTML = ''; |
| |
| |
| document.getElementById('selectAllBtn').addEventListener('click', () => { |
| const allCheckboxes = document.querySelectorAll('.stock-checkbox'); |
| allCheckboxes.forEach(checkbox => { |
| checkbox.checked = true; |
| selectedTickers.add(checkbox.value); |
| }); |
| }); |
| |
| document.getElementById('deselectAllBtn').addEventListener('click', () => { |
| const allCheckboxes = document.querySelectorAll('.stock-checkbox'); |
| allCheckboxes.forEach(checkbox => { |
| checkbox.checked = false; |
| selectedTickers.delete(checkbox.value); |
| }); |
| }); |
| |
| |
| sectors.forEach(sector => { |
| const sectorGroup = document.createElement('div'); |
| sectorGroup.className = 'sector-group'; |
| |
| |
| const sectorHeader = document.createElement('div'); |
| sectorHeader.className = 'sector-header'; |
| |
| const sectorNameContainer = document.createElement('div'); |
| sectorNameContainer.className = 'sector-name'; |
| |
| const sectorArrow = document.createElement('span'); |
| sectorArrow.className = 'sector-arrow'; |
| sectorArrow.textContent = '▶'; |
| sectorArrow.classList.add('open'); |
| |
| const sectorNameText = document.createElement('span'); |
| sectorNameText.textContent = sector.sector; |
| |
| sectorNameContainer.appendChild(sectorArrow); |
| sectorNameContainer.appendChild(sectorNameText); |
| |
| const sectorToggle = document.createElement('button'); |
| sectorToggle.className = 'sector-toggle'; |
| sectorToggle.textContent = 'Toggle All'; |
| |
| sectorHeader.appendChild(sectorNameContainer); |
| sectorHeader.appendChild(sectorToggle); |
| sectorGroup.appendChild(sectorHeader); |
| |
| |
| const tickerGrid = document.createElement('div'); |
| tickerGrid.className = 'ticker-grid'; |
| |
| |
| sector.tickers.forEach(ticker => { |
| const stockItem = document.createElement('div'); |
| stockItem.className = 'stock-item'; |
| |
| const checkbox = document.createElement('input'); |
| checkbox.type = 'checkbox'; |
| checkbox.className = 'stock-checkbox'; |
| checkbox.value = ticker; |
| checkbox.id = `ticker-${ticker}`; |
| checkbox.addEventListener('change', () => { |
| if (checkbox.checked) { |
| selectedTickers.add(ticker); |
| } else { |
| selectedTickers.delete(ticker); |
| } |
| }); |
| |
| const label = document.createElement('label'); |
| label.className = 'stock-ticker'; |
| label.textContent = ticker; |
| label.htmlFor = `ticker-${ticker}`; |
| |
| stockItem.appendChild(checkbox); |
| stockItem.appendChild(label); |
| tickerGrid.appendChild(stockItem); |
| }); |
| |
| sectorGroup.appendChild(tickerGrid); |
| stockListElement.appendChild(sectorGroup); |
| |
| |
| sectorToggle.addEventListener('click', () => { |
| const checkboxes = tickerGrid.querySelectorAll('.stock-checkbox'); |
| const allChecked = Array.from(checkboxes).every(cb => cb.checked); |
| |
| checkboxes.forEach(checkbox => { |
| checkbox.checked = !allChecked; |
| if (!allChecked) { |
| selectedTickers.add(checkbox.value); |
| } else { |
| selectedTickers.delete(checkbox.value); |
| } |
| }); |
| }); |
| |
| |
| sectorNameContainer.addEventListener('click', () => { |
| sectorArrow.classList.toggle('open'); |
| tickerGrid.style.display = sectorArrow.classList.contains('open') ? 'grid' : 'none'; |
| }); |
| }); |
| } |
|
|
| |
| function initializeCharts() { |
| const returnsCtx = document.getElementById('returnsChart').getContext('2d'); |
| const weightsCtx = document.getElementById('weightsChart').getContext('2d'); |
| const concentrationCtx = document.getElementById('concentrationChart').getContext('2d'); |
| |
| |
| Chart.defaults.color = '#b0b0b8'; |
| Chart.defaults.scale.grid.color = 'rgba(56, 56, 64, 0.5)'; |
| Chart.defaults.scale.grid.borderColor = 'rgba(56, 56, 64, 0.8)'; |
| |
| |
| returnChart = new Chart(returnsCtx, { |
| type: 'line', |
| data: { |
| datasets: [ |
| { |
| label: 'OGD Portfolio', |
| borderColor: '#3f88e2', |
| backgroundColor: 'rgba(63, 136, 226, 0.1)', |
| borderWidth: 1.5, |
| pointRadius: 0, |
| tension: 0.1, |
| data: [] |
| }, |
| { |
| label: 'Equal Weight', |
| borderColor: '#4caf50', |
| backgroundColor: 'rgba(76, 175, 80, 0.1)', |
| borderWidth: 1.5, |
| pointRadius: 0, |
| tension: 0.1, |
| data: [] |
| }, |
| { |
| label: 'Random Portfolio', |
| borderColor: '#e2b53f', |
| backgroundColor: 'rgba(226, 181, 63, 0.1)', |
| borderWidth: 1.5, |
| pointRadius: 0, |
| tension: 0.1, |
| data: [] |
| } |
| ] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| interaction: { |
| mode: 'index', |
| intersect: false |
| }, |
| scales: { |
| x: { |
| type: 'time', |
| time: { |
| unit: 'month', |
| displayFormats: { |
| month: 'MMM yyyy' |
| } |
| }, |
| title: { |
| display: true, |
| text: 'Date' |
| } |
| }, |
| y: { |
| title: { |
| display: true, |
| text: 'Cumulative Return' |
| }, |
| beginAtZero: false |
| } |
| }, |
| plugins: { |
| legend: { |
| position: 'top', |
| labels: { |
| usePointStyle: true, |
| padding: 15 |
| } |
| }, |
| tooltip: { |
| callbacks: { |
| label: function(context) { |
| const label = context.dataset.label || ''; |
| const value = ((context.parsed.y - 1) * 100).toFixed(2); |
| return `${label}: ${value}%`; |
| } |
| } |
| } |
| } |
| } |
| }); |
| |
| |
| weightChart = new Chart(weightsCtx, { |
| type: 'line', |
| data: { |
| datasets: [] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| interaction: { |
| mode: 'index', |
| intersect: false |
| }, |
| scales: { |
| x: { |
| type: 'time', |
| time: { |
| unit: 'month', |
| displayFormats: { |
| month: 'MMM yyyy' |
| } |
| }, |
| title: { |
| display: true, |
| text: 'Date' |
| } |
| }, |
| y: { |
| title: { |
| display: true, |
| text: 'Asset Weight' |
| }, |
| min: 0, |
| ticks: { |
| callback: function(value) { |
| return (value * 100).toFixed(0) + '%'; |
| } |
| } |
| } |
| }, |
| plugins: { |
| legend: { |
| position: 'top', |
| labels: { |
| usePointStyle: true, |
| padding: 15 |
| } |
| }, |
| tooltip: { |
| callbacks: { |
| label: function(context) { |
| const label = context.dataset.label || ''; |
| const value = (context.parsed.y * 100).toFixed(2); |
| return `${label}: ${value}%`; |
| } |
| } |
| } |
| }, |
| elements: { |
| line: { |
| borderWidth: 1 |
| }, |
| point: { |
| radius: 0, |
| hoverRadius: 3 |
| } |
| } |
| } |
| }); |
| |
| |
| concentrationChart = new Chart(concentrationCtx, { |
| type: 'line', |
| data: { |
| datasets: [ |
| { |
| label: 'Effective Number of Positions', |
| borderColor: '#3f88e2', |
| backgroundColor: 'rgba(63, 136, 226, 0.1)', |
| borderWidth: 1.5, |
| pointRadius: 0, |
| pointHoverRadius: 3, |
| tension: 0.1, |
| data: [] |
| } |
| ] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| interaction: { |
| mode: 'index', |
| intersect: false |
| }, |
| scales: { |
| x: { |
| type: 'time', |
| time: { |
| unit: 'month', |
| displayFormats: { |
| month: 'MMM yyyy' |
| } |
| }, |
| title: { |
| display: true, |
| text: 'Date' |
| } |
| }, |
| y: { |
| title: { |
| display: true, |
| text: 'ENP' |
| }, |
| beginAtZero: false |
| } |
| }, |
| plugins: { |
| legend: { |
| position: 'top', |
| labels: { |
| usePointStyle: true, |
| padding: 15 |
| } |
| }, |
| tooltip: { |
| callbacks: { |
| label: function(context) { |
| const label = context.dataset.label || ''; |
| const value = context.parsed.y.toFixed(2); |
| return `${label}: ${value}`; |
| } |
| } |
| }, |
| annotation: { |
| annotations: [] |
| } |
| }, |
| elements: { |
| line: { |
| borderWidth: 1.5 |
| }, |
| point: { |
| radius: 0, |
| hoverRadius: 3 |
| } |
| } |
| } |
| }); |
| } |
|
|
| |
| async function runOptimization() { |
| |
| const startDate = document.getElementById('startDate').value; |
| const endDate = document.getElementById('endDate').value; |
| const learningRate = document.getElementById('learningRate').value; |
| const windowSize = document.getElementById('windowSize').value; |
| const alphaSortino = document.getElementById('alphaSortino').value; |
| const alphaMaxDrawdown = document.getElementById('alphaMaxDrawdown').value; |
| const alphaTurnover = document.getElementById('alphaTurnover').value; |
| const alphaConcentration = document.getElementById('alphaConcentration').value; |
| const enpMin = document.getElementById('enpMin').value; |
| const enpMax = document.getElementById('enpMax').value; |
| |
| |
| const tickers = Array.from(selectedTickers).join(','); |
| |
| |
| const loadingOverlay = document.getElementById('loadingOverlay'); |
| loadingOverlay.style.display = 'flex'; |
| |
| |
| const statsRow = document.querySelector('.stats-row'); |
| const originalStatsContent = statsRow ? statsRow.innerHTML : ''; |
| |
| |
| const requestData = { |
| start_date: startDate, |
| end_date: endDate, |
| learning_rate: parseFloat(learningRate), |
| window_size: parseInt(windowSize), |
| alpha_sortino: parseFloat(alphaSortino), |
| alpha_max_drawdown: parseFloat(alphaMaxDrawdown), |
| alpha_turnover: parseFloat(alphaTurnover), |
| alpha_concentration: parseFloat(alphaConcentration), |
| enp_min: parseFloat(enpMin), |
| enp_max: parseFloat(enpMax), |
| tickers: tickers |
| }; |
| |
| try { |
| |
| const response = await fetch('/api/run_optimization', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(requestData) |
| }); |
| |
| |
| if (!response.ok) { |
| throw new Error(`HTTP error! Status: ${response.status}`); |
| } |
| |
| |
| const data = await response.json(); |
| |
| |
| loadingOverlay.style.display = 'none'; |
| |
| |
| if (data.error) { |
| alert(`Optimization failed: ${data.error}`); |
| console.error('Optimization error:', data.error); |
| return; |
| } |
| |
| |
| updateCharts(data.cumulative_returns, data.weights, data.concentration, enpMin, enpMax); |
| |
| |
| updateMetrics(data.metrics); |
| |
| } catch (error) { |
| console.error('Error during optimization:', error); |
| |
| |
| loadingOverlay.style.display = 'none'; |
| |
| |
| if (statsRow && originalStatsContent) { |
| statsRow.innerHTML = originalStatsContent; |
| } |
| |
| |
| alert(`Optimization failed: ${error.message}. Check the console for more details.`); |
| |
| |
| if (confirm('Would you like to see a demo simulation instead?')) { |
| runFakeSimulation(); |
| } |
| } |
| } |
|
|
| |
| function updateCharts(returnsData, weightsData, concentrationData, enpMin, enpMax) { |
| if (!returnsData || !returnChart) { |
| console.error('Returns data or chart not available'); |
| return; |
| } |
| |
| |
| const ogdData = returnsData.ogd.filter(d => d.value !== null); |
| const equalWeightData = returnsData.equal_weight.filter(d => d.value !== null); |
| const randomData = returnsData.random.filter(d => d.value !== null); |
| |
| |
| returnChart.data.datasets[0].data = ogdData.map(d => ({ x: d.date, y: d.value })); |
| returnChart.data.datasets[1].data = equalWeightData.map(d => ({ x: d.date, y: d.value })); |
| returnChart.data.datasets[2].data = randomData.map(d => ({ x: d.date, y: d.value })); |
| returnChart.update(); |
| |
| |
| if (weightsData && weightsData.length > 0 && weightChart) { |
| |
| const allTickers = new Set(); |
| weightsData.forEach(day => { |
| Object.keys(day.weights).forEach(ticker => allTickers.add(ticker)); |
| }); |
| |
| |
| const datasets = Array.from(allTickers).map(ticker => { |
| |
| const color = getColor(ticker); |
| |
| |
| const data = weightsData.map(day => ({ |
| x: day.date, |
| y: day.weights[ticker] || 0 |
| })); |
| |
| |
| const validData = data.filter(d => d.y !== null); |
| |
| return { |
| label: ticker, |
| data: validData, |
| backgroundColor: color.replace('0.7', '0.2'), |
| borderColor: color, |
| fill: true, |
| tension: 0.1, |
| borderWidth: 1, |
| pointRadius: 0, |
| pointHoverRadius: 3 |
| }; |
| }); |
| |
| |
| weightChart.data.datasets = datasets; |
| weightChart.update(); |
| } |
| |
| |
| if (concentrationData && concentrationData.enp && concentrationData.enp.length > 0 && concentrationChart) { |
| |
| const enpData = concentrationData.enp.filter(d => d.value !== null); |
| |
| |
| concentrationChart.data.datasets[0].data = enpData.map(d => ({ x: d.date, y: d.value })); |
| concentrationChart.data.datasets[0].borderWidth = 1.5; |
| concentrationChart.data.datasets[0].pointRadius = 0; |
| concentrationChart.data.datasets[0].pointHoverRadius = 3; |
| |
| |
| if (concentrationChart.options.plugins.annotation.annotations.length === 0) { |
| concentrationChart.options.plugins.annotation.annotations.push({ |
| type: 'line', |
| borderColor: 'rgba(244, 67, 54, 0.5)', |
| borderWidth: 1, |
| borderDash: [6, 6], |
| label: { |
| enabled: true, |
| content: 'Min Target', |
| position: 'start' |
| }, |
| scaleID: 'y', |
| value: enpMin || 5.0 |
| }); |
| |
| concentrationChart.options.plugins.annotation.annotations.push({ |
| type: 'line', |
| borderColor: 'rgba(76, 175, 80, 0.5)', |
| borderWidth: 1, |
| borderDash: [6, 6], |
| label: { |
| enabled: true, |
| content: 'Max Target', |
| position: 'start' |
| }, |
| scaleID: 'y', |
| value: enpMax || 20.0 |
| }); |
| } else { |
| |
| concentrationChart.options.plugins.annotation.annotations[0].value = enpMin || 5.0; |
| concentrationChart.options.plugins.annotation.annotations[1].value = enpMax || 20.0; |
| } |
| |
| concentrationChart.update(); |
| } |
| } |
|
|
| |
| function updateMetrics(metrics) { |
| |
| const formatValue = (value, format = 'decimal') => { |
| if (value === undefined || value === null) return '-'; |
| |
| if (format === 'percent') { |
| |
| if (format === 'percent' && value > 1) { |
| |
| return ((value - 1) * 100).toFixed(2) + '%'; |
| } |
| return (value * 100).toFixed(2) + '%'; |
| } else if (format === 'decimal') { |
| return value.toFixed(2); |
| } |
| return value; |
| }; |
|
|
| |
| if (!metrics) { |
| console.error('No metrics data provided to updateMetrics'); |
| metrics = { |
| ogd: { sharpe: 0, max_drawdown: 0, cumulative_return: 1, capm_alpha: 0, ff3_alpha: 0 }, |
| equal_weight: { sharpe: 0, max_drawdown: 0, cumulative_return: 1, capm_alpha: 0, ff3_alpha: 0 }, |
| random: { sharpe: 0, max_drawdown: 0, cumulative_return: 1, capm_alpha: 0, ff3_alpha: 0 } |
| }; |
| } |
| |
| |
| if (!metrics.ogd) metrics.ogd = {}; |
| if (!metrics.equal_weight) metrics.equal_weight = {}; |
| if (!metrics.random) metrics.random = {}; |
|
|
| const statsRow = document.querySelector('.stats-row'); |
| if (!statsRow) { |
| console.error('Stats row element not found'); |
| return; |
| } |
| |
| |
| statsRow.innerHTML = ''; |
| |
| |
| const strategies = [ |
| { id: 'ogd', name: 'OGD Portfolio', class: 'ogd-strategy' }, |
| { id: 'equal_weight', name: 'Equal Weight', class: 'equal-weight-strategy' }, |
| { id: 'random', name: 'Random Portfolio', class: 'random-strategy' } |
| ]; |
| |
| |
| const metricTypes = [ |
| { id: 'sharpe', name: 'Sharpe Ratio', format: 'decimal' }, |
| { id: 'max_drawdown', name: 'Max Drawdown', format: 'percent' }, |
| { id: 'cumulative_return', name: 'Return', format: 'percent' }, |
| { id: 'capm_alpha', name: 'CAPM Alpha', format: 'percent' }, |
| { id: 'ff3_alpha', name: 'FF3 Alpha', format: 'percent' } |
| ]; |
| |
| |
| |
| const emptyHeader = document.createElement('div'); |
| emptyHeader.className = 'stat-card metric-label strategy-header'; |
| emptyHeader.innerHTML = '<div class="stat-value">Metrics</div>'; |
| statsRow.appendChild(emptyHeader); |
| |
| |
| strategies.forEach(strategy => { |
| const strategyHeader = document.createElement('div'); |
| strategyHeader.className = `stat-card strategy-header ${strategy.class}`; |
| strategyHeader.innerHTML = `<div class="stat-value">${strategy.name}</div>`; |
| statsRow.appendChild(strategyHeader); |
| }); |
| |
| |
| metricTypes.forEach(metric => { |
| |
| const metricLabel = document.createElement('div'); |
| metricLabel.className = 'stat-card metric-label'; |
| metricLabel.innerHTML = `<div class="stat-value">${metric.name}</div>`; |
| statsRow.appendChild(metricLabel); |
| |
| |
| strategies.forEach(strategy => { |
| |
| const value = metrics[strategy.id][metric.id]; |
| let formattedValue = '-'; |
| |
| |
| if (value !== null && value !== undefined) { |
| |
| if (metric.id === 'cumulative_return') { |
| formattedValue = ((value - 1) * 100).toFixed(2) + '%'; |
| } else if (metric.format === 'percent') { |
| formattedValue = (value * 100).toFixed(2) + '%'; |
| } else { |
| formattedValue = formatValue(value, metric.format); |
| } |
| } |
| |
| const metricCell = document.createElement('div'); |
| metricCell.className = `stat-card metric-row ${strategy.class}`; |
| metricCell.setAttribute('data-metric', metric.name); |
| metricCell.innerHTML = `<div class="stat-value">${formattedValue}</div>`; |
| statsRow.appendChild(metricCell); |
| }); |
| }); |
| |
| |
| console.log('Metrics grid created with', strategies.length * metricTypes.length, 'cells'); |
| |
| |
| try { |
| |
| const ogdSharpe = document.getElementById('ogdSharpeRatio'); |
| if (ogdSharpe) ogdSharpe.textContent = formatValue(metrics.ogd.sharpe); |
| |
| const ogdDrawdown = document.getElementById('ogdMaxDrawdown'); |
| if (ogdDrawdown) ogdDrawdown.textContent = formatValue(metrics.ogd.max_drawdown, 'percent'); |
| |
| const ogdReturn = document.getElementById('ogdReturn'); |
| if (ogdReturn && metrics.ogd.cumulative_return !== null) { |
| ogdReturn.textContent = ((metrics.ogd.cumulative_return - 1) * 100).toFixed(2) + '%'; |
| } else if (ogdReturn) { |
| ogdReturn.textContent = '-'; |
| } |
| |
| const ewSharpe = document.getElementById('ewSharpeRatio'); |
| if (ewSharpe) ewSharpe.textContent = formatValue(metrics.equal_weight.sharpe); |
| |
| const ewDrawdown = document.getElementById('ewMaxDrawdown'); |
| if (ewDrawdown) ewDrawdown.textContent = formatValue(metrics.equal_weight.max_drawdown, 'percent'); |
| |
| const ewReturn = document.getElementById('ewReturn'); |
| if (ewReturn && metrics.equal_weight.cumulative_return !== null) { |
| ewReturn.textContent = ((metrics.equal_weight.cumulative_return - 1) * 100).toFixed(2) + '%'; |
| } else if (ewReturn) { |
| ewReturn.textContent = '-'; |
| } |
| |
| const randomSharpe = document.getElementById('randomSharpeRatio'); |
| if (randomSharpe) randomSharpe.textContent = formatValue(metrics.random.sharpe); |
| |
| const randomDrawdown = document.getElementById('randomMaxDrawdown'); |
| if (randomDrawdown) randomDrawdown.textContent = formatValue(metrics.random.max_drawdown, 'percent'); |
| |
| const randomReturn = document.getElementById('randomReturn'); |
| if (randomReturn && metrics.random.cumulative_return !== null) { |
| randomReturn.textContent = ((metrics.random.cumulative_return - 1) * 100).toFixed(2) + '%'; |
| } else if (randomReturn) { |
| randomReturn.textContent = '-'; |
| } |
| |
| |
| if (metrics.ogd.capm_alpha !== undefined) { |
| const ogdCAPM = document.getElementById('ogdCAPMAlpha'); |
| if (ogdCAPM) ogdCAPM.textContent = formatValue(metrics.ogd.capm_alpha, 'percent'); |
| |
| const ogdFF3 = document.getElementById('ogdFF3Alpha'); |
| if (ogdFF3) ogdFF3.textContent = formatValue(metrics.ogd.ff3_alpha, 'percent'); |
| |
| const ewCAPM = document.getElementById('ewCAPMAlpha'); |
| if (ewCAPM) ewCAPM.textContent = formatValue(metrics.equal_weight.capm_alpha, 'percent'); |
| |
| const ewFF3 = document.getElementById('ewFF3Alpha'); |
| if (ewFF3) ewFF3.textContent = formatValue(metrics.equal_weight.ff3_alpha, 'percent'); |
| |
| const randomCAPM = document.getElementById('randomCAPMAlpha'); |
| if (randomCAPM) randomCAPM.textContent = formatValue(metrics.random.capm_alpha, 'percent'); |
| |
| const randomFF3 = document.getElementById('randomFF3Alpha'); |
| if (randomFF3) randomFF3.textContent = formatValue(metrics.random.ff3_alpha, 'percent'); |
| } |
| } catch (e) { |
| console.warn('Error updating legacy elements:', e); |
| |
| } |
| } |
|
|
| |
| function runFakeSimulation() { |
| const loadingOverlay = document.getElementById('loadingOverlay'); |
| if (loadingOverlay.style.display === 'none') return; |
| |
| |
| const enpMin = parseFloat(document.getElementById('enpMin').value) || 5.0; |
| const enpMax = parseFloat(document.getElementById('enpMax').value) || 20.0; |
| |
| |
| const dates = []; |
| const today = new Date(); |
| for (let i = 365; i >= 0; i--) { |
| const date = new Date(today); |
| date.setDate(today.getDate() - i); |
| dates.push(date.toISOString().split('T')[0]); |
| } |
| |
| |
| const generateFakeData = (volatility, bias = 0) => { |
| let value = 1.0; |
| return dates.map(date => { |
| value *= (1 + (Math.random() - 0.45 + bias) * volatility); |
| return { date, value }; |
| }); |
| }; |
| |
| const fakeReturns = { |
| ogd: generateFakeData(0.015, 0.02), |
| equal_weight: generateFakeData(0.01, 0.01), |
| random: generateFakeData(0.02, 0) |
| }; |
| |
| |
| const fakeWeights = []; |
| const fakeTickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA', 'NVDA', 'JPM', 'JNJ', 'V']; |
| |
| dates.forEach(date => { |
| const weights = {}; |
| let totalWeight = 0; |
| |
| fakeTickers.forEach(ticker => { |
| |
| weights[ticker] = Math.random(); |
| totalWeight += weights[ticker]; |
| }); |
| |
| |
| Object.keys(weights).forEach(ticker => { |
| weights[ticker] = weights[ticker] / totalWeight; |
| }); |
| |
| fakeWeights.push({ date, weights }); |
| }); |
| |
| |
| const fakeConcentration = { |
| enp: dates.map(date => { |
| |
| const range = enpMax - enpMin; |
| const center = enpMin + range / 2; |
| const variation = range / 3; |
| const value = center + (Math.random() * 2 - 1) * variation; |
| return { date, value }; |
| }), |
| hhi: dates.map(date => { |
| return { date, value: Math.random() * 0.3 + 0.1 }; |
| }) |
| }; |
| |
| |
| updateCharts(fakeReturns, fakeWeights, fakeConcentration, enpMin, enpMax); |
| |
| |
| updateMetrics({ |
| ogd: { |
| sharpe: 1.2 + Math.random() * 0.5, |
| max_drawdown: 0.12 + Math.random() * 0.1, |
| cumulative_return: 1.3 + Math.random() * 0.5, |
| capm_alpha: 2.5 + Math.random() * 1.0, |
| ff3_alpha: 1.8 + Math.random() * 0.8 |
| }, |
| equal_weight: { |
| sharpe: 0.9 + Math.random() * 0.3, |
| max_drawdown: 0.15 + Math.random() * 0.1, |
| cumulative_return: 1.2 + Math.random() * 0.3, |
| capm_alpha: 1.2 + Math.random() * 0.5, |
| ff3_alpha: 0.8 + Math.random() * 0.4 |
| }, |
| random: { |
| sharpe: 0.6 + Math.random() * 0.3, |
| max_drawdown: 0.2 + Math.random() * 0.15, |
| cumulative_return: 1.1 + Math.random() * 0.2, |
| capm_alpha: 0.4 + Math.random() * 0.6, |
| ff3_alpha: 0.2 + Math.random() * 0.4 |
| } |
| }); |
| } |
|
|
| |
| function getColor(ticker) { |
| |
| if (colorMap[ticker]) { |
| return colorMap[ticker]; |
| } |
| |
| |
| const predefinedColors = { |
| 'AAPL': 'rgba(63, 136, 226, 0.7)', |
| 'MSFT': 'rgba(76, 175, 80, 0.7)', |
| 'GOOGL': 'rgba(226, 181, 63, 0.7)', |
| 'AMZN': 'rgba(226, 77, 63, 0.7)', |
| 'META': 'rgba(156, 39, 176, 0.7)', |
| 'TSLA': 'rgba(0, 188, 212, 0.7)', |
| 'NVDA': 'rgba(255, 235, 59, 0.7)', |
| 'JPM': 'rgba(121, 85, 72, 0.7)', |
| 'V': 'rgba(96, 125, 139, 0.7)', |
| 'JNJ': 'rgba(233, 30, 99, 0.7)' |
| }; |
| |
| if (predefinedColors[ticker]) { |
| colorMap[ticker] = predefinedColors[ticker]; |
| return colorMap[ticker]; |
| } |
| |
| |
| let hash = 0; |
| for (let i = 0; i < ticker.length; i++) { |
| hash = ticker.charCodeAt(i) + ((hash << 5) - hash); |
| } |
| |
| |
| const r = (hash & 0xFF) % 200 + 55; |
| const g = ((hash >> 8) & 0xFF) % 200 + 55; |
| const b = ((hash >> 16) & 0xFF) % 200 + 55; |
| |
| |
| const color = `rgba(${r}, ${g}, ${b}, 0.7)`; |
| |
| |
| colorMap[ticker] = color; |
| return color; |
| } |
|
|
| |
| async function initialize() { |
| try { |
| |
| setActiveNavLink(); |
| |
| |
| initializeCharts(); |
| |
| |
| document.getElementById('startDate').value = '2007-01-01'; |
| document.getElementById('endDate').value = '2025-04-01'; |
| |
| |
| sectorData = await fetchTickersBySector(); |
| populateStockList(sectorData); |
| |
| |
| document.getElementById('runButton').addEventListener('click', runOptimization); |
| |
| |
| updateMetrics({ |
| ogd: { |
| sharpe: 1.2, |
| max_drawdown: 0.12, |
| cumulative_return: 1.3, |
| capm_alpha: 2.5, |
| ff3_alpha: 1.8 |
| }, |
| equal_weight: { |
| sharpe: 0.9, |
| max_drawdown: 0.15, |
| cumulative_return: 1.2, |
| capm_alpha: 1.2, |
| ff3_alpha: 0.8 |
| }, |
| random: { |
| sharpe: 0.6, |
| max_drawdown: 0.2, |
| cumulative_return: 1.1, |
| capm_alpha: 0.4, |
| ff3_alpha: 0.2 |
| } |
| }); |
| |
| |
| runFakeSimulation(); |
| |
| } catch (error) { |
| console.error('Error initializing application:', error); |
| } |
| } |
|
|
| |
| document.addEventListener('DOMContentLoaded', initialize); |