diff --git a/app.py b/app.py index 3f02bb1..b9e410e 100644 --- a/app.py +++ b/app.py @@ -356,11 +356,16 @@ def api_cond_orders_create(): return jsonify({"ok": False, "msg": "没有可下发的客户端", "rejected": rejected}) hub_order_id = (data.get("hub_order_id") or "").strip() or str(uuid.uuid4()) + opt_type = (data.get("opt_type") or "P").strip().upper() + if opt_type not in ("P", "C"): + opt_type = "P" + # 左侧单:看跌上穿、看涨下穿 + trigger_side = "gte" if opt_type == "P" else "lte" payload = { "hub_order_id": hub_order_id, "underlying": (data.get("underlying") or "ETH").strip().upper(), - "opt_type": (data.get("opt_type") or "P").strip().upper(), - "trigger_side": (data.get("trigger_side") or "gte").strip().lower(), + "opt_type": opt_type, + "trigger_side": trigger_side, "trigger_px": data.get("trigger_px"), "deviation_pct": data.get("deviation_pct"), "min_expiry_hours": data.get("min_expiry_hours"), diff --git a/static/cond_orders.js b/static/cond_orders.js index 595745e..46b6ac4 100644 --- a/static/cond_orders.js +++ b/static/cond_orders.js @@ -7,6 +7,28 @@ .replace(/"/g, """); } + function leftTriggerSide(optType) { + return String(optType || "P").toUpperCase() === "C" ? "lte" : "gte"; + } + + function triggerHintText(optType, px) { + const put = String(optType || "P").toUpperCase() !== "C"; + const price = px && String(px).trim() !== "" ? String(px) : "触发价"; + if (put) { + return "看跌左侧单:指数上穿 " + price + " 后,在偏离区内开最近实值 Put。"; + } + return "看涨左侧单:指数下穿 " + price + " 后,在偏离区内开最近实值 Call。"; + } + + function syncTriggerHint() { + const hint = document.getElementById("cf-trigger-hint"); + const typeEl = document.getElementById("cf-opt-type"); + const pxEl = document.getElementById("cf-trigger-px"); + if (!hint || !typeEl) return; + hint.textContent = triggerHintText(typeEl.value, pxEl && pxEl.value); + hint.className = "cond-trigger-hint " + (typeEl.value === "C" ? "is-call" : "is-put"); + } + function statusLabel(o) { const st = o.status || ""; const reason = o.end_reason || ""; @@ -23,6 +45,14 @@ return map2[st] || st || "—"; } + function statusClass(o) { + const st = o.status || ""; + if (st === "open") return "st-open"; + if (st === "triggering") return "st-trig"; + if (st === "armed") return "st-armed"; + return "st-done"; + } + async function loadCandidates() { const box = document.getElementById("cf-instances"); if (!box) return; @@ -46,18 +76,22 @@ : "离线" : "可选"; return ( - '" + "" ); }) .join(""); @@ -70,16 +104,16 @@ const d = await r.json(); const list = d.orders || []; if (!list.length) { - box.innerHTML = '暂无条件单'; + box.innerHTML = '
暂无条件单
'; return; } box.innerHTML = - '
' + + '
' + "" + "" + list .map(function (o) { - const side = o.trigger_side === "lte" ? "≤" : "≥"; + const side = o.trigger_side === "lte" ? "下穿 ≤" : "上穿 ≥"; const exit = (o.profit_exit_enabled ? "翻倍×" + (o.profit_exit_mult || "") : "") + (o.target_index != null ? " 目标" + o.target_index : ""); @@ -113,9 +147,11 @@ "" + - "" + + "" + "" + @@ -131,7 +167,7 @@ "" + - "" + '
客户端状态标的触发偏离到期h出场说明操作
" + escapeHtml(o.instance_name || "") + "" + + '' + escapeHtml(statusLabel(o)) + - "" + escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) + "" + escapeHtml(exit || "—") + "" + + '' + escapeHtml(o.message || "") + "
' + @@ -202,11 +238,12 @@ alert("请选择至少一台可选客户端"); return; } + const optType = document.getElementById("cf-opt-type").value; const body = { instance_ids: ids, underlying: document.getElementById("cf-underlying").value, - opt_type: document.getElementById("cf-opt-type").value, - trigger_side: document.getElementById("cf-trigger-side").value, + opt_type: optType, + trigger_side: leftTriggerSide(optType), trigger_px: document.getElementById("cf-trigger-px").value, deviation_pct: document.getElementById("cf-deviation").value, min_expiry_hours: document.getElementById("cf-expiry-h").value, @@ -235,6 +272,11 @@ if (btn) btn.addEventListener("click", refresh); const create = document.getElementById("btn-cond-create"); if (create) create.addEventListener("click", createOrder); + const typeEl = document.getElementById("cf-opt-type"); + const pxEl = document.getElementById("cf-trigger-px"); + if (typeEl) typeEl.addEventListener("change", syncTriggerHint); + if (pxEl) pxEl.addEventListener("input", syncTriggerHint); + syncTriggerHint(); refresh(); setInterval(refresh, 10000); } diff --git a/static/hub.css b/static/hub.css index d4f77e9..3b46f73 100644 --- a/static/hub.css +++ b/static/hub.css @@ -175,7 +175,7 @@ body.page-login .main { } .stack label, .form-grid label { display: block; margin: 8px 0; } -input[type="text"], input[type="password"], input:not([type]) { +input[type="text"], input[type="password"], input[type="number"], input:not([type]), select { width: 100%; padding: 8px 10px; border-radius: 8px; @@ -431,3 +431,115 @@ input.readonly { padding-right: clamp(32px, 6vw, 96px); } } + +.cond-head { justify-content: space-between; align-items: flex-start; } +.cond-lead { margin: 6px 0 0; max-width: 720px; line-height: 1.5; } +.cond-layout { display: grid; gap: 16px; } +.cond-card { padding: 18px 18px 16px; } +.cond-card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 14px; +} +.cond-card-head h2 { + margin: 0; + font-size: 1.02rem; + letter-spacing: 0.03em; +} +.cond-chip { + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.06em; + color: #041018; + background: linear-gradient(90deg, var(--accent-deep), var(--accent)); + border-radius: 999px; + padding: 3px 10px; +} +.cond-section { + border-top: 1px solid rgba(56, 189, 248, 0.12); + padding: 14px 0 4px; +} +.cond-section:first-of-type { border-top: 0; padding-top: 0; } +.cond-section-title { + font-size: 0.78rem; + color: var(--accent-2); + letter-spacing: 0.08em; + margin-bottom: 10px; + font-weight: 600; +} +.cond-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 12px 14px; + align-items: end; +} +.cond-grid label { + margin: 0; + font-size: 0.8rem; + color: #b6c7da; +} +.cond-grid input, +.cond-grid select { + margin-top: 6px; +} +.cond-trigger-hint { + grid-column: 1 / -1; + padding: 9px 12px; + border-radius: 10px; + font-size: 0.82rem; + line-height: 1.45; + border: 1px solid var(--line); + background: rgba(8, 16, 30, 0.7); + color: var(--text); +} +.cond-trigger-hint.is-put { border-color: rgba(251, 113, 133, 0.35); } +.cond-trigger-hint.is-call { border-color: rgba(52, 211, 153, 0.35); } +.cond-switch { + display: flex !important; + align-items: center; + gap: 8px; + min-height: 62px; +} +.cond-switch input { width: auto; margin: 0; } +.cond-inst-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 10px; +} +.cond-inst { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 12px; + border-radius: 10px; + border: 1px solid var(--line); + background: rgba(8, 16, 30, 0.72); + cursor: pointer; +} +.cond-inst input { width: auto; margin: 0; } +.cond-inst-name { flex: 1; font-weight: 600; } +.cond-inst-tag { color: var(--muted); font-size: 0.75rem; } +.cond-inst.is-off { opacity: 0.5; cursor: not-allowed; } +.cond-actions { + display: flex; + align-items: center; + gap: 12px; + margin-top: 14px; +} +.cond-empty { padding: 28px 8px; text-align: center; } +.cond-st { + display: inline-block; + min-width: 64px; + padding: 2px 8px; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 600; +} +.cond-st.st-armed { color: var(--accent-2); background: rgba(56, 189, 248, 0.12); } +.cond-st.st-trig { color: #fbbf24; background: rgba(251, 191, 36, 0.12); } +.cond-st.st-open { color: var(--ok); background: rgba(52, 211, 153, 0.12); } +.cond-st.st-done { color: var(--muted); background: rgba(138, 160, 184, 0.12); } +.cond-table-wrap { margin-top: 0; } diff --git a/templates/cond_orders.html b/templates/cond_orders.html index 73835f6..7f392ff 100644 --- a/templates/cond_orders.html +++ b/templates/cond_orders.html @@ -1,63 +1,82 @@ {% extends "base.html" %} {% block title %}条件单 · 期权中控{% endblock %} {% block body %} -
-

条件开仓单

+
+
+

左侧条件开仓

+

中控只下发;盯盘、买币、开平仓都在客户端。有仓或已占用的机器不可选。

+
-

中控只下发指令;行情监控与买币/开仓/平仓/卖币均在客户端本地执行。有仓或已占用的机器不可选。

-
-

新建条件单

-
- - - - - - - - - -
-
-
选择客户端
-
-
-
- - -
-
+
+
+
+

新建条件单

+ 左侧单 +
-
-

条件单列表

-
加载中…
+
+
入场
+
+ + + + + +
+
+
+ +
+
出场
+
+ + + +
+
+ +
+
客户端
+
+
+ +
+ + +
+
+ +
+
+

条件单列表

+
+
加载中…
+
{% endblock %} {% block scripts %}