chore(analysis): dedup engine gate PORT06 + drift monitor giornaliero + impact bfill
- _port06_gate_common.py: build_trades_variant/equity_from_trades/port_metrics/dd fattorizzati dai 3 gate exit16/trendmax/dip01 (-214 righe duplicate). Nessun copy-drift trovato; versione promossa = trendmax (superset con hurst_mask). Output dei 3 gate verificato BYTE-IDENTICO prima/dopo. dip_trades resta nel suo script (sibling deliberato long-only/orig_gap, non una copia). - drift_monitor.py: rolling-return per famiglia vs distribuzione storica propria (warn sotto p5; oggi: FADE 120g al p2). In crontab host giornaliero 07:15 UTC con report Telegram. Osservabilita', non filtro di trading. - daily_equity_bfill_impact.py: bug bfill _daily_equity QUANTIFICATO -> non materiale (OOS invariato per costruzione, FULL DD 3.46->3.67 col fix, nessun verdetto gate a rischio). Lasciato documentato in TODO, niente fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,116 +29,20 @@ 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 src.data.downloader import load_data
|
||||
from src.strategies.fade_base import hurst_skip_mask
|
||||
from scripts.analysis.strategy_research import atr
|
||||
from scripts.analysis.risk_management import strats_for, FEE_RT, LEV, POS, INIT
|
||||
from scripts.analysis.combine_portfolio import (
|
||||
_norm, IDX, port_returns, metrics, SPLIT, OOS_DATE,
|
||||
from scripts.analysis.risk_management import strats_for
|
||||
from scripts.analysis.combine_portfolio import OOS_DATE
|
||||
from scripts.analysis._port06_gate_common import (
|
||||
build_trades_variant, equity_from_trades, port_metrics, dd as _dd,
|
||||
)
|
||||
from scripts.portfolios._defs import PORTFOLIOS
|
||||
from src.portfolio import weighting as W
|
||||
|
||||
BUFFER = 0.5 # EXIT-16 close-confirm (come in produzione)
|
||||
HURST_MAX = 0.55 # loss-guard live
|
||||
TREND_MAX = 3.0
|
||||
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):
|
||||
"""Engine di exit16_port06_impact.build_trades_variant + skip Hurst opzionale.
|
||||
|
||||
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 (live attuale); 3.0 = candidato.
|
||||
hurst_mask : bool[i]=True -> salta l'ingresso (loss-guard live).
|
||||
"""
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
pk = s.cummax()
|
||||
return float(((pk - s) / pk).max() * 100)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user