fix模拟盘期权手续费按OKX币本位公式计费,并纠正复盘旧单展示。
费率作用在标的币数量上并以权利金7%封顶,不再误用权利金×费率。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -280,6 +280,148 @@ def hide_review_trade(conn: sqlite3.Connection, trade_id: int) -> dict[str, Any]
|
||||
return {"ok": True, "entry": _row_to_dict(entry) if entry else None, "history_key": d.get("history_key")}
|
||||
|
||||
|
||||
def _sum_sim_order_fees(conn: sqlite3.Connection, ord_ids: list[str]) -> float | None:
|
||||
"""从 sim_option_orders 汇总手续费(权利金币种,正数)."""
|
||||
ids = [str(x).strip() for x in (ord_ids or []) if str(x).strip()]
|
||||
if not ids:
|
||||
return None
|
||||
try:
|
||||
conn.execute("SELECT 1 FROM sim_option_orders LIMIT 1").fetchone()
|
||||
except Exception:
|
||||
return None
|
||||
total = 0.0
|
||||
n = 0
|
||||
for oid in ids:
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT fee FROM sim_option_orders WHERE ord_id=?",
|
||||
(oid,),
|
||||
).fetchone()
|
||||
except Exception:
|
||||
continue
|
||||
if not row:
|
||||
continue
|
||||
fee = _safe_float(row["fee"] if hasattr(row, "keys") else row[0])
|
||||
if fee is None:
|
||||
continue
|
||||
total += abs(float(fee))
|
||||
n += 1
|
||||
return round(total, 8) if n else None
|
||||
|
||||
|
||||
def _estimate_okx_option_fee_coin(
|
||||
*,
|
||||
underlying_coin: float | None,
|
||||
premium_coin: float | None,
|
||||
) -> float:
|
||||
"""按 OKX 币本位公式估算期权手续费(权利金币种)."""
|
||||
try:
|
||||
from lib.sim.pricing_lib import option_fee_coin
|
||||
|
||||
return float(
|
||||
option_fee_coin(
|
||||
underlying_coin=float(underlying_coin or 0.0),
|
||||
premium_coin=float(premium_coin or 0.0),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _fee_looks_like_legacy_premium_rate(
|
||||
fee_coin: float,
|
||||
*,
|
||||
premium_coin: float | None,
|
||||
underlying_coin: float | None,
|
||||
) -> bool:
|
||||
"""旧模拟盘把手续费算成权利金×费率,约为正确值的 premium/underlying 倍."""
|
||||
if fee_coin <= 0:
|
||||
return True
|
||||
prem = abs(float(premium_coin or 0.0))
|
||||
uly = abs(float(underlying_coin or 0.0))
|
||||
if prem <= 0 or uly <= 0:
|
||||
return False
|
||||
# 正确量级 ~ uly * fee_rate; 错误量级 ~ prem * fee_rate
|
||||
# 若 fee << uly*1e-4 且接近 prem*5e-4,视为旧算法
|
||||
try:
|
||||
from lib.sim.pricing_lib import sim_fee_rate
|
||||
|
||||
fr = float(sim_fee_rate())
|
||||
except Exception:
|
||||
fr = 0.0005
|
||||
expect_okx = uly * fr
|
||||
expect_legacy = prem * fr
|
||||
if expect_okx <= 0:
|
||||
return False
|
||||
# 比 OKX 估算小一个数量级以上,且靠近旧公式
|
||||
if fee_coin < expect_okx * 0.25 and abs(fee_coin - expect_legacy) <= max(expect_legacy * 0.5, 1e-10):
|
||||
return True
|
||||
if fee_coin < expect_okx * 0.05:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _resolve_option_fees_coin(
|
||||
conn: sqlite3.Connection,
|
||||
row: Any,
|
||||
*,
|
||||
open_fee: float,
|
||||
close_fee: float,
|
||||
premium_ccy: str,
|
||||
) -> tuple[float, float]:
|
||||
"""解析期权开/平手续费(权利金币种);修正模拟盘旧算法偏小值."""
|
||||
ccy = (premium_ccy or "").strip().upper()
|
||||
paid = _safe_float(row["premium_paid"] if hasattr(row, "keys") else None)
|
||||
recv = _safe_float(row["premium_received"] if hasattr(row, "keys") else None)
|
||||
uly = _safe_float(row["eth_amount"] if hasattr(row, "keys") else None)
|
||||
if uly is None or uly <= 0:
|
||||
sheets = _safe_float(row["sheets"] if hasattr(row, "keys") else None) or 0.0
|
||||
# BTC 期权约 0.01,ETH 约 0.1
|
||||
ct = 0.01 if ccy == "BTC" else 0.1
|
||||
uly = float(sheets) * ct if sheets > 0 else None
|
||||
|
||||
open_oids = [
|
||||
p.strip()
|
||||
for p in str(row["exchange_ord_id"] if hasattr(row, "keys") else "").split(",")
|
||||
if p.strip()
|
||||
]
|
||||
close_oids = [
|
||||
p.strip()
|
||||
for p in str(row["close_ord_id"] if hasattr(row, "keys") else "").split(",")
|
||||
if p.strip()
|
||||
]
|
||||
|
||||
ofee = abs(float(open_fee or 0.0))
|
||||
cfee = abs(float(close_fee or 0.0))
|
||||
|
||||
# 优先用订单表(若订单本身仍是旧算法,下面会再纠正)
|
||||
sim_open = _sum_sim_order_fees(conn, open_oids)
|
||||
sim_close = _sum_sim_order_fees(conn, close_oids)
|
||||
if sim_open is not None and sim_open > 0:
|
||||
ofee = sim_open
|
||||
if sim_close is not None and sim_close > 0:
|
||||
cfee = sim_close
|
||||
|
||||
if ccy in ("ETH", "BTC"):
|
||||
if _fee_looks_like_legacy_premium_rate(ofee, premium_coin=paid, underlying_coin=uly):
|
||||
est = _estimate_okx_option_fee_coin(underlying_coin=uly, premium_coin=paid)
|
||||
if est > 0:
|
||||
ofee = est
|
||||
if _fee_looks_like_legacy_premium_rate(cfee, premium_coin=recv, underlying_coin=uly):
|
||||
est = _estimate_okx_option_fee_coin(underlying_coin=uly, premium_coin=recv)
|
||||
if est > 0:
|
||||
cfee = est
|
||||
elif cfee <= 0 and recv is not None and recv > 0:
|
||||
est = _estimate_okx_option_fee_coin(underlying_coin=uly, premium_coin=recv)
|
||||
if est > 0:
|
||||
cfee = est
|
||||
if ofee <= 0 and paid is not None and paid > 0:
|
||||
est = _estimate_okx_option_fee_coin(underlying_coin=uly, premium_coin=paid)
|
||||
if est > 0:
|
||||
ofee = est
|
||||
|
||||
return round(ofee, 8), round(cfee, 8)
|
||||
|
||||
|
||||
def sync_options_from_local_trades(
|
||||
conn: sqlite3.Connection,
|
||||
@@ -317,11 +459,11 @@ def sync_options_from_local_trades(
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT id, inst_id, underlying, opt_type, strike, exp_time, sheets,
|
||||
open_quote, close_quote, premium_paid, premium_received, realized_pnl,
|
||||
realized_pnl_usdt, close_index_px, open_index_px, budget_usdt,
|
||||
premium_ccy, created_at, closed_at, signal_note, status,
|
||||
eth_amount, open_quote, close_quote, premium_paid, premium_received,
|
||||
realized_pnl, realized_pnl_usdt, close_index_px, open_index_px,
|
||||
budget_usdt, premium_ccy, created_at, closed_at, signal_note, status,
|
||||
trading_usdt_before, pnl_settled, initial_premium_usdt,
|
||||
open_fee, close_fee
|
||||
open_fee, close_fee, exchange_ord_id, close_ord_id
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
ORDER BY id DESC
|
||||
@@ -445,6 +587,14 @@ def sync_options_from_local_trades(
|
||||
# 仅期权开/平手续费折 U(不含现货)
|
||||
ofee = abs(_safe_float(r["open_fee"]) or 0.0)
|
||||
cfee = abs(_safe_float(r["close_fee"]) or 0.0)
|
||||
# 模拟盘旧单曾按「权利金×费率」计费偏小:按 OKX 公式/订单回填
|
||||
ofee, cfee = _resolve_option_fees_coin(
|
||||
conn,
|
||||
r,
|
||||
open_fee=ofee,
|
||||
close_fee=cfee,
|
||||
premium_ccy=ccy,
|
||||
)
|
||||
fee_u = None
|
||||
if ccy in ("ETH", "BTC"):
|
||||
fee_parts = 0.0
|
||||
|
||||
+43
-5
@@ -1,4 +1,9 @@
|
||||
"""成交价与手续费: 滑点 = 1×fee_rate."""
|
||||
"""成交价与手续费.
|
||||
|
||||
永续:滑点=1×fee_rate,手续费=名义×fee_rate.
|
||||
期权(币本位):滑点仍按权利金价×fee_rate;手续费对齐 OKX=
|
||||
Min(fee_rate×标的币数量, 权利金×7%).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -57,6 +62,30 @@ def perp_fill(
|
||||
return PriceResult(base_px=base, fill_px=fill, fee=fee, slip=slip, notional=notional)
|
||||
|
||||
|
||||
def option_fee_coin(
|
||||
*,
|
||||
underlying_coin: float,
|
||||
premium_coin: float,
|
||||
fee_rate: float | None = None,
|
||||
premium_fee_cap: float = 0.07,
|
||||
) -> float:
|
||||
"""OKX 币本位期权手续费(权利金币种).
|
||||
|
||||
Min(费率 × 张数对应标的币数量, 权利金×封顶比例).
|
||||
注意:费率作用在标的币面值上,不是权利金名义上.
|
||||
"""
|
||||
f = sim_fee_rate(fee_rate)
|
||||
qty = abs(float(underlying_coin or 0.0))
|
||||
prem = abs(float(premium_coin or 0.0))
|
||||
if qty <= 0 and prem <= 0:
|
||||
return 0.0
|
||||
by_rate = qty * f
|
||||
cap_ratio = abs(float(premium_fee_cap or 0.07))
|
||||
if cap_ratio <= 0 or prem <= 0:
|
||||
return round(by_rate, 8)
|
||||
return round(min(by_rate, prem * cap_ratio), 8)
|
||||
|
||||
|
||||
def option_fill(
|
||||
*,
|
||||
action: str,
|
||||
@@ -65,7 +94,11 @@ def option_fill(
|
||||
qty: float,
|
||||
fee_rate: float,
|
||||
) -> PriceResult:
|
||||
"""开仓买入吃卖一; 平仓卖出吃买一. qty = sheets × ct_mult."""
|
||||
"""开仓买入吃卖一; 平仓卖出吃买一.
|
||||
|
||||
qty = sheets × ct_mult(标的币数量).
|
||||
滑点仍按权利金价×费率;手续费按 OKX 币本位公式(标的币×费率,权利金7%封顶).
|
||||
"""
|
||||
f = float(fee_rate)
|
||||
if action == "open":
|
||||
base = float(ask)
|
||||
@@ -73,9 +106,14 @@ def option_fill(
|
||||
else:
|
||||
base = float(bid)
|
||||
fill = base * (1.0 - f)
|
||||
notional = abs(fill * float(qty))
|
||||
fee = notional * f
|
||||
slip = abs(fill - base) * float(qty)
|
||||
qty_f = abs(float(qty))
|
||||
notional = abs(fill * qty_f)
|
||||
fee = option_fee_coin(
|
||||
underlying_coin=qty_f,
|
||||
premium_coin=notional,
|
||||
fee_rate=f,
|
||||
)
|
||||
slip = abs(fill - base) * qty_f
|
||||
return PriceResult(base_px=base, fill_px=fill, fee=fee, slip=slip, notional=notional)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user