"""模拟盘到期结算:币本位盈亏须用币,不能把 U 实值减权利金币.""" from __future__ import annotations import sqlite3 import unittest from lib.options.options_db import init_options_tables from lib.sim.broker_lib import SimBroker from lib.sim.db_lib import init_sim_tables class SimExpiryCoinPnlTests(unittest.TestCase): def test_coin_expiry_pnl_not_usd_minus_coin(self): conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row init_options_tables(conn) init_sim_tables(conn) # 251001 = 2025-10-01 08:00 UTC 已过期 inst = "BTC-USD-251001-100000-P" prem = 0.001 sheets = 1.0 ct_mult = 0.01 spot = 90000.0 conn.execute( """ INSERT INTO sim_option_positions( inst_id, side, sheets, entry_px, ct_mult, premium_paid_usdc, updated_at ) VALUES (?,?,?,?,?,?,?) """, (inst, "buy", sheets, 0.1, ct_mult, prem, "2025-09-01 10:00:00"), ) conn.execute( """ INSERT INTO options_trades( inst_id, underlying, opt_type, strike, sheets, eth_amount, premium_paid, status, premium_ccy, open_index_px, open_fee, created_at ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) """, ( inst, "BTC", "P", 100000, int(sheets), sheets * ct_mult, prem, "open", "BTC", 95000.0, 0.0, "2025-09-01 10:00:00", ), ) conn.commit() class _Proxy: def __getattr__(self, name): return getattr(conn, name) def close(self): return None broker = SimBroker(lambda: _Proxy()) # type: ignore[arg-type] broker._index_px_for_option = lambda *_a, **_k: spot # type: ignore[method-assign] # 跳过卖回(本测只核币盈亏) import lib.options.options_spot_bridge_lib as bridge bridge.sell_residual_after_option_flat = lambda *_a, **_k: {"ok": True, "skipped": True} # 2025-10-02 UTC now_ms = 1759363200000 out = broker.settle_expired_option_positions(None, now_ms=now_ms) self.assertEqual(len(out), 1) intrinsic_u = 100000.0 - spot # 10000 settle_recv = (intrinsic_u / spot) * sheets * ct_mult expected_pnl = settle_recv - prem # 旧 bug: option_expiry_pnl = intrinsic_u*sheets*ct_mult - prem ≈ 99.999 wrong_old = intrinsic_u * sheets * ct_mult - prem self.assertGreater(abs(wrong_old - expected_pnl), 1.0) row = conn.execute( "SELECT premium_received, realized_pnl, close_index_px, status FROM options_trades WHERE id=1" ).fetchone() self.assertEqual(str(row["status"]), "closed") self.assertAlmostEqual(float(row["premium_received"]), settle_recv, places=8) self.assertAlmostEqual(float(row["realized_pnl"]), expected_pnl, places=8) self.assertAlmostEqual(float(row["close_index_px"]), spot, places=2) self.assertLess(abs(float(row["realized_pnl"])), 1.0) # 币量级,绝不是 ~100 U if __name__ == "__main__": unittest.main()