/** * 中控策略计算器:趋势回调 / 滚仓历史测算 */ (function () { const page = document.getElementById("page-calculator"); if (!page) return; let inited = false; function $(id) { return document.getElementById(id); } function esc(s) { return String(s == null ? "" : s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function num(id) { const el = $(id); if (!el) return null; const n = Number(el.value); return Number.isFinite(n) ? n : null; } function fmt(v, digits) { if (v == null || v === "") return "—"; const n = Number(v); if (!Number.isFinite(n)) return esc(v); if (digits != null) return n.toFixed(digits); return String(n); } function fmtU(v) { if (v == null || v === "") return "—"; const n = Number(v); if (!Number.isFinite(n)) return "—"; return (n >= 0 ? "+" : "") + n.toFixed(2) + "U"; } function pnlClass(v) { const n = Number(v); if (!Number.isFinite(n) || n === 0) return ""; return n > 0 ? "calc-pnl-profit" : "calc-pnl-loss"; } function syncTrendAddLabel() { const dir = ($("calc-trend-direction") && $("calc-trend-direction").value) || "long"; const lab = $("calc-trend-add-label"); if (lab) lab.textContent = dir === "short" ? "补仓下沿价" : "补仓上沿价"; } function renderTrendTable(rows) { if (!rows || !rows.length) { return '

无档位数据

'; } let html = '
' + "" + ""; rows.forEach(function (r) { html += "" + "" + "" + "" + "" + '" + "" + "" + ""; }); html += "
档位触发价张数加仓后均价止盈盈利止损金额盈亏比
" + esc(r.label) + "" + fmt(r.price, 4) + "" + fmt(r.contracts, 4) + "" + fmt(r.avg_entry, 4) + "' + fmtU(r.profit_u) + "" + fmtU(r.risk_u) + "" + (r.rr != null ? fmt(r.rr, 2) + ":1" : "—") + "
"; return html; } function renderTrendResult(data) { const box = $("calc-trend-result"); if (!box) return; box.classList.remove("hidden"); box.innerHTML = '
' + "
计划保证金" + fmt(data.plan_margin_u, 2) + "U
" + "
止损预算" + fmt(data.risk_budget_u, 2) + "U
" + "
总张数" + fmt(data.target_contracts, 4) + "
" + "
首仓张数" + fmt(data.first_contracts, 4) + "
" + '
首仓止盈盈利' + fmtU(data.first_profit_u) + "
" + "
首仓盈亏比" + (data.first_rr != null ? fmt(data.first_rr, 2) + ":1" : "—") + "
" + "
" + renderTrendTable(data.rows); } function renderRollResult(data) { const box = $("calc-roll-result"); if (!box) return; box.classList.remove("hidden"); box.innerHTML = '
' + "
风险预算" + fmt(data.risk_budget_u, 2) + "U
" + "
本次加仓张数" + fmt(data.add_contracts, 4) + "
" + "
合并后张数" + fmt(data.qty_after, 4) + "
" + "
合并后均价" + fmt(data.avg_entry_after, 4) + "
" + "
打到新止损亏损" + fmtU(-Math.abs(Number(data.loss_at_sl_u) || 0)) + "
" + '
到达首仓止盈盈利' + fmtU(data.profit_at_tp_u) + "
" + "
金额盈亏比" + (data.rr != null ? fmt(data.rr, 2) + ":1" : "—") + "
" + "
下一滚仓序号第 " + esc(data.leg_index_next) + " 次
" + "
"; } function showErr(boxId, msg) { const box = $(boxId); if (!box) return; box.classList.remove("hidden"); box.innerHTML = '

' + esc(msg || "计算失败") + "

"; } async function submitTrend(e) { e.preventDefault(); const body = { direction: ($("calc-trend-direction") && $("calc-trend-direction").value) || "long", capital_usdt: num("calc-trend-capital"), risk_percent: num("calc-trend-risk"), leverage: num("calc-trend-leverage"), entry_price: num("calc-trend-entry"), stop_loss: num("calc-trend-sl"), add_upper: num("calc-trend-add-upper"), take_profit: num("calc-trend-tp"), dca_legs: num("calc-trend-dca-legs") || 5, contract_size: num("calc-trend-contract-size") || 1, }; try { const r = await fetch("/api/calculator/trend", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const j = await r.json(); if (!j.ok) { showErr("calc-trend-result", j.msg || "计算失败"); return; } renderTrendResult(j.data); } catch (err) { showErr("calc-trend-result", String(err)); } } async function submitRoll(e) { e.preventDefault(); const body = { direction: ($("calc-roll-direction") && $("calc-roll-direction").value) || "long", capital_usdt: num("calc-roll-capital"), risk_percent: num("calc-roll-risk"), qty_existing: num("calc-roll-qty"), entry_existing: num("calc-roll-entry"), take_profit: num("calc-roll-tp"), add_price: num("calc-roll-add-price"), new_stop_loss: num("calc-roll-sl"), legs_done: num("calc-roll-legs-done") || 0, }; try { const r = await fetch("/api/calculator/roll", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const j = await r.json(); if (!j.ok) { showErr("calc-roll-result", j.msg || "计算失败"); return; } renderRollResult(j.data); } catch (err) { showErr("calc-roll-result", String(err)); } } function bindOnce() { if (inited) return; inited = true; const trendForm = $("calc-trend-form"); const rollForm = $("calc-roll-form"); const dirSel = $("calc-trend-direction"); if (trendForm) trendForm.addEventListener("submit", submitTrend); if (rollForm) rollForm.addEventListener("submit", submitRoll); if (dirSel) { dirSel.addEventListener("change", syncTrendAddLabel); syncTrendAddLabel(); } } window.hubCalculatorPage = { init: bindOnce, destroy: function () {}, }; })();