Files
crypto_monitor/static/manual_order_rr_preview.js
T
dekun 58e940629a fix(order): hide estimated RR preview in fixed RR SLTP mode
Only price and percentage modes show estimated profit-loss ratio before open; fixed RR mode keeps the existing estimated take-profit display.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:26:18 +08:00

209 lines
5.7 KiB
JavaScript

/**
* 实盘下单:币种 + 止盈止损填完后拉 /api/order_defaults 快照,在开仓按钮前显示预估盈亏比。
* 仅价格 / 百分比模式;固定盈亏比模式沿用「预估止盈」,不显示预估盈亏比。
*/
(function (global) {
"use strict";
let debounceMs = 400;
let minRr = 1.5;
let debounceTimer = null;
let fetchSeq = 0;
function $(id) {
return document.getElementById(id);
}
function num(v) {
const n = Number(v);
return Number.isFinite(n) ? n : null;
}
function formatRr(rr) {
if (rr === null || typeof rr === "undefined") return "—";
const n = Number(rr);
if (!Number.isFinite(n)) return "—";
const body = Number.isInteger(n) ? String(n) : String(parseFloat(n.toFixed(2)));
return body + ":1";
}
function calcRr(direction, entry, sl, tp) {
const e = num(entry);
const s = num(sl);
const t = num(tp);
if (e === null || s === null || t === null) return null;
if (direction === "short") {
if (s <= e || t >= e) return null;
return (e - t) / (s - e);
}
if (s >= e || t <= e) return null;
return (t - e) / (e - s);
}
function calcRrFromPct(slPct, tpPct) {
const sl = num(slPct);
const tp = num(tpPct);
if (sl === null || tp === null || sl <= 0 || tp <= 0) return null;
return tp / sl;
}
function currentMode() {
return ($("sltp-mode") && $("sltp-mode").value) || "fixed_rr";
}
function currentDirection() {
return ($("order-direction") && $("order-direction").value) || "long";
}
function currentSymbol() {
return (($("order-symbol") && $("order-symbol").value) || "").trim();
}
function inputsComplete(m) {
const dir = currentDirection();
if (!currentSymbol() || !dir) return false;
if (m === "pct") {
const sl = num($("order-sl-pct") && $("order-sl-pct").value);
const tp = num($("order-tp-pct") && $("order-tp-pct").value);
return sl !== null && tp !== null && sl > 0 && tp > 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 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;
}
el.style.display = "";
if (state === "empty") {
el.textContent = "预估盈亏比:—";
el.style.color = "#8fc8ff";
return;
}
if (state === "loading") {
el.textContent = "预估盈亏比:计算中…";
el.style.color = "#8fc8ff";
return;
}
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";
}
function refreshNow() {
const m = currentMode();
if (m === "fixed_rr") {
hidePreview();
return;
}
if (!inputsComplete(m)) {
paint(null, "empty");
return;
}
const sym = currentSymbol();
const dir = currentDirection();
const seq = ++fetchSeq;
paint(null, "loading");
fetch(
"/api/order_defaults?symbol=" +
encodeURIComponent(sym) +
"&direction=" +
encodeURIComponent(dir)
)
.then(function (r) {
return r.json();
})
.then(function (data) {
if (seq !== fetchSeq) return;
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");
return;
}
const entry = num(data.last_price != null ? data.last_price : data.price);
if (entry === null) {
paint(null, "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");
})
.catch(function () {
if (seq !== fetchSeq) return;
paint(null, "fetch_fail");
});
}
function schedule() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(refreshNow, debounceMs);
}
function wire(opts) {
opts = opts || {};
if (opts.minRr != null && Number.isFinite(Number(opts.minRr))) {
minRr = Number(opts.minRr);
}
if (opts.debounceMs != null && Number.isFinite(Number(opts.debounceMs))) {
debounceMs = Number(opts.debounceMs);
}
[
"order-symbol",
"order-direction",
"sltp-mode",
"order-sl",
"order-tp",
"order-sl-pct",
"order-tp-pct",
"order-fixed-rr",
].forEach(function (id) {
const el = $(id);
if (!el || el._rrPreviewBound) return;
el._rrPreviewBound = true;
el.addEventListener("input", schedule);
el.addEventListener("change", schedule);
});
schedule();
}
global.ManualOrderRrPreview = {
wire: wire,
schedule: schedule,
refresh: refreshNow,
calcRr: calcRr,
calcRrFromPct: calcRrFromPct,
formatRr: formatRr,
};
})(typeof window !== "undefined" ? window : globalThis);