Improve OKX options sizing, expiry sync, and multi-position accordion UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-09 16:51:48 +08:00
parent d092f213a7
commit f41af0bf1c
7 changed files with 542 additions and 18 deletions
+72 -3
View File
@@ -132,6 +132,38 @@ def _require_options_ex(cfg: dict[str, Any]):
return ex, ""
def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
"""交易账户 USDC 可用余额(由 calc_order_size 再乘 budget_buffer 留余量)."""
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
raw = fetch_options_trading_usdc(ex)
if raw is None or float(raw) <= 0:
return None, "交易账户 USDC 可用余额不足"
return float(raw), ""
def _sync_options_trades(cfg: dict[str, Any]) -> None:
ex = cfg.get("exchange_options")
if ex is None:
return
from lib.exchange.okx_options_lib import fetch_option_position_history
from lib.options.options_monitor_lib import sync_open_options_trades
raw = cfg["fetch_option_positions"](ex)
live_ids = {str(p.get("instId") or "") for p in raw if str(p.get("instId") or "")}
def _hist(inst_id: str):
return fetch_option_position_history(ex, inst_id)
conn = cfg["get_db"]()
try:
init_options_tables(conn)
sync_open_options_trades(conn, live_inst_ids=live_ids, fetch_history_fn=_hist)
conn.commit()
finally:
conn.close()
def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
lr = cfg["login_required"]
@@ -177,6 +209,16 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
min_sz = q.get("min_sz") or 1
mode = (request.args.get("mode") or "budget_full").strip()
budget = cfg["trade_budget"]
budget_cap = cfg["trade_budget"]
available_usdc = None
if mode == "budget_full":
budget, budget_err = _budget_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
budget_cap = budget
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
available_usdc = fetch_options_trading_usdc(ex)
eth_amount = None
sheet_count = None
try:
@@ -199,7 +241,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
budget_buffer=cfg["budget_buffer"],
eth_amount=eth_amount if mode == "eth_amount" else None,
sheets=sheet_count if mode == "sheets" else None,
budget_cap=cfg["trade_budget"],
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
)
return jsonify(
{
@@ -207,6 +249,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
"quote_per_unit": ask,
"premium_per_sheet": premium_per_sheet(float(ask), float(ct_mult)),
"sizing": sizing,
"available_usdc": available_usdc,
"budget_full_usdc": budget if mode == "budget_full" else None,
}
)
@@ -242,15 +286,22 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
sheet_count = int(data.get("sheets"))
except (TypeError, ValueError):
return jsonify({"ok": False, "msg": "张数无效"})
budget = cfg["trade_budget"]
budget_cap = cfg["trade_budget"]
if mode == "budget_full":
budget, budget_err = _budget_full_usdc(cfg, ex)
if budget is None:
return jsonify({"ok": False, "msg": budget_err})
budget_cap = budget
sizing = calc_order_size(
quote_per_unit=float(ask),
ct_mult=ct_mult,
min_sz=min_sz,
budget_usdc=cfg["trade_budget"] if mode == "budget_full" else None,
budget_usdc=budget if mode == "budget_full" else None,
budget_buffer=cfg["budget_buffer"],
eth_amount=eth_amount,
sheets=sheet_count,
budget_cap=cfg["trade_budget"],
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
)
if not sizing.get("ok"):
return jsonify({"ok": False, "msg": sizing.get("msg") or "张数计算失败", "sizing": sizing})
@@ -304,6 +355,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
ex, err = _require_options_ex(cfg)
if ex is None:
return jsonify({"ok": False, "msg": err})
_sync_options_trades(cfg)
raw = cfg["fetch_option_positions"](ex)
rows = [cfg["format_position_row"](p) for p in raw]
conn = cfg["get_db"]()
@@ -554,6 +606,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
ex, err = _require_options_ex(cfg)
if ex is None:
return jsonify({"ok": False, "msg": err})
_sync_options_trades(cfg)
conn = cfg["get_db"]()
try:
init_options_tables(conn)
@@ -660,6 +713,21 @@ def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
raw = cfg["fetch_option_positions"](ex)
return [cfg["format_position_row"](p) for p in raw]
def _sync(conn):
from lib.exchange.okx_options_lib import fetch_option_position_history
from lib.options.options_monitor_lib import sync_open_options_trades
ex = cfg.get("exchange_options")
if ex is None:
return 0
raw = cfg["fetch_option_positions"](ex)
live_ids = {str(p.get("instId") or "") for p in raw if str(p.get("instId") or "")}
return sync_open_options_trades(
conn,
live_inst_ids=live_ids,
fetch_history_fn=lambda inst_id: fetch_option_position_history(ex, inst_id),
)
t = threading.Thread(
target=options_monitor_loop,
kwargs={
@@ -671,6 +739,7 @@ def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
"send_wechat": cfg["send_wechat"],
"account_label": cfg["account_label"],
"profit_ratio": cfg["profit_ratio"],
"sync_trades_fn": _sync,
},
daemon=True,
name="options-monitor",