import { useState } from 'react'
import { useBacktest } from '../hooks/useApi'
import clsx from 'clsx'
import {
AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer,
CartesianGrid, ReferenceLine,
} from 'recharts'
import { History, Play, TrendingUp, TrendingDown, AlertTriangle } from 'lucide-react'
import type { BacktestResult } from '../types'
const STRATEGIES = [
{ key: 'long_call', label: 'Long Call' },
{ key: 'long_put', label: 'Long Put' },
{ key: 'bull_call_spread', label: 'Bull Call Spread' },
{ key: 'bear_put_spread', label: 'Bear Put Spread' },
]
const SYMBOLS = [
'GLD', 'USO', 'WEAT', 'UNG', 'SPY', 'QQQ', 'GDX', 'COPX',
'XLE', 'FXE', 'XOM', 'LMT', 'BA', 'RTX',
]
function StatCard({ label, value, sub, positive }: { label: string; value: string; sub?: string; positive?: boolean }) {
return (
{label}
{value}
{sub &&
{sub}
}
)
}
export default function Backtest() {
const { mutate: runBacktest, data: result, isPending } = useBacktest()
const [form, setForm] = useState({
symbol: 'GLD',
start_date: '2022-01-01',
end_date: '2024-12-31',
strategy: 'long_call',
strike_offset_pct: 0.05,
expiry_days: 90,
capital: 1000,
})
const set = (k: string, v: unknown) => setForm(f => ({ ...f, [k]: v }))
const run = () => runBacktest(form as Record)
const typed = result as BacktestResult | undefined
const hasResult = typed && !typed.error
return (
Backtest & Simulation
Test your options strategies on real historical data
{/* Config panel */}
Configuration
{STRATEGIES.map(s => (
))}
set('start_date', e.target.value)}
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white mb-1 focus:outline-none focus:border-blue-500"
/>
set('end_date', e.target.value)}
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500"
/>
set('strike_offset_pct', Number(e.target.value))}
className="w-full accent-blue-500"
/>
{[30, 60, 90, 180].map(d => (
))}
set('capital', Number(e.target.value))}
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500"
/>
{/* Results panel */}
{typed?.error && (
)}
{hasResult && (
<>
{/* KPIs */}
= 0 ? '+' : ''}${typed.total_return_pct.toFixed(2)}%`}
positive={typed.total_return_pct >= 0}
/>
= 50}
/>
= 1}
/>
{/* Equity curve */}
Equity curve — {form.symbol} {STRATEGIES.find(s => s.key === form.strategy)?.label}
Final capital: {typed.final_capital.toFixed(2)}€
{' '}(initial: {form.capital}€ · P&L: {typed.total_pnl >= 0 ? '+' : ''}{typed.total_pnl.toFixed(2)}€)
= 0 ? '#10b981' : '#ef4444'} stopOpacity={0.3} />
= 0 ? '#10b981' : '#ef4444'} stopOpacity={0} />
`${v.toFixed(0)}€`} />
[`${v.toFixed(2)}€`, 'Capital']}
/>
= 0 ? '#10b981' : '#ef4444'}
fill="url(#equity-grad)" strokeWidth={2} dot={false}
/>
{/* Last trades */}
Last executed trades
| Entry |
Exit |
Entry price |
Strike |
Premium |
Exit price |
P&L |
Capital |
{typed.trades.map((t, i) => {
const pnl = t.pnl as number
return (
| {t.entry_date as string} |
{t.exit_date as string} |
${(t.S_entry as number).toFixed(2)} |
${(t.K as number).toFixed(2)} |
${(t.premium as number).toFixed(4)} |
${(t.S_expiry as number).toFixed(2)} |
= 0 ? 'positive' : 'negative')}>
{pnl >= 0 ? '+' : ''}{pnl.toFixed(2)}€
|
{(t.capital as number).toFixed(2)}€ |
)
})}
>
)}
{!hasResult && !isPending && !typed?.error && (
Configure and run a backtest
yfinance data — full history available
)}
)
}