feat(dashboard): lista trades TP01 (entry/exit dal segnale causale)

Nuova sezione "Trades TP01" nella dashboard: eventi ENTRY long / EXIT flat dedotti da
target_series sui dati certificati (data, asset, transizione di posizione, prezzo). In
src/live/shadow.tp01_trades(): account-independent (gira anche offline nel container),
ricalcolata a ogni render -> storico + forward. Empty-state se TP01 non ha mai mosso.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-20 14:18:46 +00:00
parent bec2fb2089
commit 715f197cf2
2 changed files with 47 additions and 2 deletions
+17 -2
View File
@@ -15,7 +15,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd import numpy as np, pandas as pd
from src.portfolio.portfolio import StrategyPortfolio, metrics, HOLDOUT from src.portfolio.portfolio import StrategyPortfolio, metrics, HOLDOUT
from src.portfolio.sleeves import active_sleeves from src.portfolio.sleeves import active_sleeves
from src.live.shadow import shadow_report from src.live.shadow import shadow_report, tp01_trades
from src.version import APP_VERSION from src.version import APP_VERSION
PAPER = PROJECT_ROOT / "data" / "paper_portfolio" / "state.json" PAPER = PROJECT_ROOT / "data" / "paper_portfolio" / "state.json"
@@ -37,12 +37,16 @@ def build():
shadow = shadow_report() # mainnet sola lettura, best-effort shadow = shadow_report() # mainnet sola lettura, best-effort
except Exception as e: except Exception as e:
shadow = {"error": f"{type(e).__name__}: {e}"} shadow = {"error": f"{type(e).__name__}: {e}"}
try:
trades = tp01_trades(limit=15) # entry/exit TP01 dal segnale causale
except Exception:
trades = []
data = dict( data = dict(
version=APP_VERSION, version=APP_VERSION,
last_data=str(idx[-1].date()), last_data=str(idx[-1].date()),
full=bt["full"], holdout=bt["holdout"], weights=bt["weights"], full=bt["full"], holdout=bt["holdout"], weights=bt["weights"],
per_sleeve=bt["per_sleeve"], yearly=bt["yearly"], per_sleeve=bt["per_sleeve"], yearly=bt["yearly"],
positions=pf.current_positions(), spark=spark, paper=paper, shadow=shadow, positions=pf.current_positions(), spark=spark, paper=paper, shadow=shadow, trades=trades,
bh=None, bh=None,
) )
_CACHE.update(t=time.time(), data=data) _CACHE.update(t=time.time(), data=data)
@@ -99,6 +103,15 @@ def html():
f"TP01 target: {bits}<br>→ per gli ordini reali: <code>uv run python scripts/live/live_trend.py</code> (host)") f"TP01 target: {bits}<br>→ per gli ordini reali: <code>uv run python scripts/live/live_trend.py</code> (host)")
else: else:
shadow_html = "non disponibile" + (f"{sh['error']}" if sh and sh.get('error') else "") shadow_html = "non disponibile" + (f"{sh['error']}" if sh and sh.get('error') else "")
trows = ""
for t in d.get("trades", []):
cls = "g" if t["action"] == "ENTRY" else "r"
trows += (f"<tr><td>{t['date']}</td><td>{t['asset']}</td>"
f"<td class={cls}>{t['action']}</td>"
f"<td>{t['from_pos']:+.2f}{t['to_pos']:+.2f}x</td>"
f"<td>${t['price']:,.0f}</td></tr>")
if not trows:
trows = "<tr><td colspan=5 style='color:#8a93a0'>nessun trade ancora (TP01 flat / in cash)</td></tr>"
return f"""<!doctype html><html><head><meta charset=utf-8> return f"""<!doctype html><html><head><meta charset=utf-8>
<meta http-equiv=refresh content=300><title>PythagorasGoal — Portafoglio</title> <meta http-equiv=refresh content=300><title>PythagorasGoal — Portafoglio</title>
<style>body{{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#0e1116;color:#e6e6e6;margin:0;padding:24px;max-width:980px;margin:auto}} <style>body{{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#0e1116;color:#e6e6e6;margin:0;padding:24px;max-width:980px;margin:auto}}
@@ -126,6 +139,8 @@ th{{color:#8a93a0;font-weight:500}}.y{{display:inline-block;background:#161b22;b
<table><tr><th>sleeve</th><th>peso</th><th>FULL Sh</th><th>DD</th><th>HOLD Sh</th></tr>{rows}</table> <table><tr><th>sleeve</th><th>peso</th><th>FULL Sh</th><th>DD</th><th>HOLD Sh</th></tr>{rows}</table>
<h3 style="font-size:14px;color:#8a93a0">Posizioni correnti (ultima barra chiusa)</h3> <h3 style="font-size:14px;color:#8a93a0">Posizioni correnti (ultima barra chiusa)</h3>
<table>{pos}</table> <table>{pos}</table>
<h3 style="font-size:14px;color:#8a93a0">Trades TP01 — entry/exit (segnale causale, ultimi 15)</h3>
<table><tr><th>data</th><th>asset</th><th>azione</th><th>posizione</th><th>prezzo</th></tr>{trows}</table>
<div style="margin-top:10px">{yrs}</div> <div style="margin-top:10px">{yrs}</div>
<p class=warn>⚠️ Paper/monitor. XS01 e' STAT-MODE (book a 19 gambe market-neutral, non eseguibile a €2k, storia ~2.5 anni). VRP01 = lead short-vol MODELLATO (non deploy pieno). TP01 e' l'unico deployable pieno: lo "Shadow live" mostra cosa farebbe sul mainnet, ma NON invia ordini.</p> <p class=warn>⚠️ Paper/monitor. XS01 e' STAT-MODE (book a 19 gambe market-neutral, non eseguibile a €2k, storia ~2.5 anni). VRP01 = lead short-vol MODELLATO (non deploy pieno). TP01 e' l'unico deployable pieno: lo "Shadow live" mostra cosa farebbe sul mainnet, ma NON invia ordini.</p>
</body></html>""" </body></html>"""
+30
View File
@@ -7,6 +7,7 @@ from __future__ import annotations
import json import json
from pathlib import Path from pathlib import Path
import numpy as np
import pandas as pd import pandas as pd
from src.backtest.harness import load from src.backtest.harness import load
@@ -20,6 +21,35 @@ FALLBACK_CAPITAL = 2000.0
PAPER_STATE = PROJECT_ROOT / "data" / "paper_trend" / "state.json" PAPER_STATE = PROJECT_ROOT / "data" / "paper_trend" / "state.json"
def tp01_trades(limit: int = 15) -> list[dict]:
"""Lista dei TRADE di TP01 = cambi di posizione (ENTRY long / EXIT flat) dedotti dal segnale
causale `target_series` sui dati certificati. Account-independent (gira anche offline nel
container). Si ricalcola a ogni render -> include sia lo storico sia i trade forward man mano
che arrivano. Ritorna gli ultimi `limit` (piu' recenti primi)."""
tp = TrendPortfolio(**CANONICAL)
out: list[dict] = []
for a in ASSETS:
df = resample_1d(load(a, "1h"))
c = df["close"].to_numpy(dtype=float)
dt = pd.to_datetime(df["datetime"]).to_numpy()
tgt = tp.target_series(df)
prev = 0.0
for i in range(len(tgt)):
if np.sign(tgt[i]) != np.sign(prev):
out.append(dict(
ts=int(pd.Timestamp(dt[i]).value // 10**6),
date=str(pd.Timestamp(dt[i]).date()),
asset=a,
action="ENTRY" if tgt[i] != 0 else "EXIT",
from_pos=round(float(prev), 3),
to_pos=round(float(tgt[i]), 3),
price=round(float(c[i]), 1),
))
prev = tgt[i]
out.sort(key=lambda r: r["ts"], reverse=True)
return out[:limit]
def _safe_client() -> DeribitRead | None: def _safe_client() -> DeribitRead | None:
try: try:
return DeribitRead() return DeribitRead()