feat: unify key monitor UI with one-line summary and expandable details across all exchanges
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -321,114 +321,7 @@
|
|||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{% if page == 'key_monitor' %}
|
{% if page == 'key_monitor' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
{% include 'key_monitor_panel.html' %}
|
||||||
<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>
|
|
||||||
<div class="rule-tip">{{ key_gate_rule_text }}</div>
|
|
||||||
<div class="panel-scroll pos-list">
|
|
||||||
{% for k in key %}
|
|
||||||
<div class="pos-card" id="key-row-{{ k.id }}">
|
|
||||||
<div class="pos-card-head">
|
|
||||||
<div class="pos-card-symbol">
|
|
||||||
<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' }}">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
|
||||||
{% endif %}
|
|
||||||
<span class="badge direction" style="margin-left:4px">{{ k.monitor_type }}</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="pos-close-btn" style="border:none;cursor:pointer" onclick="deleteKeyMonitor({{ k.id }})">删</button>
|
|
||||||
</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">方案: {{ '标准突破' if (k.sl_tp_mode or 'standard') == 'standard' else ('箱体1R·止盈1.5H' if k.sl_tp_mode == 'box_1p5' else '趋势单') }}</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" style="margin-top:8px"><span class="pos-meta-item" id="key-gate-metrics-{{ k.id }}" style="color:#8fc8ff"></span></div>
|
|
||||||
</div>
|
|
||||||
{% 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">
|
|
||||||
{% for h in key_history %}
|
|
||||||
<div class="pos-card">
|
|
||||||
<div class="pos-card-head">
|
|
||||||
<div class="pos-card-symbol">
|
|
||||||
<strong>{{ h.symbol }}</strong>
|
|
||||||
<span class="pos-side-badge {{ 'pos-side-long' if h.direction == 'long' else 'pos-side-short' }}">{{ '做多' if h.direction == 'long' else '做空' }}</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="table-del" onclick="deleteKeyHistory({{ h.id }})">删除</button>
|
|
||||||
</div>
|
|
||||||
<div class="pos-meta">
|
|
||||||
<span class="pos-meta-item">{{ h.monitor_type }}</span>
|
|
||||||
<span class="pos-meta-item">{{ h.close_reason }}</span>
|
|
||||||
<span class="pos-meta-item">{{ (h.closed_at or '-')[:16] }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="pos-meta">
|
|
||||||
<span class="pos-meta-item">上: {{ h.upper }} 下: {{ h.lower }}</span>
|
|
||||||
<span class="pos-meta-item">提醒: {{ h.notification_count }}</span>
|
|
||||||
</div>
|
|
||||||
{% if h.last_alert_message %}<div style="font-size:.75rem;color:#aab;margin-top:6px;white-space:pre-wrap">{{ h.last_alert_message[:180] }}{% if h.last_alert_message|length > 180 %}…{% endif %}</div>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="pos-empty">暂无历史</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif page == 'trade' %}
|
{% elif page == 'trade' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
<div class="dual-panel-grid" style="grid-column:1/-1">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -2235,6 +2128,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
|
if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
|
||||||
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
||||||
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
||||||
|
if(typeof paintKeyMonitorSummary === "function") paintKeyMonitorSummary(k.id, k);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(page === "trade"){
|
if(page === "trade"){
|
||||||
|
|||||||
@@ -322,114 +322,7 @@
|
|||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{% if page == 'key_monitor' %}
|
{% if page == 'key_monitor' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
{% include 'key_monitor_panel.html' %}
|
||||||
<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>
|
|
||||||
<div class="rule-tip">{{ key_gate_rule_text }}</div>
|
|
||||||
<div class="panel-scroll pos-list">
|
|
||||||
{% for k in key %}
|
|
||||||
<div class="pos-card" id="key-row-{{ k.id }}">
|
|
||||||
<div class="pos-card-head">
|
|
||||||
<div class="pos-card-symbol">
|
|
||||||
<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' }}">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
|
||||||
{% endif %}
|
|
||||||
<span class="badge direction" style="margin-left:4px">{{ k.monitor_type }}</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="pos-close-btn" style="border:none;cursor:pointer" onclick="deleteKeyMonitor({{ k.id }})">删</button>
|
|
||||||
</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">方案: {{ '标准突破' if (k.sl_tp_mode or 'standard') == 'standard' else ('箱体1R·止盈1.5H' if k.sl_tp_mode == 'box_1p5' else '趋势单') }}</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" style="margin-top:8px"><span class="pos-meta-item" id="key-gate-metrics-{{ k.id }}" style="color:#8fc8ff"></span></div>
|
|
||||||
</div>
|
|
||||||
{% 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">
|
|
||||||
{% for h in key_history %}
|
|
||||||
<div class="pos-card">
|
|
||||||
<div class="pos-card-head">
|
|
||||||
<div class="pos-card-symbol">
|
|
||||||
<strong>{{ h.symbol }}</strong>
|
|
||||||
<span class="pos-side-badge {{ 'pos-side-long' if h.direction == 'long' else 'pos-side-short' }}">{{ '做多' if h.direction == 'long' else '做空' }}</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="table-del" onclick="deleteKeyHistory({{ h.id }})">删除</button>
|
|
||||||
</div>
|
|
||||||
<div class="pos-meta">
|
|
||||||
<span class="pos-meta-item">{{ h.monitor_type }}</span>
|
|
||||||
<span class="pos-meta-item">{{ h.close_reason }}</span>
|
|
||||||
<span class="pos-meta-item">{{ (h.closed_at or '-')[:16] }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="pos-meta">
|
|
||||||
<span class="pos-meta-item">上: {{ h.upper }} 下: {{ h.lower }}</span>
|
|
||||||
<span class="pos-meta-item">提醒: {{ h.notification_count }}</span>
|
|
||||||
</div>
|
|
||||||
{% if h.last_alert_message %}<div style="font-size:.75rem;color:#aab;margin-top:6px;white-space:pre-wrap">{{ h.last_alert_message[:180] }}{% if h.last_alert_message|length > 180 %}…{% endif %}</div>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="pos-empty">暂无历史</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif page == 'trade' %}
|
{% elif page == 'trade' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
<div class="dual-panel-grid" style="grid-column:1/-1">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -2219,6 +2112,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
|
if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
|
||||||
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
||||||
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
||||||
|
if(typeof paintKeyMonitorSummary === "function") paintKeyMonitorSummary(k.id, k);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(page === "trade"){
|
if(page === "trade"){
|
||||||
|
|||||||
@@ -5431,6 +5431,12 @@ def render_main_page(page="trade"):
|
|||||||
orphan_positions = collect_orphan_exchange_positions(order_list, conn)
|
orphan_positions = collect_orphan_exchange_positions(order_list, conn)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"[render_main_page] orphan positions: {exc}")
|
print(f"[render_main_page] orphan positions: {exc}")
|
||||||
|
key_gate_rule_text = (
|
||||||
|
f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 0.03%~0.5%|"
|
||||||
|
f"确认K收于箱外|量能>前20均量×1.3|日成交前30|"
|
||||||
|
f"偏离关键位 > {KEY_BREAKOUT_LIMIT_PCT}% 不提醒|"
|
||||||
|
f"【阻力/支撑】收盘突破任一侧即提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分),不自动开仓"
|
||||||
|
)
|
||||||
conn.close()
|
conn.close()
|
||||||
return render_template(
|
return render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
@@ -5502,6 +5508,7 @@ def render_main_page(page="trade"):
|
|||||||
journal_chart_default_limit=JOURNAL_CHART_DEFAULT_LIMIT,
|
journal_chart_default_limit=JOURNAL_CHART_DEFAULT_LIMIT,
|
||||||
journal_chart_default_anchor=JOURNAL_CHART_DEFAULT_ANCHOR,
|
journal_chart_default_anchor=JOURNAL_CHART_DEFAULT_ANCHOR,
|
||||||
exchange_display=EXCHANGE_DISPLAY_NAME,
|
exchange_display=EXCHANGE_DISPLAY_NAME,
|
||||||
|
key_gate_rule_text=key_gate_rule_text,
|
||||||
**strategy_extra,
|
**strategy_extra,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -5512,6 +5519,12 @@ def index():
|
|||||||
return redirect("/trade")
|
return redirect("/trade")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/key_monitor")
|
||||||
|
@login_required
|
||||||
|
def key_monitor_page():
|
||||||
|
return render_main_page("key_monitor")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/trade")
|
@app.route("/trade")
|
||||||
@login_required
|
@login_required
|
||||||
def trade_page():
|
def trade_page():
|
||||||
@@ -6284,7 +6297,7 @@ def add_key():
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total})")
|
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total})")
|
||||||
return redirect("/")
|
return redirect("/key_monitor")
|
||||||
|
|
||||||
@app.route("/add_order", methods=["POST"])
|
@app.route("/add_order", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -322,6 +322,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="top-nav">
|
<div class="top-nav">
|
||||||
|
<a href="/key_monitor" class="{% if page == 'key_monitor' %}active{% endif %}">关键位监控</a>
|
||||||
<a href="/trade" class="{% if page == 'trade' %}active{% endif %}">交易执行</a>
|
<a href="/trade" class="{% if page == 'trade' %}active{% endif %}">交易执行</a>
|
||||||
<a href="/strategy" class="{% if page in ('strategy', 'strategy_trend', 'strategy_roll') %}active{% endif %}">策略交易</a>
|
<a href="/strategy" class="{% if page in ('strategy', 'strategy_trend', 'strategy_roll') %}active{% endif %}">策略交易</a>
|
||||||
<a href="/strategy/records" class="{% if page == 'strategy_records' %}active{% endif %}">策略交易记录</a>
|
<a href="/strategy/records" class="{% if page == 'strategy_records' %}active{% endif %}">策略交易记录</a>
|
||||||
@@ -354,6 +355,8 @@
|
|||||||
<div class="export-bar">
|
<div class="export-bar">
|
||||||
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列及交易所对齐字段):</span>
|
<span style="color:#9aa">数据导出(v{{ data_export_version }} CSV,UTF-8;交易记录含开仓类型列及交易所对齐字段):</span>
|
||||||
<a href="/export/trade_records">交易记录</a>
|
<a href="/export/trade_records">交易记录</a>
|
||||||
|
<a href="/export/key_monitors">关键位(当前)</a>
|
||||||
|
<a href="/export/key_monitor_history">关键位历史</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-box">
|
<div class="stat-box">
|
||||||
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
<div class="stat-item"><div class="label">交易所</div><div class="value">{{ exchange_display }}</div></div>
|
||||||
@@ -368,7 +371,9 @@
|
|||||||
{% include 'gate_transfer_block.html' %}
|
{% include 'gate_transfer_block.html' %}
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{% if page == 'trade' %}
|
{% if page == 'key_monitor' %}
|
||||||
|
{% include 'key_monitor_panel.html' %}
|
||||||
|
{% elif page == 'trade' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
<div class="dual-panel-grid" style="grid-column:1/-1">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:8px">
|
||||||
@@ -1577,6 +1582,62 @@ if(journalForm){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncKeyMonitorFormFields(){
|
||||||
|
const typeEl = document.querySelector('#key-form [name="type"]');
|
||||||
|
const dirEl = document.getElementById("key-direction");
|
||||||
|
const modeEl = document.getElementById("key-sl-tp-mode");
|
||||||
|
const manualTp = document.getElementById("key-manual-tp");
|
||||||
|
const beWrap = document.getElementById("key-breakeven-wrap");
|
||||||
|
if(!typeEl) return;
|
||||||
|
const t = (typeEl.value || "").trim();
|
||||||
|
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
||||||
|
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
||||||
|
const fbTypes = new Set(["假突破"]);
|
||||||
|
const rsTypes = new Set(["关键阻力位","关键支撑位"]);
|
||||||
|
const showAuto = autoTypes.has(t);
|
||||||
|
const showFb = fbTypes.has(t);
|
||||||
|
const showBe = showAuto || fibTypes.has(t) || showFb;
|
||||||
|
const showDir = !rsTypes.has(t);
|
||||||
|
const upperEl = document.getElementById("key-upper");
|
||||||
|
const lowerEl = document.getElementById("key-lower");
|
||||||
|
const fbPriceEl = document.getElementById("key-fb-price");
|
||||||
|
if(dirEl){
|
||||||
|
dirEl.style.display = showDir ? "" : "none";
|
||||||
|
dirEl.required = showDir;
|
||||||
|
if(!showDir) dirEl.value = "";
|
||||||
|
}
|
||||||
|
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
||||||
|
if(manualTp){
|
||||||
|
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
||||||
|
manualTp.style.display = trend ? "" : "none";
|
||||||
|
manualTp.required = !!trend;
|
||||||
|
}
|
||||||
|
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||||
|
if(upperEl){
|
||||||
|
upperEl.style.display = showFb ? "none" : "";
|
||||||
|
upperEl.required = !showFb;
|
||||||
|
if(showFb) upperEl.value = "";
|
||||||
|
}
|
||||||
|
if(lowerEl){
|
||||||
|
lowerEl.style.display = showFb ? "none" : "";
|
||||||
|
lowerEl.required = !showFb;
|
||||||
|
if(showFb) lowerEl.value = "";
|
||||||
|
}
|
||||||
|
if(fbPriceEl){
|
||||||
|
fbPriceEl.style.display = showFb ? "" : "none";
|
||||||
|
fbPriceEl.required = showFb;
|
||||||
|
if(!showFb) fbPriceEl.value = "";
|
||||||
|
fbPriceEl.placeholder = (dirEl && dirEl.value === "short") ? "高点(阻力)" : ((dirEl && dirEl.value === "long") ? "低点(支撑)" : "做空填高点/做多填低点");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
||||||
|
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
||||||
|
const keyDirSel = document.getElementById("key-direction");
|
||||||
|
if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
|
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
|
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
|
syncKeyMonitorFormFields();
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
const keyForm = document.getElementById("key-form");
|
||||||
if(keyForm){
|
if(keyForm){
|
||||||
keyForm.addEventListener("submit", (e)=>{
|
keyForm.addEventListener("submit", (e)=>{
|
||||||
@@ -1874,7 +1935,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
(data.key_prices || []).forEach(k=>{
|
(data.key_prices || []).forEach(k=>{
|
||||||
const pEl = document.getElementById(`key-price-${k.id}`);
|
const pEl = document.getElementById(`key-price-${k.id}`);
|
||||||
if(pEl){
|
if(pEl){
|
||||||
pEl.innerText = Number(k.price).toFixed(6);
|
pEl.innerText = k.price_display || (Number.isFinite(Number(k.price)) ? Number(k.price).toFixed(6) : "-");
|
||||||
paintPriceTrend(pEl, `k-${k.id}`, Number(k.price));
|
paintPriceTrend(pEl, `k-${k.id}`, Number(k.price));
|
||||||
}
|
}
|
||||||
const upEl = document.getElementById(`key-up-diff-${k.id}`);
|
const upEl = document.getElementById(`key-up-diff-${k.id}`);
|
||||||
@@ -1888,6 +1949,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
}
|
}
|
||||||
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
||||||
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
||||||
|
if(typeof paintKeyMonitorSummary === "function") paintKeyMonitorSummary(k.id, k);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(page === "trade"){
|
if(page === "trade"){
|
||||||
|
|||||||
@@ -330,114 +330,7 @@
|
|||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{% if page == 'key_monitor' %}
|
{% if page == 'key_monitor' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
{% include 'key_monitor_panel.html' %}
|
||||||
<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>
|
|
||||||
<div class="rule-tip">{{ key_gate_rule_text }}</div>
|
|
||||||
<div class="panel-scroll pos-list">
|
|
||||||
{% for k in key %}
|
|
||||||
<div class="pos-card" id="key-row-{{ k.id }}">
|
|
||||||
<div class="pos-card-head">
|
|
||||||
<div class="pos-card-symbol">
|
|
||||||
<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' }}">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
|
||||||
{% endif %}
|
|
||||||
<span class="badge direction" style="margin-left:4px">{{ k.monitor_type }}</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="pos-close-btn" style="border:none;cursor:pointer" onclick="deleteKeyMonitor({{ k.id }})">删</button>
|
|
||||||
</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">方案: {{ '标准突破' if (k.sl_tp_mode or 'standard') == 'standard' else ('箱体1R·止盈1.5H' if k.sl_tp_mode == 'box_1p5' else '趋势单') }}</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" style="margin-top:8px"><span class="pos-meta-item" id="key-gate-metrics-{{ k.id }}" style="color:#8fc8ff"></span></div>
|
|
||||||
</div>
|
|
||||||
{% 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">
|
|
||||||
{% for h in key_history %}
|
|
||||||
<div class="pos-card">
|
|
||||||
<div class="pos-card-head">
|
|
||||||
<div class="pos-card-symbol">
|
|
||||||
<strong>{{ h.symbol }}</strong>
|
|
||||||
<span class="pos-side-badge {{ 'pos-side-long' if h.direction == 'long' else 'pos-side-short' }}">{{ '做多' if h.direction == 'long' else '做空' }}</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="table-del" onclick="deleteKeyHistory({{ h.id }})">删除</button>
|
|
||||||
</div>
|
|
||||||
<div class="pos-meta">
|
|
||||||
<span class="pos-meta-item">{{ h.monitor_type }}</span>
|
|
||||||
<span class="pos-meta-item">{{ h.close_reason }}</span>
|
|
||||||
<span class="pos-meta-item">{{ (h.closed_at or '-')[:16] }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="pos-meta">
|
|
||||||
<span class="pos-meta-item">上: {{ h.upper }} 下: {{ h.lower }}</span>
|
|
||||||
<span class="pos-meta-item">提醒: {{ h.notification_count }}</span>
|
|
||||||
</div>
|
|
||||||
{% if h.last_alert_message %}<div style="font-size:.75rem;color:#aab;margin-top:6px;white-space:pre-wrap">{{ h.last_alert_message[:180] }}{% if h.last_alert_message|length > 180 %}…{% endif %}</div>{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="pos-empty">暂无历史</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif page == 'trade' %}
|
{% elif page == 'trade' %}
|
||||||
<div class="dual-panel-grid" style="grid-column:1/-1">
|
<div class="dual-panel-grid" style="grid-column:1/-1">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -2268,6 +2161,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
|
if(gateEl){ gateEl.innerText = k.gate_summary || "-"; gateEl.style.color = k.gate_ok ? "#4cd97f" : "#ff8f8f"; }
|
||||||
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
const gateMetricEl = document.getElementById(`key-gate-metrics-${k.id}`);
|
||||||
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
if(gateMetricEl) gateMetricEl.innerText = k.gate_metrics || "";
|
||||||
|
if(typeof paintKeyMonitorSummary === "function") paintKeyMonitorSummary(k.id, k);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(page === "trade"){
|
if(page === "trade"){
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<style>
|
||||||
|
.key-row-collapse{border:1px solid #2a3348;border-radius:10px;background:#141923;overflow:hidden}
|
||||||
|
.key-row-collapse+.key-row-collapse{margin-top:0}
|
||||||
|
.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-actions{flex:0 0 auto;display:flex;gap:6px;align-items:center}
|
||||||
|
.key-row-collapse-body{padding:0 12px 12px;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;white-space:pre-wrap;line-height:1.45}
|
||||||
|
</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_brief(h) -%}
|
||||||
|
{{ h.close_reason or '—' }} · {{ (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>
|
||||||
|
<div class="rule-tip">{{ key_gate_rule_text }}</div>
|
||||||
|
<div class="panel-scroll pos-list">
|
||||||
|
{% 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-line">{{ key_monitor_brief(k) }}</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="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">
|
||||||
|
{% for h in key_history %}
|
||||||
|
<details class="key-row-collapse">
|
||||||
|
<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>
|
||||||
|
<span class="key-row-summary-line">{{ key_history_brief(h) }}</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="pos-meta">
|
||||||
|
<span class="pos-meta-item">类型: {{ h.monitor_type }}</span>
|
||||||
|
<span class="pos-meta-item">结案: {{ h.close_reason or '—' }}</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 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}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user