"""期权已实现盈亏:扣手续费 + 按开仓成本冻结 U(不跟平仓价重估开仓成本).""" from __future__ import annotations import unittest from lib.options.options_pnl_settle_lib import ( fee_to_premium_ccy, settle_closed_pnl, settle_exchange_realized_pnl, ) from lib.options.options_stats_lib import ( compute_options_stats_from_history, history_pnl_to_usdt, recompute_closed_realized_pnl_usdt, ) class FeeConvertTests(unittest.TestCase): def test_fee_same_ccy(self): self.assertAlmostEqual( fee_to_premium_ccy(0.001, "ETH", premium_ccy="ETH", index_px=3000), 0.001, places=8, ) def test_fee_usdt_to_eth(self): self.assertAlmostEqual( fee_to_premium_ccy(3.0, "USDT", premium_ccy="ETH", index_px=3000), 0.001, places=8, ) class SettleClosedPnlTests(unittest.TestCase): def test_same_index_matches_net_times_index(self): # 开平指数相同:(recv-paid-fees)*idx s = settle_closed_pnl( premium_paid=0.01, premium_received=0.02, open_fee=0.0005, close_fee=0.0005, premium_ccy="ETH", index_px=3000, open_index_px=3000, ) self.assertAlmostEqual(float(s["realized_pnl"]), 0.009, places=8) self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4) def test_open_cost_not_revalued_at_close(self): # 开仓 2500、平仓 3000:不得把已付也按 3000 计 # exit=0.02*3000=60, entry=0.01*2500=25, fee=0.0005*2500+0.0005*3000=2.75 # usdt=60-25-2.75=32.25 (旧口径 0.009*3000=27 偏亏) s = settle_closed_pnl( premium_paid=0.01, premium_received=0.02, open_fee=0.0005, close_fee=0.0005, premium_ccy="ETH", index_px=3000, open_index_px=2500, ) self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 32.25, places=4) def test_budget_usdt_as_entry(self): # budget=26 作为真实开仓 USDT 成本;ETH 开仓费不再重复扣 s = settle_closed_pnl( premium_paid=0.01, premium_received=0.02, open_fee=0.0005, close_fee=0.0005, premium_ccy="ETH", index_px=3000, budget_usdt=26.0, fee_ccy="ETH", ) # exit=60, entry=26, close_fee_u=1.5 → 32.5 self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 32.5, places=4) def test_usdc_freeze_is_net(self): s = settle_closed_pnl( premium_paid=10, premium_received=12, open_fee=0.1, close_fee=0.1, premium_ccy="USDC", index_px=None, ) self.assertAlmostEqual(float(s["realized_pnl"]), 1.8, places=4) self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 1.8, places=4) def test_exchange_realized_not_fee_again(self): s = settle_exchange_realized_pnl( realized_pnl=0.009, premium_ccy="ETH", index_px=3000, fee_hint=0.001, ) self.assertAlmostEqual(float(s["realized_pnl"]), 0.009, places=8) self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4) class FrozenStatsTests(unittest.TestCase): def test_frozen_usdt_ignores_live_index(self): history = [ { "status": "closed", "inst_id": "ETH-USD-260701-2500-C", "underlying": "ETH", "premium_ccy": "ETH", "realized_pnl": 0.01, "realized_pnl_usdt": 25.0, "close_index_px": 2500.0, "created_at": "2026-10-01 10:00:00", "closed_at": "2026-10-01 12:00:00", } ] class _Ex: def public_get_market_ticker(self, params): return {"data": [{"last": "99999"}]} converted = history_pnl_to_usdt(history, _Ex()) self.assertAlmostEqual(float(converted[0]["realized_pnl"]), 25.0, places=4) stats = compute_options_stats_from_history(converted) self.assertAlmostEqual(float(stats["net_realized_pnl"]), 25.0, places=4) class RecomputeClosedUsdtTests(unittest.TestCase): def test_recompute_uses_budget_not_close_revalue(self): import sqlite3 from lib.options.options_db import init_options_tables conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row init_options_tables(conn) # 旧错误冻结: (0.02-0.01)*3000=30 conn.execute( """ INSERT INTO options_trades( inst_id, underlying, opt_type, sheets, eth_amount, premium_paid, premium_received, realized_pnl, premium_ccy, status, budget_usdt, close_index_px, realized_pnl_usdt ) VALUES ( 'ETH-USD-260701-2500-C', 'ETH', 'C', 1, 0.01, 0.01, 0.02, 0.01, 'ETH', 'closed', 26.0, 3000, 30.0 ) """ ) conn.commit() n = recompute_closed_realized_pnl_usdt(conn, None) self.assertEqual(n, 1) row = conn.execute( "SELECT realized_pnl_usdt FROM options_trades WHERE id=1" ).fetchone() # exit 60 - budget 26 = 34 (无手续费列) self.assertAlmostEqual(float(row["realized_pnl_usdt"]), 34.0, places=4) conn.close() if __name__ == "__main__": unittest.main()