Files
Adriano Dal Pastro 8d1fe173f7 feat(portfolio): wire SKH01-V2-DD sleeve @25% effective -> 4-sleeve book
Add Skyhook (SKH01_V2_DD) as a portfolio sleeve. Effective weight 25%: the three
existing sleeves scaled into the remaining 0.75 keeping their 55:25:20 ratio
(TP01 41.25% / XS01 18.75% / VRP01 15% / SKH01 25%).

_skyhook_returns(): 50/50 BTC+ETH daily series of the dual-TF regime+breakout engine
(causal, net 0.10% RT), same convention as the marginal lens.

Portfolio impact (run_portfolio.py), 3-sleeve -> 4-sleeve:
  FULL Sharpe 1.68 -> 2.13 (+0.45), FULL maxDD 14.3% -> 7.8% (halved)
  HOLD-OUT Sharpe 1.63 -> 2.30 (+0.67), HOLD-OUT maxDD ~3.5% (flat)
  Positive every year 2019-26 (annual DD <=7.8%) vs buy&hold 50/50 FULL Sh 0.93 / DD 76%.

Skyhook is quasi-orthogonal (corr ~0.09 to TP01) so it lifts Sharpe AND cuts DD.
Research portfolio (fixed weights, no real rebalancing cost at $600; Skyhook daily
Sharpe is the step-marked lens convention) -> forward-monitor, not deploy.
Tests: 25 pass (skyhook 8 + portfolio 7 + vrp 4 + trend 6). Diary + CLAUDE.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:22:15 +00:00

142 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-06-23 — SKH01 "Skyhook": porting onesto del sistema ES dual-timeframe su BTC/ETH
Branch: `strategy_skyhook`. Engine: `src/strategies/skyhook.py`. Harness: `scripts/research/skyhook/skyhooklib.py`.
Test: `tests/test_skyhook.py` (5 pass). Ricerca: `scripts/research/skyhook/{sweep,grid,check_v1}.py` + `runs/`.
## Il brief
Sistema "Skyhook" (origine ES / E-mini S&P, genetico, a doppio timeframe), da portare su crypto:
- **data2 = 690 min (segnale)**, **data1 = 230 min (esecuzione)**. NB **690 = 3 × 230**.
- NON trend-follower: entra **solo** quando coincidono (a) un **regime** di volatilità/volume e
(b) un **pattern** di breakout.
- Pipeline per barra: indicatori (BuzVola su ATR, BuzVolume su volume, tipo-Chande 0-100) →
fasce regime → pattern (Donchian/breakout su data2) → composer (regime AND pattern) →
ingresso (max 1/giorno, stop-and-reverse) → uscite (time-based asimmetrico uscitalong=24 /
uscitashort=18 + stop/profit).
- Ancore demo: trend lineare → **BuzVola=50** (vol steady → neutro), **BuzVolume=100** (volume in rampa).
## Ricostruzione (fedele + onesta)
- **Resample dal feed 5m certificato** con `origin='epoch'`: 230 min = 46×5m, 690 min = 138×5m,
e i confini 690 sono un **sottoinsieme** dei confini 230 → una barra HTF chiude esattamente su
una chiusura LTF. Merge HTF→LTF causale: `merge_asof` backward sulla **chiusura HTF** (≤ chiusura
LTF), così una barra HTF è usata solo quando è davvero chiusa. (~2287 barre/anno LTF, ~762 HTF.)
- **BuzVola / BuzVolume = `chande01`** (Chande Momentum Oscillator normalizzato 0-100): serie
steady → 50, rampa-su → 100, rampa-giù → 0. Le ancore demo sono soddisfatte a livello di
indicatore (è la lettura fedele: "vol steady → neutro"). NB: l'EMA-ATR su un *linspace* sintetico
dà 100 per drift di warm-up/floating-point, non per comportamento reale — su BTC reale BuzVola
oscilla intorno a 50 (EMA-ATR vs SMA-ATR corr 0.90).
- **Pattern** = Donchian breakout leak-free (shift(1)) su HTF, `ptn_n` barre (default 13 da 13/13/1).
- **Regime** = bande-soglia tunabili su BuzVola/BuzVolume (i magici interi 4/3/2 - 4/2/2 non sono
nel brief; ricostruiti come `[vola_lo,vola_hi]` × `[vol_lo,vol_hi]`).
- **Composer** = regime AND pattern. **Ingressi** ≤1/giorno (prima barra qualificante).
- **Uscite**: time-based asimmetrico (`uscitalong`/`uscitashort` barre LTF) + hard stop/profit. Lo
"stop 2000 / profit 5000" in $ del sistema ES → **multipli di ATR LTF** (scale-free): default
`sl_atr=2.0`, `tp_atr=5.0` (~ rapporto 40:100 pt ES), con modalità `pct` alternativa.
- Engine espresso come **entries `{dir,tp,sl,max_bars}`** per `backtest_signals` (motore onesto del
progetto: TP/SL intrabar, max_bars, non-overlap). Causalità verificata con prefix-recompute
(0 mismatch).
## Baseline → V1 (lever scout + grid, inline, veloce)
- **Baseline** (default 13/13, sl2/tp5, vola[35,95], vol_lo50): causale, fee-surviving, FULL Sharpe
BTC +0.91 / ETH +0.64, ma **HOLD-OUT debole** (BTC 0.09 / ETH +0.17) → FAIL del gate onesto.
- **Lever scout** (`sweep.py`): gli **short servono** (long_only → HOLD 0.52); il **regime gate
conta** (togliere la banda vola → HOLD 0.80); il **floor di volume** a 50 *frenava* l'hold-out
(vol_lo=40 o 0 → PASS); **breakout più lento** (ptn_n=55) e **stop più larghi** (sl2.5/tp6)
alzano l'hold-out.
- **Grid combinato** (`grid.py`): vincitrice **SKH01-V1**
`SkyhookParams(ptn_n=55, sl_atr=2.5, tp_atr=6.0, vola_lo=35, vola_hi=95, vol_lo=0.0)`:
- **min-asset FULL +0.69, HOLD-OUT +0.64** (BTC 0.64 / ETH 0.64), **PASS**, fee-surviving a 0.30%RT.
- BTC FULL +0.69/+275% DD49% ; ETH FULL +1.01/+871% DD31% ; entrambi HOLD-OUT positivi.
- **Marginal vs TP01 = ADDS** e regge i gate induriti: **corr 0.06** (ortogonale, NON trend-beta),
`has_insample_edge=True` (Sharpe in-sample standalone 1.15), `is_hedge=False`, multi-cut
persistente. Blend **0.75·TP01 + 0.25·SKH01: HOLD Sharpe 0.31 → 0.74 (+0.44), DD 11.9%**;
blend 50/50 HOLD 0.88, DD 17.8%.
- Unico sub-gate fallito: `clean_year_uplift` +0.014 (sotto 0.02) → `earns_slot=False` per un pelo,
nonostante tutto il resto sia forte. **Debolezza principale: DD standalone alto (40-49%).**
→ SKH01 è un **diversificatore quasi-ortogonale** reale (non un TP01 travestito): da solo è
volatile, ma come sleeve al 25% migliora moltissimo l'hold-out del portafoglio a DD bassissimo.
## Onda 1 (`skyhook-improve`, 30 agenti) — winner intermedio
Famiglie: param (RR, ptn_n, regime bands, exit bars, chande, local), regime-redef (percentile,
realized-vol, vol-expansion, LTF), pattern (confirmation, ROC, Keltner, NR, dual), exit + overlay,
ognuna verificata da 2 scettici. Risultato: **winner intermedio**
`SkyhookParams(ptn_n=45, sl_atr=2.5, tp_atr=7.0, uscitalong=24, uscitashort=16, vola_lo=35, vola_hi=95, vol_lo=0)`
**minFull +0.83, minHold +0.81** (vs V1 +0.69/+0.64), causale, fee-surviving 0.30%RT, marginal
**ADDS** (corr 0.05, has_insample_edge, robust_oos, multicut, clean_year_uplift +0.37), blend w25
uplift_hold +0.58. **MA standalone maxDD ancora 34% (BTC) / 31% (ETH) → l'unico goal mancato era il DD<30%.**
## Onda 2 (`skyhook-improve-v2`, 14 famiglie DD-reduction) — SKH01-V2-DD vince
Obiettivo: tagliare il **DD standalone <30%** tenendo hold-out + `earns_slot`, e alzare l'uplift di
portafoglio. 14 famiglie (ensemble param/struct, vol-target, DD kill-switch, RR/stop grid, regime
tight, percentile, vol-expansion, breakout confirmation, dual-TF, asimmetria L/S, cadenza, chande,
Keltner), ognuna verificata da 2 scettici avversariali (window-luck/multicut/jackknife +
causalità/fee/plateau/overfit). Esito: **il winner intermedio cade.** Nuovo campione **SKH01-V2-DD**
(famiglia ASYM_LS, `src/strategies/skyhook.py:SKH01_V2_DD`, run `runs/SKH2_ASYM_LS.py`):
- **Config:** stesso SEGNALE del winner (`ptn_n=45, vola_lo=35, vola_hi=95, vol_lo=0, exit-bars 24/16`)
ma EXIT commutati da ATR a **percentuale fissa ASIMMETRICA** — long `sl=4% / tp=10%`, short
`sl=2% (più stretto) / tp=8%`. Motivazione meccanica: in crypto lo short si fa steamrollare da uno
spike vola e lo stop-ATR si allarga lasciando correre la perdita → il %-SL stretto sullo short
**cappa la perdita per-trade** che FORMA il maxDD. (Implementato come override per-direzione nel
motore, backward-compatible: campi `*_short=None` → comportamento simmetrico invariato.)
- **Numeri veri (verificati indipendentemente via `sk.study(SKH01_V2_DD)`):** standalone maxDD
**BTC 21.4% / ETH 27.4%** (<30% ✓, vs 34.4/30.5 del winner) — **goal RAGGIUNTO**; minFull **+0.99**,
minHold **+1.26**; causalità **0/400** entrambi gli asset; fee@0.30%RT BTC +1.05 / ETH +0.80
(positiva anche a 0.40%). Marginal vs TP01 **ADDS** (corr 0.09, has_insample_edge, is_hedge=False,
robust_oos, multicut, clean_year_uplift +0.57). **Blend 0.75·TP01 + 0.25·SKH: uplift_hold +0.87**
(vs +0.58 del winner); **blend 50/50: full 1.84 / hold 1.59 / DD 10.7%**. earns_slot=True,
beats_winner=True. **Plateau reale** (i vicini Spct_mb14/16 sl2% tengono DD 27-28%), non knife-edge.
Entrambi gli scettici: holds_up=True, confidence high, killer_finding=null.
**Top-3 dell'onda 2 (criteri onesti):**
| # | Famiglia | maxDD (BTC/ETH) | minHold | w25 uplift_hold | Verifica |
|---|---|---|---|---|---|
| **1** | **ASYM_LS → SKH01-V2-DD** | 27.4% (21.4/27.4) | +1.26 | **+0.87** | 2/2 high, killer=null ✅ |
| 2 | ENS_STRUCT (3-regime ensemble) | **22.9%** (21.2/22.8) | +1.00 | +0.67 | 2/2 high — ma 3 motori da eseguire |
| 3 | TPSL_DD (%-SL/TP hard) | 28.0% (28/25.5) | +1.11 | +0.75 | 1/1 (rate-limit) — caveat hedge-like |
**Lezioni anti-DD:**
- **Ha funzionato (STRUTTURA dell'exit, non i parametri):** cambiare il MECCANISMO di uscita — %-SL
hard, asimmetria L/S, o ensemble di exit/regime diversi (decorrelazione). Il DD del winner nasce
dalla coda intra-trade negli spike ATR; il %-SL la cappa.
- **NON ha funzionato (la leva non raggiunge il DD vincolante):** DD kill-switch entry-only (sopprime
solo le NUOVE entry, non chiude il trade aperto che forma il maxDD → floor 33-36%); vol-target
causale (DD<30 e uplift≥0.55 mutuamente esclusivi; cap>1 PEGGIORA il DD levereggiando nel pre-crash);
cadenza/FREQ (accorciare gli hold short fa esplodere ETH a 50-66%); dual-TF (LTF è resample dello
stesso prezzo → quasi-tautologico, DD invariato).
- **Bocciato dagli scettici come overfit:** PATTERN_CONF (sub-30 solo a vola_lo=45, knife-edge: sl_atr
±0.5 → ETH 40-47%; la conferma "close_loc" da sola NON taglia il DD). Esempio canonico del perché
serviva la doppia verifica.
- **Non promuovibili:** PCTL_DD (numeri spettacolari ma **0 verifiche**, le 2 sono morte per rate-limit
→ forward-monitor, non fidato); ENS_PARAM / TPSL_DD (battono i gate ma uplift recency/hedge-loaded,
concentrato nei regimi TP01-down → forward-monitor).
**Promozione (questa sessione):** `SKH01_V2_DD` canonico nel motore + override exit-short
asimmetrici (backward-compatible, V1/winner invariati) + 3 test nuovi (8/8 pass).
**Sleeve cablato @0.25 effettivo** (`src/portfolio/sleeves.skyhook_sleeve``active_sleeves`): i tre
sleeve preesistenti scalati nel restante 0.75 mantenendo il rapporto 55:25:20 → **TP01 41.25% / XS01
18.75% / VRP01 15% / SKH01 25%**. Report del portafoglio (4 sleeve, `run_portfolio.py`):
| | FULL Sharpe | FULL DD | HOLD-OUT Sharpe | HOLD-OUT DD |
|---|---|---|---|---|
| 3 sleeve (TP01+XS01+VRP01) | 1.68 | 14.3% | 1.63 | 3.4% |
| **+ SKH01 @25%** | **2.13** | **7.8%** | **2.30** | 3.5% |
| Δ | **+0.45** | **6.5pt** | **+0.67** | ~0 |
→ aggiungere Skyhook **alza lo Sharpe full +0.45 e DIMEZZA il DD full (14.3→7.8%)**, e alza l'hold-out
+0.67 a DD invariato. Portafoglio combinato: FULL Sh 2.13 / ret +365% / DD 7.8%, HOLD Sh 2.30 / DD 3.5%,
positivo ogni anno (2019-26, DD annuo ≤7.8%) vs buy&hold 50/50 FULL Sh 0.93 / DD 76%.
**Caveat onesti / NON deploy:** è un portafoglio di **ricerca** (peso fisso, no costi di ribilanciamento
reale a $600; lo Sharpe daily-step di Skyhook è la convenzione del lens). ETH DD standalone 27.4% ha
margine sottile vs 30%. Prima di un eventuale deploy: ri-verificare la causalità sul **codice di
esecuzione reale** (qui è l'harness di ricerca) e i costi del book a 230m (ribilanciamento più frequente
del resto). XS01/VRP01 restano STAT-MODE/lead. Per ora: research win + sleeve cablato, forward-monitor.