Compare commits
2 Commits
b691f48f43
...
67f7b89e4c
| Author | SHA1 | Date | |
|---|---|---|---|
| 67f7b89e4c | |||
| 9fa3e6a0be |
@@ -38,7 +38,9 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
|
||||
(~1.60 → reale ~1.1). Il calcolo per-singolo-TF è leak-free, ma **NON scendere sotto le 12h**:
|
||||
costi+overfitting dominano senza vantaggio (FULL Sh piatto ~1.3 da 12h a 4h; hold-out migliore a 1d).
|
||||
Deploy/paper a **1d**. Diari `2026-06-19-tp01-verification.md` / `-tp01-lookahead-fix-lf.md`.
|
||||
Paper trader: `scripts/live/paper_trend.py` (1d). Test: `tests/test_trend_portfolio.py`.
|
||||
Paper/forward: `scripts/live/paper_portfolio.py` (TP01+XS01, 1d). Test: `tests/test_trend_portfolio.py`.
|
||||
(Il vecchio `paper_trend.py` standalone TP01 è RITIRATO in `Old/scripts/live/` 2026-07-17 — inerte
|
||||
dal 2026-06-19, non in cron, sostituito da `paper_portfolio`.)
|
||||
Ri-verifica: `scripts/analysis/{verify_tp01,stress_tp01,tp01_lowfreq}.py`.
|
||||
⚠️ **ANCHOR TIMING-LUCK (2026-07-02, confermato da scettico):** l'hold-out ~0.31 è calcolato
|
||||
sull'ancora daily 00:00 UTC, che è la **migliore delle 24 possibili** (mediana ancore 0.04, banda
|
||||
@@ -399,7 +401,7 @@ src/backtest/harness.py → harness ONESTO (load BTC/ETH, backtest_signals no
|
||||
src/version.py → APP_VERSION (legge il file VERSION)
|
||||
scripts/research/ → ricerca: track{A-I}_*.py + options_vrp_*.py + fetch_dvol.py
|
||||
scripts/portfolio/ → run_portfolio.py (report) + xsec_*.py (ricerca/affinamento XS01)
|
||||
scripts/live/paper_trend.py → paper trader forward-only di TP01 (1d) (no esecuzione reale)
|
||||
scripts/live/paper_portfolio.py → paper/forward-only del book attivo TP01+XS01 (1d) (no esecuzione reale)
|
||||
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)
|
||||
@@ -424,7 +426,7 @@ uv run python scripts/research/trackD_timing.py # vincitrice su 15
|
||||
uv run python scripts/analysis/fetch_hyperliquid.py # fetch+certify universo Hyperliquid (Cerbero mainnet) -> data/raw/hl_*
|
||||
uv run python scripts/portfolio/xsec_research.py # ricerca cross-sectional su Hyperliquid (XS01)
|
||||
uv run python scripts/portfolio/run_portfolio.py # report del PORTAFOGLIO attivo (TP01+XS01)
|
||||
uv run python scripts/live/paper_trend.py # avanza il paper trader TP01 (forward-only, 1d)
|
||||
uv run python scripts/live/paper_portfolio.py # avanza il paper del book TP01+XS01 (forward-only, 1d)
|
||||
uv run pytest # test
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# 2026-07-15 — Incident ops: feed BTC/ETH del book congelato 7 giorni (`copy2`→`copystat` EPERM)
|
||||
|
||||
**Tipo:** incident operativo (pipeline dati), non ricerca. Nessun edge, nessun cambio a
|
||||
strategie/pesi/config di trading. Documentato qui perché ha **congelato il segnale del book live**
|
||||
per una settimana: il book ha ri-girato ogni ora ma su una barra vecchia (`ultima barra 2026-07-08`),
|
||||
senza ri-valutare TP01. Trovato durante un check di `stato`, non da un alert (il fallimento era
|
||||
silenzioso — vedi Lezioni).
|
||||
|
||||
## Sintomo
|
||||
|
||||
Nel log del `cron_book` orario, ogni run dal 2026-07-08 in poi mostrava:
|
||||
|
||||
```
|
||||
ultima barra : 2026-07-08 # <-- oggi e' 2026-07-15
|
||||
...
|
||||
ETH TP +0.000 · SKH +1(LONG@1868.9) -> net $+75 | pos $+77 -> HOLD (a target)
|
||||
=> Nessuna azione: conto gia' al target netto del book.
|
||||
```
|
||||
|
||||
Conto **online** e sano ($600.5), esecuzione armata, cron regolari — ma il segnale TP01 (1d) fermo
|
||||
al 8 luglio. SKH01 meno colpito: prende il 5m fresco **in memoria** via `livefeed.fresh_5m`, non da
|
||||
disco. Il congelamento riguardava il **feed certificato su disco** (`data/raw/{btc,eth}_*.parquet`),
|
||||
mtime fermo a **Jul 8 00:32**.
|
||||
|
||||
## Diagnosi (catena completa)
|
||||
|
||||
Il `cron_daily` (00:30) ricostruisce il feed Deribit con `rebuild_history.py --asset BTC ETH`. Nel
|
||||
`logs/cron_daily.log`, **ogni notte dal 9 luglio**:
|
||||
|
||||
```
|
||||
REBUILD STORICO da DERIBIT MAINNET — FULL (scrive data/raw, backup)
|
||||
Traceback (most recent call last):
|
||||
File "scripts/analysis/rebuild_history.py", line 189, in build
|
||||
shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.prebuild.bak")
|
||||
PermissionError: [Errno 1] Operation not permitted
|
||||
```
|
||||
|
||||
Il resto del daily (Hyperliquid, DVOL, ETF, paper trader) proseguiva regolare — solo il **primo**
|
||||
step, il rebuild BTC/ETH, moriva. Sequenza dentro `build()`:
|
||||
|
||||
```python
|
||||
if path.exists():
|
||||
shutil.copy2(path, BACKUP / f"...prebuild.bak") # <-- crash QUI
|
||||
tmp = path.with_suffix(".parquet.tmp")
|
||||
d.to_parquet(tmp, index=False) # <-- mai raggiunto
|
||||
tmp.replace(path) # <-- il feed non viene mai scritto
|
||||
```
|
||||
|
||||
**Causa radice:** `shutil.copy2` = `copyfile` **+ `copystat`**. `copystat` fa `os.utime`/`chmod`/
|
||||
`chflags` sulla destinazione con valori espliciti → richiede la **proprietà** del file (o `CAP_FOWNER`),
|
||||
**non basta il group-write**. I file erano `root:adriano 660`; il cron gira come **adriano** (uid 1001,
|
||||
membro del gruppo `adriano`). adriano può *scrivere* nel `.bak` (group-write) ma non `utime`-arlo →
|
||||
`EPERM`.
|
||||
|
||||
Firma diagnostica coerente: `btc_5m.parquet.prebuild.bak` aveva mtime **odierno** mentre gli altri
|
||||
`.bak` erano fermi al 7 luglio. Ovvero il `copyfile` interno riusciva (scriveva contenuto + mtime),
|
||||
poi `copystat` lanciava → l'eccezione abortiva il loop **prima** di scrivere `data/raw/btc_5m.parquet`.
|
||||
|
||||
Perché ha iniziato il 9 luglio e non prima: l'ownership `root:*` dei file di `data/raw` è residuo di
|
||||
run/manutenzione eseguiti come root (l'incidente Traefik del 9 luglio è nella stessa finestra). Finché
|
||||
il processo era root, `copystat` sui file root riusciva; passato a run come adriano, EPERM. La causa
|
||||
prossima non è *chi* ha creato i file, ma che **un backup difensivo poteva abortire il feed live**.
|
||||
|
||||
## Impatto
|
||||
|
||||
- **7 giorni (2026-07-08 → 07-15)** di feed Deribit BTC/ETH fermo → TP01 (1d) del book non ha
|
||||
ri-valutato. Il book ha tenuto un ETH long SKH01 (@1868.9) impostato il 8 luglio.
|
||||
- **Nessuna perdita né ordine errato:** il segnale ricalcolato sul feed fresco **coincide** con quello
|
||||
tenuto (BTC flat, ETH long $75, già a target → 0 ordini di divergenza). Rischio evitato per fortuna
|
||||
di regime, non per design — il book è comunque stato **cieco** a una settimana di segnale TP01.
|
||||
- Paper trader e dashboard **non impattati**: usano feed diversi (HL/DVOL/ETF, aggiornati regolarmente).
|
||||
|
||||
## Fix
|
||||
|
||||
`scripts/analysis/rebuild_history.py` — backup reso **best-effort e non fatale**:
|
||||
|
||||
```python
|
||||
if path.exists():
|
||||
# copyfile (solo contenuto, NO copystat) dentro try/except: un .bak difensivo
|
||||
# non deve MAI poter bloccare la scrittura del feed live.
|
||||
try:
|
||||
shutil.copyfile(path, BACKUP / f"{asset.lower()}_{tf}.parquet.prebuild.bak")
|
||||
except OSError as e:
|
||||
print(f" WARN backup {path.name} saltato (non fatale): {e}")
|
||||
```
|
||||
|
||||
Due cambi: (1) `copy2` → `copyfile` — un `.bak` non ha bisogno dei metadati originali, e `copystat`
|
||||
era l'unica fonte dell'EPERM; (2) `try/except OSError` con warning — anche un futuro errore di backup
|
||||
(disco pieno, permessi) non blocca più il feed.
|
||||
|
||||
**Verifica end-to-end:**
|
||||
- rebuild rigirato a mano → `scritto {btc,eth}_{5m,15m,1h}.parquet`, feed fresco a **2026-07-15**,
|
||||
audit cross-venue **BTC 1.9 bps / ETH 2.0 bps** (pulito).
|
||||
- i file di `data/raw` ora sono **`adriano:adriano`** (`tmp.replace` li rigenera con l'owner del
|
||||
processo) → l'ownership si auto-sana, il problema non si ripresenta anche con `copy2`.
|
||||
- `book_execute.py` in dry-run legge **`ultima barra 2026-07-15`** e ricalcola → BTC flat / ETH long
|
||||
$75 → HOLD. Pipeline sbloccata.
|
||||
|
||||
## Lezioni
|
||||
|
||||
1. **Un backup difensivo non deve mai poter uccidere l'operazione che protegge.** Il `.bak` è una
|
||||
rete di sicurezza; il suo fallimento (metadati, permessi, disco) va isolato in `try/except`, mai
|
||||
propagato allo scrittore del feed live.
|
||||
2. **`shutil.copy2` è insidioso in ambienti multi-utente:** `copystat` richiede *ownership*, non
|
||||
group-write. In una dir a proprietà mista (`root:adriano`) un cron non-root ci sbatte. Per un
|
||||
backup, `copyfile` è la scelta giusta (i metadati del `.bak` non servono).
|
||||
3. **Fallimento silenzioso = il peggiore.** Il conto era online, i cron giravano, gli alert tacevano:
|
||||
solo la barra vecchia nel log tradiva il congelamento. Il gate `online` del book protegge dai
|
||||
problemi di *conto*, non da un feed *stale*. **Follow-up raccomandato:** alert se
|
||||
`ultima_barra < today − 2g` in `book_execute` (staleness-gate esplicito).
|
||||
4. **La sicurezza del book ha retto a metà:** non ha operato a cieco su un conto irraggiungibile, ma
|
||||
*ha* operato (HOLD) su un segnale stantio. La coincidenza segnale-vecchio == segnale-nuovo è
|
||||
fortuna di regime; un mercato in movimento avrebbe lasciato il book fuori posizione.
|
||||
|
||||
## Runbook (feed BTC/ETH stantio)
|
||||
|
||||
1. **Rilevare:** `stat -c '%y %n' data/raw/btc_1h.parquet` → se mtime > 1-2g fa, il feed è fermo.
|
||||
Conferma incrociata: `ultima barra` nel `logs/cron_book.log`.
|
||||
2. **Diagnosi:** `grep -A3 "REBUILD STORICO" logs/cron_daily.log | tail` → cercare Traceback nello
|
||||
step rebuild.
|
||||
3. **Sbloccare:** `uv run python scripts/analysis/rebuild_history.py --asset BTC ETH` → deve stampare
|
||||
`scritto ..._5m/15m/1h.parquet` e l'audit cross-venue (atteso < ~10 bps mediana).
|
||||
4. **Verificare il book:** `uv run python scripts/live/book_execute.py` (senza `--execute`) → `ultima
|
||||
barra` deve essere la data odierna.
|
||||
|
||||
Nessun file di trading (strategie/pesi/config) toccato. Solo `rebuild_history.py`.
|
||||
@@ -186,7 +186,15 @@ def build(asset: str, write: bool, smoke: bool) -> None:
|
||||
for tf, d in built.items():
|
||||
path = _parquet_path(asset, tf)
|
||||
if path.exists():
|
||||
shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.prebuild.bak")
|
||||
# Backup best-effort: copyfile (solo contenuto, NO copystat).
|
||||
# copy2 copiava anche i metadati -> os.utime/chmod su un .bak di proprieta'
|
||||
# altrui (es. root) fallisce con EPERM per un membro-gruppo e ABORTIVA il
|
||||
# rebuild del feed live (incidente 2026-07-15: feed fermo 7g). Un backup e'
|
||||
# difensivo: un suo errore non deve MAI bloccare la scrittura del feed.
|
||||
try:
|
||||
shutil.copyfile(path, BACKUP / f"{asset.lower()}_{tf}.parquet.prebuild.bak")
|
||||
except OSError as e:
|
||||
print(f" WARN backup {path.name} saltato (non fatale): {e}")
|
||||
tmp = path.with_suffix(".parquet.tmp")
|
||||
d.to_parquet(tmp, index=False)
|
||||
tmp.replace(path)
|
||||
|
||||
@@ -58,7 +58,7 @@ def main():
|
||||
|
||||
print("\n PARITA' vs paper trader (target = current_target):")
|
||||
if all(a["paper"] is None for a in r["assets"]):
|
||||
print(" (paper non inizializzato: avvia scripts/live/paper_trend.py)")
|
||||
print(" (paper TP01 standalone RITIRATO 2026-07-17: la parita' vive ora in paper_portfolio/shadow)")
|
||||
else:
|
||||
for a in r["assets"]:
|
||||
print(f" {a['asset']}: paper {a['paper']:+.3f}x shadow {a['target']:+.3f}x -> {'OK' if a['parity'] else 'DIFFERISCE'}")
|
||||
|
||||
@@ -18,6 +18,8 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
ASSETS = ["BTC", "ETH"]
|
||||
WEIGHT = 0.5
|
||||
FALLBACK_CAPITAL = 2000.0
|
||||
# Fallback LEGACY: lo scrittore (paper_trend.py) e' RITIRATO 2026-07-17 (-> Old/). Il file non esiste
|
||||
# piu' -> guardia .exists() sotto degrada a FALLBACK_CAPITAL (=2000) e paper_pos=None (parita' disattiva).
|
||||
PAPER_STATE = PROJECT_ROOT / "data" / "paper_trend" / "state.json"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user