← back to Zero Code Trading Bot
assets/bot-template/dashboard.py
95 lines
#!/usr/bin/env python3
"""
dashboard.py — a zero-dependency P&L viewer for the paper ledger.
python dashboard.py # print a P&L snapshot to the terminal
python dashboard.py --serve # serve an auto-refreshing HTML table on a free port
Marks open positions against live prices (venue_adapter) so unrealized P&L is
current. Read-only — it never trades.
"""
import argparse
import json
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
import ledger
from venue_adapter import get_adapter
_adapter = None
def _mark(mid):
global _adapter
if _adapter is None:
_adapter = get_adapter("polymarket")
return _adapter.mark_price(mid)
def snapshot():
return ledger.summary(mark_fn=_mark)
def render_html(s):
rows = "".join(
f"<tr><td>{p['title'][:70]}</td><td>{p['side']}</td>"
f"<td>{p['entry_price']:.3f}</td><td>{p.get('mark','—')}</td>"
f"<td>${p['size_usd']:.0f}</td><td>${p.get('unrealized_usd',0):.2f}</td></tr>"
for p in s["open_positions"]
) or "<tr><td colspan=6>no open positions</td></tr>"
return f"""<!doctype html><meta charset=utf-8><meta http-equiv=refresh content=15>
<title>paper P&L</title>
<style>body{{font:14px/1.5 system-ui;margin:2rem;color:#111}}
table{{border-collapse:collapse;width:100%}}td,th{{border:1px solid #ddd;padding:.4rem .6rem;text-align:left}}
.big{{font-size:1.4rem;font-weight:600}}.k{{color:#666}}</style>
<h2>Paper trading — P&L <span class=k>(read-only · $0)</span></h2>
<p class=big>realized ${s['realized_pnl_usd']:.2f} · unrealized ${s['unrealized_pnl_usd']:.2f}
· today ${s['pnl_today_usd']:.2f}</p>
<p class=k>open {s['open_count']} · exposure ${s['exposure_usd']:.0f}</p>
<table><tr><th>market</th><th>side</th><th>entry</th><th>mark</th><th>size</th><th>unreal</th></tr>
{rows}</table>"""
def _free_port():
s = socket.socket()
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
s.close()
return port
def serve():
port = _free_port()
class H(BaseHTTPRequestHandler):
def do_GET(self):
body = (json.dumps(snapshot()) if self.path.startswith("/api")
else render_html(snapshot())).encode()
self.send_response(200)
self.send_header("Content-Type",
"application/json" if self.path.startswith("/api") else "text/html")
self.end_headers()
self.wfile.write(body)
def log_message(self, *a):
pass
print(f"paper P&L dashboard → http://127.0.0.1:{port} (Ctrl-C to stop)")
HTTPServer(("127.0.0.1", port), H).serve_forever()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--serve", action="store_true")
args = ap.parse_args()
if args.serve:
serve()
else:
s = snapshot()
print(json.dumps(s, indent=2))
if __name__ == "__main__":
main()