Hallmark redesign + QuantReport fix

Header/Tabs: Hallmark Cobalt aesthetic
  - Hairlines, cool paper bg, JetBrains Mono + Inter
  - Electric cobalt accent on active tab
  - No branding, no purple badges, no gradients

QuantReport: inline in strategy detail view
  - Renders below trade history on every tab
  - API maps strategy name -> file prefix
  - Proper backtestId from historical data

Server: strategy-name-to-prefix lookup
  ofi, avellaneda, iceberg, momentum, mean_rev,
  funding_arb, kalman_pairs, pairs
This commit is contained in:
ramseshk
2026-08-06 04:14:21 +00:00
parent 8cb59239c6
commit 98ee58dfaa
3 changed files with 39 additions and 23 deletions
+13 -3
View File
@@ -13,6 +13,7 @@ import { PositionsPanel } from "@/components/positions-panel";
import { OBIDetail } from "@/components/obi-detail"; import { OBIDetail } from "@/components/obi-detail";
import OrderBookDepthMap from "@/components/orderbook-depth-map"; import OrderBookDepthMap from "@/components/orderbook-depth-map";
import L2Terminal from "@/components/L2Terminal"; import L2Terminal from "@/components/L2Terminal";
import QuantReport from "@/components/QuantReport";
import { useLiveMetrics, usePaperMetrics, fetchHistorical, fetchBacktestDetail, recalcBacktest } from "@/lib/api"; import { useLiveMetrics, usePaperMetrics, fetchHistorical, fetchBacktestDetail, recalcBacktest } from "@/lib/api";
import type { Strategy, BacktestSummary, BacktestFull, Trade, Position, Order } from "@/lib/types"; import type { Strategy, BacktestSummary, BacktestFull, Trade, Position, Order } from "@/lib/types";
@@ -102,7 +103,7 @@ export default function Dashboard() {
if (detailOpen) { if (detailOpen) {
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-[#f8f9fb]">
<header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-xl"> <header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-xl">
<div className="flex items-center justify-between px-6 py-3 max-w-[1440px] mx-auto"> <div className="flex items-center justify-between px-6 py-3 max-w-[1440px] mx-auto">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@@ -277,7 +278,16 @@ export default function Dashboard() {
<p className="text-xs text-muted-foreground text-center py-12">No trades recorded yet</p> <p className="text-xs text-muted-foreground text-center py-12">No trades recorded yet</p>
)} )}
</div> </div>
{/* Live L2 Order Book + Trade Tape (all strategies, live tab only) */}
{/* QF-Lib Quant Report — Hallmark Cobalt inline */}
<div className="mt-6 border-t border-[#e0e4ec] pt-4">
<QuantReport
strategyName={detailName}
backtestId={historical[detailName]?.name || `${detailName.replace(/\s+/g, "_").toLowerCase()}.json`}
/>
</div>
{/* Live L2 Order Book + Trade Tape */}
{detailTab === "live" && ( {detailTab === "live" && (
<div className="mt-6"> <div className="mt-6">
<OrderBookDepthMap coin="BTC" height={480} topRatio={0.55} /> <OrderBookDepthMap coin="BTC" height={480} topRatio={0.55} />
@@ -292,7 +302,7 @@ export default function Dashboard() {
} }
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-[#f8f9fb]">
{/* Header — Hallmark Cobalt */} {/* Header — Hallmark Cobalt */}
<header className="sticky top-0 z-50 border-b border-[#e0e4ec] bg-[#f8f9fb]/95 backdrop-blur-sm"> <header className="sticky top-0 z-50 border-b border-[#e0e4ec] bg-[#f8f9fb]/95 backdrop-blur-sm">
<div className="flex items-center justify-between px-6 h-12 max-w-[1440px] mx-auto"> <div className="flex items-center justify-between px-6 h-12 max-w-[1440px] mx-auto">
+24 -18
View File
@@ -441,40 +441,46 @@ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
@app.get("/api/quant-report/{name}") @app.get("/api/quant-report/{name}")
async def get_quant_report(name: str): async def get_quant_report(name: str):
"""Compute full QF-Lib quant report from a backtest file. """Compute full QF-Lib quant report from a backtest file.
Accepts either an exact filename or a strategy-name prefix. Accepts strategy name and auto-maps to filename prefix.
""" """
# Strategy name → file prefix mapping
NAME_MAP = {
"order book imbalance": "ofi",
"avellaneda-stoikov": "avellaneda",
"funding rate arb": "funding_arb",
"iceberg detection": "iceberg",
"momentum breakout": "momentum",
"mean reversion": "mean_rev",
"kalman pairs": "kalman_pairs",
"pairs trading": "pairs",
}
name_lower = name.lower()
prefix = NAME_MAP.get(name_lower, name_lower.replace(" ", "_"))
# Build candidate paths # Build candidate paths
candidates = [] candidates = []
exact_path = os.path.join(BACKTEST_DIR, name) exact_path = os.path.join(BACKTEST_DIR, name)
hist_exact = os.path.join(HISTORICAL_DIR, name) hist_exact = os.path.join(HISTORICAL_DIR, name)
candidates.extend([exact_path, hist_exact]) candidates.extend([exact_path, hist_exact])
# Try exact match first # Try exact match
for path in candidates: for path in candidates:
if os.path.exists(path): if os.path.exists(path):
backtest_path = path backtest_path = path
break break
else: else:
# Fuzzy match: look for files containing the name # Fuzzy match: find files starting with the mapped prefix
fuzzy = []
for d in [BACKTEST_DIR, HISTORICAL_DIR]: for d in [BACKTEST_DIR, HISTORICAL_DIR]:
if not os.path.exists(d): continue if not os.path.exists(d): continue
for f in os.listdir(d): for f in os.listdir(d):
# Match: name is a substring of filename (case insensitive)
name_clean = name.lower().replace(" ", "_").replace(".json", "")
f_clean = f.lower() f_clean = f.lower()
if name_clean in f_clean or f_clean.startswith(name_clean): # Match by prefix, then prefer BTC/ETH files
candidates.append(os.path.join(d, f)) if f_clean.startswith(f"{prefix}_"):
if not candidates: fuzzy.append(os.path.join(d, f))
# Try partial match on strategy name if fuzzy:
for d in [BACKTEST_DIR, HISTORICAL_DIR]: backtest_path = fuzzy[0]
if not os.path.exists(d): continue
for f in os.listdir(d):
parts = f.lower().replace(".json", "").split("_")
name_parts = name.lower().replace(" ", "_").split("_")
if all(p in parts for p in name_parts):
candidates.append(os.path.join(d, f))
if candidates:
backtest_path = candidates[0]
else: else:
return JSONResponse({"error": f"Backtest '{name}' not found"}, status_code=404) return JSONResponse({"error": f"Backtest '{name}' not found"}, status_code=404)
try: try:
File diff suppressed because one or more lines are too long