| | |
| | 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); |