Deduct round-trip taker fees from estimated perp PnL.

Unify hub/instance TP profit, calc_pnl, and push/accounting to net of 0.05% per side; leave exchange floating PnL unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 15:12:52 +08:00
parent d5e6132b13
commit 2269bc51ff
13 changed files with 307 additions and 20 deletions
+2 -1
View File
@@ -88,7 +88,8 @@ class HubCalculatorLibTests(unittest.TestCase):
self.assertEqual(data["first_contracts"], 10.0)
self.assertEqual(len(data["rows"]), 1)
self.assertEqual(data["rows"][0]["loss_at_sl_u"], 50.0)
self.assertEqual(data["rows"][0]["profit_at_tp_u"], 200.0)
# 毛利 200 双边费 (1000+1200)*0.0005=1.1 → 198.9
self.assertEqual(data["rows"][0]["profit_at_tp_u"], 198.9)
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
def test_roll_calculator_chain_two_legs(self, _mock):
+2 -1
View File
@@ -136,7 +136,8 @@ def test_apply_order_price_display_fields_gate_contract_size():
avg_entry_price=62063.4,
)
assert payload["reward_at_tp_usdt"] is not None
assert abs(payload["reward_at_tp_usdt"] - 6.73) < 0.1
# 毛利约 6.73, 扣双边 0.05% 后约 6.25
assert abs(payload["reward_at_tp_usdt"] - 6.25) < 0.1
def test_calc_latest_risk_amount_long():
+2 -1
View File
@@ -43,4 +43,5 @@ def test_infer_initial_position_from_live():
def test_reward_at_tp_long():
assert roll_ui.reward_at_tp_usdt("long", 100.0, 110.0, 2.0) == 20.0
# 毛利 20, 双边费 (200+220)*0.0005=0.21 → 净 19.79
assert abs(roll_ui.reward_at_tp_usdt("long", 100.0, 110.0, 2.0) - 19.79) < 1e-6
+48
View File
@@ -0,0 +1,48 @@
"""永续固定费率净盈亏."""
from __future__ import annotations
import os
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from lib.trade.trade_fee_lib import ( # noqa: E402
estimate_roundtrip_fee_usdt,
net_pnl_after_fee,
notional_usdt,
taker_fee_rate,
)
class TestTradeFeeLib(unittest.TestCase):
def test_default_rate(self):
os.environ.pop("PERP_TAKER_FEE_RATE", None)
self.assertAlmostEqual(taker_fee_rate(), 0.0005)
def test_notional(self):
self.assertAlmostEqual(notional_usdt(100, 2, 1.0), 200.0)
self.assertAlmostEqual(notional_usdt(62000, 78, 0.0001), 483.6, places=2)
def test_roundtrip_fee_qty(self):
# 开 100*2=200, 平 110*2=220, 费=(200+220)*0.0005=0.21
fee = estimate_roundtrip_fee_usdt(100, 110, 2.0, 1.0, rate=0.0005)
self.assertAlmostEqual(fee, 0.21, places=6)
def test_net_long_matches_checklist(self):
# 毛利 20, 费 0.21 → 净 19.79
net = net_pnl_after_fee(20.0, 100, 110, 2.0, 1.0, rate=0.0005)
self.assertAlmostEqual(net, 19.79, places=4)
def test_open_notional_fallback(self):
# 无张数:开名义 1000, 出场 110/100 → 平 1100, 费=1.05
fee = estimate_roundtrip_fee_usdt(100, 110, open_notional=1000, rate=0.0005)
self.assertAlmostEqual(fee, 1.05, places=6)
net = net_pnl_after_fee(50.0, 100, 110, open_notional=1000, rate=0.0005)
self.assertAlmostEqual(net, 48.95, places=4)
if __name__ == "__main__":
unittest.main()