Files
crypto_okx/tests/test_options_account_pnl.py
T

241 lines
9.0 KiB
Python

"""交易账户快照盈亏结算."""
from __future__ import annotations
import sqlite3
import unittest
class AccountPnlSettleTests(unittest.TestCase):
def test_settle_after_minus_before(self):
from lib.options.options_account_pnl_lib import (
settle_closed_trades_from_trading_account,
)
from lib.options.options_db import init_options_tables
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, status, bridge_id, trading_usdt_before, pnl_settled,
created_at, closed_at
) VALUES (
'BTC-USD-261008-86500-P', 'BTC', 'P', 1, 0.01,
0.01, 'closed', 7, 1000.0, 0,
'2026-10-05 05:11:30', '2026-10-07 10:03:53'
)
"""
)
conn.commit()
n = settle_closed_trades_from_trading_account(
conn, trading_usdt_after=1947.05, bridge_id=7
)
self.assertEqual(n, 1)
row = conn.execute(
"SELECT trading_usdt_after, realized_pnl_usdt, pnl_settled FROM options_trades WHERE id=1"
).fetchone()
self.assertAlmostEqual(float(row["trading_usdt_after"]), 1947.05, places=2)
self.assertAlmostEqual(float(row["realized_pnl_usdt"]), 947.05, places=2)
self.assertEqual(int(row["pnl_settled"]), 1)
# 已冻结不再改
n2 = settle_closed_trades_from_trading_account(
conn, trading_usdt_after=9999.0, bridge_id=7
)
self.assertEqual(n2, 0)
row2 = conn.execute(
"SELECT realized_pnl_usdt FROM options_trades WHERE id=1"
).fetchone()
self.assertAlmostEqual(float(row2["realized_pnl_usdt"]), 947.05, places=2)
conn.close()
def test_stats_use_coin_times_sell_px_not_account(self):
"""口径 B:有币盈亏即计入,U=币×卖回价;不读账户差分."""
from lib.options.options_db import init_options_tables
from lib.options.options_stats_lib import (
compute_options_stats_from_history,
history_pnl_to_usdt_stats,
load_local_options_stats_history,
)
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
init_options_tables(conn)
# 账户未结算但有币盈亏+卖回价 → 进统计 B
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, premium_received, realized_pnl, premium_ccy,
close_index_px, status, trading_usdt_before, pnl_settled,
created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.02, 0.01, 'ETH',
3000.0, 'closed', 500.0, 0,
'2026-10-06 10:00:00', '2026-10-08 14:00:00'
)
"""
)
# 账户冻结为负、币盈亏为正 → 统计仍用币×价
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, premium_received, realized_pnl, premium_ccy,
close_index_px, status, trading_usdt_before, trading_usdt_after,
realized_pnl_usdt, pnl_settled, created_at, closed_at
) VALUES (
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
0.01, 0.025, 0.015, 'BTC',
100000.0, 'closed', 1000.0, 948.82,
-51.18, 1, '2026-10-01 10:00:00', '2026-10-02 14:00:00'
)
"""
)
conn.commit()
hist = load_local_options_stats_history(conn, None)
stats = compute_options_stats_from_history(history_pnl_to_usdt_stats(hist, None))
self.assertEqual(int(stats["total_closed"]), 2)
# 0.01*3000 + 0.015*100000 = 30 + 1500 = 1530
self.assertAlmostEqual(float(stats["net_realized_pnl"]), 1530.0, places=2)
conn.close()
def test_header_stats_count_coin_pnl_rows(self):
from datetime import datetime, timezone
from lib.options.options_db import init_options_tables
from lib.options.options_stats_lib import header_options_stats_for_window
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, realized_pnl, premium_ccy, close_index_px, status,
trading_usdt_before, pnl_settled, created_at, closed_at
) VALUES (
'ETH-USD-261009-2500-P', 'ETH', 'P', 1, 0.01,
0.01, 0.01, 'ETH', 3000.0, 'closed',
500.0, 0, '2026-10-08 10:00:00', '2026-10-08 12:00:00'
)
"""
)
conn.execute(
"""
INSERT INTO options_trades(
inst_id, underlying, opt_type, sheets, eth_amount,
premium_paid, realized_pnl, premium_ccy, close_index_px, 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.01, 'ETH', 3000.0, 'closed',
1000.0, 1100.0, 100.0,
1, '2026-10-08 08:00:00', '2026-10-08 09:00:00'
)
"""
)
conn.commit()
win = {
"start_utc": datetime(2026, 10, 1, tzinfo=timezone.utc),
"end_utc": datetime(2026, 10, 31, 23, 59, 59, tzinfo=timezone.utc),
}
out = header_options_stats_for_window(conn, win, timezone.utc, None)
self.assertEqual(int(out["total"]), 2)
conn.close()
def test_resolve_true_pnl_prefers_account_then_bridge(self):
from lib.options.options_account_pnl_lib import resolve_true_pnl_usdt
from lib.options.options_db import init_options_tables
from lib.options.options_spot_bridge_lib import ensure_bridge_table
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
init_options_tables(conn)
ensure_bridge_table(conn)
conn.execute(
"""
INSERT INTO options_spot_bridge(
id, underlying, status, budget_usdt, usdt_recovered
) VALUES (3, 'ETH', 'closed', 100.0, 160.0)
"""
)
conn.commit()
pnl, src = resolve_true_pnl_usdt(
conn,
trading_usdt_before=500.0,
trading_usdt_after=580.0,
bridge_id=3,
)
self.assertEqual(src, "account")
self.assertAlmostEqual(float(pnl), 80.0, places=4)
pnl2, src2 = resolve_true_pnl_usdt(
conn,
trading_usdt_before=500.0,
trading_usdt_after=None,
bridge_id=3,
)
self.assertEqual(src2, "bridge")
self.assertAlmostEqual(float(pnl2), 60.0, places=4)
pnl3, src3 = resolve_true_pnl_usdt(
conn,
trading_usdt_before=None,
realized_pnl_usdt=999.0,
pnl_settled=1,
)
self.assertEqual(src3, "none")
self.assertIsNone(pnl3)
conn.close()
def test_settle_blocked_when_sellable_coin(self):
from lib.options.options_account_pnl_lib import try_settle_after_sellback
from lib.options.options_db import init_options_tables
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, status, bridge_id, trading_usdt_before, pnl_settled,
created_at, closed_at
) VALUES (
'BTC-USD-261015-84000-P', 'BTC', 'P', 1, 0.01,
0.01, 'closed', 7, 1000.0, 0,
'2026-10-12 01:46:50', '2026-10-13 08:07:35'
)
"""
)
conn.commit()
class _Ex:
pass
# monkey: 仍有可卖 BTC
import lib.options.options_account_pnl_lib as mod
old_block = mod.sellable_coin_blocks_settle
mod.sellable_coin_blocks_settle = lambda ex, uly: "仍有可卖 BTC"
try:
n = try_settle_after_sellback(
conn, _Ex(), bridge_id=7, sell_complete=True
)
self.assertEqual(n, 0)
row = conn.execute(
"SELECT pnl_settled FROM options_trades WHERE id=1"
).fetchone()
self.assertEqual(int(row["pnl_settled"]), 0)
finally:
mod.sellable_coin_blocks_settle = old_block
conn.close()
if __name__ == "__main__":
unittest.main()