14522262e6
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>
61 lines
2.3 KiB
Python
61 lines
2.3 KiB
Python
"""SH01 path live: last_block_only == tail del walk-forward completo (parity by
|
|
construction) + merge storia parquet/feed del runner."""
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from src.portfolio.runner import _with_history
|
|
|
|
|
|
def _df(n=6500, seed=0, t0=1_600_000_000_000):
|
|
rng = np.random.default_rng(seed)
|
|
c = 100 * np.exp(np.cumsum(rng.normal(0, 0.01, n)))
|
|
h = c * (1 + np.abs(rng.normal(0, 0.004, n)))
|
|
l = c * (1 - np.abs(rng.normal(0, 0.004, n)))
|
|
o = np.roll(c, 1); o[0] = c[0]
|
|
ts = t0 + 3_600_000 * np.arange(n)
|
|
return pd.DataFrame({"timestamp": ts, "open": o, "high": h, "low": l,
|
|
"close": c, "volume": 1.0})
|
|
|
|
|
|
def test_last_block_only_matches_full_wf_tail():
|
|
from scripts.analysis.shape_ml_research import ml_wf_entries
|
|
df = _df()
|
|
cfg = dict(W=24, H=12, model="logit", thresh=0.55, train_min=4000, retrain=500)
|
|
full = ml_wf_entries(df, **cfg)
|
|
last = ml_wf_entries(df, last_block_only=True, **cfg)
|
|
n = len(df)
|
|
# confine dell'ultimo blocco: start + k*retrain (deterministico)
|
|
start = max(cfg["train_min"], cfg["W"] - 1)
|
|
B = start
|
|
while B + cfg["retrain"] < n - 1:
|
|
B += cfg["retrain"]
|
|
full_tail = [e for e in full if e["i"] >= B]
|
|
assert last == full_tail # identita' esatta, non solo simile
|
|
assert all(e["i"] >= B for e in last)
|
|
|
|
|
|
def test_with_history_merges_contiguous():
|
|
hist = _df(n=100)
|
|
live = _df(n=50, t0=int(hist["timestamp"].iloc[60])) # overlap: live parte dentro hist
|
|
out = _with_history(hist, live)
|
|
assert len(out) == 60 + 50 # hist pre-overlap + live completo
|
|
ts = out["timestamp"].values
|
|
assert (np.diff(ts) > 0).all() # serie strettamente crescente
|
|
|
|
|
|
def test_with_history_gap_falls_back_to_live():
|
|
hist = _df(n=100)
|
|
gap_start = int(hist["timestamp"].iloc[-1]) + 10 * 3_600_000 # buco di 10 barre
|
|
live = _df(n=50, t0=gap_start)
|
|
warned = set()
|
|
out = _with_history(hist, live, warned, "BTC")
|
|
assert len(out) == 50 and "BTC" in warned # solo feed + warn deduplicato
|
|
out2 = _with_history(hist, live, warned, "BTC")
|
|
assert len(out2) == 50 # secondo giro: nessun nuovo warn
|
|
|
|
|
|
def test_with_history_none_hist_passthrough():
|
|
live = _df(n=50)
|
|
assert _with_history(None, live) is live
|