diff --git a/README.md b/README.md index 1d48e1d8..a6cdb449 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/index.html b/src/quant_research_starter/frontend/cauweb/index.html new file mode 100644 index 00000000..1f8fbb75 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/index.html @@ -0,0 +1,403 @@ + + + + + + + CAUQuant - Quantitative Research Platform + + + +
+ + + \ No newline at end of file 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 00000000..9f5cbe5a --- /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/Header.js b/src/quant_research_starter/frontend/cauweb/src/components/Header.js new file mode 100644 index 00000000..e1056731 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/components/Header.js @@ -0,0 +1,5 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { Bell, Search, User } from 'lucide-react'; +export const Header = () => { + return (_jsx("header", { className: "bg-white shadow-sm border-b border-gray-200", children: _jsxs("div", { className: "flex justify-between items-center px-8 py-4", children: [_jsx("div", { className: "flex-1 max-w-2xl", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" }), _jsx("input", { type: "text", placeholder: "Search strategies, assets, or metrics...", className: "w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" })] }) }), _jsxs("div", { className: "flex items-center space-x-4", children: [_jsx("button", { className: "p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors", children: _jsx(Bell, { className: "w-5 h-5" }) }), _jsxs("div", { className: "flex items-center space-x-3", children: [_jsx("div", { className: "w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center", children: _jsx(User, { className: "w-4 h-4 text-white" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Researcher" })] })] })] }) })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/components/Header.tsx b/src/quant_research_starter/frontend/cauweb/src/components/Header.tsx new file mode 100644 index 00000000..174ea306 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/components/Header.tsx @@ -0,0 +1,208 @@ +import { useState, useRef, useEffect } from 'react'; +import { Bell, Search, User, Settings, LogOut, ChevronDown, Menu, X } from 'lucide-react'; + +export const Header = () => { + const [isProfileOpen, setIsProfileOpen] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [showSuggestions, setShowSuggestions] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + + const profileRef = useRef(null); + const searchRef = useRef(null); + + // Close dropdowns when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (profileRef.current && !profileRef.current.contains(event.target as Node)) { + setIsProfileOpen(false); + } + if (searchRef.current && !searchRef.current.contains(event.target as Node)) { + setShowSuggestions(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // Mock search suggestions + const searchSuggestions = [ + "Mean Reversion Strategy", + "BTC/USD Correlation", + "Portfolio Optimization", + "Risk Metrics Analysis", + "Volatility Forecasting" + ]; + + const filteredSuggestions = searchSuggestions.filter(suggestion => + suggestion.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Handle mobile search + const handleMobileSearch = () => { + // Implement mobile search modal or expandable search + console.log("Mobile search activated"); + }; + + // Handle profile actions + const handleProfileAction = (action: string) => { + setIsProfileOpen(false); + console.log(`${action} clicked`); + // Add your navigation or action logic here + }; + + return ( +
+
+ {/* Logo and Mobile Menu Button */} +
+ {/* Mobile Menu Button */} + + + {/* Logo */} +

+ QuantResearch +

+
+ + {/* Search Bar - Hidden on mobile, visible on desktop */} +
+
+ + { + setSearchQuery(e.target.value); + setShowSuggestions(true); + }} + onFocus={() => setShowSuggestions(true)} + placeholder="Search strategies, assets, or metrics..." + className="w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400" + /> + + {/* Search Suggestions Dropdown */} + {showSuggestions && searchQuery && ( +
+
+ {filteredSuggestions.length > 0 ? ( + filteredSuggestions.map((suggestion, index) => ( + + )) + ) : ( +
+ No results found +
+ )} +
+
+ )} +
+
+ + {/* Mobile Search Button */} + + + {/* Right Section - Notifications & Profile */} +
+ {/* Notifications - Hidden on mobile */} + + + {/* Profile Dropdown */} +
+ + + {/* Dropdown Menu */} + {isProfileOpen && ( +
+
+ {/* Mobile-only user name */} +
+ + Sarah Wilson + +
+ + + + + +
+ + +
+
+ )} +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/components/Navigation.js b/src/quant_research_starter/frontend/cauweb/src/components/Navigation.js new file mode 100644 index 00000000..e8342c1e --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/components/Navigation.js @@ -0,0 +1,18 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { NavLink } from 'react-router-dom'; +import { LayoutDashboard, PlayCircle, FlaskConical, PieChart, Settings, TrendingUp } from 'lucide-react'; +export const Navigation = () => { + const navItems = [ + { path: '/', icon: LayoutDashboard, label: 'Dashboard' }, + { path: '/backtest', icon: PlayCircle, label: 'Backtest Studio' }, + { path: '/research', icon: FlaskConical, label: 'Research Lab' }, + { path: '/portfolio', icon: PieChart, label: 'Portfolio Analytics' }, + { path: '/settings', icon: Settings, label: 'Settings' } + ]; + return (_jsxs("nav", { className: "fixed left-0 top-0 h-full w-64 bg-white shadow-xl border-r border-gray-200", children: [_jsx("div", { className: "p-6 border-b border-gray-200", children: _jsxs("div", { className: "flex items-center space-x-3", children: [_jsx("div", { className: "w-10 h-10 bg-gradient-to-br from-blue-600 to-purple-600 rounded-lg flex items-center justify-center", children: _jsx(TrendingUp, { className: "w-6 h-6 text-white" }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-bold text-gray-900", children: "CAUQuant" }), _jsx("p", { className: "text-xs text-gray-500", children: "Research Platform" })] })] }) }), _jsx("div", { className: "p-4 space-y-2", children: navItems.map((item) => { + const Icon = item.icon; + return (_jsxs(NavLink, { to: item.path, className: ({ isActive }) => `flex items-center space-x-3 px-4 py-3 rounded-xl transition-colors ${isActive + ? 'bg-blue-50 text-blue-700 border border-blue-200' + : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}`, children: [_jsx(Icon, { className: "w-5 h-5" }), _jsx("span", { className: "font-medium", children: item.label })] }, item.path)); + }) }), _jsx("div", { className: "absolute bottom-0 left-0 right-0 p-4 border-t border-gray-200", children: _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex justify-between text-sm", children: [_jsx("span", { className: "text-gray-500", children: "Strategies" }), _jsx("span", { className: "font-semibold", children: "12" })] }), _jsxs("div", { className: "flex justify-between text-sm", children: [_jsx("span", { className: "text-gray-500", children: "Assets" }), _jsx("span", { className: "font-semibold", children: "248" })] }), _jsxs("div", { className: "flex justify-between text-sm", children: [_jsx("span", { className: "text-gray-500", children: "Backtests" }), _jsx("span", { className: "font-semibold", children: "1.2K" })] })] }) })] })); +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/components/Navigation.tsx b/src/quant_research_starter/frontend/cauweb/src/components/Navigation.tsx new file mode 100644 index 00000000..0cf03465 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/components/Navigation.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; +import { + LayoutDashboard, + PlayCircle, + Beaker, + PieChart, + Settings, + TrendingUp +} from 'lucide-react'; + +export const Navigation: React.FC = () => { + const navItems = [ + { path: '/', icon: LayoutDashboard, label: 'Dashboard' }, + { path: '/backtest', icon: PlayCircle, label: 'Backtest Studio' }, + { path: '/research', icon: Beaker, label: 'Research Lab' }, + { path: '/portfolio', icon: PieChart, label: 'Portfolio Analytics' }, + { path: '/settings', icon: Settings, label: 'Settings' } + ]; + + return ( + + ); +}; \ No newline at end of file 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 00000000..21319a61 --- /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/hooks/useQuantData.js b/src/quant_research_starter/frontend/cauweb/src/hooks/useQuantData.js new file mode 100644 index 00000000..1ccd7a3f --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/hooks/useQuantData.js @@ -0,0 +1,38 @@ +import { useState, useEffect } from 'react'; +import { api } from '../utils/api'; +export const useQuantData = () => { + const [assets, setAssets] = useState([]); + const [loading, setLoading] = useState(false); + const [backtestResults, setBacktestResults] = useState(null); + useEffect(() => { + loadAssets(); + }, []); + const loadAssets = async () => { + try { + const data = await api.getAssets(); + setAssets(data); + } + catch (error) { + console.error('Failed to load assets:', error); + } + }; + const runBacktest = async (config) => { + setLoading(true); + try { + const results = await api.runBacktest(config); + setBacktestResults(results); + } + catch (error) { + console.error('Backtest failed:', error); + } + finally { + setLoading(false); + } + }; + return { + assets, + loading, + backtestResults, + runBacktest + }; +}; diff --git a/src/quant_research_starter/frontend/cauweb/src/hooks/useQuantData.ts b/src/quant_research_starter/frontend/cauweb/src/hooks/useQuantData.ts new file mode 100644 index 00000000..87e5c573 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/hooks/useQuantData.ts @@ -0,0 +1,26 @@ +import { useState, useEffect } from 'react'; + +export const useQuantData = () => { + const [assets, setAssets] = useState([]); + + useEffect(() => { + loadAssets(); + }, []); + + const loadAssets = async () => { + // Mock data + const mockAssets = [ + { symbol: 'AAPL', name: 'Apple Inc.', sector: 'Technology', marketCap: 2800000000000, price: 182.63 }, + { symbol: 'MSFT', name: 'Microsoft Corp.', sector: 'Technology', marketCap: 2750000000000, price: 370.73 }, + { symbol: 'GOOGL', name: 'Alphabet Inc.', sector: 'Technology', marketCap: 1750000000000, price: 138.21 }, + ]; + setAssets(mockAssets); + }; + + return { + assets, + loading: false, + backtestResults: null, + runBacktest: async () => {} // Empty function for now + }; +}; \ 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 00000000..7859640c --- /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 00000000..a72e6759 --- /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 00000000..f2feb894 --- /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 00000000..91757a3e --- /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 00000000..0e96033b --- /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 00000000..2309ba82 --- /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 00000000..4ca3eb6c --- /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 00000000..e203b639 --- /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 00000000..6c3c4838 --- /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 00000000..6056ed63 --- /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/cauweb/src/styles/globals.css b/src/quant_research_starter/frontend/cauweb/src/styles/globals.css new file mode 100644 index 00000000..99154070 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/styles/globals.css @@ -0,0 +1,1344 @@ +/* ===== RESET & BASE STYLES ===== */ + + + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); + min-height: 100vh; + color: #e2e8f0; + line-height: 1.6; +} + +/* ===== MAIN LAYOUT ===== */ +.min-h-screen { + min-height: 100vh; +} + +.bg-gray-50 { + background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); +} + +/* ===== NAVIGATION - FIXED & PERFECT ===== */ +.fixed { + position: fixed; +} + +.left-0 { + left: 0; +} + +.top-0 { + top: 0; +} + +.h-full { + height: 100vh; +} + +.w-64 { + width: 16rem; +} + +.bg-white { + background: rgba(15, 23, 42, 0.95); + border-right: 1px solid rgba(255, 255, 255, 0.1); +} + +.shadow-xl { + box-shadow: 2px 0 10px rgba(0, 0, 0, 0.3); +} + +.z-50 { + z-index: 50; +} + +/* Navigation Content */ +.p-6 { + padding: 1.5rem; +} + +.p-4 { + padding: 1rem; +} + +.border-b { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +/* Flexbox Utilities */ +.flex { + display: flex; +} + +.items-center { + align-items: center; +} + +.justify-between { + justify-content: space-between; +} + +.space-x-3 > * + * { + margin-left: 0.75rem; +} + +.space-y-2 > * + * { + margin-top: 0.5rem; +} + +/* Logo Styles */ +.w-10 { + width: 2.5rem; +} + +.h-10 { + height: 2.5rem; +} + +.bg-gradient-to-br { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.text-white { + color: white; +} + +.text-xl { + font-size: 1.25rem; +} + +.font-bold { + font-weight: bold; +} + +.text-xs { + font-size: 0.75rem; +} + +.opacity-80 { + opacity: 0.8; +} + +/* Navigation Items */ +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.transition-colors { + transition: all 0.3s ease; +} + +.text-gray-600 { + color: #94a3b8; +} + +.hover\:bg-gray-50:hover { + background: rgba(255, 255, 255, 0.05); +} + +.hover\:text-gray-900:hover { + color: white; +} + +.bg-blue-50 { + background: rgba(102, 126, 234, 0.1); +} + +.text-blue-700 { + color: #60a5fa; +} + +.border { + border: 1px solid rgba(102, 126, 234, 0.3); +} + +.border-blue-200 { + border-color: rgba(102, 126, 234, 0.3); +} + +.w-5 { + width: 1.25rem; +} + +.h-5 { + height: 1.25rem; +} + +.font-medium { + font-weight: 500; +} + +/* Navigation Stats */ +.absolute { + position: absolute; +} + +.bottom-0 { + bottom: 0; +} + +.border-t { + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.bg-gray-50 { + background: rgba(30, 41, 59, 0.8); +} + +.text-sm { + font-size: 0.875rem; +} + +.text-gray-500 { + color: #64748b; +} + +.font-semibold { + font-weight: 600; +} + +/* ===== MAIN CONTENT AREA ===== */ +.ml-64 { + margin-left: 16rem; + min-height: 100vh; + background: #0f172a; +} + +/* ===== HEADER ===== */ +.bg-white { + background: rgba(15, 23, 42, 0.9); +} + +.shadow-sm { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.border-gray-200 { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.flex-1 { + flex: 1; +} + +.max-w-2xl { + max-width: 42rem; +} + +/* Search Bar */ +.relative { + position: relative; +} + +.pl-10 { + padding-left: 2.5rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.search-input { + width: 100%; + padding: 0.75rem 1rem 0.75rem 2.5rem; + background: rgba(30, 41, 59, 0.8); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0.5rem; + color: white; + font-size: 0.875rem; +} + +.search-input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2); +} + +.search-input::placeholder { + color: #94a3b8; +} + +/* Header Icons */ +.space-x-4 > * + * { + margin-left: 1rem; +} + +.p-2 { + padding: 0.5rem; +} + +.text-gray-600 { + color: #94a3b8; +} + +.hover\:text-gray-900:hover { + color: white; +} + +.hover\:bg-gray-100:hover { + background: rgba(255, 255, 255, 0.05); +} + +.rounded-lg { + border-radius: 0.5rem; +} + +/* User Profile */ +.w-8 { + width: 2rem; +} + +.h-8 { + height: 2rem; +} + +.bg-gradient-to-br { + background: linear-gradient(135deg, #667eea, #764ba2); +} + +.rounded-full { + border-radius: 50%; +} + +.text-gray-700 { + color: #e2e8f0; +} + +/* ===== PAGE CONTENT ===== */ +.page-container { + padding: 2rem; +} + +.page-header { + margin-bottom: 2rem; +} + +.page-title { + font-size: 2rem; + font-weight: bold; + color: white; + margin-bottom: 0.5rem; +} + +.page-subtitle { + color: #94a3b8; + font-size: 1.125rem; +} + +/* ===== METRICS GRID - PERFECT ALIGNMENT ===== */ +.metrics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.metric-card { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95)); + border: 1px solid rgba(102, 126, 234, 0.2); + border-radius: 1rem; + padding: 1.5rem; + transition: all 0.3s ease; +} + +.metric-card:hover { + transform: translateY(-4px); + border-color: rgba(102, 126, 234, 0.4); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); +} + +.metric-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.metric-icon { + width: 3rem; + height: 3rem; + border-radius: 0.75rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.25rem; +} + +.metric-icon.up { + background: linear-gradient(135deg, #10b981, #059669); +} + +.metric-icon.down { + background: linear-gradient(135deg, #ef4444, #dc2626); +} + +.metric-trend { + font-size: 0.875rem; + font-weight: 600; + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; +} + +.trend-up { + background: rgba(16, 185, 129, 0.2); + color: #10b981; +} + +.trend-down { + background: rgba(239, 68, 68, 0.2); + color: #ef4444; +} + +.metric-title { + font-size: 0.875rem; + color: #94a3b8; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.metric-value { + font-size: 1.75rem; + font-weight: bold; + color: white; + margin-bottom: 0.25rem; +} + +.metric-change { + font-size: 0.75rem; + font-weight: 500; +} + +/* ===== BUTTONS ===== */ +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 0.5rem; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +/* ===== RESPONSIVE DESIGN ===== */ +@media (max-width: 1024px) { + .ml-64 { + margin-left: 0; + } + + .w-64 { + transform: translateX(-100%); + transition: transform 0.3s ease; + } + + .w-64.mobile-open { + transform: translateX(0); + } +} + +@media (max-width: 768px) { + .page-container { + padding: 1rem; + } + + .metrics-grid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .px-8 { + padding-left: 1rem; + padding-right: 1rem; + } + + .header-content { + flex-direction: column; + gap: 1rem; + } + + .search-bar { + max-width: 100%; + } +} + +/* ===== SIMPLE ANIMATIONS ===== */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in { + animation: fadeIn 0.6s ease-out; +} + +/* ===== LOADING STATES ===== */ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem; + text-align: center; +} + +.spinner { + width: 2rem; + height: 2rem; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top: 2px solid #667eea; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* ===== SCROLLBAR ===== */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: rgba(15, 23, 42, 0.8); +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(135deg, #667eea, #764ba2); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(135deg, #764ba2, #667eea); +} +/* ===== BACKTEST STUDIO SPECIFIC STYLES ===== */ +.studio-layout { + display: grid; + grid-template-columns: 400px 1fr; + gap: 2rem; + align-items: start; +} + +.config-panel { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.98)); + border: 1px solid rgba(102, 126, 234, 0.2); + border-radius: 20px; + padding: 2rem; + position: sticky; + top: 2rem; + backdrop-filter: blur(20px); +} + +.results-panel { + background: transparent; + border-radius: 20px; +} + +.panel-header { + margin-bottom: 2rem; +} + +.panel-title { + font-size: 1.5rem; + font-weight: 600; + color: #ffffff; + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.5rem; +} + +.panel-icon { + width: 1.5rem; + height: 1.5rem; + color: #667eea; +} + +.panel-subtitle { + color: #94a3b8; + font-size: 0.875rem; +} + +.config-form { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.form-section { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-label { + font-size: 0.875rem; + font-weight: 500; + color: #e2e8f0; +} + +.form-input { + padding: 0.75rem 1rem; + background: rgba(30, 41, 59, 0.8); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + color: #ffffff; + font-size: 0.875rem; + transition: all 0.3s ease; +} + +.form-input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); + background: rgba(30, 41, 59, 0.9); +} + +.form-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +.run-btn { + margin-top: 1rem; + position: relative; + overflow: hidden; +} + +.btn-spinner { + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-left: 8px; +} + +/* Results Content */ +.results-content { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.metrics-section { + margin-bottom: 1rem; +} + +.section-title { + font-size: 1.25rem; + font-weight: 600; + color: #ffffff; + margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* Enhanced Metric Cards */ +.metric-card { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95)); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + padding: 1.5rem; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.metric-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, #667eea, #764ba2); + transform: scaleX(0); + transition: transform 0.3s ease; +} + +.metric-card:hover::before { + transform: scaleX(1); +} + +.metric-card:hover { + transform: translateY(-4px); + border-color: rgba(102, 126, 234, 0.3); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3); +} + +.metric-description { + font-size: 0.75rem; + color: #94a3b8; + margin-top: 0.5rem; + line-height: 1.4; +} + +/* Chart Section */ +.chart-section { + margin: 2rem 0; +} + +.performance-chart-container { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.98)); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + padding: 1.5rem; +} + +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.chart-legend { + display: flex; + gap: 1.5rem; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: #94a3b8; +} + +.legend-color { + width: 12px; + height: 12px; + border-radius: 2px; +} + +.legend-color.portfolio { + background: linear-gradient(135deg, #667eea, #764ba2); +} + +.legend-color.benchmark { + background: linear-gradient(135deg, #10b981, #059669); +} + +.chart-stats { + display: flex; + gap: 1.5rem; +} + +.stat { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.stat-label { + font-size: 0.75rem; + color: #94a3b8; +} + +.stat-value { + font-size: 1rem; + font-weight: 600; + color: #ffffff; +} + +.chart-placeholder.enhanced { + position: relative; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01)); + border: 1px dashed rgba(255, 255, 255, 0.1); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.chart-grid { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.grid-line { + height: 1px; + background: rgba(255, 255, 255, 0.05); + margin: 0 2rem; +} + +.chart-message { + text-align: center; + color: #94a3b8; + z-index: 2; +} + +.chart-message p { + font-size: 1rem; + margin: 0.5rem 0; + font-weight: 500; +} + +.chart-message small { + font-size: 0.75rem; + opacity: 0.7; +} + +/* Trades & Additional Sections */ +.additional-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; +} + +.trades-section, +.stats-section { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95)); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + padding: 1.5rem; +} + +.trades-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.trade-item { + display: grid; + grid-template-columns: 1fr auto auto auto auto; + gap: 1rem; + align-items: center; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + font-size: 0.875rem; + transition: background 0.3s ease; +} + +.trade-item:hover { + background: rgba(255, 255, 255, 0.08); +} + +.trade-symbol { + font-weight: 600; + color: #ffffff; +} + +.trade-side { + padding: 0.25rem 0.5rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; +} + +.trade-side.buy { + background: rgba(16, 185, 129, 0.2); + color: #10b981; + border: 1px solid rgba(16, 185, 129, 0.3); +} + +.trade-side.sell { + background: rgba(239, 68, 68, 0.2); + color: #ef4444; + border: 1px solid rgba(239, 68, 68, 0.3); +} + +.trade-pnl { + font-weight: 600; +} + +.trade-pnl.positive { + color: #10b981; +} + +.trade-pnl.negative { + color: #ef4444; +} + +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +.stat-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; +} + +.stat-label { + color: #94a3b8; + font-size: 0.875rem; +} + +.stat-value { + font-weight: 600; + color: #ffffff; +} + +.stat-value.positive { + color: #10b981; +} + +.stat-value.negative { + color: #ef4444; +} + +/* Loading & Empty States */ +.loading-state { + text-align: center; + padding: 4rem 2rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.loading-state h4 { + color: #ffffff; + font-size: 1.25rem; + font-weight: 600; +} + +.loading-state p { + color: #94a3b8; + max-width: 300px; +} + +.pulse-dots { + display: flex; + gap: 4px; + margin-top: 1rem; +} + +.pulse-dots span { + width: 8px; + height: 8px; + border-radius: 50%; + background: #667eea; + animation: pulse 1.4s ease-in-out infinite both; +} + +.pulse-dots span:nth-child(2) { + animation-delay: 0.2s; +} + +.pulse-dots span:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes pulse { + 0%, 80%, 100% { + transform: scale(0.8); + opacity: 0.5; + } + 40% { + transform: scale(1.2); + opacity: 1; + } +} + +.empty-state { + text-align: center; + padding: 4rem 2rem; + color: #94a3b8; +} + +.empty-state h4 { + color: #ffffff; + margin: 1rem 0 0.5rem; + font-size: 1.25rem; +} + +.empty-state p { + max-width: 400px; + margin: 0 auto; + line-height: 1.5; +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .studio-layout { + grid-template-columns: 1fr; + } + + .config-panel { + position: static; + } +} + +@media (max-width: 768px) { + .additional-grid { + grid-template-columns: 1fr; + } + + .form-grid { + grid-template-columns: 1fr; + } + + .chart-header { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .trade-item { + grid-template-columns: 1fr 1fr; + gap: 0.5rem; + } +} + +@media (max-width: 480px) { + .metrics-grid { + grid-template-columns: 1fr; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .chart-legend { + flex-direction: column; + gap: 0.5rem; + } +} +/* ===== REAL CHART STYLES ===== */ +.performance-chart-container { + background: linear-gradient(135deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.98)); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + padding: 1.5rem; + backdrop-filter: blur(20px); +} + +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 1rem; +} + +.chart-legend { + display: flex; + gap: 2rem; + align-items: center; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 0.875rem; + color: #94a3b8; + background: rgba(255, 255, 255, 0.05); + padding: 0.5rem 1rem; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.legend-color { + width: 16px; + height: 16px; + border-radius: 4px; + position: relative; +} + +.legend-color.portfolio { + background: linear-gradient(135deg, #667eea, #764ba2); +} + +.legend-color.portfolio::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(102, 126, 234, 0.8), rgba(118, 75, 162, 0.8)); + border-radius: 4px; +} + +.legend-color.benchmark { + background: linear-gradient(135deg, #10b981, #059669); +} + +.legend-color.benchmark::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(16, 185, 129, 0.8), rgba(5, 150, 105, 0.8)); + border-radius: 4px; + opacity: 0.8; +} + +.legend-return { + font-weight: 600; + color: #ffffff; + margin-left: 0.5rem; + padding: 0.25rem 0.5rem; + border-radius: 4px; + background: rgba(255, 255, 255, 0.1); + font-size: 0.75rem; +} + +.chart-stats { + display: flex; + gap: 1.5rem; + align-items: center; +} + +.stat { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.25rem; +} + +.stat-label { + font-size: 0.75rem; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stat-value { + font-size: 1.125rem; + font-weight: 600; + color: #ffffff; +} + +.stat-value.positive { + color: #10b981; +} + +.stat-value.negative { + color: #ef4444; +} + +.chart-wrapper { + position: relative; + background: rgba(255, 255, 255, 0.02); + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.05); + padding: 1rem; + margin: 1rem 0; +} + +/* Chart.js canvas styling */ +.chart-wrapper canvas { + border-radius: 8px; +} + +.chart-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.period-info { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; +} + +.period-label { + color: #94a3b8; +} + +.period-dates { + color: #e2e8f0; + font-weight: 500; +} + +.chart-actions { + display: flex; + gap: 0.75rem; +} + +.chart-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #e2e8f0; + padding: 0.5rem 1rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.chart-btn:hover { + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.3); + transform: translateY(-1px); +} + +/* Chart tooltip customization */ +.chartjs-tooltip { + background: rgba(15, 23, 42, 0.95) !important; + border: 1px solid rgba(102, 126, 234, 0.3) !important; + border-radius: 8px !important; + backdrop-filter: blur(20px) !important; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3) !important; +} + +.chartjs-tooltip-key { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 2px; + margin-right: 8px; +} + +/* Responsive chart design */ +@media (max-width: 768px) { + .chart-header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .chart-legend { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .chart-stats { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .stat { + align-items: flex-start; + } + + .chart-footer { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .chart-actions { + width: 100%; + justify-content: flex-start; + } +} + +/* Chart hover effects */ +.chart-wrapper:hover { + border-color: rgba(102, 126, 234, 0.2); +} + +/* Animation for chart loading */ +@keyframes chartFadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.chart-wrapper { + animation: chartFadeIn 0.6s ease-out; +} + +/* Custom scrollbar for chart container */ +.chart-wrapper::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.chart-wrapper::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 3px; +} + +.chart-wrapper::-webkit-scrollbar-thumb { + background: rgba(102, 126, 234, 0.5); + border-radius: 3px; +} + +.chart-wrapper::-webkit-scrollbar-thumb:hover { + background: rgba(102, 126, 234, 0.7); +} \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/utils/animations.ts b/src/quant_research_starter/frontend/cauweb/src/utils/animations.ts new file mode 100644 index 00000000..109d5213 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/utils/animations.ts @@ -0,0 +1,74 @@ +// Scroll animation observer +export const initScrollAnimations = (): void => { + if (typeof window === 'undefined') return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('is-visible'); + } + }); + }, { threshold: 0.1 }); + + const fadeElements = document.querySelectorAll('.fade-in-section'); + fadeElements.forEach((el) => { + observer.observe(el); + }); +}; + +// Parallax effect +export const initParallax = (): void => { + if (typeof window === 'undefined') return; + + const handleScroll = (): void => { + const scrolled = window.pageYOffset; + const parallaxElements = document.querySelectorAll('[data-parallax]'); + + parallaxElements.forEach((el) => { + const speed = parseFloat(el.getAttribute('data-parallax-speed') || '0.5'); + (el as HTMLElement).style.transform = `translateY(${scrolled * speed}px)`; + }); + }; + + window.addEventListener('scroll', handleScroll); +}; + +// Typewriter effect +export const initTypewriter = (element: HTMLElement, text: string, speed: number = 100): void => { + let i = 0; + element.innerHTML = ''; + + const type = (): void => { + if (i < text.length) { + element.innerHTML += text.charAt(i); + i++; + setTimeout(type, speed); + } + }; + + type(); +}; + +// Initialize all animations +export const initAllAnimations = (): void => { + if (typeof window === 'undefined') return; + + initScrollAnimations(); + initParallax(); + + // Initialize typewriter effects for elements with data-typewriter attribute + const typewriterElements = document.querySelectorAll('[data-typewriter]'); + typewriterElements.forEach((el) => { + const text = el.getAttribute('data-typewriter') || ''; + const speed = parseInt(el.getAttribute('data-typewriter-speed') || '100'); + initTypewriter(el as HTMLElement, text, speed); + }); +}; + +// Cleanup function for useEffect +export const cleanupAnimations = (): void => { + if (typeof window === 'undefined') return; + + // Remove scroll event listeners + window.removeEventListener('scroll', () => {}); +}; \ No newline at end of file diff --git a/src/quant_research_starter/frontend/cauweb/src/utils/api.js b/src/quant_research_starter/frontend/cauweb/src/utils/api.js new file mode 100644 index 00000000..07983a91 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/utils/api.js @@ -0,0 +1,45 @@ +export const api = { + async runBacktest(config) { + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 2000)); + return { + metrics: { + totalReturn: 0.2345, + annualizedReturn: 0.0876, + volatility: 0.1567, + sharpeRatio: 1.234, + maxDrawdown: 0.1234, + winRate: 0.645, + turnover: 2.34 + }, + portfolioSnapshots: generateDemoSnapshots(), + trades: [] + }; + }, + async getAssets() { + return [ + { symbol: 'AAPL', name: 'Apple Inc.', sector: 'Technology', marketCap: 2800000000000, price: 182.63 }, + { symbol: 'MSFT', name: 'Microsoft Corp.', sector: 'Technology', marketCap: 2750000000000, price: 370.73 }, + { symbol: 'GOOGL', name: 'Alphabet Inc.', sector: 'Technology', marketCap: 1750000000000, price: 138.21 }, + { symbol: 'AMZN', name: 'Amazon.com Inc.', sector: 'Consumer', marketCap: 1500000000000, price: 145.18 }, + { symbol: 'TSLA', name: 'Tesla Inc.', sector: 'Consumer', marketCap: 750000000000, price: 238.83 } + ]; + } +}; +function generateDemoSnapshots() { + const snapshots = []; + let value = 100000; + const startDate = new Date('2020-01-01'); + for (let i = 0; i < 100; i++) { + const date = new Date(startDate); + date.setDate(date.getDate() + i * 7); + value *= 1 + (Math.random() - 0.45) * 0.02; + snapshots.push({ + timestamp: date.toISOString(), + totalValue: value, + cash: 10000, + holdingsValue: value - 10000 + }); + } + return snapshots; +} diff --git a/src/quant_research_starter/frontend/cauweb/src/utils/api.ts b/src/quant_research_starter/frontend/cauweb/src/utils/api.ts new file mode 100644 index 00000000..b00733f0 --- /dev/null +++ b/src/quant_research_starter/frontend/cauweb/src/utils/api.ts @@ -0,0 +1,68 @@ +// Mock API functions for development +export interface BacktestMetrics { + totalReturn: number; + annualizedReturn: number; + volatility: number; + sharpeRatio: number; + maxDrawdown: number; + winRate: number; + turnover: number; +} + +export interface BacktestResult { + metrics: BacktestMetrics; + portfolioSnapshots: any[]; + trades: any[]; +} + +export const api = { + async runBacktest(config: any): Promise { + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 2000)); + + return { + metrics: { + totalReturn: 0.2345, + annualizedReturn: 0.0876, + volatility: 0.1567, + sharpeRatio: 1.234, + maxDrawdown: 0.1234, + winRate: 0.645, + turnover: 2.34 + }, + portfolioSnapshots: generateDemoSnapshots(), + trades: [] + }; + }, + + async getAssets(): Promise { + return [ + { symbol: 'AAPL', name: 'Apple Inc.', sector: 'Technology', marketCap: 2800000000000, price: 182.63 }, + { symbol: 'MSFT', name: 'Microsoft Corp.', sector: 'Technology', marketCap: 2750000000000, price: 370.73 }, + { symbol: 'GOOGL', name: 'Alphabet Inc.', sector: 'Technology', marketCap: 1750000000000, price: 138.21 }, + { symbol: 'AMZN', name: 'Amazon.com Inc.', sector: 'Consumer', marketCap: 1500000000000, price: 145.18 }, + { symbol: 'TSLA', name: 'Tesla Inc.', sector: 'Consumer', marketCap: 750000000000, price: 238.83 } + ]; + } +}; + +function generateDemoSnapshots() { + const snapshots = []; + let value = 100000; + const startDate = new Date('2020-01-01'); + + for (let i = 0; i < 100; i++) { + const date = new Date(startDate); + date.setDate(date.getDate() + i * 7); + value *= 1 + (Math.random() - 0.45) * 0.02; + + snapshots.push({ + timestamp: date.toISOString(), + totalValue: value, + cash: 10000, + holdingsValue: value - 10000 + }); + } + + return snapshots; +} \ 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 00000000..990ed999 --- /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