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:
@@ -1,667 +1,133 @@
|
||||
# PythagorasGoal — Istruzioni per agenti
|
||||
|
||||
## Panoramica
|
||||
## Stato del progetto — v2.0.0 RESET (2026-06-19)
|
||||
|
||||
Progetto di ricerca: riconoscimento pattern frattali per trading algoritmico su criptovalute (BTC, ETH). L'obiettivo è arrivare a €50/giorno di profitto partendo da €1.000, entro 6–8 mesi.
|
||||
**LEGGERE PRIMA DI TUTTO.** Il progetto è stato resettato dopo aver scoperto che l'intera
|
||||
libreria di strategie "validata OOS" (FADE, PAIRS, DIP01, TR01, ROT02, TSM01, XS01, SH01) era
|
||||
**artefatto di uno storico contaminato** — print fantasma del feed Cerbero **testnet** + storico
|
||||
**Binance/USDT**. Ri-testate sul feed reale, tutte perdono ogni anno (vedi
|
||||
`docs/diary/2026-06-19-deribit-history.md`, il documento di fondazione).
|
||||
|
||||
Cosa è cambiato:
|
||||
- Lo storico è stato **ricostruito da Deribit mainnet** e **certificato**. Universo affidabile =
|
||||
**solo BTC/ETH** (tutti i TF). Gli alt sono esclusi (illiquidi/divergenti/non certificabili).
|
||||
- Tutto il codice vecchio (strategie, stack live, ~100 script di ricerca/gate, dati non
|
||||
certificati, 60+ diari) è **archiviato in `Old/`** (preservato in git, non cancellato).
|
||||
- L'esecuzione è **DISABILITATA**, il conto mainnet è flat. **Non c'è trading live attivo.**
|
||||
- Si riparte dalla ricerca di strategie NUOVE, su dati certi, con la metodologia qui sotto.
|
||||
|
||||
## Obiettivo
|
||||
|
||||
Ricerca: riconoscimento pattern frattali per trading algoritmico su crypto. Target dichiarato
|
||||
€50/giorno partendo da €1.000. **Onestà prima di tutto**: nessun numero va creduto finché non è
|
||||
netto fee, out-of-sample, robusto su griglia, e su dati certificati + liquidi + eseguibili.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Linguaggio:** Python 3.11+
|
||||
- **Package manager:** uv (dipendenze in `pyproject.toml`, lock in `uv.lock`)
|
||||
- **Dati:** Parquet in `data/raw/` (non committati, ~70 MB)
|
||||
- **ML:** scikit-learn (GradientBoostingClassifier)
|
||||
- **Analisi:** numpy, pandas, scipy
|
||||
- **API dati:** Cerbero MCP su `cerbero-mcp.tielogic.xyz` (Deribit, Bybit, Hyperliquid), ccxt/Binance come fallback
|
||||
- **Config:** pyyaml per `strategies.yml`
|
||||
- **Linguaggio:** Python 3.11+ — **Package manager:** uv (`pyproject.toml`, `uv.lock`)
|
||||
- **Dati:** Parquet in `data/raw/` (gitignored). Solo BTC/ETH (5m/15m/1h).
|
||||
- **Analisi/ML:** numpy, pandas, scipy, scikit-learn
|
||||
- **Fonte dati storici:** Deribit mainnet via `ccxt` (pubblico, tokenless)
|
||||
|
||||
## Struttura
|
||||
## Struttura (post-reset)
|
||||
|
||||
```
|
||||
src/data/ → download e caricamento dati
|
||||
downloader.py → download/caricamento parquet (gate: solo strumenti validati)
|
||||
instruments.py → discovery + validazione strumenti per exchange, registry
|
||||
src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py)
|
||||
src/backtest/ → engine di backtesting (engine.py)
|
||||
src/strategies/ → classe base Strategy ABC + indicatori condivisi
|
||||
base.py → Strategy, Signal, BacktestResult, YearlyStats
|
||||
indicators.py → keltner_ratio, detect_squeezes, ema, atr, rv, correlation
|
||||
src/live/ → paper trading live multi-strategia
|
||||
multi_runner.py → orchestratore: carica YAML (strategies + pairs), fetch candele, tick worker
|
||||
strategy_worker.py → worker single-leg: capital, trade log, stato persistente.
|
||||
Exit guidati da strategia (TP/SL/max_bars via Signal.metadata),
|
||||
fallback hold_bars/stop -2%. Usa fee_rt della strategia.
|
||||
pairs_worker.py → worker a 2 GAMBE per PR01 (market-neutral): long A / short B sullo
|
||||
z-score del log-ratio, exit |z|<=z_exit o max_bars, fee su 2 gambe.
|
||||
strategy_loader.py → import dinamico classi Strategy da scripts/strategies/
|
||||
cerbero_client.py → client HTTP per Cerbero MCP (Deribit testnet): dati + ordini
|
||||
(place_order market/reduce_only, get_trade_history, get_positions)
|
||||
execution.py → ExecutionClient: esecuzione REALE su Deribit (shadow). notional→
|
||||
amount (lineari USDC + inverse), open/close_amount reduce-only,
|
||||
verifica sul trade (order_id), fee reali dai trades[]
|
||||
signal_engine.py → squeeze + ML real-time (legacy ML01, ora in waste) + validazione OOS
|
||||
telegram_notifier.py → notifiche Telegram per trade
|
||||
src/portfolio/ → portafogli di prima classe (capitale-pool, backtest+live)
|
||||
base.py → SleeveSpec, Portfolio (.backtest), load_active_portfolio
|
||||
weighting.py → schemi pesi: equal/cap/inverse_vol/cluster_rp/manual
|
||||
sleeves.py → builder unificato equity-per-sleeve (fonte unica, parità report)
|
||||
ledger.py → PortfolioLedger: capitale/PnL/DD/persistenza+resume
|
||||
runner.py → PortfolioRunner live (data Cerbero v2, sizing, ribilancio)
|
||||
src/version.py → APP_VERSION (legge il file VERSION) — mostrato nei msg Telegram
|
||||
src/strategies/fade_base.py → FadeStrategy + helper: atr, trend_distance, hurst_skip_mask (loss-guard),
|
||||
exit close-confirm SL via param sl_confirm_atr (EXIT-16)
|
||||
scripts/strategies/ → strategie con edge validato OOS: FADE (MR01/MR02/MR07),
|
||||
HONEST (DIP01/TR01/ROT02), PAIRS (PR01), TSMOM + portafogli (PORT01/02/03);
|
||||
FR01_hurst_calm_fade.py = record ricerca (robusto ma NON deployato)
|
||||
scripts/portfolios/ → definizioni PORT01-06 (_defs.py) + report run() + hourly_report.py (Telegram)
|
||||
scripts/waste/ → strategie scartate (W01-W28 + famiglia squeeze SQ/MT/ML/AD/CM/PD)
|
||||
scripts/analysis/ → ricerca/validazione OOS fee-aware (strategy_research, oos_validation, ...);
|
||||
regime_fetcher.py + regime_lab.py (DVOL/funding/feature regime per la ricerca);
|
||||
exit_lab.py + exit_policies/ (harness ricerca exit: cache segnali, train/OOS);
|
||||
options_fetcher.py + options_chain.py (storico opzioni REALE da cerbero-bite);
|
||||
option_overlay_lab.py (overlay opzioni prezzato; mr02eth_port06_gate.py = gate swap-sleeve);
|
||||
pairs15m_*.py (gate/flatcheck/smoke ETH/BTC 15m -> sleeve PR_ETHBTC_15M)
|
||||
scripts/games/ → gioco "Blind Traders": 100 agenti ciechi su BTC/ETH anonimi (engine.py,
|
||||
arena.py epoche+cull, agent_brief.py digest, run_game.py). Origine del BLEND 15m.
|
||||
Varianti: options_* (strutture in opzioni, BS+skew+DVOL; opt_calibrate da
|
||||
cerbero-bite), session_* (pattern orari), grid_* (griglie da STRATEGIA_GRIGLIA.md;
|
||||
gate scripts/analysis/grid_game_gate.py). arena: GAME_NO_LIVE=1 vieta le
|
||||
strategie gia' deployate (pairs, fade zscore/breakout/momentum)
|
||||
scripts/bump_version.py, scripts/deploy.sh → versionamento e deploy (bump+commit+rebuild)
|
||||
VERSION → versione semver (cotta nell'immagine, +1 ad ogni deploy)
|
||||
strategies.yml → config multi-strategy paper trader
|
||||
docs/diary/ → diario di ricerca giornaliero
|
||||
docs/specs/ → specifiche di design
|
||||
data/raw/ → file .parquet OHLCV (gitignored) | data/regime/ → DVOL+funding+feature (gitignored)
|
||||
data/options/ → storico catena opzioni per-strike (bid/ask/IV/greche/OI) importato da cerbero-bite (gitignored)
|
||||
data/instruments_registry.json → allowlist strumenti validati (gate del downloader)
|
||||
src/data/downloader.py → load_data(asset, tf): legge i parquet certificati da data/raw/
|
||||
src/strategies/base.py → Strategy (ABC), Signal, BacktestResult, YearlyStats
|
||||
src/strategies/indicators.py → indicatori condivisi (ema, atr, keltner, ...)
|
||||
src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py)
|
||||
src/backtest/engine.py → engine di backtesting riusabile
|
||||
src/version.py → APP_VERSION (legge il file VERSION)
|
||||
scripts/analysis/ → SOLO i tool dati certificati:
|
||||
rebuild_history.py → (ri)costruisce lo storico da Deribit mainnet (base 5m + resample)
|
||||
certify_feed.py → certifica il feed (integrità, coerenza resample, spike, cross-venue)
|
||||
audit_feed.py → audit per-barra vs riferimento esterno
|
||||
multi_source_check.py → cross-check multi-venue (quale venue è "vero")
|
||||
data/raw/ → btc/eth × {5m,15m,1h} (gitignored). UNICO dato attivo.
|
||||
data/instruments_registry.json → registry strumenti (reference)
|
||||
docs/diary/ → diario di ricerca (1 voce: il reset; aggiungere dopo ogni esperimento)
|
||||
Old/ → ARCHIVIO: tutto il vecchio (strategie, live, ricerca, dati, diari)
|
||||
VERSION → semver (2.0.0)
|
||||
```
|
||||
|
||||
## Comandi
|
||||
|
||||
```bash
|
||||
uv sync # installa dipendenze
|
||||
uv run python -m src.data.downloader # scarica dati storici (solo strumenti validati)
|
||||
uv run python -m src.data.instruments # (ri)costruisci il registry strumenti validati
|
||||
uv run python scripts/strategies/MR01_bollinger_fade.py # backtest una strategia (es. fade)
|
||||
uv run python scripts/strategies/PR01_pairs_reversion.py # backtest pairs market-neutral
|
||||
uv run python scripts/analysis/strategy_research.py # ricerca strategie fee-aware OOS
|
||||
uv run python scripts/analysis/oos_validation.py # perche' la famiglia squeeze e' scartata
|
||||
uv run python scripts/analysis/report_families.py # report per anno di tutte le famiglie
|
||||
uv run python scripts/analysis/validate_worker_pairs.py # replay worker 2 gambe == backtest
|
||||
uv run python -m src.live.multi_runner # paper trading live multi-strategia (strategie + pairs)
|
||||
uv run python scripts/portfolios/PORT06_master_shape.py # report backtest portafoglio (default)
|
||||
uv run python -m src.portfolio.runner # paper trading a PORTAFOGLIO (capitale pool)
|
||||
uv run python scripts/analysis/smoke_portfolio.py # smoke live data layer Cerbero v2
|
||||
uv run python scripts/analysis/live_exec_smoke.py # smoke ESECUZIONE reale (ordine→verifica→fee, testnet)
|
||||
uv run python scripts/analysis/live_shadow_smoke.py # smoke catena shadow nel worker (open/close reali)
|
||||
uv run python scripts/analysis/regime_fetcher.py # fetch DVOL+funding (Deribit mainnet) -> data/regime/
|
||||
uv run python scripts/analysis/exit_lab.py # (ri)costruisci cache segnali exit-lab + parity check
|
||||
uv run python -m src.live.dashboard --port 8787 # dashboard web (servizio compose 'dashboard', porta 8787)
|
||||
uv run python scripts/analysis/ledger_vs_backtest.py --since 2026-06-13 # report fuga di esecuzione (reale vs sim)
|
||||
uv run python scripts/analysis/trades_status.py [--since ISO] # report "stato trades": POOL reale vs PAPER-STATS, realizzato + unrealized
|
||||
./scripts/deploy.sh [patch|minor|major] # DEPLOY: bump versione + commit + rebuild Docker
|
||||
uv run pytest # test
|
||||
uv sync # installa dipendenze
|
||||
uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # (ri)costruisci storico da Deribit mainnet
|
||||
uv run python scripts/analysis/certify_feed.py # certifica i feed (locale + cross-venue)
|
||||
uv run python scripts/analysis/certify_feed.py --local # solo check locali (veloce)
|
||||
uv run pytest # test (da ripopolare con le nuove strategie)
|
||||
```
|
||||
|
||||
> **Dashboard web (`src/live/dashboard.py`, servizio compose `dashboard`, porta 8787).**
|
||||
> Stdlib http.server, legge `data/`: equity (restyling), PnL totale e per-strategia,
|
||||
> **grafico equity per famiglia**, trade attivi in tempo reale (entry/mark/PnL **REALI** —
|
||||
> il feed sim-decisione testnet è dislocato, vedi sotto) + chiusi, modal per-strategia
|
||||
> (curva reale vs sim + scheda `strategie_attive.html`), **area PAPER distinta** (equity
|
||||
> propria dei 4 multi-asset), strategie **ritirate** marcate (staleness >30min) + versione
|
||||
> sistema/strategia. Nessuna auth → solo rete interna. `docker compose up -d --build dashboard`.
|
||||
|
||||
> **Cron host (monitoraggio, già schedulati):** `hourly_report.py` (orario), `drift_monitor.py`
|
||||
> (07:15), `reconcile_account.py --telegram` (:40), **`ledger_vs_backtest.py --since 2026-06-13
|
||||
> --telegram` (08:30)** = il GATE per scalare il capitale (per gli sleeve eseguiti sim==backtest
|
||||
> per costruzione → reale-vs-sim = fuga di esecuzione: slippage+fee+netting; verdetto 🟢/🟡/🔴).
|
||||
|
||||
> **Deploy.** Il sorgente è **COPY nell'immagine Docker** (non montato) → `docker compose restart`
|
||||
> NON ricarica il codice: serve **`docker compose up -d --build`** (o `./scripts/deploy.sh`, che bumpa
|
||||
> la versione, committa e rebuilda). Il volume `data/` persiste → i worker fanno RESUME dello stato.
|
||||
> La **versione** (file `VERSION`, semver, +1 ad ogni deploy via `deploy.sh`) compare nei messaggi
|
||||
> Telegram (notifiche trade + report orario) → correli ogni msg al codice che l'ha generato.
|
||||
|
||||
## Dati storici
|
||||
|
||||
Scaricati e salvati localmente in Parquet. Per rigenerarli:
|
||||
|
||||
```python
|
||||
from src.data.downloader import download_all, load_data
|
||||
download_all() # scarica BTC + ETH su 5m/15m/1h dal 2018
|
||||
df = load_data("ETH", "15m") # carica un asset/timeframe
|
||||
from src.data.downloader import load_data
|
||||
df = load_data("BTC", "1h") # OK. load_data("SOL", ...) -> FileNotFoundError (guardrail: solo dati certi)
|
||||
```
|
||||
|
||||
Fonte primaria: Cerbero MCP (endpoint `/mcp-deribit/tools/get_historical`).
|
||||
Token observer: nel file `secrets/observer.token` del progetto CerberoSuite.
|
||||
## IL DATO — fonte di verità (regola di prim'ordine)
|
||||
|
||||
### Strumenti & validazione (gate raccolta dati)
|
||||
- **La verità è Deribit mainnet**, perché è dove (in futuro) eseguiamo. Cross-check multi-venue:
|
||||
Deribit mainnet è a 0-1 bps dal consenso. **Binance NON è la verità** (è USDT, ~10 bps fuori, e
|
||||
sotto depeg USDT fino al 3% off) → usare Binance/Coinbase SOLO come audit indipendente, mai come
|
||||
ancora per "ripulire" i dati.
|
||||
- **Aggiornare lo storico SOLO con `rebuild_history.py`** (ccxt Deribit mainnet, base 5m unica +
|
||||
resample → coerenza interna garantita). **MAI** il vecchio downloader Cerbero (token testnet =
|
||||
feed farlocco: è la causa della contaminazione).
|
||||
- **Certificare sempre** dopo un rebuild con `certify_feed.py` (integrità OHLC, zero gap, coerenza
|
||||
resample maxΔ≈0, spike = solo crash reali, accordo cross-venue per-anno vs Coinbase USD).
|
||||
|
||||
`src/data/instruments.py` scopre e **valida** gli strumenti per ogni exchange
|
||||
implementato — **Deribit** e **Hyperliquid** (esclusi Alpaca/stocks e **Bybit**,
|
||||
il cui feed testnet è farlocco). Per ogni perpetuo enumera via `get_instruments`
|
||||
/`get_markets` e verifica sui **dati storici realmente raccoglibili**:
|
||||
esistenza, congruenza OHLC, not-flat (scarta contratti morti), liquidità (volume
|
||||
daily) e **congruenza prezzo cross-exchange** (scostamento dalla mediana del
|
||||
base-coin ≤ 5% → scarta outlier come `SOL-PERPETUAL`=9.6 vs SOL reale ~82).
|
||||
### Universo ricercabile certificato
|
||||
- **BTC / ETH**: puliti (2-6 bps vs Coinbase USD su tutta la storia), liquidi (~0% barre flat a 1h),
|
||||
storia lunga (2018/2019→oggi) → **ogni timeframe (5m/15m/1h)**. È l'unico dato in `data/raw`.
|
||||
- **Alt (SOL/XRP/ADA/LTC/DOGE/BNB): FUORI.** Illiquidi (LTC 5m 82% barre flat O=H=L=C, run fino a
|
||||
~3 giorni), divergenti (LTC/DOGE >1% su 10-21% delle barre 2022-23), o non certificabili
|
||||
(XRP delistato da Coinbase per causa SEC; BNB non listato + storia da 2024-10). Sono archiviati in
|
||||
`Old/data/raw`. Riammetterne uno richiede prima una ricertificazione che dimostri liquidità + accordo.
|
||||
|
||||
Output: `data/instruments_registry.json` (strumenti validi, timeframe, start-date).
|
||||
**Gate:** `_download_cerbero_range` rifiuta gli strumenti non validati (override
|
||||
`allow_unvalidated=True` solo per casi eccezionali). Rigenera con
|
||||
`python -m src.data.instruments`.
|
||||
## Metodologia obbligatoria per ogni nuova strategia
|
||||
|
||||
> **NB testnet.** Il token Cerbero punta a testnet; la congruenza cross-exchange
|
||||
> è il filtro che distingue i feed realistici (Deribit, Hyperliquid) da quelli
|
||||
> farlocchi (Bybit). Simboli Deribit: BTC/ETH = `<COIN>-PERPETUAL` (inverse);
|
||||
> alt = `<COIN>_USDC-PERPETUAL` (lineari USDC). Registry attuale: Deribit 18/106,
|
||||
> Hyperliquid 66/74 validi (major liquidi: BTC dal 2018, alt dal 2022).
|
||||
1. **Ingresso eseguibile**: direzione e prezzo decisi con dati **fino a `close[i]`**, mai
|
||||
`close[i-1]` con direzione presa da `i`; mai entry sull'estremo (high/low) di una candela.
|
||||
2. **Backtest NETTO** dopo fee realistiche Deribit (**0.10% RT** taker; maker ~0%) + leva.
|
||||
3. **Out-of-sample** held-out + robustezza su **griglia parametri** (entrambi gli asset, tutte le
|
||||
celle positive) + **sweep fee** (0.00-0.20% RT, margine ampio).
|
||||
4. **Liquidità & plausibilità** (lezione v2.0.0): incrociare ogni edge con la liquidità reale del
|
||||
book (quota di barre flat) e con la plausibilità del prezzo (cross-venue). Un edge full+OOS
|
||||
robusto su un book fermo o su wick fantasma NON è un edge.
|
||||
5. Strategia in `scripts/strategies/` (codice univoco), test in `tests/`, diario aggiornato.
|
||||
|
||||
## Strategie attive
|
||||
## Lezioni critiche (da NON ripetere — la storia di questo progetto)
|
||||
|
||||
> **LEZIONE CRITICA (2026-05-28).** L'intera famiglia squeeze-breakout (SQ01-04,
|
||||
> MT01, ML01, AD01, CM01, PD01) è stata **scartata in `scripts/waste/`**: le
|
||||
> accuratezze storiche 76-82% erano un **artefatto di look-ahead**. Quei backtest
|
||||
> decidono la direzione con `sign(close[i]-close[i-1])` (la candela di breakout `i`)
|
||||
> ma entrano a `close[i-1]` — cioè comprano *prima* della candela che usano per
|
||||
> scegliere la direzione. Dal vivo il worker scopre il breakout solo a `close[i]`
|
||||
> ed entra lì: l'edge sparisce (win-rate ~47%, lancio di moneta). Sotto ingresso
|
||||
> onesto e fee reali **tutte perdono, anche a fee zero**. Inoltre i breakout
|
||||
> *rientrano* (mean-reversion > continuation). Vedi `scripts/analysis/oos_validation.py`
|
||||
> e `intrabar_test.py`.
|
||||
|
||||
Tutte le strategie estendono `src.strategies.base.Strategy`
|
||||
(`generate_signals() → backtest()`). Le strategie mean-reversion condividono
|
||||
`src.strategies.fade_base.FadeStrategy` (backtest intrabar TP/SL/max_bars).
|
||||
**Strategie con edge netto validato OOS fee-aware (tutte fade/mean-reversion):**
|
||||
|
||||
| Codice | Nome | Meccanismo | Edge OOS netto (1h, fee 0.10% RT) | DD | Note |
|
||||
|--------|------|-----------|-----------------------------------|----|------|
|
||||
| **MR01** | Bollinger Fade | banda std attorno a SMA | BTC +201% / ETH +1238% | 15-72% | Fada la banda, TP alla media, SL ad ATR |
|
||||
| **MR02** | Donchian Fade | estremi canale H/L | BTC +172% / ETH enorme | 30-42% | Fada la rottura del canale, TP al centro |
|
||||
| **MR07** | Return Reversal | z dei rendimenti di barra | BTC +105% / ETH +195% | 25-46% | Fada il movimento estremo, exit in ATR; esposizione ~8% |
|
||||
|
||||
> **MR03 Keltner Fade** spostata in `scripts/waste/`: era la fade più debole
|
||||
> (BTC Sharpe 1.22, il filtro trend la peggiorava) e ridondante con MR01 (stessa
|
||||
> idea di banda). Rimuoverla dal portafoglio ne ha *migliorato* le metriche.
|
||||
> La funzione `keltner_fade` resta in `strategy_research_v2.py` come record.
|
||||
|
||||
**Lezione confermata:** l'edge è sempre *mean-reversion* (i breakout rientrano).
|
||||
Il trend-following (Donchian trend, RSI cross) e gli oscillatori senza filtro
|
||||
(RSI revert, ADX-filtered fade) perdono netti → restano scartati.
|
||||
|
||||
Ogni strategia è robusta su **tutta** la sua griglia parametri (entrambi gli asset
|
||||
→ tutte positive OOS) e su **tutte** le fee 0.00-0.20% RT (margine ampio).
|
||||
MR01 validato col worker reale: BTC +196% / ETH +251% OOS (nov 2023→mag 2026).
|
||||
Ricerca completa: `scripts/analysis/strategy_research.py` (MR01) e
|
||||
`scripts/analysis/strategy_research_v2.py` (MR02/MR03/MR07).
|
||||
Validazione live-path: `scripts/analysis/oos_validation.py`.
|
||||
|
||||
**Filtro trend (riduzione DD + aumento Acc).** Tutte le fade accettano i parametri
|
||||
opzionali `trend_max` / `ema_long`: saltano i segnali quando il prezzo è troppo
|
||||
esteso rispetto al trend di fondo (`|close − EMA(ema_long)| / ATR(14) > trend_max`),
|
||||
cioè quando si starebbe fadando un trend/crollo estremo. Con `trend_max=3.0`,
|
||||
`ema_long=200` (default in `strategies.yml`): accuratezza su tutti gli sleeve
|
||||
e DD giù drasticamente su ETH (MR01 71%→26%, MR02 42%→25%, MR03 66%→34%,
|
||||
MR07 46%→21%), edge OOS confermato (vedi `scripts/analysis/risk_management.py`).
|
||||
Unica eccezione: MR03 BTC, dove il filtro peggiora entrambe → lasciato disattivo.
|
||||
Leva non robusta scartate: vol-target sizing e skip-alta-volatilità (peggiorano).
|
||||
|
||||
**SWAP filtri fade: hurst→trend (2026-06-07).** Il gate PORT06 sul path live
|
||||
(`scripts/analysis/trendmax_port06_impact.py`, parità 1.00000 col canonico) ha mostrato che
|
||||
**post-EXIT-16 il loss-guard Hurst è ridondante-dannoso**: EXIT-16 ha eliminato i wick-stop che
|
||||
hurst evitava → gli ingressi saltati (66% delle barre!) sono in maggioranza tornati vincenti.
|
||||
Su PORT06: LIVE hurst-only FULL Sh 7.23 / OOS 9.35-DD 1.68 vs **TREND-ONLY 7.89 / 9.91-DD 1.20**
|
||||
(domina su tutte le metriche; hurst+trend insieme over-filtra: 7.11, metà dei trade; plateau
|
||||
trend_max 2.5/3.0/3.5 robusto). TREND-ONLY è la config che la ricerca EXIT-16 aveva davvero
|
||||
promosso (entries trend-filtrate, no hurst) e che il live non aveva mai eseguito. **Live ora:
|
||||
`trend_max=3.0, ema_long=200` nelle 6 fade di `_defs.py`, `hurst_max` rimosso** (la maschera resta
|
||||
in `fade_base`). Monitor: `hourly_report` traccia lo stop-rate per epoca PRE→HURST→TREND.
|
||||
Lezione: ri-gateare ogni filtro quando cambia l'exit engine. Diario
|
||||
`docs/diary/2026-06-07-trendmax-gate.md`. Il paragrafo sotto resta come record storico:
|
||||
|
||||
**Loss-guard Hurst (storico: live dal 2026-06-02 al 2026-06-07, poi sostituito dal filtro trend).** Le fade accettano `hurst_max`: saltano i segnali in
|
||||
regime PERSISTENTE/trending (rolling-Hurst ≥ soglia), dove si concentrano stop-loss e perdite
|
||||
(diagnosi: stop-rate 43% per hurst>0.55 vs 21% anti-persistente; i peggiori 1% trade hanno hurst
|
||||
medio 0.61). Helper `src.strategies.fade_base.hurst_skip_mask` (rolling-Hurst causale **dalle sole
|
||||
close** → nessun feed esterno; step=6 per velocità live). **`hurst_max=0.55` attivo sulle 6 fade in
|
||||
`_defs.py`**: il test decisivo a livello PORT06 lo conferma — **FULL DD 4.10%→2.39% (quasi dimezzato),
|
||||
Sharpe 6.62→6.76, OOS Sharpe 8.89→9.15**. È l'UNICO meccanismo anti-perdite che supera il gate (ADX,
|
||||
vol-expansion/vratio, efficiency-ratio, time-stop, vol-target FALLISCONO: tagliano i winner insieme ai
|
||||
loser; i claim esterni ADX/ATR-ratio non si replicano su queste fade crypto). NB: il filtro agisce solo
|
||||
sul path LIVE (`spec.params`); il backtest canonico (`build_everything`/regression-lock) NON è filtrato
|
||||
→ il live farà MEGLIO del backtest sul DD. Ricerca: `scripts/analysis/fade_lossguard_workflow.js`,
|
||||
diagnosi `fade_loss_by_regime.py`, diario `docs/diary/2026-06-02-fade-lossguard.md`.
|
||||
**Effetto misurato (backtest):** stop-loss fade −67% in numero (1881→621), perdite totali −68%, coda
|
||||
−61%→−48% (lo stop-RATE per-trade scende poco, 42→38%: il guard lavora riducendo l'ESPOSIZIONE nel
|
||||
regime tossico, non rendendo sicuro ogni trade). **Monitor live:** `hourly_report.py` traccia lo
|
||||
stop-rate fade PRIMA/DOPO l'attivazione (14:34 UTC del 2026-06-02) e dà il verdetto su Telegram quando
|
||||
il campione DOPO ≥30 (già confermato: stop-rate live PRIMA 42% == backtest 42.1%).
|
||||
|
||||
**FIX EXIT-16 live — confirm su barra COMPLETATA (2026-06-05).** Il worker valutava il confirm-SL
|
||||
sul prezzo della candela IN FORMAZIONE ad ogni poll → reintroduceva la wick-sensitivity che EXIT-16
|
||||
elimina (audit: 2 stop su 3 del crash ETH erano wick-stop che il backtest non avrebbe preso in quel
|
||||
momento). Ora `tick` valuta il confirm SOLO sul close dell'ultima barra completata (detection: la
|
||||
riga -1 del df è la candela in corso finché `now < ts[-1]+bar_ms`), buf dall'ATR della stessa barra;
|
||||
fill al prezzo corrente (≈ stress lag_close_exit, OK in exit-lab); TP intrabar invariato. La concausa
|
||||
feed-gap NON è mitigabile lato exit (fill reali ≈ sim) e l'entry-guard post-flat è BOCCIATA (skippare
|
||||
i segnali dopo barre flat PEGGIORA tutti gli sleeve ETH: la candela-gap è l'overshoot che la fade
|
||||
fada). Aggiunto alert Telegram `STALE_FEED` (≥2 barre 1h flat → notifica + gap % al risveglio, solo
|
||||
osservabilità). Diario `docs/diary/2026-06-05-confirm-sl-forming-bar.md`.
|
||||
|
||||
**EXIT-16 close-confirm SL (ATTIVO LIVE, 2026-06-04; esteso a DIP01 il 2026-06-07).** Le fade E
|
||||
DIP01 accettano `sl_confirm_atr` (live: 0.5 in `_defs.py`): lo SL **intrabar è disattivato** e lo stop scatta solo se il **CLOSE** della barra
|
||||
sfonda `sl ∓ 0.5·ATR14`, con uscita al close (TP intrabar al livello e max_bars invariati; in modalità
|
||||
confirm il TP ha priorità nel bar). Scoperta della ricerca exit-lab (34 agenti, 23 famiglie esplorate +
|
||||
10 verifiche avversariali + test PORT06): **gli stop intrabar da wick sono falsi negativi** — l'overshoot
|
||||
che buca lo stop e rientra è esattamente il movimento che la fade fada. Verificato: indipendente dal
|
||||
loss-guard Hurst, plateau buffer 0.4-1.0, regge fee 2x/lag/slippage, coda ≈ base nei crash veri (FTX:
|
||||
+2.4% vs −39% del no-SL puro). **PORT06: FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS Sharpe 8.82→10.06 DD
|
||||
1.30→1.15.** La famiglia "cavalca il prezzo" (trailing/ride/partial-runner, 15 varianti) è invece tutta
|
||||
SCARTATA: oltre il TP=media non c'è coda (4ª conferma). Collaterali: l'engine intrabar filla gli stop
|
||||
"al livello" anche su gap-through (54% dei casi per stop tight) → bias PRO stop-stretti nelle ricerche
|
||||
future; mai deployare strategie con `sl=0` (il fallback −2% del worker non si applicherebbe). Harness
|
||||
riusabile `scripts/analysis/exit_lab.py` + policy in `exit_policies/`. Implementazione: `fade_base.backtest`
|
||||
+ `StrategyWorker.tick` (param `sl_confirm_atr`, None = comportamento storico; il backtest canonico
|
||||
`build_everything` resta NON filtrato → il live farà meglio del backtest, come per il loss-guard).
|
||||
Diario `docs/diary/2026-06-04-exit-lab.md`.
|
||||
|
||||
**Portafoglio.** Diversificare su sotto-conti indipendenti equipesati (le 4 strategie
|
||||
× BTC/ETH, pos 0.15 ciascuno) abbatte il DD aggregato: ~14% full / ~10% OOS sul
|
||||
paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown.
|
||||
|
||||
**Combinare le due famiglie (fade + honest).** Le fade (reversione intraday 1h) e le
|
||||
honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate**
|
||||
(correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il
|
||||
rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve
|
||||
→ DD 5.2% full / 4.7% OOS e Sharpe 4.23 full / 4.33 OOS (vs honest-only 12.6% DD /
|
||||
2.20 Sharpe e fade-only 8.2% DD / 4.09 Sharpe), CAGR ~47% mantenuta. Studio in
|
||||
`scripts/analysis/combine_portfolio.py`.
|
||||
|
||||
**ROT02 — riduzione DD (top_k 2→3).** La rotazione dual-momentum honest concentrava
|
||||
il book su 2 soli asset (DD 40%). Diversificare su 3 (`top_k=3`) dimezza quasi il DD
|
||||
(40%→26%) e *alza* pure il ritorno full (+1095%→+1303%, ret/DD da 27 a 50); il
|
||||
vol-target abbassa il DD ma sacrifica ritorno, quindi si tiene top_k=3 senza VT.
|
||||
Applicato a `ROT02_dual_momentum.py` e a `_rot_daily_equity` (usata dai portafogli).
|
||||
|
||||
**Portafogli pronti (artefatti accorpati e migliorati).** Oltre a `PORT01` (solo
|
||||
honest), due script in `scripts/strategies/`:
|
||||
- `PORT02_fade_master.py` — le 3 fade × BTC/ETH accorpate (6 sleeve, filtro trend),
|
||||
equal-weight daily: DD ~8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%.
|
||||
- `PORT03_all_master.py` — portafoglio MASTER (fade + honest, 9 sleeve). Due varianti:
|
||||
`equal` (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 4.23/4.33) e `5050` fra le due
|
||||
famiglie (minimo DD: 5.0% full / 4.5% OOS). È la configurazione consigliata.
|
||||
Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con
|
||||
`generate_signals`, quindi non nel `strategy_loader`.
|
||||
|
||||
**Esplorazione famiglie alternative (branch `strategy_explore`, 2026-05-29).** Esplorate
|
||||
9 famiglie nuove con agenti paralleli su harness onesto condiviso
|
||||
(`scripts/analysis/explore_lab.py`). 7 sono rumore (rifiutate: stagionalità oraria/mensile,
|
||||
cross-sectional reversal, opening-range breakout, lead-lag BTC→alt, continuation intraday —
|
||||
quest'ultima riconferma la dominanza mean-reversion). Due edge reali:
|
||||
- **PR01 Pairs** (`scripts/strategies/PR01_pairs_reversion.py`): spread reversion
|
||||
market-neutral sul log-ratio z-score, **config UNIVERSALE** `n=50 z_in=2.0 z_exit=0.75
|
||||
max_bars=72` (anti-overfit, niente tuning per-coppia). **5 coppie robuste**: ETH/BTC
|
||||
(Sharpe 4.36), LTC/ETH (3.08), ADA/ETH (2.69), BTC/LTC (2.36, robusta anche 4h), ETH/SOL
|
||||
(1.96, la più debole). Pattern: sempre alt-liquido vs major. Plateau confermato
|
||||
(heatmap 20/20 Sharpe>1) + walk-forward (ETH/BTC 11/12 finestre+). **BNB/ETH scartata**
|
||||
(overfit). Corr col mercato ~0.02-0.08. Fee su **2 gambe**: worker live implementato
|
||||
(`src/live/pairs_worker.py`, sezione `pairs:` in `strategies.yml`). LOGICA validata
|
||||
(`validate_worker_pairs.py`: replay == backtest ESATTO). LIVE (`live_smoke_pairs.py`,
|
||||
smoke reale Cerbero): **tutte e 5 le coppie con feed live fresco**. Naming Deribit:
|
||||
BTC/ETH = `<COIN>-PERPETUAL` (inverse); alt = `<COIN>_USDC-PERPETUAL` (lineari USDC,
|
||||
storia dal 2022). Trappola: `LTC-PERPETUAL`/`SOL-PERPETUAL` danno vuoto/dati errati →
|
||||
usare sempre `_USDC-PERPETUAL`. Resta da verificare solo liquidità/fill in esecuzione.
|
||||
Verifica edge: `pairs_research.py`.
|
||||
- **TSM01** (`scripts/analysis/tsmom_research.py`): TSMOM multi-orizzonte 3/6/12m + risk-off,
|
||||
**gross 0.30**, distinto da ROT02 (corr 0.62), DD 15-22%, mai un anno negativo. Robusto
|
||||
(36/36 config OOS+) ma diversificatore, non motore di ritorno (rende meno di ROT02).
|
||||
|
||||
Aggiungere i **5 pairs** al MASTER (quasi scorrelati, ~0.02-0.09) è il free-lunch più
|
||||
grande (`scripts/analysis/combine_v2.py`). **Numeri sobri onesti** (l'OOS singolo 2024-25
|
||||
è regime calmo → ottimistico ~50%): worst-DD su 90g rolling **~6%** (non 2.3%), Sharpe
|
||||
atteso **~5** (mediana semestrale), ogni anno positivo dal 2021, regge **leva 2x +
|
||||
slippage doppio** (CAGR 36%, Sharpe 5.1). Config robusta raccomandata: **MASTER-esteso
|
||||
equal-weight, leva 2x, cap pairs ~30-35%** (i pairs sono ~57% del rischio; worker live a
|
||||
2 gambe implementato, validato e con feed live su tutte e 5 le coppie — resta da
|
||||
verificare liquidità/fill in esecuzione reale). La confluenza multi-TF è stata SCARTATA (overfit).
|
||||
|
||||
**Pattern del segnale per FORMA (branch `shape_patterns`, 2026-05-29).** Esplorate 5 famiglie
|
||||
di *shape forecasting* con agenti paralleli su harness onesto (`scripts/analysis/shape_lab.py`:
|
||||
analog kNN causale, no-look-ahead verificato). **4/5 sono RUMORE** (riconfermano la dominanza
|
||||
mean-reversion): analog kNN sulla forma grezza (solo BTC-overfit), encoding candele
|
||||
UP/DOWN/DOJI+body/shadow (hit-rate ~50%), DTW+template geometrici (DTW *peggiora* l'euclidea;
|
||||
template overfit), PIP/pivot/zig-zag (0/48 robuste). Vedi `scripts/analysis/shape_*_research.py`.
|
||||
- **SH01 Shape-ML** (`scripts/strategies/SH01_shape_ml.py`): UNICO edge. Una LogisticRegression
|
||||
legge 17 feature di forma (body/shadow, rendimenti, pendenza/curvatura, pos max/min, RSI,
|
||||
estensione) e predice il segno del rendimento a H barre in **walk-forward** (scaler+modello
|
||||
solo sul passato, no leakage). Config **W24 H12 th0.58**. A differenza dello squeeze
|
||||
**regge fee 0.20% RT**. Win-rate ~50% → l'edge è nell'**asimmetria**, non nella frequenza.
|
||||
Validazione (`scripts/analysis/shape_ml_validate.py`): BTC robusto OVUNQUE (expanding +219%/
|
||||
OOS +42% Sharpe 2.72 8-9 anni; rolling 2y +166%/+96%; stress 2x+slippage OK), ETH/ADA
|
||||
robusti solo expanding (secondari), LTC/SOL/XRP scartati. Griglia: **5/27 celle robuste su
|
||||
cresta stretta W24/H8-12** → overfit moderato, scelta la config conservativa. **Valore vero:
|
||||
diversificatore** (corr +0.08 col MASTER); aggiungerlo migliora l'OOS del MASTER (Sharpe
|
||||
4.33→5.10, DD 4.7%→4.2%). NON motore standalone. **LIVE (2026-06-01): gira come StrategyWorker
|
||||
reale** (vedi fix wiring sotto in SCOPE LIVE). Diario: `docs/diary/2026-05-29-shape.md`.
|
||||
|
||||
**ARGO / GEX opzioni (analisi 2026-06-01, SCARTATO).** Valutato ARGO (lettura del gamma-exposure dei
|
||||
dealer Deribit) come filtro di regime. Esito **NO-GO**: il net-GEX si calcola live (Deribit mainnet
|
||||
public API, OI reale ~368k contratti, DVOL/funding storici gratis) ma **lo storico per-strike dell'OI
|
||||
non è gratuito → non backtestabile OOS** (stesso muro delle opzioni W18/19/21). Niente evidenza crypto,
|
||||
segno fragile, mercato dominato dai perp. Diario `docs/diary/2026-06-01-argo-gex-feasibility.md`.
|
||||
|
||||
**Storico opzioni REALE da cerbero-bite (2026-06-09, il muro ARGO è caduto per il periodo recente).**
|
||||
Il container **cerbero-bite** (accanto, `/opt/docker/cerbero-bite`) accumula in continuo lo **storico
|
||||
per-strike** della catena opzioni Deribit (BTC+ETH) nella tabella `option_chain_snapshots` del suo
|
||||
SQLite (`bite-data:/app/data/state.sqlite`, root-only): bid/ask/mid/**IV per-strike**/greche/OI/volume,
|
||||
**dal 2026-05-01**, cadenza ~12 min (~110k righe/asset). È esattamente il dato che ARGO/W18-21
|
||||
credevano non-gratuito. `scripts/analysis/options_fetcher.py` lo importa via `docker exec` →
|
||||
`data/options/{eth,btc}_chain.parquet`; `scripts/analysis/options_chain.py` (`OptionChain`) espone
|
||||
loader + `skew_curve()` / `premium_levels()` (aggregati ROBUSTI) + `quote()` causale best-effort.
|
||||
**Caveat granularità:** cerbero-bite snapshotta una **fetta rotante** (~1 scadenza per ciclo) → ottimo
|
||||
per skew/premi **aggregati**, limitato per il pricing **per-trade** preciso (usare la finestra di
|
||||
staleness). **Pannello regime `market_snapshots`** (anch'esso importato → `data/options/market_snapshots.parquet`,
|
||||
loader `options_chain.load_market(asset)`; merge causale su prezzo via `options_chain.attach_market(df, asset)`):
|
||||
feature REALI pre-calcolate — **spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross,
|
||||
`dealer_net_gamma` (net-GEX dealer!), `gamma_flip_level`, oi_delta_pct_4h, liquidation_long/short_risk**.
|
||||
È il segnale ARGO/GEX che il progetto dichiarò non-backtestabile: ora accumula, reale e allineato (lo
|
||||
`spot` elimina il proxy ATM). **Copertura reale:** spot/dvol dal 2026-03-26 (sparse/daily fino a fine
|
||||
aprile), **net-GEX denso orario solo da ~2026-05-01** → ~5-6 settimane, **un singolo regime calmo**:
|
||||
analisi esplorativa OK ma NIENTE edge validabile ora (i prior GEX/VRP non si replicano su 9 sett.;
|
||||
`liquidation_risk` costante 'low'). Valore FORWARD: rivalutare quando il pannello attraversa
|
||||
gamma-flip/liquidazioni/crash con held-out. **NB look-ahead:** usare `attach_market` (merge_asof
|
||||
causale, NaN prima della copertura), MAI `astype(int64)` su un timestamp datetime (darebbe ns →
|
||||
match all'ultimo snapshot = leak). **Numeri reali misurati (ETH):** skew put 10% OTM = **×1.1** (liquido, spread ~7%, NON ×1.6
|
||||
come sembrava da un singolo snapshot a 1g, illiquido); premio reale put 10% OTM ≈ **1.0%/mese** (0.65%/sett),
|
||||
catastrofe 15%+ OTM ≈ 0.33%/mese. **Vincolo strutturale:** gli strike **10% OTM a 24h NON esistono**
|
||||
(Deribit lista solo near-ATM sui tenor corti) → un overlay **per-trade a 24h è infattibile**; l'unica
|
||||
struttura eseguibile è una **put settimanale/mensile standing** (catastrofe-cap di sleeve), da gateare
|
||||
coi premi reali. Vedi `docs/diary/2026-06-08-mr02eth-replace-search.md`.
|
||||
|
||||
**Frattali del segnale × Regime ARGO (ricerca 100 agenti, 2026-06-02, RECORD).** Cercata una strategia
|
||||
che combini un segnale frattale (Hurst/Higuchi/Williams/analog) con un gate di regime (DVOL/VRP/funding).
|
||||
Infrastruttura riusabile: `scripts/analysis/regime_fetcher.py` (DVOL+funding da Deribit mainnet →
|
||||
**`data/regime/`**, NON `data/raw/` che è solo OHLCV) e `regime_lab.py` (feature regime+frattali causali,
|
||||
cache, harness netto-OOS). Esito: **15 strategie robuste e causali, ma NESSUNA batte/migliora il PORT06**
|
||||
(diversificatori sovrapposti alle fade). **Finding: il prior ARGO "VRP>0=range=fade" è SMENTITO** —
|
||||
l'edge è su **VRP<0 + DVOL bassa**. Il vincitore `FR01_hurst_calm_fade.py` è robusto ma DILUISCE il
|
||||
PORT06 (OOS Sharpe 8.89→8.72) → **non deployato** (in `scripts/strategies/` ma NON in MODULE_MAP/yml).
|
||||
Il sottoprodotto utile è stato il **loss-guard Hurst** (vedi sopra), che invece MIGLIORA il PORT06.
|
||||
Diario `docs/diary/2026-06-02-fractal-argo-search.md`.
|
||||
|
||||
**Metodologia obbligatoria per ogni nuova strategia** (per non ripetere l'errore squeeze):
|
||||
1. Ingresso eseguibile: direzione e prezzo decisi con dati **fino a `close[i]`**, mai `close[i-1]` con direzione da `i`.
|
||||
2. Backtest **NETTO** dopo fee realistiche Deribit (**0.10% RT** taker, non 0.20%) + leva.
|
||||
3. Validazione **out-of-sample** (held-out) + robustezza su griglia parametri + sweep fee.
|
||||
4. Crea script in `scripts/strategies/`, aggiungi a `MODULE_MAP` (`strategy_loader.py`) e a `strategies.yml`.
|
||||
|
||||
Strategie scartate storiche in `scripts/waste/` (W01-W28 + la famiglia squeeze).
|
||||
|
||||
**Verso €50/giorno.** Con 4 strategie indipendenti (MR01/MR02/MR03/MR07) × 2 asset
|
||||
(BTC/ETH) su €1000 ciascuna, il PnL medio storico aggregato è ben oltre €50/giorno;
|
||||
ma quei numeri sono backtest a leva 3x su 8 anni e includono anni eccezionali (es.
|
||||
ETH 2024). Stima onesta: il target è *plausibile* su un portafoglio diversificato di
|
||||
queste fade, ma va confermato col paper trader live prima di rischiare capitale reale.
|
||||
|
||||
## Portafogli
|
||||
|
||||
- Un `Portfolio` è un oggetto di prima classe (`src/portfolio/`) con definizione (sleeve + schema pesi) e due facce sulla **STESSA** definizione: `.backtest()` (riusa il builder unico di `sleeves.py` → parità esatta con `report_families`) e live (`PortfolioRunner`: capitale pool condiviso, sizing per peso, ribilancio giornaliero, ledger aggregato in `data/portfolios/{code}/`).
|
||||
- **Schemi peso:** `equal` (default), `cap` (tetto per famiglia, es. pairs 33% — config raccomandata), `inverse_vol`, `cluster_rp` (equal fra cluster naturali poi inverse-vol intra-cluster), `manual`. Definiti in `weighting.py`; la chiave cap è la famiglia (PAIRS/FADE/HONEST/SHAPE/TSM/XSEC).
|
||||
- **Default `portfolios.yml`:** PORT06 (master+shape), `weighting=cap pairs 0.33 + shape 0.0588`, **leva 3x** (era 2x fino al 2026-06-12, vedi sotto), ribilancio 1D. Backtest PORT06 canonico (dati al 2026-05-28, pre-cap-shape): FULL Sharpe 6.47 DD 4.10% / OOS Sharpe 8.82 DD 1.30%; **con EXIT-16 close-confirm: FULL 7.84 / 2.60%, OOS 10.06 / 1.15%** (i vecchi 6.07/8.19 erano pre-loss-guard/pre-refresh dati). Col cap SHAPE (2026-06-05): FULL 6.43 / 3.96%, OOS 8.58 / 1.36% — assicurazione sulla coda SH01, vedi sotto. Col BLEND ETH/BTC 15m (2026-06-09, v1.1.16): FULL 7.20 / 3.68%, OOS 9.66 / 1.31% — 18 sleeve. Con XS01 (2026-06-09): OOS Sharpe 10.07, FULL DD 3.46 — 19 sleeve (pool live real-only 15; i 4 book multi-asset TR01/ROT02/TSM01/XS01 girano in statistica). **Con SWAP fade 1h→15m (config live attuale, v1.1.30, 2026-06-12, vedi sotto): FULL Sharpe 8.13 DD 2.47% / OOS Sharpe 10.86 DD 2.09%** (regression-lock `test_backtest_parity_cap`).
|
||||
- **LEVA 3x (v1.1.29, 2026-06-12, scelta utente).** Era 2x ("sobrio") dal primo deploy live. Frontiera ACCEL50 (`scripts/analysis/accel50_research.py`): a Sharpe OOS ~10 il collo di bottiglia è la TAGLIA, non il rischio — lev 2→3 porta gli anni-a-€50/g da 3.3 a 1.9 con FULL DD 3.5→5.2% (modello lineare). PAIRS `position_size_family` 0.20→0.13 per CONSERVARE l'esposizione validata ~0.40 (la leva accelera le famiglie con stop, non i pairs no-stop). Diario `docs/diary/2026-06-12-accel50.md`.
|
||||
- **SWAP fade 1h→15m (v1.1.30, 2026-06-12, scelta utente "swap secco").** Le 6 fade (MR01/02/07 × BTC/ETH) girano a **15m** invece di 1h: stessi `sid` (pesi/alloc/ledger intatti), `tf="15m"` in `_defs.FADE` + `combine_portfolio.FADE_TF` (le due facce backtest/live sulla stessa def). Gate `scripts/analysis/fade15m_port06_gate.py`: parametri 1h NON ri-tunati (anti-overfit), corr daily 15m↔1h media **0.26** (diversificazione vera, non lo stesso edge più veloce), edge ETH regge il flat-entry-skip (non stale-print). SWAP promosso: FULL 7.34→8.13 / OOS 10.07→10.86 (OOS DD 1.48→2.09 accettato). DIP01 resta 1h (non era nel gate). `max_bars=24` ora = 6h; EXIT-16 confirm sulla barra 15m completata. Caveat: MR02_BTC 15m è il più fee-sensitive (fee2x OOS Sh 0.60) → monitorare le fee reali. **TF sweep** (`fade_tf_sweep.py`): 1m/2m chiusi (fee-death + flat 13-26%), 5m no-swap (MR02_BTC muore a fee2x), 10m in watchlist (ADD bocciato: OOS 10.86→10.76). Diari `docs/diary/2026-06-12-fade15m-gate.md` / `-fade-tf-sweep.md`.
|
||||
- **XS01 — Cross-Sectional Reversion (ATTIVO LIVE 2026-06-09; dispersion-gate v1.1.20, 2026-06-10).**
|
||||
Famiglia nuova XSEC, distinta da pairs (pairwise) e fade (single-asset): ogni `hold=12` ore
|
||||
classifica 8 crypto (UNIVERSE8) per rendimento a `lb=48` ore e va long i perdenti relativi /
|
||||
short i vincenti (peso ∝ −(ret − media cross-section)), market-neutral gross 1, fee 0.20%
|
||||
RT/book (turnover 2). Scorrelato ~0 da pairs e fade. FULL Sharpe ~3.3, plateau lb 12-72 ×
|
||||
hold 6-24; cost-sensitive (muore a ~0.35% RT/book). Gate PORT06: OOS Sharpe 9.66→10.07,
|
||||
FULL DD 3.68→3.46. **Dispersion-gate** (`disp_min=0.0313` = p50 TRAIN): entra solo se la
|
||||
std cross-section del momentum ≥ soglia — diagnostica monotona TRAIN+OOS, plateau p30-p70,
|
||||
standalone Sharpe 2.50→3.46 (regge fee 2x), PORT06 OOS 10.07→10.37 a DD pari. Come
|
||||
trend/hurst sulle fade, il gate agisce SOLO sul path live (backtest canonico non filtrato
|
||||
→ il live farà meglio del backtest). 8 gambe → PAPER (niente esecuzione reale a €2k).
|
||||
Worker `CrossSectionalWorker` validato (`validate_xsec_worker`: replay == backtest esatto).
|
||||
**PHASE-TRANCHING (2026-06-11, `tranches=3`)**: la fase del roll non-sovrapposto è arbitraria
|
||||
e da sola muove Sharpe daily FULL 1.52-2.33 / DD 13.8-33% (timing-luck) → live gira con 3
|
||||
sub-book sfasati di hold/3 su capitale comune (PnL/K), ensemble di fase SENZA parametri
|
||||
fittati. Gate `xs01_tranche_gate.py`: plateau K=2 E K=3 promossi (PORT06 OOS Sh 10.07→10.15,
|
||||
DD 1.48→1.38, FULL pari); validatore esteso (K=1 == xsec_sim esatto; K=3 == unione fasi
|
||||
0/4/8 esatto). Solo path live (backtest canonico single-phase). Strategia
|
||||
`scripts/strategies/XS01_cross_sectional.py`, gate `scripts/analysis/xsec_port06_gate.py` /
|
||||
`xs01_dispersion_gate.py`. NB scartata nella stessa tornata: FC01 funding-carry (nessun
|
||||
edge su 6.5 anni). Diari `docs/diary/2026-06-09/10-*`.
|
||||
- **BLEND ETH/BTC 15m flat-skip (ATTIVO LIVE v1.1.16, 2026-06-09).** Sleeve `PR_ETHBTC_15M` accanto al 1h: pairs ETH/BTC a **15m** (n=66, z_in=1.674, z_exit=1.0, max_bars=35) a **mezza size** (`params.position_size=0.10`). **Origine: gioco "Blind Traders"** (100 agenti ciechi su BTC/ETH anonimizzati → riscoprono la mean-reversion, vincitore = spread ETH/BTC 15m; `scripts/games/`). Gate PORT06 sul serio: **non duplicato (corr 1h↔15m = 0.37)**, robusto (16/16 celle Sharpe>1), e l'edge **NON è artefatto delle candele flat ETH 15m** (16% storico; filtrandole resta l'83% dello Sharpe). Engine `pairs_research.pairs_sim_flat` con uscita **LIVE-REALIZABLE** (`flat_skip`: niente entry/exit su barre O=H=L=C, esce alla 1a barra pulita), **regression-lock** a `pairs_sim` con flat_skip=False. **Worker validato** (`validate_worker_pairs` a 15m: replay == backtest, 8452 vs 8453 trade; 1h byte-exact). Runner: fetch sub-orario diretto da Cerbero + `pos_for_spec` override **per-sleeve** (params.position_size > famiglia > globale). Mezza size perché a peso pieno il 15m pesava il 25.8% del rischio PORT06 (→ 11.5%, bilanciato col 1h) — blend-tilt prudente sul **caveat slippage** (a slippage realistico il vantaggio di Sharpe regge, quello di DD si assottiglia: il vero banco di prova è il ledger reale shadow). Diari `docs/diary/2026-06-09-pairs15m-*.md`, gate `scripts/analysis/pairs15m_port06_gate.py` / `pairs15m_gate_final.py` / `pairs15m_flatcheck.py`, smoke `pairs15m_live_smoke.py`.
|
||||
- **SH01 SENZA STOP-LOSS — by design, CONFERMATO da ricerca (2026-06-05).** Dopo il crash ETH (−15.6% su un trade SH01 live), ricerca multi-agente con harness dedicato `scripts/analysis/sh01_exit_lab.py` (cache segnali walk-forward, engine con **fill gap-aware** `worse(livello, open)`, parity esatta con explore_lab, protocollo train≤2023-11-01/OOS): **11 famiglie di stop testate (ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop, disaster-cap close+intrabar, swing, vol-regime), 0 sopravvissute** al gate (ETH migliorato senza degradare BTC, train E oos, plateau). Pattern: ogni stop stretto abbastanza da toccare la coda ETH rompe BTC; ogni stop largo non arriva alla coda; nei crash il fill è al gap, non al livello (lo stop "protettivo" PEGGIORA la coda OOS). Mitigazione adottata: **cap famiglia SHAPE a 0.0588 in PORT06** (≈ dimezzata; costo OOS Sharpe −0.24, FULL DD −0.14pp) — la prossima coda impatta il conto per metà. NON impostare mai `sl`/`sl_confirm_atr` su SH_BTC/SH_ETH. Direzione futura: liquidity-gate sull'entry (skip dopo feed flat). Diario `docs/diary/2026-06-05-sh01-sl-research.md`.
|
||||
- **Data layer Cerbero v2:** `get_historical_v2` unificato + `get_instruments` (naming robusto) + `get_ticker_batch`. Trading su Deribit.
|
||||
- **SCOPE LIVE (fase 2 completata):** il runner esegue TUTTI gli sleeve di PORT06. Worker: single `StrategyWorker` (fade MR01/02/07, DIP01, **e SH01**), `PairsWorker` (PR01 2 gambe), e i multi-asset dedicati `BasketTrendWorker` (TR01 4h), `RotationWorker` (ROT02 1d), `TsmomWorker` (TSM01 1d). Il runner fetcha 1h da Cerbero v2 e **resampla a 4h/1d** (lookback dimensionato sui daily: TSM01 usa 252g). Validazione: runner pool/ribilancio/ledger == backtest (`validate_portfolio_runner.py`, identico); worker multi-asset == reference (`validate_honest_workers.py`: TSM01 esatto, ROT02 +1303% canonico, TR01 +32% vs +42% — residuo di convenzione capitale-unico vs media-equity; il bug `mean(rets)` sui soli asset in posizione, che dava −44%, è FIXATO il 2026-06-11: sovrappesava N/k a paniere parziale).
|
||||
- **SH01 bootstrap full-history (punto-10, 2026-06-07).** La ri-validazione col train-window del
|
||||
regime live (`sh01_trainwindow_validate.py`) ha mostrato che SH01 a train 365g **NON è robusto**
|
||||
(BTC fee-2x FULL −42%, ETH Sharpe −0.02, trade-rate 22-26% vs 10% validato: LogReg over-confident,
|
||||
soglia inerte — la diagnosi sweep era esatta). L'edge è MONOTONO nella memoria: solo l'expanding
|
||||
full-history passa il gate. Fix live: il runner passa agli sleeve `ml` la storia FULL
|
||||
(`_with_history`: parquet locale + feed, gap-guard con WARN) e `ml_wf_entries(last_block_only=True)`
|
||||
fitta SOLO l'ultimo blocco del walk-forward → segnali **identici per costruzione** al WF completo
|
||||
(parity test esatto), 0.6 s/tick su 73k barre. Manutenzione: tenere fresco il parquet
|
||||
(`download_all()`). MAI ri-tunare la soglia nel regime corto (instabile/incoerente fra asset).
|
||||
Diario `docs/diary/2026-06-07-sh01-trainwindow.md`.
|
||||
- **FIX SH01 wiring (2026-06-01).** SH01 gira come **`StrategyWorker` NORMALE** (NON il vecchio `MLWorkerWrapper` di `multi_runner`, che usava il `SignalEngine` **squeeze SCARTATO**: apriva senza metadata ed usciva a `hold_bars=3`, ignorando del tutto SH01_shape_ml). `SH01_shape_ml.generate_signals` fa il walk-forward (retraining) internamente ad ogni tick ed emette `metadata.max_bars=12` → exit a orizzonte via `StrategyWorker.tick`. Serve ≥4000 barre 1h (`_ML_LOOKBACK_DAYS=365`). Vedi `docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md`.
|
||||
- **Altri fix StrategyWorker (2026-06-01).** Exit a orizzonte puro per strategie senza TP/SL (`elif self.max_bars`, SH01 esce a H=12 non hold_bars=3); `is_win = net > 0` (win NETTO fee, non lordo); filtro `min_tp_frac` (salta micro-scalp col TP entro le fee); loss-guard `hurst_max=0.55` sulle fade (vedi sopra).
|
||||
- **Exit intrabar (fase 3, risolto):** lo `StrategyWorker` ora esce sui TP/SL toccati INTRABAR (high/low della barra, al livello, SL prioritario) come il backtest — non più solo sul close. Allinea fade/DIP01 live al backtest intrabar (`tests/portfolio/test_intrabar_exit.py`). Caveat residuo onesto: nel paper trading l'high/low usato è quello della barra in corso al poll; su un fill reale conterebbe il momento del tocco.
|
||||
- **ESECUZIONE REALE — pairs 2 gambe + SH01 (2026-06-08).** Estesa oltre i fade: i **5 pairs PR01**
|
||||
eseguono reale a 2 gambe (`PairsExecutionClient`: open/close long A/short B, leg-risk unwind, MAI
|
||||
close_position; `pairs_enabled: true` acceso a conto flat, v1.1.12); **SH01** (BTC/ETH) esegue
|
||||
single-leg con exit a orizzonte H=12 — niente TP/SL, `_place_real_tp` no-op e `_real_close` chiude
|
||||
tutto market reduce-only, disaster-bracket on-book come unica protezione di coda (v1.1.13). Motivo:
|
||||
SH01 è il diversificatore più decorrelato (senza i 5 sleeve PAPER il DD del portafoglio sale
|
||||
3.96→5.35%). Copertura reale ora 15 sleeve su 19 (fade+DIP+**6 pairs incl. ETH/BTC 15m**+SH01);
|
||||
restano simulati TR01/ROT02/TSM01/XS01 (book multi-asset, bloccati dal capitale: rumore
|
||||
arrotondamento 20-30% a €2k, serve ~€20k). Diari `docs/diary/2026-06-08-pairs-executor.md`.
|
||||
- **ESECUZIONE REALE — shadow (v1.0.3, 2026-06-03).** I **6 fade** (MR01/MR02/MR07 × BTC/ETH) eseguono ordini **REALI su Deribit testnet** accanto al fill simulato (*shadow*: sim + reale in parallelo, il sim resta la verità che guida le decisioni). `src/live/execution.py` `ExecutionClient`: `open` (market) + `close_amount` (market **reduce-only della SOLA quota del worker** — i 3 fade BTC condividono lo strumento e le posizioni si nettano per conto, quindi NON si usa `close_position` che flatterebbe le quote altrui); **verifica l'esecuzione sul TRADE** (order_id in `get_trade_history`, non sulla size netta aggregata); **fee REALI lette dai `trades[]`**. Strumenti = **lineari USDC** (`BTC_USDC`/`ETH_USDC-PERPETUAL`, amount nel base-coin, step 0.0001/0.001): scelti perché il **payoff lineare == matematica del backtest** (l'inverse `*-PERPETUAL` introdurrebbe una base 1/prezzo) e fee/PnL sono in USDC. Lo `StrategyWorker` tiene un **ledger reale parallelo** (`real_capital`, persistito) e logga `REAL_OPEN`/`REAL_CLOSE` col confronto **slippage** (prezzo sim vs eseguito) e **fee** (assunta 0.10% vs reale). Config: `portfolios.yml` → `overrides.execution {enabled, sleeves:[MR01,MR02,MR07,DIP01], instruments:{BTC:BTC_USDC-PERPETUAL, ETH:ETH_USDC-PERPETUAL}}` (**DIP01 aggiunto il 2026-06-04**: stesso wiring single-leg, TP resting incluso); pairs/rotation/tsmom/shape restano **simulati** (pairs richiede un executor a 2 gambe con gestione leg-risk; shape non ha TP). **Fee reali misurate = 0.05%/lato = 0.10% RT** (== assunto del backtest, su ETH; BTC inverse era ~0.094%). **Alert Telegram:** `REAL_EXEC_LIVE` (primo ordine reale verificato per worker) + `REAL_OPEN_FAIL`. Smoke (testnet, €0): `scripts/analysis/live_exec_smoke.py` (layer: ordine→verifica→fee) e `live_shadow_smoke.py` (catena worker open/close). **Capitale live portato a 2000** (notional fade ~$35) per ridurre il rumore di arrotondamento su BTC (step lineare ~$6.7). NB: ledger reale ≠ ledger sim — i worker già in posizione sim a un restart non hanno quota reale corrispondente; lo shadow reale parte pulito dalla prossima apertura.
|
||||
- **Disaster-bracket on-book + alert outage (v1.1.4, 2026-06-07).** A ogni `REAL_OPEN` dei fade
|
||||
eseguiti il worker piazza uno **STOP_MARKET reduce-only a ~−30%** dall'ingresso (trigger sul mark,
|
||||
`ExecutionClient.place_disaster_sl`, cancellato in `_real_close`): assicurazione per gli outage
|
||||
(poll-loop fermo = exit non valutati), in operatività normale non scatta mai → 0 costo Sharpe.
|
||||
Config `overrides.execution.disaster_sl_pct` (0.30; 0=off). NB: il `set_stop_loss` di cerbero-mcp
|
||||
è un `private/edit` (solo ordini APERTI) → inutilizzabile su market fillati; la cancel di un
|
||||
trigger order risponde `untriggered` (= successo, verificato testnet). Alert Telegram `FEED_OUTAGE`
|
||||
dopo 5 poll falliti consecutivi (con elenco posizioni reali aperte) + notifica di ripresa. I fix
|
||||
di parità della stessa tornata (v1.1.3): TR01 fee×leva + forming-bar TR01/Pairs + WARN
|
||||
`PANEL_SHORT` su TSM01/ROT02; `hourly_report` ora mostra i multi-asset (sezione MULTI-ASSET).
|
||||
Diario `docs/diary/2026-06-07-sweep-fixes.md`.
|
||||
- **VERITÀ CONTABILE su netting di conto (v1.1.24, 2026-06-11, da audit live).** Il modello
|
||||
"quote per-worker con reduce-only" si rompe quando worker dello stesso strumento hanno
|
||||
direzioni OPPOSTE (pairs long ETH vs fade short ETH): Deribit cappa/respinge i reduce-only
|
||||
in silenzio. L'audit ha trovato: close fillato 0.078 ma bookato 0.105 (`Fill.amount` era il
|
||||
RICHIESTO), 3 gambe pairs mai eseguite col PnL sim sommato al ledger reale (gamba orfana sul
|
||||
conto, ETH/SOL di fatto short nudo), e il conto short 0.027 ETH più dei libri (riallineato a
|
||||
mano). Fix: (1) `Fill.filled_amount` (fonte `order.filled_amount`) usato da TUTTI i ledger;
|
||||
(2) `REAL_CLOSE_PARTIAL` (log+alert) quando il close filla meno del residuo; (3) pairs:
|
||||
PnL bookato SOLO per le gambe verificate, gamba respinta → `orphan_legs` persistito +
|
||||
alert `PAIR_LEG_ORPHAN`, `applied` solo con ENTRAMBE le gambe (altrimenti sim_fallback
|
||||
dichiarato); (4) `REAL_DIVERGENCE` anche su jsonl (prima solo Telegram); (5) runner:
|
||||
tick isolato per-worker (un'eccezione non salta gli altri; alert `WORKER_ERROR_STREAK` a 5).
|
||||
**RISOLTO in v1.1.25 col NETTING delle chiusure**: `close_amount` tenta il reduce-only
|
||||
(sicurezza storica: un bug di stato filla 0) e riesegue il RESIDUO cappato/respinto in
|
||||
**market puro** — che muove il conto esattamente del delta del libro, cioè netta contro le
|
||||
quote opposte → niente più gambe orfane/close cappati per costruzione (copre anche i pairs
|
||||
via `close_pair`). Il chiamante riceve UN Fill combinato (prezzo medio pesato, fee sommate,
|
||||
`notes` con 'netting'); evento `NET_CLOSE` (log+Telegram) a ogni fallback. La sicurezza
|
||||
persa sul residuo è coperta dal **reconciler orario** (`reconcile_account.py`, cron host
|
||||
:40, alert `ACCOUNT_DRIFT`): conto vs Σ libri+orfani, tolleranza 1.5×step, anti-race.
|
||||
`orphan_legs`/`REAL_CLOSE_PARTIAL` restano come ultima difesa (se fallisce anche il market
|
||||
puro). Test `tests/portfolio/test_netting_close.py`.
|
||||
- **TP_PHANTOM — il tocco TP va confermato dal book reale (v1.1.23, 2026-06-11).** Il feed
|
||||
testnet stampa wick anomali che (a) generano segnali fade su ETH e (b) "toccano" il TP
|
||||
intrabar della stessa barra: il sim bookava +4% fantasma a bars_held=0 e `_real_close`
|
||||
chiudeva A MERCATO una posizione il cui resting TP non aveva mai fillato (−fee/spread a
|
||||
giro, 14 giri l'11-06, report Telegram 26/0 vs reale 11/15 — fix conteggio in v1.1.22).
|
||||
Gate in `StrategyWorker._tp_phantom` (zero parametri, verita' d'esecuzione, NON un filtro
|
||||
di strategia): tocco sim + **resting LIMIT a zero fill** + prezzo corrente che non ha
|
||||
raggiunto il livello → exit SOPPRESSA (il limit sul book reale e' l'oracolo: se il prezzo
|
||||
avesse scambiato li', avrebbe fillato); SL close-confirm e max_bars restano attivi.
|
||||
Fill (anche parziale) o prezzo oltre il livello o worker non eseguito → comportamento
|
||||
storico. Fail-open su errori di rete. Log `TP_PHANTOM` dedup per barra + alert Telegram.
|
||||
Test `tests/portfolio/test_tp_phantom.py`.
|
||||
- **INVERTED_TP_SKIP — l'entry con TP dal lato sbagliato va soppressa (v1.1.33, 2026-06-16).**
|
||||
Un wick transitorio sul feed puo' far calcolare alla strategia un `tp` dal lato SBAGLIATO
|
||||
dell'entry (es. donchian: segnale su barra wickata in basso → tp=centro canale, entry al
|
||||
prezzo RECUPERATO sopra il proprio tp). L'exit intrabar `bar_high>=tp` (long)/`bar_low<=tp`
|
||||
(short) scatta a `bars_held=0` in PERDITA, con churn di fee e TP reduce-only respinti
|
||||
(16-06: 8 giri MR02_BTC 15m, sim −17.9 / reale −2.3 grazie al real-truth; TP_PHANTOM NON
|
||||
lo prende — niente resting oracle, il prezzo HA superato il livello). Gate in
|
||||
`StrategyWorker._open_position` (zero parametri, verita' d'esecuzione): se `tp<=entry`
|
||||
(long) / `tp>=entry` (short) → **entry soppressa** (niente ordine reale/disaster-SL), log
|
||||
`INVERTED_TP_SKIP` dedup per-barra + alert Telegram. Solo path live (il backtest entra al
|
||||
close del bar del segnale → mai invertito, resta non filtrato → live meglio del backtest).
|
||||
Cerotto testnet (il fix vero e' mainnet). Test `tests/portfolio/test_inverted_tp.py`,
|
||||
diario `docs/diary/2026-06-16-inverted-tp-guard.md`.
|
||||
- **TP reale = LIMIT reduce-only AL LIVELLO (2026-06-04).** Misurati +235 bps di slippage medio sulle uscite take-profit market-on-poll (sim esce al livello intrabar, il reale chiudeva al poll post-rimbalzo: sim +11.85 vs reale +0.62 USD sui primi 7 close). Fix: a ogni `REAL_OPEN` il worker piazza un **limit reduce-only al TP** (`ExecutionClient.place_tp_limit`, prezzo quantizzato al tick, SOLA quota del worker) → `REAL_TP_RESTING`; a ogni chiusura sim `_real_close` **cancella il resting → riconcilia i fill (anche parziali) via `get_trade_history` per order_id → market reduce-only solo del residuo** → ledger su prezzo combinato. `real_tp_order_id` persistito in `status.json` (resume-safe). Lo **SL resta market-on-poll** (deliberato: i trigger Deribit generano un nuovo order_id al trigger → fill non verificabile per order_id; e sul SL il rimbalzo lavora a favore). Fill da resting = fee **maker ~0%**. Smoke: `live_shadow_smoke.py` (2 scenari, testnet). Diario `docs/diary/2026-06-04-shadow-divergence.md`.
|
||||
- **position_size per-famiglia (2026-06-07).** `portfolios.yml` accetta `position_size_family`
|
||||
(chiave = `weighting.family_of`); plumbing `runner.pos_for_spec`. **PAIRS a 0.20** (esposizione
|
||||
0.40 ≈ il validato 0.45): la famiglia è senza stop e col globale 0.5×lev2 girava a ~2.2x il
|
||||
validato (ETH/BTC DD grezzo 78% a quella taglia). PORT06 OOS DD 3.40→1.26% al costo di OOS
|
||||
Sharpe 9.05→8.43 — assicurazione come il cap SHAPE. Gate `pairspos_port06_impact.py`,
|
||||
diario `docs/diary/2026-06-07-pairspos-gate.md`. NB (2026-06-11): il **disaster-cap sullo
|
||||
z** (exit se |z|≥z_stop, griglia pre-registrata 3.0-5.0 su tutte le coppie) è **BOCCIATO**
|
||||
— coda e Sharpe OOS peggiorano ovunque (lo stop realizza la perdita al massimo overshoot e
|
||||
l'engine rientra nello spread divergente: churn di fee), **5ª conferma** che gli stop su
|
||||
mean-reversion sono falsi negativi. Record `scripts/analysis/pairs_zstop_research.py`,
|
||||
diario `docs/diary/2026-06-11-stability-sweep.md`.
|
||||
- **REAL-TRUTH ledger (2026-06-10, scelta utente).** Con `overrides.execution.real_truth: true`
|
||||
(ATTIVO) il `capital` dei worker eseguiti si aggiorna col **PnL dei FILL REALI** (fee reali
|
||||
incluse) invece del PnL sim: `_real_close`/`_real_close_pair` ritornano `(real_pnl, applied)` e
|
||||
`_close_position`/`_close` applicano il reale al ledger; il sim resta SOLO diagnostica nel log
|
||||
CLOSE (`pnl_source`/`sim_pnl`/`real_pnl`). Fallback al sim dichiarato (`pnl_source=sim_fallback`)
|
||||
solo se il trade reale non è mai esistito/fillato (REAL_OPEN_FAIL/leg-fail). Equity → pesi →
|
||||
allocazioni → notional derivano così dai soldi veri sul conto (il notional reale era GIÀ la
|
||||
formula sim `capital·ps·lev`; il gap storico sim/reale era contabile: ledger separati + spike
|
||||
print delle candele testnet che il sim bookava e il reale no). Le DECISIONI (entry/exit) restano
|
||||
guidate dal feed; i multi-asset (TR01/ROT02/TSM01/XS01) restano sim per costruzione. Test:
|
||||
`tests/portfolio/test_real_truth.py`. Diario `docs/diary/2026-06-10-real-truth-ledger.md`.
|
||||
- **RIBILANCIO CONSERVA L'EQUITY (fix v1.1.31, 2026-06-13).** Il ribilancio giornaliero
|
||||
gonfiava l'equity quando un worker era in posizione: i flat si dividevano l'INTERO `total`
|
||||
(che includeva il capitale degli in-position) e gli in-position lo trattenevano in più →
|
||||
doppio conteggio di Σ(capital−alloc) sugli in-pos. Caso reale: MR02_BTC 15m seedato a 181.19
|
||||
(eredità INIT_LINEAGE) e in posizione al ribilancio delle 00:01 → **+4.77 di equity dal nulla**
|
||||
(scoperto perché l'equity saliva senza un trade dietro). Fix: `ledger.allocate(weights, reserved={sid:cap})`
|
||||
— gli in-position TRATTENGONO il loro capitale (deployato, non spostabile), i flat si dividono
|
||||
`total − Σreserved` per peso RINORMALIZZATO → `Σalloc == total` sempre, equity conservata per
|
||||
costruzione. `runner.rebalance_allocations` passa i `reserved` dai worker `in_position`. Parità
|
||||
runner intatta (5.8e-08). Test `test_ledger.py`/`test_runner_rebalance.py`. Diario
|
||||
`docs/diary/2026-06-13-rebalance-conservation.md`.
|
||||
- **INIT_LINEAGE — eredità capitale al cambio timeframe (2026-06-12).** Un worker al primo avvio
|
||||
(niente status.json) eredita `capital`/`real_capital` dal worker più recente di STESSA
|
||||
strategia+asset su altro tf (glob `{strategy}__{asset}__*`), MAI la posizione. Nato dallo swap
|
||||
fade 1h→15m: i worker nuovi partivano dall'allocazione del pool scartando il PnL del gemello
|
||||
(−16.8 di equity, riallineata a mano col seed la prima volta). `StrategyWorker._inherit_lineage_capital`,
|
||||
evento `INIT_LINEAGE`. Test `tests/portfolio/test_capital_lineage.py`.
|
||||
- **STALE_REAL_POSITION — guard anti-orfano nel reconciler (v1.1.31, 2026-06-13).** Lo swap
|
||||
1h→15m aveva ritirato MR02_BTC 1h dal config mentre teneva uno short REALE → posizione nuda non
|
||||
gestita per 13h, e il reconciler NON la vedeva (lo status fermo del worker morto contava ancora
|
||||
come "libro" → conto == libri). `reconcile_account.compute_stale_real_positions(max_age_min=15)`:
|
||||
un worker con `real_in_position` e status.json fermo da >15 min = non gestito → alert
|
||||
`STALE_REAL_POSITION`. Discriminante = STALENESS (un worker vivo riscrive ogni poll): cattura
|
||||
ritirati-da-swap, crashati, rimossi dal config. Orfano chiuso a mano (testnet, +0.85). Resta da
|
||||
fare la PREVENZIONE lato runner (flattare/consegnare la posizione del ritirato al boot). Diario
|
||||
`docs/diary/2026-06-13-orphan-swap-guard.md`.
|
||||
- **Limite noto:** al ribilancio le posizioni APERTE restano sul loro notional (non travasate); fedele al backtest daily-rebalanced entro il turnover infragiornaliero.
|
||||
|
||||
## Multi-Strategy Paper Trader
|
||||
|
||||
Orchestratore che esegue N strategie in parallelo su dati live Cerbero, ognuna con €1000 USDC virtuali indipendenti.
|
||||
|
||||
**Config:** `strategies.yml` — due sezioni: `strategies` (single-leg: fade/honest) e
|
||||
`pairs` (a 2 gambe). Attive: 6 fade (MR01/MR02/MR07 × BTC/ETH) + 5 coppie PR01.
|
||||
**Due worker:** `strategy_worker.py` (single-leg) e `pairs_worker.py` (2 gambe,
|
||||
long A / short B sullo z-score del log-ratio, fee su 2 gambe).
|
||||
**Persistenza:** `data/paper_trades/{worker_id}/` con `trades.jsonl` (append-only) + `status.json` (resume al restart).
|
||||
**Hot-add:** aggiungi riga YAML → `docker compose restart` → storico intatto.
|
||||
**Exit strategia:** se un `Signal` porta `tp`/`sl`/`max_bars` in `metadata` (come le fade), il worker esce su take-profit/stop-loss/time-limit; i pairs escono su |z|≤z_exit o max_bars.
|
||||
**Naming Deribit (feed live):** major = `<COIN>-PERPETUAL` (inverse); alt = `<COIN>_USDC-PERPETUAL` (lineari USDC). Vedi INSTRUMENT_MAP in `multi_runner.py`.
|
||||
**Notifiche:** Telegram per ogni trade (richiede `.env` con `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID`).
|
||||
- **Feed contaminato → libreria fasulla (v2.0.0).** Print fantasma testnet + Binance/USDT hanno
|
||||
prodotto edge inesistenti (+201%/+1238%/+16492% "OOS"). Tutti spariti sul feed reale. Lezione: il
|
||||
dato viene prima della strategia; certificare sempre.
|
||||
- **Look-ahead squeeze (storico).** L'intera famiglia squeeze-breakout aveva accuratezze 76-82% che
|
||||
erano artefatto: decideva la direzione con la candela di breakout `i` ma entrava a `close[i-1]`.
|
||||
Con ingresso onesto: lancio di moneta. (Dettagli nei diari in `Old/`.)
|
||||
- **Entry sugli estremi di candela.** Strategie che entrano a `close` quando `close` è all'estremo
|
||||
del range (≤0.1% o ≥99.9%) gonfiano i ritorni in modo irrealistico (ETH 2024: +30.848% → +2.725%
|
||||
rimuovendoli). Spesso è un artefatto di dato o di entry non eseguibile.
|
||||
- **Mean-reversion vs breakout.** Sui dati storici l'unica direzione che mostrava edge era la
|
||||
mean-reversion (i breakout rientrano) — MA anche quegli edge erano per lo più artefatto del feed:
|
||||
da riverificare da zero su dati certi.
|
||||
- **Fee** = vincolo di prim'ordine. 0.10% RT baseline. Molte operazioni = morte per fee.
|
||||
- **Leva**: testare 3x; 5x raddoppia il drawdown. I numeri a leva alta NON sono il caso base.
|
||||
- **Data leakage** con rendimenti log: `returns[k] = log(close[k+1]/close[k])` usa `close[k+1]`. I
|
||||
feature devono fermarsi a `returns[i-2]` se il prezzo corrente è `close[i-1]`. Verificare SEMPRE.
|
||||
|
||||
## Convenzioni
|
||||
|
||||
- Strategie in `scripts/strategies/` con codice univoco (MR01, ...).
|
||||
- Script scartati in `scripts/waste/` (W01-W28 + famiglia squeeze).
|
||||
- Diario in `docs/diary/YYYY-MM-DD.md`. Aggiornare dopo ogni esperimento significativo.
|
||||
- Nessun dato sensibile nei commit (token, chiavi API). Usare `.gitignore`.
|
||||
- Verificare sempre assenza di data leakage prima di fidarsi dei risultati. In particolare: `returns[i-w : i]` include `close[i]` che è un candle nel futuro — usare `returns[i-w : i-1]`.
|
||||
- Strategie in `scripts/strategies/` con codice univoco; scartate documentate nel diario.
|
||||
- Diario in `docs/diary/YYYY-MM-DD.md`, aggiornato dopo ogni esperimento significativo.
|
||||
- **Nessun segreto nei commit** (token/chiavi). `.env` e `.env.mainnet` sono gitignored.
|
||||
- Versionamento: `VERSION` (semver) + `scripts/bump_version.py`. `src/version.py` lo legge.
|
||||
|
||||
## Attenzione
|
||||
## Archivio `Old/`
|
||||
|
||||
- **Data leakage:** è stata trovata e corretta nello script 05. Ogni volta che si usano rendimenti logaritmici (`np.diff(np.log(close))`), ricordare che `returns[k]` usa `close[k+1]`. I feature devono fermarsi a `returns[i-2]` se il prezzo corrente è `close[i-1]`.
|
||||
- **Fee:** Deribit perp reale = taker ~0.05%/lato (**0.10% round-trip**), maker ~0%. Usare 0.10% RT come baseline (lo 0.20% storico era pessimista 2x). Includere SEMPRE nel backtest: sono vincolo di prim'ordine, molte operazioni = morte per fee. Il worker usa `strategy.fee_rt` (MR01 = 0.001).
|
||||
- **Leva:** testato con 3x. Aumentare a 5x migliora i rendimenti ma raddoppia il drawdown.
|
||||
- **GBM:** GradientBoostingClassifier di scikit-learn. Ensemble di alberi decisionali sequenziali. Walk-forward per evitare leakage temporale.
|
||||
- **Report "stato trades" — separare SEMPRE pool reale e paper-stats.** La directory è la
|
||||
fonte di verità: `data/portfolio_paper/` = sleeve nel POOL (capitale reale, muovono
|
||||
equity/conto); `data/portfolio_paper_stats/` = TR01/ROT02/TSM01/**XS01** in SOLA statistica
|
||||
(PnL a precisione frazionaria, MAI nel ledger). **NON** fare glob su `portfolio_paper*`
|
||||
(matcha entrambi → gli utili sim dei paper-stats inquinano il realizzato: errore reale del
|
||||
2026-06-17, +8.15 XS01 attribuiti per sbaglio all'equity). Verità aggregata = ledger PORT06
|
||||
(`equity`, `total_capital`; unrealized = equity−total_capital). Usare
|
||||
`scripts/analysis/trades_status.py` (riconcilia: realizzato POOL + Δunrealized ≈ Δequity).
|
||||
- **Cerbero `get_historical` (fix 2026-05-28):** `end_date` come data nuda è inclusivo dell'intera giornata fino all'ultima candela chiusa (es. `end=oggi` arriva fino ad ora, non più a mezzanotte); accettati anche timestamp con orario (`...T14:00:00`, naive=UTC); nessun cap a ~5000 righe (paginazione interna). Il client passa già `end=oggi`, ora corretto. Prima del fix il paper trader restava a zero trade perché il feed era fermo a mezzanotte.
|
||||
- **Dati ETH Deribit 15m:** 14-30%/anno di candele *flat* (O=H=L=C, volume 0, run fino a ~54h) per bassa liquidità del perpetuo. Verificato (2026-05-28): escluderle NON cambia i backtest (Δacc ≤0.5pp) → edge robusto. Resta un caveat operativo (slippage/fill in trading reale, irrilevante per paper). BTC pulito eccetto picco ~8% nel 2024.
|
||||
- **FEED TESTNET INAFFIDABILE — `ETH-PERPETUAL` inverse CONGELATO (2026-06-13).** Il feed di
|
||||
DECISIONE che il runner usa per BTC/ETH (inverse perp, `INSTRUMENT_MAP`) può congelarsi sul
|
||||
testnet: il 13-06 `ETH-PERPETUAL` era fermo a **1661.95 per 12h+** (1 solo valore) mentre il
|
||||
reale (`ETH_USDC-PERPETUAL`, lineare, dove si ESEGUE) si muoveva (gap −1.3%); BTC era vivo.
|
||||
Effetto: tutte le decisioni ETH (SH01_ETH, 3 fade ETH 15m, gambe ETH dei pairs) girano su un
|
||||
prezzo morto → **spiega lo 0-trade delle fade ETH 15m** (feed piatto = niente rottura banda).
|
||||
È un GUASTO testnet, non di strategia: su **mainnet** l'arbitraggio tiene inverse ≈ lineare ≈
|
||||
realtà. Fix testnet possibile (reroute decisione ETH a USDC lineare in `INSTRUMENT_MAP`, con
|
||||
caveat bootstrap SH01) NON applicato — il fix vero è andare a mainnet. Nota: la dashboard mostra
|
||||
entry/mark/PnL **REALI** (non il sim-feed dislocato); il sim resta solo nel modal diagnostico.
|
||||
**Update 2026-06-14: ANCORA congelato a 1661.95 (36h+).** Confermato anche un vincolo strutturale
|
||||
del feed: **Cerbero v2 (`get_historical_v2`) serve SOLO 5m/15m/1h** — 30m/10m danno HTTP 400 in
|
||||
ogni formato (l'endpoint legacy `get_historical` è 404, rimosso). La voce `"30m"` in
|
||||
`runner._SUBHOURLY` era **speculativa, mai testata live** → qualsiasi TF sub-orario oltre 5m/15m
|
||||
va DERIVATO resamplando nel runner (come 4h/1d dal 1h), non fetchato diretto.
|
||||
- **GATE FEED CONGELATO — sleeve ETH-leg sospesi (v1.1.32, 2026-06-15, scelta utente "congela
|
||||
gli sleeve ETH-leg finché il feed non si sblocca").** Audit "stato ordini": il feed ETH congelato
|
||||
stava generando **perdite REALI** (SH01_ETH −2.83 reali vs −0.09 sim su un close, poi riapertura
|
||||
della stessa trappola; 4 pairs con gamba ETH entrati su z-score spuri −3/−5/+5.6 = artefatto del
|
||||
log-ratio con ETH pinnato). Fix: `runner._frozen_assets` + `_feed_gated_sids` — quando il feed di
|
||||
decisione 1h di un asset è CONGELATO, gli sleeve CONCENTRATI (single/ml/pairs) che ne dipendono
|
||||
**saltano il tick** (entry E exit) finché non si sblocca (come un outage; disaster-SL on-book =
|
||||
protezione di coda). **Auto-guarente**: si rilascia alla prima barra COMPLETA non-flat (NON è
|
||||
l'entry-guard post-flat bocciata: quella salta la candela-gap di ripresa, questo no). **Detector
|
||||
guasto-vs-illiquido**: conta la run di close **INVARIATE** (prezzo mai cambiato), NON le barre flat
|
||||
— sul feed reale ETH/BNB/DOGE hanno run 40-64 con 1-4 valori distinti/48h (MORTI) mentre
|
||||
SOL/LTC/ADA sono flat ma VIVI (run ≤12, 5-31 valori/48h: si muovono ogni ~10 barre). Soglia
|
||||
`feed_freeze_gate_bars=24` (1 giorno di prezzo immobile, configurabile, 0=off): gatea le **9 gambe
|
||||
ETH esatte** (3 fade ETH 15m + SH_ETH + 5 pairs con gamba ETH) lasciando attivi BTC-only,
|
||||
**PR_BTCLTC** (BTC vivo + LTC solo illiquido) e i multi-asset PAPER. Le posizioni ETH già aperte
|
||||
NON vengono flattate (freeze=pausa: un close forzato passerebbe per lo stesso feed morto). Alert
|
||||
Telegram `FEED_FROZEN_GATE` GATED/RIPRESO. Test `tests/portfolio/test_freeze_gate.py`, diario
|
||||
`docs/diary/2026-06-15-frozen-feed-gate.md`. **Resta un cerotto testnet: il fix vero è mainnet.**
|
||||
- **TIMING SWEEP pairs & honest (2026-06-14, NESSUN deploy).** Domanda utente: pairs/honest
|
||||
beneficiano di TF più veloci come le fade (swap 1h→15m)? Esito: **no, niente deployato.** Vincolo
|
||||
dati (alt 1h-only) → sweepabili solo **pairs ETH/BTC** e **DIP01 (BTC)**; TR01/ROT02 fuori scope
|
||||
(multi-asset alt + lente). Il gate full+OOS migliorava PORT06 (+10m/30m/5m su tutte le metriche),
|
||||
MA due muri: (a) **30m/10m feed-blocked** (v2 serve solo 5/15m/1h, vedi sopra); (b) **5m** è
|
||||
nativo ma nel regime ATTUALE è il PEGGIORE (−10.5% 30g, DD 19.5%/180g vs 3% del 1h, Sharpe sotto
|
||||
15m/1h) + flat-share ETH 29% (slippage reale) → guadagno backtest modesto (FULL Sh +0.39) non vale
|
||||
il rischio su soldi reali. **Lezione: il gate PORT06 full+OOS è necessario ma NON sufficiente** —
|
||||
incrociare con (1) la fattibilità del FEED live e (2) il regime RECENTE (un edge full-history può
|
||||
essere un relitto di volatilità passata: "più veloce = più Sharpe storico" si rovescia in "più
|
||||
veloce = più DD nel regime calmo attuale"). Artefatti: `scripts/analysis/timing_sweep_pairs_honest.py`,
|
||||
diario `docs/diary/2026-06-14-timing-sweep-pairs-honest.md`. (Config live invariata: 19 sleeve.)
|
||||
- **MICRO-TEST MAINNET = il gate per scalare il capitale (piano pronto, 2026-06-13).** Testnet
|
||||
valida solo la MECCANICA (feed/fill farlocchi); l'edge sopravvive ai fill veri? si risponde solo
|
||||
con poco denaro reale su mainnet. **Switch già eseguibile via `.env`**: `CerberoClient` legge il
|
||||
token da env (`CERBERO_TOKEN`, default testnet invariato; `is_mainnet()` helper) → puntare a
|
||||
mainnet = solo `.env`, niente codice. Piano completo (fasi smoke→fade-only €1000 2-4 sett→verdetto
|
||||
ledger-vs-backtest→espansione; sizing: fade €1000 = arrotondamento 2.6% BTC, pairs esclusi al 30%):
|
||||
`docs/specs/mainnet-microtest-plan.md`. **Stato 2026-06-14:** token mainnet (`MAINNET_TOKEN` di
|
||||
cerbero-mcp, che ha già le chiavi Deribit LIVE) **wired e VERIFICATO** — `is_mainnet()=True`, auth
|
||||
OK su `get_account_summary` read-only. Tenuto in **`.env.mainnet`** dedicato (chmod 600, gitignored),
|
||||
FUORI dal `.env` condiviso → il runner testnet NON flippa a mainnet a un riavvio accidentale (il
|
||||
micro-test girerà come **servizio separato** con dir dati pulita + portfolios fade-only, per non
|
||||
mescolare i ledger). NB architettura token: il `MAINNET_TOKEN` è la chiave d'accesso *all'MCP* +
|
||||
selettore d'ambiente (non una chiave Deribit) → il nostro client DEVE presentarlo per essere
|
||||
instradato al live. **UNICO BLOCCO: conto Deribit mainnet VUOTO (€0 USDC/BTC/ETH) → serve deposito
|
||||
~€1000 USDC** prima di Fase 0 smoke. Minimo assoluto €500 (rumore BTC ~5%), raccomandato €1000 (2.6%).
|
||||
Tutto il lavoro pre-reset (preservato in git per consultazione storica): strategie
|
||||
(`Old/scripts/strategies`), stack live e portafogli (`Old/src/live`, `Old/src/portfolio`,
|
||||
`Old/scripts/portfolios`), ricerca/gate (`Old/scripts/analysis`), dati non certificati
|
||||
(`Old/data`), 60+ diari (`Old/docs/diary`), test (`Old/tests`). Consultabile come riferimento
|
||||
("come facevamo X"), ma **nessun edge lì dentro è fidato** finché non è ri-validato su dati certi.
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user