Multi-ticker historical backtests: ticker filter + per-coin grouping

- Historical cards now deduplicated by strategy+ticker (28 entries: 7×4)
- Ticker filter bar: ALL | BTC | ETH | HYPE | VVV
- Coin badge on each card
- BacktestSummary.coin now required string field
- fetchHistorical groups by strategy · coin composite key
This commit is contained in:
ramseshk
2026-08-05 05:11:32 +00:00
parent 96ca132fa2
commit 6665d0cd2a
4 changed files with 22 additions and 5 deletions
+16 -2
View File
@@ -32,6 +32,7 @@ export default function Dashboard() {
const [feeTier, setFeeTier] = useState(0);
const [stakingTier, setStakingTier] = useState("none");
const [posOpen, setPosOpen] = useState(false);
const [tickerFilter, setTickerFilter] = useState("ALL");
useEffect(() => { fetchHistorical().then(setHistorical); }, []);
@@ -301,6 +302,18 @@ export default function Dashboard() {
</div>
<main className="max-w-[1440px] mx-auto px-6 py-6">
{/* Ticker filter for Historical tab */}
{tab === "historical" && (
<div className="flex items-center gap-2 mb-4 flex-wrap">
<span className="text-[9px] text-muted-foreground uppercase tracking-wider mr-1">Ticker:</span>
{["ALL", "BTC", "ETH", "HYPE", "VVV"].map((t) => (
<button key={t} onClick={() => setTickerFilter(t)}
className={`text-[10px] px-3 py-1 rounded-md border transition-colors ${tickerFilter === t ? "bg-primary text-primary-foreground border-primary" : "bg-card text-muted-foreground border-border hover:border-primary/50"}`}>
{t}
</button>
))}
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 mb-6">
<AnimatePresence mode="popLayout">
{Object.entries(strategies).map(([name, s], i) => (
@@ -309,10 +322,11 @@ export default function Dashboard() {
</motion.div>
))}
</AnimatePresence>
{tab === "historical" && Object.entries(historical).map(([name, b], i) => (
{tab === "historical" && Object.entries(historical).filter(([, b]) => tickerFilter === "ALL" || b.coin === tickerFilter).map(([name, b], i) => (
<motion.div key={name} layout initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.2, delay: i * 0.03 }}>
<StrategyCard name={name} tab="historical" onClick={() => handleCardClick(name, "historical")}
badge={`30d · ${b.coin ?? "BTC"} · Mainnet`}
coin={String(b.coin ?? "?")}
badge={`30d · Mainnet`}
stats={[{ label: "Sharpe", value: b.sharpe.toFixed(2) }, { label: "Max DD", value: `${(b.max_dd * 100).toFixed(1)}%`, negative: true }, { label: "Win", value: `${Math.round(b.win_rate * 100)}%` }]}
pnlPct={b.pnl_pct} status="REAL DATA" />
</motion.div>
@@ -11,12 +11,13 @@ interface StrategyCardProps {
tab: "live" | "paper" | "backtest" | "historical";
onClick: () => void;
badge?: string;
coin?: string;
stats?: { label: string; value: string; negative?: boolean }[];
pnlPct?: number;
status?: string;
}
export function StrategyCard({ name, strategy, tab, onClick, badge, stats, pnlPct, status }: StrategyCardProps) {
export function StrategyCard({ name, strategy, tab, onClick, badge, stats, pnlPct, status, coin }: StrategyCardProps) {
if (strategy) {
const equity = strategy.allocation + (strategy.pnl ?? 0);
const isUp = equity >= strategy.allocation;
@@ -76,6 +77,7 @@ export function StrategyCard({ name, strategy, tab, onClick, badge, stats, pnlPc
<p className="text-[9px] text-muted-foreground mt-0.5">{badge ?? "Backtest"}</p>
</div>
<Badge variant="secondary" className="text-[8px] h-4 px-1.5">{status ?? "BACKTEST"}</Badge>
{coin && <Badge variant="outline" className="text-[8px] h-4 px-1.5 bg-blue-500/10 text-blue-400 border-0">{coin}</Badge>}
</div>
<div className={`text-xl font-mono font-bold mb-2 flex items-center gap-1 ${(pnlPct ?? 0) >= 0 ? "text-green-500" : "text-red-500"}`}>
{(pnlPct ?? 0) >= 0 ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
+2 -1
View File
@@ -82,7 +82,8 @@ export async function fetchHistorical(): Promise<Record<string, import("./types"
const list: import("./types").BacktestSummary[] = await res.json();
const byStrat: Record<string, import("./types").BacktestSummary> = {};
for (const b of list) {
if (!byStrat[b.strategy]) byStrat[b.strategy] = b;
const key = `${b.strategy} · ${b.coin ?? "?"}`;
if (!byStrat[key]) byStrat[key] = b;
}
return byStrat;
}
+1 -1
View File
@@ -93,7 +93,7 @@ export interface BacktestSummary {
max_dd: number;
win_rate: number;
total_trades: number;
coin?: string;
coin: string;
}
export interface BacktestFull {