feat(hub): align trend pullback display with instance in fullscreen
Position cards show trend plan source, risk%, program TP price and RR; trend section uses plan grid; hub API enriches floating PnL and planned_rr. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1320,6 +1320,72 @@ body.market-chart-fs-open {
|
|||||||
border-color: rgba(0, 255, 157, 0.38);
|
border-color: rgba(0, 255, 157, 0.38);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card {
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-status {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-meta .pos-meta-accent,
|
||||||
|
.hub-trend-plan-meta strong {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-foot {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pos-value.pos-tp-program {
|
||||||
|
color: #8fc8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exchange-fullscreen .hub-trend-plan-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exchange-fullscreen .hub-trend-plan-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* 顺势加仓 */
|
/* 顺势加仓 */
|
||||||
.card-stat-chip.card-stat-roll {
|
.card-stat-chip.card-stat-roll {
|
||||||
color: #ffb020;
|
color: #ffb020;
|
||||||
|
|||||||
@@ -303,19 +303,34 @@
|
|||||||
return side || "—";
|
return side || "—";
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitorOrderSourceLabel(mo) {
|
function isTrendContext(monitorOrder, trendPlan) {
|
||||||
|
const mo = monitorOrder || {};
|
||||||
|
const tp = trendPlan || {};
|
||||||
|
if (tp.id != null && Number(tp.id) > 0) return true;
|
||||||
|
const tid = Number(mo.trend_plan_id);
|
||||||
|
if (Number.isFinite(tid) && tid > 0) return true;
|
||||||
|
const mt = String(mo.monitor_type || "").trim();
|
||||||
|
if (mt === "趋势回调") return true;
|
||||||
|
const kst = String(mo.key_signal_type || "").trim();
|
||||||
|
return kst === "趋势回调" || kst === "趋势回调计划";
|
||||||
|
}
|
||||||
|
|
||||||
|
function trendAddZoneLabel(direction) {
|
||||||
|
return (direction || "long").toLowerCase() === "short" ? "补仓下沿" : "补仓上沿";
|
||||||
|
}
|
||||||
|
|
||||||
|
function monitorOrderSourceLabel(mo, trendPlan) {
|
||||||
|
if (isTrendContext(mo, trendPlan)) return "趋势回调计划";
|
||||||
const o = mo || {};
|
const o = mo || {};
|
||||||
const tid = Number(o.trend_plan_id);
|
|
||||||
if (Number.isFinite(tid) && tid > 0) return "趋势回调";
|
|
||||||
const mt = String(o.monitor_type || "").trim();
|
const mt = String(o.monitor_type || "").trim();
|
||||||
if (mt === "趋势回调") return "趋势回调";
|
|
||||||
const kst = String(o.key_signal_type || "").trim();
|
|
||||||
if (kst === "趋势回调" || kst === "趋势回调计划") return "趋势回调";
|
|
||||||
return mt || "下单监控";
|
return mt || "下单监控";
|
||||||
}
|
}
|
||||||
|
|
||||||
function monitorOrderSourceHtml(mo) {
|
function monitorOrderSourceHtml(mo, trendPlan) {
|
||||||
const src = monitorOrderSourceLabel(mo);
|
if (isTrendContext(mo, trendPlan)) {
|
||||||
|
return `来源: ${esc(monitorOrderSourceLabel(mo, trendPlan))}`;
|
||||||
|
}
|
||||||
|
const src = monitorOrderSourceLabel(mo, trendPlan);
|
||||||
const kst = String((mo && mo.key_signal_type) || "").trim();
|
const kst = String((mo && mo.key_signal_type) || "").trim();
|
||||||
let text = src;
|
let text = src;
|
||||||
if (kst && kst !== src && !text.includes(kst)) {
|
if (kst && kst !== src && !text.includes(kst)) {
|
||||||
@@ -776,7 +791,23 @@
|
|||||||
return reward / risk;
|
return reward / risk;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSnapshotRr(mo, side, entry, sl, tp, tpMonitored) {
|
function resolveTrendPlanRr(trendPlan, side, entry, sl, tp) {
|
||||||
|
const t = trendPlan || {};
|
||||||
|
if (t.planned_rr != null && t.planned_rr !== "") {
|
||||||
|
const n = Number(t.planned_rr);
|
||||||
|
if (Number.isFinite(n) && n > 0) return n;
|
||||||
|
}
|
||||||
|
const e = t.avg_entry_price != null && t.avg_entry_price !== "" ? t.avg_entry_price : entry;
|
||||||
|
const s = t.stop_loss != null && t.stop_loss !== "" ? t.stop_loss : sl;
|
||||||
|
const p = t.take_profit != null && t.take_profit !== "" ? t.take_profit : tp;
|
||||||
|
return calcRrRatio(side, e, s, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSnapshotRr(mo, side, entry, sl, tp, tpMonitored, trendPlan) {
|
||||||
|
if (tpMonitored && isTrendContext(mo, trendPlan)) {
|
||||||
|
const rr = resolveTrendPlanRr(trendPlan, side, entry, sl, tp);
|
||||||
|
if (rr != null) return rr;
|
||||||
|
}
|
||||||
if (tpMonitored) return null;
|
if (tpMonitored) return null;
|
||||||
const snap = mo && mo.rr_ratio;
|
const snap = mo && mo.rr_ratio;
|
||||||
if (snap != null && snap !== "") {
|
if (snap != null && snap !== "") {
|
||||||
@@ -787,6 +818,17 @@
|
|||||||
return calcRrRatio(side, entry, initSl || sl, tp);
|
return calcRrRatio(side, entry, initSl || sl, tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTpCellValue(tp, tpMonitored, symbol, tickMap) {
|
||||||
|
if (tpMonitored) {
|
||||||
|
if (tp != null && tp !== "") {
|
||||||
|
return `程序监控 · ${fmtSymbolPrice(tp, symbol, tickMap)}`;
|
||||||
|
}
|
||||||
|
return "程序监控";
|
||||||
|
}
|
||||||
|
if (tp != null && tp !== "") return fmtSymbolPrice(tp, symbol, tickMap);
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
function isBreakevenSecured(side, entry, monitorOrder, cond, pos) {
|
function isBreakevenSecured(side, entry, monitorOrder, cond, pos) {
|
||||||
const mo = monitorOrder || {};
|
const mo = monitorOrder || {};
|
||||||
const p = pos || {};
|
const p = pos || {};
|
||||||
@@ -1072,10 +1114,7 @@
|
|||||||
? mo.trigger_price
|
? mo.trigger_price
|
||||||
: tp.avg_entry_price;
|
: tp.avg_entry_price;
|
||||||
const entryN = entryRaw != null && entryRaw !== "" ? Number(entryRaw) : null;
|
const entryN = entryRaw != null && entryRaw !== "" ? Number(entryRaw) : null;
|
||||||
const isTrend =
|
const isTrend = isTrendContext(mo, trendPlan);
|
||||||
!!(trendPlan && trendPlan.id) ||
|
|
||||||
String(mo.monitor_type || "").trim() === "趋势回调" ||
|
|
||||||
(mo.trend_plan_id != null && Number(mo.trend_plan_id) > 0);
|
|
||||||
|
|
||||||
let sl = mo.stop_loss != null && mo.stop_loss !== "" ? mo.stop_loss : "";
|
let sl = mo.stop_loss != null && mo.stop_loss !== "" ? mo.stop_loss : "";
|
||||||
let takeProfit = mo.take_profit != null && mo.take_profit !== "" ? mo.take_profit : "";
|
let takeProfit = mo.take_profit != null && mo.take_profit !== "" ? mo.take_profit : "";
|
||||||
@@ -1083,10 +1122,14 @@
|
|||||||
|
|
||||||
if (isTrend) {
|
if (isTrend) {
|
||||||
tpMonitored = true;
|
tpMonitored = true;
|
||||||
takeProfit = "";
|
|
||||||
if (trendPlan && trendPlan.stop_loss != null && trendPlan.stop_loss !== "") {
|
if (trendPlan && trendPlan.stop_loss != null && trendPlan.stop_loss !== "") {
|
||||||
sl = trendPlan.stop_loss;
|
sl = trendPlan.stop_loss;
|
||||||
}
|
}
|
||||||
|
if (trendPlan && trendPlan.take_profit != null && trendPlan.take_profit !== "") {
|
||||||
|
takeProfit = trendPlan.take_profit;
|
||||||
|
} else {
|
||||||
|
takeProfit = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const inferred = inferTpslFromCondOrders(pos.side, cond, entryN);
|
const inferred = inferTpslFromCondOrders(pos.side, cond, entryN);
|
||||||
@@ -1401,20 +1444,79 @@
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderTrendPlanCard(t, tickMap) {
|
||||||
|
const sym = t.exchange_symbol || t.symbol || "";
|
||||||
|
const side = (t.direction || "long").toLowerCase();
|
||||||
|
const sl = t.stop_loss_display || fmtSymbolPrice(t.stop_loss, sym, tickMap);
|
||||||
|
const tp = t.take_profit_display || fmtSymbolPrice(t.take_profit, sym, tickMap);
|
||||||
|
const avg = t.avg_entry_price_display || fmtSymbolPrice(t.avg_entry_price, sym, tickMap);
|
||||||
|
const addZone =
|
||||||
|
t.add_upper_display || fmtSymbolPrice(t.add_upper, sym, tickMap) || "—";
|
||||||
|
const rr = resolveTrendPlanRr(t, side, t.avg_entry_price, t.stop_loss, t.take_profit);
|
||||||
|
const rrTxt = rr != null ? `${fmt(rr, 2)}:1` : "—";
|
||||||
|
const mark =
|
||||||
|
t.floating_mark != null && t.floating_mark !== ""
|
||||||
|
? fmtSymbolPrice(t.floating_mark, sym, tickMap)
|
||||||
|
: "—";
|
||||||
|
const legsDone = t.add_count != null ? t.add_count : t.legs_done;
|
||||||
|
const legsTotal = t.add_count_total != null ? t.add_count_total : t.dca_legs;
|
||||||
|
const legsTxt =
|
||||||
|
legsDone != null && legsTotal != null
|
||||||
|
? `${esc(legsDone)}/${esc(legsTotal)}`
|
||||||
|
: legsDone != null
|
||||||
|
? esc(legsDone)
|
||||||
|
: "—";
|
||||||
|
let pnlInner = "—";
|
||||||
|
if (t.floating_pnl != null && t.floating_pnl !== "") {
|
||||||
|
let pnlText = `${fmt(t.floating_pnl, 2)}U`;
|
||||||
|
const margin = Number(t.plan_margin_capital);
|
||||||
|
if (Number.isFinite(margin) && margin > 0) {
|
||||||
|
const pct = (Number(t.floating_pnl) / margin) * 100;
|
||||||
|
pnlText += ` (${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%)`;
|
||||||
|
}
|
||||||
|
pnlInner = `<span class="pos-value ${pnlCls(t.floating_pnl)}">${esc(pnlText)}</span>`;
|
||||||
|
}
|
||||||
|
const riskTxt =
|
||||||
|
t.risk_percent != null && t.risk_percent !== "" ? `${esc(t.risk_percent)}%` : "—";
|
||||||
|
const foot = [];
|
||||||
|
if (t.snapshot_available_usdt != null) {
|
||||||
|
foot.push(`快照可用 ${fmt(t.snapshot_available_usdt, 2)}U`);
|
||||||
|
}
|
||||||
|
if (t.plan_margin_capital != null) {
|
||||||
|
foot.push(`计划保证金≈${fmt(t.plan_margin_capital, 2)}U`);
|
||||||
|
}
|
||||||
|
if (t.leverage != null) foot.push(`杠杆 ${esc(t.leverage)}x`);
|
||||||
|
const footHtml = foot.length
|
||||||
|
? `<div class="hub-trend-plan-foot">${foot.map((x) => esc(x)).join(" | ")}</div>`
|
||||||
|
: "";
|
||||||
|
return `<div class="hub-trend-plan-card hub-pos-card">
|
||||||
|
<div class="hub-trend-plan-head">
|
||||||
|
<span class="hub-trend-plan-title">#${esc(t.id)} ${esc(sym)} ${renderDirectionHtml(t.direction)}</span>
|
||||||
|
<span class="hub-trend-plan-status">${esc(t.status || "active")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="hub-trend-plan-meta">
|
||||||
|
<span>来源: 趋势回调计划</span>
|
||||||
|
<span>风险: ${riskTxt}</span>
|
||||||
|
<span>${esc(trendAddZoneLabel(t.direction))} ${esc(addZone)}</span>
|
||||||
|
<span>已补仓 <strong>${legsTxt}</strong></span>
|
||||||
|
</div>
|
||||||
|
<div class="pos-grid hub-trend-plan-grid">
|
||||||
|
<div class="pos-cell"><span class="pos-label">均价</span><span class="pos-value">${esc(avg)}</span></div>
|
||||||
|
<div class="pos-cell"><span class="pos-label">止损</span><span class="pos-value">${esc(sl)}</span></div>
|
||||||
|
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value pos-tp-program">程序监控 · ${esc(tp)}</span></div>
|
||||||
|
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${esc(rrTxt)}</span></div>
|
||||||
|
<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">${esc(mark)}</span></div>
|
||||||
|
<div class="pos-cell"><span class="pos-label">浮盈亏</span>${pnlInner}</div>
|
||||||
|
</div>
|
||||||
|
${footHtml}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderTrendSection(trends, tickMap) {
|
function renderTrendSection(trends, tickMap) {
|
||||||
if (!trends || !trends.length) return "";
|
if (!trends || !trends.length) return "";
|
||||||
return trends
|
return `<div class="hub-trend-plan-list">${trends
|
||||||
.map((t) => {
|
.map((t) => renderTrendPlanCard(t, tickMap))
|
||||||
const sym = t.exchange_symbol || t.symbol || "";
|
.join("")}</div>`;
|
||||||
const sl = t.stop_loss_display || fmtSymbolPrice(t.stop_loss, sym, tickMap);
|
|
||||||
const tp = t.take_profit_display || fmtSymbolPrice(t.take_profit, sym, tickMap);
|
|
||||||
const avg = t.avg_entry_price_display || fmtSymbolPrice(t.avg_entry_price, sym, tickMap);
|
|
||||||
return `<div class="hub-mini-card">
|
|
||||||
<div class="hub-mini-title">#${esc(t.id)} · ${esc(t.symbol)} · ${renderDirectionHtml(t.direction)}</div>
|
|
||||||
<div class="hub-mini-line">均价 ${esc(avg)} · SL ${esc(sl)} · TP ${esc(tp)}${trendAddSummaryHtml(t, tickMap)} · 状态 ${esc(t.status || "active")}</div>
|
|
||||||
</div>`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLivePositionCard(exchangeId, exchangeKey, pos, monitorOrder, trendPlan, tickMap) {
|
function renderLivePositionCard(exchangeId, exchangeKey, pos, monitorOrder, trendPlan, tickMap) {
|
||||||
@@ -1436,7 +1538,8 @@
|
|||||||
const sl = tpsl.sl;
|
const sl = tpsl.sl;
|
||||||
const tp = tpsl.tp;
|
const tp = tpsl.tp;
|
||||||
const tpMonitored = tpsl.tp_monitored;
|
const tpMonitored = tpsl.tp_monitored;
|
||||||
const rr = resolveSnapshotRr(mo, side, entry, sl, tp, tpMonitored);
|
const isTrend = isTrendContext(mo, trendPlan);
|
||||||
|
const rr = resolveSnapshotRr(mo, side, entry, sl, tp, tpMonitored, trendPlan);
|
||||||
const beSecured = isBreakevenSecured(side, entry, mo, cond, pos);
|
const beSecured = isBreakevenSecured(side, entry, mo, cond, pos);
|
||||||
const upnl = pos.unrealized_pnl;
|
const upnl = pos.unrealized_pnl;
|
||||||
let pnlText = fmt(upnl, 2) + "U";
|
let pnlText = fmt(upnl, 2) + "U";
|
||||||
@@ -1445,22 +1548,42 @@
|
|||||||
pnlText += ` (${pct >= 0 ? "" : ""}${pct.toFixed(2)}%)`;
|
pnlText += ` (${pct >= 0 ? "" : ""}${pct.toFixed(2)}%)`;
|
||||||
}
|
}
|
||||||
const meta = [];
|
const meta = [];
|
||||||
if (mo.monitor_type || mo.key_signal_type || mo.trend_plan_id) {
|
if (isTrend) {
|
||||||
meta.push(monitorOrderSourceHtml(mo));
|
meta.push(monitorOrderSourceHtml(mo, trendPlan));
|
||||||
|
const riskPct =
|
||||||
|
trendPlan && trendPlan.risk_percent != null && trendPlan.risk_percent !== ""
|
||||||
|
? trendPlan.risk_percent
|
||||||
|
: mo.risk_percent;
|
||||||
|
if (riskPct != null && riskPct !== "") {
|
||||||
|
meta.push(`风险: ${esc(riskPct)}%`);
|
||||||
|
}
|
||||||
|
if (trendPlan && trendPlan.id) {
|
||||||
|
const zone =
|
||||||
|
trendPlan.add_upper_display ||
|
||||||
|
fmtSymbolPrice(trendPlan.add_upper, symbol, tickMap) ||
|
||||||
|
"—";
|
||||||
|
meta.push(
|
||||||
|
`<span class="pos-meta-accent">${esc(trendAddZoneLabel(trendPlan.direction))} ${esc(zone)}</span>`
|
||||||
|
);
|
||||||
|
const addSum = trendAddSummaryHtml(trendPlan, tickMap);
|
||||||
|
if (addSum) meta.push(addSum.replace(/^ · /, ""));
|
||||||
|
}
|
||||||
|
meta.push(`<span class="pos-meta-off">移动保本:关</span>`);
|
||||||
|
} else if (mo.monitor_type || mo.key_signal_type || mo.trend_plan_id) {
|
||||||
|
meta.push(monitorOrderSourceHtml(mo, trendPlan));
|
||||||
|
if (mo.trade_style) meta.push(`风格: ${esc(mo.trade_style)}`);
|
||||||
|
else meta.push("风格: —");
|
||||||
|
if (mo.risk_percent != null) {
|
||||||
|
meta.push(`风险: ${esc(mo.risk_percent)}%`);
|
||||||
|
}
|
||||||
|
const beOn = mo.breakeven_enabled === 1 || mo.breakeven_enabled === true;
|
||||||
|
meta.push(
|
||||||
|
`<span class="${beOn ? "pos-meta-on" : "pos-meta-off"}">移动保本:${beOn ? "开" : "关"}</span>`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
meta.push("来源: 交易所持仓");
|
meta.push("来源: 交易所持仓");
|
||||||
}
|
meta.push("风格: —");
|
||||||
if (mo.trade_style) meta.push(`风格: ${esc(mo.trade_style)}`);
|
meta.push(`<span class="pos-meta-off">移动保本:关</span>`);
|
||||||
else meta.push("风格: —");
|
|
||||||
if (mo.risk_percent != null) {
|
|
||||||
meta.push(`风险: ${esc(mo.risk_percent)}%`);
|
|
||||||
}
|
|
||||||
const beOn = mo.breakeven_enabled === 1 || mo.breakeven_enabled === true;
|
|
||||||
meta.push(
|
|
||||||
`<span class="${beOn ? "pos-meta-on" : "pos-meta-off"}">移动保本:${beOn ? "开" : "关"}</span>`
|
|
||||||
);
|
|
||||||
if (trendPlan && trendPlan.id) {
|
|
||||||
meta.push(`趋势回调${trendAddSummaryHtml(trendPlan, tickMap)}`);
|
|
||||||
}
|
}
|
||||||
const symBeBadge = beSecured ? ` ${breakevenBadgeHtml()}` : "";
|
const symBeBadge = beSecured ? ` ${breakevenBadgeHtml()}` : "";
|
||||||
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, symbol, pos, monitorOrder, trendPlan);
|
const mktAttrs = marketOpenBtnAttrs(exchangeId, exchangeKey, symbol, pos, monitorOrder, trendPlan);
|
||||||
@@ -1480,8 +1603,8 @@
|
|||||||
<div class="pos-cell"><span class="pos-label">开仓价</span><span class="pos-value">${fmtEntryPrice(pos, tickMap)}</span></div>
|
<div class="pos-cell"><span class="pos-label">开仓价</span><span class="pos-value">${fmtEntryPrice(pos, tickMap)}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">${fmtMarkPrice(pos, tickMap)}</span></div>
|
<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">${fmtMarkPrice(pos, tickMap)}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">止损</span><span class="pos-value">${sl != null && sl !== "" ? fmtSymbolPrice(sl, symbol, tickMap) : "—"}</span></div>
|
<div class="pos-cell"><span class="pos-label">止损</span><span class="pos-value">${sl != null && sl !== "" ? fmtSymbolPrice(sl, symbol, tickMap) : "—"}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value">${tpMonitored ? "程序监控" : tp != null && tp !== "" ? fmtSymbolPrice(tp, symbol, tickMap) : "—"}</span></div>
|
<div class="pos-cell"><span class="pos-label">止盈</span><span class="pos-value${tpMonitored ? " pos-tp-program" : ""}">${formatTpCellValue(tp, tpMonitored, symbol, tickMap)}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${tpMonitored ? "—" : rr != null ? fmt(rr, 2) + ":1" : "-:1"}</span></div>
|
<div class="pos-cell"><span class="pos-label">盈亏比</span><span class="pos-value">${rr != null ? fmt(rr, 2) + ":1" : "—"}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">张数</span><span class="pos-value">${fmt(pos.contracts, 4)}</span></div>
|
<div class="pos-cell"><span class="pos-label">张数</span><span class="pos-value">${fmt(pos.contracts, 4)}</span></div>
|
||||||
<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ${pnlCls(upnl)}">${pnlText}</span></div>
|
<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ${pnlCls(upnl)}">${pnlText}</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -890,7 +890,10 @@
|
|||||||
if (elPosSl) elPosSl.textContent = ctx.stop_loss != null ? fmtPrice(ctx.stop_loss) : "—";
|
if (elPosSl) elPosSl.textContent = ctx.stop_loss != null ? fmtPrice(ctx.stop_loss) : "—";
|
||||||
if (elPosTp) {
|
if (elPosTp) {
|
||||||
if (ctx.tp_monitored) {
|
if (ctx.tp_monitored) {
|
||||||
elPosTp.textContent = "程序监控";
|
elPosTp.textContent =
|
||||||
|
ctx.take_profit != null
|
||||||
|
? "程序监控 · " + fmtPrice(ctx.take_profit)
|
||||||
|
: "程序监控";
|
||||||
elPosTp.classList.add("market-pos-tp-monitored");
|
elPosTp.classList.add("market-pos-tp-monitored");
|
||||||
} else {
|
} else {
|
||||||
elPosTp.textContent = ctx.take_profit != null ? fmtPrice(ctx.take_profit) : "—";
|
elPosTp.textContent = ctx.take_profit != null ? fmtPrice(ctx.take_profit) : "—";
|
||||||
@@ -944,8 +947,12 @@
|
|||||||
{ price: posContext.entry, color: "#5b9cf5", title: "入场" },
|
{ price: posContext.entry, color: "#5b9cf5", title: "入场" },
|
||||||
{ price: posContext.stop_loss, color: "#ff4d6d", title: "止损" },
|
{ price: posContext.stop_loss, color: "#ff4d6d", title: "止损" },
|
||||||
];
|
];
|
||||||
if (!posContext.tp_monitored && posContext.take_profit != null) {
|
if (posContext.take_profit != null) {
|
||||||
specs.push({ price: posContext.take_profit, color: "#00ff9d", title: "止盈" });
|
specs.push({
|
||||||
|
price: posContext.take_profit,
|
||||||
|
color: "#00ff9d",
|
||||||
|
title: posContext.tp_monitored ? "止盈(程序)" : "止盈",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
specs.forEach(function (s) {
|
specs.forEach(function (s) {
|
||||||
if (s.price == null || !Number.isFinite(Number(s.price))) return;
|
if (s.price == null || !Number.isFinite(Number(s.price))) return;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||||
<link rel="stylesheet" href="/assets/app.css?v=20260604-hub-stat-colors" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260604-hub-trend-plan" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-bg" aria-hidden="true"></div>
|
<div class="app-bg" aria-hidden="true"></div>
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
|
|
||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||||
<script src="/assets/chart.js?v=20260603-hub-binance-tick"></script>
|
<script src="/assets/chart.js?v=20260604-hub-trend-plan"></script>
|
||||||
<script src="/assets/app.js?v=20260604-trend-handoff-src"></script>
|
<script src="/assets/app.js?v=20260604-hub-trend-plan"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ curl -s http://127.0.0.1:5100/api/ping
|
|||||||
| 功能 | 说明 |
|
| 功能 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **2×2 主界面** | 四所信息**完整展示**:余额、持仓表、委托/平仓、折叠委托单、下单监控、关键位、趋势/加仓摘要 |
|
| **2×2 主界面** | 四所信息**完整展示**:余额、持仓表、委托/平仓、折叠委托单、下单监控、关键位、趋势/加仓摘要 |
|
||||||
| **全屏放大** | **点击卡片标题栏**(非按钮区)→ 该所**全屏**:每币种一张实盘风格持仓卡;独立卡片:**关键位**、**下单监控**、**趋势回调**、**顺势加仓** |
|
| **全屏放大** | **点击卡片标题栏**(非按钮区)→ 该所**全屏**:每币种一张实盘风格持仓卡(趋势持仓显示**来源: 趋势回调计划**、**风险%**、**程序监控·止盈价**、**盈亏比**,与实例策略页一致);独立卡片:**关键位**、**下单监控**、**趋势回调**(计划卡含均价/止损/止盈/盈亏比/标记价/浮盈亏)、**顺势加仓** |
|
||||||
| **委托单折叠** | 仅「委托单」区块默认折叠;展开状态存浏览器本地,**5 秒刷新不重置** |
|
| **委托单折叠** | 仅「委托单」区块默认折叠;展开状态存浏览器本地,**5 秒刷新不重置** |
|
||||||
| **条件单 / 委托** | 每个持仓下方展示交易所 **条件单**(默认折叠)与 **普通委托**;数据来自子代理实时拉取(币安含 Algo 通道) |
|
| **条件单 / 委托** | 每个持仓下方展示交易所 **条件单**(默认折叠)与 **普通委托**;数据来自子代理实时拉取(币安含 Algo 通道) |
|
||||||
| **撤单** | 条件单区内单笔「撤单」或「撤销全部」;经中控 `POST /api/orders/{id}/cancel`、`cancel-symbol` |
|
| **撤单** | 条件单区内单笔「撤单」或「撤销全部」;经中控 `POST /api/orders/{id}/cancel`、`cancel-symbol` |
|
||||||
|
|||||||
@@ -336,8 +336,23 @@ def _trend_add_leg_fields(cfg: dict, d: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def enrich_trend_plan_for_hub(cfg: dict, raw: dict) -> dict:
|
def enrich_trend_plan_for_hub(cfg: dict, raw: dict) -> dict:
|
||||||
"""中控 /api/hub/monitor:补仓次数、加仓价(交易所精度)。"""
|
"""中控 /api/hub/monitor:与策略页运行中计划卡片同字段(浮盈亏、标记价、盈亏比等)。"""
|
||||||
return _trend_add_leg_fields(cfg, dict(raw or {}))
|
d = enrich_trend_plan(cfg, dict(raw or {}))
|
||||||
|
d["monitor_source"] = "趋势回调计划"
|
||||||
|
m = _m(cfg)
|
||||||
|
direction = (d.get("direction") or "long").lower()
|
||||||
|
try:
|
||||||
|
avg_e = float(d["avg_entry_price"])
|
||||||
|
sl = float(d["stop_loss"])
|
||||||
|
tp = float(d["take_profit"])
|
||||||
|
rr_fn = getattr(m, "calc_rr_ratio", None)
|
||||||
|
if callable(rr_fn):
|
||||||
|
rr = rr_fn(direction, avg_e, sl, tp)
|
||||||
|
if rr is not None:
|
||||||
|
d["planned_rr"] = float(rr)
|
||||||
|
except (TypeError, ValueError, KeyError):
|
||||||
|
pass
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def _patch_hub_monitor_enrich(app: Flask, cfg: dict) -> None:
|
def _patch_hub_monitor_enrich(app: Flask, cfg: dict) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user