实盘数据统计改用期权历史交易所盈亏;模拟盘仍走本地结算。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-08 23:20:16 +08:00
parent d05629c362
commit 7943cd2167
6 changed files with 222 additions and 139 deletions
+67 -71
View File
@@ -1,4 +1,4 @@
"""期权已实现盈亏:扣手续费 + 按开仓成本冻结 U(不跟平仓价重估开仓成本)."""
"""期权盈亏结算:模拟盘本地扣费;实盘以交易所历史为准并冻结折 U."""
from __future__ import annotations
import unittest
@@ -9,9 +9,9 @@ from lib.options.options_pnl_settle_lib import (
settle_exchange_realized_pnl,
)
from lib.options.options_stats_lib import (
apply_exchange_pnl_freeze,
compute_options_stats_from_history,
history_pnl_to_usdt,
recompute_closed_realized_pnl_usdt,
)
@@ -23,17 +23,9 @@ class FeeConvertTests(unittest.TestCase):
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
def test_sim_net_times_close_index(self):
s = settle_closed_pnl(
premium_paid=0.01,
premium_received=0.02,
@@ -41,40 +33,22 @@ class SettleClosedPnlTests(unittest.TestCase):
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 偏亏)
def test_budget_ignored_for_usdt(self):
# budget 不得抬高开仓成本
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,
open_fee=0,
close_fee=0,
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)
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 30.0, places=4)
def test_usdc_freeze_is_net(self):
s = settle_closed_pnl(
@@ -99,7 +73,65 @@ class SettleClosedPnlTests(unittest.TestCase):
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4)
class FrozenStatsTests(unittest.TestCase):
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 = [
{
@@ -121,42 +153,6 @@ class FrozenStatsTests(unittest.TestCase):
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__":