fix模拟盘期权手续费按OKX币本位公式计费,并纠正复盘旧单展示。

费率作用在标的币数量上并以权利金7%封顶,不再误用权利金×费率。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 09:37:14 +08:00
parent bdc9eb532a
commit 78f18e731a
3 changed files with 249 additions and 9 deletions
+154 -4
View File
@@ -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