修复全仓复利关闭后仍无法开仓:前端不再残留选中全仓,后端强制改指定张数。
审计:开关关闭时隐藏全仓芯片并自动勾选指定张数;报价/余额热同步 compound_full_enabled;API 将 compound_full 归一为 sheets(缺张数默认1);单测覆盖开关开关两种归一路径。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4424,6 +4424,9 @@ html[data-theme="light"] .opt-pending-item {
|
|||||||
.opt-size-mode-chip {
|
.opt-size-mode-chip {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.opt-size-mode-chip[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
.opt-size-mode-chip input[type="radio"] {
|
.opt-size-mode-chip input[type="radio"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -271,13 +271,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentSizeMode() {
|
function compoundFullEnabled() {
|
||||||
const el = document.querySelector('input[name="opt-size-mode"]:checked');
|
// 缺省按关闭,避免热更关闭后仍误用全仓复利
|
||||||
return el ? el.value : "sheets";
|
return !!(root && String(root.dataset.compoundFullEnabled || "0") === "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
function compoundFullEnabled() {
|
function currentSizeMode() {
|
||||||
return !!(root && String(root.dataset.compoundFullEnabled || "1") === "1");
|
const el = document.querySelector('input[name="opt-size-mode"]:checked:not(:disabled)');
|
||||||
|
if (el) return el.value;
|
||||||
|
const any = document.querySelector('input[name="opt-size-mode"]:checked');
|
||||||
|
if (any && any.value === "compound_full" && !compoundFullEnabled()) return "sheets";
|
||||||
|
if (any && any.value === "budget_full" && compoundFullEnabled()) return "compound_full";
|
||||||
|
return "sheets";
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCompoundModeUi(compoundOn) {
|
||||||
|
if (root) root.dataset.compoundFullEnabled = compoundOn ? "1" : "0";
|
||||||
|
updateSizeInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncCompoundFlagsFromPayload(d) {
|
||||||
|
if (!d || typeof d !== "object") return;
|
||||||
|
if (d.compound_full_enabled != null) {
|
||||||
|
applyCompoundModeUi(!!d.compound_full_enabled);
|
||||||
|
} else if (d.cfg && d.cfg.compound_full_enabled != null) {
|
||||||
|
applyCompoundModeUi(!!d.cfg.compound_full_enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSizeInputs() {
|
function updateSizeInputs() {
|
||||||
@@ -292,21 +311,38 @@
|
|||||||
const compoundCapLine = document.getElementById("opt-compound-cap-line");
|
const compoundCapLine = document.getElementById("opt-compound-cap-line");
|
||||||
const compoundOn = compoundFullEnabled();
|
const compoundOn = compoundFullEnabled();
|
||||||
if (budgetWrap) {
|
if (budgetWrap) {
|
||||||
budgetWrap.hidden = compoundOn;
|
budgetWrap.hidden = !!compoundOn;
|
||||||
|
budgetWrap.style.display = compoundOn ? "none" : "";
|
||||||
const radio = budgetWrap.querySelector('input[name="opt-size-mode"]');
|
const radio = budgetWrap.querySelector('input[name="opt-size-mode"]');
|
||||||
if (radio) radio.disabled = compoundOn;
|
if (radio) radio.disabled = !!compoundOn;
|
||||||
}
|
}
|
||||||
if (compoundWrap) {
|
if (compoundWrap) {
|
||||||
compoundWrap.hidden = !compoundOn;
|
compoundWrap.hidden = !compoundOn;
|
||||||
|
compoundWrap.style.display = compoundOn ? "" : "none";
|
||||||
const radio = compoundWrap.querySelector('input[name="opt-size-mode"]');
|
const radio = compoundWrap.querySelector('input[name="opt-size-mode"]');
|
||||||
if (radio) radio.disabled = !compoundOn;
|
if (radio) radio.disabled = !compoundOn;
|
||||||
}
|
}
|
||||||
if (compoundOn && mode === "budget_full") {
|
if (compoundOn && (mode === "budget_full" || mode === "compound_full")) {
|
||||||
const compoundRadio = document.querySelector('input[name="opt-size-mode"][value="compound_full"]');
|
const compoundRadio = document.querySelector('input[name="opt-size-mode"][value="compound_full"]');
|
||||||
if (compoundRadio) compoundRadio.checked = true;
|
if (compoundRadio) {
|
||||||
} else if (!compoundOn && mode === "compound_full") {
|
compoundRadio.disabled = false;
|
||||||
const sheetsRadio = document.querySelector('input[name="opt-size-mode"][value="sheets"]');
|
compoundRadio.checked = true;
|
||||||
if (sheetsRadio) sheetsRadio.checked = true;
|
}
|
||||||
|
} else if (!compoundOn) {
|
||||||
|
const compoundRadio = document.querySelector('input[name="opt-size-mode"][value="compound_full"]');
|
||||||
|
if (compoundRadio) {
|
||||||
|
compoundRadio.checked = false;
|
||||||
|
compoundRadio.disabled = true;
|
||||||
|
}
|
||||||
|
// currentSizeMode 会把残留 compound 映射成 sheets,须实际勾选,避免无选中无法开仓
|
||||||
|
const checkedOk = document.querySelector('input[name="opt-size-mode"]:checked:not(:disabled)');
|
||||||
|
if (!checkedOk) {
|
||||||
|
const sheetsRadio = document.querySelector('input[name="opt-size-mode"][value="sheets"]');
|
||||||
|
if (sheetsRadio) {
|
||||||
|
sheetsRadio.disabled = false;
|
||||||
|
sheetsRadio.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const modeNow = currentSizeMode();
|
const modeNow = currentSizeMode();
|
||||||
if (sheetsEl) sheetsEl.style.display = modeNow === "sheets" ? "" : "none";
|
if (sheetsEl) sheetsEl.style.display = modeNow === "sheets" ? "" : "none";
|
||||||
@@ -328,8 +364,9 @@
|
|||||||
}
|
}
|
||||||
document.querySelectorAll(".opt-size-mode-chip").forEach(function (chip) {
|
document.querySelectorAll(".opt-size-mode-chip").forEach(function (chip) {
|
||||||
const radio = chip.querySelector('input[name="opt-size-mode"]');
|
const radio = chip.querySelector('input[name="opt-size-mode"]');
|
||||||
chip.classList.toggle("is-selected", !!(radio && radio.checked));
|
const selected = !!(radio && radio.checked && !radio.disabled);
|
||||||
chip.classList.toggle("active", !!(radio && radio.checked));
|
chip.classList.toggle("is-selected", selected);
|
||||||
|
chip.classList.toggle("active", selected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,6 +402,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function quoteUrl(instId) {
|
function quoteUrl(instId) {
|
||||||
|
updateSizeInputs();
|
||||||
const mode = currentSizeMode();
|
const mode = currentSizeMode();
|
||||||
let url = "/api/options/quote?inst_id=" + encodeURIComponent(instId) + "&mode=" + mode;
|
let url = "/api/options/quote?inst_id=" + encodeURIComponent(instId) + "&mode=" + mode;
|
||||||
if (mode === "eth_amount") {
|
if (mode === "eth_amount") {
|
||||||
@@ -1186,6 +1224,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fillOrderPanel(d) {
|
function fillOrderPanel(d) {
|
||||||
|
syncCompoundFlagsFromPayload(d);
|
||||||
state.orderQuote = d && d.ok ? d : null;
|
state.orderQuote = d && d.ok ? d : null;
|
||||||
const sz = d.sizing || {};
|
const sz = d.sizing || {};
|
||||||
const canOpen = !!(d && d.ok && d.can_open);
|
const canOpen = !!(d && d.ok && d.can_open);
|
||||||
@@ -1386,6 +1425,7 @@
|
|||||||
const btn = document.getElementById("opt-open-btn");
|
const btn = document.getElementById("opt-open-btn");
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
try {
|
try {
|
||||||
|
updateSizeInputs();
|
||||||
const mode = currentSizeMode();
|
const mode = currentSizeMode();
|
||||||
const body = {
|
const body = {
|
||||||
inst_id: state.selectedInst,
|
inst_id: state.selectedInst,
|
||||||
@@ -1395,7 +1435,10 @@
|
|||||||
if (mode === "eth_amount") {
|
if (mode === "eth_amount") {
|
||||||
body.eth_amount = parseFloat(document.getElementById("opt-eth-amount").value);
|
body.eth_amount = parseFloat(document.getElementById("opt-eth-amount").value);
|
||||||
} else if (mode === "sheets") {
|
} else if (mode === "sheets") {
|
||||||
body.sheets = parseInt(document.getElementById("opt-sheets-amount").value, 10);
|
body.sheets = parseInt(document.getElementById("opt-sheets-amount").value, 10) || 1;
|
||||||
|
} else if (mode === "compound_full" && !compoundFullEnabled()) {
|
||||||
|
body.mode = "sheets";
|
||||||
|
body.sheets = parseInt(document.getElementById("opt-sheets-amount").value, 10) || 1;
|
||||||
}
|
}
|
||||||
const tgtRaw = (document.getElementById("opt-target-idx").value || "").trim();
|
const tgtRaw = (document.getElementById("opt-target-idx").value || "").trim();
|
||||||
if (tgtRaw !== "") {
|
if (tgtRaw !== "") {
|
||||||
@@ -2369,6 +2412,15 @@
|
|||||||
function bootOptionsPanel() {
|
function bootOptionsPanel() {
|
||||||
applyBudgetBuffer(state.budgetBuffer);
|
applyBudgetBuffer(state.budgetBuffer);
|
||||||
updateSizeInputs();
|
updateSizeInputs();
|
||||||
|
void (async function syncLiveCompoundFlag() {
|
||||||
|
try {
|
||||||
|
const d = await apiJson("/api/options/balances");
|
||||||
|
syncCompoundFlagsFromPayload(d);
|
||||||
|
if (d && d.trade_budget != null && root) {
|
||||||
|
root.dataset.tradeBudget = String(d.trade_budget);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
})();
|
||||||
syncMoneyFilterButtons();
|
syncMoneyFilterButtons();
|
||||||
syncChainViewUI();
|
syncChainViewUI();
|
||||||
updateUnderlyingLabel();
|
updateUnderlyingLabel();
|
||||||
|
|||||||
Vendored
+5
@@ -95,6 +95,11 @@ HOT_RELOAD_EXACT = frozenset({
|
|||||||
"OKX_OPTIONS_CHAIN_MAX_DTE_DAYS",
|
"OKX_OPTIONS_CHAIN_MAX_DTE_DAYS",
|
||||||
"OKX_OPTIONS_MAX_DTE_DAYS",
|
"OKX_OPTIONS_MAX_DTE_DAYS",
|
||||||
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS",
|
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS",
|
||||||
|
"OKX_OPTIONS_COMPOUND_FULL_ENABLED",
|
||||||
|
"OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED",
|
||||||
|
"OKX_OPTIONS_COMPOUND_FULL_CAP_USDC",
|
||||||
|
"OKX_OPTIONS_TRADE_BUDGET_USDC",
|
||||||
|
"OKX_OPTIONS_BUDGET_BUFFER",
|
||||||
"OKX_TRADE_MODE",
|
"OKX_TRADE_MODE",
|
||||||
"MAX_ACTIVE_HEDGE_PLANS",
|
"MAX_ACTIVE_HEDGE_PLANS",
|
||||||
"HEDGE_PLAN_LIVE_ORDER",
|
"HEDGE_PLAN_LIVE_ORDER",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=116">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=117">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<script src="/static/open_submit_gate.js?v=1"></script>
|
<script src="/static/open_submit_gate.js?v=1"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ pwa_app_name }}</title>
|
<title>{{ pwa_app_name }}</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=116">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=117">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -200,6 +200,16 @@ def _size_mode_budget_cap(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_size_mode(mode: str) -> tuple[str, str | None]:
|
||||||
|
"""全仓复利关闭时强制离开 compound_full,避免前端残留选中导致无法开仓."""
|
||||||
|
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
|
||||||
|
return m, None
|
||||||
|
|
||||||
|
|
||||||
def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||||
"""全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer."""
|
"""全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer."""
|
||||||
if not _compound_full_enabled():
|
if not _compound_full_enabled():
|
||||||
@@ -411,7 +421,16 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
return jsonify({"ok": False, "msg": err})
|
return jsonify({"ok": False, "msg": err})
|
||||||
force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
force = (request.args.get("force") or "").strip().lower() in ("1", "true", "yes")
|
||||||
bal = cfg["fetch_options_balances"](ex, force=force, scope="main")
|
bal = cfg["fetch_options_balances"](ex, force=force, scope="main")
|
||||||
return jsonify({"ok": True, **bal, "trade_budget": cfg["trade_budget"]})
|
return jsonify(
|
||||||
|
{
|
||||||
|
"ok": True,
|
||||||
|
**bal,
|
||||||
|
"trade_budget": _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", float(cfg.get("trade_budget") or 10)),
|
||||||
|
"compound_full_enabled": _compound_full_enabled(),
|
||||||
|
"compound_full_cap_enabled": _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False),
|
||||||
|
"compound_full_cap_usdc": _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@app.route("/api/options/chain")
|
@app.route("/api/options/chain")
|
||||||
@lr
|
@lr
|
||||||
@@ -475,7 +494,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
ask = q.get("ask")
|
ask = q.get("ask")
|
||||||
ct_mult = q.get("ct_mult") or 0.01
|
ct_mult = q.get("ct_mult") or 0.01
|
||||||
min_sz = q.get("min_sz") or 1
|
min_sz = q.get("min_sz") or 1
|
||||||
mode = (request.args.get("mode") or "budget_full").strip()
|
mode = (request.args.get("mode") or "sheets").strip()
|
||||||
sheet_count = None
|
sheet_count = None
|
||||||
try:
|
try:
|
||||||
if request.args.get("sheets"):
|
if request.args.get("sheets"):
|
||||||
@@ -486,26 +505,39 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
paid = _open_premium_paid(cfg, inst_id)
|
paid = _open_premium_paid(cfg, inst_id)
|
||||||
target = sheet_count if sheet_count is not None else 0
|
target = sheet_count if sheet_count is not None else 0
|
||||||
return jsonify(_attach_close_preview(cfg, ex, {**q, "pos": target, "premium_paid": paid}, sheets=target, premium_paid=paid))
|
return jsonify(_attach_close_preview(cfg, ex, {**q, "pos": target, "premium_paid": paid}, sheets=target, premium_paid=paid))
|
||||||
|
mode, mode_note = _normalize_size_mode(mode)
|
||||||
budget = cfg["trade_budget"]
|
budget = cfg["trade_budget"]
|
||||||
budget_cap = cfg["trade_budget"]
|
budget_cap = cfg["trade_budget"]
|
||||||
available_usdc = None
|
available_usdc = None
|
||||||
if mode == "budget_full":
|
if mode == "budget_full":
|
||||||
blocked = _budget_full_blocked_by_compound_msg()
|
blocked = _budget_full_blocked_by_compound_msg()
|
||||||
if blocked:
|
if blocked:
|
||||||
return jsonify({"ok": False, "msg": blocked})
|
return jsonify(
|
||||||
|
{
|
||||||
|
"ok": False,
|
||||||
|
"msg": blocked,
|
||||||
|
"compound_full_enabled": _compound_full_enabled(),
|
||||||
|
}
|
||||||
|
)
|
||||||
budget, budget_err = _budget_full_usdc(cfg, ex)
|
budget, budget_err = _budget_full_usdc(cfg, ex)
|
||||||
if budget is None:
|
if budget is None:
|
||||||
return jsonify({"ok": False, "msg": budget_err})
|
return jsonify({"ok": False, "msg": budget_err, "compound_full_enabled": _compound_full_enabled()})
|
||||||
budget_cap = budget
|
budget_cap = budget
|
||||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||||
|
|
||||||
available_usdc = fetch_options_trading_usdc(ex)
|
available_usdc = fetch_options_trading_usdc(ex)
|
||||||
elif mode == "compound_full":
|
elif mode == "compound_full":
|
||||||
if not _compound_full_enabled():
|
if not _compound_full_enabled():
|
||||||
return jsonify({"ok": False, "msg": "全仓复利未开启(OKX_OPTIONS_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)
|
budget, budget_err = _compound_full_usdc(cfg, ex)
|
||||||
if budget is None:
|
if budget is None:
|
||||||
return jsonify({"ok": False, "msg": budget_err})
|
return jsonify({"ok": False, "msg": budget_err, "compound_full_enabled": True})
|
||||||
budget_cap = budget
|
budget_cap = budget
|
||||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||||
|
|
||||||
@@ -721,6 +753,9 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
"available_usdc": available_usdc,
|
"available_usdc": available_usdc,
|
||||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||||
"compound_full_usdc": budget if mode == "compound_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(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -752,8 +787,12 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
return jsonify({"ok": False, "msg": f"互斥校验失败: {e}"})
|
return jsonify({"ok": False, "msg": f"互斥校验失败: {e}"})
|
||||||
data = request.get_json(silent=True) or {}
|
data = request.get_json(silent=True) or {}
|
||||||
inst_id = (data.get("inst_id") or "").strip()
|
inst_id = (data.get("inst_id") or "").strip()
|
||||||
mode = (data.get("mode") or "budget_full").strip()
|
mode = (data.get("mode") or "sheets").strip()
|
||||||
|
mode, mode_note = _normalize_size_mode(mode)
|
||||||
signal_note = (data.get("signal_note") or "").strip()
|
signal_note = (data.get("signal_note") or "").strip()
|
||||||
|
if mode_note and mode == "sheets" and (data.get("mode") or "").strip() == "compound_full":
|
||||||
|
# 前端残留全仓复利选中时,已自动改指定张数;继续开仓
|
||||||
|
pass
|
||||||
target_index = None
|
target_index = None
|
||||||
raw_target = data.get("target_index")
|
raw_target = data.get("target_index")
|
||||||
if raw_target is not None and str(raw_target).strip() != "":
|
if raw_target is not None and str(raw_target).strip() != "":
|
||||||
@@ -819,20 +858,32 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
try:
|
try:
|
||||||
sheet_count = int(data.get("sheets"))
|
sheet_count = int(data.get("sheets"))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return jsonify({"ok": False, "msg": "张数无效"})
|
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 = cfg["trade_budget"]
|
||||||
budget_cap = cfg["trade_budget"]
|
budget_cap = cfg["trade_budget"]
|
||||||
if mode == "budget_full":
|
if mode == "budget_full":
|
||||||
blocked = _budget_full_blocked_by_compound_msg()
|
blocked = _budget_full_blocked_by_compound_msg()
|
||||||
if blocked:
|
if blocked:
|
||||||
return jsonify({"ok": False, "msg": blocked})
|
return jsonify({"ok": False, "msg": blocked, "compound_full_enabled": _compound_full_enabled()})
|
||||||
budget, budget_err = _budget_full_usdc(cfg, ex)
|
budget, budget_err = _budget_full_usdc(cfg, ex)
|
||||||
if budget is None:
|
if budget is None:
|
||||||
return jsonify({"ok": False, "msg": budget_err})
|
return jsonify({"ok": False, "msg": budget_err})
|
||||||
budget_cap = budget
|
budget_cap = budget
|
||||||
elif mode == "compound_full":
|
elif mode == "compound_full":
|
||||||
if not _compound_full_enabled():
|
if not _compound_full_enabled():
|
||||||
return jsonify({"ok": False, "msg": "全仓复利未开启(OKX_OPTIONS_COMPOUND_FULL_ENABLED)"})
|
return jsonify(
|
||||||
|
{
|
||||||
|
"ok": False,
|
||||||
|
"msg": "全仓复利未开启,请改用指定张数或先开启全仓复利",
|
||||||
|
"compound_full_enabled": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
budget, budget_err = _compound_full_usdc(cfg, ex)
|
budget, budget_err = _compound_full_usdc(cfg, ex)
|
||||||
if budget is None:
|
if budget is None:
|
||||||
return jsonify({"ok": False, "msg": budget_err})
|
return jsonify({"ok": False, "msg": budget_err})
|
||||||
|
|||||||
@@ -350,4 +350,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/static/options_panel.js?v=61"></script>
|
<script src="/static/options_panel.js?v=62"></script>
|
||||||
|
|||||||
@@ -64,6 +64,35 @@ class TestOptionsBudgetModes(unittest.TestCase):
|
|||||||
self.assertIsNone(msg)
|
self.assertIsNone(msg)
|
||||||
self.assertEqual(count_live_option_positions([]), 0)
|
self.assertEqual(count_live_option_positions([]), 0)
|
||||||
|
|
||||||
|
def test_normalize_size_mode_when_compound_off(self):
|
||||||
|
import os
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from lib.options import options_register as reg
|
||||||
|
|
||||||
|
with patch.dict(os.environ, {"OKX_OPTIONS_COMPOUND_FULL_ENABLED": "false"}):
|
||||||
|
mode, note = reg._normalize_size_mode("compound_full")
|
||||||
|
self.assertEqual(mode, "sheets")
|
||||||
|
self.assertIsNotNone(note)
|
||||||
|
mode2, note2 = reg._normalize_size_mode("budget_full")
|
||||||
|
self.assertEqual(mode2, "budget_full")
|
||||||
|
self.assertIsNone(note2)
|
||||||
|
mode3, _ = reg._normalize_size_mode("sheets")
|
||||||
|
self.assertEqual(mode3, "sheets")
|
||||||
|
|
||||||
|
def test_normalize_size_mode_when_compound_on(self):
|
||||||
|
import os
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from lib.options import options_register as reg
|
||||||
|
|
||||||
|
with patch.dict(os.environ, {"OKX_OPTIONS_COMPOUND_FULL_ENABLED": "true"}):
|
||||||
|
mode, note = reg._normalize_size_mode("budget_full")
|
||||||
|
self.assertEqual(mode, "compound_full")
|
||||||
|
self.assertIsNone(note)
|
||||||
|
mode2, _ = reg._normalize_size_mode("compound_full")
|
||||||
|
self.assertEqual(mode2, "compound_full")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user