From a2c08743ac7de0e8e7fc5a2c47e139770a1ef3bd Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Mon, 23 Feb 2026 22:25:41 +0000 Subject: [PATCH] feat: make backtest work end-to-end with Alpaca bars, ticker selection, all 9 strategies - Change BacktestRequest from strategy_weights dict to strategies list to match frontend - Add tickers field so users can select which stocks to backtest - Fetch historical bars from Alpaca StockHistoricalDataClient instead of empty data loader - Register all 9 strategies (momentum, mean_reversion, news_driven, value, macd_crossover, bollinger_breakout, vwap, liquidity, ma_stack) filtered by user selection - Fix response format: use frontend field names (max_drawdown, total_trades, win_rate as 0-1 decimal), include equity_curve and run_id in response - Add ticker selector with checkboxes and custom ticker input to dashboard - Add alpaca-py to api dependency group in pyproject.toml --- dashboard/src/pages/Backtest.tsx | 83 ++++++++++- pyproject.toml | 2 +- services/api_gateway/routes/backtest.py | 182 ++++++++++++++++++++---- 3 files changed, 236 insertions(+), 31 deletions(-) diff --git a/dashboard/src/pages/Backtest.tsx b/dashboard/src/pages/Backtest.tsx index 3071616..48e6bba 100644 --- a/dashboard/src/pages/Backtest.tsx +++ b/dashboard/src/pages/Backtest.tsx @@ -4,11 +4,14 @@ import client from '../api/client'; import { EquityCurve } from '../components/EquityCurve'; import { MetricsRow } from '../components/MetricsRow'; +const DEFAULT_TICKERS = ['AAPL', 'TSLA', 'NVDA', 'MSFT', 'GOOGL']; + interface BacktestConfig { start_date: string; end_date: string; initial_capital: number; strategies: string[]; + tickers: string[]; } interface BacktestResult { @@ -37,6 +40,8 @@ export default function Backtest() { const [endDate, setEndDate] = useState('2026-01-01'); const [initialCapital, setInitialCapital] = useState(100000); const [selectedStrategies, setSelectedStrategies] = useState([]); + const [selectedTickers, setSelectedTickers] = useState([...DEFAULT_TICKERS]); + const [customTicker, setCustomTicker] = useState(''); const [currentRunId, setCurrentRunId] = useState(null); const { data: strategyOptions } = useQuery({ @@ -74,12 +79,13 @@ export default function Backtest() { }); const handleSubmit = () => { - if (!startDate || !endDate || selectedStrategies.length === 0) return; + if (!startDate || !endDate || selectedStrategies.length === 0 || selectedTickers.length === 0) return; runMutation.mutate({ start_date: startDate, end_date: endDate, initial_capital: initialCapital, strategies: selectedStrategies, + tickers: selectedTickers, }); }; @@ -89,6 +95,20 @@ export default function Backtest() { ); }; + const toggleTicker = (ticker: string) => { + setSelectedTickers((prev) => + prev.includes(ticker) ? prev.filter((t) => t !== ticker) : [...prev, ticker] + ); + }; + + const addCustomTicker = () => { + const ticker = customTicker.trim().toUpperCase(); + if (ticker && !selectedTickers.includes(ticker)) { + setSelectedTickers((prev) => [...prev, ticker]); + } + setCustomTicker(''); + }; + const metricsDisplay = result?.metrics ? [ { @@ -132,7 +152,7 @@ export default function Backtest() {

Configuration

-
+
+
+ +
+ +
+ +
+ {DEFAULT_TICKERS.map((ticker) => ( + + ))} + {selectedTickers + .filter((t) => !DEFAULT_TICKERS.includes(t)) + .map((ticker) => ( + + ))} +
+ setCustomTicker(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && addCustomTicker()} + placeholder="Add ticker..." + className="w-28 px-2 py-1 bg-slate-700 border border-slate-600 rounded text-white text-sm focus:outline-none focus:ring-1 focus:ring-blue-500" + /> + +
+
+