chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""REGRESSION-LOCK COMUNE dei gate PORT06 live (exit16 / trendmax / dip01).
|
||||
|
||||
Queste funzioni erano copiate quasi-verbatim in exit16_port06_impact.py,
|
||||
trendmax_port06_impact.py e dip01_exit16_impact.py. Sono il regression-lock
|
||||
delle DECISIONI LIVE (EXIT-16, swap hurst->trend, DIP01 EXIT-16): la copy-drift
|
||||
fra le copie avrebbe corrotto i verdetti, quindi vivono qui in un'unica copia.
|
||||
|
||||
NON cambiare la matematica: i gate devono restare riproducibili byte-a-byte.
|
||||
Se un nuovo gate richiede un comportamento diverso, PARAMETRIZZARE (come fu
|
||||
fatto per hurst_mask/trend_max), mai biforcare una copia.
|
||||
|
||||
Contenuto:
|
||||
build_trades_variant : replay ESATTO di risk_management.build_trades sulle
|
||||
fade (mode="orig" == canonico), con i rami varianti
|
||||
EXIT-16 (mode="exit16"), filtro trend (trend_max) e
|
||||
loss-guard Hurst (hurst_mask) parametrici.
|
||||
equity_from_trades : trade -> equity giornaliera normalizzata su IDX
|
||||
(stesso flusso di combine_portfolio.fade_daily_equity).
|
||||
port_metrics : metriche FULL/OOS del portafoglio con la STESSA
|
||||
matematica pesi di Portfolio.backtest (weight_vector
|
||||
su tutti gli sleeve, ribilancio come port_returns).
|
||||
dd : max drawdown % di una equity.
|
||||
|
||||
NB: l'engine DIP01 (dip_trades in dip01_exit16_impact.py) NON e' una copia di
|
||||
build_trades_variant ma un sibling deliberatamente diverso (long-only, mode
|
||||
"orig_gap" gap-aware, j clampato a n-1 a fine serie, niente filtri trend/hurst)
|
||||
-> resta nel suo script.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from scripts.analysis.strategy_research import atr
|
||||
from scripts.analysis.risk_management import FEE_RT, LEV, POS, INIT
|
||||
from scripts.analysis.combine_portfolio import (
|
||||
_norm, IDX, port_returns, metrics, SPLIT,
|
||||
)
|
||||
from src.portfolio import weighting as W
|
||||
|
||||
BUFFER = 0.5 # EXIT-16 close-confirm (come in produzione)
|
||||
EMA_LONG = 200
|
||||
|
||||
|
||||
def build_trades_variant(ents, df, mode, trend_max, hurst_mask=None,
|
||||
buffer=BUFFER, lev=LEV, fee_rt=FEE_RT, ema_long=EMA_LONG):
|
||||
"""Replica ESATTA di risk_management.build_trades, con i rami varianti.
|
||||
|
||||
mode="orig" : SL intrabar al livello (SL prima del TP) == canonico.
|
||||
mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
|
||||
SL solo se il CLOSE sfonda sl0 -/+ buffer*ATR14[j], fill a close[j].
|
||||
trend_max : None = filtro OFF; 3.0 = config live.
|
||||
hurst_mask : bool[i]=True -> salta l'ingresso (loss-guard storico).
|
||||
"""
|
||||
h, l, c = df["high"].values, df["low"].values, df["close"].values
|
||||
n = len(c)
|
||||
a = atr(df, 14)
|
||||
el = pd.Series(c).ewm(span=ema_long, adjust=False).mean().values
|
||||
fee = fee_rt * lev
|
||||
out = []
|
||||
last = -1
|
||||
for e in ents:
|
||||
i, d = e["i"], e["d"]
|
||||
if i <= last or i + 1 >= n:
|
||||
continue
|
||||
if hurst_mask is not None and hurst_mask[i]:
|
||||
continue
|
||||
if trend_max is not None and a[i] and abs(c[i] - el[i]) / a[i] > trend_max:
|
||||
continue
|
||||
entry = c[i]
|
||||
tp, sl0, mb = e["tp"], e["sl"], e["max_bars"]
|
||||
exit_p = c[min(i + mb, n - 1)]
|
||||
j = min(i + mb, n - 1)
|
||||
for k in range(1, mb + 1):
|
||||
j = i + k
|
||||
if j >= n:
|
||||
exit_p = c[n - 1]
|
||||
break
|
||||
if mode == "orig":
|
||||
hs = (d == 1 and l[j] <= sl0) or (d == -1 and h[j] >= sl0)
|
||||
ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
|
||||
if hs:
|
||||
exit_p = sl0
|
||||
break
|
||||
if ht:
|
||||
exit_p = tp
|
||||
break
|
||||
if k == mb:
|
||||
exit_p = c[j]
|
||||
else: # exit16
|
||||
ht = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
|
||||
if ht:
|
||||
exit_p = tp
|
||||
break
|
||||
aj = a[j] if np.isfinite(a[j]) else 0.0
|
||||
confirm = (d == 1 and c[j] < sl0 - buffer * aj) or \
|
||||
(d == -1 and c[j] > sl0 + buffer * aj)
|
||||
if confirm:
|
||||
exit_p = c[j]
|
||||
break
|
||||
if k == mb:
|
||||
exit_p = c[j]
|
||||
ret = (exit_p - entry) / entry * d * lev - fee
|
||||
out.append((i, j, ret))
|
||||
last = j
|
||||
return out
|
||||
|
||||
|
||||
def equity_from_trades(df, trades):
|
||||
"""Trade -> equity giornaliera su IDX (flusso di combine_portfolio.fade_daily_equity)."""
|
||||
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
||||
n = len(df)
|
||||
eq = np.full(n, INIT, dtype=float)
|
||||
cap = INIT
|
||||
for i, j, ret in sorted(trades, key=lambda t: t[1]):
|
||||
cap = max(cap + cap * POS * ret, 10.0)
|
||||
eq[j:] = cap
|
||||
s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill()
|
||||
return _norm(s)
|
||||
|
||||
|
||||
def port_metrics(members: dict[str, pd.Series], p):
|
||||
"""Metriche (FULL, OOS) del portafoglio p con la STESSA matematica pesi cap
|
||||
di Portfolio.backtest."""
|
||||
ids = p.sleeve_ids
|
||||
dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids})
|
||||
w = W.weight_vector(p.weighting, ids, dr, weights=p.weights,
|
||||
caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback)
|
||||
drp = port_returns({i: members[i] for i in ids}, w)
|
||||
return metrics(drp), metrics(drp, lo=SPLIT)
|
||||
|
||||
|
||||
def dd(s):
|
||||
"""Max drawdown % di una serie equity."""
|
||||
pk = s.cummax()
|
||||
return float(((pk - s) / pk).max() * 100)
|
||||
Reference in New Issue
Block a user