"""期权已实现盈亏:交易账户 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