Files
crypto_okx/tests/test_options_pnl_settle.py
T

247 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""期权盈亏结算:模拟盘本地扣费;实盘以交易所历史为准并冻结折 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,
true_pnl_usdt_premium_move,
)
from lib.options.options_stats_lib import (
apply_exchange_pnl_freeze,
compute_options_stats_from_history,
history_pnl_to_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,
)
class PremiumMoveFormulaTests(unittest.TestCase):
def test_user_formula_matches_proceeds_minus_open_cost(self):
coin = 0.01
paid = 0.01
open_px = 90000.0
close_px = 80000.0
a = true_pnl_usdt_premium_move(
coin_pnl=coin,
premium_paid=paid,
open_index_px=open_px,
sell_back_px=close_px,
)
# 历史盈亏×卖回 − (权利金×开 − 权利金×平)
expect = coin * close_px - (paid * open_px - paid * close_px)
self.assertAlmostEqual(float(a), expect, places=4)
# 无费时 ≡ 收回×平 − 已付×开 (recv=paid+coin)
recv = paid + coin
self.assertAlmostEqual(float(a), recv * close_px - paid * open_px, places=4)
class SettleClosedPnlTests(unittest.TestCase):
def test_sim_net_times_close_index(self):
# 无开仓指数时回退净币×平仓价
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,
)
self.assertAlmostEqual(float(s["realized_pnl"]), 0.009, places=8)
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4)
def test_sim_proceeds_minus_open_premium_usdt(self):
"""盈利=(收回币)×卖回价 − 已付币×开仓指数 − 手续费U."""
s = settle_closed_pnl(
premium_paid=0.011005,
premium_received=0.02270864,
open_fee=0,
close_fee=0,
premium_ccy="BTC",
index_px=81000, # 卖回价
open_index_px=86000, # 初始 U 权利金用开仓指数
)
# 0.02270864*81000 - 0.011005*86000
expect = 0.02270864 * 81000 - 0.011005 * 86000
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), expect, places=2)
def test_sim_loss_remaining_minus_fees(self):
"""亏损:剩余权利金U − 手续费U − 初始U(同一式)."""
s = settle_closed_pnl(
premium_paid=0.01,
premium_received=0.004,
open_fee=0.0001,
close_fee=0.0001,
premium_ccy="ETH",
index_px=2500,
open_index_px=3000,
)
# 0.004*2500 - 0.01*3000 - (0.0001*3000+0.0001*2500) = 10 - 30 - 0.55 = -20.55
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), -20.55, places=2)
def test_budget_ignored_for_usdt(self):
# budget 不得抬高开仓成本
s = settle_closed_pnl(
premium_paid=0.01,
premium_received=0.02,
open_fee=0,
close_fee=0,
premium_ccy="ETH",
index_px=3000,
budget_usdt=26.0,
)
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 30.0, 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 SimPremiumPnlStatsTests(unittest.TestCase):
def test_sync_sim_no_longer_overwrites_account_settled(self):
"""账户结算后 sync 兼容入口不得改写."""
import sqlite3
from lib.options.options_db import init_options_tables
from lib.options.options_stats_lib import sync_sim_cash_realized_pnl_usdt
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
init_options_tables(conn)
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, premium_received, realized_pnl, premium_ccy,
status, trading_usdt_before, trading_usdt_after,
realized_pnl_usdt, pnl_settled, created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.02, 0.01, 'ETH',
'closed', 1000.0, 1952.96,
952.96, 1, '2026-10-06 10:00:00', '2026-10-08 14:00:00'
)
"""
)
conn.commit()
n = sync_sim_cash_realized_pnl_usdt(conn, None)
self.assertEqual(n, 0)
row = conn.execute(
"SELECT realized_pnl_usdt FROM options_trades WHERE id=1"
).fetchone()
self.assertAlmostEqual(float(row["realized_pnl_usdt"]), 952.96, places=2)
conn.close()
class ExchangeFreezeStatsTests(unittest.TestCase):
def test_freeze_uses_exchange_coin_pnl(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)
class _Ex:
def public_get_market_ticker(self, params):
inst = (params or {}).get("instId") or ""
if inst.startswith("BTC"):
return {"data": [{"last": "100000"}]}
return {"data": [{"last": "2500"}]}
history = [
{
"status": "closed",
"source": "exchange",
"history_key": "ex|BTC-USD-261009-84000-P|1",
"inst_id": "BTC-USD-261009-84000-P",
"underlying": "BTC",
"premium_ccy": "BTC",
"realized_pnl": 0.000138,
"created_at": "2026-10-08 10:00:00",
"closed_at": "2026-10-08 22:22:56",
},
{
"status": "closed",
"source": "exchange",
"history_key": "ex|ETH-USD-261006-2700-P|2",
"inst_id": "ETH-USD-261006-2700-P",
"underlying": "ETH",
"premium_ccy": "ETH",
"realized_pnl": -0.00648,
"created_at": "2026-10-05 10:00:00",
"closed_at": "2026-10-06 16:00:27",
},
]
frozen = apply_exchange_pnl_freeze(conn, history, _Ex())
converted = history_pnl_to_usdt(frozen, _Ex())
# 0.000138*100000=13.8; -0.00648*2500=-16.2; net=-2.4
self.assertAlmostEqual(float(converted[0]["realized_pnl"]), 13.8, places=4)
self.assertAlmostEqual(float(converted[1]["realized_pnl"]), -16.2, places=4)
stats = compute_options_stats_from_history(converted)
self.assertAlmostEqual(float(stats["net_realized_pnl"]), -2.4, places=4)
# 指数再变也不改冻结 U
class _Ex2:
def public_get_market_ticker(self, params):
return {"data": [{"last": "999999"}]}
frozen2 = apply_exchange_pnl_freeze(conn, history, _Ex2())
converted2 = history_pnl_to_usdt(frozen2, _Ex2())
self.assertAlmostEqual(float(converted2[0]["realized_pnl"]), 13.8, places=4)
conn.close()
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)
if __name__ == "__main__":
unittest.main()