diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.jsx b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.jsx new file mode 100644 index 0000000..9d65acb --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.jsx @@ -0,0 +1,1020 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { + Play, + Save, + Download, + Upload, + Settings, + BarChart3, + TrendingUp, + TrendingDown, + Clock, + DollarSign, + Target, + Activity, + Shield, + RefreshCw, + Plus, + Trash2, + Copy, + History, + BookOpen, + Zap, + Cpu, + LineChart, + PieChart +} from 'lucide-react'; + +// Chart.js components +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + BarElement, + Title, + Tooltip, + Legend, + Filler +} from 'chart.js'; +import { Line, Bar } from 'react-chartjs-2'; + +// Register ChartJS components +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + BarElement, + Title, + Tooltip, + Legend, + Filler +); + +// Mock hook with enhanced data +const useQuantData = () => { + const [loading, setLoading] = useState(false); + const [progress, setProgress] = useState(0); + const [backtestResults, setBacktestResults] = useState(null); + + const runBacktest = async (config) => { + setLoading(true); + setProgress(0); + + // Simulate progress updates + const progressInterval = setInterval(() => { + setProgress(prev => { + if (prev >= 95) { + clearInterval(progressInterval); + return 95; + } + return prev + 5; + }); + }, 150); + + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 3000)); + + clearInterval(progressInterval); + setProgress(100); + + // Enhanced mock results with realistic data + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const years = [2020, 2021, 2022, 2023]; + + const equityData = []; + let currentValue = config.initialCapital; + + for (let year of years) { + for (let month of months) { + // Realistic market simulation with trends and noise + const monthlyReturn = (Math.random() * 0.08 - 0.02) + 0.005; // -2% to +6% with slight positive bias + currentValue = currentValue * (1 + monthlyReturn); + equityData.push({ + date: `${month} ${year}`, + value: Math.round(currentValue) + }); + } + } + + const drawdownData = equityData.map((point, index) => { + const peak = Math.max(...equityData.slice(0, index + 1).map(p => p.value)); + const drawdown = ((point.value - peak) / peak) * 100; + return { date: point.date, value: drawdown }; + }); + + // Generate realistic trades + const trades = Array.from({ length: 25 }, (_, i) => { + const symbols = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'JPM', 'JNJ', 'V']; + const action = Math.random() > 0.4 ? 'BUY' : 'SELL'; + const basePrices = { + 'AAPL': 150, 'MSFT': 300, 'GOOGL': 2800, 'AMZN': 3300, + 'TSLA': 200, 'META': 350, 'NVDA': 800, 'JPM': 170, 'JNJ': 160, 'V': 230 + }; + + const symbol = symbols[Math.floor(Math.random() * symbols.length)]; + const price = basePrices[symbol] * (0.9 + Math.random() * 0.2); + + return { + id: i, + symbol, + action, + quantity: Math.floor(Math.random() * 100) + 10, + price: Math.round(price * 100) / 100, + timestamp: new Date(2020 + Math.floor(i/6), (i % 12), (i % 28) + 1), + pnl: (Math.random() - 0.3) * 5000 + }; + }); + + setBacktestResults({ + metrics: { + totalReturn: 0.2345, + annualizedReturn: 0.156, + volatility: 0.182, + sharpeRatio: 1.234, + maxDrawdown: -0.1234, + winRate: 0.645, + turnover: 0.89, + alpha: 0.0234, + beta: 1.12, + sortinoRatio: 1.89, + calmarRatio: 1.45, + informationRatio: 0.78 + }, + equityCurve: equityData, + drawdownCurve: drawdownData, + trades: trades.sort((a, b) => b.timestamp - a.timestamp), + performance: { + monthlyReturns: Array.from({ length: 36 }, () => (Math.random() - 0.5) * 0.1), + benchmarkReturns: Array.from({ length: 36 }, () => (Math.random() - 0.5) * 0.08) + } + }); + + setTimeout(() => setProgress(0), 1000); + setLoading(false); + }; + + return { loading, progress, runBacktest, backtestResults }; +}; + +export const BacktestStudio = () => { + const { loading, progress, runBacktest, backtestResults } = useQuantData(); + const [activeTab, setActiveTab] = useState('configuration'); + const [savedStrategies, setSavedStrategies] = useState([]); + const [strategyName, setStrategyName] = useState(''); + const [newSymbol, setNewSymbol] = useState(''); + + const [config, setConfig] = useState({ + strategyName: 'Momentum Strategy v1', + initialCapital: 100000, + startDate: '2020-01-01', + endDate: '2023-12-31', + rebalanceFrequency: 'monthly', + strategyType: 'momentum', + universe: 'large_cap', + riskModel: 'min_variance', + transactionCosts: 0.001, + maxPositionSize: 0.1, + stopLoss: 0.05, + takeProfit: 0.15, + symbols: ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'JPM', 'JNJ', 'V'] + }); + + // Chart options and data + const equityChartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + title: { + display: false, + }, + tooltip: { + mode: 'index', + intersect: false, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: 'rgba(255, 255, 255, 0.1)', + borderWidth: 1, + }, + }, + scales: { + x: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + }, + }, + y: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + callback: function(value) { + return '$' + value.toLocaleString(); + }, + }, + }, + }, + }; + + const drawdownChartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + }, + scales: { + x: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + }, + }, + y: { + grid: { + color: 'rgba(0, 0, 0, 0.1)', + }, + ticks: { + color: '#6B7280', + callback: function(value) { + return value + '%'; + }, + }, + }, + }, + }; + + const getEquityChartData = () => { + if (!backtestResults?.equityCurve) return { labels: [], datasets: [] }; + + return { + labels: backtestResults.equityCurve.map(point => point.date), + datasets: [ + { + label: 'Portfolio Value', + data: backtestResults.equityCurve.map(point => point.value), + borderColor: 'rgb(34, 197, 94)', + backgroundColor: 'rgba(34, 197, 94, 0.1)', + fill: true, + tension: 0.4, + }, + ], + }; + }; + + const getDrawdownChartData = () => { + if (!backtestResults?.drawdownCurve) return { labels: [], datasets: [] }; + + return { + labels: backtestResults.drawdownCurve.map(point => point.date), + datasets: [ + { + label: 'Drawdown', + data: backtestResults.drawdownCurve.map(point => point.value), + borderColor: 'rgb(239, 68, 68)', + backgroundColor: 'rgba(239, 68, 68, 0.1)', + fill: true, + tension: 0.4, + }, + ], + }; + }; + + const strategyTypes = [ + { value: 'momentum', label: 'Momentum', description: 'Follow trending assets', icon: TrendingUp }, + { value: 'mean_reversion', label: 'Mean Reversion', description: 'Bet on price normalization', icon: Activity }, + { value: 'factor', label: 'Factor Investing', description: 'Use quantitative factors', icon: Target }, + { value: 'ml', label: 'Machine Learning', description: 'AI-powered predictions', icon: Cpu } + ]; + + const universes = [ + { value: 'large_cap', label: 'Large Cap (S&P 500)' }, + { value: 'small_cap', label: 'Small Cap (Russell 2000)' }, + { value: 'technology', label: 'Technology Sector' }, + { value: 'all', label: 'All Available Assets' } + ]; + + const riskModels = [ + { value: 'min_variance', label: 'Minimum Variance' }, + { value: 'equal_weight', label: 'Equal Weight' }, + { value: 'risk_parity', label: 'Risk Parity' }, + { value: 'black_litterman', label: 'Black-Litterman' } + ]; + + const handleRunBacktest = () => { + runBacktest(config); + setActiveTab('results'); + }; + + const handleSaveStrategy = () => { + if (strategyName.trim()) { + const newStrategy = { + id: Date.now(), + name: strategyName, + config: { ...config }, + createdAt: new Date().toISOString() + }; + setSavedStrategies(prev => [newStrategy, ...prev]); + setStrategyName(''); + } + }; + + const handleLoadStrategy = (strategy) => { + setConfig(strategy.config); + setActiveTab('configuration'); + }; + + const addSymbol = () => { + if (newSymbol.trim()) { + const symbol = newSymbol.trim().toUpperCase(); + if (!config.symbols.includes(symbol)) { + setConfig({ + ...config, + symbols: [...config.symbols, symbol] + }); + } + setNewSymbol(''); + } + }; + + const removeSymbol = (index) => { + const newSymbols = config.symbols.filter((_, i) => i !== index); + setConfig({ ...config, symbols: newSymbols }); + }; + + const formatMetricValue = (value) => { + if (typeof value === 'number') { + if (Math.abs(value) > 10 || (value >= -1 && value <= 1 && Math.abs(value) < 0.01)) { + return value.toFixed(3); + } + return (value * 100).toFixed(2) + '%'; + } + return String(value); + }; + + const getMetrics = () => { + if (!backtestResults?.metrics) return []; + + const metrics = backtestResults.metrics; + return [ + { key: 'totalReturn', label: 'Total Return', value: metrics.totalReturn, icon: TrendingUp, trend: metrics.totalReturn >= 0 ? 'up' : 'down' }, + { key: 'annualizedReturn', label: 'Annualized Return', value: metrics.annualizedReturn, icon: DollarSign, trend: metrics.annualizedReturn >= 0 ? 'up' : 'down' }, + { key: 'volatility', label: 'Volatility', value: metrics.volatility, icon: Activity, trend: 'neutral' }, + { key: 'sharpeRatio', label: 'Sharpe Ratio', value: metrics.sharpeRatio, icon: Target, trend: metrics.sharpeRatio >= 1 ? 'up' : 'down' }, + { key: 'maxDrawdown', label: 'Max Drawdown', value: metrics.maxDrawdown, icon: TrendingDown, trend: 'down' }, + { key: 'winRate', label: 'Win Rate', value: metrics.winRate, icon: Target, trend: metrics.winRate >= 0.5 ? 'up' : 'down' }, + { key: 'alpha', label: 'Alpha', value: metrics.alpha, icon: TrendingUp, trend: metrics.alpha >= 0 ? 'up' : 'down' }, + { key: 'beta', label: 'Beta', value: metrics.beta, icon: Activity, trend: 'neutral' } + ]; + }; + + // Enhanced loading component with progress + if (loading) { + return ( +
+
+
+ + +
+

Running Backtest

+

Analyzing strategy performance across historical data

+ +
+
+
+

{progress}% Complete

+
+
+ ); + } + + return ( +
+ {/* Header Section */} +
+
+

+ Backtest Studio +

+

+ + Test, optimize, and validate your trading strategies with precision +

+
+
+ + + +
+
+ + {/* Tab Navigation */} +
+ {[ + { id: 'configuration', label: 'Configuration', icon: Settings }, + { id: 'results', label: 'Results', icon: BarChart3 }, + { id: 'saved', label: 'Saved Strategies', icon: Save } + ].map((tab) => { + const Icon = tab.icon; + return ( + + ); + })} +
+ + {/* Main Content */} +
+ {/* Configuration Panel */} + {activeTab === 'configuration' && ( + <> +
+
+
+
+

+ Strategy Configuration +

+

Define your trading strategy parameters

+
+
+ + +
+
+ +
+ {/* Basic Settings */} +
+
+

+ + Basic Settings +

+ +
+
+ + setConfig({ ...config, strategyName: e.target.value })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + placeholder="Enter strategy name" + /> +
+ +
+ + setConfig({ ...config, initialCapital: Number(e.target.value) })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+ +
+
+ + setConfig({ ...config, startDate: e.target.value })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+ + setConfig({ ...config, endDate: e.target.value })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+
+
+ + {/* Risk Management */} +
+

+ + Risk Management +

+ +
+
+ + setConfig({ ...config, transactionCosts: Number(e.target.value) })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+ +
+ + setConfig({ ...config, maxPositionSize: Number(e.target.value) / 100 })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+ +
+
+ + setConfig({ ...config, stopLoss: Number(e.target.value) / 100 })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+ + setConfig({ ...config, takeProfit: Number(e.target.value) / 100 })} + className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> +
+
+
+
+
+ + {/* Strategy Parameters & Asset Selection */} +
+
+

+ + Strategy Parameters +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + {/* Asset Selection */} +
+

+ + Asset Selection +

+ +
+ +
+
+ {config.symbols.map((symbol, index) => ( + + {symbol} + + + ))} +
+
+ setNewSymbol(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && addSymbol()} + placeholder="Add symbol (e.g., AAPL)" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200" + /> + +
+
+
+
+
+
+ + {/* Run Button */} +
+ +
+
+
+ + {/* Strategy Templates */} +
+
+

+ + Quick Templates +

+
+ {[ + { name: 'Momentum Strategy', type: 'momentum', description: 'Follow trending assets with momentum signals', color: 'blue' }, + { name: 'Mean Reversion', type: 'mean_reversion', description: 'Bet on price normalization in oversold assets', color: 'green' }, + { name: 'Quality Factor', type: 'factor', description: 'Invest in high-quality companies with strong fundamentals', color: 'purple' } + ].map((template, index) => ( + + ))} +
+
+
+ + )} + + {/* Results Panel */} + {activeTab === 'results' && backtestResults && ( +
+ {/* Key Metrics */} +
+

+ Performance Summary +

+
+ {getMetrics().map((metric) => { + const Icon = metric.icon; + return ( +
+ +
{metric.label}
+
+ {formatMetricValue(metric.value)} +
+
+ ); + })} +
+
+ + {/* Charts and Detailed Analysis */} +
+ {/* Equity Curve */} +
+

Equity Curve

+
+ +
+
+ + {/* Drawdown Chart */} +
+

Drawdown Analysis

+
+ +
+
+ + {/* Recent Trades */} +
+
+

Recent Trades

+ {backtestResults.trades.length} trades +
+
+ {backtestResults.trades.slice(0, 10).map((trade) => ( +
+
+
+ {trade.action === 'BUY' ? 'B' : 'S'} +
+
+
{trade.symbol}
+
+ {trade.quantity} shares @ ${trade.price.toFixed(2)} +
+
+
+
+
= 0 ? 'text-green-600' : 'text-red-600' + }`}> + {trade.pnl >= 0 ? '+' : ''}${trade.pnl.toFixed(2)} +
+
+ {trade.timestamp.toLocaleDateString()} +
+
+
+ ))} +
+
+ + {/* Risk Analysis */} +
+

Risk Metrics

+
+ {[ + { label: 'Value at Risk (95%)', value: '-5.2%', color: 'red' }, + { label: 'Conditional VaR', value: '-7.8%', color: 'red' }, + { label: 'Tail Ratio', value: '0.89', color: 'green' }, + { label: 'Skewness', value: '-0.23', color: 'yellow' }, + { label: 'Kurtosis', value: '3.45', color: 'yellow' }, + { label: 'Information Ratio', value: '0.78', color: 'green' } + ].map((metric, index) => ( +
+ {metric.label} + + {metric.value} + +
+ ))} +
+
+
+
+ )} + + {/* Saved Strategies */} + {activeTab === 'saved' && ( +
+
+
+
+

+ Saved Strategies +

+

Manage your strategy configurations

+
+
+ setStrategyName(e.target.value)} + placeholder="Strategy name" + className="px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 bg-white/80" + /> + +
+
+ + {savedStrategies.length === 0 ? ( +
+ +

No saved strategies yet

+

Save your current configuration to get started

+
+ ) : ( +
+ {savedStrategies.map((strategy) => ( +
+
+

{strategy.name}

+ +
+
+
+ Capital: + ${strategy.config.initialCapital.toLocaleString()} +
+
+ Period: + {strategy.config.startDate} to {strategy.config.endDate} +
+
+ Assets: + {strategy.config.symbols.length} symbols +
+
+
+ Created: {new Date(strategy.createdAt).toLocaleDateString()} +
+
+
+
+ ))} +
+ )} +
+
+ )} + + {/* Empty State for Results */} + {activeTab === 'results' && !backtestResults && ( +
+
+
+ +

No Results Yet

+

+ Configure and run a backtest to see detailed performance results and analytics +

+ +
+
+
+ )} +
+
+ ); +}; + +export default BacktestStudio; \ No newline at end of file