quanthedge / frontend /src /pages /BacktestResults.tsx
jashdoshi77's picture
added dark mode
e85ce30
import { useState, useEffect } from 'react';
import { XAxis, YAxis, Tooltip, ResponsiveContainer, AreaChart, Area } from 'recharts';
import { backtestAPI, strategyAPI } from '../api/client';
export default function BacktestResults() {
const [strategies, setStrategies] = useState<any[]>([]);
const [backtests, setBacktests] = useState<any[]>([]);
const [selectedBt, setSelectedBt] = useState<any>(null);
const [form, setForm] = useState({ strategy_id: 0, start_date: '2023-01-01', end_date: '2024-12-31', initial_capital: 1000000, commission_pct: 0.001 });
const [running, setRunning] = useState(false);
useEffect(() => {
Promise.all([strategyAPI.list(), backtestAPI.list()])
.then(([s, b]) => { setStrategies(s.data.strategies || []); setBacktests(b.data.backtests || []); })
.catch(console.error);
}, []);
const runBacktest = async () => {
if (!form.strategy_id) return;
setRunning(true);
try {
const { data } = await backtestAPI.run(form);
setSelectedBt(data);
const b = await backtestAPI.list();
setBacktests(b.data.backtests || []);
} catch (e: any) { alert(e.response?.data?.detail || 'Failed'); } finally { setRunning(false); }
};
const viewBt = async (id: number) => {
try { const { data } = await backtestAPI.get(id); setSelectedBt(data); } catch (e) { console.error(e); }
};
const m = selectedBt?.metrics || {};
const F = (v: any, pct = false) => v != null ? (pct ? (v * 100).toFixed(2) + '%' : v.toFixed(2)) : '—';
return (
<div className="page animate-fade-in">
<div className="page-header"><h1>Backtest <span className="text-gradient">Results</span></h1><p>Run simulations and analyze strategy performance</p></div>
<div className="card" style={{marginBottom:'1.5rem'}}>
<div style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
<div><label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em',display:'block',marginBottom:'0.375rem'}}>Strategy</label>
<select className="input" style={{width:220}} value={form.strategy_id} onChange={e => setForm(f => ({...f, strategy_id: +e.target.value}))}>
<option value={0}>Select...</option>{strategies.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
</select></div>
<div><label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',display:'block',marginBottom:'0.375rem'}}>Start</label>
<input className="input" type="date" style={{width:160}} value={form.start_date} onChange={e => setForm(f => ({...f, start_date: e.target.value}))} /></div>
<div><label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',display:'block',marginBottom:'0.375rem'}}>End</label>
<input className="input" type="date" style={{width:160}} value={form.end_date} onChange={e => setForm(f => ({...f, end_date: e.target.value}))} /></div>
<button className="btn btn-primary" onClick={runBacktest} disabled={running || !form.strategy_id}>{running ? 'Running...' : '▶ Run'}</button>
</div>
</div>
<div className="grid-2" style={{gridTemplateColumns: selectedBt ? '1fr 300px' : '1fr'}}>
{selectedBt && (
<div style={{display:'flex',flexDirection:'column',gap:'1.5rem'}}>
<div className="card">
<div className="card-header"><h3>Performance Metrics</h3></div>
<div className="grid-4">
{[{l:'Total Return',v:F(m.total_return,true),c:m.total_return>0?'positive':'negative'},{l:'Ann. Return',v:F(m.annualized_return,true),c:m.annualized_return>0?'positive':'negative'},
{l:'Sharpe',v:F(m.sharpe_ratio),c:m.sharpe_ratio>1?'positive':'neutral'},{l:'Max DD',v:F(m.max_drawdown,true),c:'negative'},
{l:'Volatility',v:F(m.volatility,true),c:''},{l:'Win Rate',v:F(m.win_rate,true),c:m.win_rate>0.5?'positive':'negative'},
{l:'Trades',v:m.total_trades||0,c:''},{l:'Sortino',v:F(m.sortino_ratio),c:''}
].map((x,i) => <div key={i} className="metric" style={{padding:'0.75rem'}}><div className={`metric-value ${x.c}`} style={{fontSize:'1.3rem'}}>{x.v}</div><div className="metric-label">{x.l}</div></div>)}
</div>
</div>
<div className="card">
<div className="card-header"><h3>Equity Curve</h3></div>
<div className="chart-container" style={{height:260,padding:'0.5rem'}}>
<ResponsiveContainer><AreaChart data={selectedBt.equity_curve||[]}>
<defs><linearGradient id="eqG" x1="0" y1="0" x2="0" y2="1"><stop offset="5%" stopColor="var(--chart-green)" stopOpacity={0.18}/><stop offset="95%" stopColor="var(--chart-green)" stopOpacity={0}/></linearGradient></defs>
<XAxis dataKey="date" tick={{fontSize:9,fill:'var(--chart-axis)'}} tickFormatter={v=>v?.slice(5)}/>
<YAxis tick={{fontSize:9,fill:'var(--chart-axis)'}} tickFormatter={v=>`$${(v/1e6).toFixed(1)}M`}/>
<Tooltip contentStyle={{background:'var(--chart-tooltip-bg)',border:'1px solid var(--chart-tooltip-border)',borderRadius:8,fontSize:'0.8rem',boxShadow:'var(--shadow-md)'}}/>
<Area type="monotone" dataKey="portfolio_value" stroke="var(--chart-green)" fill="url(#eqG)" strokeWidth={2} dot={false}/>
</AreaChart></ResponsiveContainer>
</div>
</div>
</div>
)}
<div className="card" style={{alignSelf:'start'}}>
<div className="card-header"><h3>History ({backtests.length})</h3></div>
{backtests.map(bt => (
<div key={bt.id} onClick={() => viewBt(bt.id)} style={{padding:'0.75rem',cursor:'pointer',borderBottom:'1px solid var(--border-subtle)'}}>
<div style={{fontSize:'0.85rem',fontWeight:600}}>{bt.name||`#${bt.id}`}</div>
<div style={{display:'flex',justifyContent:'space-between',marginTop:'0.25rem'}}>
<span style={{fontSize:'0.7rem',color:'var(--text-muted)'}}>{bt.start_date}</span>
{bt.sharpe_ratio!=null&&<span className={`badge ${bt.sharpe_ratio>0?'badge-emerald':'badge-rose'}`}>SR: {bt.sharpe_ratio.toFixed(2)}</span>}
</div>
</div>
))}
{!backtests.length&&<p style={{fontSize:'0.8rem',color:'var(--text-muted)',padding:'1rem 0'}}>No backtests yet</p>}
</div>
</div>
</div>
);
}