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:
+17
-2
@@ -15,7 +15,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
|
||||
import numpy as np, pandas as pd
|
||||
from src.portfolio.portfolio import StrategyPortfolio, metrics, HOLDOUT
|
||||
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
|
||||
|
||||
PAPER = PROJECT_ROOT / "data" / "paper_portfolio" / "state.json"
|
||||
@@ -37,12 +37,16 @@ def build():
|
||||
shadow = shadow_report() # mainnet sola lettura, best-effort
|
||||
except Exception as 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(
|
||||
version=APP_VERSION,
|
||||
last_data=str(idx[-1].date()),
|
||||
full=bt["full"], holdout=bt["holdout"], weights=bt["weights"],
|
||||
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,
|
||||
)
|
||||
_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)")
|
||||
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>
|
||||
<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}}
|
||||
@@ -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>
|
||||
<h3 style="font-size:14px;color:#8a93a0">Posizioni correnti (ultima barra chiusa)</h3>
|
||||
<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>
|
||||
<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>"""
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from src.backtest.harness import load
|
||||
@@ -20,6 +21,35 @@ FALLBACK_CAPITAL = 2000.0
|
||||
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:
|
||||
try:
|
||||
return DeribitRead()
|
||||
|
||||
Reference in New Issue
Block a user