Harden residual mid-close: IOC partial fills, exchange reconcile, atomic book.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-02 14:41:00 +08:00
parent 640ecc9530
commit 469e7a258a
8 changed files with 729 additions and 238 deletions
@@ -134,6 +134,61 @@ def test_residual_liquidity_fail_skips(tmp_path, monkeypatch) -> None:
db.close()
def test_residual_recheck_bid_drop_skips(tmp_path, monkeypatch) -> None:
monkeypatch.setenv("MODE", "SIM")
db = Database(tmp_path / "recheck.db")
db.set_setting("residual_min_premium_pct", "20")
_seed_residual(db, initial_premium=100.0, qty=2.0)
m = Matcher(db)
good = SimpleNamespace(bid=15.0, ask=15.5, bid_sz=10_000.0, mark_px=15.0)
bad = SimpleNamespace(bid=5.0, ask=5.5, bid_sz=10_000.0, mark_px=5.0)
quotes = iter([good, bad])
monkeypatch.setattr(m, "_quote_held_option", lambda _id: next(quotes))
monkeypatch.setattr(m, "_close_spot_px", lambda _snap: 1900.0)
monkeypatch.setattr(m, "_ct_mult", lambda _id: 0.01)
assert m.try_close_one_residual(m.list_residual_options()[0]) is None
row = db.fetchone(
"SELECT status FROM residual_options WHERE group_id=?", ("G-res",)
)
assert row is not None and row["status"] == "pending"
db.close()
def test_residual_book_pending_guard(tmp_path, monkeypatch) -> None:
monkeypatch.setenv("MODE", "SIM")
db = Database(tmp_path / "guard.db")
_seed_residual(db, initial_premium=100.0, qty=2.0)
m = Matcher(db)
row = m.list_residual_options()[0]
first = m._book_residual_market_close(
row,
fill_px=15.0,
fee=0.01,
notional=30.0,
slip=0.0,
now_ms=1_700_000_100_000,
note="first",
filled_contracts=200.0,
remaining_contracts=0.0,
)
assert first is not None and first.get("fully_done") is True
second = m._book_residual_market_close(
row,
fill_px=15.0,
fee=0.01,
notional=30.0,
slip=0.0,
now_ms=1_700_000_200_000,
note="second",
filled_contracts=200.0,
remaining_contracts=0.0,
)
assert second is None
db.close()
def test_settings_exposes_residual_min_premium_pct(tmp_path, monkeypatch) -> None:
monkeypatch.setenv("MODE", "SIM")
from app.api import settings as settings_api