Files
eth_hedge_sim/backend/tests/test_exchange_sot_close.py

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()