Use exchange fees, UPL, funding for LIVE PnL; USDC 1:1 to USDT.
Option open P&L stays local (option net); exit target ignores estimated close fees. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -163,16 +163,18 @@ class BinanceTradeClient:
|
||||
data = q
|
||||
if not avg or avg <= 0:
|
||||
raise RuntimeError(f"币安永续无成交均价: {data}")
|
||||
# 手续费:优先 cumCommission;否则用名义×费率估
|
||||
from .money import abs_fee_usdt
|
||||
|
||||
fee = abs(safe_float(data.get("cumCommission")) or 0.0)
|
||||
if fee <= 0:
|
||||
fee = float(avg) * float(sz or 0) * float(self.settings.fee_rate)
|
||||
fee_asset = str(data.get("commissionAsset") or "USDT")
|
||||
if fee <= 0 and ord_id:
|
||||
fee, fee_asset = self.sum_perp_trade_fees(symbol, ord_id)
|
||||
return LiveFill(
|
||||
inst_id=symbol,
|
||||
side=str(data.get("side") or "").lower(),
|
||||
avg_px=float(avg),
|
||||
sz=float(sz or 0),
|
||||
fee=float(fee),
|
||||
fee=abs_fee_usdt(fee, fee_asset),
|
||||
ord_id=ord_id,
|
||||
raw=data if isinstance(data, dict) else {},
|
||||
)
|
||||
@@ -223,15 +225,156 @@ class BinanceTradeClient:
|
||||
raise RuntimeError(f"币安期权订单失败 status={st} {q}")
|
||||
if not avg or avg <= 0:
|
||||
raise RuntimeError(f"币安期权无成交均价: {data}")
|
||||
from .money import abs_fee_usdt
|
||||
|
||||
fee = abs(safe_float(data.get("fee")) or 0.0)
|
||||
if fee <= 0:
|
||||
fee = float(avg) * float(sz or 0) * float(self.settings.fee_rate)
|
||||
fee_asset = "USDT"
|
||||
if fee <= 0 and ord_id:
|
||||
fee, fee_asset = self.sum_option_trade_fees(symbol, ord_id)
|
||||
return LiveFill(
|
||||
inst_id=symbol,
|
||||
side=str(data.get("side") or "").lower(),
|
||||
avg_px=float(avg),
|
||||
sz=float(sz or 0),
|
||||
fee=float(fee),
|
||||
fee=abs_fee_usdt(fee, fee_asset),
|
||||
ord_id=ord_id,
|
||||
raw=data if isinstance(data, dict) else {},
|
||||
)
|
||||
|
||||
def sum_perp_trade_fees(self, symbol: str, order_id: str) -> tuple[float, str]:
|
||||
try:
|
||||
rows = self._signed(
|
||||
self._fapi,
|
||||
"GET",
|
||||
"/fapi/v1/userTrades",
|
||||
{"symbol": symbol, "orderId": order_id},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("binance perp userTrades fee failed: %s", e)
|
||||
return 0.0, "USDT"
|
||||
if not isinstance(rows, list):
|
||||
rows = [rows] if isinstance(rows, dict) else []
|
||||
total = 0.0
|
||||
asset = "USDT"
|
||||
for row in rows:
|
||||
total += abs(safe_float(row.get("commission")) or 0.0)
|
||||
if row.get("commissionAsset"):
|
||||
asset = str(row.get("commissionAsset"))
|
||||
return total, asset
|
||||
|
||||
def sum_option_trade_fees(self, symbol: str, order_id: str) -> tuple[float, str]:
|
||||
try:
|
||||
rows = self._signed(
|
||||
self._eapi,
|
||||
"GET",
|
||||
"/eapi/v1/userTrades",
|
||||
{"symbol": symbol, "orderId": order_id},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("binance option userTrades fee failed: %s", e)
|
||||
return 0.0, "USDT"
|
||||
if not isinstance(rows, list):
|
||||
rows = [rows] if isinstance(rows, dict) else []
|
||||
total = 0.0
|
||||
asset = "USDT"
|
||||
for row in rows:
|
||||
total += abs(safe_float(row.get("commission")) or safe_float(row.get("fee")) or 0.0)
|
||||
if row.get("commissionAsset") or row.get("feeAsset"):
|
||||
asset = str(row.get("commissionAsset") or row.get("feeAsset"))
|
||||
return total, asset
|
||||
|
||||
def get_perp_upl_usdt(self, symbol: str, *, position_side: str | None = None) -> float | None:
|
||||
from .money import to_usdt
|
||||
|
||||
try:
|
||||
rows = self._signed(
|
||||
self._fapi, "GET", "/fapi/v2/positionRisk", {"symbol": symbol}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("binance positionRisk failed: %s", e)
|
||||
return None
|
||||
if isinstance(rows, dict):
|
||||
rows = [rows]
|
||||
want = (position_side or "").strip().upper()
|
||||
for row in rows:
|
||||
amt = safe_float(row.get("positionAmt")) or 0.0
|
||||
if abs(amt) < 1e-12:
|
||||
continue
|
||||
ps = str(row.get("positionSide") or "").upper()
|
||||
if want and ps and ps not in ("BOTH",) and ps != want:
|
||||
continue
|
||||
upl = safe_float(row.get("unRealizedProfit"))
|
||||
if upl is None:
|
||||
continue
|
||||
return to_usdt(float(upl), "USDT")
|
||||
return 0.0
|
||||
|
||||
def get_funding_usdt(
|
||||
self, symbol: str, *, begin_ms: int, end_ms: int | None = None
|
||||
) -> float:
|
||||
from .money import to_usdt
|
||||
|
||||
end = int(end_ms or int(time.time() * 1000))
|
||||
try:
|
||||
rows = self._signed(
|
||||
self._fapi,
|
||||
"GET",
|
||||
"/fapi/v1/income",
|
||||
{
|
||||
"symbol": symbol,
|
||||
"incomeType": "FUNDING_FEE",
|
||||
"startTime": int(begin_ms),
|
||||
"endTime": end,
|
||||
"limit": 1000,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("binance funding income failed: %s", e)
|
||||
return 0.0
|
||||
if isinstance(rows, dict):
|
||||
rows = [rows]
|
||||
total = 0.0
|
||||
for row in rows:
|
||||
raw = safe_float(row.get("income"))
|
||||
if raw is None:
|
||||
continue
|
||||
asset = str(row.get("asset") or "USDT")
|
||||
total += to_usdt(float(raw), asset)
|
||||
return total
|
||||
|
||||
def get_closed_perp_pnl_usdt(
|
||||
self, symbol: str, *, begin_ms: int, end_ms: int | None = None
|
||||
) -> float | None:
|
||||
"""用 REALIZED_PNL income 近似已实现(含部分平仓);资金费另计。"""
|
||||
from .money import to_usdt
|
||||
|
||||
end = int(end_ms or int(time.time() * 1000))
|
||||
try:
|
||||
rows = self._signed(
|
||||
self._fapi,
|
||||
"GET",
|
||||
"/fapi/v1/income",
|
||||
{
|
||||
"symbol": symbol,
|
||||
"incomeType": "REALIZED_PNL",
|
||||
"startTime": int(begin_ms),
|
||||
"endTime": end,
|
||||
"limit": 1000,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("binance realized income failed: %s", e)
|
||||
return None
|
||||
if isinstance(rows, dict):
|
||||
rows = [rows]
|
||||
if not rows:
|
||||
return None
|
||||
total = 0.0
|
||||
for row in rows:
|
||||
raw = safe_float(row.get("income"))
|
||||
if raw is None:
|
||||
continue
|
||||
asset = str(row.get("asset") or "USDT")
|
||||
total += to_usdt(float(raw), asset)
|
||||
return total
|
||||
|
||||
|
||||
Reference in New Issue
Block a user