@@ -356,11 +356,16 @@ def api_cond_orders_create():
|
|||||||
return jsonify({"ok": False, "msg": "没有可下发的客户端", "rejected": rejected})
|
return jsonify({"ok": False, "msg": "没有可下发的客户端", "rejected": rejected})
|
||||||
|
|
||||||
hub_order_id = (data.get("hub_order_id") or "").strip() or str(uuid.uuid4())
|
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 = {
|
payload = {
|
||||||
"hub_order_id": hub_order_id,
|
"hub_order_id": hub_order_id,
|
||||||
"underlying": (data.get("underlying") or "ETH").strip().upper(),
|
"underlying": (data.get("underlying") or "ETH").strip().upper(),
|
||||||
"opt_type": (data.get("opt_type") or "P").strip().upper(),
|
"opt_type": opt_type,
|
||||||
"trigger_side": (data.get("trigger_side") or "gte").strip().lower(),
|
"trigger_side": trigger_side,
|
||||||
"trigger_px": data.get("trigger_px"),
|
"trigger_px": data.get("trigger_px"),
|
||||||
"deviation_pct": data.get("deviation_pct"),
|
"deviation_pct": data.get("deviation_pct"),
|
||||||
"min_expiry_hours": data.get("min_expiry_hours"),
|
"min_expiry_hours": data.get("min_expiry_hours"),
|
||||||
|
|||||||
+54
-12
@@ -7,6 +7,28 @@
|
|||||||
.replace(/"/g, """);
|
.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) {
|
function statusLabel(o) {
|
||||||
const st = o.status || "";
|
const st = o.status || "";
|
||||||
const reason = o.end_reason || "";
|
const reason = o.end_reason || "";
|
||||||
@@ -23,6 +45,14 @@
|
|||||||
return map2[st] || st || "—";
|
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() {
|
async function loadCandidates() {
|
||||||
const box = document.getElementById("cf-instances");
|
const box = document.getElementById("cf-instances");
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
@@ -46,18 +76,22 @@
|
|||||||
: "离线"
|
: "离线"
|
||||||
: "可选";
|
: "可选";
|
||||||
return (
|
return (
|
||||||
'<label class="check" title="' +
|
'<label class="cond-inst' +
|
||||||
|
(disabled ? " is-off" : "") +
|
||||||
|
'" title="' +
|
||||||
escapeHtml(tip) +
|
escapeHtml(tip) +
|
||||||
'">' +
|
'">' +
|
||||||
'<input type="checkbox" name="cf-inst" value="' +
|
'<input type="checkbox" name="cf-inst" value="' +
|
||||||
encodeURIComponent(String(it.id)) +
|
encodeURIComponent(String(it.id)) +
|
||||||
'"' +
|
'"' +
|
||||||
(disabled ? " disabled" : "") +
|
(disabled ? " disabled" : "") +
|
||||||
"> " +
|
">" +
|
||||||
|
'<span class="cond-inst-name">' +
|
||||||
escapeHtml(it.name || "") +
|
escapeHtml(it.name || "") +
|
||||||
' <span class="muted">(' +
|
"</span>" +
|
||||||
|
'<span class="cond-inst-tag">' +
|
||||||
escapeHtml(tip) +
|
escapeHtml(tip) +
|
||||||
")</span></label>"
|
"</span></label>"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
@@ -70,16 +104,16 @@
|
|||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
const list = d.orders || [];
|
const list = d.orders || [];
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
box.innerHTML = '<span class="muted">暂无条件单</span>';
|
box.innerHTML = '<div class="cond-empty">暂无条件单</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
box.innerHTML =
|
box.innerHTML =
|
||||||
'<div class="overview-table-wrap"><table class="overview-table"><thead><tr>' +
|
'<div class="overview-table-wrap cond-table-wrap"><table class="overview-table"><thead><tr>' +
|
||||||
"<th>客户端</th><th>状态</th><th>标的</th><th>触发</th><th>偏离</th><th>到期h</th><th>出场</th><th>说明</th><th>操作</th>" +
|
"<th>客户端</th><th>状态</th><th>标的</th><th>触发</th><th>偏离</th><th>到期h</th><th>出场</th><th>说明</th><th>操作</th>" +
|
||||||
"</tr></thead><tbody>" +
|
"</tr></thead><tbody>" +
|
||||||
list
|
list
|
||||||
.map(function (o) {
|
.map(function (o) {
|
||||||
const side = o.trigger_side === "lte" ? "≤" : "≥";
|
const side = o.trigger_side === "lte" ? "下穿 ≤" : "上穿 ≥";
|
||||||
const exit =
|
const exit =
|
||||||
(o.profit_exit_enabled ? "翻倍×" + (o.profit_exit_mult || "") : "") +
|
(o.profit_exit_enabled ? "翻倍×" + (o.profit_exit_mult || "") : "") +
|
||||||
(o.target_index != null ? " 目标" + o.target_index : "");
|
(o.target_index != null ? " 目标" + o.target_index : "");
|
||||||
@@ -113,9 +147,11 @@
|
|||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml(o.instance_name || "") +
|
escapeHtml(o.instance_name || "") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td>" +
|
'<td><span class="cond-st ' +
|
||||||
|
statusClass(o) +
|
||||||
|
'">' +
|
||||||
escapeHtml(statusLabel(o)) +
|
escapeHtml(statusLabel(o)) +
|
||||||
"</td>" +
|
"</span></td>" +
|
||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) +
|
escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
@@ -131,7 +167,7 @@
|
|||||||
"<td>" +
|
"<td>" +
|
||||||
escapeHtml(exit || "—") +
|
escapeHtml(exit || "—") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
"<td class=\"muted\">" +
|
'<td class="muted">' +
|
||||||
escapeHtml(o.message || "") +
|
escapeHtml(o.message || "") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td><div class="overview-actions">' +
|
'<td><div class="overview-actions">' +
|
||||||
@@ -202,11 +238,12 @@
|
|||||||
alert("请选择至少一台可选客户端");
|
alert("请选择至少一台可选客户端");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const optType = document.getElementById("cf-opt-type").value;
|
||||||
const body = {
|
const body = {
|
||||||
instance_ids: ids,
|
instance_ids: ids,
|
||||||
underlying: document.getElementById("cf-underlying").value,
|
underlying: document.getElementById("cf-underlying").value,
|
||||||
opt_type: document.getElementById("cf-opt-type").value,
|
opt_type: optType,
|
||||||
trigger_side: document.getElementById("cf-trigger-side").value,
|
trigger_side: leftTriggerSide(optType),
|
||||||
trigger_px: document.getElementById("cf-trigger-px").value,
|
trigger_px: document.getElementById("cf-trigger-px").value,
|
||||||
deviation_pct: document.getElementById("cf-deviation").value,
|
deviation_pct: document.getElementById("cf-deviation").value,
|
||||||
min_expiry_hours: document.getElementById("cf-expiry-h").value,
|
min_expiry_hours: document.getElementById("cf-expiry-h").value,
|
||||||
@@ -235,6 +272,11 @@
|
|||||||
if (btn) btn.addEventListener("click", refresh);
|
if (btn) btn.addEventListener("click", refresh);
|
||||||
const create = document.getElementById("btn-cond-create");
|
const create = document.getElementById("btn-cond-create");
|
||||||
if (create) create.addEventListener("click", createOrder);
|
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();
|
refresh();
|
||||||
setInterval(refresh, 10000);
|
setInterval(refresh, 10000);
|
||||||
}
|
}
|
||||||
|
|||||||
+113
-1
@@ -175,7 +175,7 @@ body.page-login .main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stack label, .form-grid label { display: block; margin: 8px 0; }
|
.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%;
|
width: 100%;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -431,3 +431,115 @@ input.readonly {
|
|||||||
padding-right: clamp(32px, 6vw, 96px);
|
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; }
|
||||||
|
|||||||
+72
-53
@@ -1,63 +1,82 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}条件单 · 期权中控{% endblock %}
|
{% block title %}条件单 · 期权中控{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<section class="page-head">
|
<section class="page-head cond-head">
|
||||||
<h1>条件开仓单</h1>
|
<div>
|
||||||
|
<h1>左侧条件开仓</h1>
|
||||||
|
<p class="muted cond-lead">中控只下发;盯盘、买币、开平仓都在客户端。有仓或已占用的机器不可选。</p>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn" id="btn-cond-refresh">刷新</button>
|
<button type="button" class="btn" id="btn-cond-refresh">刷新</button>
|
||||||
</section>
|
</section>
|
||||||
<p class="muted">中控只下发指令;行情监控与买币/开仓/平仓/卖币均在客户端本地执行。有仓或已占用的机器不可选。</p>
|
|
||||||
|
|
||||||
<div class="panel">
|
<div class="cond-layout">
|
||||||
<h2 style="margin:0 0 10px;font-size:1rem">新建条件单</h2>
|
<section class="panel cond-card">
|
||||||
<div class="form-grid" id="cond-form">
|
<div class="cond-card-head">
|
||||||
<label>标的
|
<h2>新建条件单</h2>
|
||||||
<select id="cf-underlying">
|
<span class="cond-chip">左侧单</span>
|
||||||
<option value="ETH">ETH</option>
|
</div>
|
||||||
<option value="BTC">BTC</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>方向
|
|
||||||
<select id="cf-opt-type">
|
|
||||||
<option value="P">看跌 Put</option>
|
|
||||||
<option value="C">看涨 Call</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>触发
|
|
||||||
<select id="cf-trigger-side">
|
|
||||||
<option value="gte">指数 ≥ 触发价(上穿)</option>
|
|
||||||
<option value="lte">指数 ≤ 触发价(下穿)</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>触发价
|
|
||||||
<input id="cf-trigger-px" type="number" step="any" placeholder="如 3000" required>
|
|
||||||
</label>
|
|
||||||
<label>偏离 %
|
|
||||||
<input id="cf-deviation" type="number" step="0.1" value="1" placeholder="1" required>
|
|
||||||
</label>
|
|
||||||
<label>最短到期(小时)
|
|
||||||
<input id="cf-expiry-h" type="number" step="1" value="24" placeholder="24" required>
|
|
||||||
</label>
|
|
||||||
<label>出场目标指数(可选)
|
|
||||||
<input id="cf-target" type="number" step="any" placeholder="如 2800">
|
|
||||||
</label>
|
|
||||||
<label class="check"><input id="cf-pe" type="checkbox" checked> 翻倍出场</label>
|
|
||||||
<label>翻倍倍数
|
|
||||||
<input id="cf-pe-mult" type="number" step="0.1" value="1" placeholder="1">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div style="margin-top:12px">
|
|
||||||
<div class="muted" style="margin-bottom:6px">选择客户端</div>
|
|
||||||
<div id="cf-instances" class="inst-pick"></div>
|
|
||||||
</div>
|
|
||||||
<div style="margin-top:12px">
|
|
||||||
<button type="button" class="btn" id="btn-cond-create">下发条件单</button>
|
|
||||||
<span class="muted" id="cf-status"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel">
|
<div class="cond-section">
|
||||||
<h2 style="margin:0 0 10px;font-size:1rem">条件单列表</h2>
|
<div class="cond-section-title">入场</div>
|
||||||
<div id="cond-list" class="muted">加载中…</div>
|
<div class="cond-grid">
|
||||||
|
<label>标的
|
||||||
|
<select id="cf-underlying">
|
||||||
|
<option value="ETH">ETH</option>
|
||||||
|
<option value="BTC">BTC</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>方向
|
||||||
|
<select id="cf-opt-type">
|
||||||
|
<option value="P">看跌 Put</option>
|
||||||
|
<option value="C">看涨 Call</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>触发价
|
||||||
|
<input id="cf-trigger-px" type="number" step="any" placeholder="如 3000" required>
|
||||||
|
</label>
|
||||||
|
<label>偏离 %
|
||||||
|
<input id="cf-deviation" type="number" step="0.1" value="1" placeholder="1" required>
|
||||||
|
</label>
|
||||||
|
<label>最短到期(小时)
|
||||||
|
<input id="cf-expiry-h" type="number" step="1" value="24" placeholder="24" required>
|
||||||
|
</label>
|
||||||
|
<div class="cond-trigger-hint" id="cf-trigger-hint"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cond-section">
|
||||||
|
<div class="cond-section-title">出场</div>
|
||||||
|
<div class="cond-grid">
|
||||||
|
<label>目标指数(可选)
|
||||||
|
<input id="cf-target" type="number" step="any" placeholder="如 2800">
|
||||||
|
</label>
|
||||||
|
<label>翻倍倍数
|
||||||
|
<input id="cf-pe-mult" type="number" step="0.1" value="1" placeholder="1">
|
||||||
|
</label>
|
||||||
|
<label class="cond-switch">
|
||||||
|
<input id="cf-pe" type="checkbox" checked>
|
||||||
|
<span>翻倍出场</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cond-section">
|
||||||
|
<div class="cond-section-title">客户端</div>
|
||||||
|
<div id="cf-instances" class="cond-inst-grid"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cond-actions">
|
||||||
|
<button type="button" class="btn" id="btn-cond-create">下发条件单</button>
|
||||||
|
<span class="muted" id="cf-status"></span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="panel cond-card">
|
||||||
|
<div class="cond-card-head">
|
||||||
|
<h2>条件单列表</h2>
|
||||||
|
</div>
|
||||||
|
<div id="cond-list" class="muted">加载中…</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
|||||||
Reference in New Issue
Block a user