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
+42 -4
View File
@@ -2281,7 +2281,7 @@ def format_hold_minutes(minutes):
def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage, notional_usdt=None):
"""估算盈亏(USDT).优先用名义价值 notional_usdt,否则 margin×leverage."""
"""估算盈亏(USDT).优先用名义价值 notional_usdt,否则 margin×leverage;扣双边 taker 费."""
try:
trigger = float(trigger_price)
exit_p = float(exit_price)
@@ -2299,7 +2299,14 @@ def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage, not
pnl_ratio = (trigger - exit_p) / trigger
else:
pnl_ratio = (exit_p - trigger) / trigger
return round(notional * pnl_ratio, FUNDS_DECIMALS)
gross = notional * pnl_ratio
try:
from lib.trade.trade_fee_lib import net_pnl_after_fee
net = net_pnl_after_fee(gross, trigger, exit_p, open_notional=notional)
return round(float(net), FUNDS_DECIMALS) if net is not None else round(gross, FUNDS_DECIMALS)
except Exception:
return round(gross, FUNDS_DECIMALS)
except Exception:
return 0.0
@@ -2449,7 +2456,7 @@ def _sum_binance_income(entries, income_types, trade_ids=None):
def calc_pnl_from_closing_trades(direction, entry_price, trades, exchange_symbol=None):
"""按减仓成交数量×价差汇总盈亏(不含资金费;比单点标记价更接近交易所)."""
"""按减仓成交数量×价差汇总盈亏(扣固定双边 taker 费;不含资金费)."""
try:
entry = float(entry_price)
except (TypeError, ValueError):
@@ -2465,6 +2472,7 @@ def calc_pnl_from_closing_trades(direction, entry_price, trades, exchange_symbol
contract_size = 1.0
pnl = 0.0
qty = 0.0
notional_close = 0.0
for t in trades:
try:
price = float(t.get("price") or 0)
@@ -2474,13 +2482,22 @@ def calc_pnl_from_closing_trades(direction, entry_price, trades, exchange_symbol
if price <= 0 or amount <= 0:
continue
qty += amount
notional_close += amount * price
if direction == "short":
pnl += amount * (entry - price)
else:
pnl += amount * (price - entry)
if qty <= 0:
return None
return round(pnl, FUNDS_DECIMALS)
exit_px = (notional_close / qty) if qty > 0 else entry
try:
from lib.trade.trade_fee_lib import net_pnl_after_fee
# amount 已乘 contractSize,此处面值用 1
net = net_pnl_after_fee(pnl, entry, exit_px, qty, 1.0)
return round(float(net), FUNDS_DECIMALS) if net is not None else round(pnl, FUNDS_DECIMALS)
except Exception:
return round(pnl, FUNDS_DECIMALS)
def resolve_trade_pnl_amount(
@@ -2535,10 +2552,31 @@ def resolve_trade_pnl_amount(
ex_sym, direction, open_ms, close_ms, closing_trades=closing_trades
)
if net is not None:
# income 已含真实手续费,直接用.
return net, exit_price, eo, ec, sync_key
if closing_trades:
trade_pnl = calc_binance_realized_pnl_from_trades(closing_trades)
if trade_pnl is not None:
# fill.realizedPnl 通常不含 commission,补固定双边费.
try:
from lib.trade.trade_fee_lib import net_pnl_after_fee
entry = float(entry_price or 0)
exit_p = float(exit_price or entry or 0)
if entry > 0 and exit_p > 0:
open_n = get_plan_notional_usdt(row)
if open_n is None:
margin = row["margin_capital"] if hasattr(row, "keys") else row.get("margin_capital")
lev = row["leverage"] if hasattr(row, "keys") else row.get("leverage")
try:
open_n = float(margin or 0) * float(lev or 1)
except (TypeError, ValueError):
open_n = None
adj = net_pnl_after_fee(trade_pnl, entry, exit_p, open_notional=open_n)
if adj is not None:
trade_pnl = adj
except Exception:
pass
return trade_pnl, exit_price, None, None, None
fill_pnl = calc_pnl_from_closing_trades(direction, entry_price, closing_trades, ex_sym)
if fill_pnl is not None: