diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html
index 68bb524..16a5381 100644
--- a/crypto_monitor_binance/templates/index.html
+++ b/crypto_monitor_binance/templates/index.html
@@ -37,6 +37,12 @@
.form-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;align-items:center}
.form-row > input:not([type=checkbox]):not([type=radio]),.form-row > select{flex:0 1 auto;width:10rem;max-width:200px;min-width:7rem}
#add-order-form #sltp-mode{min-width:12.5rem;max-width:16rem;width:auto}
+ .order-plan-preview{display:flex;gap:18px;flex-wrap:wrap;align-items:center;margin:4px 0 10px;padding:10px 12px;background:#151a28;border:1px solid #2a3150;border-radius:8px;font-size:.85rem}
+ .order-preview-risk{color:#ff6b6b}.order-preview-risk strong{color:#ff8f8f;font-weight:600}
+ .order-preview-profit{color:#4cd97f}.order-preview-profit strong{color:#6ee7a0;font-weight:600}
+ .order-preview-rr{color:#cfd3ef}.order-preview-rr strong{font-weight:600;color:#dbe4ff}
+ .order-preview-rr.order-preview-rr-low strong{color:#ff8f8f}
+ .order-preview-rr.order-preview-rr-ok strong{color:#8fc8ff}
.form-row > button,.form-row > label{flex:0 0 auto}
.form-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
/* 复盘表单:长下拉文案需可收缩,否则会撑破四列网格 */
@@ -240,7 +246,7 @@
-
+
{% macro period_stats(title, s) %}
实时持仓
@@ -827,7 +833,7 @@
-
+
-
+
-
+
-
+
-
+
{% include 'embed_boot_scripts.html' %}
diff --git a/static/instance_embed.js b/static/instance_embed.js
index 623ed9a..2fb63f0 100644
--- a/static/instance_embed.js
+++ b/static/instance_embed.js
@@ -64,6 +64,9 @@
}
if (tab === "trade") {
if (typeof global.refreshOrderDefaults === "function") global.refreshOrderDefaults();
+ if (global.ManualOrderRrPreview && typeof global.ManualOrderRrPreview.wire === "function") {
+ global.ManualOrderRrPreview.wire();
+ }
}
if (tab === "key_monitor" && global.KeyMonitorForm && typeof global.KeyMonitorForm.init === "function") {
global.KeyMonitorForm.init();
diff --git a/static/instance_page.css b/static/instance_page.css
index 211f0a1..9b07ed7 100644
--- a/static/instance_page.css
+++ b/static/instance_page.css
@@ -19,6 +19,15 @@
.form-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;align-items:center}
.form-row > input:not([type=checkbox]):not([type=radio]),.form-row > select{flex:0 1 auto;width:10rem;max-width:200px;min-width:7rem}
#add-order-form #sltp-mode{min-width:12.5rem;max-width:16rem;width:auto}
+ .order-plan-preview{display:flex;gap:18px;flex-wrap:wrap;align-items:center;margin:4px 0 10px;padding:10px 12px;background:#151a28;border:1px solid #2a3150;border-radius:8px;font-size:.85rem}
+ .order-preview-risk{color:#ff6b6b}
+ .order-preview-risk strong{color:#ff8f8f;font-weight:600}
+ .order-preview-profit{color:#4cd97f}
+ .order-preview-profit strong{color:#6ee7a0;font-weight:600}
+ .order-preview-rr{color:#cfd3ef}
+ .order-preview-rr strong{font-weight:600;color:#dbe4ff}
+ .order-preview-rr.order-preview-rr-low strong{color:#ff8f8f}
+ .order-preview-rr.order-preview-rr-ok strong{color:#8fc8ff}
.form-row > button,.form-row > label{flex:0 0 auto}
.form-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
/* 复盘表单:长下拉文案需可收缩,否则会撑破四列网格 */
diff --git a/static/manual_order_rr_preview.js b/static/manual_order_rr_preview.js
index 85dc781..4469524 100644
--- a/static/manual_order_rr_preview.js
+++ b/static/manual_order_rr_preview.js
@@ -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 + ":
" + valueText + "";
+ }
+
+ 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;
diff --git a/strategy_templates/order_plan_preview_bar.html b/strategy_templates/order_plan_preview_bar.html
new file mode 100644
index 0000000..7e59ba7
--- /dev/null
+++ b/strategy_templates/order_plan_preview_bar.html
@@ -0,0 +1,5 @@
+
+ 预估风险:—
+ 预估盈利:—
+ 预估盈亏比:—
+