feat(live): INIT_LINEAGE — eredita' capitale al cambio timeframe + sweep migliorie serale
Worker nuovo (no status.json) eredita capital/real_capital dal gemello piu' recente stessa strategia+asset (mai la posizione): niente piu' seed manuale al prossimo swap. 3 test nuovi, suite 129 passed. Ricerca: gate 10m ADD bocciato (OOS Sh 10.86->10.76, watchlist chiusa); XSEC breadth 8->14 Deribit BOCCIATO (gambe flat 91-99% corrompono il ranking) ma promettente su dati HL reali (-6%->+9% stessa finestra) -> sblocco = routing dati Hyperliquid quando la storia sara' sufficiente. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
# 2026-06-12 — Sweep migliorie/strategie nuove (sera, post-swap 15m)
|
||||||
|
|
||||||
|
Richiesta: cercare altre migliorie da implementare o strategie nuove. Tre
|
||||||
|
esperimenti + una miglioria di codice.
|
||||||
|
|
||||||
|
## 1. INIT_LINEAGE — eredità capitale al cambio timeframe (IMPLEMENTATO)
|
||||||
|
|
||||||
|
`StrategyWorker._inherit_lineage_capital`: al primo avvio (niente status.json)
|
||||||
|
il worker eredita `capital`/`real_capital` dal worker più recente di stessa
|
||||||
|
strategia+asset su altro tf (glob `{strategy}__{asset}__*`). MAI la posizione.
|
||||||
|
Nato dallo swap 1h→15m di oggi: i worker nuovi partivano dall'allocazione del
|
||||||
|
pool scartando il PnL del gemello (−16.8 di equity fantasma, riallineata a mano
|
||||||
|
col seed). Il prossimo swap non avrà bisogno di seed manuale.
|
||||||
|
Test: `tests/portfolio/test_capital_lineage.py` (eredita / no-sibling / resume).
|
||||||
|
|
||||||
|
## 2. Gate 10m ADD selettivo (MR01/MR07) — BOCCIATO, watchlist chiusa
|
||||||
|
|
||||||
|
Baseline aggiornata al PORT06 post-swap (fade 15m): ADD di 4 sleeve 10m
|
||||||
|
(MR01/MR07 × BTC/ETH, MR02 escluso perché fee-fragile) dà FULL Sh 8.13→8.33 e
|
||||||
|
DD 2.47→2.28, ma **OOS Sharpe 10.86→10.76** → fallisce il criterio standard.
|
||||||
|
Il 15m cattura già quasi tutto l'alpha veloce (corr 10m↔15m 0.53). Chiuso.
|
||||||
|
|
||||||
|
## 3. XSEC breadth (universo 8 → 14/15) — direzione GIUSTA, venue SBAGLIATO
|
||||||
|
|
||||||
|
La breadth è la leva classica delle strategie cross-sectional. Due banchi:
|
||||||
|
|
||||||
|
- **Hyperliquid 15 coin** (dati REALI, ma profondità v2 limitata a ~207 giorni,
|
||||||
|
regime calmo recente): CORE-8 **−6.2%** (Sh −1.38, coerente col
|
||||||
|
dispersion-gate live che tiene XS01 fuori in questo regime) vs FULL-15
|
||||||
|
**+9.4%** (Sh 1.58, WR 41→51%). La breadth trasforma un book perdente in
|
||||||
|
vincente — **sui prezzi veri**.
|
||||||
|
- **Deribit 14 coin** (storia piena 2022-10→2026-06, +AVAX/DOT/TRX/LINK/BCH/UNI,
|
||||||
|
parity del sim verificata ESATTA vs `xsec_sim`): FULL Sh 1.48→**1.22**, OOS
|
||||||
|
4.66→**3.41**, fee 2x da −0.33 a **−1.45**. PEGGIO dell'8: le 6 gambe nuove
|
||||||
|
hanno chiusure flat al **91-99%** e il loro "momentum" è rumore stale che
|
||||||
|
corrompe il ranking cross-section (3ª conferma della lezione del giorno:
|
||||||
|
pairs-alt, XEX/DOGE, ora XSEC).
|
||||||
|
|
||||||
|
**Conclusione:** l'espansione dell'universo XS01 è promettente ma bloccata dalla
|
||||||
|
qualità dati del venue. Sblocco strategico: **routing dati Hyperliquid** nel
|
||||||
|
runner (il client v2 già supporta `exchange="hyperliquid"`) + accumulo storia
|
||||||
|
HL in avanti. Rivalutare quando HL avrà ≥12-18 mesi di storia utile.
|
||||||
|
|
||||||
|
## Direzioni aperte residue (non attaccate oggi)
|
||||||
|
|
||||||
|
- **Put settimanale standing** (catastrofe-cap): unica struttura opzioni
|
||||||
|
eseguibile, da gateare coi premi reali cerbero-bite (~1%/mese il 10% OTM).
|
||||||
|
Harness `option_overlay_lab.py` pronto.
|
||||||
|
- **Hyperliquid come venue di esecuzione** (oltre che dati): aprirebbe fades su
|
||||||
|
alt liquidi con fill realistici; lavoro infrastrutturale grosso.
|
||||||
|
- 10m fade: chiuso oggi. 1m/2m/5m: chiusi oggi. Pairs nuove/PAXG/XEX: chiusi oggi.
|
||||||
@@ -114,10 +114,40 @@ class StrategyWorker:
|
|||||||
self._load_state()
|
self._load_state()
|
||||||
self._save_state()
|
self._save_state()
|
||||||
|
|
||||||
|
def _inherit_lineage_capital(self):
|
||||||
|
"""Al primo avvio (nessun status.json) eredita capital/real_capital dal
|
||||||
|
worker piu' recente di STESSA strategia+asset su altro timeframe (lineage).
|
||||||
|
Nato dallo swap fade 1h->15m (2026-06-12): i worker nuovi partivano
|
||||||
|
dall'allocazione del pool scartando il PnL accumulato dal gemello 1h
|
||||||
|
(-16.8 di equity fantasma, riallineata a mano). Eredita SOLO i ledger,
|
||||||
|
MAI la posizione (quella appartiene al worker vecchio)."""
|
||||||
|
try:
|
||||||
|
stem = f"{self.strategy.name}__{self.asset}__"
|
||||||
|
siblings = [d for d in self.work_dir.parent.glob(f"{stem}*")
|
||||||
|
if d.is_dir() and d.name != self.worker_id
|
||||||
|
and (d / "status.json").exists()]
|
||||||
|
if not siblings:
|
||||||
|
return
|
||||||
|
latest = max(siblings, key=lambda d: (d / "status.json").stat().st_mtime)
|
||||||
|
with open(latest / "status.json") as f:
|
||||||
|
old = json.load(f)
|
||||||
|
if old.get("capital") is not None:
|
||||||
|
self.capital = float(old["capital"])
|
||||||
|
if old.get("real_capital") is not None:
|
||||||
|
self.real_capital = float(old["real_capital"])
|
||||||
|
self._log("INIT_LINEAGE", {"da": latest.name,
|
||||||
|
"capital": round(self.capital, 2),
|
||||||
|
"real_capital": round(self.real_capital, 2)})
|
||||||
|
except Exception as e: # mai bloccare il boot per il lineage
|
||||||
|
print(f" [{self.worker_id}] WARN lineage: {e}")
|
||||||
|
|
||||||
def _load_state(self):
|
def _load_state(self):
|
||||||
"""Riprende stato da status.json se esiste."""
|
"""Riprende stato da status.json se esiste."""
|
||||||
if not self.status_path.exists():
|
if not self.status_path.exists():
|
||||||
self._log("INIT", {"capital": self.capital, "strategy": self.strategy.name,
|
self._inherit_lineage_capital()
|
||||||
|
self._log("INIT", {"capital": round(self.capital, 2),
|
||||||
|
"real_capital": round(self.real_capital, 2),
|
||||||
|
"strategy": self.strategy.name,
|
||||||
"asset": self.asset, "tf": self.tf})
|
"asset": self.asset, "tf": self.tf})
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""INIT_LINEAGE (2026-06-12): al primo avvio un worker eredita capital/real_capital
|
||||||
|
dal worker piu' recente di stessa strategia+asset su altro timeframe. Nato dallo
|
||||||
|
swap fade 1h->15m: i worker nuovi partivano dall'allocazione del pool scartando
|
||||||
|
il PnL del gemello (-16.8 di equity fantasma, riallineata a mano col seed)."""
|
||||||
|
import json
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from src.live.strategy_worker import StrategyWorker
|
||||||
|
|
||||||
|
|
||||||
|
def _mk(tmp_path, tf, capital=100.0):
|
||||||
|
w = StrategyWorker(strategy=SimpleNamespace(name="FAKE", fee_rt=0.001),
|
||||||
|
asset="BTC", tf=tf, capital=capital, position_size=0.5,
|
||||||
|
leverage=2.0, data_dir=tmp_path)
|
||||||
|
w._notify = lambda *a, **k: None
|
||||||
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def test_new_tf_inherits_capital_from_sibling(tmp_path):
|
||||||
|
old = _mk(tmp_path, "1h", capital=100.0)
|
||||||
|
old.capital = 181.19
|
||||||
|
old.real_capital = 181.18
|
||||||
|
old._save_state()
|
||||||
|
|
||||||
|
new = _mk(tmp_path, "15m", capital=174.50)
|
||||||
|
assert new.capital == 181.19
|
||||||
|
assert new.real_capital == 181.18
|
||||||
|
# la posizione NON si eredita
|
||||||
|
assert new.in_position is False
|
||||||
|
events = [json.loads(l)["event"] for l in new.trades_path.read_text().splitlines()]
|
||||||
|
assert "INIT_LINEAGE" in events
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_sibling_starts_from_allocation(tmp_path):
|
||||||
|
w = _mk(tmp_path, "15m", capital=174.50)
|
||||||
|
assert w.capital == 174.50
|
||||||
|
events = [json.loads(l)["event"] for l in w.trades_path.read_text().splitlines()]
|
||||||
|
assert "INIT_LINEAGE" not in events
|
||||||
|
|
||||||
|
|
||||||
|
def test_resume_ignores_lineage(tmp_path):
|
||||||
|
old = _mk(tmp_path, "1h", capital=100.0)
|
||||||
|
old.capital = 999.0
|
||||||
|
old._save_state()
|
||||||
|
new = _mk(tmp_path, "15m", capital=174.50) # eredita 999
|
||||||
|
new.capital = 150.0
|
||||||
|
new._save_state()
|
||||||
|
again = _mk(tmp_path, "15m", capital=174.50) # RESUME, non lineage
|
||||||
|
assert again.capital == 150.0
|
||||||
Reference in New Issue
Block a user