Remove USDC product path; CoinOX is coin-margined only.
Drop USDC open/quote/swap APIs and env knobs; retarget hedge/settings copy to USDT/coin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-10
@@ -120,21 +120,16 @@ OKX_OPTIONS_ENABLED=true
|
||||
# OKX_OPTIONS_API_SECRET=
|
||||
# OKX_OPTIONS_API_PASSPHRASE=
|
||||
OKX_OPTIONS_ACCOUNT_LABEL=账户·期权
|
||||
# 单笔期权本位: coin(默认,币本位+USDT买币桥) | usdc(权利金USDC;对冲仍仅USDC)
|
||||
# CoinOX 固定币本位(USDT 买币桥);勿再配置 USDC 权利金模式
|
||||
OKX_OPTIONS_MARGIN_MODE=coin
|
||||
OKX_OPTIONS_TRADE_BUDGET_USDC=10
|
||||
OKX_OPTIONS_BUDGET_BUFFER=0.95
|
||||
# 币本位:按交易户USDT×缓冲复利;上限开关默认关(靠人工转走)
|
||||
# 按交易户USDT×缓冲复利;上限开关默认关(靠人工转走)
|
||||
OKX_OPTIONS_COIN_COMPOUND=true
|
||||
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
||||
OKX_OPTIONS_COIN_MAX_USDT_ENABLED=false
|
||||
OKX_OPTIONS_COIN_MAX_USDT=50
|
||||
# 现货买入相对权利金缓冲:1.10=多买10%;也可写 0.10。按最大可开张数×权利金×缓冲买币,不全额兑换
|
||||
OKX_OPTIONS_COIN_SPOT_BUY_BUFFER=1.10
|
||||
# 全仓复利:开启时隐藏单笔预算且不可用打满;关闭后恢复单笔预算
|
||||
OKX_OPTIONS_COMPOUND_FULL_ENABLED=true
|
||||
OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED=false
|
||||
OKX_OPTIONS_COMPOUND_FULL_CAP_USDC=300
|
||||
# 交易模式三选一(热更):options=单独期权 / perp_options=永期对冲 / options_options=期期对冲
|
||||
# 选单独期权时隐藏对冲导航与对冲配置;选对冲时不可单独开期权,仓位按「对冲组数上限」
|
||||
OKX_TRADE_MODE=perp_options
|
||||
@@ -152,10 +147,8 @@ OKX_OPTIONS_TD_MODE=isolated
|
||||
OKX_OPTIONS_ALLOW_MARKET_CLOSE=false
|
||||
# 目标平仓门控(目标触达后自动平才校验;比较口径均为 USDT 估值)
|
||||
OKX_OPTIONS_CLOSE_GATE_MODE=premium
|
||||
# 全局倍数可选;留空则按本位用下方 COIN/USDC
|
||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT=
|
||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN=1.05
|
||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT_USDC=2
|
||||
OKX_OPTIONS_CLOSE_NET_PNL_MIN_U=0
|
||||
OKX_OPTIONS_CLOSE_HOLD_SECONDS=120
|
||||
# 对冲买期权等成交超时(秒);超时撤未成交部分,未完全成交则开仓失败
|
||||
@@ -337,5 +330,4 @@ APP_TIMEZONE=Asia/Shanghai
|
||||
# 切实盘:填 OKX_API_* , LIVE_TRADING_ENABLED=true , SIM_DEFAULT_MODE=live , 并在 OKX 白名单加云主机出口 IP
|
||||
SIM_DEFAULT_MODE=sim
|
||||
SIM_INITIAL_EQUITY_USDT=10000
|
||||
SIM_INITIAL_USDC=0
|
||||
SIM_FEE_RATE=0.0005
|
||||
|
||||
@@ -694,16 +694,16 @@
|
||||
}
|
||||
const line =
|
||||
label +
|
||||
" · 交易 USDC " +
|
||||
fmt(acct.trading_usdc, 2) +
|
||||
" · 资金 USDC " +
|
||||
fmt(acct.funding_usdc, 2);
|
||||
" · 交易 USDT " +
|
||||
fmt(acct.trading_usdt != null ? acct.trading_usdt : acct.trading_usdc, 2) +
|
||||
" · 资金 USDT " +
|
||||
fmt(acct.funding_usdt != null ? acct.funding_usdt : acct.funding_usdc, 2);
|
||||
const el = $("hp-opt-bal-line");
|
||||
if (el) el.textContent = line;
|
||||
const fundEl = $("hp-oo-funding-usdc");
|
||||
const tradeEl = $("hp-oo-trading-usdc");
|
||||
if (fundEl) fundEl.textContent = fmt(acct.funding_usdc, 2);
|
||||
if (tradeEl) tradeEl.textContent = fmt(acct.trading_usdc, 2);
|
||||
if (fundEl) fundEl.textContent = fmt(acct.funding_usdt != null ? acct.funding_usdt : acct.funding_usdc, 2);
|
||||
if (tradeEl) tradeEl.textContent = fmt(acct.trading_usdt != null ? acct.trading_usdt : acct.trading_usdc, 2);
|
||||
autoFillOoSheets();
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@
|
||||
const d = await apiJson("/api/options/transfer", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ccy: "USDC", from: from, to: to, amount: amount }),
|
||||
body: JSON.stringify({ ccy: "USDT", from: from, to: to, amount: amount }),
|
||||
});
|
||||
if (!d.ok) {
|
||||
setOoXferMsg(d.msg || "划转失败", "err");
|
||||
@@ -1312,7 +1312,7 @@
|
||||
const ct = Number(state.selected.ct_mult || 0.01);
|
||||
const ask = Number(state.selected.ask || 0);
|
||||
const prem = ask * sheets * ct;
|
||||
line.textContent = "预估权利金 ≈ " + fmt(prem, 4) + " USDC(期权账户)";
|
||||
line.textContent = "预估权利金 ≈ " + fmt(prem, 4) + " 币(币本位)";
|
||||
}
|
||||
|
||||
function buildStraddleRows(contracts) {
|
||||
@@ -1561,7 +1561,7 @@
|
||||
fmt(b, 4) +
|
||||
" ≈ " +
|
||||
fmt(a + b, 4) +
|
||||
" USDC";
|
||||
" 币";
|
||||
}
|
||||
|
||||
function legPayload(c, sheets) {
|
||||
@@ -2051,7 +2051,7 @@
|
||||
}
|
||||
const amt = Math.floor(max * 100) / 100;
|
||||
const label = dir === "trading_to_funding" ? "交易 → 资金" : "资金 → 交易";
|
||||
if (!window.confirm("确认全部划转?\n方向:" + label + "\n数量:" + amt + " USDC")) return;
|
||||
if (!window.confirm("确认全部划转?\n方向:" + label + "\n数量:" + amt + " USDT")) return;
|
||||
const amtEl = $("hp-oo-xfer-amount");
|
||||
if (amtEl) {
|
||||
amtEl.removeAttribute("readonly");
|
||||
@@ -2435,7 +2435,7 @@
|
||||
html +=
|
||||
"<div><span class=\"muted\">权利金合计</span> " +
|
||||
fmt(p.premium_total, 4) +
|
||||
" USDC</div>";
|
||||
" 币</div>";
|
||||
html +=
|
||||
"<div><span class=\"muted\">合约摘要</span> <code>" +
|
||||
(d.contracts_summary || "—") +
|
||||
|
||||
Vendored
-1
@@ -169,7 +169,6 @@ SELECT_OPTIONS: dict[str, tuple[tuple[str, str], ...]] = {
|
||||
),
|
||||
"OKX_OPTIONS_MARGIN_MODE": (
|
||||
("coin", "币本位(USDT买币桥)"),
|
||||
("usdc", "USDC(USDⓈ权利金)"),
|
||||
),
|
||||
"OKX_OPTIONS_CLOSE_GATE_MODE": (
|
||||
("premium", "权利金×倍数"),
|
||||
|
||||
Vendored
+19
-58
@@ -27,7 +27,7 @@ _EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = {
|
||||
(
|
||||
"OKX_SHOW_PERP_FUNDS",
|
||||
"显示永续资金",
|
||||
"默认开启;关闭后顶栏隐藏 USDT 资金账户与交易账户,总资金仅计期权 USDC 侧",
|
||||
"默认开启;关闭后顶栏隐藏 USDT 资金账户与交易账户",
|
||||
),
|
||||
],
|
||||
"binance": [
|
||||
@@ -176,63 +176,38 @@ _OPTIONS_SECTION: dict[str, Any] = {
|
||||
"title": "期权账户",
|
||||
"exchanges": frozenset({"okx"}),
|
||||
"fields": [
|
||||
("OKX_OPTIONS_ENABLED", "启用期权模块", "与永续共用上方 OKX_API_*;不再单独配置期权密钥"),
|
||||
("OKX_OPTIONS_ENABLED", "启用期权模块", "与永续共用上方 OKX_API_*;CoinOX 固定币本位"),
|
||||
("OKX_OPTIONS_ACCOUNT_LABEL", "期权账户备注", ""),
|
||||
(
|
||||
"OKX_OPTIONS_MARGIN_MODE",
|
||||
"单笔期权本位",
|
||||
"usdc=USDⓈ权利金;coin=币本位+USDT买币桥(默认)。有持仓/半成品桥时勿切换;改后需重启",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_TRADE_BUDGET_USDC",
|
||||
"单笔预算(USDC)",
|
||||
"仅 USDC 模式且全仓复利关闭时显示/生效;用于「按可用余额打满」及张数/币数上限",
|
||||
),
|
||||
("OKX_OPTIONS_BUDGET_BUFFER", "预算缓冲比例", "如 0.95;USDC 打满/全仓复利与币本位复利共用"),
|
||||
("OKX_OPTIONS_BUDGET_BUFFER", "预算缓冲比例", "如 0.95;币本位复利/固定预算共用"),
|
||||
(
|
||||
"OKX_OPTIONS_COIN_COMPOUND",
|
||||
"币本位按交易户USDT复利",
|
||||
"按交易户USDT复利",
|
||||
"默认 true;预算=交易账户USDT×缓冲;关闭则用下方固定 USDT 预算×缓冲",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COIN_BUDGET_USDT",
|
||||
"币本位固定预算(USDT)",
|
||||
"仅币本位且复利关闭时生效",
|
||||
"固定预算(USDT)",
|
||||
"仅复利关闭时生效",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COIN_MAX_USDT_ENABLED",
|
||||
"币本位单笔上限开关",
|
||||
"单笔上限开关",
|
||||
"默认 false=靠人工转走控规模;true 时预算不超过下方 N U",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COIN_MAX_USDT",
|
||||
"币本位单笔上限(USDT)",
|
||||
"单笔上限(USDT)",
|
||||
"仅上限开关开启时生效",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COIN_SPOT_BUY_BUFFER",
|
||||
"币本位现货买入缓冲",
|
||||
"相对权利金倍数,默认 1.10(=多买10%);也可写 0.10 表示+10%。按最大可开张数×卖一权利金×本缓冲买币,不全额兑换",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_ENABLED",
|
||||
"全仓复利开关",
|
||||
"默认 true;仅 USDC 模式。开启时隐藏单笔预算且不可用打满预算,下单以全仓复利为主;关闭则恢复单笔预算并隐藏全仓复利",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED",
|
||||
"全仓复利上限开关",
|
||||
"仅全仓复利开启时有意义;默认 false=不设上限用期权户全部可用;true 时按下方上限封顶",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_USDC",
|
||||
"全仓复利上限(USDC)",
|
||||
"仅「全仓复利」且「上限开关」都开启时生效;例如 300",
|
||||
"现货买入缓冲",
|
||||
"相对权利金倍数,默认 1.10(=多买10%);按最大可开张数×卖一权利金×本缓冲买币",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS",
|
||||
"期权持仓上限(笔)",
|
||||
"仅「单独期权」模式生效;默认 0=不限制;按交易所期权合约笔数计数,同合约加仓不占新笔数",
|
||||
"仅「单独期权」模式生效;默认 0=不限制",
|
||||
),
|
||||
("OKX_OPTIONS_DEFAULT_UNDERLY", "默认标的", "如 ETH"),
|
||||
(
|
||||
@@ -248,7 +223,7 @@ _OPTIONS_SECTION: dict[str, Any] = {
|
||||
(
|
||||
"OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED",
|
||||
"链上仅显示有卖一",
|
||||
"默认 true;开启后隐藏无卖一深度或深度不足1张的合约(含标记价估算行)",
|
||||
"默认 true;开启后隐藏无卖一深度或深度不足1张的合约",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_CLOSE_GATE_MODE",
|
||||
@@ -258,18 +233,13 @@ _OPTIONS_SECTION: dict[str, Any] = {
|
||||
(
|
||||
"OKX_OPTIONS_CLOSE_RECYCLE_MULT",
|
||||
"门控权利金倍数(全局)",
|
||||
"可选;填写则覆盖下方分本位默认值",
|
||||
"可选;填写则覆盖下方币本位默认值",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN",
|
||||
"门控权利金倍数(币本位)",
|
||||
"门控权利金倍数",
|
||||
"默认 1.05;premium 模式下 recyclable(U)≥premium(U)×本值",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_CLOSE_RECYCLE_MULT_USDC",
|
||||
"门控权利金倍数(USDC)",
|
||||
"默认 2;premium 模式下 recyclable(U)≥premium(U)×本值",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_CLOSE_NET_PNL_MIN_U",
|
||||
"门控净盈亏下限(U)",
|
||||
@@ -583,28 +553,19 @@ def build_env_ui_payload(
|
||||
|
||||
|
||||
def _mark_options_env_field_visibility(fields: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""按本位/全仓复利隐藏无关项(供 SSR/前端;切换开关仍可再显示)."""
|
||||
compound_on = True
|
||||
margin_mode = "coin"
|
||||
"""CoinOX 固定币本位;按复利开关隐藏固定预算等项."""
|
||||
coin_compound = True
|
||||
for f in fields:
|
||||
key = f.get("key")
|
||||
cur = str(f.get("current") or f.get("default") or "").strip()
|
||||
if key == "OKX_OPTIONS_COMPOUND_FULL_ENABLED":
|
||||
compound_on = _env_truthy(cur or "true")
|
||||
elif key == "OKX_OPTIONS_MARGIN_MODE":
|
||||
margin_mode = (cur or "coin").lower()
|
||||
if margin_mode not in ("coin", "usdc"):
|
||||
margin_mode = "coin"
|
||||
if key == "OKX_OPTIONS_COIN_COMPOUND":
|
||||
coin_compound = _env_truthy(cur or "true")
|
||||
out: list[dict[str, Any]] = []
|
||||
for f in fields:
|
||||
item = dict(f)
|
||||
key = item.get("key")
|
||||
hide = False
|
||||
if key == "OKX_OPTIONS_TRADE_BUDGET_USDC" and compound_on:
|
||||
hide = True
|
||||
if key == "OKX_OPTIONS_CLOSE_RECYCLE_MULT_USDC" and margin_mode == "coin":
|
||||
hide = True
|
||||
if key == "OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN" and margin_mode == "usdc":
|
||||
if key == "OKX_OPTIONS_COIN_BUDGET_USDT" and coin_compound:
|
||||
hide = True
|
||||
if hide:
|
||||
item["hidden"] = True
|
||||
|
||||
@@ -850,7 +850,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)
|
||||
chain_margin = "usdc" if "_UM" in family.upper() else "coin"
|
||||
|
||||
@@ -93,7 +93,11 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
"chain_max_dte": float(os.getenv("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS") or os.getenv("OKX_OPTIONS_MAX_DTE_DAYS") or "14"),
|
||||
"perp_account_label": (os.getenv("OKX_ACCOUNT_LABEL") or "合约账户").strip(),
|
||||
"options_account_label": (os.getenv("OKX_OPTIONS_ACCOUNT_LABEL") or "期权账户").strip(),
|
||||
"trade_budget_usdc": float(os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC") or "10"),
|
||||
"trade_budget_usdc": float(
|
||||
os.getenv("OKX_OPTIONS_COIN_BUDGET_USDT")
|
||||
or os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC")
|
||||
or "10"
|
||||
),
|
||||
# 对冲专用缓冲;与期权页 OKX_OPTIONS_BUDGET_BUFFER 独立
|
||||
"budget_buffer": float(os.getenv("HEDGE_PLAN_BUDGET_BUFFER") or "0.95"),
|
||||
"oo_bias_split_by": _oo_bias_split_by(),
|
||||
@@ -769,7 +773,7 @@ def register_hedge_plan_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
"chain_max_dte_days": chain_max_dte,
|
||||
"account_kind": "options",
|
||||
"account_label": cfg.get("options_account_label") or "期权账户",
|
||||
"account_note": "期权腿使用期权账户(交易 USDC)",
|
||||
"account_note": "期权腿使用币本位(交易账户 USDT 买币开期权)",
|
||||
"options_account": opt_acct,
|
||||
"trade_budget_usdc": cfg.get("trade_budget_usdc"),
|
||||
"budget_buffer": cfg.get("budget_buffer"),
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<details class="tip-collapse hp-rule-collapse">
|
||||
<summary class="tip-collapse-summary">规则说明</summary>
|
||||
<div class="tip-collapse-body rule-tip">
|
||||
<p><strong>账户</strong>:永续腿走<strong>合约账户</strong>(USDT);期权腿走<strong>期权账户</strong>(USDC)。两账户分开下单、资金不互通。</p>
|
||||
<p><strong>账户</strong>:永续腿走<strong>合约账户</strong>(USDT);期权腿走<strong>币本位</strong>(交易账户 USDT 买币开期权)。</p>
|
||||
<p><strong>模式</strong>:在 env <code>HEDGE_PLAN_OPTION_PRIMARY</code> 切换(true=以期权为主 / false=保险模式);标题前标识当前模式。</p>
|
||||
<p><strong>保险模式</strong>:做多配 Put、做空配 Call;左填开仓/止盈止损;仅实值/平值;交易所 TP/SL 出场。</p>
|
||||
<p><strong>以期权为主</strong>:填参后点「策略启动」进入<strong>盯盘</strong>(非现场开仓);杠杆/间隔达标后自动先开期权再市价永续。右侧列表仅展示达标候选。</p>
|
||||
@@ -92,7 +92,7 @@
|
||||
<h3 id="hp-po-sec-capital" class="hp-po-section-title">资金与杠杆配置</h3>
|
||||
<div class="hp-po-fields hp-po-fields--section hp-po-fields--capital">
|
||||
<label class="hp-po-field">
|
||||
<span class="hp-po-field-lab">权利金 <em>USDC</em></span>
|
||||
<span class="hp-po-field-lab">权利金 <em>币</em></span>
|
||||
<input type="number" step="any" id="hp-premium-budget" placeholder="预算(执行×0.95)" autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />
|
||||
</label>
|
||||
<label class="hp-po-field">
|
||||
@@ -211,7 +211,7 @@
|
||||
<details class="tip-collapse hp-rule-collapse">
|
||||
<summary class="tip-collapse-summary">规则说明</summary>
|
||||
<div class="tip-collapse-body rule-tip">
|
||||
<p><strong>账户</strong>:两腿都在<strong>期权账户</strong>。可用预算 = min(交易 USDC × 对冲缓冲 <strong id="hp-oo-buf-ratio">{{ '%.2f'|format(hedge_plan_budget_buffer|default(0.95)|float) }}</strong>, 单笔预算);可在 env「对冲预算缓冲比例」改。</p>
|
||||
<p><strong>账户</strong>:两腿都走<strong>币本位期权</strong>。可用预算 = min(交易 USDT × 对冲缓冲 <strong id="hp-oo-buf-ratio">{{ '%.2f'|format(hedge_plan_budget_buffer|default(0.95)|float) }}</strong>, 单笔预算);可在 env「对冲预算缓冲比例」改。</p>
|
||||
<p><strong>下单</strong>:选 Call + Put 后「计算」再「启动」。启动会再拉卖一并按最新价重算张数,IOC 完全成交才算成功;资金不足可在右侧划转。</p>
|
||||
<p><strong>板块</strong>:左填<strong>盈亏比</strong>(盈利金额÷总权利金,默认2)与张数模式(同张数/做多/做空);右 T 型选腿。<strong>两腿仅允许平值或虚值</strong>(禁实值)。出场:盈利腿达盈亏比即平;亏损腿「残值平」=本合约权利金跌至20%且有买一时平,「到期平」=持有至到期。</p>
|
||||
</div>
|
||||
@@ -294,7 +294,7 @@
|
||||
<span>资金 <strong id="hp-oo-funding-usdc">—</strong></span>
|
||||
<span class="hp-oo-transfer-sep">·</span>
|
||||
<span>交易 <strong id="hp-oo-trading-usdc">—</strong></span>
|
||||
<span class="hp-oo-transfer-unit">USDC</span>
|
||||
<span class="hp-oo-transfer-unit">USDT</span>
|
||||
<span class="muted" id="hp-oo-xfer-msg"></span>
|
||||
</div>
|
||||
<div class="form-row hp-oo-transfer-form" autocomplete="off">
|
||||
@@ -405,4 +405,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/hedge_plan.js?v=48"></script>
|
||||
<script src="/static/hedge_plan.js?v=49"></script>
|
||||
|
||||
@@ -26,7 +26,7 @@ DEFAULT_INSTANCE_DISPLAY: dict[str, bool] = {
|
||||
"show_settings_transfer": True,
|
||||
"show_settings_export": True,
|
||||
"show_settings_password": True,
|
||||
"show_settings_options_swap": True,
|
||||
"show_settings_options_swap": False, # CoinOX 已移除 USDT↔USDC 兑换
|
||||
"show_settings_options_transfer": True,
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ def normalize_display_prefs(raw: dict | None) -> dict[str, bool]:
|
||||
out["show_nav_strategy"] = False
|
||||
out["show_nav_strategy_records"] = False
|
||||
out["show_nav_system_guide"] = False
|
||||
out["show_settings_options_swap"] = False
|
||||
return out
|
||||
|
||||
|
||||
@@ -140,7 +141,6 @@ def display_meta_for_ui() -> list[dict[str, Any]]:
|
||||
"show_settings_transfer",
|
||||
"show_settings_export",
|
||||
"show_settings_password",
|
||||
"show_settings_options_swap",
|
||||
"show_settings_options_transfer",
|
||||
]
|
||||
return [
|
||||
|
||||
@@ -53,9 +53,8 @@ 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:
|
||||
"""CoinOX 仅币本位:权利金币种为标的币."""
|
||||
return (underlying or "ETH").strip().upper() or "ETH"
|
||||
return "USDC"
|
||||
|
||||
|
||||
def spot_quote_inst_id(underlying: str) -> str:
|
||||
|
||||
@@ -698,258 +698,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.hedge_plan.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}",
|
||||
}
|
||||
)
|
||||
try:
|
||||
from lib.hedge_plan.hedge_options_exclusive_lib import block_standalone_option_open_msg
|
||||
|
||||
conn_q = cfg["get_db"]()
|
||||
try:
|
||||
excl = block_standalone_option_open_msg(conn_q)
|
||||
finally:
|
||||
conn_q.close()
|
||||
if excl:
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": excl,
|
||||
"quote_per_unit": ask,
|
||||
"premium_per_sheet": None,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": excl,
|
||||
"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": "CoinOX 仅支持币本位期权"})
|
||||
|
||||
@app.route("/api/options/open", methods=["POST"])
|
||||
@lr
|
||||
@@ -1044,270 +793,8 @@ 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
|
||||
from lib.options.options_time_lib import options_app_now_str
|
||||
|
||||
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
|
||||
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,
|
||||
)
|
||||
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": "CoinOX 仅支持币本位期权"})
|
||||
|
||||
@app.route("/api/options/orders/pending")
|
||||
@lr
|
||||
@@ -1719,44 +1206,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": "CoinOX 已移除 USDC 兑换"}), 410
|
||||
|
||||
@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": "CoinOX 已移除 USDC 兑换"}), 410
|
||||
|
||||
@app.route("/api/options/transfer", methods=["POST"])
|
||||
@lr
|
||||
@@ -1765,7 +1220,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
data = request.get_json(silent=True) or {}
|
||||
ccy = (data.get("ccy") or "USDC").upper()
|
||||
ccy = (data.get("ccy") or "USDT").upper()
|
||||
from_acct = (data.get("from") or "funding").strip()
|
||||
to_acct = (data.get("to") or "trading").strip()
|
||||
try:
|
||||
@@ -1793,19 +1248,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": "CoinOX 已移除 USDC 兑换"}), 410
|
||||
|
||||
@app.route("/api/options/history")
|
||||
@lr
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
<input type="text" name="username" autocomplete="username" tabindex="-1" aria-hidden="true"
|
||||
style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0" value="">
|
||||
<select id="opt-set-int-ccy" aria-label="币种" autocomplete="off">
|
||||
<option value="USDC" selected>USDC</option>
|
||||
<option value="USDT">USDT</option>
|
||||
<option value="USDT" selected>USDT</option>
|
||||
<option value="ETH">ETH</option>
|
||||
<option value="BTC">BTC</option>
|
||||
</select>
|
||||
<select id="opt-set-int-from" aria-label="划出账户">
|
||||
<option value="funding" selected>from: 资金</option>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
import sys
|
||||
|
||||
HOST = "47.245.56.5"
|
||||
USER = "root"
|
||||
PASSWORD = "woaini88@"
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(HOST, username=USER, password=PASSWORD, timeout=20)
|
||||
cmd = (
|
||||
"cd /opt/coinox && git fetch origin main && git reset --hard origin/main && "
|
||||
"git log -1 --oneline && pm2 restart coinox --update-env && sleep 2 && "
|
||||
"grep -n '仅支持币本位' lib/options/options_register.py | head -3 && "
|
||||
"grep -n 'TRADE_BUDGET_USDC' lib/env/env_ui_manifest.py || echo NO_USDC_BUDGET_IN_UI"
|
||||
)
|
||||
_, o, e = c.exec_command(cmd, timeout=90)
|
||||
print(o.read().decode("utf-8", "replace")[:4000])
|
||||
err = e.read().decode("utf-8", "replace")
|
||||
if err.strip():
|
||||
print("ERR:", err[:1000])
|
||||
c.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Strip dead USDC quote/open paths from options_register.py."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
P = Path(__file__).resolve().parents[1] / "lib" / "options" / "options_register.py"
|
||||
text = P.read_text(encoding="utf-8")
|
||||
|
||||
MARKER_Q = 'return jsonify({"ok": False, "msg": f"币本位报价失败: {e}"})'
|
||||
MARKER_O = 'return jsonify({"ok": False, "msg": f"币本位开仓失败: {e}"})'
|
||||
ROUTE_OPEN = ' @app.route("/api/options/open", methods=["POST"])'
|
||||
ROUTE_PENDING = ' @app.route("/api/options/orders/pending")'
|
||||
|
||||
iq = text.find(MARKER_Q)
|
||||
io = text.find(MARKER_O)
|
||||
i_open = text.find(ROUTE_OPEN)
|
||||
i_pending = text.find(ROUTE_PENDING)
|
||||
if min(iq, io, i_open, i_pending) < 0:
|
||||
raise SystemExit(f"anchors missing q={iq} o={io} open={i_open} pending={i_pending}")
|
||||
|
||||
# after quote coin except → reject; drop USDC quote body
|
||||
q_end = text.find("\n", iq) + 1
|
||||
new_q = (
|
||||
text[:q_end]
|
||||
+ "\n return jsonify({\"ok\": False, \"msg\": \"CoinOX 仅支持币本位期权\"})\n\n"
|
||||
+ text[i_open:]
|
||||
)
|
||||
|
||||
# recompute open anchors on new text
|
||||
io = new_q.find(MARKER_O)
|
||||
i_pending = new_q.find(ROUTE_PENDING)
|
||||
o_end = new_q.find("\n", io) + 1
|
||||
new_text = (
|
||||
new_q[:o_end]
|
||||
+ "\n return jsonify({\"ok\": False, \"msg\": \"CoinOX 仅支持币本位期权\"})\n\n"
|
||||
+ new_q[i_pending:]
|
||||
)
|
||||
|
||||
# Replace convert + spot swap handlers with 410 stubs (keep route names for old UI)
|
||||
import re
|
||||
|
||||
def stub_route(src: str, route_sig: str, stub_body: str) -> str:
|
||||
m = re.search(
|
||||
rf"( @app\.route\({re.escape(route_sig)}.*?\)\n @lr\n def \w+\(.*?\):\n)",
|
||||
src,
|
||||
flags=re.S,
|
||||
)
|
||||
if not m:
|
||||
print("warn: route not found", route_sig)
|
||||
return src
|
||||
start = m.end()
|
||||
# find next route at same indent
|
||||
n = re.search(r"\n @app\.route\(", src[start:])
|
||||
end = start + n.start() + 1 if n else len(src)
|
||||
return src[:start] + stub_body + src[end:]
|
||||
|
||||
stub = ' return jsonify({"ok": False, "msg": "CoinOX 已移除 USDC 兑换"}), 410\n\n'
|
||||
for sig in ('"/api/options/convert/quote", methods=["POST"]', '"/api/options/convert/execute", methods=["POST"]', '"/api/options/spot/swap", methods=["POST"]'):
|
||||
new_text = stub_route(new_text, sig, stub)
|
||||
|
||||
# transfer default USDC -> USDT
|
||||
new_text = new_text.replace(
|
||||
'ccy = (data.get("ccy") or "USDC").upper()',
|
||||
'ccy = (data.get("ccy") or "USDT").upper()',
|
||||
)
|
||||
|
||||
P.write_text(new_text, encoding="utf-8")
|
||||
print("updated", P)
|
||||
print("size", len(text), "->", len(new_text))
|
||||
Reference in New Issue
Block a user