a1abe159fa
Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
8.7 KiB
HTML
196 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>实盘下单放大 | 100根K线</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px}
|
|
.container{width:min(98vw,1900px);margin:0 auto}
|
|
.card{background:#121726;border-radius:10px;padding:12px;border:1px solid #2a3150;margin-bottom:12px}
|
|
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
.btn{padding:7px 10px;border-radius:8px;text-decoration:none;border:1px solid #304164;background:#151a2a;color:#8fc8ff;cursor:pointer}
|
|
.btn:hover{background:#1f2740}
|
|
select,button{padding:8px 10px;border-radius:8px;border:1px solid #2e2e45;background:#1a1a29;color:#fff}
|
|
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin-top:10px}
|
|
.meta-item{background:#141b2f;border:1px solid #27324e;border-radius:8px;padding:8px}
|
|
.meta-item .k{font-size:.76rem;color:#9fb0d8}
|
|
.meta-item .v{font-size:1rem;margin-top:4px;word-break:break-all}
|
|
.status{font-size:.84rem;color:#95a2c2}
|
|
.status.err{color:#ff8080}
|
|
#chart-wrap{height:560px;background:#0f1320;border:1px solid #2a3150;border-radius:10px;padding:8px}
|
|
#chart{width:100%;height:100%}
|
|
.empty{padding:18px;color:#95a2c2}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="card">
|
|
<div class="row" style="justify-content:space-between">
|
|
<div class="row">
|
|
<a class="btn" href="/">返回首页</a>
|
|
<strong style="color:#dbe4ff">实盘下单放大(100根K线)</strong>
|
|
</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"><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-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-price">-</div></div>
|
|
<div class="meta-item"><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/dist/lightweight-charts.standalone.production.js"></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 fmt = (v, d=6) => (v === null || typeof v === "undefined" || Number.isNaN(Number(v))) ? "-" : Number(v).toFixed(d);
|
|
|
|
let chart = null;
|
|
let candleSeries = null;
|
|
let priceLines = [];
|
|
|
|
function ensureChart(){
|
|
if(chart){ return true; }
|
|
if(!window.LightweightCharts){
|
|
statusEl.className = "status err";
|
|
statusEl.innerText = "图表库加载失败";
|
|
return false;
|
|
}
|
|
chart = LightweightCharts.createChart(chartHost, {
|
|
layout: { background: { color: "#0f1320" }, textColor: "#d6deff" },
|
|
grid: { vertLines: { color: "#1e263d" }, horzLines: { color: "#1e263d" } },
|
|
rightPriceScale: { borderColor: "#2a3150" },
|
|
timeScale: { borderColor: "#2a3150", timeVisible: true, secondsVisible: false },
|
|
crosshair: { mode: 0 }
|
|
});
|
|
candleSeries = chart.addCandlestickSeries({
|
|
upColor: "#4cd97f",
|
|
downColor: "#ff6666",
|
|
borderVisible: false,
|
|
wickUpColor: "#4cd97f",
|
|
wickDownColor: "#ff6666"
|
|
});
|
|
window.addEventListener("resize", () => {
|
|
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
|
|
});
|
|
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
|
|
return true;
|
|
}
|
|
|
|
function resetPriceLines(){
|
|
if(!candleSeries){ return; }
|
|
priceLines.forEach(line => {
|
|
try { candleSeries.removePriceLine(line); } catch (_) {}
|
|
});
|
|
priceLines = [];
|
|
}
|
|
|
|
function addLine(price, title, color){
|
|
if(!candleSeries || typeof price === "undefined" || price === null){ return; }
|
|
const p = Number(price);
|
|
if(Number.isNaN(p) || p <= 0){ return; }
|
|
priceLines.push(candleSeries.createPriceLine({
|
|
price: p, color, lineWidth: 1, lineStyle: 0, axisLabelVisible: true, title
|
|
}));
|
|
}
|
|
|
|
function paintOrder(order){
|
|
document.getElementById("m-symbol").innerText = order.symbol || "-";
|
|
document.getElementById("m-direction").innerText = (order.direction === "short") ? "做空" : "做多";
|
|
document.getElementById("m-entry").innerText = fmt(order.trigger_price, 8);
|
|
document.getElementById("m-sl").innerText = fmt(order.stop_loss, 8);
|
|
document.getElementById("m-tp").innerText = fmt(order.take_profit, 8);
|
|
const rr = order.rr_ratio;
|
|
document.getElementById("m-rr").innerText = (rr === null || typeof rr === "undefined") ? "-:1" : `${Number(rr).toFixed(2)}:1`;
|
|
document.getElementById("m-price").innerText = fmt(order.current_price, 8);
|
|
const pnlEl = document.getElementById("m-pnl");
|
|
pnlEl.innerText = `${fmt(order.float_pnl, 4)}U (${fmt(order.float_pct, 2)}%)`;
|
|
pnlEl.style.color = Number(order.float_pnl || 0) > 0 ? "#4cd97f" : (Number(order.float_pnl || 0) < 0 ? "#ff6666" : "#d6deff");
|
|
}
|
|
|
|
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 || "请求失败"); }
|
|
const candles = Array.isArray(data.candles) ? data.candles : [];
|
|
if(!candles.length){
|
|
statusEl.className = "status err";
|
|
statusEl.innerText = "暂无K线数据";
|
|
return;
|
|
}
|
|
candleSeries.setData(candles);
|
|
resetPriceLines();
|
|
addLine(data.order.trigger_price, "成交价", "#42a5f5");
|
|
addLine(data.order.stop_loss, "止损", "#ff6666");
|
|
addLine(data.order.take_profit, "止盈", "#4cd97f");
|
|
chart.timeScale().fitContent();
|
|
paintOrder(data.order || {});
|
|
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>
|