99e58910d3
Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
4.9 KiB
Python
176 lines
4.9 KiB
Python
"""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()
|