Count exit fees in live/SIM net PnL as entry fee x2.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-01 09:27:41 +08:00
parent dffcc4eeb9
commit 22c42a19a0
3 changed files with 20 additions and 28 deletions
+5 -21
View File
@@ -37,8 +37,8 @@ def enrich_live_unrealized(
) -> dict[str, Any]:
"""
在 Matcher.unrealized 结果上覆盖 LIVE 口径:
net = 永续交易所UPL + 期权净盈亏(本地) − 已付手续费 + 资金费(signed)
不再扣预估平仓费
net = 永续交易所UPL + 期权净盈亏(本地) − 入场手续费×2 + 资金费(signed)
离场手续费按入场手续费估算(开+平 ≈ 已付×2)
"""
if not base.get("has_position"):
return base
@@ -72,25 +72,9 @@ def enrich_live_unrealized(
logger.warning("live unrealized exchange overlay failed: %s", e)
option_upl = float(base.get("option_upl") or 0.0) # 期权净盈亏(本地)
# LIVE 达标口径:必须扣预估平仓手续费(与 SIM / 策略说明一致)
try:
from ..config import get_settings
fr = float(get_settings().fee_rate or 0.0005)
except Exception:
fr = 0.0005
notional_est = abs(float(base.get("perp_notional") or 0.0)) + abs(
float(base.get("option_notional") or 0.0)
)
if notional_est <= 0:
# 兜底:用标记价粗算
notional_est = abs(float(base.get("spot") or 0.0)) * (
abs(float(base.get("perp_qty_eth") or 0.0))
+ abs(float(base.get("option_qty_eth") or 0.0))
)
est_close = max(0.0, notional_est * fr * 2.0)
# 资金费 signed:付出为负;再减估平仓费
net_pnl = perp_upl + option_upl - fees_paid + funding - est_close
# 盯盘/达标:离场费 ≈ 入场费 → 合计扣 已付×2
est_close = float(fees_paid)
net_pnl = perp_upl + option_upl - fees_paid * 2.0 + funding
out = dict(base)
out["perp_upl"] = perp_upl
+10 -3
View File
@@ -1055,9 +1055,7 @@ class Matcher:
if opt_mark is not None:
option_upl = float(opt_mark) * opt_qty - initial_premium
est_close_fees = est_perp_close_fee + est_opt_close_fee
# 净盈利:永续浮盈 + 期权浮盈 − 预估平仓手续费
net_pnl = perp_upl + option_upl - est_close_fees
book_close_fees = est_perp_close_fee + est_opt_close_fee
entry_idx = float(pos["entry_index_px"] or 0)
move = abs(float(index_px) - entry_idx) if index_px is not None and entry_idx else 0.0
@@ -1081,6 +1079,14 @@ class Matcher:
strike = float(g["strike"]) if g and g["strike"] is not None else None
expiry_ymd = str(g["expiry_ymd"]) if g and g["expiry_ymd"] else None
open_at_ms = int(g["open_at_ms"]) if g and g["open_at_ms"] else None
open_fees = abs(float(g["fees"] or 0)) if g else 0.0
# 净盈利:浮盈 − 入场手续费×2(离场费按入场估算);无入场费时退回盘口估平仓费
if open_fees > 1e-12:
est_close_fees = open_fees
net_pnl = perp_upl + option_upl - open_fees * 2.0
else:
est_close_fees = book_close_fees
net_pnl = perp_upl + option_upl - est_close_fees
expiry_ms = None
if expiry_ymd and len(expiry_ymd) == 6:
try:
@@ -1120,6 +1126,7 @@ class Matcher:
"expiry_ms": expiry_ms,
"perp_upl": perp_upl,
"option_upl": option_upl,
"fees_paid": open_fees,
"est_close_fees": est_close_fees,
"net_pnl": net_pnl,
"index_px": index_px,
+5 -4
View File
@@ -45,7 +45,7 @@ class _FakeClient:
return -1.5
def test_enrich_live_unrealized_no_est_close_fee() -> None:
def test_enrich_live_unrealized_entry_fee_times_two() -> None:
base = {
"has_position": True,
"group_id": "G1",
@@ -73,7 +73,8 @@ def test_enrich_live_unrealized_no_est_close_fee() -> None:
assert out["option_upl"] == 5.0
assert out["fees_paid"] == 0.5
assert out["funding_usdt"] == -1.5
assert out["est_close_fees"] == 0.0
# 8 + 5 - 0.5 + (-1.5) = 11
assert abs(out["net_pnl"] - 11.0) < 1e-9
# 离场费按入场估算
assert out["est_close_fees"] == 0.5
# 8 + 5 - 0.5*2 + (-1.5) = 10.5
assert abs(out["net_pnl"] - 10.5) < 1e-9
assert out["pnl_source"] == "live_exchange"