Spaces:
Running on Zero
Running on Zero
| """HTML fragments in the Bit design language. | |
| Gradio gives us controls; the design needs panels, chips, stat bands and | |
| micro-labels that Gradio has no equivalent for. Those live here as small pure | |
| functions returning markup, so `app.py` stays layout and wiring only. | |
| Every fragment uses design-system tokens (`var(--...)`) rather than raw colour, | |
| so a theme change in `assets/tokens/colors.css` propagates without edits here. | |
| """ | |
| from __future__ import annotations | |
| import pandas as pd | |
| from .. import config | |
| from .format import EM, arrow, count, esc, money, num, pct, seg, sharpe_tone, tone | |
| # The actual mark shipped with the design (uploads/bit-trading-mark.svg), | |
| # inlined so it can be recoloured through a token rather than loaded as a | |
| # fixed-colour file. Previously this was a CSS box-shadow approximation -- | |
| # a guess at a logo that was sitting in the design folder the whole time. | |
| MARK_SVG = ( | |
| '<svg class="bit-mark" viewBox="0 0 100 100" role="img" ' | |
| 'aria-label="The Bit Trading Company">' | |
| '<path fill="currentColor" fill-rule="evenodd" ' | |
| 'd="M0 0H100V100H0Z M50 10H90V90H50Z M22 42H38V58H22Z M62 42H78V58H62Z">' | |
| '</path></svg>' | |
| ) | |
| # -------------------------------------------------------------------------- | |
| # Primitives | |
| # -------------------------------------------------------------------------- | |
| def micro(text: str, color: str = "var(--text-tertiary)") -> str: | |
| return f'<div class="bit-micro" style="color:{color}">{esc(text)}</div>' | |
| def chip(text: str, kind: str = "") -> str: | |
| cls = {"ok": " bit-chip-ok", "run": " bit-chip-run", | |
| "warn": " bit-chip-warn", "accent": " bit-chip-accent"}.get(kind, "") | |
| return f'<span class="bit-chip{cls}">{esc(text)}</span>' | |
| def note(text: str, danger: bool = False) -> str: | |
| cls = "bit-note bit-note-danger" if danger else "bit-note" | |
| return f'<div class="{cls}">{text}</div>' | |
| def panel_head(title: str, meta: str = "", right: str = "") -> str: | |
| m = f'<span class="bit-micro">{esc(meta)}</span>' if meta else "" | |
| r = f'<span style="margin-left:auto">{right}</span>' if right else "" | |
| return (f'<div class="bit-panel-head"><span class="bit-h3">{esc(title)}</span>' | |
| f'{m}{r}</div>') | |
| def panel(title: str, body: str, meta: str = "", right: str = "") -> str: | |
| return f'<div class="bit-panel">{panel_head(title, meta, right)}{body}</div>' | |
| def kbd(text: str) -> str: | |
| return f'<span class="bit-kbd">{esc(text)}</span>' | |
| # -------------------------------------------------------------------------- | |
| # Top bar & footer | |
| # -------------------------------------------------------------------------- | |
| def top_bar(status_text: str = "NO RUN LOADED", status_kind: str = "", | |
| context: str = "", elapsed: str = "") -> str: | |
| ctx = f'<span class="bit-chip bit-chip-ctx">{esc(context)}</span>' if context else "" | |
| el = f'<span class="bit-micro" style="margin:0 4px">{esc(elapsed)}</span>' if elapsed else "" | |
| return f""" | |
| <div class="bit-topbar"> | |
| {MARK_SVG} | |
| <span class="bit-wordmark">BIT</span> | |
| <span class="bit-slash">/</span> | |
| <span class="bit-h1">Backtest Lab</span> | |
| {ctx} | |
| <span class="bit-spacer"></span> | |
| {el}{chip(status_text, status_kind)} | |
| <a class="bit-chip bit-link" target="_blank" rel="noopener" | |
| href="https://huggingface.co/datasets/{config.STORE_REPO}">SIGNAL STORE ↗</a> | |
| <a class="bit-chip bit-link" target="_blank" rel="noopener" | |
| href="https://huggingface.co/spaces/{config.SPACE_REPO}">SPACE ↗</a> | |
| </div>""" | |
| def footer() -> str: | |
| return f""" | |
| <div class="bit-footer"> | |
| <span>{esc(config.DISCLAIMER)}</span> | |
| <span class="bit-footer-right">BITTRADING BACKTEST LAB v1.1.1</span> | |
| </div>""" | |
| # -------------------------------------------------------------------------- | |
| # Empty & loading states | |
| # -------------------------------------------------------------------------- | |
| def empty_state() -> str: | |
| return f""" | |
| <div class="bit-empty"> | |
| <div class="bit-empty-glyph">◴</div> | |
| <div class="bit-h2">No run loaded</div> | |
| <div class="bit-empty-copy"> | |
| Configure a strategy on the left, or start from a worked example and edit it. | |
| </div> | |
| <div class="bit-kbd-row"> | |
| {kbd("RUN BACKTEST")}{kbd("COSTS DEFAULT ON")}{kbd("FILLS AT NEXT BAR OPEN")} | |
| </div> | |
| </div>""" | |
| STAGE_LABELS = ("Reading cached slices", "Simulating trades", | |
| "Walking forward", "Computing robustness") | |
| def loading_stages(active: int = 0) -> str: | |
| rows = [] | |
| for i, label in enumerate(STAGE_LABELS): | |
| mark = "✓" if i < active else ("▸" if i == active else "·") | |
| color = ("var(--accent-moss-strong)" if i < active else | |
| "var(--text-primary)" if i == active else "var(--text-tertiary)") | |
| rows.append(f'<div class="bit-stage" style="color:{color}">{mark} {esc(label)}</div>') | |
| return f'<div class="bit-panel">{"".join(rows)}</div>' | |
| # -------------------------------------------------------------------------- | |
| # Stat band | |
| # -------------------------------------------------------------------------- | |
| def stat_band(rec) -> str: | |
| """The design's stat band: big mono number, tiny label, IS/OOS underneath.""" | |
| if rec is None: | |
| return "" | |
| r = rec.result | |
| a, i, o = r.metrics_all, r.metrics_is, r.metrics_oos | |
| def isoos(fmt, key, *fargs): | |
| return (f"IS {seg(i, fmt, getattr(i, key), *fargs)} · " | |
| f"OOS {seg(o, fmt, getattr(o, key), *fargs)}") | |
| bench = (float(r.benchmark_equity.iloc[-1] / r.benchmark_equity.iloc[0] - 1.0) | |
| if len(r.benchmark_equity) else float("nan")) | |
| gap = a.total_return - bench if pd.notna(bench) else float("nan") | |
| cells = [ | |
| ("Total return", f"{pct(a.total_return)}{arrow(a.total_return)}", | |
| tone(a.total_return), isoos(pct, "total_return"), | |
| "Cumulative return of the strategy equity curve, costs included."), | |
| ("CAGR", pct(a.cagr), tone(a.cagr), isoos(pct, "cagr"), | |
| "Compound annual growth rate implied by the equity curve."), | |
| ("Sharpe", num(a.sharpe), tone(a.sharpe), isoos(num, "sharpe"), | |
| "Annualized mean excess return over return volatility."), | |
| ("Sortino", num(a.sortino), tone(a.sortino), isoos(num, "sortino"), | |
| "Sharpe with only downside deviation in the denominator."), | |
| ("Max drawdown", pct(a.max_drawdown), "bit-down", | |
| isoos(pct, "max_drawdown"), "Worst peak-to-trough decline."), | |
| ("Win rate", pct(a.win_rate, 0, signed=False), "", | |
| f"IS {seg(i, pct, i.win_rate, 0, False)} · OOS {seg(o, pct, o.win_rate, 0, False)}", | |
| "Share of closed trades with positive net P&L."), | |
| ("Profit factor", num(a.profit_factor), tone(a.profit_factor - 1.0), | |
| isoos(num, "profit_factor"), "Gross profit over gross loss."), | |
| ("Trades", count(a.trade_count), "", | |
| f"IS {seg(i, count, i.trade_count)} · OOS {seg(o, count, o.trade_count)}", | |
| "Closed round-trip trades."), | |
| ("Exposure", pct(a.exposure, 0, signed=False), "", | |
| f"IS {seg(i, pct, i.exposure, 0, False)} · OOS {seg(o, pct, o.exposure, 0, False)}", | |
| "Fraction of bars holding a position."), | |
| ("vs buy & hold", f"{pct(gap)}{arrow(gap)}", tone(gap), | |
| f"costs paid {money(r.costs_paid)}", | |
| "Strategy return minus buy-and-hold over the same window."), | |
| ] | |
| html = ['<div class="bit-statband">'] | |
| for label, value, cls, sub, tip in cells: | |
| html.append( | |
| f'<div class="bit-stat" title="{esc(tip)}">' | |
| f'<div class="bit-stat-label">{esc(label)}</div>' | |
| f'<div class="bit-stat-value {cls}">{value}</div>' | |
| f'<div class="bit-stat-sub">{sub}</div></div>') | |
| html.append("</div>") | |
| for n in getattr(r.plan, "notes", []): | |
| html.append(note(esc(n), danger=True)) | |
| if r.metrics_holdout is not None: | |
| h = r.metrics_holdout | |
| ok = h.total_return > 0 | |
| html.append(note( | |
| f'<b>LOCKED HOLDOUT</b> · return {pct(h.total_return)} · ' | |
| f'Sharpe {num(h.sharpe)} · {h.bars} bars never used for any parameter ' | |
| f'choice.' + ("" if ok else " <b>It loses money here.</b>"), | |
| danger=not ok)) | |
| return "".join(html) | |
| # -------------------------------------------------------------------------- | |
| # Run manager | |
| # -------------------------------------------------------------------------- | |
| def run_card(rec, selected: bool = False) -> str: | |
| s = rec.sharpe | |
| color = sharpe_tone(s) | |
| mark = "✓" if selected else "" | |
| return ( | |
| f'<div class="bit-run-card{" bit-run-card-sel" if selected else ""}">' | |
| f'<div class="bit-run-top">' | |
| f'<span class="bit-run-name">{esc(rec.label[:36])}</span>' | |
| f'<span class="bit-run-mark">{mark}</span></div>' | |
| f'<div class="bit-micro">{esc(rec.meta)}</div>' | |
| f'<div class="bit-run-sharpe" style="color:{color}">' | |
| f'SHARPE {num(s)}<span class="bit-run-ret">{pct(rec.result.metrics_all.total_return)}</span>' | |
| f'</div></div>') | |
| def run_history(hist, selected_ids: set[str] | None = None) -> str: | |
| if not hist: | |
| return micro("no runs yet in this session") | |
| sel = selected_ids or set() | |
| head = micro(f"{len(hist)} run{'s' if len(hist) != 1 else ''} this session") | |
| return head + "".join(run_card(r, r.run_id in sel) for r in hist[:14]) | |
| def glossary(items) -> str: | |
| rows = "".join( | |
| f'<div class="bit-gloss"><div class="bit-micro">{esc(t)}</div>' | |
| f'<div class="bit-gloss-def">{esc(d)}</div></div>' | |
| for t, d in items) | |
| return f'<div class="bit-panel">{rows}</div>' | |
| # -------------------------------------------------------------------------- | |
| # Coverage | |
| # -------------------------------------------------------------------------- | |
| def coverage_summary(cells) -> str: | |
| if not cells: | |
| return note("The signal store has no coverage yet.", danger=True) | |
| models = sorted({c.model_slug for c in cells}) | |
| assets = sorted({c.asset for c in cells}) | |
| tfs = sorted({c.timeframe for c in cells}) | |
| rows = sum(c.rows for c in cells) | |
| ph = sum(1 for c in cells if c.is_placeholder) | |
| body = ( | |
| f'<div class="bit-kpi-row">' | |
| f'{_kpi(len(cells), "slices")}{_kpi(len(models), "models")}' | |
| f'{_kpi(len(assets), "assets")}{_kpi(len(tfs), "timeframes")}' | |
| f'{_kpi(f"{rows:,}", "signal rows")}' | |
| f'{_kpi(ph, "placeholder", danger=ph > 0)}</div>') | |
| return panel("Coverage", body, meta="SIGNAL STORE") | |
| def _kpi(value, label, danger: bool = False) -> str: | |
| color = "var(--fin-down)" if danger else "var(--text-primary)" | |
| return (f'<div class="bit-kpi"><div class="bit-kpi-value" style="color:{color}">' | |
| f'{esc(value)}</div><div class="bit-stat-label">{esc(label)}</div></div>') | |
| # -------------------------------------------------------------------------- | |
| # Leaderboard & consensus (the global compare interface) | |
| # -------------------------------------------------------------------------- | |
| def podium(df: pd.DataFrame, metric: str = "oos_sharpe") -> str: | |
| """Top three rows of the leaderboard, called out above the table.""" | |
| if df is None or df.empty: | |
| return micro("no catalog rows yet") | |
| top = df.head(3) | |
| cards = [] | |
| for rank, (_, r) in enumerate(top.iterrows(), start=1): | |
| val = r.get(metric) | |
| model = r.get("model_display") or EM | |
| cards.append( | |
| f'<div class="bit-podium bit-podium-{rank}">' | |
| f'<div class="bit-podium-rank">#{rank}</div>' | |
| f'<div class="bit-podium-name">{esc(r.get("strategy", ""))}</div>' | |
| f'<div class="bit-micro">{esc(r.get("asset", ""))} · ' | |
| f'{esc(r.get("timeframe", ""))}' | |
| + (f' · {esc(model)}' if r.get("model_slug") else "") + '</div>' | |
| f'<div class="bit-podium-value" style="color:{sharpe_tone(val)}">' | |
| f'{num(val)}<span class="bit-podium-unit">OOS SHARPE</span></div>' | |
| f'<div class="bit-micro">ret {pct(r.get("total_return"))} · ' | |
| f'dd {pct(r.get("max_drawdown"))} · {count(r.get("trades"))} trades</div>' | |
| f'</div>') | |
| return f'<div class="bit-podium-row">{"".join(cards)}</div>' | |
| def consensus_panel(consensus: pd.DataFrame, verdict: dict, | |
| asset: str, timeframe: str) -> str: | |
| """Signal Aggregator: every model's latest call for one asset, plus a | |
| calibration-weighted consensus. | |
| Weight is how far a model's realised directional accuracy sits above a coin | |
| flip on this exact slice, so a confident but historically wrong model does | |
| not get to shout. | |
| """ | |
| if consensus is None or consensus.empty: | |
| return note(f"No model signals cached for {esc(asset)} {esc(timeframe)}.") | |
| rows = [] | |
| for _, r in consensus.iterrows(): | |
| d = r["direction"] | |
| dcolor = ("var(--fin-up-strong)" if d == "LONG" else | |
| "var(--fin-down-strong)" if d == "SHORT" else "var(--text-tertiary)") | |
| glyph = "▲" if d == "LONG" else ("▼" if d == "SHORT" else "■") | |
| tag = ' <span class="bit-tag">BASELINE</span>' if r["is_baseline"] else "" | |
| acc = r["directional_accuracy"] | |
| rows.append( | |
| f'<div class="bit-sig-row">' | |
| f'<span class="bit-sig-name">{esc(r["model"])}{tag}</span>' | |
| f'<span class="bit-sig-dir" style="color:{dcolor}">{glyph} {d}</span>' | |
| f'<span class="bit-sig-edge" style="color:{dcolor}">{pct(r["edge"], 2)}</span>' | |
| f'<span class="bit-sig-acc">acc {pct(acc, 0, signed=False)}</span>' | |
| f'<span class="bit-sig-w">w{num(r["weight"])}</span>' | |
| f'</div>') | |
| d = verdict["direction"] | |
| vcolor = ("var(--fin-up-strong)" if d == "LONG" else | |
| "var(--fin-down-strong)" if d == "SHORT" else "var(--text-secondary)") | |
| glyph = "▲" if d == "LONG" else ("▼" if d == "SHORT" else "■") | |
| conf = verdict["confidence"] | |
| verdict_box = ( | |
| f'<div class="bit-consensus">' | |
| f'<div class="bit-micro">{esc(asset)} · {esc(timeframe)}</div>' | |
| f'<div class="bit-consensus-dir" style="color:{vcolor}">{d} {glyph}</div>' | |
| f'<div class="bit-micro">WEIGHTED EDGE {pct(verdict["edge"], 2)}</div>' | |
| f'<div class="bit-conf-track"><div class="bit-conf-fill" ' | |
| f'style="width:{conf * 100:.0f}%;background:{vcolor}"></div></div>' | |
| f'<div class="bit-micro">{verdict["agree"]}/{verdict["n_models"]} MODELS AGREE</div>' | |
| f'</div>') | |
| return panel( | |
| "Signal aggregator", | |
| f'<div class="bit-sig-grid"><div class="bit-sig-list">{"".join(rows)}</div>' | |
| f'{verdict_box}</div>' | |
| + micro("weight = realised directional accuracy above a coin flip, " | |
| "on this asset and timeframe"), | |
| meta=f"{len(consensus)} MODELS", | |
| ) | |
| def scorecard_note(sc: pd.DataFrame) -> str: | |
| """One honest sentence about whether the models beat the baselines.""" | |
| if sc is None or sc.empty: | |
| return "" | |
| learned = sc[~sc["is_baseline"]] if "is_baseline" in sc.columns else sc | |
| base = sc[sc["is_baseline"]] if "is_baseline" in sc.columns else pd.DataFrame() | |
| if learned.empty or base.empty: | |
| return "" | |
| la = learned["directional_accuracy"].mean() | |
| ba = base["directional_accuracy"].mean() | |
| if pd.isna(la) or pd.isna(ba): | |
| return "" | |
| delta = la - ba | |
| if delta > 0.01: | |
| return note(f"Learned models call direction correctly " | |
| f"{pct(la, 1, signed=False)} of the time against " | |
| f"{pct(ba, 1, signed=False)} for naive baselines " | |
| f"({pct(delta, 1)} better).") | |
| return note(f"Learned models call direction correctly " | |
| f"{pct(la, 1, signed=False)} of the time; naive baselines manage " | |
| f"{pct(ba, 1, signed=False)}. <b>The models are not beating the " | |
| f"baselines on direction.</b> Read the leaderboard with that in mind.", | |
| danger=True) | |