(function (global) { function escapeHtml(s) { return String(s) .replace(/&/g, "&") .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) { const st = o.status || ""; const reason = o.end_reason || ""; if (st === "done") { const map = { leave_up: "结束·上破", leave_down: "结束·下破", cancel: "已取消", closed: "已完成", }; return map[reason] || "已结束"; } const map2 = { armed: "待命", triggering: "触发中", open: "持仓中" }; 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; const r = await fetch("/api/cond_orders/candidates"); const d = await r.json(); const list = d.instances || []; if (!list.length) { box.innerHTML = '暂无启用实例'; return; } box.innerHTML = list .map(function (it) { const disabled = !it.selectable; const tip = disabled ? it.has_position ? "已有持仓" : it.cond_order_locked ? "条件单占用" : it.online ? "不可选" : "离线" : "可选"; return ( '" ); }) .join(""); } async function loadOrders() { const box = document.getElementById("cond-list"); if (!box) return; const r = await fetch("/api/cond_orders"); const d = await r.json(); const list = d.orders || []; if (!list.length) { box.innerHTML = '
暂无条件单
'; return; } box.innerHTML = '
' + "" + "" + list .map(function (o) { const side = o.trigger_side === "lte" ? "下穿 ≤" : "上穿 ≥"; const exit = (o.profit_exit_enabled ? "翻倍×" + (o.profit_exit_mult || "") : "") + (o.target_index != null ? " 目标" + o.target_index : ""); const canCancel = o.status === "armed" || o.status === "triggering"; const canExit = o.status === "open"; let actions = ""; if (canCancel) { actions += ''; } if (canExit) { actions += ''; } return ( "" + "" + '" + "" + "" + "" + "" + "" + '" + '" + "" ); }) .join("") + "
客户端状态标的触发偏离到期h出场说明操作
" + escapeHtml(o.instance_name || "") + "' + escapeHtml(statusLabel(o)) + "" + escapeHtml((o.underlying || "") + " " + (o.opt_type || "")) + "" + escapeHtml(side + " " + (o.trigger_px != null ? o.trigger_px : "")) + "" + escapeHtml(o.deviation_pct != null ? o.deviation_pct + "%" : "—") + "" + escapeHtml(o.min_expiry_hours != null ? String(o.min_expiry_hours) : "—") + "" + escapeHtml(exit || "—") + "' + escapeHtml(o.message || "") + "
' + actions + "
"; box.querySelectorAll("[data-cancel]").forEach(function (btn) { btn.addEventListener("click", async function () { if (!confirm("确认取消该条件单?")) return; const oid = decodeURIComponent(btn.getAttribute("data-cancel") || ""); const iid = decodeURIComponent(btn.getAttribute("data-iid") || ""); const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/cancel", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ instance_id: Number(iid) }), }); const dd = await rr.json(); alert(dd.msg || (dd.ok ? "已取消" : "失败")); refresh(); }); }); box.querySelectorAll("[data-exit]").forEach(function (btn) { btn.addEventListener("click", async function () { const oid = decodeURIComponent(btn.getAttribute("data-exit") || ""); const iid = decodeURIComponent(btn.getAttribute("data-iid") || ""); const curT = decodeURIComponent(btn.getAttribute("data-target") || ""); const curM = decodeURIComponent(btn.getAttribute("data-mult") || "1"); const pe = btn.getAttribute("data-pe") === "1"; const t = prompt("目标指数(留空清除)", curT); if (t === null) return; const peOn = confirm("是否启用翻倍出场?\n当前:" + (pe ? "开" : "关")); let mult = curM; if (peOn) { const m = prompt("翻倍倍数", curM); if (m === null) return; mult = m; } const rr = await fetch("/api/cond_orders/" + encodeURIComponent(oid) + "/exit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ instance_id: Number(iid), target_index: t === "" ? "" : t, profit_exit_enabled: peOn, profit_exit_mult: mult, }), }); const dd = await rr.json(); alert(dd.msg || (dd.ok ? "已更新" : "失败")); refresh(); }); }); } async function createOrder() { const status = document.getElementById("cf-status"); const ids = Array.prototype.slice .call(document.querySelectorAll('input[name="cf-inst"]:checked')) .map(function (el) { return Number(decodeURIComponent(el.value)); }); if (!ids.length) { alert("请选择至少一台可选客户端"); return; } const optType = document.getElementById("cf-opt-type").value; const body = { instance_ids: ids, underlying: document.getElementById("cf-underlying").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, target_index: document.getElementById("cf-target").value, profit_exit_enabled: document.getElementById("cf-pe").checked, profit_exit_mult: document.getElementById("cf-pe-mult").value, }; if (status) status.textContent = "下发中…"; const r = await fetch("/api/cond_orders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const d = await r.json(); if (status) status.textContent = d.msg || ""; alert(d.msg || (d.ok ? "已下发" : "失败")); refresh(); } async function refresh() { await Promise.all([loadCandidates(), loadOrders()]); } function boot() { const btn = document.getElementById("btn-cond-refresh"); 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); } global.HubCondOrders = { boot: boot, refresh: refresh }; })(window);