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/components/PerformanceChart.tsx b/src/quant_research_starter/frontend/cauweb/src/components/PerformanceChart.tsx new file mode 100644 index 0000000..21319a6 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/components/PerformanceChart.tsx @@ -0,0 +1,272 @@ +import React from 'react'; + +interface ChartData { + date: string; + value: number; +} + +interface PerformanceChartProps { + portfolioData: ChartData[]; + benchmarkData: ChartData[]; + height?: number; +} + +export const PerformanceChart: React.FC = ({ + portfolioData, + benchmarkData, + height = 300 +}) => { + if (!portfolioData.length || !benchmarkData.length) { + return ( +
+
+
📊
+

No chart data available

+
+
+ ); + } + + // Calculate chart dimensions and scales + const chartWidth = 800; + const chartHeight = height; + const padding = { top: 40, right: 40, bottom: 40, left: 60 }; + + // Find min and max values for scaling + const allValues = [...portfolioData.map(d => d.value), ...benchmarkData.map(d => d.value)]; + const maxValue = Math.max(...allValues); + const minValue = Math.min(...allValues); + const valueRange = maxValue - minValue; + + // Scale functions + const scaleX = (index: number) => + padding.left + (index / (portfolioData.length - 1)) * (chartWidth - padding.left - padding.right); + + const scaleY = (value: number) => + chartHeight - padding.bottom - ((value - minValue) / valueRange) * (chartHeight - padding.top - padding.bottom); + + // Generate SVG path data + const generatePath = (data: ChartData[]) => { + if (data.length === 0) return ''; + + let path = `M ${scaleX(0)} ${scaleY(data[0].value)}`; + + for (let i = 1; i < data.length; i++) { + path += ` L ${scaleX(i)} ${scaleY(data[i].value)}`; + } + + return path; + }; + + const portfolioPath = generatePath(portfolioData); + const benchmarkPath = generatePath(benchmarkData); + + // Generate grid lines and labels + const gridLines = []; + const yLabels = []; + const numGridLines = 5; + + for (let i = 0; i <= numGridLines; i++) { + const value = minValue + (i / numGridLines) * valueRange; + const y = scaleY(value); + + gridLines.push( + + ); + + yLabels.push( + + ${Math.round(value).toLocaleString()} + + ); + } + + // Generate x-axis labels (dates) + const xLabels = []; + const labelInterval = Math.max(1, Math.floor(portfolioData.length / 6)); + + for (let i = 0; i < portfolioData.length; i += labelInterval) { + const date = new Date(portfolioData[i].date); + const label = date.toLocaleDateString('en-US', { + year: '2-digit', + month: 'short' + }); + + xLabels.push( + + {label} + + ); + } + + // Calculate performance metrics + const portfolioStart = portfolioData[0].value; + const portfolioEnd = portfolioData[portfolioData.length - 1].value; + const portfolioReturn = ((portfolioEnd - portfolioStart) / portfolioStart) * 100; + + const benchmarkStart = benchmarkData[0].value; + const benchmarkEnd = benchmarkData[benchmarkData.length - 1].value; + const benchmarkReturn = ((benchmarkEnd - benchmarkStart) / benchmarkStart) * 100; + + return ( +
+ {/* Chart Header */} +
+
+

Portfolio Performance vs Benchmark

+
+ {portfolioData[0]?.date} to {portfolioData[portfolioData.length - 1]?.date} +
+
+ +
+
+ Portfolio Return: + = 0 ? 'text-green-400' : 'text-red-400'}`}> + {portfolioReturn >= 0 ? '+' : ''}{portfolioReturn.toFixed(2)}% + +
+ +
+ Benchmark Return: + = 0 ? 'text-green-400' : 'text-red-400'}`}> + {benchmarkReturn >= 0 ? '+' : ''}{benchmarkReturn.toFixed(2)}% + +
+ +
+ Outperformance: + = benchmarkReturn ? 'text-green-400' : 'text-red-400'}`}> + {(portfolioReturn - benchmarkReturn).toFixed(2)}% + +
+
+
+ + {/* Chart Container */} +
+ + {/* Background */} + + + {/* Grid lines */} + {gridLines} + + {/* Y-axis labels */} + {yLabels} + + {/* X-axis labels */} + {xLabels} + + {/* Axis lines */} + + + + {/* Portfolio line with glow effect */} + + + + + + + + + + + + + {/* Benchmark line */} + + + {/* Portfolio points */} + {portfolioData.map((point, index) => ( + + ))} + + {/* Benchmark points */} + {benchmarkData.map((point, index) => ( + + ))} + +
+ + {/* Chart Legend */} +
+
+
+ + Strategy Portfolio (${portfolioEnd.toLocaleString()}) + +
+ +
+
+ + Benchmark - SPY (${benchmarkEnd.toLocaleString()}) + +
+
+
+ ); +}; \ No newline at end of file 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 ( +
+
+

Backtest Studio

+

Test and optimize your trading strategies with advanced analytics

+
+ +
+ {/* Configuration Panel */} +
+
+

+ + Strategy Configuration +

+

Configure your backtest parameters

+
+ +
+
+ + setConfig({...config, initialCapital: Number(e.target.value)})} + className="form-input" + min="1000" + step="1000" + /> +
+ +
+
+ + setConfig({...config, startDate: e.target.value})} + className="form-input" + /> +
+
+ + setConfig({...config, endDate: e.target.value})} + className="form-input" + /> +
+
+ +
+ + +
+ +
+ + +
+ +
+ + setConfig({...config, symbols: e.target.value.split(',').map(s => s.trim())})} + className="form-input" + placeholder="AAPL, MSFT, GOOGL, AMZN" + /> +
+ + +
+
+ + {/* Results Panel */} +
+
+

Backtest Results

+

+ {backtestResults ? 'Strategy performance analysis' : 'Configure and run backtest to see results'} +

+
+ + {loading ? ( +
+
+

Running Backtest Analysis

+

Simulating trades and calculating performance metrics...

+
+ + + +
+
+ ) : backtestResults ? ( +
+ {/* Key Metrics */} +
+

Performance Metrics

+
+ {getMetrics().map((metric) => { + const Icon = metric.icon; + return ( +
+
+
+ +
+ + {metric.change} + +
+
+

{metric.key}

+
{metric.value}
+

{metric.description}

+
+
+ ); + })} +
+
+ + {/* Performance Chart */} +
+

Portfolio Performance

+
+ +
+
+ + {/* Additional Metrics & Trades */} +
+
+

Recent Trades

+
+ {getRecentTrades().map((trade, index) => ( +
+
{trade.symbol}
+
+ {trade.side} +
+
{trade.quantity} shares
+
${trade.price.toFixed(2)}
+
= 0 ? 'positive' : 'negative'}`}> + {trade.pnl >= 0 ? '+' : ''}${trade.pnl.toFixed(2)} +
+
+ ))} +
+
+ +
+

Strategy Statistics

+
+
+ Total Trades + {backtestResults.trades.length} +
+
+ Win Rate + + {(backtestResults.metrics.winRate * 100).toFixed(1)}% + +
+
+ Avg. Trade Duration + 5.2 days +
+
+ Largest Gain + +12.4% +
+
+ Largest Loss + -8.7% +
+
+ Profit Factor + + {backtestResults.metrics.profitFactor.toFixed(2)} + +
+
+
+
+
+ ) : ( +
+ +

No Backtest Results

+

Configure your strategy parameters and run a backtest to see performance analytics

+
+ )} +
+
+
+ ); +}; + +// Mock data generators +function generateMockTrades(): Trade[] { + const trades: Trade[] = []; + const symbols = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META']; + + for (let i = 0; i < 25; i++) { + trades.push({ + symbol: symbols[Math.floor(Math.random() * symbols.length)], + quantity: Math.floor(Math.random() * 100) + 10, + price: Math.random() * 500 + 50, + timestamp: `2023-${String(Math.floor(Math.random() * 12) + 1).padStart(2, '0')}-${String(Math.floor(Math.random() * 28) + 1).padStart(2, '0')}`, + side: Math.random() > 0.5 ? 'BUY' : 'SELL', + pnl: (Math.random() - 0.3) * 2000 + }); + } + + return trades; +} + +// Replace the existing generatePortfolioHistory and generateBenchmarkHistory functions: + +function generatePortfolioHistory() { + const history = []; + let value = 100000; + const startDate = new Date('2020-01-01'); + + // Create more realistic market data with trends and volatility + let trend = 0.002; // Slight upward trend + let volatility = 0.08; + + for (let i = 0; i < 48; i++) { // 4 years of monthly data + const date = new Date(startDate); + date.setMonth(date.getMonth() + i); + + // Realistic market simulation with momentum + const randomChange = (Math.random() - 0.5) * volatility; + const monthlyReturn = trend + randomChange; + + // Add some momentum effect + if (i > 0) { + const prevReturn = (history[i-1].value / (i > 1 ? history[i-2].value : 100000)) - 1; + if (prevReturn > 0.02) { + trend = 0.003; // Positive momentum + } else if (prevReturn < -0.02) { + trend = 0.001; // Negative momentum + } + } + + value *= (1 + monthlyReturn); + + // Ensure minimum value + value = Math.max(value, 80000); + + history.push({ + date: date.toISOString().split('T')[0], + value: Math.round(value) + }); + } + + return history; +} + +function generateBenchmarkHistory() { + const history = []; + let value = 100000; + const startDate = new Date('2020-01-01'); + + // Benchmark (SPY-like) with less volatility + let trend = 0.0015; + let volatility = 0.05; + + for (let i = 0; i < 48; i++) { + const date = new Date(startDate); + date.setMonth(date.getMonth() + i); + + const randomChange = (Math.random() - 0.5) * volatility; + const monthlyReturn = trend + randomChange; + + value *= (1 + monthlyReturn); + value = Math.max(value, 85000); + + history.push({ + date: date.toISOString().split('T')[0], + value: Math.round(value) + }); + } + + return history; +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.js b/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.js new file mode 100644 index 0000000..f2feb89 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.js @@ -0,0 +1,20 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { TrendingUp, TrendingDown, Target } from 'lucide-react'; +export const Dashboard = () => { + const metrics = [ + { title: 'Total Return', value: '+23.45%', change: '+2.1%', icon: TrendingUp, trend: 'up' }, + { title: 'Sharpe Ratio', value: '1.234', change: '+0.12', icon: TrendingUp, trend: 'up' }, + { title: 'Max Drawdown', value: '-12.34%', change: '-1.2%', icon: TrendingDown, trend: 'down' }, + { title: 'Win Rate', value: '64.50%', change: '+3.2%', icon: Target, trend: 'up' } + ]; + return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Dashboard" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Welcome to your quantitative research workspace" })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8", children: metrics.map((metric, index) => { + const Icon = metric.icon; + return (_jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx("div", { className: "p-2 bg-blue-50 rounded-lg", children: _jsx(Icon, { className: `w-6 h-6 ${metric.trend === 'up' ? 'text-green-600' : 'text-red-600'}` }) }), _jsx("span", { className: `text-sm font-medium ${metric.trend === 'up' ? 'text-green-600' : 'text-red-600'}`, children: metric.change })] }), _jsx("h3", { className: "text-gray-600 text-sm font-medium mb-1", children: metric.title }), _jsx("div", { className: `text-2xl font-bold ${metric.trend === 'up' ? 'text-green-600' : 'text-red-600'}`, children: metric.value })] }, index)); + }) }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8", 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: "Recent Backtests" }), _jsx("div", { className: "space-y-4", children: [ + { name: 'Momentum Strategy', date: '2 hours ago', status: 'Completed' }, + { name: 'Mean Reversion', date: '5 hours ago', status: 'Completed' }, + { name: 'Sector Rotation', date: '1 day ago', status: 'Running' } + ].map((test, index) => (_jsxs("div", { className: "flex items-center justify-between p-3 bg-gray-50 rounded-lg", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium text-gray-900", children: test.name }), _jsx("div", { className: "text-sm text-gray-500", children: test.date })] }), _jsx("span", { className: `px-2 py-1 rounded-full text-xs font-medium ${test.status === 'Completed' + ? 'bg-green-100 text-green-800' + : 'bg-blue-100 text-blue-800'}`, children: test.status })] }, index))) })] }), _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: "Quick Actions" }), _jsxs("div", { className: "space-y-3", children: [_jsxs("button", { className: "w-full text-left p-4 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition-colors", children: [_jsx("div", { className: "font-medium text-blue-900", children: "Run New Backtest" }), _jsx("div", { className: "text-sm text-blue-700", children: "Test a new trading strategy" })] }), _jsxs("button", { className: "w-full text-left p-4 bg-green-50 border border-green-200 rounded-lg hover:bg-green-100 transition-colors", children: [_jsx("div", { className: "font-medium text-green-900", children: "Analyze Portfolio" }), _jsx("div", { className: "text-sm text-green-700", children: "Deep dive into performance metrics" })] }), _jsxs("button", { className: "w-full text-left p-4 bg-purple-50 border border-purple-200 rounded-lg hover:bg-purple-100 transition-colors", children: [_jsx("div", { className: "font-medium text-purple-900", children: "Research Factors" }), _jsx("div", { className: "text-sm text-purple-700", children: "Explore alpha factors" })] })] })] })] })] })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.tsx b/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.tsx new file mode 100644 index 0000000..91757a3 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/Dashboard.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { TrendingUp, TrendingDown, DollarSign, Target } from 'lucide-react'; + +export const Dashboard: React.FC = () => { + const metrics = [ + { title: 'Total Return', value: '+23.45%', change: '+2.1%', icon: TrendingUp, trend: 'up' }, + { title: 'Sharpe Ratio', value: '1.234', change: '+0.12', icon: TrendingUp, trend: 'up' }, + { title: 'Max Drawdown', value: '-12.34%', change: '-1.2%', icon: TrendingDown, trend: 'down' }, + { title: 'Win Rate', value: '64.50%', change: '+3.2%', icon: Target, trend: 'up' } + ]; + + return ( +
+
+

Dashboard

+

Welcome to your quantitative research workspace

+
+ + {/* Metrics Grid */} +
+ {metrics.map((metric, index) => { + const Icon = metric.icon; + return ( +
+
+
+ +
+ + {metric.change} + +
+

{metric.title}

+
+ {metric.value} +
+
+ ); + })} +
+ + {/* Recent Activity */} +
+
+

Recent Backtests

+
+ {[ + { name: 'Momentum Strategy', date: '2 hours ago', status: 'Completed' }, + { name: 'Mean Reversion', date: '5 hours ago', status: 'Completed' }, + { name: 'Sector Rotation', date: '1 day ago', status: 'Running' } + ].map((test, index) => ( +
+
+
{test.name}
+
{test.date}
+
+ + {test.status} + +
+ ))} +
+
+ +
+

Quick Actions

+
+ + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.js b/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.js new file mode 100644 index 0000000..0e96033 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.js @@ -0,0 +1,19 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { PieChart, BarChart } from 'lucide-react'; +export const PortfolioAnalytics = () => { + return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Portfolio Analytics" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Deep dive into portfolio performance and risk" })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8", 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: "Asset Allocation" }), _jsxs("div", { className: "h-64 flex items-center justify-center text-gray-500", children: [_jsx(PieChart, { className: "w-12 h-12 opacity-50 mr-4" }), _jsx("span", { children: "Allocation chart will be displayed here" })] })] }), _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: "Sector Exposure" }), _jsxs("div", { className: "h-64 flex items-center justify-center text-gray-500", children: [_jsx(BarChart, { className: "w-12 h-12 opacity-50 mr-4" }), _jsx("span", { children: "Sector exposure chart will be displayed here" })] })] }), _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: "Risk Analysis" }), _jsx("div", { className: "space-y-4", children: [ + { metric: 'Value at Risk (95%)', value: '-5.2%' }, + { metric: 'Expected Shortfall', value: '-7.8%' }, + { metric: 'Beta to Market', value: '1.12' }, + { metric: 'Tracking Error', value: '4.5%' } + ].map((item, index) => (_jsxs("div", { className: "flex justify-between items-center p-3 bg-gray-50 rounded-lg", children: [_jsx("span", { className: "text-gray-700", children: item.metric }), _jsx("span", { className: "font-semibold text-gray-900", children: item.value })] }, index))) })] }), _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: "Performance Attribution" }), _jsx("div", { className: "space-y-3", children: [ + { factor: 'Stock Selection', contribution: '+3.2%' }, + { factor: 'Sector Allocation', contribution: '+1.8%' }, + { factor: 'Currency Effects', contribution: '-0.4%' }, + { factor: 'Transaction Costs', contribution: '-0.9%' } + ].map((item, index) => (_jsxs("div", { className: "flex justify-between items-center", children: [_jsx("span", { className: "text-gray-600", children: item.factor }), _jsx("span", { className: `font-medium ${item.contribution.startsWith('+') + ? 'text-green-600' + : item.contribution.startsWith('-') + ? 'text-red-600' + : 'text-gray-600'}`, children: item.contribution })] }, index))) })] })] })] })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.tsx b/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.tsx new file mode 100644 index 0000000..2309ba8 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/PortfolioAnalytics.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { PieChart, BarChart, TrendingUp, Users } from 'lucide-react'; + +export const PortfolioAnalytics: React.FC = () => { + return ( +
+
+

Portfolio Analytics

+

Deep dive into portfolio performance and risk

+
+ +
+
+

Asset Allocation

+
+ + Allocation chart will be displayed here +
+
+ +
+

Sector Exposure

+
+ + Sector exposure chart will be displayed here +
+
+ +
+

Risk Analysis

+
+ {[ + { metric: 'Value at Risk (95%)', value: '-5.2%' }, + { metric: 'Expected Shortfall', value: '-7.8%' }, + { metric: 'Beta to Market', value: '1.12' }, + { metric: 'Tracking Error', value: '4.5%' } + ].map((item, index) => ( +
+ {item.metric} + {item.value} +
+ ))} +
+
+ +
+

Performance Attribution

+
+ {[ + { factor: 'Stock Selection', contribution: '+3.2%' }, + { factor: 'Sector Allocation', contribution: '+1.8%' }, + { factor: 'Currency Effects', contribution: '-0.4%' }, + { factor: 'Transaction Costs', contribution: '-0.9%' } + ].map((item, index) => ( +
+ {item.factor} + + {item.contribution} + +
+ ))} +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.js b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.js new file mode 100644 index 0000000..4ca3eb6 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.js @@ -0,0 +1,20 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { LineChart, BarChart3, Activity } from 'lucide-react'; +export const ResearchLab = () => { + const factors = [ + { name: 'Momentum', description: 'Price momentum factors', icon: Activity, color: 'blue' }, + { name: 'Value', description: 'Valuation metrics', icon: BarChart3, color: 'green' }, + { name: 'Size', description: 'Market capitalization', icon: LineChart, color: 'purple' }, + { name: 'Volatility', description: 'Price volatility measures', icon: Activity, color: 'red' } + ]; + return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Research Lab" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Explore and analyze alpha factors" })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8", children: factors.map((factor, index) => { + const Icon = factor.icon; + const colorClasses = { + blue: 'bg-blue-100 text-blue-600', + green: 'bg-green-100 text-green-600', + purple: 'bg-purple-100 text-purple-600', + red: 'bg-red-100 text-red-600' + }; + return (_jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow", children: [_jsx("div", { className: `w-12 h-12 ${colorClasses[factor.color]} rounded-lg flex items-center justify-center mb-4`, children: _jsx(Icon, { className: "w-6 h-6" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 mb-2", children: factor.name }), _jsx("p", { className: "text-gray-600 text-sm", children: factor.description }), _jsx("button", { className: "mt-4 w-full bg-gray-100 text-gray-700 py-2 rounded-lg hover:bg-gray-200 transition-colors text-sm", children: "Analyze Factor" })] }, index)); + }) }), _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: "Factor Performance" }), _jsx("div", { className: "h-96 bg-gray-100 rounded-lg flex items-center justify-center text-gray-500", children: "Factor performance charts will be displayed here" })] })] })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.tsx b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.tsx new file mode 100644 index 0000000..e203b63 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/ResearchLab.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Beaker, LineChart, BarChart3, Activity } from 'lucide-react'; + +export const ResearchLab: React.FC = () => { + const factors = [ + { name: 'Momentum', description: 'Price momentum factors', icon: Activity, color: 'blue' }, + { name: 'Value', description: 'Valuation metrics', icon: BarChart3, color: 'green' }, + { name: 'Size', description: 'Market capitalization', icon: LineChart, color: 'purple' }, + { name: 'Volatility', description: 'Price volatility measures', icon: Activity, color: 'red' } + ]; + + return ( +
+
+

Research Lab

+

Explore and analyze alpha factors

+
+ +
+ {factors.map((factor, index) => { + const Icon = factor.icon; + const colorClasses = { + blue: 'bg-blue-100 text-blue-600', + green: 'bg-green-100 text-green-600', + purple: 'bg-purple-100 text-purple-600', + red: 'bg-red-100 text-red-600' + }; + + return ( +
+
+ +
+

{factor.name}

+

{factor.description}

+ +
+ ); + })} +
+ +
+

Factor Performance

+
+ Factor performance charts will be displayed here +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Settings.js b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.js new file mode 100644 index 0000000..6c3c483 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.js @@ -0,0 +1,5 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { Save } from 'lucide-react'; +export const Settings = () => { + return (_jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "mb-8", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900", children: "Settings" }), _jsx("p", { className: "text-gray-600 mt-2", children: "Configure your research environment" })] }), _jsx("div", { className: "max-w-2xl", 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-6", children: "Preferences" }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Default Timezone" }), _jsxs("select", { 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", { children: "UTC" }), _jsx("option", { children: "EST" }), _jsx("option", { children: "PST" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Currency" }), _jsxs("select", { 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", { children: "USD ($)" }), _jsx("option", { children: "EUR (\u20AC)" }), _jsx("option", { children: "GBP (\u00A3)" })] })] }), _jsx("div", { children: _jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", className: "rounded border-gray-300 text-blue-600 focus:ring-blue-500" }), _jsx("span", { className: "ml-2 text-sm text-gray-700", children: "Enable real-time data updates" })] }) }), _jsx("div", { children: _jsxs("label", { className: "flex items-center", children: [_jsx("input", { type: "checkbox", className: "rounded border-gray-300 text-blue-600 focus:ring-blue-500", defaultChecked: true }), _jsx("span", { className: "ml-2 text-sm text-gray-700", children: "Send performance reports via email" })] }) }), _jsxs("button", { className: "bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2", children: [_jsx(Save, { className: "w-4 h-4" }), _jsx("span", { children: "Save Preferences" })] })] })] }) })] })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/pages/Settings.tsx b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.tsx new file mode 100644 index 0000000..6056ed6 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/pages/Settings.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Save } from 'lucide-react'; + +export const Settings: React.FC = () => { + return ( +
+
+

Settings

+

Configure your research environment

+
+ +
+
+

Preferences

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/readme.md b/src/quant_research_starter/frontend/readme.md new file mode 100644 index 0000000..990ed99 --- /dev/null +++ b/src/quant_research_starter/frontend/readme.md @@ -0,0 +1,50 @@ +frontend/ +│ +├── core/ # Shared logic library +│ ├── src/ +│ │ ├── components/ # Shared React components +│ │ ├── hooks/ # Shared React hooks (fetch, state mgmt) +│ │ ├── utils/ # Reusable helper functions +│ │ ├── types/ # Shared TypeScript types +│ │ └── index.ts # Export all shared modules +│ ├── package.json +│ ├── tsconfig.json +│ └── README.md + +│ +├── cauweb/ # Main Web UI App +│ ├── src/ +│ │ ├── pages/ # UI Screens (Home, Dashboard, About) +│ │ ├── layouts/ # Shared layouts (Navbar, Sidebar) +│ │ ├── features/ # Domain features (Logs, Trading UI) +│ │ ├── assets/ # Images, icons, fonts +│ │ ├── styles/ # Global CSS/Tailwind configs +│ │ └── main.tsx # App entry point +│ ├── public/ +│ ├── package.json +│ ├── tsconfig.json +│ └── README.md + +│ +├── cli/ # Terminal UI (Node/React Ink CLI) +│ ├── src/ +│ │ └── index.ts +│ ├── package.json +│ └── tsconfig.json + +│ +├── metrics/ # Data visual charts + API metrics +│ ├── src/ +│ │ ├── charts/ +│ │ ├── analytics/ +│ │ └── index.ts +│ ├── package.json +│ └── tsconfig.json + +│ +├── node_modules/ # Shared dependencies root install +│ +├── package.json # root scripts + global deps +├── pnpm-workspace.yaml # defines workspace packages +├── tsconfig.base.json # shared TS config +└── README.md