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:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user