e8f8a76753
Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
"""币本位期权浮盈折 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()
|