"""币本位期权浮盈折 U(BTC/ETH 不得把币数量直接当 U).""" from __future__ import annotations import unittest from lib.options.options_positions_lib import display_pnl_usdt_from_option_row class DisplayPnlUsdtTests(unittest.TestCase): def test_btc_coin_pnl_times_index(self): row = { "inst_id": "BTC-USD-260327-100000-P", "premium_ccy": "BTC", "margin_mode": "coin", "idx_px": 100000.0, "upl": -0.001, "close_preview": {"bid_invalid": True}, } # 残档回退 upl: -0.001 BTC × 100000 = -100 U self.assertAlmostEqual(display_pnl_usdt_from_option_row(row), -100.0, places=4) def test_btc_infers_ccy_when_premium_ccy_missing(self): row = { "inst_id": "BTC-USD-260327-100000-C", "margin_mode": "coin", "underlying": "BTC", "idx_px": 95000.0, "upl": 0.002, "close_preview": {"bid_invalid": True}, } self.assertAlmostEqual(display_pnl_usdt_from_option_row(row), 190.0, places=4) def test_usdc_unchanged(self): row = { "inst_id": "BTC-USD_UM-260327-100000-C", "premium_ccy": "USDC", "margin_mode": "usdc", "upl": -12.5, "close_preview": {"bid_invalid": True}, } self.assertAlmostEqual(display_pnl_usdt_from_option_row(row), -12.5, places=4) class HeaderPlRatioUsdtTests(unittest.TestCase): """顶栏盈亏比须先把币数量折 U,避免 ETH/BTC 混算失真.""" def test_mixed_coin_pnl_ratio_after_usdt(self): from lib.options.options_stats_lib import ( compute_options_stats_from_history, history_pnl_to_usdt, ) # 赢 0.01 ETH、亏 0.0001 BTC;不折 U 时盈亏比≈0.01/0.0001=100 或反过来极小 # 按指数折 U: +0.01*3000=30U, -0.0001*100000=-10U → 盈亏比=3 history = [ { "status": "closed", "inst_id": "ETH-USD-260701-2500-C", "underlying": "ETH", "premium_ccy": "ETH", "realized_pnl": 0.01, "created_at": "2026-10-01 10:00:00", "closed_at": "2026-10-01 12:00:00", }, { "status": "closed", "inst_id": "BTC-USD-260701-100000-P", "underlying": "BTC", "premium_ccy": "BTC", "realized_pnl": -0.0001, "created_at": "2026-10-01 11:00:00", "closed_at": "2026-10-01 13:00:00", }, ] class _Ex: def public_get_market_ticker(self, params): inst = (params or {}).get("instId") or "" if inst.startswith("ETH"): return {"data": [{"last": "3000"}]} if inst.startswith("BTC"): return {"data": [{"last": "100000"}]} return {"data": []} converted = history_pnl_to_usdt(history, _Ex()) self.assertAlmostEqual(float(converted[0]["realized_pnl"]), 30.0, places=4) self.assertAlmostEqual(float(converted[1]["realized_pnl"]), -10.0, places=4) stats = compute_options_stats_from_history(converted) self.assertEqual(stats["win_count"], 1) self.assertEqual(stats["loss_count"], 1) self.assertAlmostEqual(float(stats["profit_loss_ratio"]), 3.0, places=2) def test_raw_coin_mix_would_distort_without_convert(self): from lib.options.options_stats_lib import compute_options_stats_from_history raw = [ { "status": "closed", "realized_pnl": 0.01, "created_at": "2026-10-01 10:00:00", "closed_at": "2026-10-01 12:00:00", }, { "status": "closed", "realized_pnl": -0.0001, "created_at": "2026-10-01 11:00:00", "closed_at": "2026-10-01 13:00:00", }, ] stats = compute_options_stats_from_history(raw) # 未折 U: 0.01 / 0.0001 = 100, 与折 U 后的 3 截然不同 self.assertAlmostEqual(float(stats["profit_loss_ratio"]), 100.0, places=2) if __name__ == "__main__": unittest.main()