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
This commit is contained in:
Viktor Barzin 2026-02-23 22:25:41 +00:00
parent 82d30bde80
commit a2c08743ac
No known key found for this signature in database
GPG key ID: 0EB088298288D958
3 changed files with 236 additions and 31 deletions

View file

@ -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<string[]>([]);
const [selectedTickers, setSelectedTickers] = useState<string[]>([...DEFAULT_TICKERS]);
const [customTicker, setCustomTicker] = useState('');
const [currentRunId, setCurrentRunId] = useState<string | null>(null);
const { data: strategyOptions } = useQuery<StrategyOption[]>({
@ -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() {
<h3 className="text-lg font-semibold text-white mb-4">
Configuration
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
<div>
<label className="block text-xs text-slate-400 mb-1">
Start Date
@ -168,6 +188,9 @@ export default function Backtest() {
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label className="block text-xs text-slate-400 mb-1">
Strategies
@ -191,6 +214,61 @@ export default function Backtest() {
)}
</div>
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">
Tickers
</label>
<div className="space-y-2 mt-1">
{DEFAULT_TICKERS.map((ticker) => (
<label
key={ticker}
className="flex items-center gap-2 cursor-pointer"
>
<input
type="checkbox"
checked={selectedTickers.includes(ticker)}
onChange={() => toggleTicker(ticker)}
className="w-4 h-4 rounded border-slate-600 bg-slate-700 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-slate-300">{ticker}</span>
</label>
))}
{selectedTickers
.filter((t) => !DEFAULT_TICKERS.includes(t))
.map((ticker) => (
<label
key={ticker}
className="flex items-center gap-2 cursor-pointer"
>
<input
type="checkbox"
checked
onChange={() => toggleTicker(ticker)}
className="w-4 h-4 rounded border-slate-600 bg-slate-700 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-slate-300">{ticker}</span>
</label>
))}
<div className="flex items-center gap-2 mt-2">
<input
type="text"
value={customTicker}
onChange={(e) => 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"
/>
<button
type="button"
onClick={addCustomTicker}
className="px-2 py-1 bg-slate-600 hover:bg-slate-500 text-white text-sm rounded transition-colors"
>
+
</button>
</div>
</div>
</div>
</div>
<button
@ -198,6 +276,7 @@ export default function Backtest() {
disabled={
runMutation.isPending ||
selectedStrategies.length === 0 ||
selectedTickers.length === 0 ||
!startDate ||
!endDate
}