book_execute: pos_after di nuovo nel jsonl, letta subito dopo il rebalance in un try (#39)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 17:42:34 +00:00
parent d34b0a81d8
commit ae887a3d98
2 changed files with 105 additions and 10 deletions
+88
View File
@@ -332,6 +332,94 @@ def test_position_usd_illeggibile_dopo_un_fill_non_perde_il_fill(tmp_path, monke
assert righe[0]["asset"] == "BTC" and righe[0]["pos_after"] is None
# ---------------------------------------------------------------------------
# #39 — regressione di #23: pos_after finiva sempre None nel jsonl, anche quando il reconcile
# leggeva la posizione senza problemi (la lettura era rimasta un side-effect informativo, il suo
# risultato non arrivava piu' a `_registra_fill`).
# ---------------------------------------------------------------------------
def test_rebalance_ok_scrive_pos_after_dal_reconcile(tmp_path, monkeypatch, capsys):
"""book_execute (#39): rebalance_signed e position_usd vanno a segno entrambi -> il record
di log_event ha pos_after = il valore letto dal reconcile, non None."""
mod = carica_script("scripts/live/book_execute.py")
monkeypatch.setattr(mod, "LOG_DIR", tmp_path)
monkeypatch.setattr(mod, "LOG", tmp_path / "book_executions.jsonl")
fill = Fill("BTC_USDC-PERPETUAL", "buy", 0.003, 0.003, 60000.0, 0.02, "o1", "filled", True, "")
class _TraderOk:
def set_open_cap(self, inst, cap_usd, mark):
pass
def rebalance_signed(self, inst, net, mark, min_usd=5.0):
return [fill] if inst.startswith("BTC") else []
def position_usd(self, inst):
return 500.0
def ensure_disaster_sl(self, inst, sl_pct):
return {"state": "ok", "stop": 1.0, "amount": 0.0}
canned = _canned_report()
monkeypatch.setattr(mod, "book_report", lambda **k: canned)
monkeypatch.setattr(mod, "notify", lambda t, d=None: None)
monkeypatch.setattr(mod, "DeribitTrader", lambda *a, **k: _TraderOk())
monkeypatch.setattr(mod, "load_config",
lambda: dict(execution_enabled=True, min_order_usd=5.0,
disaster_sl_pct=0.30, max_data_age_days=2.0,
skh_feed_max_age_min=30.0))
monkeypatch.setattr(sys, "argv", ["book_execute.py", "--execute"])
mod._run()
out = capsys.readouterr().out
assert "reconcile: pos $500" in out
righe = [json.loads(l) for l in (tmp_path / "book_executions.jsonl").read_text().splitlines()]
assert len(righe) == 1
assert righe[0]["asset"] == "BTC" and righe[0]["pos_after"] == 500.0
def test_position_usd_che_solleva_non_impedisce_il_fill_pos_after_none(tmp_path, monkeypatch, capsys):
"""book_execute (#39): stesso scenario di sopra ma il reconcile solleva -> il fill si
registra comunque (vincolo #23), con pos_after None."""
mod = carica_script("scripts/live/book_execute.py")
monkeypatch.setattr(mod, "LOG_DIR", tmp_path)
monkeypatch.setattr(mod, "LOG", tmp_path / "book_executions.jsonl")
fill = Fill("BTC_USDC-PERPETUAL", "buy", 0.003, 0.003, 60000.0, 0.02, "o1", "filled", True, "")
class _TraderReconcileBoom:
def set_open_cap(self, inst, cap_usd, mark):
pass
def rebalance_signed(self, inst, net, mark, min_usd=5.0):
return [fill] if inst.startswith("BTC") else []
def position_usd(self, inst):
raise RuntimeError("api 500")
def ensure_disaster_sl(self, inst, sl_pct):
return {"state": "ok", "stop": 1.0, "amount": 0.0}
canned = _canned_report()
monkeypatch.setattr(mod, "book_report", lambda **k: canned)
monkeypatch.setattr(mod, "notify", lambda t, d=None: None)
monkeypatch.setattr(mod, "DeribitTrader", lambda *a, **k: _TraderReconcileBoom())
monkeypatch.setattr(mod, "load_config",
lambda: dict(execution_enabled=True, min_order_usd=5.0,
disaster_sl_pct=0.30, max_data_age_days=2.0,
skh_feed_max_age_min=30.0))
monkeypatch.setattr(sys, "argv", ["book_execute.py", "--execute"])
mod._run() # non deve sollevare: il fill non e' perso
out = capsys.readouterr().out
assert "reconcile: pos illeggibile (RuntimeError)" in out
righe = [json.loads(l) for l in (tmp_path / "book_executions.jsonl").read_text().splitlines()]
assert len(righe) == 1
assert righe[0]["asset"] == "BTC" and righe[0]["filled"] == 0.003
assert righe[0]["pos_after"] is None
# ---------------------------------------------------------------------------
# Il formato delle righe stampate non e' cambiato: tradesdb.parse_cron_log deve continuare a
# leggerlo, sia in un giro normale sia in un giro con RebalanceParziale (#23).