修复币本位统计:开仓成本按预算/开仓指数计,不再用平仓价重估。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""期权已实现盈亏:扣手续费 + 平仓冻结 U 不跟实时指数变."""
|
||||
"""期权已实现盈亏:扣手续费 + 按开仓成本冻结 U(不跟平仓价重估开仓成本)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
@@ -11,6 +11,7 @@ from lib.options.options_pnl_settle_lib import (
|
||||
from lib.options.options_stats_lib import (
|
||||
compute_options_stats_from_history,
|
||||
history_pnl_to_usdt,
|
||||
recompute_closed_realized_pnl_usdt,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,9 +32,8 @@ class FeeConvertTests(unittest.TestCase):
|
||||
|
||||
|
||||
class SettleClosedPnlTests(unittest.TestCase):
|
||||
def test_net_of_fees_and_freeze_usdt(self):
|
||||
# 收回 0.02 ETH,已付 0.01,开+平手续费各 0.0005 → 净 0.009
|
||||
# × 3000 = 27 U
|
||||
def test_same_index_matches_net_times_index(self):
|
||||
# 开平指数相同:(recv-paid-fees)*idx
|
||||
s = settle_closed_pnl(
|
||||
premium_paid=0.01,
|
||||
premium_received=0.02,
|
||||
@@ -41,10 +41,40 @@ 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)
|
||||
self.assertAlmostEqual(float(s["close_index_px"]), 3000.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 偏亏)
|
||||
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,
|
||||
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)
|
||||
|
||||
def test_usdc_freeze_is_net(self):
|
||||
s = settle_closed_pnl(
|
||||
@@ -59,7 +89,6 @@ class SettleClosedPnlTests(unittest.TestCase):
|
||||
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 1.8, places=4)
|
||||
|
||||
def test_exchange_realized_not_fee_again(self):
|
||||
# OKX realizedPnl 已含费:只折 U,不得再减 fee
|
||||
s = settle_exchange_realized_pnl(
|
||||
realized_pnl=0.009,
|
||||
premium_ccy="ETH",
|
||||
@@ -70,67 +99,8 @@ class SettleClosedPnlTests(unittest.TestCase):
|
||||
self.assertAlmostEqual(float(s["realized_pnl_usdt"]), 27.0, places=4)
|
||||
|
||||
|
||||
class RepairDoubleFeeTests(unittest.TestCase):
|
||||
def test_repair_detects_double_fee(self):
|
||||
import sqlite3
|
||||
|
||||
from lib.options.options_db import init_options_tables
|
||||
from lib.options.options_stats_lib import repair_double_counted_fee_pnl
|
||||
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.row_factory = sqlite3.Row
|
||||
init_options_tables(conn)
|
||||
# recv-paid=0.01, fees=0.001 → 单次净 0.009;错误写成双重 0.008
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO options_trades(
|
||||
inst_id, underlying, opt_type, sheets, eth_amount,
|
||||
premium_paid, premium_received, realized_pnl, open_fee, close_fee,
|
||||
premium_ccy, status, close_index_px, realized_pnl_usdt
|
||||
) VALUES (
|
||||
'ETH-USD-260701-2500-C', 'ETH', 'C', 1, 0.01,
|
||||
0.01, 0.02, 0.008, 0.0005, 0.0005,
|
||||
'ETH', 'closed', 3000, 24.0
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
n = repair_double_counted_fee_pnl(conn, None)
|
||||
self.assertEqual(n, 1)
|
||||
row = conn.execute(
|
||||
"SELECT realized_pnl, realized_pnl_usdt FROM options_trades WHERE id=1"
|
||||
).fetchone()
|
||||
self.assertAlmostEqual(float(row["realized_pnl"]), 0.009, places=8)
|
||||
self.assertAlmostEqual(float(row["realized_pnl_usdt"]), 27.0, places=4)
|
||||
conn.close()
|
||||
|
||||
|
||||
class FrozenStatsTests(unittest.TestCase):
|
||||
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)
|
||||
stats = compute_options_stats_from_history(converted)
|
||||
self.assertAlmostEqual(float(stats["net_realized_pnl"]), 25.0, places=4)
|
||||
|
||||
def test_close_index_used_when_no_frozen_usdt(self):
|
||||
history = [
|
||||
{
|
||||
"status": "closed",
|
||||
@@ -138,6 +108,7 @@ class FrozenStatsTests(unittest.TestCase):
|
||||
"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",
|
||||
@@ -150,6 +121,42 @@ 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__":
|
||||
|
||||
Reference in New Issue
Block a user