93e148a3e7
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>
152 lines
7.2 KiB
HTML
152 lines
7.2 KiB
HTML
<!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">实盘下单放大(100根K线)</strong><span class="exchange-tag">{{ exchange_display }}</span>
|
|
</div>
|
|
<div class="status">最近刷新:<span id="updated-at">--</span></div>
|
|
</div>
|
|
{% if orders %}
|
|
<div class="row" style="margin-top:10px">
|
|
<label>订单</label>
|
|
<select id="order-id">
|
|
{% for o in orders %}
|
|
<option value="{{ o.id }}" {% if selected_order and o.id == selected_order.id %}selected{% endif %}>
|
|
#{{ o.id }} {{ o.symbol }} {{ '做多' if o.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>
|
|
<button id="manual-refresh" type="button">刷新</button>
|
|
<span id="load-status" class="status"></span>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty">当前没有激活订单,无法展示放大K线。</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if orders %}
|
|
<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 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-entry">-</div></div>
|
|
<div class="meta-item"><div class="k">止损</div><div class="v" id="m-sl">-</div></div>
|
|
<div class="meta-item"><div class="k">止盈</div><div class="v" id="m-tp">-</div></div>
|
|
<div class="meta-item"><div class="k">盈亏比</div><div class="v" id="m-rr">-</div></div>
|
|
<div class="meta-item"><div class="k">移动保本</div><div class="v" id="m-breakeven">-</div></div>
|
|
<div class="meta-item"><div class="k">现价</div><div class="v" id="m-price">-</div></div>
|
|
<div class="meta-item meta-item--emph meta-item--pnl"><div class="k">浮盈亏</div><div class="v" id="m-pnl">-</div></div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div id="chart-wrap"><div id="chart"></div></div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if orders %}
|
|
<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 orderSelect = document.getElementById("order-id");
|
|
const tfSelect = document.getElementById("timeframe");
|
|
const statusEl = document.getElementById("load-status");
|
|
const updatedAtEl = document.getElementById("updated-at");
|
|
const chartHost = document.getElementById("chart");
|
|
const FCP = window.FocusChartPage;
|
|
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;
|
|
}
|
|
|
|
async function loadOrderKline(){
|
|
if(!ensureChart()) return;
|
|
const orderId = orderSelect.value;
|
|
const timeframe = tfSelect.value;
|
|
if(!orderId) return;
|
|
statusEl.className = "status";
|
|
statusEl.innerText = "加载中...";
|
|
try{
|
|
const resp = await fetch(`/api/order_kline?order_id=${encodeURIComponent(orderId)}&timeframe=${encodeURIComponent(timeframe)}`);
|
|
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();
|
|
const o = data.order || {};
|
|
fc.addLine(o.trigger_price, FCP.lineTitle("成交价", o.trigger_price_display), "#42a5f5");
|
|
fc.addLine(o.stop_loss, FCP.lineTitle("止损", o.stop_loss_display), "#ff6666");
|
|
fc.addLine(o.take_profit, FCP.lineTitle("止盈", o.take_profit_display), "#4cd97f");
|
|
const markPx = o.current_price;
|
|
if(markPx) fc.addLine(markPx, FCP.lineTitle("现价", o.current_price_display), "#ffb74d");
|
|
fc.chart.timeScale().fitContent();
|
|
FCP.paintOrderMeta(o);
|
|
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", loadOrderKline);
|
|
orderSelect.addEventListener("change", loadOrderKline);
|
|
tfSelect.addEventListener("change", loadOrderKline);
|
|
loadOrderKline();
|
|
setInterval(loadOrderKline, refreshMs);
|
|
</script>
|
|
{% endif %}
|
|
</body>
|
|
</html>
|