diff --git a/lib/common/static/instance_page.css b/lib/common/static/instance_page.css index 01e3131..adee9a2 100644 --- a/lib/common/static/instance_page.css +++ b/lib/common/static/instance_page.css @@ -166,6 +166,37 @@ .list-window-bar label{color:#9aa;display:flex;align-items:center;gap:6px} .stats-segment-block{margin-top:20px;padding-top:14px;border-top:1px solid #3a4468} .stats-segment-block h2{font-size:1.05rem;color:#dbe4ff;margin-bottom:8px} + .stats-period-tabs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px} + .stats-period-tab{background:#151a2a;color:#9aa3bf;border:1px solid #304164;border-radius:8px;padding:7px 14px;font-size:.84rem;cursor:pointer;transition:background .15s,border-color .15s,color .15s} + .stats-period-tab:hover{background:#1c2438;color:#cfd3ef} + .stats-period-tab.active{background:#1f3a5a;color:#8fc8ff;border-color:#3d5f8a;font-weight:600} + .stats-period-pane[hidden]{display:none!important} + .stats-period-range{font-size:.78rem;color:#8892b0;margin-bottom:12px;line-height:1.45} + .inst-stats-viz{display:flex;flex-direction:column;gap:14px;margin-bottom:14px} + .inst-stats-kpis{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px} + .inst-stats-kpi{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:12px 10px;background:#151a2a;border:1px solid #2a3152;border-radius:10px;text-align:center;min-height:88px} + .inst-stats-kpi-val{font-size:1.15rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1.2} + .inst-stats-kpi-lbl{font-size:.72rem;color:#8892b0;line-height:1.3} + .inst-stats-ring{--win-pct:0;width:56px;height:56px;border-radius:50%;background:conic-gradient(#4cd97f 0 calc(var(--win-pct) * 1%),#ff6b6b calc(var(--win-pct) * 1%) 100%);display:flex;align-items:center;justify-content:center;position:relative} + .inst-stats-ring::before{content:"";position:absolute;inset:7px;border-radius:50%;background:#151a2a} + .inst-stats-ring-label{position:relative;z-index:1;font-size:.78rem;font-weight:700;font-variant-numeric:tabular-nums} + .inst-stats-block{padding:12px;background:#141923;border:1px solid #2a3150;border-radius:10px} + .inst-stats-block-title{font-size:.72rem;color:#8892b0;margin-bottom:8px} + .inst-stats-stacked-bar{display:flex;height:10px;border-radius:6px;overflow:hidden;background:#1e2438} + .inst-stats-stacked-fill{height:100%;min-width:0;transition:width .2s ease} + .inst-stats-stacked-fill--profit{background:#4cd97f} + .inst-stats-stacked-fill--loss{background:#ff6b6b} + .inst-stats-bar-labels{display:flex;justify-content:space-between;gap:10px;margin-top:8px;font-size:.76rem;font-variant-numeric:tabular-nums} + .inst-stats-risk-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px} + .inst-stats-risk-item{display:flex;flex-direction:column;gap:3px;min-width:0} + .inst-stats-risk-item .k{font-size:.7rem;color:#8892b0} + .inst-stats-risk-item .v{font-size:.84rem;font-weight:600;font-variant-numeric:tabular-nums;color:#e8ecf4;word-break:break-word} + .inst-stats-empty{margin:0;padding:18px;text-align:center;color:#8892b0;font-size:.85rem;background:#141923;border:1px dashed #2a3348;border-radius:10px} + .inst-stats-details{margin-top:4px} + .inst-stats-details>summary{cursor:pointer;font-size:.84rem;color:#9aa3bf;padding:8px 0;user-select:none;list-style-position:inside} + .inst-stats-details>summary::-webkit-details-marker{color:#6d7689} + .inst-stats-details[open]>summary{margin-bottom:6px;color:#cfd3ef} + @media (max-width:640px){.inst-stats-kpis{grid-template-columns:1fr}.inst-stats-risk-grid{grid-template-columns:1fr}} .key-history{margin-top:12px;padding-top:10px;border-top:1px solid #2a3150} .key-history h3{font-size:.88rem;color:#b8c4ff;margin-bottom:6px} .key-history .sub{font-size:.72rem;color:#8892b0;margin-bottom:6px} diff --git a/lib/common/static/instance_stats.js b/lib/common/static/instance_stats.js new file mode 100644 index 0000000..67dabcb --- /dev/null +++ b/lib/common/static/instance_stats.js @@ -0,0 +1,96 @@ +(function (global) { + "use strict"; + + var PERIODS = ["day", "week", "month"]; + + function statsSegmentSelect() { + return document.getElementById("stats-segment-select"); + } + + function activeSegmentPanel() { + var sel = statsSegmentSelect(); + if (!sel) return null; + var key = sel.value; + return document.querySelector( + '.stats-segment-panel[data-stats-segment="' + key + '"]' + ); + } + + function replaceStatsUrl(params) { + var q = new URLSearchParams(global.location.search); + Object.keys(params).forEach(function (k) { + if (params[k] == null || params[k] === "") q.delete(k); + else q.set(k, params[k]); + }); + var qs = q.toString(); + global.history.replaceState( + null, + "", + qs ? global.location.pathname + "?" + qs : global.location.pathname + ); + } + + function switchStatsPeriod(periodKey) { + var panel = activeSegmentPanel(); + if (!panel) return; + var key = PERIODS.indexOf(periodKey) >= 0 ? periodKey : "day"; + panel.querySelectorAll(".stats-period-pane").forEach(function (pane) { + var match = pane.getAttribute("data-stats-period") === key; + if (match) pane.removeAttribute("hidden"); + else pane.setAttribute("hidden", ""); + }); + panel.querySelectorAll(".stats-period-tab").forEach(function (btn) { + var on = btn.getAttribute("data-stats-period") === key; + btn.classList.toggle("active", on); + btn.setAttribute("aria-selected", on ? "true" : "false"); + }); + replaceStatsUrl({ stats_period: key }); + } + + function switchStatsSegment() { + var sel = statsSegmentSelect(); + if (!sel) return; + var key = sel.value; + document.querySelectorAll(".stats-segment-panel").forEach(function (p) { + p.style.display = + p.getAttribute("data-stats-segment") === key ? "block" : "none"; + }); + replaceStatsUrl({ stats_segment: key }); + var period = + new URLSearchParams(global.location.search).get("stats_period") || "day"; + switchStatsPeriod(period); + } + + function bindPeriodTabs() { + document.querySelectorAll(".stats-period-tab").forEach(function (btn) { + if (btn.getAttribute("data-stats-bound") === "1") return; + btn.setAttribute("data-stats-bound", "1"); + btn.addEventListener("click", function () { + switchStatsPeriod(btn.getAttribute("data-stats-period") || "day"); + }); + }); + } + + function initStatsFromUrl() { + var sel = statsSegmentSelect(); + if (!sel) return; + bindPeriodTabs(); + var url = new URLSearchParams(global.location.search); + var segKey = url.get("stats_segment"); + if ( + segKey && + sel.querySelector('option[value="' + segKey.replace(/"/g, "") + '"]') + ) { + sel.value = segKey; + } + switchStatsSegment(); + var period = url.get("stats_period") || "day"; + if (PERIODS.indexOf(period) < 0) period = "day"; + switchStatsPeriod(period); + } + + global.switchStatsSegment = switchStatsSegment; + global.switchStatsPeriod = switchStatsPeriod; + global.initStatsFromUrl = initStatsFromUrl; + global.initStatsSegmentFromUrl = initStatsFromUrl; +})(window); diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index aa74a1e..8787d71 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -819,6 +819,46 @@ html[data-theme="light"] .stats-period-block { border-bottom-color: #d0dae4 !important; } +html[data-theme="light"] .stats-period-tab { + background: #f4f7fb !important; + color: #4a6078 !important; + border-color: #c8d4e0 !important; +} +html[data-theme="light"] .stats-period-tab:hover { + background: #e8eef5 !important; + color: #142232 !important; +} +html[data-theme="light"] .stats-period-tab.active { + background: #dce8f5 !important; + color: #0d4a7a !important; + border-color: #7eb0d8 !important; +} +html[data-theme="light"] .stats-period-range, +html[data-theme="light"] .inst-stats-kpi-lbl, +html[data-theme="light"] .inst-stats-block-title, +html[data-theme="light"] .inst-stats-risk-item .k, +html[data-theme="light"] .inst-stats-empty { + color: #4a6078 !important; +} +html[data-theme="light"] .inst-stats-kpi, +html[data-theme="light"] .inst-stats-block { + background: #f8fafc !important; + border-color: #c8d4e0 !important; +} +html[data-theme="light"] .inst-stats-ring::before { + background: #f8fafc !important; +} +html[data-theme="light"] .inst-stats-stacked-bar { + background: #e2e8f0 !important; +} +html[data-theme="light"] .inst-stats-risk-item .v, +html[data-theme="light"] .inst-stats-details > summary { + color: #142232 !important; +} +html[data-theme="light"] .inst-stats-details[open] > summary { + color: #0d4a7a !important; +} + html[data-theme="light"] .key-history { border-top-color: #d0dae4 !important; } diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index 6068b74..79a6b55 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -597,29 +597,6 @@ function recomputeJournalRealRr(){ } -function switchStatsSegment(){ - const sel = document.getElementById("stats-segment-select"); - if(!sel) return; - const key = sel.value; - document.querySelectorAll(".stats-segment-panel").forEach(p=>{ - p.style.display = p.getAttribute("data-stats-segment") === key ? "block" : "none"; - }); - const q = new URLSearchParams(window.location.search); - q.set("stats_segment", key); - const qs = q.toString(); - history.replaceState(null, "", qs ? (window.location.pathname + "?" + qs) : window.location.pathname); -} - -function initStatsSegmentFromUrl(){ - const sel = document.getElementById("stats-segment-select"); - if(!sel) return; - const key = new URLSearchParams(window.location.search).get("stats_segment"); - if(key && sel.querySelector('option[value="' + key.replace(/"/g, "") + '"]')){ - sel.value = key; - } - switchStatsSegment(); -} - function toggleStatsCard(){ const card = document.getElementById("stats-card"); const btn = document.getElementById("stats-toggle-btn"); diff --git a/lib/instance/templates/embed_page_fragment.html b/lib/instance/templates/embed_page_fragment.html index 2dad742..41397f0 100644 --- a/lib/instance/templates/embed_page_fragment.html +++ b/lib/instance/templates/embed_page_fragment.html @@ -1,442 +1,541 @@ -{# Hub iframe tab fragment — shared via embed_templates #} -{% macro period_stats(title, s) %} -
将先撤销该合约已有 TP/SL,再按下列价格重挂.
-| 品种 | 下单类型 | 开仓类型 | 方向 | 成交 | 止损(开仓) | 止盈 | 基数 | 杠杆 | 持仓分钟 | 开仓时间(北京) | 平仓时间(北京) | 盈亏U | 结果 | 操作 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ r.symbol }} | -{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %} | -{{ r.effective_entry_reason or '-' }} | -{{ '做多' if r.direction == 'long' else '做空' }} | -{{ price_fmt(r.symbol, r.trigger_price) }} | - {% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %} - {% set tp_show = r.effective_take_profit or r.take_profit %} -{{ price_fmt(r.symbol, stop_show) }} | -{{ price_fmt(r.symbol, tp_show) }} | -{% if r.margin_capital is not none and r.margin_capital != '' %}{{ funds_fmt(r.margin_capital) }}{% else %}-{% endif %} | -{{ r.leverage or '-' }} | -{{ r.effective_hold_minutes or 0 }} | -{{ (r.effective_opened_at or '-')[:16] }} | -{{ (r.effective_closed_at or r.created_at or '-')[:16] }} | - {% set pnl_val = (r.effective_pnl_amount or 0)|float %} -{{ funds_fmt(r.effective_pnl_amount or 0) }}{% if r.display_pnl_source == 'exchange' %}所{% elif r.display_pnl_source != 'reviewed' %}估{% endif %} | -- {% set effective_result = r.effective_result %} - {% if effective_result in ["止盈","保本止盈","移动止盈"] %}{{ effective_result }} - {% elif effective_result in ["止损","强制清仓","手动平仓"] %}{{ effective_result }} - {% elif effective_result == "时间平仓" %}{{ effective_result }} - {% else %}{{ effective_result or '-' }}{% endif %} - | -- - - - | -
当前区间暂无平仓数据
+ {% endif %} +将先撤销该合约已有 TP/SL,再按下列价格重挂.
+| 品种 | 下单类型 | 开仓类型 | 方向 | 成交 | 止损(开仓) | 止盈 | 基数 | 杠杆 | 持仓分钟 | 开仓时间(北京) | 平仓时间(北京) | 盈亏U | 结果 | 操作 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ r.symbol }} | +{{ r.monitor_type }}{% if r.key_signal_type %} · {{ r.key_signal_type }}{% endif %} | +{{ r.effective_entry_reason or '-' }} | +{{ '做多' if r.direction == 'long' else '做空' }} | +{{ price_fmt(r.symbol, r.trigger_price) }} | + {% set stop_show = r.display_open_stop_loss or r.initial_stop_loss or r.stop_loss %} + {% set tp_show = r.effective_take_profit or r.take_profit %} +{{ price_fmt(r.symbol, stop_show) }} | +{{ price_fmt(r.symbol, tp_show) }} | +{% if r.margin_capital is not none and r.margin_capital != '' %}{{ funds_fmt(r.margin_capital) }}{% else %}-{% endif %} | +{{ r.leverage or '-' }} | +{{ r.effective_hold_minutes or 0 }} | +{{ (r.effective_opened_at or '-')[:16] }} | +{{ (r.effective_closed_at or r.created_at or '-')[:16] }} | + {% set pnl_val = (r.effective_pnl_amount or 0)|float %} +{{ funds_fmt(r.effective_pnl_amount or 0) }}{% if r.display_pnl_source == 'exchange' %}所{% elif r.display_pnl_source != 'reviewed' %}估{% endif %} | ++ {% set effective_result = r.effective_result %} + {% if effective_result in ["止盈","保本止盈","移动止盈"] %}{{ effective_result }} + {% elif effective_result in ["止损","强制清仓","手动平仓"] %}{{ effective_result }} + {% elif effective_result == "时间平仓" %}{{ effective_result }} + {% else %}{{ effective_result or '-' }}{% endif %} + | ++ + + + | +