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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user