feat: add OKX options module with dual API, USDT/USDC convert, and docs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 08:17:27 +08:00
parent 814fe67a21
commit 806bb074ab
19 changed files with 2009 additions and 3 deletions
+19
View File
@@ -100,6 +100,25 @@ OKX_POSITION_INST_TYPE=SWAP
# 企业微信推送里展示的账户备注
# OKX_ACCOUNT_LABEL=
# =============================================================================
# 期权(主账户 API,与永续子账户 OKX_API_* 分离;修改后须重启 PM2)
# 详见 docs/期权方案.md 与 docs/期权用法.md
# =============================================================================
OKX_OPTIONS_ENABLED=false
OKX_OPTIONS_API_KEY=
OKX_OPTIONS_API_SECRET=
OKX_OPTIONS_API_PASSPHRASE=
OKX_OPTIONS_ACCOUNT_LABEL=主账户·期权
OKX_OPTIONS_TRADE_BUDGET_USDC=10
OKX_OPTIONS_BUDGET_BUFFER=0.95
OKX_OPTIONS_DEFAULT_UNDERLY=ETH
OKX_OPTIONS_MAX_DTE_DAYS=2
OKX_OPTIONS_ITM_MAX_DIST_USD=30
OKX_OPTIONS_PROFIT_ALERT_RATIO=1.0
OKX_OPTIONS_POLL_SECONDS=15
OKX_OPTIONS_TD_MODE=cross
OKX_OPTIONS_ALLOW_MARKET_CLOSE=false
# =============================================================================
# 关键位程序自动下单(与 POSITION_SIZING_MODE 联动,修改后须重启 PM2)
# =============================================================================
+46
View File
@@ -337,6 +337,12 @@ LIVE_TRADING_ENABLED = os.getenv("LIVE_TRADING_ENABLED", "false").lower() == "tr
OKX_API_KEY = os.getenv("OKX_API_KEY", "")
OKX_API_SECRET = os.getenv("OKX_API_SECRET", "")
OKX_API_PASSPHRASE = os.getenv("OKX_API_PASSPHRASE", "")
OKX_OPTIONS_ENABLED = os.getenv("OKX_OPTIONS_ENABLED", "false").lower() in ("1", "true", "yes", "on")
OKX_OPTIONS_API_KEY = os.getenv("OKX_OPTIONS_API_KEY", "")
OKX_OPTIONS_API_SECRET = os.getenv("OKX_OPTIONS_API_SECRET", "")
OKX_OPTIONS_API_PASSPHRASE = os.getenv("OKX_OPTIONS_API_PASSPHRASE", "")
OKX_OPTIONS_TRADE_BUDGET_USDC = float(os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC", "10"))
OKX_OPTIONS_DEFAULT_UNDERLY = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper()
OKX_TD_MODE = os.getenv("OKX_TD_MODE", "cross")
OKX_POS_MODE = os.getenv("OKX_POS_MODE", "hedge")
EXCHANGE_DISPLAY_NAME = (os.getenv("EXCHANGE_DISPLAY_NAME") or "OKX").strip() or "OKX"
@@ -462,6 +468,20 @@ if OKX_API_KEY and OKX_API_SECRET and OKX_API_PASSPHRASE:
exchange.apiKey = OKX_API_KEY
exchange.secret = OKX_API_SECRET
exchange.password = OKX_API_PASSPHRASE
exchange_options = ccxt.okx(
{
"enableRateLimit": True,
"options": {"defaultType": "option"},
}
)
if OKX_CCXT_PROXIES:
exchange_options.proxies = OKX_CCXT_PROXIES
if OKX_OPTIONS_API_KEY and OKX_OPTIONS_API_SECRET and OKX_OPTIONS_API_PASSPHRASE:
exchange_options.apiKey = OKX_OPTIONS_API_KEY
exchange_options.secret = OKX_OPTIONS_API_SECRET
exchange_options.password = OKX_OPTIONS_API_PASSPHRASE
MARKETS_LOADED = False
ACCOUNT_BALANCE_CACHE = {
"updated_at": 0.0,
@@ -1467,8 +1487,10 @@ def init_db():
)
from lib.strategy.strategy_db import init_strategy_tables
from lib.options.options_db import init_options_tables
init_strategy_tables(conn)
init_options_tables(conn)
from lib.trade.account_risk_lib import ensure_account_risk_schema
ensure_account_risk_schema(conn)
@@ -6660,6 +6682,9 @@ def render_main_page(page="trade", embed_mode=None):
key_rule_ctx=key_rule_ctx,
funds_fmt=format_funds_u,
exchange_display=EXCHANGE_DISPLAY_NAME,
options_enabled=OKX_OPTIONS_ENABLED,
options_trade_budget=OKX_OPTIONS_TRADE_BUDGET_USDC,
options_default_underly=OKX_OPTIONS_DEFAULT_UNDERLY,
risk_status=risk_status,
max_active_positions=MAX_ACTIVE_POSITIONS,
manual_min_planned_rr=MANUAL_MIN_PLANNED_RR,
@@ -6739,6 +6764,15 @@ def settings_page():
return render_main_page("settings")
@app.route("/options")
@login_required
def options_main_page():
if not OKX_OPTIONS_ENABLED:
flash("期权模块未启用,请在 .env 设置 OKX_OPTIONS_ENABLED=true")
return redirect(url_for("trade_page"))
return render_main_page("options")
@app.route("/api/account_snapshot")
@login_required
def api_account_snapshot():
@@ -8802,6 +8836,7 @@ _REPO_STATIC_DIR = common_static_dir(os.path.dirname(BASE_DIR))
_AI_REVIEW_RENDER_JS = os.path.join(_REPO_STATIC_DIR, "ai_review_render.js")
_FORM_SUBMIT_GUARD_JS = os.path.join(_REPO_STATIC_DIR, "form_submit_guard.js")
_MANUAL_ORDER_RR_PREVIEW_JS = os.path.join(_REPO_STATIC_DIR, "manual_order_rr_preview.js")
_OPTIONS_PANEL_JS = os.path.join(_REPO_STATIC_DIR, "options_panel.js")
@app.route("/static/ai_review_render.js")
@@ -8825,6 +8860,13 @@ def static_manual_order_rr_preview_js():
return send_file(_MANUAL_ORDER_RR_PREVIEW_JS, mimetype="application/javascript; charset=utf-8")
@app.route("/static/options_panel.js")
def static_options_panel_js():
if not os.path.isfile(_OPTIONS_PANEL_JS):
return Response("not found", status=404, mimetype="text/plain; charset=utf-8")
return send_file(_OPTIONS_PANEL_JS, mimetype="application/javascript; charset=utf-8")
@app.route("/export/review_md/<rid>")
@login_required
def export_review_md(rid):
@@ -9276,6 +9318,10 @@ from lib.strategy.strategy_trend_register import install_strategy_trend
install_strategy_trading(app, _REPO_ROOT, app_module=sys.modules[__name__])
install_strategy_trend(app, _REPO_ROOT, app_module=sys.modules[__name__])
from lib.options.options_register import install_options_trading
install_options_trading(app, _REPO_ROOT, app_module=sys.modules[__name__])
_purge_key_monitors_if_full_margin()