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:
parent
82d30bde80
commit
a2c08743ac
3 changed files with 236 additions and 31 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue