"""期权已实现盈亏:交易账户 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 sellable_coin_blocks_settle( ex: Any, underlying: str | None, ) -> 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}),暂不结算账户盈亏" return None def _underlying_for_settle( conn, *, bridge_id: int | None = None, trade_id: int | None = None, ) -> str | None: try: if trade_id is not None: row = conn.execute( "SELECT underlying FROM options_trades WHERE id=?", (int(trade_id),), ).fetchone() if row: return str(row["underlying"] if hasattr(row, "keys") else row[0] or "").strip().upper() or None if bridge_id is not None: row = conn.execute( """ SELECT underlying FROM options_trades WHERE bridge_id=? AND status='closed' ORDER BY id DESC LIMIT 1 """, (int(bridge_id),), ).fetchone() if row: return str(row["underlying"] if hasattr(row, "keys") else row[0] or "").strip().upper() or None brow = conn.execute( "SELECT underlying FROM options_spot_bridge WHERE id=?", (int(bridge_id),), ).fetchone() if brow: return str(brow["underlying"] if hasattr(brow, "keys") else brow[0] or "").strip().upper() or None except Exception: return None return None def try_settle_after_sellback( get_db: Any, ex: Any, *, bridge_id: int | None = None, cfg: dict[str, Any] | None = None, underlying: str | None = None, sell_complete: bool | None = None, ) -> int: """卖回路径通用结算:读交易账户 after 并冻结未结算单. 门禁:sell_complete 显式 False 时不结算;仍有可卖标的币时不结算. """ if sell_complete is False: return 0 pack = cfg if isinstance(cfg, dict) else {"fetch_options_balances": None} import sqlite3 # Windows 上 sqlite3.Connection 的 callable() 可能为 True,不能据此判断 get_db if isinstance(get_db, sqlite3.Connection): conn = get_db close_conn = False elif callable(get_db): conn = get_db() close_conn = True else: conn = get_db close_conn = False try: from lib.options.options_db import init_options_tables init_options_tables(conn) uly = (underlying or "").strip().upper() or None if not uly: uly = _underlying_for_settle(conn, bridge_id=bridge_id) block = sellable_coin_blocks_settle(ex, uly) if block: return 0 after = fetch_trading_usdt(pack, ex) if after is None: return 0 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: tid = int(row["id"] if hasattr(row, "keys") else row[0]) if not uly: uly = _underlying_for_settle(conn, trade_id=tid) block2 = sellable_coin_blocks_settle(ex, uly) if block2: return 0 n = settle_closed_trades_from_trading_account( conn, trading_usdt_after=after, trade_id=tid, ) 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