feat(shape): SH01 Shape-ML validato come diversificatore + doc

Validazione dura del solo edge sopravvissuto alla ricerca shape (ML walk-forward
LogisticRegression sulle feature di forma). SH01 config W24 H12 th0.58:
- BTC robusto ovunque (expanding +219%/OOS+42% Sharpe2.72 8-9anni; rolling2y
  +166%/+96%; stress leva2x+slippage OK), ETH/ADA solo expanding, LTC/SOL/XRP no.
- Griglia 5/27 robuste su cresta W24/H8-12 -> overfit moderato, config conservativa.
- Free-lunch: corr +0.08 col MASTER, aggiungerlo migliora OOS (Sharpe 4.33->5.10,
  DD 4.7->4.2%). Diversificatore, non motore standalone. Regge fee 0.20% RT.

SH01 come Strategy (in MODULE_MAP) + run() riproducibile. shape_ml_research esteso
con walk-forward rolling (train_window). Live richiede worker con retraining.
Diario 2026-05-29-shape.md, CLAUDE.md famiglia SHAPE-ML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 12:31:26 +02:00
parent 4ac87ab385
commit 2596687679
6 changed files with 396 additions and 4 deletions
+102
View File
@@ -0,0 +1,102 @@
"""SH01 — Shape-ML: direzione predetta dalla FORMA del segnale (ML walk-forward).
FAMIGLIA NUOVA, distinta da tutto l'esistente. Non e' una regola fissa su bande/canali
(fade) ne' momentum/rotazione (honest) ne' spread (pairs): una LogisticRegression legge
la MORFOLOGIA della finestra recente (body/shadow delle candele, rendimenti, pendenza,
curvatura, posizione di max/min, RSI, estensione) e predice il segno del rendimento a H
barre. Entra a close[i] solo se la probabilita' supera una soglia (selettivita').
E' l'UNICO edge sopravvissuto alla ricerca sui pattern-di-forma (2026-05-29): le altre
4 famiglie testate con agenti paralleli su harness onesto sono RUMORE (analog kNN forma
grezza, encoding candele UP/DOWN/DOJI, DTW+template geometrici, PIP/pivot) — confermano
la dominanza mean-reversion. Vedi scripts/analysis/shape_*_research.py e docs/diary.
Logica (engine onesto verificato in scripts/analysis/shape_ml_research.py):
feature di forma X[i] da o/h/l/c[i-W+1..i] (causali: solo dati fino a close[i])
walk-forward a blocchi: scaler+modello fittati SOLO sul passato con esito noto
(finestre e con e+H <= inizio_blocco-1), poi predicono il blocco corrente
proba(classe) >= thresh -> entra a close[i] nella direzione predetta, exit a H barre
fee 0.10% RT (single-leg). NESSUN look-ahead (check espliciti: perturbare il futuro
non cambia ne' le feature a i ne' le predizioni fino a i).
VALIDAZIONE DURA (netto fee, leva 3x, pos 0.15, OOS = ultimo 30%, config W24 H12 th0.58,
scripts/analysis/shape_ml_validate.py):
- MULTI-ASSET expanding: robusti BTC, ETH, ADA; scartati LTC/SOL/XRP.
BTC : FULL +219% / OOS +42% / Sharpe 2.72 / DD 23% / 8-9 anni+ / accOOS 56% (regge fee 0.2%: +60/+26)
ETH : FULL +80% / OOS +144% / Sharpe 1.21 / DD 61% / 6/9 anni+ / accOOS 55% (piu' volatile -> secondario)
ADA : FULL +707% / OOS +57% / Sharpe 3.22 / DD 39% / 7/8 anni+ (robusto solo expanding)
- WALK-FORWARD ROLLING (train fisso 2 anni): regge solo BTC (FULL +166% / OOS +96% / Sharpe 2.05).
-> l'edge si appoggia in parte alla memoria lunga: BTC e' il piu' solido.
- STRESS leva 2x + slippage doppio (fee 0.20% RT): BTC OK (FULL +40% / OOS +17% / Sharpe 1.24),
ETH marginale (FULL +7% / OOS +73% / Sharpe 0.37).
- GRIGLIA (W,H,thresh) su BTC: 5/27 celle robuste a fee 0.2%, su una CRESTA stretta
(W24, H8-12), non altopiano largo -> rischio overfit moderato. Per prudenza si sceglie
la config robusta sul PIU' ALTO numero di test (W24 H12 th0.58), non il PnL massimo
(W24 H8 rende di piu' ma accOOS ~49% = piu' drift che segnale).
USO CONSIGLIATO: NON motore standalone (per-asset e' troppo stretto/fragile fuori da BTC),
ma DIVERSIFICATORE di portafoglio. Corr daily col MASTER +0.08 (quasi scorrelato).
Aggiungere lo sleeve shape (BTC+ETH) al MASTER migliora l'OOS: Sharpe 4.33->5.10,
DD 4.7%->4.2% (scripts/analysis/shape_ml_validate.py sez. 5).
LIVE: richiede un worker capace di RIALLENARE periodicamente il modello (come il legacy
signal_engine ML, non lo StrategyWorker a regola fissa). Da wirare prima del paper live.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
from src.strategies.base import Strategy, Signal # noqa: E402
from scripts.analysis.explore_lab import get_df, evaluate, robust # noqa: E402
from scripts.analysis.shape_ml_research import ml_wf_entries # noqa: E402
# Config robusta scelta (cresta W24 H8-12; H12 th0.58 = la piu' robusta sui test).
CONFIG = dict(W=24, H=12, model="logit", thresh=0.58)
# Asset con edge robusto. BTC primario (regge ogni stress); ETH secondario (diversificatore
# piu' volatile). ADA robusto solo expanding -> tenuto fuori dal set live conservativo.
ASSETS = ["BTC", "ETH"]
class ShapeMLStrategy(Strategy):
name = "SH01_shape_ml"
description = "Direzione predetta dalla forma del segnale (LogisticRegression walk-forward), exit a H barre"
default_assets = ASSETS
default_timeframes = ["1h"]
fee_rt = 0.001
leverage = 3.0
position_size = 0.15
def generate_signals(self, df: pd.DataFrame, ts: pd.DatetimeIndex, **params) -> list[Signal]:
cfg = {**CONFIG, **{k: params[k] for k in ("W", "H", "model", "thresh") if k in params}}
ents = ml_wf_entries(df, W=cfg["W"], H=cfg["H"], model=cfg["model"], thresh=cfg["thresh"])
c = df["close"].values
out: list[Signal] = []
for e in ents:
out.append(Signal(idx=e["i"], direction=e["d"], entry_price=float(c[e["i"]]),
metadata={"max_bars": e["max_bars"]}))
return out
def run():
print("=" * 96)
print(" SH01 — Shape-ML | direzione dalla FORMA (LogisticRegression walk-forward) | netto fee 0.10% RT, leva 3x")
print("=" * 96)
print(f" config: {CONFIG} (W=finestra forma, H=orizzonte/exit, thresh=soglia proba)")
for a in ASSETS:
df = get_df(a, "1h")
ents = ml_wf_entries(df, **CONFIG)
res = evaluate(f"{a}", ents, df)
print(f" ^ {'ROBUSTO (FULL+OOS+, regge fee 0.2%, ~tutti anni+)' if robust(res) else 'edge presente ma con anni negativi (diversificatore)'}")
print("\n Uso: diversificatore di portafoglio (corr ~0.08 col MASTER), non motore standalone.")
print(" Live: serve worker con retraining periodico del modello (vedi docstring).")
if __name__ == "__main__":
run()