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:
@@ -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:
|
||||
|
||||
@@ -2261,6 +2261,7 @@ def format_hold_minutes(minutes):
|
||||
|
||||
|
||||
def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage):
|
||||
"""估算净盈亏(USDT):价差毛利 − 双边 taker 费(默认各 0.05%)."""
|
||||
try:
|
||||
trigger = float(trigger_price)
|
||||
exit_p = float(exit_price)
|
||||
@@ -2272,7 +2273,15 @@ def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage):
|
||||
pnl_ratio = (trigger - exit_p) / trigger
|
||||
else:
|
||||
pnl_ratio = (exit_p - trigger) / trigger
|
||||
return round(margin * lev * pnl_ratio, 4)
|
||||
notional = margin * lev
|
||||
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 float(net) if net is not None else round(gross, 4)
|
||||
except Exception:
|
||||
return round(gross, 4)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
|
||||
@@ -2157,6 +2157,7 @@ def format_hold_minutes(minutes):
|
||||
|
||||
|
||||
def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage):
|
||||
"""估算净盈亏(USDT):价差毛利 − 双边 taker 费(默认各 0.05%)."""
|
||||
try:
|
||||
trigger = float(trigger_price)
|
||||
exit_p = float(exit_price)
|
||||
@@ -2168,7 +2169,15 @@ def calc_pnl(direction, trigger_price, exit_price, margin_capital, leverage):
|
||||
pnl_ratio = (trigger - exit_p) / trigger
|
||||
else:
|
||||
pnl_ratio = (exit_p - trigger) / trigger
|
||||
return round(margin * lev * pnl_ratio, 4)
|
||||
notional = margin * lev
|
||||
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 float(net) if net is not None else round(gross, 4)
|
||||
except Exception:
|
||||
return round(gross, 4)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
|
||||
@@ -4,6 +4,54 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-17 · 永续估算盈亏统一扣双边 taker 手续费
|
||||
|
||||
### 修改原因
|
||||
|
||||
中控/实例「盈利金额」、微信推送「本单盈亏」、交易记录 `pnl_amount` 使用价差毛利,未扣开平手续费,与交易所实际净盈亏及盈亏比体感偏差较大。
|
||||
|
||||
### 定稿口径
|
||||
|
||||
| 项 | 约定 |
|
||||
|----|------|
|
||||
| 浮盈亏 | 仍读交易所,不改 |
|
||||
| 费率 | taker 单边 **0.05%**(`PERP_TAKER_FEE_RATE`,默认 `0.0005`),开+平双边 |
|
||||
| 净盈亏 | 毛利 − 开仓名义×费率 − 平仓名义×费率(不考虑滑点) |
|
||||
| RR | 净盈利 / 原风险(风险侧加费第二步再做) |
|
||||
| 历史记录 | 不回算 |
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `lib/trade/trade_fee_lib.py` | 新增公共扣费 / 净盈亏 |
|
||||
| `lib/strategy/strategy_roll_ui_lib.py` | `reward_at_tp_usdt` → 净盈利 |
|
||||
| `lib/strategy/strategy_roll_lib.py` | 同上 |
|
||||
| `lib/strategy/strategy_trend_lib.py` | `calc_tp_profit_usdt` → 净盈利 |
|
||||
| `lib/hub/hub_calculator_lib.py` | 滚仓预览止盈盈利 / 首仓盈利扣费;RR 跟净盈利 |
|
||||
| `crypto_monitor_okx/app.py` | `calc_pnl` → 净盈亏(推送/记账) |
|
||||
| `crypto_monitor_gate/app.py` | 同上 |
|
||||
| `crypto_monitor_binance/app.py` | `calc_pnl` / 成交回退扣费;income 真费路径优先不改 |
|
||||
| `tests/test_trade_fee_lib.py` | 新增 |
|
||||
| `tests/test_strategy_roll_ui_lib.py` | 断言改净额 |
|
||||
| `tests/test_order_monitor_display_lib.py` | 断言改净额 |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
1. 中控持仓「盈利金额」、实例「盈利金额」、计算器止盈盈利、趋势/滚仓预览一致为净盈亏。
|
||||
2. 微信推送与新建交易记录的 `pnl_amount` 与上述估算口径一致。
|
||||
3. 币安若能拉到 income 净额(已含真实手续费)仍优先用交易所数。
|
||||
4. 浮盈亏展示仍跟交易所。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 同一笔持仓:中控盈利金额 ≈ 实例盈利金额(均为扣费后)。
|
||||
2. 平仓推送「本单盈亏」与新写入记录接近,不再明显大于交易所净利。
|
||||
3. 浮盈亏与交易所 App 一致(本改不动)。
|
||||
4. 单测:`python -m unittest tests.test_trade_fee_lib tests.test_strategy_roll_ui_lib tests.test_order_monitor_display_lib tests.test_trend_preview_tp -v` 通过。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 计算器左侧 Tab + 三行输入
|
||||
|
||||
### 修改原因
|
||||
|
||||
@@ -315,10 +315,18 @@ def _roll_leg_preview(
|
||||
cs = float(contract_size) if contract_size else 1.0
|
||||
if direction == "long":
|
||||
loss_at_sl = (new_avg - sl) * new_qty * cs
|
||||
reward_at_tp = (tp - new_avg) * new_qty * cs
|
||||
reward_gross = (tp - new_avg) * new_qty * cs
|
||||
else:
|
||||
loss_at_sl = (sl - new_avg) * new_qty * cs
|
||||
reward_at_tp = (new_avg - tp) * new_qty * cs
|
||||
reward_gross = (new_avg - tp) * new_qty * cs
|
||||
try:
|
||||
from lib.trade.trade_fee_lib import net_pnl_after_fee
|
||||
|
||||
reward_at_tp = net_pnl_after_fee(reward_gross, new_avg, tp, new_qty, cs)
|
||||
if reward_at_tp is None:
|
||||
reward_at_tp = reward_gross
|
||||
except Exception:
|
||||
reward_at_tp = reward_gross
|
||||
return {
|
||||
"add_amount_raw": q2,
|
||||
"qty_after": new_qty,
|
||||
@@ -412,10 +420,18 @@ def calc_roll_calculator(
|
||||
|
||||
if direction == "long":
|
||||
first_loss = (avg - initial_sl) * qty_f * cs
|
||||
first_profit = (tp - avg) * qty_f * cs
|
||||
first_profit_gross = (tp - avg) * qty_f * cs
|
||||
else:
|
||||
first_loss = (initial_sl - avg) * qty_f * cs
|
||||
first_profit = (avg - tp) * qty_f * cs
|
||||
first_profit_gross = (avg - tp) * qty_f * cs
|
||||
try:
|
||||
from lib.trade.trade_fee_lib import net_pnl_after_fee
|
||||
|
||||
first_profit = net_pnl_after_fee(first_profit_gross, avg, tp, qty_f, cs)
|
||||
if first_profit is None:
|
||||
first_profit = first_profit_gross
|
||||
except Exception:
|
||||
first_profit = first_profit_gross
|
||||
|
||||
rows.append(
|
||||
{
|
||||
|
||||
@@ -143,8 +143,16 @@ def reward_at_tp_usdt(
|
||||
cs = float(contract_size or 1.0)
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "short":
|
||||
return (float(avg) - float(take_profit)) * float(qty) * cs
|
||||
return (float(take_profit) - float(avg)) * float(qty) * cs
|
||||
gross = (float(avg) - float(take_profit)) * float(qty) * cs
|
||||
else:
|
||||
gross = (float(take_profit) - float(avg)) * float(qty) * cs
|
||||
try:
|
||||
from lib.trade.trade_fee_lib import net_pnl_after_fee
|
||||
|
||||
net = net_pnl_after_fee(gross, avg, take_profit, qty, cs)
|
||||
return float(net) if net is not None else gross
|
||||
except Exception:
|
||||
return gross
|
||||
|
||||
|
||||
def roll_fib_trigger_crossed(
|
||||
|
||||
@@ -16,7 +16,7 @@ def reward_at_tp_usdt(
|
||||
*,
|
||||
contract_size: float = 1.0,
|
||||
) -> Optional[float]:
|
||||
"""与 strategy_roll_lib.preview_roll 一致:线性合约 U 本位盈利."""
|
||||
"""与 strategy_roll_lib.preview_roll 一致:线性合约 U 本位净盈利(扣双边 taker 费)."""
|
||||
try:
|
||||
avg = float(avg_entry)
|
||||
tp = float(take_profit)
|
||||
@@ -28,8 +28,16 @@ def reward_at_tp_usdt(
|
||||
return None
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "short":
|
||||
return (avg - tp) * q * cs
|
||||
return (tp - avg) * q * cs
|
||||
gross = (avg - tp) * q * cs
|
||||
else:
|
||||
gross = (tp - avg) * q * cs
|
||||
try:
|
||||
from lib.trade.trade_fee_lib import net_pnl_after_fee
|
||||
|
||||
net = net_pnl_after_fee(gross, avg, tp, q, cs)
|
||||
return net
|
||||
except Exception:
|
||||
return gross
|
||||
|
||||
|
||||
def leg_fill_price(leg: dict) -> Optional[float]:
|
||||
|
||||
@@ -306,13 +306,19 @@ def calc_tp_profit_usdt(
|
||||
contracts: float,
|
||||
contract_size: float = 1.0,
|
||||
) -> Optional[float]:
|
||||
"""到达止盈价时,按累计张数与加仓后均价的盈利 U."""
|
||||
"""到达止盈价时,按累计张数与加仓后均价的净盈利 U(扣双边 taker 费)."""
|
||||
try:
|
||||
from lib.hub.hub_position_metrics import estimate_linear_swap_upnl_usdt
|
||||
from lib.trade.trade_fee_lib import net_pnl_after_fee
|
||||
|
||||
return estimate_linear_swap_upnl_usdt(
|
||||
gross = estimate_linear_swap_upnl_usdt(
|
||||
direction, float(avg_entry), float(take_profit_price), float(contracts), float(contract_size)
|
||||
)
|
||||
if gross is None:
|
||||
return None
|
||||
return net_pnl_after_fee(
|
||||
gross, avg_entry, take_profit_price, contracts, contract_size
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"""永续估算盈亏:固定 taker 手续费(默认单边 0.05%,开+平双边).
|
||||
|
||||
浮盈亏仍读交易所;本模块只服务「盈利金额 / 止盈盈利 / 推送 / 记账 pnl_amount」等估算口径.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def _finite(v) -> Optional[float]:
|
||||
try:
|
||||
f = float(v)
|
||||
return f if math.isfinite(f) else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def taker_fee_rate() -> float:
|
||||
"""单边 taker 费率,默认 0.0005(=0.05%)."""
|
||||
raw = os.getenv("PERP_TAKER_FEE_RATE", "0.0005")
|
||||
rate = _finite(raw)
|
||||
if rate is None or rate < 0:
|
||||
return 0.0005
|
||||
return rate
|
||||
|
||||
|
||||
def notional_usdt(price, qty, contract_size: float = 1.0) -> Optional[float]:
|
||||
"""名义价值 U = 价格 × 张数 × 合约面值."""
|
||||
p = _finite(price)
|
||||
q = _finite(qty)
|
||||
cs = _finite(contract_size)
|
||||
if p is None or q is None or p <= 0 or q <= 0:
|
||||
return None
|
||||
if cs is None or cs <= 0:
|
||||
cs = 1.0
|
||||
return abs(q) * p * cs
|
||||
|
||||
|
||||
def estimate_roundtrip_fee_usdt(
|
||||
entry_price,
|
||||
exit_price,
|
||||
qty=None,
|
||||
contract_size: float = 1.0,
|
||||
*,
|
||||
open_notional: float | None = None,
|
||||
rate: float | None = None,
|
||||
) -> float:
|
||||
"""开+平双边手续费(各单边 rate).
|
||||
|
||||
优先用 价×张×面值;若无张数则用 open_notional 估开仓名义,
|
||||
平仓名义按 exit/entry 缩放.
|
||||
"""
|
||||
fee_rate = taker_fee_rate() if rate is None else float(rate)
|
||||
if fee_rate <= 0:
|
||||
return 0.0
|
||||
entry = _finite(entry_price)
|
||||
exit_p = _finite(exit_price)
|
||||
open_n = notional_usdt(entry, qty, contract_size) if qty is not None else None
|
||||
if open_n is None:
|
||||
open_n = _finite(open_notional)
|
||||
if open_n is None or open_n <= 0:
|
||||
return 0.0
|
||||
if entry is not None and entry > 0 and exit_p is not None and exit_p > 0:
|
||||
close_n = open_n * (exit_p / entry)
|
||||
else:
|
||||
close_n = open_n
|
||||
return round(open_n * fee_rate + close_n * fee_rate, 8)
|
||||
|
||||
|
||||
def net_pnl_after_fee(
|
||||
gross_pnl,
|
||||
entry_price,
|
||||
exit_price,
|
||||
qty=None,
|
||||
contract_size: float = 1.0,
|
||||
*,
|
||||
open_notional: float | None = None,
|
||||
rate: float | None = None,
|
||||
) -> Optional[float]:
|
||||
"""毛利扣双边手续费后的净盈亏;gross 无效则返回 None."""
|
||||
gross = _finite(gross_pnl)
|
||||
if gross is None:
|
||||
return None
|
||||
fee = estimate_roundtrip_fee_usdt(
|
||||
entry_price,
|
||||
exit_price,
|
||||
qty,
|
||||
contract_size,
|
||||
open_notional=open_notional,
|
||||
rate=rate,
|
||||
)
|
||||
return round(gross - fee, 4)
|
||||
@@ -88,7 +88,8 @@ class HubCalculatorLibTests(unittest.TestCase):
|
||||
self.assertEqual(data["first_contracts"], 10.0)
|
||||
self.assertEqual(len(data["rows"]), 1)
|
||||
self.assertEqual(data["rows"][0]["loss_at_sl_u"], 50.0)
|
||||
self.assertEqual(data["rows"][0]["profit_at_tp_u"], 200.0)
|
||||
# 毛利 200 − 双边费 (1000+1200)*0.0005=1.1 → 198.9
|
||||
self.assertEqual(data["rows"][0]["profit_at_tp_u"], 198.9)
|
||||
|
||||
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
|
||||
def test_roll_calculator_chain_two_legs(self, _mock):
|
||||
|
||||
@@ -136,7 +136,8 @@ def test_apply_order_price_display_fields_gate_contract_size():
|
||||
avg_entry_price=62063.4,
|
||||
)
|
||||
assert payload["reward_at_tp_usdt"] is not None
|
||||
assert abs(payload["reward_at_tp_usdt"] - 6.73) < 0.1
|
||||
# 毛利约 6.73, 扣双边 0.05% 后约 6.25
|
||||
assert abs(payload["reward_at_tp_usdt"] - 6.25) < 0.1
|
||||
|
||||
|
||||
def test_calc_latest_risk_amount_long():
|
||||
|
||||
@@ -43,4 +43,5 @@ def test_infer_initial_position_from_live():
|
||||
|
||||
|
||||
def test_reward_at_tp_long():
|
||||
assert roll_ui.reward_at_tp_usdt("long", 100.0, 110.0, 2.0) == 20.0
|
||||
# 毛利 20, 双边费 (200+220)*0.0005=0.21 → 净 19.79
|
||||
assert abs(roll_ui.reward_at_tp_usdt("long", 100.0, 110.0, 2.0) - 19.79) < 1e-6
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""永续固定费率净盈亏."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from lib.trade.trade_fee_lib import ( # noqa: E402
|
||||
estimate_roundtrip_fee_usdt,
|
||||
net_pnl_after_fee,
|
||||
notional_usdt,
|
||||
taker_fee_rate,
|
||||
)
|
||||
|
||||
|
||||
class TestTradeFeeLib(unittest.TestCase):
|
||||
def test_default_rate(self):
|
||||
os.environ.pop("PERP_TAKER_FEE_RATE", None)
|
||||
self.assertAlmostEqual(taker_fee_rate(), 0.0005)
|
||||
|
||||
def test_notional(self):
|
||||
self.assertAlmostEqual(notional_usdt(100, 2, 1.0), 200.0)
|
||||
self.assertAlmostEqual(notional_usdt(62000, 78, 0.0001), 483.6, places=2)
|
||||
|
||||
def test_roundtrip_fee_qty(self):
|
||||
# 开 100*2=200, 平 110*2=220, 费=(200+220)*0.0005=0.21
|
||||
fee = estimate_roundtrip_fee_usdt(100, 110, 2.0, 1.0, rate=0.0005)
|
||||
self.assertAlmostEqual(fee, 0.21, places=6)
|
||||
|
||||
def test_net_long_matches_checklist(self):
|
||||
# 毛利 20, 费 0.21 → 净 19.79
|
||||
net = net_pnl_after_fee(20.0, 100, 110, 2.0, 1.0, rate=0.0005)
|
||||
self.assertAlmostEqual(net, 19.79, places=4)
|
||||
|
||||
def test_open_notional_fallback(self):
|
||||
# 无张数:开名义 1000, 出场 110/100 → 平 1100, 费=1.05
|
||||
fee = estimate_roundtrip_fee_usdt(100, 110, open_notional=1000, rate=0.0005)
|
||||
self.assertAlmostEqual(fee, 1.05, places=6)
|
||||
net = net_pnl_after_fee(50.0, 100, 110, open_notional=1000, rate=0.0005)
|
||||
self.assertAlmostEqual(net, 48.95, places=4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user