38c8cdf25b
Harness onesto research_lab.py (serie di posizione causale, fee-aware, null model a rotazione circolare, hold-out 2025+ bloccato; self-test cheat/noise che valida il banco). - Fase 1: triage superstiti (DIP, shape-ML) -> morti net-fee. - Fase 2: esplorazione famiglie (reversal morta; solo trend long-only/MA-cross passa i gate base). - Fase 3: conferma avversariale del trend -> regime-luck del toro, bocciato sul hold-out 2025-26. - Ricerca frattale multi-agente (Workflow, 63 agenti, 52 ipotesi dai due documenti) con guard anti-look-ahead (eval_signal.py) + hold-out + test cross-asset -> 0 edge robusto (l'unico "confermato" su ETH fallisce su BTC con lo stesso codice). - Analisi options: VRP reale +10/+14 vol pt ma finestra 6 sett. regime unico -> non validabile; ruolo solo overlay tail-cap, tenere cerbero-bite ad accumulare. Quinta conferma indipendente: su BTC/ETH-solo-prezzo non c'e' un edge facile. Il processo disciplinato ha evitato un falso "+49% vs -49%" che sul vecchio feed contaminato sarebbe finito in produzione. Diari docs/diary/2026-06-19-research-phase0-1 / -phase2-options / -phase3-confirm / -fractal-multiagent-search. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
4.0 KiB
Python
97 lines
4.0 KiB
Python
"""EVALUATOR STANDARD per i segnali della ricerca multi-agente (Fase frattale, v2.0.0).
|
|
|
|
Ogni agente scrive SOLO una funzione `signal(df, asset, tf) -> np.ndarray` (posizione per barra
|
|
in [-1,1], decisa entro close[i]) in un file. Questo evaluator la valuta in modo UNIFORME e ONESTO
|
|
sull'harness research_lab, e — cruciale — esegue un GUARD ANTI-LOOK-AHEAD automatico: ricalcola il
|
|
segnale su prefissi del df e verifica che pos[i] non dipenda da barre future (leak>0 = sospetto).
|
|
|
|
uv run python scripts/analysis/eval_signal.py <signal_file.py> <BTC|ETH> <5m|15m|1h> [--holdout]
|
|
|
|
Stampa una riga "RESULT_JSON:{...}" con tutte le metriche (gli agenti riportano quei campi esatti).
|
|
"""
|
|
from __future__ import annotations
|
|
import sys
|
|
import json
|
|
import importlib.util
|
|
from pathlib import Path
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
import numpy as np
|
|
from src.data.downloader import load_data
|
|
from scripts.analysis.research_lab import backtest, buy_hold, mc_pvalue, VAL_START, HOLDOUT_START
|
|
|
|
|
|
def load_signal(path):
|
|
spec = importlib.util.spec_from_file_location("usig", path)
|
|
m = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(m)
|
|
if not hasattr(m, "signal"):
|
|
raise AttributeError("il file non definisce signal(df, asset, tf)")
|
|
return m.signal
|
|
|
|
|
|
def causality_guard(signal, df, asset, tf, k=12):
|
|
"""Ricalcola il segnale su prefissi df[:i+1] e confronta pos[i] col run completo.
|
|
Se differiscono -> il segnale usa dati FUTURI (look-ahead). Ritorna #violazioni (0 = pulito)."""
|
|
full = np.asarray(signal(df, asset, tf), float)
|
|
n = len(df)
|
|
if len(full) != n:
|
|
return -1
|
|
rng = np.random.default_rng(0)
|
|
idx = rng.integers(int(n * 0.6), n - 1, size=k)
|
|
bad = 0
|
|
for i in idx:
|
|
try:
|
|
p = np.asarray(signal(df.iloc[:i + 1].copy(), asset, tf), float)
|
|
except Exception:
|
|
bad += 1; continue
|
|
if len(p) != i + 1 or not np.isclose(np.nan_to_num(p[i]), np.nan_to_num(full[i]), atol=1e-6):
|
|
bad += 1
|
|
return bad
|
|
|
|
|
|
def main():
|
|
args = sys.argv[1:]
|
|
holdout = "--holdout" in args
|
|
args = [a for a in args if a != "--holdout"]
|
|
sigfile, asset, tf = args[0], args[1].upper(), args[2]
|
|
res = {"asset": asset, "tf": tf, "sigfile": sigfile}
|
|
try:
|
|
signal = load_signal(sigfile)
|
|
df = load_data(asset, tf)
|
|
pos = np.asarray(signal(df, asset, tf), float)
|
|
res["n"] = int(len(df))
|
|
res["len_ok"] = bool(len(pos) == len(df))
|
|
if not res["len_ok"]:
|
|
res["error"] = f"len(pos)={len(pos)} != len(df)={len(df)}"
|
|
print("RESULT_JSON:" + json.dumps(res)); return
|
|
res["finite"] = bool(np.isfinite(np.nan_to_num(pos, nan=0.0)).all())
|
|
res["leak"] = int(causality_guard(signal, df, asset, tf))
|
|
full = backtest(df, pos, tf)
|
|
oos = backtest(df, pos, tf, lo=VAL_START, hi=HOLDOUT_START)
|
|
bh = buy_hold(df, tf)
|
|
_, p, _, _ = mc_pvalue(df, pos, tf, n=250)
|
|
res.update(
|
|
implemented=True,
|
|
full_sharpe=round(full.sharpe, 3), full_ret=round(full.ret, 3), full_dd=round(full.maxdd, 3),
|
|
oos_sharpe=round(oos.sharpe, 3), bh_sharpe=round(bh.sharpe, 3),
|
|
gross_sharpe=round(backtest(df, pos, tf, fee_rt=0.0).sharpe, 3),
|
|
fee02_sharpe=round(backtest(df, pos, tf, fee_rt=0.002).sharpe, 3),
|
|
turnover=round(full.ntrades, 1), exposure=round(full.exposure, 3),
|
|
null_p=round(p, 4),
|
|
beats_bh=bool(full.sharpe > bh.sharpe and oos.sharpe > 0),
|
|
)
|
|
if holdout:
|
|
ho = backtest(df, pos, tf, lo=HOLDOUT_START)
|
|
res["holdout_sharpe"] = round(ho.sharpe, 3)
|
|
res["holdout_ret"] = round(ho.ret, 3)
|
|
res["holdout_dd"] = round(ho.maxdd, 3)
|
|
except Exception as e:
|
|
res["implemented"] = False
|
|
res["error"] = f"{type(e).__name__}: {str(e)[:200]}"
|
|
print("RESULT_JSON:" + json.dumps(res))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|