feat(dashboard): riordino LIVE→trades→storico + trade/anno contati

- dashboard riorganizzata in 3 sezioni: ① LIVE (mainnet sola lettura) in
  alto, ② TRADES ESEGUITI (reali) + frequenza operativa al centro,
  ③ STORICO (backtest/forward simulato) in fondo (COMBO/BOOK/FORWARD come ③·a/b/c).
- book_trade_frequency() in sleeves.py: trade/anno CONTATI sui dati certificati
  (cache di modulo, una volta per processo). SKH01 round-trip BTC ~37 / ETH ~43
  -> ~75/anno combinato; TP01 turnover ~7x/anno. Card "trade/anno" + blocco freq.
- fix: collisione var `pos` nel ramo shadow-online (-> shpos) che avrebbe rotto
  la tabella posizioni se il conto fosse leggibile dal container.
- fix: turnover TP01 nan (disallineamento groupby) -> groupby posizionale, 7.2x/anno.
- 56 test pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-23 21:18:52 +00:00
parent eeac97dde4
commit 25a22fc7c1
2 changed files with 86 additions and 15 deletions
+41
View File
@@ -273,6 +273,47 @@ def skyhook_sleeve(weight: float = 0.25) -> Sleeve:
return Sleeve("SKH01_skyhook", weight, _skyhook_returns, pos_fn=_skyhook_positions)
_TRADE_FREQ_CACHE = None
def book_trade_frequency() -> dict:
"""Frequenza operativa del book eseguibile (TP01+SKH01), conteggiata sui dati certificati.
Strategia + storico fissi -> cache a livello di MODULO: calcolata una sola volta per processo
(le nuove barre aggiungono pochissimo). Niente magic-number: e' contata, non stimata.
- SKH01: round-trip DISCRETI (apri+chiudi) no-overlap dell'harness, per asset e per anno.
- TP01: posizione CONTINUA vol-target -> 'turnover' annuo (somma |Δpeso|), non trade discreti."""
global _TRADE_FREQ_CACHE
if _TRADE_FREQ_CACHE is not None:
return _TRADE_FREQ_CACHE
skh = {}
for a in ASSETS:
ltf, htf = build_frames(load_data(a, "5m"))
ent = skyhook_entries(ltf, htf, SKH01_V2_DD)
yr = pd.to_datetime(pd.Series(ltf["timestamp"]).astype("int64"), unit="ms", utc=True).dt.year.values
yc: dict[int, int] = {}
for i, e in enumerate(ent):
if e is not None:
y = int(yr[i]); yc[y] = yc.get(y, 0) + 1
skh[a] = dict(by_year={int(k): int(v) for k, v in sorted(yc.items())},
total=int(sum(yc.values())), first=int(min(yc)), last=int(max(yc)))
sf = min(v["first"] for v in skh.values()); sl = max(v["last"] for v in skh.values())
skh_combo = sum(v["total"] for v in skh.values()) / (sl - sf + 1)
tp = TrendPortfolio(**CANONICAL); turns = []
for a in ASSETS:
df = resample_1d(load_data(a, "1h"))
tgt = np.asarray(tp.target_series(df), dtype=float)
years = pd.DatetimeIndex(pd.to_datetime(df["datetime"])).year # ndarray-aligned key
d = np.abs(np.diff(np.nan_to_num(tgt, nan=0.0), prepend=0.0)) # turnover = somma |Δpeso|
ty = pd.Series(d).groupby(years).sum() # groupby posizionale (RangeIndex)
turns.append(float(ty.mean()))
_TRADE_FREQ_CACHE = dict(
skh=skh,
skh_per_year={a: skh[a]["total"] / (skh[a]["last"] - skh[a]["first"] + 1) for a in ASSETS},
skh_combo_per_year=skh_combo,
tp01_turnover_per_year=sum(turns) / len(turns))
return _TRADE_FREQ_CACHE
# ----------------------------- REGISTRY -----------------------------
def active_sleeves() -> list[Sleeve]:
"""Sleeve ATTIVI nel portafoglio (pesi rinormalizzati; sleeve a date diverse si attivano