diff --git a/README.md b/README.md index 1d48e1d..a6cdb44 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,11 @@ python -m quant_research_starter.cli compute-factors -d data_sample/sample_price # run a backtest python -m quant_research_starter.cli backtest -d data_sample/sample_prices.csv -s output/factors.csv -o output/backtest_results.json -# optional: start the Streamlit dashboard +# DISCLAIMER: OLD VERSION +# optional: start the Streamlit dashboard, if on main stream streamlit run src/quant_research_starter/dashboard/streamlit_app.py +# NEW VERSION: if streamlit is in legacy folder +streamlit run legacy/streamlit/streamlit_app.py ``` --- diff --git a/src/quant_research_starter/frontend/cauweb/package.json b/src/quant_research_starter/frontend/cauweb/package.json new file mode 100644 index 0000000..9f5cbe5 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/package.json @@ -0,0 +1,32 @@ +{ + "name": "@qrs/cauweb", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src --ext .ts,.tsx" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.17", + "chart.js": "^4.5.1", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-chartjs-2": "^5.3.1", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0" + }, + "devDependencies": { + "@tailwindcss/cli": "^4.1.17", + "@tailwindcss/postcss": "^4.1.17", + "@types/react": "^18.3.26", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.1.0", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.17", + "typescript": "^5.9.3", + "vite": "^5.0.0" + } +} diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.js b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.js new file mode 100644 index 0000000..7859640 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.js @@ -0,0 +1,47 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { useState } from 'react'; +import { Play } from 'lucide-react'; +import { useQuantData } from '../hooks/useQuantData'; +export const BacktestStudio = () => { + const { loading, runBacktest, backtestResults } = useQuantData(); + const [config, setConfig] = useState({ + initialCapital: 100000, + startDate: '2020-01-01', + endDate: '2023-01-01', + rebalanceFrequency: 'monthly', + symbols: ['AAPL', 'MSFT', 'GOOGL', 'AMZN'] + }); + const handleRunBacktest = () => { + runBacktest(config); + }; + // Format metric value for display with proper typing + const formatMetricValue = (value) => { + if (typeof value === 'number') { + // For ratios (like sharpe), don't add percentage + if (value > 1 || value < -1) { + return value.toFixed(3); + } + return (value * 100).toFixed(2) + '%'; + } + return String(value); + }; + // Safely get metrics with proper typing + const getMetrics = () => { + if (!backtestResults?.metrics) + return []; + const metrics = backtestResults.metrics; + return [ + ['Total Return', formatMetricValue(metrics.totalReturn)], + ['Annualized Return', formatMetricValue(metrics.annualizedReturn)], + ['Volatility', formatMetricValue(metrics.volatility)], + ['Sharpe Ratio', formatMetricValue(metrics.sharpeRatio)], + ['Max Drawdown', formatMetricValue(metrics.maxDrawdown)], + ['Win Rate', formatMetricValue(metrics.winRate)], + ['Turnover', formatMetricValue(metrics.turnover)] + ]; + }; + return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Backtest Studio" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Test and optimize your trading strategies" })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-8", children: [_jsx("div", { className: "lg:col-span-1", children: _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Strategy Configuration" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Initial Capital ($)" }), _jsx("input", { type: "number", value: config.initialCapital, onChange: (e) => setConfig({ ...config, initialCapital: Number(e.target.value) }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Start Date" }), _jsx("input", { type: "date", value: config.startDate, onChange: (e) => setConfig({ ...config, startDate: e.target.value }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "End Date" }), _jsx("input", { type: "date", value: config.endDate, onChange: (e) => setConfig({ ...config, endDate: e.target.value }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Rebalance Frequency" }), _jsxs("select", { value: config.rebalanceFrequency, onChange: (e) => setConfig({ + ...config, + rebalanceFrequency: e.target.value + }), className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent", children: [_jsx("option", { value: "daily", children: "Daily" }), _jsx("option", { value: "weekly", children: "Weekly" }), _jsx("option", { value: "monthly", children: "Monthly" })] })] }), _jsxs("button", { onClick: handleRunBacktest, disabled: loading, className: "w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center space-x-2", children: [_jsx(Play, { className: "w-4 h-4" }), _jsx("span", { children: loading ? 'Running...' : 'Run Backtest' })] })] })] }) }), _jsx("div", { className: "lg:col-span-2", children: _jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-4", children: "Backtest Results" }), loading ? (_jsxs("div", { className: "text-center py-12", children: [_jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4" }), _jsx("p", { className: "text-gray-600", children: "Running backtest analysis..." })] })) : backtestResults ? (_jsxs("div", { className: "space-y-6", children: [_jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: getMetrics().map(([key, value]) => (_jsxs("div", { className: "text-center p-4 bg-gray-50 rounded-lg", children: [_jsx("div", { className: "text-sm text-gray-600 capitalize", children: key }), _jsx("div", { className: "text-xl font-bold text-gray-900 mt-1", children: value })] }, key))) }), _jsxs("div", { className: "border-t pt-6", children: [_jsx("h4", { className: "font-semibold text-gray-900 mb-3", children: "Performance Chart" }), _jsx("div", { className: "h-64 bg-gray-100 rounded-lg flex items-center justify-center text-gray-500", children: "Performance chart will be displayed here" })] })] })) : (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Play, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), _jsx("p", { children: "Configure and run a backtest to see results" })] }))] }) })] })] })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.tsx b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.tsx new file mode 100644 index 0000000..a72e675 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/BacktestStudio.tsx @@ -0,0 +1,457 @@ +import React, { useState } from 'react'; +import { Play, TrendingUp, TrendingDown, Target, DollarSign, Calendar, BarChart3, Activity } from 'lucide-react'; +import { PerformanceChart } from '../components/PerformanceChart'; + +interface BacktestMetrics { + totalReturn: number; + annualizedReturn: number; + volatility: number; + sharpeRatio: number; + maxDrawdown: number; + winRate: number; + turnover: number; + profitFactor: number; + sortinoRatio: number; + calmarRatio: number; +} + +interface Trade { + symbol: string; + quantity: number; + price: number; + timestamp: string; + side: 'BUY' | 'SELL'; + pnl: number; +} + +export const BacktestStudio: React.FC = () => { + const [loading, setLoading] = useState(false); + const [backtestResults, setBacktestResults] = useState<{ + metrics: BacktestMetrics; + trades: Trade[]; + portfolioHistory: { date: string; value: number }[]; + benchmarkHistory: { date: string; value: number }[]; + } | null>(null); + + const [config, setConfig] = useState({ + initialCapital: 100000, + startDate: '2020-01-01', + endDate: '2023-12-31', + rebalanceFrequency: 'monthly', + strategy: 'momentum', + symbols: ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NFLX', 'NVDA'] + }); + + const handleRunBacktest = async () => { + setLoading(true); + + // Simulate API call with realistic delay + await new Promise(resolve => setTimeout(resolve, 2500)); + + // Generate mock data + const mockResults = { + metrics: { + totalReturn: 0.2345, + annualizedReturn: 0.0876, + volatility: 0.1567, + sharpeRatio: 1.234, + maxDrawdown: 0.1234, + winRate: 0.645, + turnover: 2.34, + profitFactor: 1.89, + sortinoRatio: 1.567, + calmarRatio: 0.707 + }, + trades: generateMockTrades(), + portfolioHistory: generatePortfolioHistory(), + benchmarkHistory: generateBenchmarkHistory() + }; + + setBacktestResults(mockResults); + setLoading(false); + }; + + const getMetrics = () => { + if (!backtestResults?.metrics) return []; + + const metrics = backtestResults.metrics; + return [ + { + key: 'Total Return', + value: `${(metrics.totalReturn * 100).toFixed(2)}%`, + change: `${(metrics.annualizedReturn * 100).toFixed(2)}% annualized`, + icon: TrendingUp, + trend: metrics.totalReturn > 0 ? 'positive' : 'negative', + description: 'Overall strategy performance' + }, + { + key: 'Sharpe Ratio', + value: metrics.sharpeRatio.toFixed(3), + change: 'Risk-adjusted returns', + icon: Activity, + trend: metrics.sharpeRatio > 1 ? 'positive' : 'negative', + description: 'Higher is better' + }, + { + key: 'Max Drawdown', + value: `${(metrics.maxDrawdown * 100).toFixed(2)}%`, + change: 'Worst peak-to-trough decline', + icon: TrendingDown, + trend: metrics.maxDrawdown < 0.1 ? 'positive' : 'negative', + description: 'Lower is better' + }, + { + key: 'Win Rate', + value: `${(metrics.winRate * 100).toFixed(2)}%`, + change: 'Successful trade percentage', + icon: Target, + trend: metrics.winRate > 0.5 ? 'positive' : 'negative', + description: 'Trade success frequency' + }, + { + key: 'Volatility', + value: `${(metrics.volatility * 100).toFixed(2)}%`, + change: 'Portfolio risk measure', + icon: BarChart3, + trend: metrics.volatility < 0.15 ? 'positive' : 'negative', + description: 'Annualized standard deviation' + }, + { + key: 'Profit Factor', + value: metrics.profitFactor.toFixed(2), + change: 'Gross profit vs gross loss', + icon: DollarSign, + trend: metrics.profitFactor > 1.5 ? 'positive' : 'negative', + description: 'Higher is better' + } + ]; + }; + + const getRecentTrades = () => { + if (!backtestResults?.trades) return []; + return backtestResults.trades.slice(0, 5); // Last 5 trades + }; + + return ( +
Test and optimize your trading strategies with advanced analytics
+Configure your backtest parameters
++ {backtestResults ? 'Strategy performance analysis' : 'Configure and run backtest to see results'} +
+Simulating trades and calculating performance metrics...
+{metric.description}
+Configure your strategy parameters and run a backtest to see performance analytics
+Welcome to your quantitative research workspace
+