ffe5f230fd
Co-authored-by: Cursor <cursoragent@cursor.com>
635 lines
19 KiB
JavaScript
635 lines
19 KiB
JavaScript
/**
|
|
* 中控行情区:K 线 + 成交量;默认最新 OHLCV;5s 自动刷新;价格轴「自动」。
|
|
*/
|
|
(function () {
|
|
const AUTO_REFRESH_MS = 5000;
|
|
const DEFAULT_VISIBLE_BARS = 200;
|
|
const RIGHT_OFFSET_BARS = 12;
|
|
const TF_MS = {
|
|
"1m": 60_000,
|
|
"5m": 5 * 60_000,
|
|
"15m": 15 * 60_000,
|
|
"1h": 60 * 60_000,
|
|
"4h": 4 * 60 * 60_000,
|
|
"1d": 24 * 60 * 60_000,
|
|
"1w": 7 * 24 * 60 * 60_000,
|
|
};
|
|
const chartHost = document.getElementById("market-chart");
|
|
if (!chartHost) return;
|
|
|
|
const elExchange = document.getElementById("market-exchange");
|
|
const elSymbol = document.getElementById("market-symbol");
|
|
const elTf = document.getElementById("market-timeframe");
|
|
const elRefresh = document.getElementById("market-refresh");
|
|
const elStatus = document.getElementById("market-status");
|
|
const elUpdated = document.getElementById("market-updated");
|
|
const elBarCountdown = document.getElementById("market-bar-countdown");
|
|
const elPriceGutter = document.querySelector(".market-price-gutter");
|
|
const elO = document.getElementById("mkt-o");
|
|
const elH = document.getElementById("mkt-h");
|
|
const elL = document.getElementById("mkt-l");
|
|
const elC = document.getElementById("mkt-c");
|
|
const elV = document.getElementById("mkt-v");
|
|
const elAmp = document.getElementById("mkt-amp");
|
|
const elPriceTag = document.getElementById("market-price-tag");
|
|
const elPriceTagValue = document.getElementById("market-price-tag-value");
|
|
const elPriceTagTime = document.getElementById("market-price-tag-time");
|
|
const elExLabel = document.getElementById("mkt-exchange-label");
|
|
const elExBadge = document.getElementById("market-exchange-badge");
|
|
const elSymLabel = document.getElementById("mkt-symbol-label");
|
|
const elTfLabel = document.getElementById("mkt-tf-label");
|
|
const elPriceAuto = document.getElementById("market-price-auto");
|
|
|
|
let chart = null;
|
|
let candleSeries = null;
|
|
let volumeSeries = null;
|
|
let priceTick = null;
|
|
let priceAutoScale = true;
|
|
let rangeMarkers = [];
|
|
let currentPriceLine = null;
|
|
let lastCandles = [];
|
|
let candleByTime = {};
|
|
let chartMeta = null;
|
|
let loadToken = 0;
|
|
let marketInited = false;
|
|
let refreshTimer = null;
|
|
let lastViewKey = "";
|
|
let currentTf = "1d";
|
|
let priceTagTimer = null;
|
|
|
|
function fmtVol(v) {
|
|
if (v == null || Number.isNaN(Number(v))) return "-";
|
|
const n = Number(v);
|
|
if (n >= 1e9) return (n / 1e9).toFixed(2) + "B";
|
|
if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
|
|
if (n >= 1e3) return (n / 1e3).toFixed(2) + "K";
|
|
return n.toFixed(2);
|
|
}
|
|
|
|
function fmtPrice(v) {
|
|
if (v == null || Number.isNaN(Number(v))) return "-";
|
|
const n = Number(v);
|
|
if (n === 0) return "0";
|
|
const tick = priceTick;
|
|
if (tick && tick > 0) {
|
|
let decimals = tick >= 1 ? 0 : Math.max(0, Math.min(12, Math.round(-Math.log10(tick))));
|
|
let text = n.toFixed(decimals);
|
|
if (text.indexOf(".") >= 0) text = text.replace(/\.?0+$/, "");
|
|
return text;
|
|
}
|
|
const av = Math.abs(n);
|
|
let d = 8;
|
|
if (av >= 10000) d = 2;
|
|
else if (av >= 100) d = 3;
|
|
else if (av >= 1) d = 4;
|
|
else if (av >= 0.01) d = 6;
|
|
let text = n.toFixed(d);
|
|
if (text.indexOf(".") >= 0) text = text.replace(/\.?0+$/, "");
|
|
return text;
|
|
}
|
|
|
|
function exchangeLabel() {
|
|
if (!elExchange) return "";
|
|
const opt = elExchange.options[elExchange.selectedIndex];
|
|
if (opt && opt.textContent) return opt.textContent.trim();
|
|
return (elExchange.value || "").trim().toUpperCase();
|
|
}
|
|
|
|
function updateExchangeDisplay() {
|
|
const label = exchangeLabel();
|
|
if (elExLabel) elExLabel.textContent = label;
|
|
if (elExBadge) {
|
|
elExBadge.textContent = label;
|
|
elExBadge.setAttribute("aria-hidden", label ? "false" : "true");
|
|
}
|
|
}
|
|
|
|
function updateHeaderLabels(sym, tf) {
|
|
if (elSymLabel) elSymLabel.textContent = sym || "—";
|
|
if (elTfLabel) elTfLabel.textContent = tf || "—";
|
|
updateExchangeDisplay();
|
|
}
|
|
|
|
function fmtAmplitude(bar) {
|
|
if (!bar) return "-";
|
|
const o = Number(bar.open);
|
|
const h = Number(bar.high);
|
|
const l = Number(bar.low);
|
|
if (!o || o <= 0 || !Number.isFinite(h) || !Number.isFinite(l)) return "-";
|
|
return (((h - l) / o) * 100).toFixed(2) + "%";
|
|
}
|
|
|
|
function barRemainMs(tf) {
|
|
const period = TF_MS[tf] || TF_MS["1d"];
|
|
const now = Date.now();
|
|
const barOpen = Math.floor(now / period) * period;
|
|
return Math.max(0, barOpen + period - now);
|
|
}
|
|
|
|
function fmtBarCountdown(ms) {
|
|
const total = Math.max(0, Math.floor(ms / 1000));
|
|
const h = Math.floor(total / 3600);
|
|
const m = Math.floor((total % 3600) / 60);
|
|
const s = total % 60;
|
|
const pad = function (n) {
|
|
return n < 10 ? "0" + n : String(n);
|
|
};
|
|
if (h > 0) return h + ":" + pad(m) + ":" + pad(s);
|
|
return pad(m) + ":" + pad(s);
|
|
}
|
|
|
|
function paintOhlcv(bar) {
|
|
if (!bar) {
|
|
["o", "h", "l", "c", "v", "amp"].forEach(function (k) {
|
|
const el = { o: elO, h: elH, l: elL, c: elC, v: elV, amp: elAmp }[k];
|
|
if (el) el.textContent = "-";
|
|
});
|
|
return;
|
|
}
|
|
if (elO) elO.textContent = fmtPrice(bar.open);
|
|
if (elH) elH.textContent = fmtPrice(bar.high);
|
|
if (elL) elL.textContent = fmtPrice(bar.low);
|
|
if (elC) elC.textContent = fmtPrice(bar.close);
|
|
if (elV) elV.textContent = fmtVol(bar.volume);
|
|
if (elAmp) elAmp.textContent = fmtAmplitude(bar);
|
|
}
|
|
|
|
function latestCandle() {
|
|
return lastCandles.length ? lastCandles[lastCandles.length - 1] : null;
|
|
}
|
|
|
|
function showLatestOhlcv() {
|
|
paintOhlcv(latestCandle());
|
|
updateCurrentPriceLine();
|
|
updatePriceTag();
|
|
}
|
|
|
|
function clearCurrentPriceLine() {
|
|
if (currentPriceLine && candleSeries) {
|
|
try {
|
|
candleSeries.removePriceLine(currentPriceLine);
|
|
} catch (e) {}
|
|
}
|
|
currentPriceLine = null;
|
|
}
|
|
|
|
function updateCurrentPriceLine() {
|
|
clearCurrentPriceLine();
|
|
if (!candleSeries) return;
|
|
const bar = latestCandle();
|
|
if (!bar || bar.close == null) return;
|
|
const up = Number(bar.close) >= Number(bar.open);
|
|
currentPriceLine = candleSeries.createPriceLine({
|
|
price: Number(bar.close),
|
|
color: up ? "#00ff9d" : "#ff4d6d",
|
|
lineWidth: 1,
|
|
lineStyle: 2,
|
|
axisLabelVisible: true,
|
|
title: "现价",
|
|
});
|
|
}
|
|
|
|
function tickLiveClock() {
|
|
const cd = fmtBarCountdown(barRemainMs(currentTf));
|
|
if (elPriceTagTime && elPriceTag && !elPriceTag.classList.contains("hidden")) {
|
|
elPriceTagTime.textContent = cd;
|
|
}
|
|
if (elBarCountdown) elBarCountdown.textContent = "距收盘 " + cd;
|
|
}
|
|
|
|
function updatePriceTag() {
|
|
if (!elPriceTag || !candleSeries || !chart) return;
|
|
tickLiveClock();
|
|
const bar = latestCandle();
|
|
if (!bar || bar.close == null) {
|
|
elPriceTag.classList.add("hidden");
|
|
elPriceTag.setAttribute("aria-hidden", "true");
|
|
return;
|
|
}
|
|
let y = null;
|
|
try {
|
|
y = candleSeries.priceToCoordinate(Number(bar.close));
|
|
} catch (e) {
|
|
y = null;
|
|
}
|
|
const hostH = (elPriceGutter && elPriceGutter.clientHeight) || chartHost.clientHeight || 0;
|
|
if (y == null || y < 8 || y > hostH - 8) {
|
|
elPriceTag.classList.add("hidden");
|
|
elPriceTag.setAttribute("aria-hidden", "true");
|
|
return;
|
|
}
|
|
const up = Number(bar.close) >= Number(bar.open);
|
|
elPriceTag.classList.remove("hidden", "is-up", "is-down");
|
|
elPriceTag.classList.add(up ? "is-up" : "is-down");
|
|
elPriceTag.setAttribute("aria-hidden", "false");
|
|
elPriceTag.style.top = y + "px";
|
|
if (elPriceTagValue) elPriceTagValue.textContent = fmtPrice(bar.close);
|
|
}
|
|
|
|
function startPriceTagTimer() {
|
|
stopPriceTagTimer();
|
|
tickLiveClock();
|
|
priceTagTimer = setInterval(tickLiveClock, 1000);
|
|
}
|
|
|
|
function stopPriceTagTimer() {
|
|
if (priceTagTimer) clearInterval(priceTagTimer);
|
|
priceTagTimer = null;
|
|
}
|
|
|
|
function applyPriceAutoScale() {
|
|
if (!chart) return;
|
|
chart.priceScale("right").applyOptions({ autoScale: priceAutoScale });
|
|
if (elPriceAuto) elPriceAuto.classList.toggle("is-on", priceAutoScale);
|
|
}
|
|
|
|
function indexCandles(candles) {
|
|
candleByTime = {};
|
|
(candles || []).forEach(function (c) {
|
|
if (c && c.time != null) candleByTime[c.time] = c;
|
|
});
|
|
}
|
|
|
|
function candleAtTime(t) {
|
|
if (t == null) return null;
|
|
return candleByTime[t] || null;
|
|
}
|
|
|
|
function buildVolumeData(candles) {
|
|
return (candles || []).map(function (c) {
|
|
const up = Number(c.close) >= Number(c.open);
|
|
return {
|
|
time: c.time,
|
|
value: Number(c.volume) || 0,
|
|
color: up ? "rgba(0, 255, 157, 0.5)" : "rgba(255, 77, 109, 0.5)",
|
|
};
|
|
});
|
|
}
|
|
|
|
function ensureChart() {
|
|
if (chart && candleSeries && volumeSeries) return true;
|
|
if (!window.LightweightCharts) {
|
|
if (elStatus) {
|
|
elStatus.className = "market-status err";
|
|
elStatus.textContent = "图表库加载失败";
|
|
}
|
|
return false;
|
|
}
|
|
chart = LightweightCharts.createChart(chartHost, {
|
|
layout: { background: { color: "#0a1018" }, textColor: "#b8d4e8" },
|
|
grid: {
|
|
vertLines: { visible: false },
|
|
horzLines: { visible: false },
|
|
},
|
|
rightPriceScale: { borderColor: "#2a4058", autoScale: true },
|
|
timeScale: {
|
|
borderColor: "#2a4058",
|
|
timeVisible: true,
|
|
secondsVisible: false,
|
|
rightOffset: RIGHT_OFFSET_BARS,
|
|
},
|
|
crosshair: {
|
|
mode: LightweightCharts.CrosshairMode
|
|
? LightweightCharts.CrosshairMode.Normal
|
|
: 0,
|
|
},
|
|
});
|
|
|
|
const candleOpts = {
|
|
upColor: "#00ff9d",
|
|
downColor: "#ff4d6d",
|
|
borderVisible: false,
|
|
wickUpColor: "#00ff9d",
|
|
wickDownColor: "#ff4d6d",
|
|
lastValueVisible: false,
|
|
priceLineVisible: false,
|
|
};
|
|
|
|
if (typeof chart.addCandlestickSeries === "function") {
|
|
candleSeries = chart.addCandlestickSeries(candleOpts);
|
|
} else if (
|
|
typeof chart.addSeries === "function" &&
|
|
window.LightweightCharts &&
|
|
window.LightweightCharts.CandlestickSeries
|
|
) {
|
|
candleSeries = chart.addSeries(window.LightweightCharts.CandlestickSeries, candleOpts);
|
|
}
|
|
if (!candleSeries) return false;
|
|
|
|
const volOpts = {
|
|
priceFormat: { type: "volume" },
|
|
priceScaleId: "volume",
|
|
lastValueVisible: false,
|
|
};
|
|
if (typeof chart.addHistogramSeries === "function") {
|
|
volumeSeries = chart.addHistogramSeries(volOpts);
|
|
} else if (
|
|
typeof chart.addSeries === "function" &&
|
|
window.LightweightCharts &&
|
|
window.LightweightCharts.HistogramSeries
|
|
) {
|
|
volumeSeries = chart.addSeries(window.LightweightCharts.HistogramSeries, volOpts);
|
|
}
|
|
if (!volumeSeries) return false;
|
|
|
|
chart.priceScale("right").applyOptions({
|
|
scaleMargins: { top: 0.06, bottom: 0.28 },
|
|
});
|
|
chart.priceScale("volume").applyOptions({
|
|
scaleMargins: { top: 0.78, bottom: 0 },
|
|
});
|
|
applyPriceAutoScale();
|
|
|
|
chart.subscribeCrosshairMove(function (param) {
|
|
if (!param || param.time == null) {
|
|
showLatestOhlcv();
|
|
return;
|
|
}
|
|
const bar = candleAtTime(param.time);
|
|
if (!bar) {
|
|
showLatestOhlcv();
|
|
return;
|
|
}
|
|
paintOhlcv(bar);
|
|
});
|
|
|
|
chart.timeScale().subscribeVisibleLogicalRangeChange(function () {
|
|
updateVisibleRangeMarkers();
|
|
updatePriceTag();
|
|
});
|
|
|
|
window.addEventListener("resize", function () {
|
|
if (!chart) return;
|
|
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
|
|
updatePriceTag();
|
|
});
|
|
chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight });
|
|
return true;
|
|
}
|
|
|
|
function clearMarkers() {
|
|
rangeMarkers.forEach(function (m) {
|
|
try {
|
|
candleSeries.removePriceLine(m);
|
|
} catch (e) {}
|
|
});
|
|
rangeMarkers = [];
|
|
}
|
|
|
|
function viewKey(exKey, sym, tf) {
|
|
return (exKey || "") + "|" + (sym || "") + "|" + (tf || "");
|
|
}
|
|
|
|
function applyDefaultVisibleRange() {
|
|
if (!chart || !lastCandles.length) return;
|
|
const n = lastCandles.length;
|
|
const visible = Math.min(DEFAULT_VISIBLE_BARS, n);
|
|
const from = Math.max(0, n - visible);
|
|
const to = n - 1;
|
|
chart.timeScale().applyOptions({ rightOffset: RIGHT_OFFSET_BARS });
|
|
chart.timeScale().setVisibleLogicalRange({ from: from, to: to });
|
|
}
|
|
|
|
function updateVisibleRangeMarkers() {
|
|
clearMarkers();
|
|
if (!candleSeries || !chart || !lastCandles.length) return;
|
|
|
|
const range = chart.timeScale().getVisibleLogicalRange();
|
|
if (!range) return;
|
|
|
|
const from = Math.max(0, Math.floor(range.from));
|
|
const to = Math.min(lastCandles.length - 1, Math.ceil(range.to));
|
|
if (to < from) return;
|
|
|
|
let hi = null;
|
|
let lo = null;
|
|
for (let i = from; i <= to; i++) {
|
|
const c = lastCandles[i];
|
|
if (!c) continue;
|
|
if (!hi || c.high > hi.high) hi = c;
|
|
if (!lo || c.low < lo.low) lo = c;
|
|
}
|
|
if (!hi || !lo) return;
|
|
|
|
rangeMarkers.push(
|
|
candleSeries.createPriceLine({
|
|
price: Number(hi.high),
|
|
color: "#ffb84d",
|
|
lineWidth: 1,
|
|
lineStyle: 2,
|
|
axisLabelVisible: true,
|
|
title: "高点",
|
|
})
|
|
);
|
|
rangeMarkers.push(
|
|
candleSeries.createPriceLine({
|
|
price: Number(lo.low),
|
|
color: "#4cd97f",
|
|
lineWidth: 1,
|
|
lineStyle: 2,
|
|
axisLabelVisible: true,
|
|
title: "低点",
|
|
})
|
|
);
|
|
}
|
|
|
|
function readQuery() {
|
|
const qs = new URLSearchParams(window.location.search);
|
|
const ex = qs.get("exchange_key") || qs.get("exchange") || "";
|
|
const sym = qs.get("symbol") || "";
|
|
const tf = qs.get("timeframe") || "";
|
|
if (ex && elExchange) elExchange.value = ex;
|
|
if (sym && elSymbol) elSymbol.value = sym;
|
|
if (tf && elTf) elTf.value = tf;
|
|
}
|
|
|
|
function applyDefaults() {
|
|
if (elSymbol && !elSymbol.value.trim()) elSymbol.value = "BTC/USDT";
|
|
if (elTf && !elTf.value) elTf.value = "1d";
|
|
}
|
|
|
|
function startAutoRefresh() {
|
|
stopAutoRefresh();
|
|
refreshTimer = setInterval(function () {
|
|
const page = document.getElementById("page-market");
|
|
if (!page || page.classList.contains("hidden")) return;
|
|
loadChart(false, { autoTick: true });
|
|
}, AUTO_REFRESH_MS);
|
|
}
|
|
|
|
function stopAutoRefresh() {
|
|
if (refreshTimer) clearInterval(refreshTimer);
|
|
refreshTimer = null;
|
|
}
|
|
|
|
async function loadMeta() {
|
|
const r = await fetch("/api/chart/meta", { credentials: "same-origin" });
|
|
chartMeta = await r.json();
|
|
if (!elExchange || !chartMeta.exchanges) return;
|
|
elExchange.innerHTML = "";
|
|
chartMeta.exchanges.forEach(function (ex) {
|
|
const opt = document.createElement("option");
|
|
opt.value = ex.key || ex.id;
|
|
opt.textContent = ex.name || ex.key;
|
|
elExchange.appendChild(opt);
|
|
});
|
|
readQuery();
|
|
applyDefaults();
|
|
updateExchangeDisplay();
|
|
}
|
|
|
|
async function loadChart(force, options) {
|
|
options = options || {};
|
|
const autoTick = !!options.autoTick;
|
|
if (!ensureChart()) return;
|
|
const exKey = (elExchange && elExchange.value) || "";
|
|
const sym = (elSymbol && elSymbol.value.trim().toUpperCase()) || "";
|
|
const tf = (elTf && elTf.value) || "1d";
|
|
currentTf = tf;
|
|
if (!exKey || !sym) {
|
|
if (elStatus) {
|
|
elStatus.className = "market-status err";
|
|
elStatus.textContent = "请选择交易所并输入币种";
|
|
}
|
|
return;
|
|
}
|
|
const myToken = ++loadToken;
|
|
const vKey = viewKey(exKey, sym, tf);
|
|
const resetView = !!force || !autoTick || vKey !== lastViewKey;
|
|
let savedRange = null;
|
|
if (!resetView && chart) {
|
|
savedRange = chart.timeScale().getVisibleLogicalRange();
|
|
}
|
|
if (!autoTick && elStatus) {
|
|
elStatus.className = "market-status";
|
|
elStatus.textContent = "加载中…";
|
|
}
|
|
updateHeaderLabels(sym, tf);
|
|
|
|
const qs = new URLSearchParams({
|
|
exchange_key: exKey,
|
|
symbol: sym,
|
|
timeframe: tf,
|
|
});
|
|
if (force) qs.set("refresh", "1");
|
|
|
|
try {
|
|
const r = await fetch("/api/chart/ohlcv?" + qs.toString(), { credentials: "same-origin" });
|
|
const data = await r.json();
|
|
if (myToken !== loadToken) return;
|
|
if (!r.ok) {
|
|
throw new Error(data.detail || data.msg || "请求失败");
|
|
}
|
|
if (!data.ok || !data.candles || !data.candles.length) {
|
|
throw new Error(data.msg || "无 K 线");
|
|
}
|
|
|
|
priceTick = data.price_tick;
|
|
lastCandles = data.candles;
|
|
indexCandles(lastCandles);
|
|
candleSeries.setData(lastCandles);
|
|
volumeSeries.setData(buildVolumeData(lastCandles));
|
|
if (resetView) {
|
|
lastViewKey = vKey;
|
|
applyDefaultVisibleRange();
|
|
} else if (savedRange) {
|
|
chart.timeScale().setVisibleLogicalRange(savedRange);
|
|
}
|
|
applyPriceAutoScale();
|
|
updateVisibleRangeMarkers();
|
|
showLatestOhlcv();
|
|
|
|
const limit = data.limit || lastCandles.length;
|
|
let hint =
|
|
"已加载 " +
|
|
data.candles.length +
|
|
" 根(目标 " +
|
|
limit +
|
|
")· 库 " +
|
|
(data.from_cache || 0) +
|
|
" / 新拉 " +
|
|
(data.fetched || 0) +
|
|
")· 每 5s 刷新";
|
|
if (data.stale && data.stale_message) {
|
|
hint += " · 缓存:" + data.stale_message;
|
|
}
|
|
if (elStatus) {
|
|
elStatus.className = data.stale ? "market-status warn" : "market-status";
|
|
elStatus.textContent = hint;
|
|
}
|
|
if (elUpdated) elUpdated.textContent = "数据 " + (data.updated_at || "--");
|
|
tickLiveClock();
|
|
} catch (e) {
|
|
if (myToken !== loadToken) return;
|
|
if (elStatus) {
|
|
elStatus.className = "market-status err";
|
|
elStatus.textContent = String(e.message || e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function bind() {
|
|
if (elRefresh) {
|
|
elRefresh.addEventListener("click", function () {
|
|
loadChart(true);
|
|
});
|
|
}
|
|
if (elTf) {
|
|
elTf.addEventListener("change", function () {
|
|
currentTf = (elTf && elTf.value) || "1d";
|
|
tickLiveClock();
|
|
loadChart(false);
|
|
});
|
|
}
|
|
if (elExchange) {
|
|
elExchange.addEventListener("change", function () {
|
|
updateExchangeDisplay();
|
|
loadChart(false);
|
|
});
|
|
}
|
|
if (elSymbol) {
|
|
elSymbol.addEventListener("keydown", function (e) {
|
|
if (e.key === "Enter") loadChart(false);
|
|
});
|
|
}
|
|
const btnLoad = document.getElementById("market-load");
|
|
if (btnLoad) {
|
|
btnLoad.addEventListener("click", function () {
|
|
loadChart(false);
|
|
});
|
|
}
|
|
if (elPriceAuto) {
|
|
elPriceAuto.addEventListener("click", function () {
|
|
priceAutoScale = !priceAutoScale;
|
|
applyPriceAutoScale();
|
|
});
|
|
}
|
|
}
|
|
|
|
window.hubMarketChart = {
|
|
init: async function () {
|
|
if (!marketInited) {
|
|
marketInited = true;
|
|
await loadMeta();
|
|
bind();
|
|
}
|
|
startAutoRefresh();
|
|
await loadChart(false);
|
|
startPriceTagTimer();
|
|
},
|
|
reload: function (force) {
|
|
loadChart(!!force);
|
|
},
|
|
startAutoRefresh: startAutoRefresh,
|
|
stopAutoRefresh: stopAutoRefresh,
|
|
stopPriceTagTimer: stopPriceTagTimer,
|
|
};
|
|
|
|
if (
|
|
document.getElementById("page-market") &&
|
|
!document.getElementById("page-market").classList.contains("hidden")
|
|
) {
|
|
window.hubMarketChart.init();
|
|
}
|
|
})();
|