main
tsx 54 lines 4.31 KB
Raw
1 "use client";
2 import { useEffect, useState } from "react";
3 import { request } from "../lib/portfolio-api";
4
5 type Metric = { recommendation_count: number; evaluated_count: number; missing_count: number;
6 hit_rate: number | null; average_return: number | null; median_return: number | null };
7 type Example = { recommendation_id: string; global_instrument_id: string; symbol?: string; company_name?: string; return_pct: number };
8 export type Backtest = { backtest_id: string; generated_at: string; recommendation_count: number;
9 metrics: Record<string, Metric>; winners: Example[]; losers: Example[]; methodology: string };
10 const pct = (v: number | null) => v == null ? "Insufficient evidence" : `${v.toFixed(2)}%`;
11
12 export function BacktestResults({ run }: { run: Backtest }) {
13 return <section aria-label="Backtest results"><h3>Recommendations tested: {run.recommendation_count}</h3>
14 <p>{run.methodology}</p><div style={{ overflowX: "auto" }}><table><thead><tr><th>Horizon</th><th>Evaluated</th><th>Missing</th><th>Hit rate</th><th>Average return</th><th>Median return</th></tr></thead>
15 <tbody>{["1W", "1M", "3M", "6M", "1Y"].map(h => <tr key={h}><th>{h}</th><td>{run.metrics[h].evaluated_count}</td><td>{run.metrics[h].missing_count}</td>
16 <td>{pct(run.metrics[h].hit_rate)}</td><td>{pct(run.metrics[h].average_return)}</td><td>{pct(run.metrics[h].median_return)}</td></tr>)}</tbody></table></div>
17 <h4>Winner examples</h4>{run.winners.length ? <ul>{run.winners.map(e => <li key={e.recommendation_id}>{e.company_name ?? e.symbol ?? "Company unavailable"}: {pct(e.return_pct)}</li>)}</ul> : <p>No winner evidence yet.</p>}
18 <h4>Loser examples</h4>{run.losers.length ? <ul>{run.losers.map(e => <li key={e.recommendation_id}>{e.company_name ?? e.symbol ?? "Company unavailable"}: {pct(e.return_pct)}</li>)}</ul> : <p>No loser evidence yet.</p>}
19 </section>;
20 }
21
22 export function Backtesting() {
23 const [start, setStart] = useState(""); const [end, setEnd] = useState("");
24 const [horizon, setHorizon] = useState("SHORT_TERM"); const [runs, setRuns] = useState<Backtest[]>([]);
25 const [selected, setSelected] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState("");
26 useEffect(() => { let active = true;
27 request<Backtest[]>("/api/v1/research/backtesting/runs").then(r => { if (active) { setRuns(r); setSelected(r[0]?.backtest_id ?? ""); } }).catch(() => { if (active) setError("Unable to load backtests."); });
28 return () => { active = false; };
29 }, []);
30 async function run() {
31 setBusy(true); setError("");
32 try {
33 const result = await request<Backtest>("/api/v1/research/backtesting/runs", { method: "POST", body: JSON.stringify({
34 start: `${start}T00:00:00Z`, end: new Date(Math.min(new Date(`${end}T23:59:59Z`).getTime(), Date.now())).toISOString(), market: "NSE", horizon }) });
35 setRuns(old => [result, ...old]); setSelected(result.backtest_id);
36 } catch { setError("Backtest could not run. Check the date range and try again."); }
37 finally { setBusy(false); }
38 }
39 const result = runs.find(r => r.backtest_id === selected);
40 return <section className="opportunity-radar"><h2>BACKTESTING</h2>
41 <p>Evaluate persisted recommendations using recorded evidence and subsequent prices.</p>
42 <form onSubmit={e => { e.preventDefault(); void run(); }}>
43 <label>Start date <input type="date" required value={start} onChange={e => setStart(e.target.value)} /></label>{" "}
44 <label>End date <input type="date" required min={start} value={end} onChange={e => setEnd(e.target.value)} /></label>{" "}
45 <label>Market <select><option>NSE</option></select></label>{" "}
46 <label>Horizon <select value={horizon} onChange={e => setHorizon(e.target.value)}><option value="SHORT_TERM">Short-term</option><option value="LONG_TERM">Long-term</option></select></label>{" "}
47 <button disabled={busy}>{busy ? "Running…" : "Run backtest"}</button>
48 </form>
49 {error && <p role="alert">{error}</p>}
50 <label>Persisted backtest <select value={selected} onChange={e => setSelected(e.target.value)}><option value="">Select a run</option>
51 {runs.map(r => <option key={r.backtest_id} value={r.backtest_id}>{r.generated_at} · {r.recommendation_count} recommendations</option>)}</select></label>
52 {result ? <BacktestResults run={result} /> : <p>No backtest selected.</p>}
53 </section>;
54 }