Count exit fees in live/SIM net PnL as entry fee x2.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,8 +37,8 @@ def enrich_live_unrealized(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
在 Matcher.unrealized 结果上覆盖 LIVE 口径:
|
在 Matcher.unrealized 结果上覆盖 LIVE 口径:
|
||||||
net = 永续交易所UPL + 期权净盈亏(本地) − 已付手续费 + 资金费(signed)
|
net = 永续交易所UPL + 期权净盈亏(本地) − 入场手续费×2 + 资金费(signed)
|
||||||
不再扣预估平仓费。
|
离场手续费按入场手续费估算(开+平 ≈ 已付×2)。
|
||||||
"""
|
"""
|
||||||
if not base.get("has_position"):
|
if not base.get("has_position"):
|
||||||
return base
|
return base
|
||||||
@@ -72,25 +72,9 @@ def enrich_live_unrealized(
|
|||||||
logger.warning("live unrealized exchange overlay failed: %s", e)
|
logger.warning("live unrealized exchange overlay failed: %s", e)
|
||||||
|
|
||||||
option_upl = float(base.get("option_upl") or 0.0) # 期权净盈亏(本地)
|
option_upl = float(base.get("option_upl") or 0.0) # 期权净盈亏(本地)
|
||||||
# LIVE 达标口径:必须扣预估平仓手续费(与 SIM / 策略说明一致)
|
# 盯盘/达标:离场费 ≈ 入场费 → 合计扣 已付×2
|
||||||
try:
|
est_close = float(fees_paid)
|
||||||
from ..config import get_settings
|
net_pnl = perp_upl + option_upl - fees_paid * 2.0 + funding
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
out = dict(base)
|
out = dict(base)
|
||||||
out["perp_upl"] = perp_upl
|
out["perp_upl"] = perp_upl
|
||||||
|
|||||||
@@ -1055,9 +1055,7 @@ class Matcher:
|
|||||||
if opt_mark is not None:
|
if opt_mark is not None:
|
||||||
option_upl = float(opt_mark) * opt_qty - initial_premium
|
option_upl = float(opt_mark) * opt_qty - initial_premium
|
||||||
|
|
||||||
est_close_fees = est_perp_close_fee + est_opt_close_fee
|
book_close_fees = est_perp_close_fee + est_opt_close_fee
|
||||||
# 净盈利:永续浮盈 + 期权浮盈 − 预估平仓手续费
|
|
||||||
net_pnl = perp_upl + option_upl - est_close_fees
|
|
||||||
|
|
||||||
entry_idx = float(pos["entry_index_px"] or 0)
|
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
|
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
|
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
|
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_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
|
expiry_ms = None
|
||||||
if expiry_ymd and len(expiry_ymd) == 6:
|
if expiry_ymd and len(expiry_ymd) == 6:
|
||||||
try:
|
try:
|
||||||
@@ -1120,6 +1126,7 @@ class Matcher:
|
|||||||
"expiry_ms": expiry_ms,
|
"expiry_ms": expiry_ms,
|
||||||
"perp_upl": perp_upl,
|
"perp_upl": perp_upl,
|
||||||
"option_upl": option_upl,
|
"option_upl": option_upl,
|
||||||
|
"fees_paid": open_fees,
|
||||||
"est_close_fees": est_close_fees,
|
"est_close_fees": est_close_fees,
|
||||||
"net_pnl": net_pnl,
|
"net_pnl": net_pnl,
|
||||||
"index_px": index_px,
|
"index_px": index_px,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class _FakeClient:
|
|||||||
return -1.5
|
return -1.5
|
||||||
|
|
||||||
|
|
||||||
def test_enrich_live_unrealized_no_est_close_fee() -> None:
|
def test_enrich_live_unrealized_entry_fee_times_two() -> None:
|
||||||
base = {
|
base = {
|
||||||
"has_position": True,
|
"has_position": True,
|
||||||
"group_id": "G1",
|
"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["option_upl"] == 5.0
|
||||||
assert out["fees_paid"] == 0.5
|
assert out["fees_paid"] == 0.5
|
||||||
assert out["funding_usdt"] == -1.5
|
assert out["funding_usdt"] == -1.5
|
||||||
assert out["est_close_fees"] == 0.0
|
# 离场费按入场估算
|
||||||
# 8 + 5 - 0.5 + (-1.5) = 11
|
assert out["est_close_fees"] == 0.5
|
||||||
assert abs(out["net_pnl"] - 11.0) < 1e-9
|
# 8 + 5 - 0.5*2 + (-1.5) = 10.5
|
||||||
|
assert abs(out["net_pnl"] - 10.5) < 1e-9
|
||||||
assert out["pnl_source"] == "live_exchange"
|
assert out["pnl_source"] == "live_exchange"
|
||||||
|
|||||||
Reference in New Issue
Block a user