feat期权盈亏按交易账户快照结算:一次一仓、持仓禁划转、卖回后冻结。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
"""期权已实现盈亏:交易账户 USDT 快照差分(一次一仓).
|
||||
|
||||
规格见 docs/规格-期权交易账户盈亏结算.md
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _safe_float(v: Any) -> float | None:
|
||||
if v is None or v == "":
|
||||
return None
|
||||
try:
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def fetch_trading_usdt(cfg: dict[str, Any] | None, ex: Any) -> float | None:
|
||||
"""读取交易账户 USDT(可用优先)."""
|
||||
fetch = (cfg or {}).get("fetch_options_balances") if isinstance(cfg, dict) else None
|
||||
if not callable(fetch):
|
||||
from lib.exchange.okx_options_lib import fetch_options_balances
|
||||
|
||||
fetch = fetch_options_balances
|
||||
try:
|
||||
bal = fetch(ex, force=True) or {}
|
||||
except TypeError:
|
||||
try:
|
||||
bal = fetch(ex) or {}
|
||||
except Exception:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
v = _safe_float(bal.get("trading_usdt_avail"))
|
||||
if v is None:
|
||||
v = _safe_float(bal.get("trading_usdt"))
|
||||
return round(float(v), 8) if v is not None else None
|
||||
|
||||
|
||||
def sellable_coin_blocks_open_msg(ex: Any, underlying: str) -> str | None:
|
||||
"""开仓前有可卖标的币则拒开."""
|
||||
from lib.options.options_spot_bridge_lib import (
|
||||
fetch_trading_coin_available,
|
||||
is_sellable_spot_coin,
|
||||
)
|
||||
|
||||
uly = (underlying or "").strip().upper()
|
||||
if uly not in ("ETH", "BTC"):
|
||||
return None
|
||||
try:
|
||||
avail = fetch_trading_coin_available(ex, uly)
|
||||
except Exception:
|
||||
return None
|
||||
if avail is None:
|
||||
return None
|
||||
if is_sellable_spot_coin(ex, uly, avail):
|
||||
return f"交易账户仍有可卖 {uly}({float(avail):g}),请先卖回 USDT 后再开仓"
|
||||
return None
|
||||
|
||||
|
||||
def transfer_blocked_by_position_msg(conn, ex: Any = None) -> str | None:
|
||||
"""持仓中或待卖回时禁止划转."""
|
||||
from lib.options.options_spot_bridge_lib import has_unfinished_bridge
|
||||
|
||||
try:
|
||||
if has_unfinished_bridge(conn):
|
||||
return "期权持仓或待卖回期间禁止划转,请先平仓并卖回 USDT"
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) AS n FROM options_trades WHERE status='open'"
|
||||
).fetchone()
|
||||
n = int(row["n"] if hasattr(row, "keys") else row[0])
|
||||
if n > 0:
|
||||
return "期权持仓中禁止划转"
|
||||
except Exception:
|
||||
pass
|
||||
if ex is not None:
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import fetch_option_positions
|
||||
|
||||
for p in fetch_option_positions(ex) or []:
|
||||
try:
|
||||
if abs(float(p.get("pos") or 0)) > 1e-12:
|
||||
return "存在未平期权持仓,禁止划转"
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def try_settle_after_sellback(
|
||||
get_db: Any,
|
||||
ex: Any,
|
||||
*,
|
||||
bridge_id: int | None = None,
|
||||
cfg: dict[str, Any] | None = None,
|
||||
) -> int:
|
||||
"""卖回路径通用结算:读交易账户 after 并冻结未结算单."""
|
||||
pack = cfg if isinstance(cfg, dict) else {"fetch_options_balances": None}
|
||||
after = fetch_trading_usdt(pack, ex)
|
||||
if after is None:
|
||||
return 0
|
||||
conn = get_db() if callable(get_db) else get_db
|
||||
close_conn = callable(get_db)
|
||||
try:
|
||||
from lib.options.options_db import init_options_tables
|
||||
|
||||
init_options_tables(conn)
|
||||
n = 0
|
||||
if bridge_id is not None:
|
||||
n = settle_closed_trades_from_trading_account(
|
||||
conn, trading_usdt_after=after, bridge_id=int(bridge_id)
|
||||
)
|
||||
if n < 1:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT id FROM options_trades
|
||||
WHERE status='closed' AND COALESCE(pnl_settled,0)=0
|
||||
AND trading_usdt_before IS NOT NULL
|
||||
ORDER BY id DESC LIMIT 1
|
||||
"""
|
||||
).fetchone()
|
||||
if row:
|
||||
n = settle_closed_trades_from_trading_account(
|
||||
conn,
|
||||
trading_usdt_after=after,
|
||||
trade_id=int(row["id"] if hasattr(row, "keys") else row[0]),
|
||||
)
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
return n
|
||||
finally:
|
||||
if close_conn:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def settle_closed_trades_from_trading_account(
|
||||
conn,
|
||||
*,
|
||||
trading_usdt_after: float,
|
||||
bridge_id: int | None = None,
|
||||
trade_id: int | None = None,
|
||||
) -> int:
|
||||
"""卖回后:对本桥/本单未结算已平仓位冻结 after−before.
|
||||
|
||||
已 pnl_settled=1 的行永不改写.
|
||||
"""
|
||||
after = _safe_float(trading_usdt_after)
|
||||
if after is None:
|
||||
return 0
|
||||
after = round(float(after), 8)
|
||||
clauses = ["status='closed'", "COALESCE(pnl_settled, 0)=0", "trading_usdt_before IS NOT NULL"]
|
||||
params: list[Any] = []
|
||||
if trade_id is not None:
|
||||
clauses.append("id=?")
|
||||
params.append(int(trade_id))
|
||||
elif bridge_id is not None:
|
||||
clauses.append("bridge_id=?")
|
||||
params.append(int(bridge_id))
|
||||
else:
|
||||
return 0
|
||||
rows = conn.execute(
|
||||
f"""
|
||||
SELECT id, trading_usdt_before, realized_pnl_usdt
|
||||
FROM options_trades
|
||||
WHERE {" AND ".join(clauses)}
|
||||
""",
|
||||
tuple(params),
|
||||
).fetchall()
|
||||
updated = 0
|
||||
for row in rows:
|
||||
before = _safe_float(row["trading_usdt_before"] if hasattr(row, "keys") else row[1])
|
||||
if before is None:
|
||||
continue
|
||||
pnl_u = round(float(after) - float(before), 4)
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET trading_usdt_after = ?,
|
||||
realized_pnl_usdt = ?,
|
||||
pnl_settled = 1
|
||||
WHERE id = ? AND COALESCE(pnl_settled, 0)=0
|
||||
""",
|
||||
(after, pnl_u, int(row["id"] if hasattr(row, "keys") else row[0])),
|
||||
)
|
||||
updated += 1
|
||||
return updated
|
||||
Reference in New Issue
Block a user