#!/usr/bin/env python """Render the trade ledger as a self-contained interactive HTML dashboard. .venv/bin/python dashboard.py # -> dashboard.html .venv/bin/python dashboard.py -o /tmp/out.html --open .venv/bin/python dashboard.py --since 2026-08-01 Round trips come from analytics.py. The page embeds them as JSON and does its own filtering/aggregation client-side, so the strategy, symbol and date-range filters recompute every chart without regenerating the file. No external assets, no CDN: the output is one portable HTML file. """ from __future__ import annotations import argparse import json import webbrowser from pathlib import Path import analytics # Reference thresholds drawn on the return-distribution chart. Read from config # when it is importable so the annotations follow the live parameters. try: from config import config as _cfg DEFAULT_MIN_PROFIT = _cfg.short_term.min_profit_pct DEFAULT_STOP_LOSS = _cfg.short_term.stop_loss_pct DEFAULT_MAX_DAILY_LOSS = _cfg.max_daily_loss except Exception: DEFAULT_MIN_PROFIT, DEFAULT_STOP_LOSS, DEFAULT_MAX_DAILY_LOSS = 1.5, 2.5, 150.0 HTML = r"""Bot Trade History

Bot Trade History & Performance

Realized equity curve

Cumulative net P&L by exit time. Shaded band is drawdown from the running peak.

Daily realized P&L

Net per calendar day. The dashed line marks the $ daily-loss circuit breaker.

By strategy

Net P&L; label shows trade count and win rate.

By symbol

Net P&L; label shows trade count and win rate.

By entry signal

Which signal actually pays. Needs reason in the ledger.

By exit reason

How trades end. Needs reason in the ledger.

Return distribution

Hold time vs return

One dot per closed trade. Colour is the owning strategy.

Open lots

Unmatched buy lots at the end of the ledger — cost basis only, not live marks.

Trade history

Every closed round trip in range, newest first. Click a header to sort. SELL* = exit price estimated (fill missed while the bot was offline).

""" def build(data: dict, min_profit: float, stop_loss: float, max_daily_loss: float) -> str: """Inject the analysis bundle into the page template.""" payload = { "ledger": data["ledger"], "generated": data["generated"], "n_records": data["n_records"], "unmatched_sell_qty": data["unmatched_sell_qty"], "overall": data["overall"], "trips": data["trips"], "open_lots": data["open_lots"], "min_profit_pct": min_profit, "stop_loss_pct": stop_loss, "max_daily_loss": max_daily_loss, } return HTML.replace("__DATA__", json.dumps(payload, default=str)) def main(): ap = argparse.ArgumentParser(description="Render trades.jsonl as an HTML dashboard") ap.add_argument("--ledger", default=str(analytics.LEDGER)) ap.add_argument("-o", "--out", default="dashboard.html") ap.add_argument("--since", help="only include trades closed on/after YYYY-MM-DD") ap.add_argument("--until", help="only include trades closed on/before YYYY-MM-DD") ap.add_argument("--min-profit", type=float, default=DEFAULT_MIN_PROFIT, help="min-profit annotation on the distribution chart") ap.add_argument("--stop-loss", type=float, default=DEFAULT_STOP_LOSS, help="stop-loss annotation on the distribution chart") ap.add_argument("--max-daily-loss", type=float, default=DEFAULT_MAX_DAILY_LOSS, help="daily circuit-breaker reference line") ap.add_argument("--open", action="store_true", help="open the result in a browser") args = ap.parse_args() path = Path(args.ledger) if not path.exists(): raise SystemExit(f"ledger not found: {path}") data = analytics.analyze(path, args.since, args.until) out = Path(args.out) out.write_text(build(data, args.min_profit, args.stop_loss, args.max_daily_loss)) print(f"wrote {out} ({data['overall']['n']} closed trips, " f"net {data['overall']['pnl']:+.2f})") if args.open: webbrowser.open(out.resolve().as_uri()) if __name__ == "__main__": main()