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>
134 lines
5.6 KiB
Python
134 lines
5.6 KiB
Python
"""Reconcile degli ordini RESTING (2026-06-12): TP/DSL attesi dai libri vs ordini
|
|
in book + fill non bookati (caso MR02_BTC: TP resting fillato di notte e disaster-SL
|
|
sparito, scoperti solo al close sim ore dopo). Nessuna rete: client stubbato,
|
|
PAPER puntato su tmp_path."""
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
|
|
|
from src.live import books
|
|
|
|
|
|
class _Client:
|
|
def __init__(self, open_orders=None, trades=None):
|
|
self._oo = open_orders or []
|
|
self._tr = trades or []
|
|
|
|
def get_open_orders(self, currency="USDC", type="all"):
|
|
# merge 'all'+'trigger_all' nel chiamante: qui si ritorna tutto due volte
|
|
return self._oo
|
|
|
|
def get_trade_history(self, limit=100, instrument_name=None):
|
|
return [t for t in self._tr
|
|
if not instrument_name or t["instrument"] == instrument_name]
|
|
|
|
|
|
def _mk_worker(root: Path, wid: str, st: dict):
|
|
d = root / wid
|
|
d.mkdir(parents=True)
|
|
(d / "status.json").write_text(json.dumps(st))
|
|
|
|
|
|
def _setup(tmp_path, monkeypatch, statuses: dict):
|
|
root = tmp_path / "portfolio_paper"
|
|
for wid, st in statuses.items():
|
|
_mk_worker(root, wid, st)
|
|
monkeypatch.setattr(books, "PAPER", root)
|
|
import importlib
|
|
ra = importlib.import_module("scripts.analysis.reconcile_account")
|
|
monkeypatch.setattr(ra, "PAPER", root)
|
|
return ra
|
|
|
|
|
|
IN_POS = {"real_in_position": True, "real_tp_order_id": "TP-1",
|
|
"real_dsl_order_id": "DSL-1"}
|
|
|
|
|
|
def test_expected_resting_reads_single_leg(tmp_path, monkeypatch):
|
|
_setup(tmp_path, monkeypatch, {
|
|
"MR01_bollinger_fade__ETH__1h": IN_POS,
|
|
# pairs e worker flat: NESSUN resting atteso
|
|
"PR01_pairs_reversion__ETH_BTC__1h": {"real_in_position": True,
|
|
"real_amount_a": 0.1},
|
|
"MR02_donchian_fade__BTC__1h": {"real_in_position": False,
|
|
"real_tp_order_id": "TP-9"},
|
|
})
|
|
exp = books.expected_resting()
|
|
assert {(e["order_id"], e["kind"]) for e in exp} == {("TP-1", "tp"), ("DSL-1", "dsl")}
|
|
assert all(e["instrument"] == "ETH_USDC-PERPETUAL" for e in exp)
|
|
|
|
|
|
def test_resting_ok_when_on_book(tmp_path, monkeypatch):
|
|
ra = _setup(tmp_path, monkeypatch, {"MR01_bollinger_fade__ETH__1h": IN_POS})
|
|
cl = _Client(open_orders=[{"order_id": "TP-1", "label": "MR01_bollinger_fade__ETH__1h"},
|
|
{"order_id": "DSL-1", "label": "MR01_bollinger_fade__ETH__1h"}])
|
|
rows = ra.compute_resting_drift(cl)
|
|
assert [r["status"] for r in rows] == ["OK", "OK"]
|
|
|
|
|
|
def test_resting_filled_unbooked_vs_missing(tmp_path, monkeypatch):
|
|
# TP sparito dal book MA con fill nel trade history -> FILLED_UNBOOKED (caso MR02);
|
|
# DSL sparito senza fill (trigger genera order_id nuovo) -> MISSING
|
|
ra = _setup(tmp_path, monkeypatch, {"MR01_bollinger_fade__ETH__1h": IN_POS})
|
|
cl = _Client(open_orders=[],
|
|
trades=[{"instrument": "ETH_USDC-PERPETUAL", "order_id": "TP-1",
|
|
"amount": 0.103, "price": 1640.6}])
|
|
by_kind = {r["kind"]: r for r in ra.compute_resting_drift(cl)}
|
|
assert by_kind["tp"]["status"] == "FILLED_UNBOOKED"
|
|
assert abs(by_kind["tp"]["filled"] - 0.103) < 1e-9
|
|
assert by_kind["dsl"]["status"] == "MISSING"
|
|
|
|
|
|
def test_resting_stale_order_from_flat_worker(tmp_path, monkeypatch):
|
|
# ordine in book con label di un NOSTRO worker flat -> STALE (fillerebbe a sorpresa);
|
|
# ordini di altri bot (label sconosciuta) ignorati
|
|
ra = _setup(tmp_path, monkeypatch, {
|
|
"MR01_bollinger_fade__ETH__1h": {"real_in_position": False}})
|
|
cl = _Client(open_orders=[
|
|
{"order_id": "OLD-7", "label": "MR01_bollinger_fade__ETH__1h",
|
|
"instrument": "ETH_USDC-PERPETUAL", "order_type": "limit"},
|
|
{"order_id": "X-1", "label": "altro_bot", "instrument": "ETH_USDC-PERPETUAL"},
|
|
])
|
|
rows = ra.compute_resting_drift(cl)
|
|
assert len(rows) == 1
|
|
assert rows[0]["status"] == "STALE" and rows[0]["order_id"] == "OLD-7"
|
|
|
|
|
|
def _age(root: Path, wid: str, minutes: float):
|
|
"""Invecchia lo status.json di un worker di `minutes` minuti (mtime)."""
|
|
import os
|
|
p = root / wid / "status.json"
|
|
t = p.stat().st_mtime - minutes * 60
|
|
os.utime(p, (t, t))
|
|
|
|
|
|
def test_stale_real_position_flagged(tmp_path, monkeypatch):
|
|
# worker con posizione reale ma status.json vecchio = non gestito (caso
|
|
# MR02_BTC 1h ritirato dallo swap a 15m mentre short reale)
|
|
ra = _setup(tmp_path, monkeypatch, {
|
|
"MR02_donchian_fade__BTC__1h": {"real_in_position": True, "real_amount": 0.0028,
|
|
"real_side": "sell"}})
|
|
_age(ra.PAPER, "MR02_donchian_fade__BTC__1h", 800)
|
|
stale = ra.compute_stale_real_positions(max_age_min=15)
|
|
assert len(stale) == 1
|
|
assert stale[0]["worker"] == "MR02_donchian_fade__BTC__1h"
|
|
assert stale[0]["real_amount"] == 0.0028
|
|
|
|
|
|
def test_fresh_real_position_not_flagged(tmp_path, monkeypatch):
|
|
# worker vivo (status.json appena scritto) NON e' stantio anche se in posizione
|
|
ra = _setup(tmp_path, monkeypatch, {
|
|
"SH01_shape_ml__ETH__1h": {"real_in_position": True, "real_amount": 0.053,
|
|
"real_side": "sell"}})
|
|
assert ra.compute_stale_real_positions(max_age_min=15) == []
|
|
|
|
|
|
def test_flat_stale_worker_not_flagged(tmp_path, monkeypatch):
|
|
# worker vecchio MA flat: niente posizione reale -> non e' un orfano
|
|
ra = _setup(tmp_path, monkeypatch, {
|
|
"MR01_bollinger_fade__BTC__1h": {"real_in_position": False, "real_amount": 0.0}})
|
|
_age(ra.PAPER, "MR01_bollinger_fade__BTC__1h", 800)
|
|
assert ra.compute_stale_real_positions(max_age_min=15) == []
|