book_execute: SOLO PROTEZIONE su equity/SKH ignoti; fill mai persi su rebalance parziale (#21, #22, #23)
Prima, con equity reale non leggibile il giro dimensionava comunque un ordine sul paper_cap ($2.000 nominali) e lo mandava; con SKH ignoto il target restava quello (fail-safe solo nel segno). Ora: se non si sa quanto vale il conto o qual e' il segnale, nessun rebalance_signed — ma il disaster-SL si controlla LO STESSO per ogni asset (dipende da posizione/mark, non da equity/segnale). Giro degradato -> exit 2, solo se armato (non in anteprima dry-run). Fill persi: rebalance_signed puo' eseguire un fill (es. il close di un flip) e poi sollevare nel passo successivo (l'open dall'altra parte) — quel fill spariva perche' la lettura di `newpos`, PRIMA del giro che lo registrava, andava in eccezione a sua volta. Estratta la registrazione in _registra_fill(...), chiamata SUBITO dopo ogni fill (anche quelli di RebalanceParziale, nuova eccezione di execution.py con .fills/.causa) e PRIMA di leggere la posizione; la lettura ora e' in un try separato, non fatale (pos_after=None + "reconcile: pos illeggibile" se fallisce). Aggiornati due test che assumevano il comportamento vecchio (eq_fallback "prosegue" sul paper capital) e il guard sulla sorgente di test_tradesdb.py (bar_ts ora e' un parametro di _registra_fill, non piu' `r['last_data']` inline). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+21
-6
@@ -535,8 +535,11 @@ def test_book_report_propagates_eq_fallback(monkeypatch):
|
||||
assert book.book_report(offline=True, equity_override=600.0).get("eq_fallback") == "PAPER_CAP"
|
||||
|
||||
|
||||
def test_book_execute_eq_fallback_warns_but_proceeds(monkeypatch, capsys):
|
||||
"""eq_fallback: avvisa + notify MA PROSEGUE (non e' un halt, a differenza di pos_error)."""
|
||||
def test_book_execute_eq_fallback_e_SOLO_PROTEZIONE(monkeypatch, capsys):
|
||||
"""(#21, U2) Comportamento cambiato: eq_fallback NON fa piu' proseguire un rebalance sul
|
||||
paper_cap — passa a SOLO PROTEZIONE (nessun ordine, disaster-SL comunque controllato, exit 2).
|
||||
Prima di questa riparazione il giro dimensionava un ordine reale su un capitale fittizio da
|
||||
$2.000 quando non si sapeva quanto valesse il conto vero; ora non manda alcun ordine."""
|
||||
from helpers import carica_script
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
|
||||
@@ -551,18 +554,30 @@ def test_book_execute_eq_fallback_warns_but_proceeds(monkeypatch, capsys):
|
||||
alerts = []
|
||||
|
||||
class FakeDT:
|
||||
def __init__(self):
|
||||
self.rebalance_chiamato = False
|
||||
def rebalance_signed(self, *a, **k):
|
||||
self.rebalance_chiamato = True
|
||||
return []
|
||||
def ensure_disaster_sl(self, inst, sl_pct): return {"state": "flat"}
|
||||
def position_usd(self, inst): return 0.0
|
||||
|
||||
trader = FakeDT()
|
||||
monkeypatch.setattr(mod, "book_report", lambda **k: canned)
|
||||
monkeypatch.setattr(mod, "notify", lambda title, det=None: alerts.append((title, det)))
|
||||
monkeypatch.setattr(mod, "DeribitTrader", lambda: FakeDT())
|
||||
monkeypatch.setattr(mod, "DeribitTrader", lambda: trader)
|
||||
monkeypatch.setattr(mod, "load_config",
|
||||
lambda: dict(execution_enabled=True, min_order_usd=5.0, disaster_sl_pct=0.30))
|
||||
monkeypatch.setattr(sys, "argv", ["book_execute.py", "--execute"])
|
||||
|
||||
mod._run()
|
||||
uscita = None
|
||||
try:
|
||||
mod._run()
|
||||
except SystemExit as e:
|
||||
uscita = e.code
|
||||
out = capsys.readouterr().out
|
||||
assert "EQUITY FALLBACK" in out # avvisato
|
||||
assert any("equity fallback" in t for t, _ in alerts) # alert inviato
|
||||
assert "Nessuna azione" in out # HA PROSEGUITO (non halt)
|
||||
assert any("equity fallback" in t for t, _ in alerts) # alert diagnostico esistente
|
||||
assert any("solo protezione" in t.lower() for t, _ in alerts) # NUOVO alert di modo
|
||||
assert trader.rebalance_chiamato is False # NESSUN ordine dimensionato
|
||||
assert uscita == 2 # giro degradato
|
||||
|
||||
@@ -14,6 +14,8 @@ import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
@@ -76,3 +78,324 @@ def test_online_true_quando_tutti_i_mark_sono_mainnet(monkeypatch):
|
||||
monkeypatch.setattr(S, "_positions", lambda c: ({a: 0.0 for a in S.ASSETS}, "mainnet", None))
|
||||
monkeypatch.setattr(S, "_equity", lambda c, m: (600.0, "mainnet USDC"))
|
||||
assert S.shadow_report()["online"] is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# #21, #22 — SOLO PROTEZIONE: equity ignota o SKH ignoto -> nessun rebalance_signed, ma il
|
||||
# disaster-SL si controlla LO STESSO per ogni asset. Giro degradato -> exit 2 (se ARMATO).
|
||||
# ---------------------------------------------------------------------------
|
||||
def _asset(nome, inst, mark, order=None):
|
||||
return dict(asset=nome, instrument=inst, tp_frac=1.0, skh_sign=1, skh_state="flat",
|
||||
net_target=225.0, position_usd=0.0, mark=mark, order=order)
|
||||
|
||||
|
||||
def _canned_report(**overrides):
|
||||
base = dict(
|
||||
last_data=_fresh_bar(), online=True, real_equity=None, equity=2000.0,
|
||||
eq_basis="paper capital (ipotetico)", cap_per_asset=300.0, skh_error=None, pos_error=None,
|
||||
eq_fallback=None,
|
||||
assets=[
|
||||
_asset("BTC", "BTC_USDC-PERPETUAL", 60000.0,
|
||||
order=dict(side="buy", delta=225.0, is_close=False, needs_flip=False)),
|
||||
_asset("ETH", "ETH_USDC-PERPETUAL", 3000.0, order=None),
|
||||
],
|
||||
orders=[],
|
||||
)
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
class _TraderSoloProtezione:
|
||||
"""Trader finto: registra se rebalance_signed viene chiamato (NON deve) e quali asset
|
||||
ricevono ensure_disaster_sl (TUTTI, comunque)."""
|
||||
|
||||
def __init__(self):
|
||||
self.rebalance_chiamato = False
|
||||
self.visti_sl: list[str] = []
|
||||
|
||||
def rebalance_signed(self, *a, **k):
|
||||
self.rebalance_chiamato = True
|
||||
return []
|
||||
|
||||
def position_usd(self, inst):
|
||||
return 0.0
|
||||
|
||||
def ensure_disaster_sl(self, inst, sl_pct):
|
||||
self.visti_sl.append(inst)
|
||||
return {"state": "ok", "stop": 1.0, "amount": 0.0}
|
||||
|
||||
|
||||
def _run_solo_protezione(canned, monkeypatch):
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
trader = _TraderSoloProtezione()
|
||||
alerts = []
|
||||
monkeypatch.setattr(mod, "book_report", lambda **k: canned)
|
||||
monkeypatch.setattr(mod, "notify", lambda t, d=None: alerts.append((t, d)))
|
||||
monkeypatch.setattr(mod, "DeribitTrader", lambda *a, **k: trader)
|
||||
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"])
|
||||
uscita = None
|
||||
try:
|
||||
mod._run()
|
||||
except SystemExit as e:
|
||||
uscita = e.code
|
||||
return trader, alerts, uscita
|
||||
|
||||
|
||||
def test_eq_fallback_e_solo_protezione(monkeypatch):
|
||||
canned = _canned_report(
|
||||
eq_fallback="equity reale non leggibile (conto flat) -> sizing su paper_cap $2,000")
|
||||
trader, alerts, uscita = _run_solo_protezione(canned, monkeypatch)
|
||||
assert trader.rebalance_chiamato is False # nessun ordine dimensionato
|
||||
assert trader.visti_sl == ["BTC_USDC-PERPETUAL", "ETH_USDC-PERPETUAL"] # protezione per TUTTI
|
||||
assert any("solo protezione" in t.lower() for t, _ in alerts)
|
||||
assert uscita == 2
|
||||
|
||||
|
||||
def test_skh_error_e_solo_protezione(monkeypatch):
|
||||
canned = _canned_report(skh_error="RuntimeError: feed 5m giu")
|
||||
trader, alerts, uscita = _run_solo_protezione(canned, monkeypatch)
|
||||
assert trader.rebalance_chiamato is False
|
||||
assert trader.visti_sl == ["BTC_USDC-PERPETUAL", "ETH_USDC-PERPETUAL"]
|
||||
assert any("solo protezione" in t.lower() for t, _ in alerts)
|
||||
assert uscita == 2
|
||||
|
||||
|
||||
def test_solo_protezione_in_dry_run_non_esce_2(monkeypatch, capsys):
|
||||
"""Un'anteprima (senza execution_enabled) non ha tentato nulla: non e' un giro degradato."""
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
canned = _canned_report(eq_fallback="equity reale non leggibile -> sizing su paper_cap")
|
||||
monkeypatch.setattr(mod, "book_report", lambda **k: canned)
|
||||
monkeypatch.setattr(mod, "notify", lambda t, d=None: None)
|
||||
monkeypatch.setattr(mod, "load_config",
|
||||
lambda: dict(execution_enabled=False, 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"]) # niente --execute
|
||||
mod._run() # non deve sollevare
|
||||
assert "solo protezione" in capsys.readouterr().out.lower()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# #23 — fill persi: un fill gia' eseguito non sparisce se un passo successivo del rebalance
|
||||
# (flip) o la lettura della posizione dopo falliscono.
|
||||
# ---------------------------------------------------------------------------
|
||||
class _FakeExec(DeribitTrader):
|
||||
"""DeribitTrader senza rete: instrumenta close/open per il test del flip parziale."""
|
||||
|
||||
def __init__(self, pos_usd, open_boom=None, chiusura_fill=None):
|
||||
self._pos = pos_usd
|
||||
self._open_boom = open_boom
|
||||
self._chiusura_fill = chiusura_fill
|
||||
|
||||
def position_usd(self, instrument):
|
||||
return self._pos
|
||||
|
||||
def close(self, instrument, label="tp01-close"):
|
||||
return self._chiusura_fill
|
||||
|
||||
def open(self, instrument, side, amount, label="tp01-open"):
|
||||
if self._open_boom is not None:
|
||||
raise self._open_boom
|
||||
return Fill(instrument, side, amount, amount, 60000.0, 0.01, "oid", "filled", True, "")
|
||||
|
||||
|
||||
def test_flip_con_open_che_esplode_dopo_close_solleva_rebalance_parziale():
|
||||
"""execution.py (#23a): il close del flip e' andato, l'open dall'altra parte no -> il fill
|
||||
del close NON deve sparire dentro un'eccezione nuda."""
|
||||
close_fill = Fill("BTC_USDC-PERPETUAL", "sell", 0.002, 0.002, 60000.0, 0.01, "c1",
|
||||
"filled", True, "")
|
||||
t = _FakeExec(pos_usd=120.0, open_boom=RuntimeError("502 Bad Gateway"), chiusura_fill=close_fill)
|
||||
with pytest.raises(RebalanceParziale) as ei:
|
||||
t.rebalance_signed("BTC_USDC-PERPETUAL", -80.0, 60000.0, min_usd=5.0)
|
||||
assert ei.value.fills == [close_fill]
|
||||
assert isinstance(ei.value.causa, RuntimeError) and "502" in str(ei.value.causa)
|
||||
|
||||
|
||||
def test_fill_del_close_non_si_perde_se_lopen_del_flip_fallisce(tmp_path, monkeypatch, capsys):
|
||||
"""book_execute (#23b): su RebalanceParziale, il fill del close finisce nel jsonl e nella
|
||||
riga di log PRIMA che la causa venga rilanciata; l'asset risulta fallito, l'altro asset
|
||||
(ETH) resta elaborato normalmente."""
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
monkeypatch.setattr(mod, "LOG_DIR", tmp_path)
|
||||
monkeypatch.setattr(mod, "LOG", tmp_path / "book_executions.jsonl")
|
||||
|
||||
close_fill = Fill("BTC_USDC-PERPETUAL", "sell", 0.002, 0.002, 60000.0, 0.01, "c1",
|
||||
"filled", True, "")
|
||||
|
||||
class _TraderFlip:
|
||||
def __init__(self):
|
||||
self.visti_sl: list[str] = []
|
||||
|
||||
def rebalance_signed(self, inst, net, mark, min_usd=5.0):
|
||||
if inst.startswith("BTC"):
|
||||
raise RebalanceParziale([close_fill], RuntimeError("open KO dopo close"))
|
||||
return []
|
||||
|
||||
def position_usd(self, inst):
|
||||
return 0.0
|
||||
|
||||
def ensure_disaster_sl(self, inst, sl_pct):
|
||||
self.visti_sl.append(inst)
|
||||
return {"state": "ok", "stop": 1.0, "amount": 0.0}
|
||||
|
||||
trader = _TraderFlip()
|
||||
canned = _canned_report() # BTC ha un ordine, ETH order=None
|
||||
alerts = []
|
||||
monkeypatch.setattr(mod, "book_report", lambda **k: canned)
|
||||
monkeypatch.setattr(mod, "notify", lambda t, d=None: alerts.append((t, d)))
|
||||
monkeypatch.setattr(mod, "DeribitTrader", lambda *a, **k: trader)
|
||||
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"])
|
||||
|
||||
uscita = None
|
||||
try:
|
||||
mod._run()
|
||||
except SystemExit as e:
|
||||
uscita = e.code
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert uscita == 2
|
||||
assert "-> SELL 0.0020 @ $60,000.0" in out # riga di fill stampata
|
||||
assert "asset falliti in questo giro: BTC" in out # isolamento esistente
|
||||
assert trader.visti_sl == ["ETH_USDC-PERPETUAL"] # BTC salta l'SL questo giro
|
||||
# (il fill del close NON e' un fill perso: e' nel jsonl anche se il resto del flip e' fallito)
|
||||
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]["side"] == "sell" and righe[0]["filled"] == 0.002
|
||||
assert righe[0]["pos_after"] is None
|
||||
|
||||
|
||||
def test_position_usd_illeggibile_dopo_un_fill_non_perde_il_fill(tmp_path, monkeypatch, capsys):
|
||||
"""book_execute (#23b): rebalance_signed va a segno, ma `position_usd` (reconcile) solleva
|
||||
dopo -> il fill e' comunque registrato con pos_after None, e non e' un guasto del giro."""
|
||||
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 __init__(self):
|
||||
self.visti_sl: list[str] = []
|
||||
|
||||
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):
|
||||
self.visti_sl.append(inst)
|
||||
return {"state": "ok", "stop": 1.0, "amount": 0.0}
|
||||
|
||||
trader = _TraderReconcileBoom()
|
||||
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: trader)
|
||||
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 reconcile mancato non e' fatale
|
||||
out = capsys.readouterr().out
|
||||
assert "reconcile: pos illeggibile (RuntimeError)" in out
|
||||
assert "asset falliti" not in out # NON e' trattato come giro fallito
|
||||
assert trader.visti_sl == ["BTC_USDC-PERPETUAL", "ETH_USDC-PERPETUAL"] # protezione comunque
|
||||
|
||||
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"] 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).
|
||||
# ---------------------------------------------------------------------------
|
||||
def test_parse_cron_log_legge_un_giro_normale_prodotto_dal_nuovo_codice(monkeypatch, capsys, tmp_path):
|
||||
from src.live import tradesdb as T
|
||||
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_btc = Fill("BTC_USDC-PERPETUAL", "buy", 0.003, 0.003, 60000.0, 0.02, "o1", "filled", True, "")
|
||||
|
||||
class _TraderOk:
|
||||
def rebalance_signed(self, inst, net, mark, min_usd=5.0):
|
||||
return [fill_btc] if inst.startswith("BTC") else []
|
||||
|
||||
def position_usd(self, inst):
|
||||
return 180.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
|
||||
log = f"===== 2026-09-27T13:47:01Z cron_book =====\n{out}===== done 13:47:05Z =====\n"
|
||||
runs = T.parse_cron_log(log)
|
||||
assert len(runs) == 1 and len(runs[0].fills) == 1
|
||||
f = runs[0].fills[0]
|
||||
assert f.asset == "BTC" and f.side == "buy" and abs(f.qty - 0.003) < 1e-9 and f.verified == 1
|
||||
|
||||
|
||||
def test_parse_cron_log_legge_il_fill_di_un_RebalanceParziale(monkeypatch, capsys, tmp_path):
|
||||
from src.live import tradesdb as T
|
||||
mod = carica_script("scripts/live/book_execute.py")
|
||||
monkeypatch.setattr(mod, "LOG_DIR", tmp_path)
|
||||
monkeypatch.setattr(mod, "LOG", tmp_path / "book_executions.jsonl")
|
||||
|
||||
close_fill = Fill("BTC_USDC-PERPETUAL", "sell", 0.002, 0.002, 60000.0, 0.01, "c1",
|
||||
"filled", True, "")
|
||||
|
||||
class _TraderFlip:
|
||||
def rebalance_signed(self, inst, net, mark, min_usd=5.0):
|
||||
if inst.startswith("BTC"):
|
||||
raise RebalanceParziale([close_fill], RuntimeError("open KO"))
|
||||
return []
|
||||
|
||||
def position_usd(self, inst):
|
||||
return 0.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: _TraderFlip())
|
||||
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"])
|
||||
|
||||
try:
|
||||
mod._run()
|
||||
except SystemExit:
|
||||
pass
|
||||
out = capsys.readouterr().out
|
||||
log = f"===== 2026-09-27T14:47:01Z cron_book =====\n{out}===== done 14:47:05Z =====\n"
|
||||
runs = T.parse_cron_log(log)
|
||||
assert len(runs) == 1 and len(runs[0].fills) == 1
|
||||
f = runs[0].fills[0]
|
||||
assert f.asset == "BTC" and f.side == "sell" and abs(f.qty - 0.002) < 1e-9
|
||||
|
||||
@@ -200,8 +200,12 @@ def test_l_esecutore_non_data_piu_i_fill_alla_barra_di_segnale():
|
||||
"""Il difetto riparato il 2026-08-23: `ts_utc` era `pd.Timestamp(r['last_data'])`.
|
||||
|
||||
Se qualcuno lo rimette, questo test lo dice — il DB verrebbe ricostruito su date finte.
|
||||
Dal refactor U2 (#23) la registrazione di un fill vive in `_registra_fill(...)`, che riceve
|
||||
`bar_ts` come parametro invece di leggere `r['last_data']` alla riga del log_event: il call
|
||||
site passa `bar_ts=r["last_data"]`.
|
||||
"""
|
||||
src = (Path(__file__).resolve().parents[1] / "scripts" / "live" / "book_execute.py").read_text()
|
||||
assert "ts_utc=str(pd.Timestamp(r['last_data']))" not in src
|
||||
assert "bar_ts=str(pd.Timestamp(r['last_data']))" in src
|
||||
assert "bar_ts=str(pd.Timestamp(bar_ts))" in src # dentro _registra_fill
|
||||
assert 'bar_ts=r["last_data"]' in src # il call site passa la barra
|
||||
assert "ts_utc=datetime.now(timezone.utc)" in src
|
||||
|
||||
Reference in New Issue
Block a user