3527c26717
Co-authored-by: Cursor <cursoragent@cursor.com>
249 lines
15 KiB
HTML
249 lines
15 KiB
HTML
<style>
|
||
.key-panel-scroll.panel-scroll.pos-list{max-height:min(70vh,640px);overflow:auto;min-height:200px;padding-bottom:6px}
|
||
.key-row-collapse{border:1px solid #2a3348;border-radius:10px;background:#141923}
|
||
.key-row-collapse:not([open]){overflow:hidden}
|
||
.key-row-collapse[open]{overflow:visible}
|
||
.key-row-collapse+.key-row-collapse{margin-top:8px}
|
||
.key-row-collapse-summary{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:10px 12px;cursor:pointer;list-style:none;font-size:.8rem;color:#c5cde0;line-height:1.45}
|
||
.key-row-collapse-summary::-webkit-details-marker{display:none}
|
||
.key-row-collapse-summary::before{content:"▸";flex:0 0 auto;color:#6d7a99;margin-top:1px;transition:transform .15s ease}
|
||
.key-row-collapse[open]>.key-row-collapse-summary::before{transform:rotate(90deg)}
|
||
.key-row-summary-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
|
||
.key-row-summary-title{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
||
.key-row-summary-title strong{font-size:.88rem;color:#fff}
|
||
.key-row-summary-line{color:#9aa8c4;font-size:.76rem;word-break:break-word}
|
||
.key-row-summary-live{color:#8fc8ff;font-size:.74rem}
|
||
.key-row-summary-live.key-row-summary-pending{color:#4cd97f;font-weight:600}
|
||
.key-row-summary-actions{flex:0 0 auto;display:flex;gap:6px;align-items:center}
|
||
.key-row-collapse-body{padding:0 12px 16px;border-top:1px solid #232b3d}
|
||
.key-row-collapse-body .pos-meta{margin-top:10px;margin-bottom:10px}
|
||
.key-row-collapse-body .pos-grid{margin-bottom:8px}
|
||
.key-history-alert{font-size:.75rem;color:#aab;margin-top:8px;margin-bottom:2px;padding-bottom:4px;white-space:pre-wrap;word-break:break-word;line-height:1.5}
|
||
.key-history-outcome-badge{font-size:.7rem;font-weight:600;padding:1px 7px;border-radius:4px;line-height:1.35}
|
||
.key-row-collapse.key-history-success{border-color:rgba(76,217,127,.42);background:rgba(18,32,26,.92)}
|
||
.key-row-collapse.key-history-success .key-row-collapse-summary{color:#c8f0d6}
|
||
.key-row-collapse.key-history-success .key-row-summary-title strong{color:#e8fff0}
|
||
.key-row-collapse.key-history-success .key-history-brief,.key-row-collapse.key-history-success .key-history-outcome-badge{color:#4cd97f;background:rgba(76,217,127,.12);border:1px solid rgba(76,217,127,.28)}
|
||
.key-row-collapse.key-history-manual{border-color:rgba(136,146,176,.45);background:rgba(22,24,32,.95)}
|
||
.key-row-collapse.key-history-manual .key-history-brief,.key-row-collapse.key-history-manual .key-history-outcome-badge{color:#9aa8c4;background:rgba(136,146,176,.12);border:1px solid rgba(136,146,176,.28)}
|
||
.key-row-collapse.key-history-failed{border-color:rgba(232,160,144,.4);background:rgba(36,22,24,.95)}
|
||
.key-row-collapse.key-history-failed .key-row-collapse-summary{color:#e8cfc8}
|
||
.key-row-collapse.key-history-failed .key-history-brief,.key-row-collapse.key-history-failed .key-history-outcome-badge{color:#e8a090;background:rgba(232,160,144,.1);border:1px solid rgba(232,160,144,.28)}
|
||
</style>
|
||
|
||
{% macro key_direction_label(k) -%}
|
||
{% if k.direction == 'watch' %}双向{% elif k.direction == 'long' %}做多{% else %}做空{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{% macro key_sl_tp_mode_label(k) -%}
|
||
{% if (k.sl_tp_mode or 'standard') == 'standard' %}标准突破{% elif k.sl_tp_mode == 'box_1p5' %}箱体1R·止盈1.5H{% else %}趋势单{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{% macro key_monitor_brief(k) -%}
|
||
上{{ k.upper }} / 下{{ k.lower }} · 提醒 {{ k.notification_count or 0 }}/{{ k.max_notify or 3 }}
|
||
{%- if k.monitor_type in ['箱体突破','收敛突破'] %} · {{ key_sl_tp_mode_label(k) }}{% endif %}
|
||
{%- if k.breakeven_enabled %} · 保本开{% else %} · 保本关{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{% macro key_history_outcome_kind(h) -%}
|
||
{%- set r = (h.close_reason or '')|trim -%}
|
||
{%- if r in ['fib_filled', 'false_breakout_filled', 'key_level_alert_done', 'alerts_complete', 'auto_opened'] -%}success
|
||
{%- elif r == 'manual' -%}manual
|
||
{%- elif r -%}failed
|
||
{%- else -%}neutral
|
||
{%- endif -%}
|
||
{%- endmacro %}
|
||
|
||
{% macro key_history_outcome_label(h) -%}
|
||
{%- set r = (h.close_reason or '')|trim -%}
|
||
{%- if r == 'fib_filled' -%}斐波成交
|
||
{%- elif r == 'false_breakout_filled' -%}假突破成交
|
||
{%- elif r == 'key_level_alert_done' -%}提醒完成
|
||
{%- elif r == 'alerts_complete' -%}提醒已满
|
||
{%- elif r == 'auto_opened' -%}自动开仓
|
||
{%- elif r == 'manual' -%}手动删除
|
||
{%- elif r == 'fib_invalidate' -%}斐波失效
|
||
{%- elif r == 'false_breakout_expired' -%}假突破过期
|
||
{%- elif r == 'fib_plan_invalid' -%}计划无效
|
||
{%- elif r == 'rr_insufficient' -%}盈亏比不足
|
||
{%- elif r == 'exchange_failed' -%}下单失败
|
||
{%- else -%}{{ r or '—' }}
|
||
{%- endif -%}
|
||
{%- endmacro %}
|
||
|
||
{% macro key_history_brief(h) -%}
|
||
{{ key_history_outcome_label(h) }} · {{ (h.closed_at or '-')[:16] }} · 上{{ h.upper }} / 下{{ h.lower }} · 提醒 {{ h.notification_count or 0 }}
|
||
{%- endmacro %}
|
||
|
||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
||
<div class="card">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
||
<h2 style="margin-bottom:0">关键位监控</h2>
|
||
{% if focus_key_id %}
|
||
<a href="/key_focus?key_id={{ focus_key_id }}" class="btn-del" style="text-decoration:none;background:#1f3a5a;color:#8fc8ff">放大查看K线(默认200根)</a>
|
||
{% else %}
|
||
<a href="/key_focus" class="btn-del" style="text-decoration:none;background:#1f3a5a;color:#8fc8ff">输入币种查看K线</a>
|
||
{% endif %}
|
||
</div>
|
||
<form id="key-form" action="/add_key" method="post" class="form-row">
|
||
<input name="symbol" placeholder="BTC 或 BTC/USDT" required>
|
||
<select name="type" required>
|
||
<option value="箱体突破">箱体突破</option>
|
||
<option value="收敛突破">收敛突破</option>
|
||
<option value="斐波回调0.618">斐波回调0.618</option>
|
||
<option value="斐波回调0.786">斐波回调0.786</option>
|
||
<option value="假突破">假突破(BTC/ETH)</option>
|
||
<option value="关键阻力位">关键阻力位</option>
|
||
<option value="关键支撑位">关键支撑位</option>
|
||
</select>
|
||
<select name="direction" id="key-direction" required>
|
||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||
</select>
|
||
<input name="key_price" id="key-fb-price" step="0.0001" placeholder="做空填高点/做多填低点" style="display:none">
|
||
<input name="upper" id="key-upper" step="0.0001" placeholder="上沿/阻力" required>
|
||
<input name="lower" id="key-lower" step="0.0001" placeholder="下沿/支撑" required>
|
||
<select name="sl_tp_mode" id="key-sl-tp-mode" title="止盈止损方案">
|
||
<option value="standard">标准突破</option>
|
||
<option value="box_1p5">箱体1R·止盈1.5H</option>
|
||
<option value="trend_manual">趋势单·自填止盈</option>
|
||
</select>
|
||
<input name="manual_take_profit" id="key-manual-tp" step="0.0001" placeholder="趋势单止盈价" style="display:none">
|
||
<label id="key-breakeven-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.85rem;color:#9aa">
|
||
<input type="checkbox" name="breakeven_enabled" value="1" id="key-breakeven-cb"> 移动保本
|
||
</label>
|
||
<button type="submit">添加</button>
|
||
</form>
|
||
<details class="tip-collapse key-rule-collapse">
|
||
<summary class="tip-collapse-summary">关键位监控规则说明</summary>
|
||
<div class="tip-collapse-body rule-tip">{{ key_gate_rule_text }}</div>
|
||
</details>
|
||
<div class="panel-scroll pos-list key-panel-scroll">
|
||
{% for k in key %}
|
||
<details class="key-row-collapse" id="key-row-{{ k.id }}">
|
||
<summary class="key-row-collapse-summary">
|
||
<span class="key-row-summary-main">
|
||
<span class="key-row-summary-title">
|
||
<strong>{{ k.symbol }}</strong>
|
||
{% if k.direction == 'watch' %}
|
||
<span class="pos-side-badge" style="background:#2a3152;color:#9ab">双向</span>
|
||
{% else %}
|
||
<span class="pos-side-badge {{ 'pos-side-long' if k.direction == 'long' else 'pos-side-short' }}">{{ key_direction_label(k) }}</span>
|
||
{% endif %}
|
||
<span class="badge direction">{{ k.monitor_type }}</span>
|
||
</span>
|
||
<span class="key-row-summary-live" id="key-summary-live-{{ k.id }}">现价 — · 门控 —</span>
|
||
</span>
|
||
<span class="key-row-summary-actions">
|
||
<button type="button" class="table-del" onclick="event.preventDefault(); event.stopPropagation(); deleteKeyMonitor({{ k.id }})">删</button>
|
||
</span>
|
||
</summary>
|
||
<div class="key-row-collapse-body">
|
||
<div class="key-row-summary-line">{{ key_monitor_brief(k) }}</div>
|
||
<div class="pos-meta">
|
||
<span class="pos-meta-item">上沿: {{ k.upper }}</span>
|
||
<span class="pos-meta-item">下沿: {{ k.lower }}</span>
|
||
{% if k.fib_entry_price %}<span class="pos-meta-item">挂E: {{ k.fib_entry_price }}</span>{% endif %}
|
||
{% if k.monitor_type == '假突破' and k.fib_stop_loss %}<span class="pos-meta-item">SL: {{ k.fib_stop_loss }} / TP: {{ k.fib_take_profit }}</span>{% endif %}
|
||
<span class="pos-meta-item">已提醒: {{ k.notification_count or 0 }}/{{ k.max_notify or 3 }}</span>
|
||
{% if k.monitor_type in ['箱体突破','收敛突破'] %}
|
||
<span class="pos-meta-item">方案: {{ key_sl_tp_mode_label(k) }}</span>
|
||
{% endif %}
|
||
<span class="pos-meta-item">保本: {{ '开' if k.breakeven_enabled else '关' }}</span>
|
||
</div>
|
||
<div class="pos-grid">
|
||
<div class="pos-cell"><span class="pos-label">现价</span><span class="pos-value" id="key-price-{{ k.id }}">-</span></div>
|
||
<div class="pos-cell"><span class="pos-label">距上沿</span><span class="pos-value" id="key-up-diff-{{ k.id }}">-</span></div>
|
||
<div class="pos-cell"><span class="pos-label">距下沿</span><span class="pos-value" id="key-low-diff-{{ k.id }}">-</span></div>
|
||
<div class="pos-cell"><span class="pos-label">门控</span><span class="pos-value" id="key-gate-{{ k.id }}" style="color:#9aa">-</span></div>
|
||
</div>
|
||
<div class="pos-meta"><span class="pos-meta-item" id="key-gate-metrics-{{ k.id }}" style="color:#8fc8ff"></span></div>
|
||
</div>
|
||
</details>
|
||
{% else %}
|
||
<div class="pos-empty">暂无监控中的关键位</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2 style="margin-bottom:8px">关键位历史</h2>
|
||
<div class="sub" style="font-size:.72rem;color:#8892b0;margin-bottom:8px">失效或已结案的关键位 · 点击展开详情</div>
|
||
<div class="panel-scroll pos-list key-panel-scroll">
|
||
{% for h in key_history %}
|
||
<details class="key-row-collapse key-history-{{ key_history_outcome_kind(h) }}">
|
||
<summary class="key-row-collapse-summary">
|
||
<span class="key-row-summary-main">
|
||
<span class="key-row-summary-title">
|
||
<strong>{{ h.symbol }}</strong>
|
||
<span class="pos-side-badge {{ 'pos-side-long' if h.direction == 'long' else 'pos-side-short' }}">{{ key_direction_label(h) }}</span>
|
||
<span class="badge direction">{{ h.monitor_type }}</span>
|
||
<span class="key-history-outcome-badge">{{ key_history_outcome_label(h) }}</span>
|
||
</span>
|
||
</span>
|
||
<span class="key-row-summary-actions">
|
||
<button type="button" class="table-del" onclick="event.preventDefault(); event.stopPropagation(); deleteKeyHistory({{ h.id }})">删除</button>
|
||
</span>
|
||
</summary>
|
||
<div class="key-row-collapse-body">
|
||
<div class="key-row-summary-line key-history-brief">{{ key_history_brief(h) }}</div>
|
||
<div class="pos-meta">
|
||
<span class="pos-meta-item">类型: {{ h.monitor_type }}</span>
|
||
<span class="pos-meta-item">结案: {{ key_history_outcome_label(h) }}{% if h.close_reason %} ({{ h.close_reason }}){% endif %}</span>
|
||
<span class="pos-meta-item">时间: {{ h.closed_at or '—' }}</span>
|
||
</div>
|
||
<div class="pos-meta">
|
||
<span class="pos-meta-item">上沿: {{ h.upper }}</span>
|
||
<span class="pos-meta-item">下沿: {{ h.lower }}</span>
|
||
<span class="pos-meta-item">提醒次数: {{ h.notification_count or 0 }}</span>
|
||
</div>
|
||
{% if h.last_alert_message %}
|
||
<div class="key-history-alert">{{ h.last_alert_message }}</div>
|
||
{% endif %}
|
||
</div>
|
||
</details>
|
||
{% else %}
|
||
<div class="pos-empty">暂无历史</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
function keySummaryIsPending(snap){
|
||
if(!snap) return false;
|
||
const gs = String(snap.gate_summary || "");
|
||
if(gs.includes("标记价将失效")) return false;
|
||
const gm = String(snap.gate_metrics || "");
|
||
if(gm.includes("限价单") || gm.includes("挂单")) return true;
|
||
if(/等待成交/.test(gs)) return true;
|
||
if(/挂E=/.test(gs) && !gs.includes("将失效")) return true;
|
||
return false;
|
||
}
|
||
function paintKeyMonitorSummary(id, snap){
|
||
const el = document.getElementById(`key-summary-live-${id}`);
|
||
if(!el || !snap) return;
|
||
const px = snap.price_display || (Number.isFinite(Number(snap.price)) ? Number(snap.price).toFixed(6) : "—");
|
||
const gate = snap.gate_summary || "—";
|
||
el.innerText = `现价 ${px} · 门控 ${gate}`;
|
||
el.classList.toggle("key-row-summary-pending", keySummaryIsPending(snap));
|
||
}
|
||
document.querySelectorAll(".key-row-collapse").forEach((row)=>{
|
||
row.addEventListener("toggle", ()=>{
|
||
if(!row.open) return;
|
||
requestAnimationFrame(()=>{
|
||
const body = row.querySelector(".key-row-collapse-body");
|
||
const panel = row.closest(".key-panel-scroll");
|
||
if(body && panel){
|
||
const bodyRect = body.getBoundingClientRect();
|
||
const panelRect = panel.getBoundingClientRect();
|
||
if(bodyRect.bottom > panelRect.bottom - 8){
|
||
panel.scrollTop += bodyRect.bottom - panelRect.bottom + 16;
|
||
} else if(bodyRect.top < panelRect.top + 8){
|
||
panel.scrollTop -= panelRect.top - bodyRect.top + 16;
|
||
}
|
||
} else {
|
||
row.scrollIntoView({block:"nearest", behavior:"smooth"});
|
||
}
|
||
});
|
||
});
|
||
});
|
||
</script>
|