fix: unify order/key focus K-line theme, PnL, RR and exchange price tick
Share focus_chart templates and APIs across four instances; align chart Y-axis, price lines and meta bar with exchange symbol precision and live unrealized PnL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="/static/instance_theme.js?v=5"></script>
|
||||
<title>{{ exchange_display }} | 关键位放大</title>
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=5">
|
||||
<link rel="stylesheet" href="/static/focus_chart_page.css?v=1">
|
||||
</head>
|
||||
<body class="focus-page">
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="row" style="justify-content:space-between">
|
||||
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
|
||||
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
|
||||
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||||
<path fill="currentColor" d="M12.1 3a9 9 0 1 0 8.9 11 6.5 6.5 0 1 1-8.9-11z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="theme-toggle-btn" data-theme-value="light" aria-pressed="false" title="亮色主题">
|
||||
<svg class="theme-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="btn" href="/">返回首页</a>
|
||||
<strong class="focus-title">关键位放大(可输入币种)</strong><span class="exchange-tag">{{ exchange_display }}</span>
|
||||
</div>
|
||||
<div class="status">最近刷新:<span id="updated-at">--</span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-top:10px">
|
||||
<label>币种</label>
|
||||
<input id="symbol-input" value="{{ default_symbol }}" placeholder="BTC/USDT">
|
||||
<label>关键位</label>
|
||||
<select id="key-id">
|
||||
<option value="">无(仅看K线)</option>
|
||||
{% for k in key_list %}
|
||||
<option value="{{ k.id }}" {% if selected_key and k.id == selected_key.id %}selected{% endif %}>#{{ k.id }} {{ k.symbol }} {{ k.monitor_type }} {{ '做多' if k.direction == 'long' else '做空' }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>周期</label>
|
||||
<select id="timeframe">
|
||||
{% for tf in ['1m','3m','5m','15m','30m','1h','4h','1d'] %}
|
||||
<option value="{{ tf }}" {% if tf == default_timeframe %}selected{% endif %}>{{ tf }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>K线数</label>
|
||||
<select id="kline-limit">
|
||||
<option value="100" {% if default_kline_limit == 100 %}selected{% endif %}>100</option>
|
||||
<option value="200" {% if default_kline_limit == 200 %}selected{% endif %}>200</option>
|
||||
</select>
|
||||
<button id="manual-refresh" type="button">刷新</button>
|
||||
<span id="load-status" class="status"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="meta">
|
||||
<div class="meta-item meta-item--emph"><div class="k">交易对</div><div class="v" id="m-symbol">-</div></div>
|
||||
<div class="meta-item"><div class="k">监控类型</div><div class="v" id="m-type">-</div></div>
|
||||
<div class="meta-item meta-item--emph"><div class="k">方向</div><div class="v" id="m-direction">-</div></div>
|
||||
<div class="meta-item"><div class="k">上沿/阻力</div><div class="v" id="m-upper">-</div></div>
|
||||
<div class="meta-item"><div class="k">下沿/支撑</div><div class="v" id="m-lower">-</div></div>
|
||||
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
|
||||
<div class="meta-item"><div class="k">距上沿</div><div class="v" id="m-updiff">-</div></div>
|
||||
<div class="meta-item"><div class="k">距下沿</div><div class="v" id="m-lowdiff">-</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card"><div id="chart-wrap"><div id="chart"></div></div></div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/static/focus_chart_page.js?v=2"></script>
|
||||
<script>
|
||||
const refreshMs = Math.max({{ price_refresh_seconds * 1000 }}, 5000);
|
||||
const keySelect = document.getElementById("key-id");
|
||||
const symbolInput = document.getElementById("symbol-input");
|
||||
const tfSelect = document.getElementById("timeframe");
|
||||
const limitSelect = document.getElementById("kline-limit");
|
||||
const statusEl = document.getElementById("load-status");
|
||||
const updatedAtEl = document.getElementById("updated-at");
|
||||
const chartHost = document.getElementById("chart");
|
||||
const FCP = window.FocusChartPage;
|
||||
const keyMap = {};
|
||||
{% for k in key_list %}
|
||||
keyMap["{{ k.id }}"] = "{{ k.symbol }}";
|
||||
{% endfor %}
|
||||
let fc = null;
|
||||
|
||||
function ensureChart(){
|
||||
if(fc && fc.ensureSeries()) return true;
|
||||
if(!window.LightweightCharts){
|
||||
statusEl.className = "status err";
|
||||
statusEl.innerText = "图表库加载失败";
|
||||
return false;
|
||||
}
|
||||
fc = FCP.createFocusChart(chartHost);
|
||||
if(!fc || !fc.ensureSeries()){
|
||||
statusEl.className = "status err";
|
||||
statusEl.innerText = "K线序列初始化失败";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function syncSymbolByKey(){
|
||||
const keyId = keySelect.value;
|
||||
if(keyId && keyMap[keyId]) symbolInput.value = keyMap[keyId];
|
||||
}
|
||||
|
||||
async function loadKeyKline(){
|
||||
if(!ensureChart()) return;
|
||||
const keyId = keySelect.value;
|
||||
const symbol = (symbolInput.value || "").trim().toUpperCase();
|
||||
const timeframe = tfSelect.value;
|
||||
const limit = limitSelect.value;
|
||||
if(!symbol && !keyId){
|
||||
statusEl.className = "status err";
|
||||
statusEl.innerText = "请先输入币种或选择关键位";
|
||||
return;
|
||||
}
|
||||
statusEl.className = "status";
|
||||
statusEl.innerText = "加载中...";
|
||||
try{
|
||||
const qs = new URLSearchParams();
|
||||
if(keyId) qs.set("key_id", keyId);
|
||||
if(symbol) qs.set("symbol", symbol);
|
||||
qs.set("timeframe", timeframe);
|
||||
qs.set("limit", limit);
|
||||
const resp = await fetch(`/api/key_kline?${qs.toString()}`);
|
||||
const data = await resp.json();
|
||||
if(!resp.ok || !data.ok) throw new Error(data.msg || "请求失败");
|
||||
if(fc && typeof fc.setPriceTick === "function") fc.setPriceTick(data.price_tick);
|
||||
else FCP.setActivePriceTick(data.price_tick);
|
||||
const candles = Array.isArray(data.candles) ? data.candles : [];
|
||||
if(!candles.length){
|
||||
statusEl.className = "status err";
|
||||
statusEl.innerText = "暂无K线数据";
|
||||
return;
|
||||
}
|
||||
fc.candleSeries.setData(candles);
|
||||
fc.resetPriceLines();
|
||||
fc.addLine(data.current_price, FCP.lineTitle("现价", data.current_price_display), "#42a5f5");
|
||||
if(data.key_monitor){
|
||||
const km = data.key_monitor;
|
||||
fc.addLine(km.upper, FCP.lineTitle("上沿", km.upper_display), "#ffb84d");
|
||||
fc.addLine(km.lower, FCP.lineTitle("下沿", km.lower_display), "#4cd97f");
|
||||
}
|
||||
fc.chart.timeScale().fitContent();
|
||||
FCP.paintKeyMeta(data);
|
||||
updatedAtEl.innerText = data.updated_at || "--";
|
||||
statusEl.className = "status";
|
||||
statusEl.innerText = `已加载 ${candles.length} 根K线`;
|
||||
}catch(err){
|
||||
statusEl.className = "status err";
|
||||
statusEl.innerText = err && err.message ? err.message : "加载失败";
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("manual-refresh").addEventListener("click", loadKeyKline);
|
||||
keySelect.addEventListener("change", ()=>{ syncSymbolByKey(); loadKeyKline(); });
|
||||
symbolInput.addEventListener("change", ()=>{
|
||||
if(symbolInput.value.trim()) keySelect.value = "";
|
||||
loadKeyKline();
|
||||
});
|
||||
tfSelect.addEventListener("change", loadKeyKline);
|
||||
limitSelect.addEventListener("change", loadKeyKline);
|
||||
syncSymbolByKey();
|
||||
loadKeyKline();
|
||||
setInterval(loadKeyKline, refreshMs);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user