修复期权币本位高危缺陷:开仓估量、卖回封顶、握库超时与条件单嵌套锁。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,43 @@ from lib.options.options_spot_bridge_lib import (
|
||||
)
|
||||
|
||||
|
||||
def _sell_excess_bridge_coin(
|
||||
cfg: dict[str, Any],
|
||||
ex: Any,
|
||||
conn: Any,
|
||||
*,
|
||||
bridge_id: int,
|
||||
underlying: str,
|
||||
keep_coin: float,
|
||||
coin_bought: float,
|
||||
) -> dict[str, Any] | None:
|
||||
"""开仓后卖掉多买的币:卖量不超过 bridge 买入量,并保留权利金所需 keep_coin."""
|
||||
try:
|
||||
keep = max(0.0, float(keep_coin))
|
||||
except (TypeError, ValueError):
|
||||
keep = 0.0
|
||||
try:
|
||||
bought = max(0.0, float(coin_bought))
|
||||
except (TypeError, ValueError):
|
||||
bought = 0.0
|
||||
excess_cap = max(0.0, bought - keep)
|
||||
if excess_cap <= 0:
|
||||
return None
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
avail = _spot_bridge.fetch_trading_coin_available(ex, underlying)
|
||||
if avail is None:
|
||||
return None
|
||||
sell_amt = min(float(avail), excess_cap)
|
||||
if not _spot_bridge.is_sellable_spot_coin(ex, underlying, sell_amt):
|
||||
return None
|
||||
return _spot_bridge.spot_market_sell_coin_to_usdt(
|
||||
ex, underlying=underlying, coin_amount=sell_amt
|
||||
)
|
||||
|
||||
|
||||
def coin_budget_preview(cfg: dict[str, Any], ex: Any) -> dict[str, Any]:
|
||||
from lib.exchange.okx_options_lib import fetch_options_balances
|
||||
|
||||
@@ -69,19 +106,33 @@ def open_coin_option_buy_full(
|
||||
parts = inst_id.split("-")
|
||||
underlying = (parts[0] if parts else "ETH").upper()
|
||||
|
||||
# 先清粉尘 pending 桥(自管短连接),再开业务连接,避免模拟盘读余额嵌套锁库
|
||||
# 先清粉尘桥 + 空仓扫尾卖回(短连接),再开业务连接
|
||||
try:
|
||||
_spot_bridge.clear_dust_pending_sell_bridges(cfg["get_db"], ex)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
sweep_spot_after_options_flat(cfg, ex)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
conn: Any = None
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
block = _spot_bridge.bridge_blocks_new_open_msg(conn)
|
||||
if block:
|
||||
return {"ok": False, "msg": block, "can_open": False}
|
||||
|
||||
# 网络检查前再提交,避免握锁拉持仓/行情
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
compound_block = compound_full_single_position_block_msg(
|
||||
ex, fetch_positions=cfg.get("fetch_option_positions")
|
||||
)
|
||||
@@ -198,14 +249,45 @@ def open_coin_option_buy_full(
|
||||
}
|
||||
coin_bought = max(0.0, float(coin_after) - float(coin_before or 0))
|
||||
if coin_bought <= 0:
|
||||
# 落账延迟时退化为用当前可用,但仍写入上限提示
|
||||
coin_bought = float(coin_after)
|
||||
if coin_bought <= 0:
|
||||
rb = _spot_bridge.rollback_bought_coin_to_usdt(
|
||||
conn, ex, bridge_id=bridge_id, underlying=underlying, reason="买入量无效"
|
||||
)
|
||||
return {"ok": False, "msg": "买币后可用增量无效", "rollback": rb, "budget": budget_info}
|
||||
# 落账延迟:短重试取增量;绝不把整户余额当成「本次买入」
|
||||
for _ in range(3):
|
||||
time.sleep(0.6)
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import invalidate_options_balance_cache
|
||||
|
||||
invalidate_options_balance_cache()
|
||||
except Exception:
|
||||
pass
|
||||
again = _spot_bridge.fetch_trading_coin_available(ex, underlying)
|
||||
if again is None:
|
||||
continue
|
||||
coin_bought = max(0.0, float(again) - float(coin_before or 0))
|
||||
if coin_bought > 0:
|
||||
coin_after = again
|
||||
break
|
||||
if coin_bought <= 0:
|
||||
# 订单回执有数量则用之,否则回滚
|
||||
try:
|
||||
hinted = float(buy.get("coin_bought") or 0)
|
||||
except (TypeError, ValueError):
|
||||
hinted = 0.0
|
||||
if hinted > 0:
|
||||
coin_bought = hinted
|
||||
else:
|
||||
rb = _spot_bridge.rollback_bought_coin_to_usdt(
|
||||
conn, ex, bridge_id=bridge_id, underlying=underlying, reason="买入量无效"
|
||||
)
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": "买币后可用增量无效",
|
||||
"rollback": rb,
|
||||
"budget": budget_info,
|
||||
}
|
||||
_spot_bridge.update_bridge(conn, bridge_id, coin_bought=float(coin_bought))
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sizing = calc_sheets_from_coin_balance(
|
||||
quote_per_unit=float(ask),
|
||||
@@ -303,6 +385,17 @@ def open_coin_option_buy_full(
|
||||
fill_timeout = max(2.0, float(os.getenv("OKX_OPTIONS_OPEN_FILL_TIMEOUT_SEC") or "12"))
|
||||
except (TypeError, ValueError):
|
||||
fill_timeout = 12.0
|
||||
# 等待成交可能十余秒:先提交并释放业务连接,避免锁死中控/卖币
|
||||
try:
|
||||
conn.commit()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
conn = None
|
||||
|
||||
fill = wait_option_order_full_fill(
|
||||
ex,
|
||||
inst_id=inst_id,
|
||||
@@ -311,6 +404,9 @@ def open_coin_option_buy_full(
|
||||
timeout_sec=fill_timeout,
|
||||
cancel_on_timeout=True,
|
||||
)
|
||||
|
||||
conn = cfg["get_db"]()
|
||||
init_options_tables(conn)
|
||||
if not fill.get("ok"):
|
||||
filled_n = int(fill.get("filled_sheets") or 0)
|
||||
if filled_n <= 0:
|
||||
@@ -328,11 +424,27 @@ def open_coin_option_buy_full(
|
||||
premium_paid = round(float(ask) * eth_amount, 8)
|
||||
premium_ccy = premium_ccy_for_mode("coin", underlying)
|
||||
|
||||
# 部分成交/减半张数后卖掉多余买币(按 bridge coin_bought 封顶)
|
||||
need_coin = float(premium_paid)
|
||||
try:
|
||||
_sell_excess_bridge_coin(
|
||||
cfg,
|
||||
ex,
|
||||
conn,
|
||||
bridge_id=bridge_id,
|
||||
underlying=underlying,
|
||||
keep_coin=need_coin,
|
||||
coin_bought=float(coin_bought),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_spot_bridge.update_bridge(
|
||||
conn,
|
||||
bridge_id,
|
||||
status=BRIDGE_HOLDING,
|
||||
inst_id=inst_id,
|
||||
coin_bought=float(need_coin),
|
||||
message="期权持仓中",
|
||||
)
|
||||
|
||||
@@ -408,10 +520,11 @@ def open_coin_option_buy_full(
|
||||
"fill": fill,
|
||||
}
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
if conn is not None:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _insert_coin_trade(conn: Any, **kwargs: Any) -> int:
|
||||
@@ -502,19 +615,30 @@ def sweep_spot_after_options_flat(
|
||||
return {"ok": False, "msg": "exchange 未就绪"}
|
||||
live_uly: set[str] = set()
|
||||
try:
|
||||
rows = cfg["fetch_option_positions"](exchange) or []
|
||||
for p in rows:
|
||||
try:
|
||||
if abs(float(p.get("pos") or 0)) <= 1e-12:
|
||||
continue
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
inst = str(p.get("instId") or p.get("inst_id") or "").strip()
|
||||
if not inst:
|
||||
continue
|
||||
live_uly.add(inst.split("-")[0].upper())
|
||||
rows = cfg["fetch_option_positions"](exchange)
|
||||
except Exception as e:
|
||||
# 持仓查询失败:只清 pending 粉尘桥,绝不卖币
|
||||
try:
|
||||
_spot_bridge.clear_dust_pending_sell_bridges(cfg["get_db"], exchange)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": False, "msg": f"持仓查询失败: {e}"}
|
||||
if rows is None:
|
||||
try:
|
||||
_spot_bridge.clear_dust_pending_sell_bridges(cfg["get_db"], exchange)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": False, "msg": "持仓查询失败: 空响应"}
|
||||
for p in rows or []:
|
||||
try:
|
||||
if abs(float(p.get("pos") or 0)) <= 1e-12:
|
||||
continue
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
inst = str(p.get("instId") or p.get("inst_id") or "").strip()
|
||||
if not inst:
|
||||
continue
|
||||
live_uly.add(inst.split("-")[0].upper())
|
||||
|
||||
# 先关掉只剩粉尘的 pending 桥,避免拦开仓又看不到重试按钮
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user