修复期权历史/复盘金额为0:模拟盘合成历史、币本位按ETH精度展示,复盘折算为U。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable
|
||||
@@ -888,3 +889,124 @@ class SimBroker:
|
||||
row["expTime"] = str(exp_ms)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
def option_positions_history_okx_rows(
|
||||
self,
|
||||
exchange: Any = None,
|
||||
*,
|
||||
inst_id: str | None = None,
|
||||
limit: int = 200,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""模拟盘历史仓位:从本地已平 options_trades 合成 OKX positions-history 字段.
|
||||
|
||||
实盘 positions-history 在模拟模式不可用,期权历史/复盘依赖此合成数据.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from lib.exchange.okx_options_lib import option_fields_from_inst_id
|
||||
from lib.options.options_db import init_options_tables
|
||||
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
|
||||
|
||||
tz = ZoneInfo(
|
||||
(os.getenv("APP_TIMEZONE") or os.getenv("TZ") or "Asia/Shanghai").strip()
|
||||
or "Asia/Shanghai"
|
||||
)
|
||||
|
||||
def _to_ms(ts: Any) -> int | None:
|
||||
if ts is None:
|
||||
return None
|
||||
raw = str(ts).strip()
|
||||
if not raw:
|
||||
return None
|
||||
for fmt, ln in (
|
||||
("%Y-%m-%d %H:%M:%S", 19),
|
||||
("%Y-%m-%d %H:%M:%S.%f", 26),
|
||||
("%Y-%m-%d %H:%M", 16),
|
||||
):
|
||||
try:
|
||||
dt = datetime.strptime(raw[:ln], fmt).replace(tzinfo=tz)
|
||||
return int(dt.timestamp() * 1000)
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
conn = self.get_db()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
sql = """
|
||||
SELECT id, inst_id, underlying, sheets, open_quote, close_quote,
|
||||
premium_paid, premium_received, realized_pnl, premium_ccy,
|
||||
created_at, closed_at
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
"""
|
||||
params: list[Any] = []
|
||||
if inst_id:
|
||||
sql += " AND inst_id = ?"
|
||||
params.append(str(inst_id).strip())
|
||||
sql += " ORDER BY id DESC LIMIT ?"
|
||||
params.append(int(max(1, min(int(limit), 500))))
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
out: list[dict[str, Any]] = []
|
||||
for r in rows:
|
||||
iid = str(r["inst_id"] or "").strip()
|
||||
if not iid:
|
||||
continue
|
||||
sheets = float(r["sheets"] or 0)
|
||||
if sheets <= 0:
|
||||
continue
|
||||
open_px = float(r["open_quote"] or 0) or None
|
||||
close_px = float(r["close_quote"] or 0) or None
|
||||
paid = float(r["premium_paid"] or 0) if r["premium_paid"] is not None else None
|
||||
recv = float(r["premium_received"] or 0) if r["premium_received"] is not None else None
|
||||
pnl = float(r["realized_pnl"]) if r["realized_pnl"] is not None else None
|
||||
if pnl is None and paid is not None and recv is not None:
|
||||
pnl = round(recv - paid, 8)
|
||||
if pnl is None:
|
||||
pnl = 0.0
|
||||
if paid is None and open_px is not None:
|
||||
paid = round(open_px * sheets * 0.01, 8)
|
||||
ccy = str(r["premium_ccy"] or "").strip().upper()
|
||||
if not ccy:
|
||||
u = str(r["underlying"] or iid.split("-")[0] or "ETH")
|
||||
ccy = premium_ccy_for_mode(margin_mode_from_inst_id(iid), u)
|
||||
if close_px is None and exchange is not None:
|
||||
try:
|
||||
bid, _ask, _ = _option_bid_ask(exchange, iid)
|
||||
if bid and float(bid) > 0:
|
||||
close_px = float(bid)
|
||||
except Exception:
|
||||
pass
|
||||
open_ms = _to_ms(r["created_at"])
|
||||
close_ms = _to_ms(r["closed_at"]) or open_ms
|
||||
opt_type, strike = option_fields_from_inst_id(iid)
|
||||
uly = str(r["underlying"] or iid.split("-")[0] or "").upper()
|
||||
pnl_ratio = None
|
||||
if paid is not None and abs(float(paid)) > 1e-12:
|
||||
pnl_ratio = float(pnl) / float(paid)
|
||||
out.append(
|
||||
{
|
||||
"instId": iid,
|
||||
"uly": f"{uly}-USD" if margin_mode_from_inst_id(iid) == "coin" else f"{uly}-USD_UM",
|
||||
"posId": f"sim-{int(r['id'])}",
|
||||
"openAvgPx": str(open_px) if open_px is not None else "",
|
||||
"closeAvgPx": str(close_px) if close_px is not None else "",
|
||||
"closeTotalPos": str(sheets),
|
||||
"openMaxPos": str(sheets),
|
||||
"realizedPnl": str(round(pnl, 8)),
|
||||
"pnl": str(round(pnl, 8)),
|
||||
"pnlRatio": str(round(pnl_ratio, 8)) if pnl_ratio is not None else "",
|
||||
"cTime": str(open_ms or ""),
|
||||
"uTime": str(close_ms or ""),
|
||||
"type": "2",
|
||||
"optType": opt_type or "",
|
||||
"stk": str(strike) if strike is not None else "",
|
||||
"_sim_premium_ccy": ccy,
|
||||
"_sim_premium_paid": paid,
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
@@ -355,6 +355,31 @@ def _patch_okx_options_lib(app_module: Any) -> None:
|
||||
pass
|
||||
return _orig_fetch_pos(ex)
|
||||
|
||||
_orig_fetch_hist = getattr(opt_lib, "fetch_option_position_history", None)
|
||||
_orig_fetch_all_hist = getattr(opt_lib, "fetch_all_option_positions_history", None)
|
||||
|
||||
def fetch_option_position_history(ex, inst_id, limit=50):
|
||||
try:
|
||||
if _GET_DB is not None and is_sim_mode(_GET_DB):
|
||||
return broker().option_positions_history_okx_rows(
|
||||
ex, inst_id=inst_id, limit=limit
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if callable(_orig_fetch_hist):
|
||||
return _orig_fetch_hist(ex, inst_id, limit=limit)
|
||||
return []
|
||||
|
||||
def fetch_all_option_positions_history(ex, *, limit=200):
|
||||
try:
|
||||
if _GET_DB is not None and is_sim_mode(_GET_DB):
|
||||
return broker().option_positions_history_okx_rows(ex, limit=limit)
|
||||
except Exception:
|
||||
pass
|
||||
if callable(_orig_fetch_all_hist):
|
||||
return _orig_fetch_all_hist(ex, limit=limit)
|
||||
return []
|
||||
|
||||
opt_lib.options_header_balances = options_header_balances
|
||||
opt_lib.fetch_options_balances = fetch_options_balances
|
||||
opt_lib.options_api_ready = options_api_ready
|
||||
@@ -363,6 +388,8 @@ def _patch_okx_options_lib(app_module: Any) -> None:
|
||||
opt_lib.fetch_option_order = fetch_option_order
|
||||
opt_lib.wait_option_order_full_fill = wait_option_order_full_fill
|
||||
opt_lib.fetch_option_positions = fetch_option_positions
|
||||
opt_lib.fetch_option_position_history = fetch_option_position_history
|
||||
opt_lib.fetch_all_option_positions_history = fetch_all_option_positions_history
|
||||
opt_lib._sim_hooks_applied = True
|
||||
|
||||
_patch_spot_bridge_lib()
|
||||
@@ -533,6 +560,8 @@ def patch_options_cfg(cfg: dict[str, Any]) -> dict[str, Any]:
|
||||
"fetch_option_positions",
|
||||
"fetch_option_order",
|
||||
"wait_option_order_full_fill",
|
||||
"fetch_option_position_history",
|
||||
"fetch_all_option_positions_history",
|
||||
):
|
||||
if key in cfg and hasattr(opt_lib, key):
|
||||
cfg[key] = getattr(opt_lib, key)
|
||||
|
||||
Reference in New Issue
Block a user