Files
eth_hedge_sim/backend/app/sim/pnl.py
T
dekun 1c75db4a19 Split fee stats by leg and hide LIVE slip.
SIM shows perp/option fees and slip separately; LIVE keeps real exchange fees only with slip forced to zero.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 16:37:18 +08:00

89 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""从成交明细汇总腿盈亏与净盈亏。"""
from __future__ import annotations
from typing import Any
def _as_map(x: Any) -> dict[str, Any]:
if isinstance(x, dict):
return x
try:
return dict(x)
except Exception:
return {}
def summarize_fills_pnl(fills: list[Any]) -> dict[str, float | None]:
"""
价差盈亏按 fill_px;手续费另扣。
净盈亏 = 期权盈亏 + 永续盈亏 − 全部手续费(开+平)。
允许只有永续已平、期权尚未结算的半组。
手续费拆:fees_perp / fees_option;滑点合计 slip_totalSIM 记账;LIVE 应为 0)。
"""
rows = [_as_map(x) for x in fills]
opt_open = next(
(f for f in rows if f.get("leg") == "option" and f.get("action") == "open"),
None,
)
opt_close = next(
(f for f in rows if f.get("leg") == "option" and f.get("action") == "close"),
None,
)
perp_open = next(
(f for f in rows if f.get("leg") == "perp" and f.get("action") == "open"),
None,
)
perp_close = next(
(f for f in rows if f.get("leg") == "perp" and f.get("action") == "close"),
None,
)
option_pnl: float | None = None
if opt_open and opt_close:
qty = float(opt_open.get("qty_eth") or opt_close.get("qty_eth") or 0)
option_pnl = (float(opt_close["fill_px"]) - float(opt_open["fill_px"])) * qty
perp_pnl: float | None = None
if perp_open and perp_close:
qty = float(perp_open.get("qty_eth") or perp_close.get("qty_eth") or 0)
side = str(perp_open.get("side") or "")
o = float(perp_open["fill_px"])
c = float(perp_close["fill_px"])
if side == "long":
perp_pnl = (c - o) * qty
else:
perp_pnl = (o - c) * qty
fees_perp = sum(
float(f.get("fee") or 0) for f in rows if str(f.get("leg") or "") == "perp"
)
fees_option = sum(
float(f.get("fee") or 0) for f in rows if str(f.get("leg") or "") == "option"
)
fees_total = fees_perp + fees_option
slip_total = sum(float(f.get("slip") or 0) for f in rows)
gross = None
net = None
if option_pnl is not None and perp_pnl is not None:
gross = option_pnl + perp_pnl
net = gross - fees_total
elif option_pnl is not None:
gross = option_pnl
net = option_pnl - fees_total
elif perp_pnl is not None:
gross = perp_pnl
net = perp_pnl - fees_total
return {
"option_pnl": option_pnl,
"perp_pnl": perp_pnl,
"fees_perp": fees_perp,
"fees_option": fees_option,
"fees_total": fees_total,
"slip_total": slip_total,
"gross_pnl": gross,
"net_pnl": net,
}