fix(hub): align trend pullback card with instance layout
Match strategy page plan card: 3x3 metrics, DCA table, breakeven row, snapshot footer; PnL percent on plan margin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1320,139 +1320,222 @@ 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 {
|
/* 趋势回调:与四所实例 strategy_trend_panel 同款卡片 */
|
||||||
|
.hub-trend-running-title {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #b8c4ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-list.running-plans-stack {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-card {
|
.hub-trend-plan-card.plan-position-card {
|
||||||
|
background: #141a2a;
|
||||||
|
border: 1px solid #2a3150;
|
||||||
|
border-radius: 12px;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
background: rgba(0, 0, 0, 0.28);
|
|
||||||
border: 1px solid var(--border-soft);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-head {
|
.hub-trend-plan-card .plan-card-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-title {
|
.hub-trend-plan-card .plan-card-title {
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hub-trend-plan-status {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hub-trend-plan-meta {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px 14px;
|
font-size: 1rem;
|
||||||
font-size: 12px;
|
font-weight: 700;
|
||||||
color: var(--muted);
|
color: #f0f2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .plan-card-meta {
|
||||||
|
font-size: 0.76rem;
|
||||||
|
color: #8892b0;
|
||||||
|
line-height: 1.55;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-meta .pos-meta-accent,
|
.hub-trend-plan-card .plan-card-meta .accent {
|
||||||
.hub-trend-plan-meta strong {
|
color: #6ab8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .plan-card-meta strong {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-foot {
|
.hub-trend-plan-card .plan-card-grid {
|
||||||
margin-top: 10px;
|
display: grid;
|
||||||
font-size: 11px;
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
color: var(--muted);
|
gap: 10px 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pos-value.pos-tp-program {
|
.hub-trend-plan-card .plan-cell {
|
||||||
color: #8fc8ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hub-trend-plan-card--horizontal {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
gap: 14px 18px;
|
gap: 3px;
|
||||||
align-items: flex-start;
|
|
||||||
background: linear-gradient(145deg, rgba(12, 18, 32, 0.92), rgba(8, 12, 22, 0.88));
|
|
||||||
border-color: rgba(0, 212, 255, 0.22);
|
|
||||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-body {
|
.hub-trend-plan-card .plan-cell .lbl {
|
||||||
flex: 1 1 320px;
|
font-size: 0.72rem;
|
||||||
min-width: 0;
|
color: #8b95b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-plan-side {
|
.hub-trend-plan-card .plan-cell .val {
|
||||||
flex: 1 1 220px;
|
color: #f0f2ff;
|
||||||
min-width: 200px;
|
font-size: 0.88rem;
|
||||||
max-width: 100%;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-block {
|
.hub-trend-plan-card .plan-cell .val.pnl-profit {
|
||||||
padding: 8px 10px;
|
color: #4cd97f;
|
||||||
background: rgba(0, 0, 0, 0.22);
|
}
|
||||||
border: 1px solid var(--border-soft);
|
|
||||||
|
.hub-trend-plan-card .plan-cell .val.pnl-loss {
|
||||||
|
color: #ff6666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .plan-cell .val.pnl-neutral {
|
||||||
|
color: #cfd3ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .btn-close-plan {
|
||||||
|
padding: 7px 14px;
|
||||||
|
background: #5c1e2a;
|
||||||
|
color: #ffb4b4;
|
||||||
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-title {
|
.hub-trend-plan-card .btn-close-plan:hover {
|
||||||
font-size: 11px;
|
filter: brightness(1.08);
|
||||||
color: var(--muted);
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-table {
|
.hub-trend-plan-card .plan-dca-block {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px dashed #2a3558;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .plan-dca-title {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: #8b95b8;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .plan-dca-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 11px;
|
font-size: 0.76rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-table th,
|
.hub-trend-plan-card .plan-dca-table th,
|
||||||
.hub-trend-dca-table td {
|
.hub-trend-plan-card .plan-dca-table td {
|
||||||
padding: 4px 6px;
|
padding: 6px 8px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
border-bottom: 1px solid #243050;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-table th {
|
.hub-trend-plan-card .plan-dca-table th {
|
||||||
color: var(--muted);
|
color: #6a7598;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-table .dca-done {
|
.hub-trend-plan-card .plan-dca-table .st-done {
|
||||||
color: var(--green);
|
color: #4cd97f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-trend-dca-table .dca-pending {
|
.hub-trend-plan-card .plan-dca-table .st-pending {
|
||||||
color: var(--muted);
|
color: #9aa3c4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exchange-fullscreen .hub-trend-plan-list {
|
.hub-trend-plan-card .hub-plan-breakeven-row {
|
||||||
display: block;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .hub-plan-be-label {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #cfd3ef;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .hub-plan-be-input {
|
||||||
|
width: 72px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #304164;
|
||||||
|
background: #0f1424;
|
||||||
|
color: #cfd3ef;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .hub-plan-be-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #1f4a3a;
|
||||||
|
color: #8fc8ff;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .hub-plan-be-btn--static {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .hub-plan-be-done {
|
||||||
|
color: #6ab88a;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .hub-plan-account-foot {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .badge.direction-long {
|
||||||
|
color: #4cd97f;
|
||||||
|
border-color: rgba(76, 217, 127, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hub-trend-plan-card .badge.direction-short {
|
||||||
|
color: #ff6666;
|
||||||
|
border-color: rgba(255, 102, 102, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exchange-fullscreen .hub-trend-plan-card.plan-position-card {
|
||||||
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exchange-fullscreen .hub-trend-plan-card--horizontal {
|
@media (max-width: 720px) {
|
||||||
width: 100%;
|
.hub-trend-plan-card .plan-card-grid {
|
||||||
}
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
.exchange-fullscreen .hub-trend-plan-metrics-row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 10px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exchange-fullscreen .hub-trend-plan-grid {
|
|
||||||
flex: 1 1 280px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 顺势加仓 */
|
/* 顺势加仓 */
|
||||||
|
|||||||
@@ -345,6 +345,76 @@
|
|||||||
return { text: pnlText, cls: pnlCls(upnl) };
|
return { text: pnlText, cls: pnlCls(upnl) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 与实例策略页一致:浮盈亏 % = 浮盈亏 / 计划保证金 */
|
||||||
|
function formatTrendPlanFloatingPnl(upnl, planMargin) {
|
||||||
|
if (upnl == null || !Number.isFinite(Number(upnl))) {
|
||||||
|
return { text: "—", cls: "" };
|
||||||
|
}
|
||||||
|
let pnlText = fmt(upnl, 2) + "U";
|
||||||
|
const margin = Number(planMargin);
|
||||||
|
if (Number.isFinite(margin) && margin > 0) {
|
||||||
|
const pct = (Number(upnl) / margin) * 100;
|
||||||
|
pnlText += ` (${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%)`;
|
||||||
|
}
|
||||||
|
const n = Number(upnl);
|
||||||
|
let cls = "pnl-neutral";
|
||||||
|
if (n > 0) cls = "pnl-profit";
|
||||||
|
else if (n < 0) cls = "pnl-loss";
|
||||||
|
return { text: pnlText, cls };
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDirectionBadge(side) {
|
||||||
|
const s = normSide(side);
|
||||||
|
const label = sideDirLabel(side);
|
||||||
|
const cls = s === "long" ? "direction-long" : s === "short" ? "direction-short" : "";
|
||||||
|
if (!cls) return esc(String(label));
|
||||||
|
return `<span class="badge ${cls}">${esc(label)}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTrendDcaLevels(t) {
|
||||||
|
if (Array.isArray(t.dca_levels) && t.dca_levels.length) return t.dca_levels;
|
||||||
|
const plan = t || {};
|
||||||
|
let grid = [];
|
||||||
|
let legAmounts = [];
|
||||||
|
try {
|
||||||
|
grid = JSON.parse(plan.grid_prices_json || "[]");
|
||||||
|
if (!Array.isArray(grid)) grid = [];
|
||||||
|
} catch (_e) {
|
||||||
|
grid = [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
legAmounts = JSON.parse(plan.leg_amounts_json || "[]");
|
||||||
|
if (!Array.isArray(legAmounts)) legAmounts = [];
|
||||||
|
} catch (_e2) {
|
||||||
|
legAmounts = [];
|
||||||
|
}
|
||||||
|
const legsDone = Number(plan.legs_done) || 0;
|
||||||
|
const dcaLegs = Number(plan.dca_legs) || 0;
|
||||||
|
const firstDone = Number(plan.first_order_done) !== 0;
|
||||||
|
const out = [
|
||||||
|
{
|
||||||
|
label: "首仓",
|
||||||
|
price: null,
|
||||||
|
contracts: plan.first_order_amount,
|
||||||
|
status: firstDone ? "done" : "pending",
|
||||||
|
status_label: firstDone ? "已开仓" : "待开仓",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const n = Math.max(grid.length, legAmounts.length, dcaLegs);
|
||||||
|
for (let idx = 0; idx < n; idx += 1) {
|
||||||
|
const legI = idx + 1;
|
||||||
|
const done = legI <= legsDone;
|
||||||
|
out.push({
|
||||||
|
label: `补仓${legI}`,
|
||||||
|
price: idx < grid.length ? grid[idx] : null,
|
||||||
|
contracts: idx < legAmounts.length ? legAmounts[idx] : null,
|
||||||
|
status: done ? "done" : "pending",
|
||||||
|
status_label: done ? "已补仓" : "待补仓",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function pnlCls(v) {
|
function pnlCls(v) {
|
||||||
const n = Number(v);
|
const n = Number(v);
|
||||||
if (!Number.isFinite(n) || n === 0) return "";
|
if (!Number.isFinite(n) || n === 0) return "";
|
||||||
@@ -1333,6 +1403,8 @@
|
|||||||
btn.onclick = (ev) => {
|
btn.onclick = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
const msg = (btn.dataset.confirm || "").trim();
|
||||||
|
if (msg && !confirm(msg)) return;
|
||||||
openInstance(btn.dataset.exId, btn.dataset.next || "/", {
|
openInstance(btn.dataset.exId, btn.dataset.next || "/", {
|
||||||
newTab: ev.ctrlKey || ev.metaKey,
|
newTab: ev.ctrlKey || ev.metaKey,
|
||||||
});
|
});
|
||||||
@@ -1514,7 +1586,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderTrendDcaTable(t, tickMap) {
|
function renderTrendDcaTable(t, tickMap) {
|
||||||
const levels = Array.isArray(t.dca_levels) ? t.dca_levels : [];
|
const levels = resolveTrendDcaLevels(t);
|
||||||
if (!levels.length) return "";
|
if (!levels.length) return "";
|
||||||
const sym = t.exchange_symbol || t.symbol || "";
|
const sym = t.exchange_symbol || t.symbol || "";
|
||||||
const rows = levels
|
const rows = levels
|
||||||
@@ -1525,7 +1597,7 @@
|
|||||||
: "—";
|
: "—";
|
||||||
const amt =
|
const amt =
|
||||||
lv.contracts != null && lv.contracts !== "" ? esc(String(lv.contracts)) : "—";
|
lv.contracts != null && lv.contracts !== "" ? esc(String(lv.contracts)) : "—";
|
||||||
const stCls = lv.status === "done" ? "dca-done" : "dca-pending";
|
const stCls = lv.status === "done" ? "st-done" : "st-pending";
|
||||||
const label = lv.status_label || (lv.status === "done" ? "已补仓" : "待补仓");
|
const label = lv.status_label || (lv.status === "done" ? "已补仓" : "待补仓");
|
||||||
return `<tr>
|
return `<tr>
|
||||||
<td>${esc(lv.label || lv.leg_key || "—")}</td>
|
<td>${esc(lv.label || lv.leg_key || "—")}</td>
|
||||||
@@ -1535,16 +1607,16 @@
|
|||||||
</tr>`;
|
</tr>`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
return `<div class="hub-trend-dca-block">
|
return `<div class="plan-dca-block">
|
||||||
<div class="hub-trend-dca-title">补仓计划明细</div>
|
<div class="plan-dca-title">补仓计划明细</div>
|
||||||
<table class="hub-trend-dca-table">
|
<table class="plan-dca-table">
|
||||||
<thead><tr><th>档位</th><th>触发价</th><th>张数</th><th>状态</th></tr></thead>
|
<tr><th>档位</th><th>触发价</th><th>张数</th><th>状态</th></tr>
|
||||||
<tbody>${rows}</tbody>
|
${rows}
|
||||||
</table>
|
</table>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTrendPlanCard(t, tickMap, pos) {
|
function renderTrendPlanCard(t, tickMap, pos, exchangeRow) {
|
||||||
const sym = t.exchange_symbol || t.symbol || "";
|
const sym = t.exchange_symbol || t.symbol || "";
|
||||||
const side = (t.direction || "long").toLowerCase();
|
const side = (t.direction || "long").toLowerCase();
|
||||||
const sl = t.stop_loss_display || fmtSymbolPrice(t.stop_loss, sym, tickMap);
|
const sl = t.stop_loss_display || fmtSymbolPrice(t.stop_loss, sym, tickMap);
|
||||||
@@ -1564,66 +1636,79 @@
|
|||||||
? esc(legsDone)
|
? esc(legsDone)
|
||||||
: "—";
|
: "—";
|
||||||
const upnlTrend = resolveTrendFloatingPnl(pos, t);
|
const upnlTrend = resolveTrendFloatingPnl(pos, t);
|
||||||
const notional =
|
const pnlFmt = formatTrendPlanFloatingPnl(upnlTrend, t.plan_margin_capital);
|
||||||
pos && pos.notional_usdt != null
|
const pnlVal =
|
||||||
? pos.notional_usdt
|
|
||||||
: t.plan_margin_capital != null
|
|
||||||
? Number(t.plan_margin_capital) * Number(t.leverage || 1)
|
|
||||||
: null;
|
|
||||||
const pnlFmt = formatFloatingPnlText(upnlTrend, notional);
|
|
||||||
const pnlInner =
|
|
||||||
pnlFmt.text === "—"
|
pnlFmt.text === "—"
|
||||||
? "—"
|
? "—"
|
||||||
: `<span class="pos-value ${pnlFmt.cls}">${esc(pnlFmt.text)}</span>`;
|
: `<span class="val ${pnlFmt.cls}">${esc(pnlFmt.text)}</span>`;
|
||||||
const sizing = resolveTrendSizingFooter({}, t, true);
|
|
||||||
const levTxt =
|
|
||||||
sizing.leverage != null && sizing.leverage !== "" ? `${esc(sizing.leverage)}x` : "—";
|
|
||||||
const baseTxt =
|
|
||||||
sizing.planBase != null && sizing.planBase !== "" ? `${fmt(sizing.planBase, 2)}U` : "—";
|
|
||||||
const ratioTxt =
|
|
||||||
sizing.positionRatio != null && sizing.positionRatio !== ""
|
|
||||||
? `${fmt(sizing.positionRatio, 2)}%`
|
|
||||||
: "—";
|
|
||||||
const riskTxt =
|
const riskTxt =
|
||||||
t.risk_percent != null && t.risk_percent !== "" ? `${esc(t.risk_percent)}%` : "—";
|
t.risk_percent != null && t.risk_percent !== "" ? `${esc(t.risk_percent)}%` : "—";
|
||||||
const footHtml = `<div class="hub-trend-plan-foot pos-footer">
|
const snapTxt =
|
||||||
<span>杠杆: ${levTxt}</span>
|
t.snapshot_available_usdt != null && t.snapshot_available_usdt !== ""
|
||||||
<span>计划基数: ${baseTxt}</span>
|
? `${fmt(t.snapshot_available_usdt, 2)}U`
|
||||||
<span>仓位占比: ${ratioTxt}</span>
|
: "—";
|
||||||
</div>`;
|
const marginTxt =
|
||||||
|
t.plan_margin_capital != null && t.plan_margin_capital !== ""
|
||||||
|
? `≈${fmt(t.plan_margin_capital, 2)}U`
|
||||||
|
: "—";
|
||||||
|
const levTxt = t.leverage != null && t.leverage !== "" ? `${esc(t.leverage)}x` : "—";
|
||||||
|
const bePct =
|
||||||
|
t.breakeven_offset_pct != null && t.breakeven_offset_pct !== ""
|
||||||
|
? esc(t.breakeven_offset_pct)
|
||||||
|
: "0.3";
|
||||||
|
const exId = exchangeRow && exchangeRow.id != null ? esc(exchangeRow.id) : "";
|
||||||
|
const canOpen = !!(exchangeRow && (exchangeRow.flask_url_browser || exchangeRow.flask_url));
|
||||||
|
const endBtn = canOpen
|
||||||
|
? `<a href="#" class="btn-close-plan btn-open-instance" data-ex-id="${exId}" data-next="/stop_trend_pullback/${esc(t.id)}" data-confirm="结束计划:市价平仓并撤掉该合约全部挂单,确定?">结束计划</a>`
|
||||||
|
: "";
|
||||||
|
const beBtn = canOpen
|
||||||
|
? `<a href="#" class="hub-plan-be-btn btn-open-instance" data-ex-id="${exId}" data-next="/strategy">保本移交下单监控</a>`
|
||||||
|
: `<span class="hub-plan-be-btn hub-plan-be-btn--static">保本移交下单监控</span>`;
|
||||||
|
const beApplied =
|
||||||
|
t.breakeven_applied
|
||||||
|
? `<span class="hub-plan-be-done">已保本 ${esc(String(t.breakeven_applied_at || "").slice(0, 16))}</span>`
|
||||||
|
: "";
|
||||||
const dcaHtml = renderTrendDcaTable(t, tickMap);
|
const dcaHtml = renderTrendDcaTable(t, tickMap);
|
||||||
return `<div class="hub-trend-plan-card hub-pos-card hub-trend-plan-card--horizontal">
|
return `<div class="plan-position-card hub-trend-plan-card">
|
||||||
<div class="hub-trend-plan-body">
|
<div class="plan-card-head">
|
||||||
<div class="hub-trend-plan-head">
|
<div class="plan-card-title">
|
||||||
<span class="hub-trend-plan-title">#${esc(t.id)} ${esc(sym)} ${renderDirectionHtml(t.direction)}</span>
|
<span>#${esc(t.id)} ${esc(sym)}</span>
|
||||||
<span class="hub-trend-plan-status">${esc(t.status || "active")}</span>
|
${renderDirectionBadge(t.direction)}
|
||||||
</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="hub-trend-plan-metrics-row">
|
|
||||||
<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>
|
</div>
|
||||||
|
${endBtn}
|
||||||
|
</div>
|
||||||
|
<div class="plan-card-meta">
|
||||||
|
来源: 趋势回调计划 | 风险: ${riskTxt}
|
||||||
|
| <span class="accent">${esc(trendAddZoneLabel(t.direction))} ${esc(addZone)}</span>
|
||||||
|
| 已补仓 <strong>${legsTxt}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="plan-card-grid">
|
||||||
|
<div class="plan-cell"><span class="lbl">均价</span><span class="val">${esc(avg)}</span></div>
|
||||||
|
<div class="plan-cell"><span class="lbl">止损</span><span class="val">${esc(sl)}</span></div>
|
||||||
|
<div class="plan-cell"><span class="lbl">止盈</span><span class="val">${esc(tp)}</span></div>
|
||||||
|
<div class="plan-cell"><span class="lbl">盈亏比</span><span class="val">${esc(rrTxt)}</span></div>
|
||||||
|
<div class="plan-cell"><span class="lbl">标记价</span><span class="val">${esc(mark)}</span></div>
|
||||||
|
<div class="plan-cell"><span class="lbl">浮盈亏</span>${pnlVal}</div>
|
||||||
|
</div>
|
||||||
|
${dcaHtml}
|
||||||
|
<div class="plan-card-meta hub-plan-breakeven-row">
|
||||||
|
<label class="hub-plan-be-label">
|
||||||
|
保本移交 偏移%
|
||||||
|
<input type="number" disabled value="${bePct}" class="hub-plan-be-input" />
|
||||||
|
</label>
|
||||||
|
${beBtn}
|
||||||
|
${beApplied}
|
||||||
|
</div>
|
||||||
|
<div class="plan-card-meta hub-plan-account-foot">
|
||||||
|
快照可用: ${esc(snapTxt)} | 计划保证金${esc(marginTxt)} | 杠杆: ${levTxt}
|
||||||
</div>
|
</div>
|
||||||
${dcaHtml ? `<div class="hub-trend-plan-side">${dcaHtml}</div>` : ""}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTrendSection(trends, tickMap, positions) {
|
function renderTrendSection(trends, tickMap, positions, exchangeRow) {
|
||||||
if (!trends || !trends.length) return "";
|
if (!trends || !trends.length) return "";
|
||||||
const posList = Array.isArray(positions) ? positions : [];
|
const posList = Array.isArray(positions) ? positions : [];
|
||||||
return `<div class="hub-trend-plan-list">${trends
|
const cards = trends
|
||||||
.map((t) => {
|
.map((t) => {
|
||||||
const sym = t.exchange_symbol || t.symbol || "";
|
const sym = t.exchange_symbol || t.symbol || "";
|
||||||
const side = (t.direction || "long").toLowerCase();
|
const side = (t.direction || "long").toLowerCase();
|
||||||
@@ -1636,9 +1721,13 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return renderTrendPlanCard(t, tickMap, matched);
|
return renderTrendPlanCard(t, tickMap, matched, exchangeRow);
|
||||||
})
|
})
|
||||||
.join("")}</div>`;
|
.join("");
|
||||||
|
return `<div class="hub-trend-running">
|
||||||
|
<div class="hub-trend-running-title">运行中的计划</div>
|
||||||
|
<div class="running-plans-stack hub-trend-plan-list">${cards}</div>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLivePositionCard(exchangeId, exchangeKey, pos, monitorOrder, trendPlan, tickMap) {
|
function renderLivePositionCard(exchangeId, exchangeKey, pos, monitorOrder, trendPlan, tickMap) {
|
||||||
@@ -2052,7 +2141,7 @@
|
|||||||
if ((row.capabilities || []).includes("trend")) {
|
if ((row.capabilities || []).includes("trend")) {
|
||||||
html += renderHubSectionCard(
|
html += renderHubSectionCard(
|
||||||
"趋势回调",
|
"趋势回调",
|
||||||
renderTrendSection(trends, tickMap, pos),
|
renderTrendSection(trends, tickMap, pos, row),
|
||||||
"暂无运行中的趋势回调计划"
|
"暂无运行中的趋势回调计划"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,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-trend-dca" />
|
<link rel="stylesheet" href="/assets/app.css?v=20260604-hub-trend-instance" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-bg" aria-hidden="true"></div>
|
<div class="app-bg" aria-hidden="true"></div>
|
||||||
@@ -235,6 +235,6 @@
|
|||||||
<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=20260604-hub-trend-plan"></script>
|
<script src="/assets/chart.js?v=20260604-hub-trend-plan"></script>
|
||||||
<script src="/assets/app.js?v=20260604-hub-trend-dca"></script>
|
<script src="/assets/app.js?v=20260604-hub-trend-instance"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user