Files
PythagorasGoal/src/live/livefeed.py
T
Adriano Dal Pastro db738bce3b feat(live): arma il BOOK DERIBIT (TP01+SKH01 nettati in software)
Estende l'esecuzione live da TP01-only al book Deribit-only completo. TP01 e SKH01
tradano lo STESSO strumento (una sola posizione netta per conto su Deribit) -> netting
in software: un solo ordine/asset verso il target netto.

- src/live/book.py: target NETTO per asset = clamp(0.5*E*(0.75*tp_frac + 0.25*skh_sign), ±cap).
  Riusa current_target (TP01, causale) + _skyhook_positions (segno L/S, book 230m) + conto reale.
- src/live/execution.py: rebalance_signed() — reconcile CON SEGNO (long/short, flip via close+open,
  reduce reduce_only). La close resta sempre permessa (si esce da qualunque posizione).
- src/live/livefeed.py: fresh_5m() — feed 5m certificato + coda recente EFFIMERA da Deribit pubblico
  (stesso simbolo inverse, in-memory, NON scrive su disco -> dati certificati intatti; fallback al
  certificato su errore). Solo SKH01 ne ha bisogno (e' a 230m); TP01 e' giornaliero.
- scripts/live/book_execute.py: executor doppio-gate (config + --execute), disaster-SL on-book sulla
  posizione netta, log in data/live/book_executions.jsonl. Feed SKH fresco (live_feed=True).
- scripts/cron_book.sh + crontab ORARIO: book idempotente ogni ora (riconcilia al netto corrente);
  rimossa la riga live_execute.py (TP01-only) dal cron daily per non far collidere i due.
- config/live.json: ARMATO (execution_enabled=true). cap/asset $300 split 75/25, disaster-SL -30%.
  Tutto flat all'arming -> nessun ordine finche' un segnale non arma.
- tests/test_book_live.py: 20 test (sizing 75/25, cap, flip/close/reduce, parita' pesi backtest,
  gate-safety, reconcile con trader fittizio, merge/dedup feed + fallback, loader onorato). 76/76 pass.

CAVEAT: exit SKH SOFTWARE (latenza fino a fine barra 230m; solo disaster-SL on-book); TP01 scende a
peso 0.75 (max $225/asset); SKH01 resta research-grade (equity daily-step, margine DD ETH sottile).

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

84 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""FEED LIVE EFFIMERO per il segnale SKH01 (book a 230m) — NON tocca i dati certificati su disco.
SKH01 decide su griglia 230m: per eseguirlo fedelmente il segnale serve fresco all'ultima barra
chiusa. Il rebuild certificato (rebuild_history.py) gira 1×/giorno e fa un rebuild COMPLETO (pesante):
girarlo ogni ora sarebbe sbagliato e violerebbe la regola "aggiornare lo storico SOLO con
rebuild_history + certificare". Quindi qui NON scriviamo su disco: carichiamo il 5m CERTIFICATO e gli
appendiamo IN MEMORIA una coda recente presa da Deribit PUBBLICO (ccxt, tokenless, STESSO simbolo
inverse del feed certificato -> prezzi entro ~3 bps). I dati certificati restano la verità su disco;
questa estensione vive solo nel processo live e per il calcolo del segnale.
Robusto ai fallimenti: qualunque errore di rete/fetch -> ritorna il feed certificato invariato (il
runner degrada a "fermo all'ultimo dato certificato", mai opera a cieco). Solo SKH01 ne ha bisogno:
TP01 è giornaliero e gira bene sul feed certificato.
"""
from __future__ import annotations
import time
import pandas as pd
from src.data.downloader import load_data
# STESSO simbolo del feed certificato (vedi scripts/analysis/rebuild_history.DERIBIT_INSTR):
# inverse USD perp, storia lunga, entro ~3 bps dal lineare USDC su cui eseguiamo.
DERIBIT_SYMBOL = {"BTC": "BTC/USD:BTC", "ETH": "ETH/USD:ETH"}
SCHEMA = ["timestamp", "open", "high", "low", "close", "volume"]
def _fetch_recent_5m(symbol: str, lookback_days: int) -> pd.DataFrame:
"""Coda recente di 5m da Deribit pubblico (ccxt). Paginazione in avanti. Solo letture pubbliche."""
import ccxt
ex = ccxt.deribit({"enableRateLimit": True})
tf_ms = 5 * 60 * 1000
since = int((time.time() - lookback_days * 86400) * 1000)
rows: dict[int, list] = {}
guard = 0
while guard < 200:
guard += 1
try:
r = ex.fetch_ohlcv(symbol, "5m", since=since, limit=1000)
except Exception:
break
r = [x for x in r if int(x[0]) >= since]
if not r:
break
for x in r:
t = int(x[0])
rows[t] = [t, float(x[1]), float(x[2]), float(x[3]), float(x[4]), float(x[5] or 0)]
nxt = int(r[-1][0]) + tf_ms
if nxt <= since:
break
since = nxt
if not rows:
return pd.DataFrame(columns=SCHEMA)
return pd.DataFrame(rows.values(), columns=SCHEMA).sort_values("timestamp").reset_index(drop=True)
def merge_tail(base: pd.DataFrame, tail: pd.DataFrame) -> pd.DataFrame:
"""Fonde la coda fresca sul feed certificato: concat, dedup per timestamp (la coda VINCE sui
duplicati, ma le barre certificate storiche restano), riordina. Mantiene lo schema di load_data
(inclusa 'datetime' se presente). PURA, testabile senza rete."""
if tail is None or tail.empty:
return base
cols = [c for c in SCHEMA if c in base.columns]
t = tail[[c for c in SCHEMA if c in tail.columns]].copy()
merged = pd.concat([base[cols], t], ignore_index=True)
merged = merged.drop_duplicates("timestamp", keep="last").sort_values("timestamp").reset_index(drop=True)
# ricostruisci 'datetime' coerente (build_frames non la usa, ma load_data la espone)
merged["datetime"] = pd.to_datetime(merged["timestamp"], unit="ms", utc=True)
return merged
def fresh_5m(asset: str, lookback_days: int = 12) -> pd.DataFrame:
"""Feed 5m certificato + coda recente effimera (in-memory). Fallback al certificato su errore."""
base = load_data(asset, "5m")
sym = DERIBIT_SYMBOL.get(asset)
if sym is None:
return base
try:
tail = _fetch_recent_5m(sym, lookback_days)
except Exception:
return base
return merge_tail(base, tail)