Treat exchange as LIVE SoT: expiry closes perp only, no invented option settles.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""LIVE 交易所 SoT:平仓数量与到期路径。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.live.reconcile import perp_close_contracts_okx, perp_close_qty_eth_binance
|
||||
|
||||
|
||||
class _FakeOkx:
|
||||
def __init__(self, sz) -> None:
|
||||
self._sz = sz
|
||||
|
||||
def get_perp_pos_sz(self, _inst, pos_side=None):
|
||||
return self._sz
|
||||
|
||||
|
||||
class _FakeBn:
|
||||
def __init__(self, sz) -> None:
|
||||
self._sz = sz
|
||||
|
||||
def get_perp_pos_sz(self, _inst, position_side=None):
|
||||
return self._sz
|
||||
|
||||
|
||||
def test_perp_close_okx_confirmed_flat_never_uses_db() -> None:
|
||||
# 交易所已空:即使 allow_db_fallback=True 也返回 0
|
||||
assert (
|
||||
perp_close_contracts_okx(
|
||||
_FakeOkx(0.0),
|
||||
perp_inst="ETH-USDT-SWAP",
|
||||
perp_side="short",
|
||||
perp_qty_eth=8.0,
|
||||
ct_val=0.01,
|
||||
allow_db_fallback=True,
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_perp_close_okx_unknown_fail_closed_by_default() -> None:
|
||||
assert (
|
||||
perp_close_contracts_okx(
|
||||
_FakeOkx(None),
|
||||
perp_inst="ETH-USDT-SWAP",
|
||||
perp_side="long",
|
||||
perp_qty_eth=8.0,
|
||||
ct_val=0.01,
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_perp_close_okx_uses_exchange_size() -> None:
|
||||
assert (
|
||||
perp_close_contracts_okx(
|
||||
_FakeOkx(123.0),
|
||||
perp_inst="ETH-USDT-SWAP",
|
||||
perp_side="long",
|
||||
perp_qty_eth=1.0,
|
||||
ct_val=0.01,
|
||||
)
|
||||
== 123
|
||||
)
|
||||
|
||||
|
||||
def test_perp_close_bn_confirmed_flat_never_uses_db() -> None:
|
||||
assert (
|
||||
perp_close_qty_eth_binance(
|
||||
_FakeBn(0.0),
|
||||
perp_inst="ETHUSDT",
|
||||
perp_side="short",
|
||||
perp_qty_eth=8.0,
|
||||
allow_db_fallback=True,
|
||||
)
|
||||
== 0.0
|
||||
)
|
||||
|
||||
|
||||
def test_matcher_live_residual_no_local_invent(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setenv("MODE", "LIVE")
|
||||
from app.models.db import Database
|
||||
from app.sim.matcher import Matcher
|
||||
|
||||
db = Database(tmp_path / "sot.db")
|
||||
m = Matcher(db)
|
||||
monkeypatch.setattr(m, "_try_exchange_flatten_residual", lambda *a, **k: None)
|
||||
row = {
|
||||
"group_id": "G1",
|
||||
"option_inst_id": "ETH-OPT",
|
||||
"option_side": "call",
|
||||
"option_qty_eth": 2.0,
|
||||
"option_qty_contracts": 200.0,
|
||||
"strike": 2000.0,
|
||||
"initial_premium": 10.0,
|
||||
}
|
||||
assert m._settle_one_residual(row, now_ms=1) is None
|
||||
db.close()
|
||||
|
||||
|
||||
def test_okx_expiry_skips_option_order(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setenv("MODE", "LIVE")
|
||||
from app.live.executor import OkxLiveExecutor
|
||||
from app.models.db import Database
|
||||
|
||||
db = Database(tmp_path / "exp.db")
|
||||
ex = OkxLiveExecutor(db)
|
||||
monkeypatch.setattr(ex, "_guard_live", lambda: None)
|
||||
with db._lock:
|
||||
db._conn.execute(
|
||||
"""UPDATE positions SET
|
||||
group_id=?, perp_side=?, perp_qty_eth=?, perp_entry_px=?,
|
||||
option_inst_id=?, option_side=?, option_qty_eth=?, option_qty_contracts=?,
|
||||
option_entry_px=?, status='open' WHERE id=1""",
|
||||
(
|
||||
"G-exp",
|
||||
"short",
|
||||
4.0,
|
||||
2000.0,
|
||||
"ETH-OPT",
|
||||
"call",
|
||||
1.0,
|
||||
100.0,
|
||||
20.0,
|
||||
),
|
||||
)
|
||||
db._conn.execute(
|
||||
"""INSERT INTO groups(group_id, status, option_inst_id, perp_inst_id, strike, open_at_ms)
|
||||
VALUES (?,?,?,?,?,?)""",
|
||||
("G-exp", "open", "ETH-OPT", "ETH-USDT-SWAP", 1900.0, 1),
|
||||
)
|
||||
db._conn.commit()
|
||||
|
||||
placed = {"opt": 0, "perp": 0}
|
||||
|
||||
class _C:
|
||||
def get_ct_val(self, *_a, **_k):
|
||||
return 0.01
|
||||
|
||||
def get_perp_pos_sz(self, *_a, **_k):
|
||||
return 400.0
|
||||
|
||||
def place_market(self, *, inst_id, side, sz, **_k):
|
||||
if "OPT" in inst_id or "-C" in inst_id or "-P" in inst_id:
|
||||
placed["opt"] += 1
|
||||
else:
|
||||
placed["perp"] += 1
|
||||
return SimpleNamespace(avg_px=2010.0, fee=0.1, sz=float(sz))
|
||||
|
||||
monkeypatch.setattr(ex, "_client", lambda: _C())
|
||||
monkeypatch.setattr(
|
||||
"app.live.executor.exchange_option_abs_size", lambda *_a, **_k: 0.0
|
||||
)
|
||||
monkeypatch.setattr(ex, "_group_strike", lambda *_a, **_k: 1900.0)
|
||||
monkeypatch.setattr(ex, "_close_spot_px", lambda *_a, **_k: 1950.0)
|
||||
monkeypatch.setattr(
|
||||
"app.live.executor.get_session",
|
||||
lambda: SimpleNamespace(snapshot=lambda: {}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.live.executor.resolve_perp_inst_id",
|
||||
lambda *_a, **_k: "ETH-USDT-SWAP",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.live.live_pnl.reconcile_closed_group_pnl",
|
||||
lambda **_k: 0.0,
|
||||
)
|
||||
|
||||
r = ex.close_group(reason="expiry", bypass_liquidity=True)
|
||||
assert r.ok, r.detail
|
||||
assert placed["opt"] == 0
|
||||
assert placed["perp"] == 1
|
||||
st = db.fetchone("SELECT status FROM positions WHERE id=1")
|
||||
assert str(st["status"]) == "flat"
|
||||
db.close()
|
||||
Reference in New Issue
Block a user