下线永续监控与 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_API_PASSPHRASE=
|
||||||
OKX_OPTIONS_ACCOUNT_LABEL=账户·期权
|
OKX_OPTIONS_ACCOUNT_LABEL=账户·期权
|
||||||
|
|
||||||
# 固定币本位(USDT买币桥);历史 OKX_OPTIONS_MARGIN_MODE=usdc 会被忽略并视为 coin
|
# 仅币本位期权(USDT买币桥);USDC 期权已下线,此项恒视为 coin
|
||||||
OKX_OPTIONS_MARGIN_MODE=coin
|
OKX_OPTIONS_MARGIN_MODE=coin
|
||||||
OKX_OPTIONS_COIN_COMPOUND=true
|
OKX_OPTIONS_COIN_COMPOUND=true
|
||||||
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
||||||
|
|||||||
@@ -5117,23 +5117,9 @@ def force_close_before_reset():
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# 后台线程
|
# 后台线程(永续已下线:不再跑划转/强平/关键位/委托监控;期权监控见 options_monitor_loop)
|
||||||
def background_task():
|
def background_task():
|
||||||
while True:
|
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)
|
time.sleep(MONITOR_POLL_SECONDS)
|
||||||
|
|
||||||
|
|
||||||
@@ -5637,16 +5623,14 @@ def index():
|
|||||||
@app.route("/key_monitor")
|
@app.route("/key_monitor")
|
||||||
@login_required
|
@login_required
|
||||||
def key_monitor_page():
|
def key_monitor_page():
|
||||||
redir = redirect_to_embed_shell_if_enabled("key_monitor")
|
# 永续关键位监控已下线
|
||||||
if redir is not None:
|
return redirect("/options")
|
||||||
return redir
|
|
||||||
return render_main_page("key_monitor")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/trade")
|
@app.route("/trade")
|
||||||
@login_required
|
@login_required
|
||||||
def trade_page():
|
def trade_page():
|
||||||
# 实盘下单界面已移除;永续下单仍由对冲计划走 place_exchange_order
|
# 永续实盘下单已下线
|
||||||
return redirect("/options")
|
return redirect("/options")
|
||||||
|
|
||||||
|
|
||||||
@@ -6303,7 +6287,7 @@ def order_focus():
|
|||||||
@login_required
|
@login_required
|
||||||
def add_order():
|
def add_order():
|
||||||
# 实盘下单界面已移除;永续开平仓请走对冲计划(place_exchange_order / close_exchange_order)
|
# 实盘下单界面已移除;永续开平仓请走对冲计划(place_exchange_order / close_exchange_order)
|
||||||
flash("实盘下单界面已移除,请使用对冲计划或期权页")
|
flash("永续实盘下单已下线,请使用期权页")
|
||||||
return redirect("/options")
|
return redirect("/options")
|
||||||
|
|
||||||
|
|
||||||
@@ -7679,5 +7663,5 @@ if __name__ == "__main__":
|
|||||||
from lib.common.flask_access_log_lib import silence_werkzeug_access_log
|
from lib.common.flask_access_log_lib import silence_werkzeug_access_log
|
||||||
|
|
||||||
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)
|
app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True)
|
||||||
|
|||||||
@@ -415,42 +415,26 @@
|
|||||||
|
|
||||||
function syncOptionsSectionVisibility(body) {
|
function syncOptionsSectionVisibility(body) {
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
var margin = (envFieldValue(body, "OKX_OPTIONS_MARGIN_MODE", "coin") || "coin").toLowerCase();
|
// 仅币本位期权:USDC/永续相关 env 始终隐藏
|
||||||
var isCoin = margin !== "usdc";
|
var isCoin = true;
|
||||||
var gateMode = (envFieldValue(body, "OKX_OPTIONS_CLOSE_GATE_MODE", "premium") || "premium").toLowerCase();
|
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 coinCompoundOn = envTruthy(envFieldValue(body, "OKX_OPTIONS_COIN_COMPOUND", "true"));
|
||||||
var coinMaxOn = envTruthy(envFieldValue(body, "OKX_OPTIONS_COIN_MAX_USDT_ENABLED", "false"));
|
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) {
|
COIN_ONLY_ENV_KEYS.forEach(function (key) {
|
||||||
setEnvRowHidden(envFieldRowByKey(body, key), !isCoin);
|
setEnvRowHidden(envFieldRowByKey(body, key), !isCoin);
|
||||||
});
|
});
|
||||||
USDC_ONLY_ENV_KEYS.forEach(function (key) {
|
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");
|
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_NET_PNL_MIN_USDT"), gateMode !== "net_pnl");
|
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_BUDGET_USDT"), coinCompoundOn);
|
||||||
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COIN_MAX_USDT"), !coinMaxOn);
|
setEnvRowHidden(envFieldRowByKey(body, "OKX_OPTIONS_COIN_MAX_USDT"), !coinMaxOn);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function bindOptionsSectionVisibility(body) {
|
function bindOptionsSectionVisibility(body) {
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
|
|||||||
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]]:
|
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()
|
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_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"))
|
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]] = []
|
out: list[dict[str, Any]] = []
|
||||||
for f in fields:
|
for f in fields:
|
||||||
key = str(f.get("key") or "")
|
key = str(f.get("key") or "")
|
||||||
hide = False
|
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
|
hide = True
|
||||||
elif key == "OKX_OPTIONS_CLOSE_NET_PNL_MIN_USDT" and gate_mode != "net_pnl":
|
elif key == "OKX_OPTIONS_CLOSE_NET_PNL_MIN_USDT" and gate_mode != "net_pnl":
|
||||||
hide = True
|
hide = True
|
||||||
|
|||||||
@@ -814,7 +814,7 @@ def build_option_chain(
|
|||||||
|
|
||||||
family = inst_family_for_underlying(u, margin_mode=margin_mode)
|
family = inst_family_for_underlying(u, margin_mode=margin_mode)
|
||||||
except Exception:
|
except Exception:
|
||||||
family = f"{u}-USD_UM"
|
family = f"{u}-USD"
|
||||||
uly = f"{u}-USD"
|
uly = f"{u}-USD"
|
||||||
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
||||||
now_ms = time.time() * 1000
|
now_ms = time.time() * 1000
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ DISPLAY_RUNTIME_PREFIX = "display."
|
|||||||
DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
||||||
"show_nav_dashboard": False,
|
"show_nav_dashboard": False,
|
||||||
"show_nav_account_ledger": False,
|
"show_nav_account_ledger": False,
|
||||||
"show_nav_key_monitor": True,
|
"show_nav_key_monitor": False, # 永续关键位已下线
|
||||||
"show_nav_trade": False, # 实盘下单界面已移除
|
"show_nav_trade": False, # 永续实盘下单已下线
|
||||||
"show_nav_strategy": False,
|
"show_nav_strategy": False,
|
||||||
"show_nav_strategy_records": False,
|
"show_nav_strategy_records": False,
|
||||||
"show_nav_records": False, # 永续交易记录与复盘已移除
|
"show_nav_records": False, # 永续交易记录与复盘已移除
|
||||||
@@ -22,10 +22,10 @@ DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
|||||||
"show_nav_env_config": True,
|
"show_nav_env_config": True,
|
||||||
"show_nav_options": True,
|
"show_nav_options": True,
|
||||||
"show_nav_options_review": True,
|
"show_nav_options_review": True,
|
||||||
"show_settings_transfer": False, # 永续资金划转默认隐藏
|
"show_settings_transfer": False, # 永续资金划转已下线
|
||||||
"show_settings_export": True,
|
"show_settings_export": True,
|
||||||
"show_settings_password": True,
|
"show_settings_password": True,
|
||||||
"show_settings_options_swap": True,
|
"show_settings_options_swap": False, # USDC 币种兑换已下线
|
||||||
"show_settings_options_transfer": True,
|
"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:
|
def tab_allowed(tab: str, display: Optional[dict[str, bool]] = None) -> bool:
|
||||||
prefs = normalize_display_prefs(display or {})
|
prefs = normalize_display_prefs(display or {})
|
||||||
t = (tab or "").strip()
|
t = (tab or "").strip()
|
||||||
if t in ("trade", "records", "stats", "hedge_plan"):
|
if t in ("trade", "records", "stats", "hedge_plan", "key_monitor"):
|
||||||
return False # 实盘下单 / 交易记录复盘 / 统计分析 / 对冲计划 已移除
|
return False # 永续相关页面已下线
|
||||||
key = NAV_TAB_ALLOWED.get(t)
|
key = NAV_TAB_ALLOWED.get(t)
|
||||||
if not key:
|
if not key:
|
||||||
return True
|
return True
|
||||||
@@ -123,7 +123,6 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
|||||||
nav_keys = [
|
nav_keys = [
|
||||||
"show_nav_dashboard",
|
"show_nav_dashboard",
|
||||||
"show_nav_account_ledger",
|
"show_nav_account_ledger",
|
||||||
"show_nav_key_monitor",
|
|
||||||
"show_nav_risk_policy",
|
"show_nav_risk_policy",
|
||||||
"show_nav_live_guide",
|
"show_nav_live_guide",
|
||||||
"show_nav_system_guide",
|
"show_nav_system_guide",
|
||||||
@@ -132,10 +131,8 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
|||||||
"show_nav_options_review",
|
"show_nav_options_review",
|
||||||
]
|
]
|
||||||
settings_keys = [
|
settings_keys = [
|
||||||
"show_settings_transfer",
|
|
||||||
"show_settings_export",
|
"show_settings_export",
|
||||||
"show_settings_password",
|
"show_settings_password",
|
||||||
"show_settings_options_swap",
|
|
||||||
"show_settings_options_transfer",
|
"show_settings_options_transfer",
|
||||||
]
|
]
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -127,21 +127,10 @@ def build_settings_tabs(display: dict[str, Any] | None, instance_settings: dict[
|
|||||||
tabs.append({"key": "sim_funds", "title": "模拟资金"})
|
tabs.append({"key": "sim_funds", "title": "模拟资金"})
|
||||||
if disp.get("show_settings_password", True):
|
if disp.get("show_settings_password", True):
|
||||||
tabs.append({"key": "password", "title": "账户密码"})
|
tabs.append({"key": "password", "title": "账户密码"})
|
||||||
if inst.get("show_transfer") and disp.get("show_settings_transfer", False):
|
# 永续划转 / USDC 币种兑换已下线
|
||||||
tabs.append({"key": "transfer", "title": "永续划转"})
|
|
||||||
if disp.get("show_settings_export", True):
|
if disp.get("show_settings_export", True):
|
||||||
tabs.append({"key": "export", "title": "数据导出"})
|
tabs.append({"key": "export", "title": "数据导出"})
|
||||||
tabs.append({"key": "backup", "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):
|
if inst.get("options_settings_enabled") and disp.get("show_settings_options_transfer", True):
|
||||||
tabs.append({"key": "options_transfer", "title": "期权划转"})
|
tabs.append({"key": "options_transfer", "title": "期权划转"})
|
||||||
return tabs
|
return tabs
|
||||||
|
|||||||
@@ -642,14 +642,13 @@ def _try_open_full(
|
|||||||
inst_id: str,
|
inst_id: str,
|
||||||
order: dict[str, Any],
|
order: dict[str, Any],
|
||||||
) -> 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
|
from lib.options.options_coin_open_lib import open_coin_option_buy_full
|
||||||
|
|
||||||
note = f"中控条件单:{order.get('hub_order_id')}"
|
note = f"中控条件单:{order.get('hub_order_id')}"
|
||||||
target_index = _safe_float(order.get("target_index"))
|
target_index = _safe_float(order.get("target_index"))
|
||||||
pe_en = bool(order.get("profit_exit_enabled"))
|
pe_en = bool(order.get("profit_exit_enabled"))
|
||||||
pe_mult = float(order.get("profit_exit_mult") or 1.0)
|
pe_mult = float(order.get("profit_exit_mult") or 1.0)
|
||||||
if is_coin_margin_mode():
|
# USDC 期权开仓已下线,条件单仅走币本位
|
||||||
return open_coin_option_buy_full(
|
return open_coin_option_buy_full(
|
||||||
cfg,
|
cfg,
|
||||||
ex,
|
ex,
|
||||||
@@ -660,11 +659,6 @@ def _try_open_full(
|
|||||||
profit_exit_mult=pe_mult,
|
profit_exit_mult=pe_mult,
|
||||||
target_sheets=None,
|
target_sheets=None,
|
||||||
)
|
)
|
||||||
return {
|
|
||||||
"ok": False,
|
|
||||||
"msg": "条件单开仓当前仅支持币本位客户端规则(买币→开仓)",
|
|
||||||
"need_depth": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def run_cond_orders_tick(
|
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:
|
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"
|
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"
|
||||||
return f"{u}-USD_UM"
|
|
||||||
|
|
||||||
|
|
||||||
def margin_mode_from_inst_id(inst_id: str) -> str:
|
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:
|
def premium_ccy_for_mode(margin_mode: str, underlying: str = "ETH") -> str:
|
||||||
if normalize_options_margin_mode(margin_mode) == MODE_COIN:
|
"""币本位权利金为标的币;历史 USDC 行(margin_mode=usdc)仍标 USDC."""
|
||||||
return (underlying or "ETH").strip().upper() or "ETH"
|
if str(margin_mode or "").strip().lower() == MODE_USDC:
|
||||||
return "USDC"
|
return "USDC"
|
||||||
|
return (underlying or "ETH").strip().upper() or "ETH"
|
||||||
|
|
||||||
|
|
||||||
def spot_quote_inst_id(underlying: str) -> str:
|
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_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_monitor_lib import options_monitor_loop
|
||||||
from lib.options.options_pricing_lib import (
|
from lib.exchange.okx_options_lib import _safe_float
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _env_bool(key: str, default: bool = False) -> bool:
|
def _env_bool(key: str, default: bool = False) -> bool:
|
||||||
@@ -253,84 +242,19 @@ def _run_manual_bid_close(
|
|||||||
return result
|
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:
|
def _compound_full_enabled() -> bool:
|
||||||
return _env_bool("OKX_OPTIONS_COMPOUND_FULL_ENABLED", True)
|
"""USDC 全仓复利已下线;恒为 False,避免前端残留 compound_full 模式."""
|
||||||
|
return False
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_size_mode(mode: str) -> tuple[str, str | None]:
|
def _normalize_size_mode(mode: str) -> tuple[str, str | None]:
|
||||||
"""全仓复利关闭时强制离开 compound_full,避免前端残留选中导致无法开仓."""
|
"""USDC 预算/全仓模式已下线;统一落到指定张数."""
|
||||||
m = (mode or "sheets").strip() or "sheets"
|
m = (mode or "sheets").strip() or "sheets"
|
||||||
if m == "compound_full" and not _compound_full_enabled():
|
if m in ("compound_full", "budget_full"):
|
||||||
return "sheets", "全仓复利已关闭,已改用指定张数"
|
return "sheets", "USDC 期权模式已下线,已改用指定张数"
|
||||||
if m == "budget_full" and _compound_full_enabled():
|
|
||||||
return "compound_full", None
|
|
||||||
return m, None
|
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:
|
def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
|
||||||
conn = cfg["get_db"]()
|
conn = cfg["get_db"]()
|
||||||
try:
|
try:
|
||||||
@@ -822,228 +746,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"ok": False, "msg": f"币本位报价失败: {e}"})
|
return jsonify({"ok": False, "msg": f"币本位报价失败: {e}"})
|
||||||
|
return jsonify({"ok": False, "msg": "仅支持币本位期权报价", "can_open": False})
|
||||||
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(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.route("/api/options/open", methods=["POST"])
|
@app.route("/api/options/open", methods=["POST"])
|
||||||
@lr
|
@lr
|
||||||
@@ -1141,272 +844,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"ok": False, "msg": f"币本位开仓失败: {e}"})
|
return jsonify({"ok": False, "msg": f"币本位开仓失败: {e}"})
|
||||||
q = cfg["quote_option_contract"](ex, inst_id)
|
return jsonify({"ok": False, "msg": "仅支持币本位期权开仓"})
|
||||||
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,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.route("/api/options/orders/pending")
|
@app.route("/api/options/orders/pending")
|
||||||
@lr
|
@lr
|
||||||
@@ -1762,44 +1200,12 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
@app.route("/api/options/convert/quote", methods=["POST"])
|
@app.route("/api/options/convert/quote", methods=["POST"])
|
||||||
@lr
|
@lr
|
||||||
def api_options_convert_quote():
|
def api_options_convert_quote():
|
||||||
ex, err = _require_options_ex(cfg)
|
return jsonify({"ok": False, "msg": "USDC 兑换已下线,仅支持币本位期权"})
|
||||||
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))
|
|
||||||
|
|
||||||
@app.route("/api/options/convert/execute", methods=["POST"])
|
@app.route("/api/options/convert/execute", methods=["POST"])
|
||||||
@lr
|
@lr
|
||||||
def api_options_convert_execute():
|
def api_options_convert_execute():
|
||||||
ex, err = _require_options_ex(cfg)
|
return jsonify({"ok": False, "msg": "USDC 兑换已下线,仅支持币本位期权"})
|
||||||
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)
|
|
||||||
|
|
||||||
@app.route("/api/options/transfer", methods=["POST"])
|
@app.route("/api/options/transfer", methods=["POST"])
|
||||||
@lr
|
@lr
|
||||||
@@ -1847,19 +1253,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
@app.route("/api/options/spot/swap", methods=["POST"])
|
@app.route("/api/options/spot/swap", methods=["POST"])
|
||||||
@lr
|
@lr
|
||||||
def api_options_spot_swap():
|
def api_options_spot_swap():
|
||||||
ex, err = _require_options_ex(cfg)
|
return jsonify({"ok": False, "msg": "USDT↔USDC 兑换已下线,仅支持币本位期权"})
|
||||||
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)
|
|
||||||
|
|
||||||
@app.route("/api/options/history")
|
@app.route("/api/options/history")
|
||||||
@lr
|
@lr
|
||||||
|
|||||||
Reference in New Issue
Block a user