下线永续监控与 USDC 期权,仅保留币本位期权路径。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -116,7 +116,7 @@ OKX_OPTIONS_ENABLED=true
|
||||
# OKX_OPTIONS_API_PASSPHRASE=
|
||||
OKX_OPTIONS_ACCOUNT_LABEL=账户·期权
|
||||
|
||||
# 固定币本位(USDT买币桥);历史 OKX_OPTIONS_MARGIN_MODE=usdc 会被忽略并视为 coin
|
||||
# 仅币本位期权(USDT买币桥);USDC 期权已下线,此项恒视为 coin
|
||||
OKX_OPTIONS_MARGIN_MODE=coin
|
||||
OKX_OPTIONS_COIN_COMPOUND=true
|
||||
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
||||
|
||||
@@ -5117,23 +5117,9 @@ def force_close_before_reset():
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# 后台线程
|
||||
# 后台线程(永续已下线:不再跑划转/强平/关键位/委托监控;期权监控见 options_monitor_loop)
|
||||
def background_task():
|
||||
while True:
|
||||
try:
|
||||
auto_transfer_once_per_day()
|
||||
# force_close 自带连接,勿套在另一未关闭连接上
|
||||
force_close_before_reset()
|
||||
conn = get_db()
|
||||
try:
|
||||
reconcile_external_closes(conn)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
check_key_monitors()
|
||||
check_order_monitors()
|
||||
except Exception as e:
|
||||
print(f"[monitor_loop] {e}", flush=True)
|
||||
time.sleep(MONITOR_POLL_SECONDS)
|
||||
|
||||
|
||||
@@ -5637,16 +5623,14 @@ def index():
|
||||
@app.route("/key_monitor")
|
||||
@login_required
|
||||
def key_monitor_page():
|
||||
redir = redirect_to_embed_shell_if_enabled("key_monitor")
|
||||
if redir is not None:
|
||||
return redir
|
||||
return render_main_page("key_monitor")
|
||||
# 永续关键位监控已下线
|
||||
return redirect("/options")
|
||||
|
||||
|
||||
@app.route("/trade")
|
||||
@login_required
|
||||
def trade_page():
|
||||
# 实盘下单界面已移除;永续下单仍由对冲计划走 place_exchange_order
|
||||
# 永续实盘下单已下线
|
||||
return redirect("/options")
|
||||
|
||||
|
||||
@@ -6303,7 +6287,7 @@ def order_focus():
|
||||
@login_required
|
||||
def add_order():
|
||||
# 实盘下单界面已移除;永续开平仓请走对冲计划(place_exchange_order / close_exchange_order)
|
||||
flash("实盘下单界面已移除,请使用对冲计划或期权页")
|
||||
flash("永续实盘下单已下线,请使用期权页")
|
||||
return redirect("/options")
|
||||
|
||||
|
||||
@@ -7679,5 +7663,5 @@ if __name__ == "__main__":
|
||||
from lib.common.flask_access_log_lib import silence_werkzeug_access_log
|
||||
|
||||
silence_werkzeug_access_log()
|
||||
threading.Thread(target=background_task, daemon=True).start()
|
||||
# 永续 background_task 已下线;期权监控由 install_options_trading 启动
|
||||
app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True)
|
||||
|
||||
@@ -415,41 +415,25 @@
|
||||
|
||||
function syncOptionsSectionVisibility(body) {
|
||||
if (!body) return;
|
||||
var margin = (envFieldValue(body, "OKX_OPTIONS_MARGIN_MODE", "coin") || "coin").toLowerCase();
|
||||
var isCoin = margin !== "usdc";
|
||||
// 仅币本位期权:USDC/永续相关 env 始终隐藏
|
||||
var isCoin = true;
|
||||
var gateMode = (envFieldValue(body, "OKX_OPTIONS_CLOSE_GATE_MODE", "premium") || "premium").toLowerCase();
|
||||
var compoundOn = envTruthy(envFieldValue(body, "OKX_OPTIONS_COMPOUND_FULL_ENABLED", "true"));
|
||||
var coinCompoundOn = envTruthy(envFieldValue(body, "OKX_OPTIONS_COIN_COMPOUND", "true"));
|
||||
var coinMaxOn = envTruthy(envFieldValue(body, "OKX_OPTIONS_COIN_MAX_USDT_ENABLED", "false"));
|
||||
var compoundCapOn = envTruthy(envFieldValue(body, "OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", "false"));
|
||||
|
||||
COIN_ONLY_ENV_KEYS.forEach(function (key) {
|
||||
setEnvRowHidden(envFieldRowByKey(body, key), !isCoin);
|
||||
});
|
||||
USDC_ONLY_ENV_KEYS.forEach(function (key) {
|
||||
setEnvRowHidden(envFieldRowByKey(body, key), isCoin);
|
||||
setEnvRowHidden(envFieldRowByKey(body, key), true);
|
||||
});
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_MARGIN_MODE"), true);
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_SHOW_PERP_FUNDS"), true);
|
||||
|
||||
// 门控子项:按模式显隐(仍受本位过滤)
|
||||
if (isCoin) {
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN"), gateMode !== "premium");
|
||||
} else {
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_CLOSE_RECYCLE_MULT"), gateMode !== "premium");
|
||||
}
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN"), gateMode !== "premium");
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_CLOSE_NET_PNL_MIN_USDT"), gateMode !== "net_pnl");
|
||||
|
||||
if (!isCoin) {
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_TRADE_BUDGET_USDC"), compoundOn);
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED"), !compoundOn);
|
||||
setEnvRowHidden(
|
||||
envFieldRowByKey(body, "OKX_OPTIONS_COMPOUND_FULL_CAP_USDC"),
|
||||
!(compoundOn && compoundCapOn)
|
||||
);
|
||||
}
|
||||
if (isCoin) {
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COIN_BUDGET_USDT"), coinCompoundOn);
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COIN_MAX_USDT"), !coinMaxOn);
|
||||
}
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COIN_BUDGET_USDT"), coinCompoundOn);
|
||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COIN_MAX_USDT"), !coinMaxOn);
|
||||
}
|
||||
|
||||
function bindOptionsSectionVisibility(body) {
|
||||
|
||||
Vendored
+13
-2
@@ -313,16 +313,27 @@ def _field_current(fields: list[dict[str, Any]], key: str, default: str = "") ->
|
||||
|
||||
|
||||
def _mark_options_section_hidden(fields: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""按门控模式/复利/单笔上限标记 hidden."""
|
||||
"""按门控模式/复利/单笔上限标记 hidden;USDC/永续相关项始终隐藏."""
|
||||
gate_mode = (_field_current(fields, "OKX_OPTIONS_CLOSE_GATE_MODE", "premium") or "premium").lower()
|
||||
coin_compound_on = _env_truthy(_field_current(fields, "OKX_OPTIONS_COIN_COMPOUND", "true"))
|
||||
coin_max_on = _env_truthy(_field_current(fields, "OKX_OPTIONS_COIN_MAX_USDT_ENABLED", "false"))
|
||||
retired = {
|
||||
"OKX_OPTIONS_MARGIN_MODE",
|
||||
"OKX_OPTIONS_TRADE_BUDGET_USDC",
|
||||
"OKX_OPTIONS_COMPOUND_FULL_ENABLED",
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED",
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_USDC",
|
||||
"OKX_OPTIONS_CLOSE_RECYCLE_MULT",
|
||||
"OKX_SHOW_PERP_FUNDS",
|
||||
}
|
||||
|
||||
out: list[dict[str, Any]] = []
|
||||
for f in fields:
|
||||
key = str(f.get("key") or "")
|
||||
hide = False
|
||||
if key == "OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN" and gate_mode != "premium":
|
||||
if key in retired:
|
||||
hide = True
|
||||
elif key == "OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN" and gate_mode != "premium":
|
||||
hide = True
|
||||
elif key == "OKX_OPTIONS_CLOSE_NET_PNL_MIN_USDT" and gate_mode != "net_pnl":
|
||||
hide = True
|
||||
|
||||
@@ -814,7 +814,7 @@ def build_option_chain(
|
||||
|
||||
family = inst_family_for_underlying(u, margin_mode=margin_mode)
|
||||
except Exception:
|
||||
family = f"{u}-USD_UM"
|
||||
family = f"{u}-USD"
|
||||
uly = f"{u}-USD"
|
||||
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
||||
now_ms = time.time() * 1000
|
||||
|
||||
@@ -10,8 +10,8 @@ DISPLAY_RUNTIME_PREFIX = "display."
|
||||
DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
||||
"show_nav_dashboard": False,
|
||||
"show_nav_account_ledger": False,
|
||||
"show_nav_key_monitor": True,
|
||||
"show_nav_trade": False, # 实盘下单界面已移除
|
||||
"show_nav_key_monitor": False, # 永续关键位已下线
|
||||
"show_nav_trade": False, # 永续实盘下单已下线
|
||||
"show_nav_strategy": False,
|
||||
"show_nav_strategy_records": False,
|
||||
"show_nav_records": False, # 永续交易记录与复盘已移除
|
||||
@@ -22,10 +22,10 @@ DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
||||
"show_nav_env_config": True,
|
||||
"show_nav_options": True,
|
||||
"show_nav_options_review": True,
|
||||
"show_settings_transfer": False, # 永续资金划转默认隐藏
|
||||
"show_settings_transfer": False, # 永续资金划转已下线
|
||||
"show_settings_export": True,
|
||||
"show_settings_password": True,
|
||||
"show_settings_options_swap": True,
|
||||
"show_settings_options_swap": False, # USDC 币种兑换已下线
|
||||
"show_settings_options_transfer": True,
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ def display_prefs_template_context(get_db: Callable) -> dict[str, Any]:
|
||||
def tab_allowed(tab: str, display: Optional[dict[str, bool]] = None) -> bool:
|
||||
prefs = normalize_display_prefs(display or {})
|
||||
t = (tab or "").strip()
|
||||
if t in ("trade", "records", "stats", "hedge_plan"):
|
||||
return False # 实盘下单 / 交易记录复盘 / 统计分析 / 对冲计划 已移除
|
||||
if t in ("trade", "records", "stats", "hedge_plan", "key_monitor"):
|
||||
return False # 永续相关页面已下线
|
||||
key = NAV_TAB_ALLOWED.get(t)
|
||||
if not key:
|
||||
return True
|
||||
@@ -123,7 +123,6 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
||||
nav_keys = [
|
||||
"show_nav_dashboard",
|
||||
"show_nav_account_ledger",
|
||||
"show_nav_key_monitor",
|
||||
"show_nav_risk_policy",
|
||||
"show_nav_live_guide",
|
||||
"show_nav_system_guide",
|
||||
@@ -132,10 +131,8 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
||||
"show_nav_options_review",
|
||||
]
|
||||
settings_keys = [
|
||||
"show_settings_transfer",
|
||||
"show_settings_export",
|
||||
"show_settings_password",
|
||||
"show_settings_options_swap",
|
||||
"show_settings_options_transfer",
|
||||
]
|
||||
return [
|
||||
|
||||
@@ -127,21 +127,10 @@ def build_settings_tabs(display: dict[str, Any] | None, instance_settings: dict[
|
||||
tabs.append({"key": "sim_funds", "title": "模拟资金"})
|
||||
if disp.get("show_settings_password", True):
|
||||
tabs.append({"key": "password", "title": "账户密码"})
|
||||
if inst.get("show_transfer") and disp.get("show_settings_transfer", False):
|
||||
tabs.append({"key": "transfer", "title": "永续划转"})
|
||||
# 永续划转 / USDC 币种兑换已下线
|
||||
if disp.get("show_settings_export", True):
|
||||
tabs.append({"key": "export", "title": "数据导出"})
|
||||
tabs.append({"key": "backup", "title": "备份恢复"})
|
||||
if inst.get("options_settings_enabled") and disp.get("show_settings_options_swap", True):
|
||||
_coin = False
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import is_coin_margin_mode
|
||||
|
||||
_coin = bool(is_coin_margin_mode())
|
||||
except Exception:
|
||||
_coin = False
|
||||
if not _coin:
|
||||
tabs.append({"key": "options_swap", "title": "币种兑换"})
|
||||
if inst.get("options_settings_enabled") and disp.get("show_settings_options_transfer", True):
|
||||
tabs.append({"key": "options_transfer", "title": "期权划转"})
|
||||
return tabs
|
||||
|
||||
@@ -642,29 +642,23 @@ def _try_open_full(
|
||||
inst_id: str,
|
||||
order: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
from lib.options.options_margin_mode_lib import is_coin_margin_mode
|
||||
from lib.options.options_coin_open_lib import open_coin_option_buy_full
|
||||
|
||||
note = f"中控条件单:{order.get('hub_order_id')}"
|
||||
target_index = _safe_float(order.get("target_index"))
|
||||
pe_en = bool(order.get("profit_exit_enabled"))
|
||||
pe_mult = float(order.get("profit_exit_mult") or 1.0)
|
||||
if is_coin_margin_mode():
|
||||
return open_coin_option_buy_full(
|
||||
cfg,
|
||||
ex,
|
||||
inst_id=inst_id,
|
||||
signal_note=note,
|
||||
target_index=target_index,
|
||||
profit_exit_enabled=pe_en,
|
||||
profit_exit_mult=pe_mult,
|
||||
target_sheets=None,
|
||||
)
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": "条件单开仓当前仅支持币本位客户端规则(买币→开仓)",
|
||||
"need_depth": False,
|
||||
}
|
||||
# USDC 期权开仓已下线,条件单仅走币本位
|
||||
return open_coin_option_buy_full(
|
||||
cfg,
|
||||
ex,
|
||||
inst_id=inst_id,
|
||||
signal_note=note,
|
||||
target_index=target_index,
|
||||
profit_exit_enabled=pe_en,
|
||||
profit_exit_mult=pe_mult,
|
||||
target_sheets=None,
|
||||
)
|
||||
|
||||
|
||||
def run_cond_orders_tick(
|
||||
|
||||
@@ -35,11 +35,10 @@ def is_coin_margin_mode(raw: Any = None) -> bool:
|
||||
|
||||
|
||||
def inst_family_for_underlying(underlying: str, *, margin_mode: str | None = None) -> str:
|
||||
"""仅币本位期权族 {U}-USD;USD_UM 已下线."""
|
||||
del margin_mode
|
||||
u = (underlying or "ETH").strip().upper() or "ETH"
|
||||
mode = normalize_options_margin_mode(margin_mode)
|
||||
if mode == MODE_COIN:
|
||||
return f"{u}-USD"
|
||||
return f"{u}-USD_UM"
|
||||
return f"{u}-USD"
|
||||
|
||||
|
||||
def margin_mode_from_inst_id(inst_id: str) -> str:
|
||||
@@ -55,9 +54,10 @@ def margin_mode_from_inst_id(inst_id: str) -> str:
|
||||
|
||||
|
||||
def premium_ccy_for_mode(margin_mode: str, underlying: str = "ETH") -> str:
|
||||
if normalize_options_margin_mode(margin_mode) == MODE_COIN:
|
||||
return (underlying or "ETH").strip().upper() or "ETH"
|
||||
return "USDC"
|
||||
"""币本位权利金为标的币;历史 USDC 行(margin_mode=usdc)仍标 USDC."""
|
||||
if str(margin_mode or "").strip().lower() == MODE_USDC:
|
||||
return "USDC"
|
||||
return (underlying or "ETH").strip().upper() or "ETH"
|
||||
|
||||
|
||||
def spot_quote_inst_id(underlying: str) -> str:
|
||||
|
||||
+11
-617
@@ -11,18 +11,7 @@ from jinja2 import ChoiceLoader, FileSystemLoader
|
||||
|
||||
from lib.options.options_db import init_options_tables, sum_open_premium_paid, sum_open_sheets
|
||||
from lib.options.options_monitor_lib import options_monitor_loop
|
||||
from lib.options.options_pricing_lib import (
|
||||
calc_order_size,
|
||||
ct_mult_from_meta,
|
||||
min_sz_from_meta,
|
||||
premium_per_sheet,
|
||||
)
|
||||
from lib.exchange.okx_options_lib import (
|
||||
_safe_float,
|
||||
cap_option_buy_sheets_to_ask_depth,
|
||||
option_buy_liquidity_ok,
|
||||
td_mode_for_option_buy,
|
||||
)
|
||||
from lib.exchange.okx_options_lib import _safe_float
|
||||
|
||||
|
||||
def _env_bool(key: str, default: bool = False) -> bool:
|
||||
@@ -253,84 +242,19 @@ def _run_manual_bid_close(
|
||||
return result
|
||||
|
||||
|
||||
def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||
"""打满可用额度 = min(交易户可用 USDC, 单笔预算);calc_order_size 再乘 budget_buffer."""
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
from lib.options.options_pricing_lib import resolve_budget_full_usdc
|
||||
|
||||
raw = fetch_options_trading_usdc(ex)
|
||||
if raw is None or float(raw) <= 0:
|
||||
return None, "交易账户 USDC 可用余额不足"
|
||||
trading = float(raw)
|
||||
cap = _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", float(cfg.get("trade_budget") or 10.0))
|
||||
if cap <= 0:
|
||||
return None, "单笔预算无效(OKX_OPTIONS_TRADE_BUDGET_USDC)"
|
||||
return resolve_budget_full_usdc(trading, float(cap)), ""
|
||||
|
||||
|
||||
def _compound_full_enabled() -> bool:
|
||||
return _env_bool("OKX_OPTIONS_COMPOUND_FULL_ENABLED", True)
|
||||
|
||||
|
||||
def _budget_full_blocked_by_compound_msg() -> str | None:
|
||||
if _compound_full_enabled():
|
||||
return "全仓复利已开启,不可使用单笔预算/打满;请关闭全仓复利或改用全仓复利模式"
|
||||
return None
|
||||
|
||||
|
||||
def _size_mode_budget_cap(
|
||||
cfg: dict[str, Any], mode: str, budget_cap: float | None
|
||||
) -> float | None:
|
||||
"""全仓复利开启时禁用单笔预算封顶(sheets/eth 也不再受 trade_budget 限制)."""
|
||||
if mode in ("budget_full", "compound_full"):
|
||||
return budget_cap
|
||||
if mode in ("sheets", "eth_amount"):
|
||||
if _compound_full_enabled():
|
||||
return None
|
||||
return budget_cap
|
||||
return None
|
||||
"""USDC 全仓复利已下线;恒为 False,避免前端残留 compound_full 模式."""
|
||||
return False
|
||||
|
||||
|
||||
def _normalize_size_mode(mode: str) -> tuple[str, str | None]:
|
||||
"""全仓复利关闭时强制离开 compound_full,避免前端残留选中导致无法开仓."""
|
||||
"""USDC 预算/全仓模式已下线;统一落到指定张数."""
|
||||
m = (mode or "sheets").strip() or "sheets"
|
||||
if m == "compound_full" and not _compound_full_enabled():
|
||||
return "sheets", "全仓复利已关闭,已改用指定张数"
|
||||
if m == "budget_full" and _compound_full_enabled():
|
||||
return "compound_full", None
|
||||
if m in ("compound_full", "budget_full"):
|
||||
return "sheets", "USDC 期权模式已下线,已改用指定张数"
|
||||
return m, None
|
||||
|
||||
|
||||
def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||
"""全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer."""
|
||||
if not _compound_full_enabled():
|
||||
return None, "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)"
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
from lib.options.options_pricing_lib import resolve_compound_full_usdc
|
||||
|
||||
raw = fetch_options_trading_usdc(ex)
|
||||
if raw is None or float(raw) <= 0:
|
||||
return None, "交易账户 USDC 可用余额不足"
|
||||
trading = float(raw)
|
||||
# 额度热更读 env(与模板启动值无关)
|
||||
cap_on = _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False)
|
||||
cap_v = _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0)
|
||||
if cap_on and cap_v <= 0:
|
||||
return None, "全仓上限无效(OKX_OPTIONS_COMPOUND_FULL_CAP_USDC)"
|
||||
return (
|
||||
resolve_compound_full_usdc(
|
||||
trading,
|
||||
cap_enabled=cap_on,
|
||||
cap_usdc=cap_v,
|
||||
),
|
||||
"",
|
||||
)
|
||||
|
||||
|
||||
def _is_budget_mode(mode: str) -> bool:
|
||||
return mode in ("budget_full", "compound_full")
|
||||
|
||||
|
||||
def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
@@ -822,228 +746,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
)
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": f"币本位报价失败: {e}"})
|
||||
|
||||
budget = cfg["trade_budget"]
|
||||
budget_cap = cfg["trade_budget"]
|
||||
available_usdc = None
|
||||
if mode == "budget_full":
|
||||
blocked = _budget_full_blocked_by_compound_msg()
|
||||
if blocked:
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": blocked,
|
||||
"compound_full_enabled": _compound_full_enabled(),
|
||||
}
|
||||
)
|
||||
budget, budget_err = _budget_full_usdc(cfg, ex)
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err, "compound_full_enabled": _compound_full_enabled()})
|
||||
budget_cap = budget
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
|
||||
available_usdc = fetch_options_trading_usdc(ex)
|
||||
elif mode == "compound_full":
|
||||
if not _compound_full_enabled():
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)",
|
||||
"compound_full_enabled": False,
|
||||
}
|
||||
)
|
||||
budget, budget_err = _compound_full_usdc(cfg, ex)
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err, "compound_full_enabled": True})
|
||||
budget_cap = budget
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
|
||||
available_usdc = fetch_options_trading_usdc(ex)
|
||||
elif mode in ("sheets", "eth_amount") and _compound_full_enabled():
|
||||
budget_cap = None
|
||||
eth_amount = None
|
||||
try:
|
||||
if request.args.get("eth_amount"):
|
||||
eth_amount = float(request.args.get("eth_amount"))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
ask = q.get("ask")
|
||||
ask_sz = q.get("ask_sz")
|
||||
try:
|
||||
from lib.options.okx_trade_mode_lib import block_standalone_open_by_mode_msg
|
||||
|
||||
mode_block = block_standalone_open_by_mode_msg()
|
||||
if mode_block:
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": mode_block,
|
||||
"quote_per_unit": ask,
|
||||
"premium_per_sheet": None,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": mode_block,
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"can_open": False,
|
||||
"msg": f"交易模式校验失败: {e}",
|
||||
}
|
||||
)
|
||||
can_open, block_msg = option_buy_liquidity_ok(ask, ask_sz)
|
||||
if not can_open:
|
||||
# 合约可报价,但不可开仓:返回参考标记价供展示
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": block_msg or q.get("open_block_msg") or "暂无卖一深度,无法买入",
|
||||
"quote_per_unit": None,
|
||||
"premium_per_sheet": None,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": block_msg or "暂无卖一深度,无法买入",
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
from lib.options.options_position_limit_lib import (
|
||||
compound_full_single_position_block_msg,
|
||||
option_position_limit_block_msg,
|
||||
)
|
||||
|
||||
if mode == "compound_full":
|
||||
compound_block = compound_full_single_position_block_msg(
|
||||
ex, fetch_positions=cfg.get("fetch_option_positions")
|
||||
)
|
||||
if compound_block:
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": compound_block,
|
||||
"quote_per_unit": ask,
|
||||
"premium_per_sheet": None,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": compound_block,
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": None,
|
||||
"compound_full_usdc": budget,
|
||||
}
|
||||
)
|
||||
|
||||
pos_limit_msg = option_position_limit_block_msg(
|
||||
ex,
|
||||
opening_inst_id=inst_id,
|
||||
fetch_positions=cfg.get("fetch_option_positions"),
|
||||
)
|
||||
if pos_limit_msg:
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": pos_limit_msg,
|
||||
"quote_per_unit": ask,
|
||||
"premium_per_sheet": None,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": pos_limit_msg,
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
sizing = calc_order_size(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=float(ct_mult),
|
||||
min_sz=int(min_sz),
|
||||
budget_usdc=budget if _is_budget_mode(mode) else 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=_size_mode_budget_cap(cfg, mode, budget_cap)
|
||||
if mode in ("budget_full", "compound_full", "sheets", "eth_amount")
|
||||
else None,
|
||||
)
|
||||
if sizing.get("ok"):
|
||||
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(
|
||||
int(sizing.get("sheets") or 0),
|
||||
ask_sz,
|
||||
min_sz=int(min_sz),
|
||||
)
|
||||
if capped is None:
|
||||
sizing = {
|
||||
"ok": False,
|
||||
"msg": cap_msg,
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
}
|
||||
elif capped < int(sizing.get("sheets") or 0):
|
||||
sizing = calc_order_size(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=float(ct_mult),
|
||||
min_sz=int(min_sz),
|
||||
sheets=capped,
|
||||
budget_cap=_size_mode_budget_cap(cfg, mode, budget_cap)
|
||||
if mode in ("budget_full", "compound_full", "sheets", "eth_amount")
|
||||
else None,
|
||||
)
|
||||
if sizing.get("ok"):
|
||||
sizing["ask_depth_capped"] = True
|
||||
sizing["ask_sz"] = ask_sz
|
||||
sizing["msg"] = f"已按卖一深度限制为 {capped} 张"
|
||||
q = _attach_close_preview(
|
||||
cfg,
|
||||
ex,
|
||||
q,
|
||||
sheets=int(sizing.get("sheets") or sheet_count or 0),
|
||||
premium_paid=_open_premium_paid(cfg, inst_id),
|
||||
)
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"can_open": True,
|
||||
"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,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
"mode": mode,
|
||||
"mode_note": mode_note,
|
||||
"compound_full_enabled": _compound_full_enabled(),
|
||||
}
|
||||
)
|
||||
return jsonify({"ok": False, "msg": "仅支持币本位期权报价", "can_open": False})
|
||||
|
||||
@app.route("/api/options/open", methods=["POST"])
|
||||
@lr
|
||||
@@ -1141,272 +844,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": f"币本位开仓失败: {e}"})
|
||||
q = cfg["quote_option_contract"](ex, inst_id)
|
||||
if not q.get("ok"):
|
||||
return jsonify(q)
|
||||
ask = q.get("ask")
|
||||
ask_sz = q.get("ask_sz")
|
||||
can_open, block_msg = option_buy_liquidity_ok(ask, ask_sz)
|
||||
if not can_open:
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": block_msg or q.get("open_block_msg") or "暂无卖一深度,无法买入",
|
||||
"can_open": False,
|
||||
"mark": q.get("mark"),
|
||||
"ref_ask": q.get("ref_ask"),
|
||||
}
|
||||
)
|
||||
from lib.options.options_position_limit_lib import (
|
||||
compound_full_single_position_block_msg,
|
||||
option_position_limit_block_msg,
|
||||
)
|
||||
|
||||
if mode == "compound_full":
|
||||
compound_block = compound_full_single_position_block_msg(
|
||||
ex, fetch_positions=cfg.get("fetch_option_positions")
|
||||
)
|
||||
if compound_block:
|
||||
return jsonify({"ok": False, "msg": compound_block, "can_open": False})
|
||||
|
||||
pos_limit_msg = option_position_limit_block_msg(
|
||||
ex,
|
||||
opening_inst_id=inst_id,
|
||||
fetch_positions=cfg.get("fetch_option_positions"),
|
||||
)
|
||||
if pos_limit_msg:
|
||||
return jsonify({"ok": False, "msg": pos_limit_msg, "can_open": False})
|
||||
ct_mult = float(q.get("ct_mult") or 0.01)
|
||||
min_sz = int(q.get("min_sz") or 1)
|
||||
eth_amount = None
|
||||
sheet_count = None
|
||||
if mode == "eth_amount":
|
||||
try:
|
||||
eth_amount = float(data.get("eth_amount"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"ok": False, "msg": "ETH 数量无效"})
|
||||
elif mode == "sheets":
|
||||
try:
|
||||
sheet_count = int(data.get("sheets"))
|
||||
except (TypeError, ValueError):
|
||||
sheet_count = None
|
||||
if sheet_count is None or int(sheet_count) < 1:
|
||||
# 全仓复利关闭后前端可能仍带着旧 mode 过来,归一后缺张数则默认 1
|
||||
if (data.get("mode") or "").strip() == "compound_full":
|
||||
sheet_count = 1
|
||||
else:
|
||||
return jsonify({"ok": False, "msg": "张数无效"})
|
||||
budget = cfg["trade_budget"]
|
||||
budget_cap = cfg["trade_budget"]
|
||||
if mode == "budget_full":
|
||||
blocked = _budget_full_blocked_by_compound_msg()
|
||||
if blocked:
|
||||
return jsonify({"ok": False, "msg": blocked, "compound_full_enabled": _compound_full_enabled()})
|
||||
budget, budget_err = _budget_full_usdc(cfg, ex)
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err})
|
||||
budget_cap = budget
|
||||
elif mode == "compound_full":
|
||||
if not _compound_full_enabled():
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": "全仓复利未开启,请改用指定张数或先开启全仓复利",
|
||||
"compound_full_enabled": False,
|
||||
}
|
||||
)
|
||||
budget, budget_err = _compound_full_usdc(cfg, ex)
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err})
|
||||
budget_cap = budget
|
||||
elif mode in ("sheets", "eth_amount") and _compound_full_enabled():
|
||||
budget_cap = None
|
||||
sizing = calc_order_size(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=ct_mult,
|
||||
min_sz=min_sz,
|
||||
budget_usdc=budget if _is_budget_mode(mode) else None,
|
||||
budget_buffer=cfg["budget_buffer"],
|
||||
eth_amount=eth_amount,
|
||||
sheets=sheet_count,
|
||||
budget_cap=_size_mode_budget_cap(cfg, mode, budget_cap)
|
||||
if mode in ("budget_full", "compound_full", "sheets", "eth_amount")
|
||||
else None,
|
||||
)
|
||||
if not sizing.get("ok"):
|
||||
return jsonify({"ok": False, "msg": sizing.get("msg") or "张数计算失败", "sizing": sizing})
|
||||
sheets = int(sizing["sheets"])
|
||||
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(sheets, ask_sz, min_sz=min_sz)
|
||||
if capped is None:
|
||||
return jsonify({"ok": False, "msg": cap_msg or "卖一深度不足,无法买入"})
|
||||
if capped < sheets:
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": f"卖一深度仅 {int(capped)} 张,不足请求 {int(sheets)} 张,拒绝缩量成交",
|
||||
"requested_sheets": int(sheets),
|
||||
"ask_sz": ask_sz,
|
||||
}
|
||||
)
|
||||
tick_sz = q.get("tick_sz")
|
||||
order = cfg["place_option_limit_order"](
|
||||
ex,
|
||||
inst_id=inst_id,
|
||||
side="buy",
|
||||
sheets=sheets,
|
||||
price=float(ask),
|
||||
td_mode=td_mode_for_option_buy(cfg["td_mode"]),
|
||||
tick_sz=tick_sz,
|
||||
ord_type="ioc",
|
||||
)
|
||||
if not order.get("ok"):
|
||||
return jsonify(order)
|
||||
ord_id = str((order.get("data") or {}).get("ordId") or "").strip()
|
||||
if not ord_id:
|
||||
return jsonify({"ok": False, "msg": "下单成功但未返回订单号", "order": order})
|
||||
from lib.exchange.okx_options_lib import wait_option_order_full_fill
|
||||
|
||||
try:
|
||||
fill_timeout = max(2.0, float(os.getenv("OKX_OPTIONS_OPEN_FILL_TIMEOUT_SEC") or "12"))
|
||||
except (TypeError, ValueError):
|
||||
fill_timeout = 12.0
|
||||
fill = wait_option_order_full_fill(
|
||||
ex,
|
||||
inst_id=inst_id,
|
||||
ord_id=ord_id,
|
||||
need_sheets=int(sheets),
|
||||
timeout_sec=fill_timeout,
|
||||
cancel_on_timeout=True,
|
||||
)
|
||||
if not fill.get("ok"):
|
||||
filled_n = int(fill.get("filled_sheets") or 0)
|
||||
orphan_close = None
|
||||
if filled_n > 0:
|
||||
try:
|
||||
from lib.options.options_close_exec_lib import close_option_by_bid1
|
||||
|
||||
orphan_close = close_option_by_bid1(
|
||||
cfg, ex, inst_id, sheets=filled_n, require_recycle_gate=False
|
||||
)
|
||||
except Exception as e:
|
||||
orphan_close = {"ok": False, "msg": str(e)}
|
||||
return jsonify(
|
||||
{
|
||||
"ok": False,
|
||||
"msg": fill.get("msg") or "未完全成交,开仓失败",
|
||||
"filled_sheets": filled_n,
|
||||
"orphan_close": orphan_close,
|
||||
"fill": fill,
|
||||
"order": order,
|
||||
}
|
||||
)
|
||||
fill_px = float(fill.get("avg_px") or ask)
|
||||
filled_n = int(fill.get("filled_sheets") or sheets)
|
||||
sheets = filled_n
|
||||
sizing = dict(sizing)
|
||||
sizing["sheets"] = sheets
|
||||
sizing["eth_amount"] = round(sheets * ct_mult, 8)
|
||||
from lib.options.options_pricing_lib import PREMIUM_DECIMALS_COIN, round_premium_amount
|
||||
|
||||
sizing["total_premium"] = round_premium_amount(
|
||||
fill_px * sheets * ct_mult, decimals=PREMIUM_DECIMALS_COIN
|
||||
)
|
||||
conn = cfg["get_db"]()
|
||||
trade_id = None
|
||||
target_mon = None
|
||||
open_underlying = ""
|
||||
open_opt_type = None
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
from lib.options.options_profit_exit_lib import ensure_profit_exit_columns
|
||||
|
||||
ensure_profit_exit_columns(conn)
|
||||
meta = q.get("meta") or {}
|
||||
u = str(meta.get("uly") or inst_id).split("-")[0]
|
||||
opt_type = meta.get("optType")
|
||||
open_underlying = u
|
||||
open_opt_type = opt_type
|
||||
from lib.options.options_time_lib import options_app_now_str
|
||||
|
||||
cur = conn.execute(
|
||||
"""
|
||||
INSERT INTO options_trades
|
||||
(inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
|
||||
open_quote, premium_paid, status, signal_note, exchange_ord_id,
|
||||
profit_exit_enabled, profit_exit_mult, profit_exit_state, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
inst_id,
|
||||
u,
|
||||
opt_type,
|
||||
q.get("strike"),
|
||||
str(q.get("exp_time") or ""),
|
||||
sheets,
|
||||
sizing["eth_amount"],
|
||||
fill_px,
|
||||
sizing["total_premium"],
|
||||
signal_note,
|
||||
ord_id,
|
||||
1 if profit_exit_enabled else 0,
|
||||
profit_exit_mult if profit_exit_enabled else 1.0,
|
||||
"active" if profit_exit_enabled else "idle",
|
||||
options_app_now_str(),
|
||||
),
|
||||
)
|
||||
trade_id = int(cur.lastrowid)
|
||||
if target_index is not None:
|
||||
from lib.options.options_target_lib import upsert_target_monitor
|
||||
|
||||
target_mon = upsert_target_monitor(
|
||||
conn,
|
||||
inst_id=inst_id,
|
||||
target_index=target_index,
|
||||
underlying=u,
|
||||
opt_type=str(opt_type) if opt_type else None,
|
||||
trade_id=trade_id,
|
||||
sheets=sheets,
|
||||
)
|
||||
if profit_exit_enabled:
|
||||
pass # 列已由 init_options_tables / ensure 迁移
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
from lib.exchange.okx_options_lib import invalidate_option_positions_cache
|
||||
from lib.options.options_notify_lib import notify_options_open
|
||||
|
||||
invalidate_option_positions_cache()
|
||||
_sync_options_trades(cfg, force=True)
|
||||
try:
|
||||
conn_n = cfg["get_db"]()
|
||||
try:
|
||||
notify_options_open(
|
||||
cfg,
|
||||
conn_n,
|
||||
trade_id=trade_id,
|
||||
inst_id=inst_id,
|
||||
underlying=open_underlying,
|
||||
opt_type=open_opt_type,
|
||||
sheets=sheets,
|
||||
premium_paid=sizing.get("total_premium"),
|
||||
open_quote=fill_px,
|
||||
target_index=target_index,
|
||||
signal_note=signal_note,
|
||||
premium_ccy=sizing.get("premium_ccy") or "USDC",
|
||||
)
|
||||
finally:
|
||||
conn_n.close()
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify(
|
||||
{
|
||||
"ok": True,
|
||||
"order": order,
|
||||
"sizing": sizing,
|
||||
"trade_id": trade_id,
|
||||
"target_monitor": target_mon,
|
||||
}
|
||||
)
|
||||
return jsonify({"ok": False, "msg": "仅支持币本位期权开仓"})
|
||||
|
||||
@app.route("/api/options/orders/pending")
|
||||
@lr
|
||||
@@ -1762,44 +1200,12 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
@app.route("/api/options/convert/quote", methods=["POST"])
|
||||
@lr
|
||||
def api_options_convert_quote():
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
data = request.get_json(silent=True) or {}
|
||||
try:
|
||||
amount = float(data.get("amount"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"ok": False, "msg": "数量无效"})
|
||||
return jsonify(cfg["estimate_usdt_to_usdc"](ex, amount))
|
||||
return jsonify({"ok": False, "msg": "USDC 兑换已下线,仅支持币本位期权"})
|
||||
|
||||
@app.route("/api/options/convert/execute", methods=["POST"])
|
||||
@lr
|
||||
def api_options_convert_execute():
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
data = request.get_json(silent=True) or {}
|
||||
quote_id = (data.get("quote_id") or "").strip()
|
||||
result = cfg["execute_convert"](ex, quote_id)
|
||||
if result.get("ok"):
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO options_convert_log (from_ccy, to_ccy, rfq_sz, received_sz, quote_id, status, message)
|
||||
VALUES ('USDT', 'USDC', ?, ?, ?, 'ok', '')
|
||||
""",
|
||||
(
|
||||
data.get("rfq_sz"),
|
||||
(result.get("data") or {}).get("baseSz"),
|
||||
quote_id,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify(result)
|
||||
return jsonify({"ok": False, "msg": "USDC 兑换已下线,仅支持币本位期权"})
|
||||
|
||||
@app.route("/api/options/transfer", methods=["POST"])
|
||||
@lr
|
||||
@@ -1847,19 +1253,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
@app.route("/api/options/spot/swap", methods=["POST"])
|
||||
@lr
|
||||
def api_options_spot_swap():
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
data = request.get_json(silent=True) or {}
|
||||
direction = (data.get("direction") or "usdt_to_usdc").strip()
|
||||
try:
|
||||
amount = float(data.get("amount"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"ok": False, "msg": "数量无效"})
|
||||
result = cfg["spot_market_swap_usdt_usdc"](ex, direction=direction, amount=amount)
|
||||
if result.get("ok"):
|
||||
_mark_balances_stale(cfg)
|
||||
return jsonify(result)
|
||||
return jsonify({"ok": False, "msg": "USDT↔USDC 兑换已下线,仅支持币本位期权"})
|
||||
|
||||
@app.route("/api/options/history")
|
||||
@lr
|
||||
|
||||
Reference in New Issue
Block a user