Show estimated risk, profit, and RR below manual order form.
Add a preview bar under the live order form with risk in red and profit in green; extend preview logic for all SL/TP modes across embed and standalone instances. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* 实盘下单:币种 + 止盈止损填完后拉 /api/order_defaults 快照,在开仓按钮前显示预估盈亏比。
|
||||
* 仅价格 / 百分比模式;固定盈亏比模式沿用「预估止盈」,不显示预估盈亏比。
|
||||
* 实盘下单:填完币种与止盈止损后,在表单下方显示预估风险 / 预估盈利 / 预估盈亏比。
|
||||
*/
|
||||
(function (global) {
|
||||
"use strict";
|
||||
@@ -27,6 +26,26 @@
|
||||
return body + ":1";
|
||||
}
|
||||
|
||||
function formatU(v) {
|
||||
if (v === null || typeof v === "undefined" || !Number.isFinite(Number(v))) return "—";
|
||||
return Number(v).toFixed(2) + "U";
|
||||
}
|
||||
|
||||
function setMetric(el, label, valueText) {
|
||||
if (!el) return;
|
||||
el.innerHTML = label + ":<strong>" + valueText + "</strong>";
|
||||
}
|
||||
|
||||
function riskPercent() {
|
||||
const form = $("add-order-form");
|
||||
const raw =
|
||||
(form && form.getAttribute("data-risk-percent")) ||
|
||||
(document.body && document.body.getAttribute("data-risk-percent")) ||
|
||||
"";
|
||||
const n = Number(raw);
|
||||
return Number.isFinite(n) && n > 0 ? n : 1;
|
||||
}
|
||||
|
||||
function calcRr(direction, entry, sl, tp) {
|
||||
const e = num(entry);
|
||||
const s = num(sl);
|
||||
@@ -47,6 +66,19 @@
|
||||
return tp / sl;
|
||||
}
|
||||
|
||||
function calcTpFromFixedRr(direction, entry, sl, rr) {
|
||||
const e = num(entry);
|
||||
const s = num(sl);
|
||||
const r = num(rr);
|
||||
if (e === null || s === null || r === null || r <= 0) return null;
|
||||
if (direction === "short") {
|
||||
if (s <= e) return null;
|
||||
return e - (s - e) * r;
|
||||
}
|
||||
if (s >= e) return null;
|
||||
return e + (e - s) * r;
|
||||
}
|
||||
|
||||
function currentMode() {
|
||||
return ($("sltp-mode") && $("sltp-mode").value) || "fixed_rr";
|
||||
}
|
||||
@@ -67,101 +99,128 @@
|
||||
const tp = num($("order-tp-pct") && $("order-tp-pct").value);
|
||||
return sl !== null && tp !== null && sl > 0 && tp > 0;
|
||||
}
|
||||
if (m === "fixed_rr") {
|
||||
const sl = num($("order-sl") && $("order-sl").value);
|
||||
const rr = num($("order-fixed-rr") && $("order-fixed-rr").value);
|
||||
return sl !== null && rr !== null && sl > 0 && rr > 0;
|
||||
}
|
||||
const sl = num($("order-sl") && $("order-sl").value);
|
||||
const tp = num($("order-tp") && $("order-tp").value);
|
||||
return sl !== null && tp !== null && sl > 0 && tp > 0;
|
||||
}
|
||||
|
||||
function hidePreview() {
|
||||
const el = $("order-rr-preview");
|
||||
if (el) el.style.display = "none";
|
||||
function paintEmpty() {
|
||||
setMetric($("order-risk-preview"), "预估风险", "—");
|
||||
setMetric($("order-profit-preview"), "预估盈利", "—");
|
||||
setMetric($("order-rr-preview"), "预估盈亏比", "—");
|
||||
}
|
||||
|
||||
function paint(rr, state) {
|
||||
const el = $("order-rr-preview");
|
||||
if (!el) return;
|
||||
const m = currentMode();
|
||||
if (m === "fixed_rr" || (m !== "price" && m !== "pct")) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
function paintLoading() {
|
||||
setMetric($("order-risk-preview"), "预估风险", "计算中…");
|
||||
setMetric($("order-profit-preview"), "预估盈利", "计算中…");
|
||||
setMetric($("order-rr-preview"), "预估盈亏比", "计算中…");
|
||||
}
|
||||
|
||||
function paintFail(kind) {
|
||||
const msg = kind === "fetch_fail" ? "取价失败" : "无效";
|
||||
setMetric($("order-risk-preview"), "预估风险", msg);
|
||||
setMetric($("order-profit-preview"), "预估盈利", msg);
|
||||
setMetric($("order-rr-preview"), "预估盈亏比", msg);
|
||||
}
|
||||
|
||||
function paintOk(riskU, profitU, rr) {
|
||||
setMetric($("order-risk-preview"), "预估风险", formatU(riskU));
|
||||
setMetric($("order-profit-preview"), "预估盈利", formatU(profitU));
|
||||
const rrEl = $("order-rr-preview");
|
||||
const rrText = formatRr(rr);
|
||||
setMetric(rrEl, "预估盈亏比", rrText);
|
||||
if (rrEl && rr !== null && Number.isFinite(Number(rr))) {
|
||||
rrEl.classList.toggle("order-preview-rr-low", Number(rr) < minRr);
|
||||
rrEl.classList.toggle("order-preview-rr-ok", Number(rr) >= minRr);
|
||||
}
|
||||
el.style.display = "";
|
||||
if (state === "empty") {
|
||||
el.textContent = "预估盈亏比:—";
|
||||
el.style.color = "#8fc8ff";
|
||||
return;
|
||||
}
|
||||
|
||||
function plannedRiskU(capital) {
|
||||
const cap = num(capital);
|
||||
if (cap === null || cap <= 0) return null;
|
||||
return Math.round((cap * riskPercent()) / 100 * 100) / 100;
|
||||
}
|
||||
|
||||
function resolvePreviewRr(m, dir, entry, data) {
|
||||
if (m === "pct") {
|
||||
return calcRrFromPct(
|
||||
$("order-sl-pct") && $("order-sl-pct").value,
|
||||
$("order-tp-pct") && $("order-tp-pct").value
|
||||
);
|
||||
}
|
||||
if (state === "loading") {
|
||||
el.textContent = "预估盈亏比:计算中…";
|
||||
el.style.color = "#8fc8ff";
|
||||
return;
|
||||
const sl = num($("order-sl") && $("order-sl").value);
|
||||
if (m === "fixed_rr") {
|
||||
const fixed = num($("order-fixed-rr") && $("order-fixed-rr").value);
|
||||
if (fixed !== null && fixed > 0) return fixed;
|
||||
const tp = calcTpFromFixedRr(dir, entry, sl, fixed);
|
||||
return calcRr(dir, entry, sl, tp);
|
||||
}
|
||||
if (state === "fetch_fail") {
|
||||
el.textContent = "预估盈亏比:取价失败";
|
||||
el.style.color = "#ff8f8f";
|
||||
return;
|
||||
}
|
||||
if (rr === null) {
|
||||
el.textContent = "预估盈亏比:无效";
|
||||
el.style.color = "#ff8f8f";
|
||||
return;
|
||||
}
|
||||
el.textContent = "预估盈亏比:" + formatRr(rr);
|
||||
el.style.color = rr >= minRr ? "#4cd97f" : "#ff8f8f";
|
||||
const tp = num($("order-tp") && $("order-tp").value);
|
||||
return calcRr(dir, entry, sl, tp);
|
||||
}
|
||||
|
||||
function refreshNow() {
|
||||
if (!$("order-plan-preview")) return;
|
||||
const m = currentMode();
|
||||
if (m === "fixed_rr") {
|
||||
hidePreview();
|
||||
return;
|
||||
}
|
||||
if (!inputsComplete(m)) {
|
||||
paint(null, "empty");
|
||||
paintEmpty();
|
||||
return;
|
||||
}
|
||||
|
||||
const sym = currentSymbol();
|
||||
const dir = currentDirection();
|
||||
const seq = ++fetchSeq;
|
||||
paint(null, "loading");
|
||||
paintLoading();
|
||||
|
||||
fetch(
|
||||
const defaultsP = fetch(
|
||||
"/api/order_defaults?symbol=" +
|
||||
encodeURIComponent(sym) +
|
||||
"&direction=" +
|
||||
encodeURIComponent(dir)
|
||||
)
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
).then(function (r) {
|
||||
return r.json();
|
||||
});
|
||||
|
||||
const capitalP = fetch("/api/account_snapshot").then(function (r) {
|
||||
return r.json();
|
||||
});
|
||||
|
||||
Promise.all([defaultsP, capitalP])
|
||||
.then(function (results) {
|
||||
if (seq !== fetchSeq) return;
|
||||
const data = results[0];
|
||||
const account = results[1] || {};
|
||||
if (!data.ok) {
|
||||
paint(null, "fetch_fail");
|
||||
return;
|
||||
}
|
||||
if (m === "pct") {
|
||||
const rr = calcRrFromPct(
|
||||
$("order-sl-pct") && $("order-sl-pct").value,
|
||||
$("order-tp-pct") && $("order-tp-pct").value
|
||||
);
|
||||
paint(rr, rr === null ? "invalid" : "ok");
|
||||
paintFail("fetch_fail");
|
||||
return;
|
||||
}
|
||||
const entry = num(data.last_price != null ? data.last_price : data.price);
|
||||
if (entry === null) {
|
||||
paint(null, "fetch_fail");
|
||||
paintFail("fetch_fail");
|
||||
return;
|
||||
}
|
||||
const sl = num($("order-sl") && $("order-sl").value);
|
||||
const tp = num($("order-tp") && $("order-tp").value);
|
||||
const rr = calcRr(dir, entry, sl, tp);
|
||||
paint(rr, rr === null ? "invalid" : "ok");
|
||||
const rr = resolvePreviewRr(m, dir, entry, data);
|
||||
if (rr === null) {
|
||||
paintFail("invalid");
|
||||
return;
|
||||
}
|
||||
const capital = num(account.current_capital);
|
||||
const riskU = plannedRiskU(capital);
|
||||
if (riskU === null) {
|
||||
paintFail("fetch_fail");
|
||||
return;
|
||||
}
|
||||
const profitU = Math.round(riskU * rr * 100) / 100;
|
||||
paintOk(riskU, profitU, rr);
|
||||
})
|
||||
.catch(function () {
|
||||
if (seq !== fetchSeq) return;
|
||||
paint(null, "fetch_fail");
|
||||
paintFail("fetch_fail");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -187,6 +246,7 @@
|
||||
"order-sl-pct",
|
||||
"order-tp-pct",
|
||||
"order-fixed-rr",
|
||||
"order-leverage",
|
||||
].forEach(function (id) {
|
||||
const el = $(id);
|
||||
if (!el || el._rrPreviewBound) return;
|
||||
|
||||
Reference in New Issue
Block a user