fix(hub): 修复币安行情区 unexpected base 价格精度
normalize_price_tick 对齐 tick 为 10^-n;chart.js 使用整数 base 并在 applyOptions 失败时回退安全 priceFormat。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1009,35 +1009,49 @@
|
||||
return Math.max(0, Math.min(12, Math.round(-Math.log10(minMove))));
|
||||
}
|
||||
|
||||
const SAFE_PRICE_FORMAT = { type: "price", precision: 4, minMove: 0.0001, base: 10000 };
|
||||
|
||||
function tickToPriceFormat(tick) {
|
||||
const minMove =
|
||||
tick != null && Number.isFinite(Number(tick)) && Number(tick) > 0 ? Number(tick) : 0.01;
|
||||
const precision = decimalsFromTick(minMove) ?? 2;
|
||||
const fmt = { type: "price", precision: precision, minMove: minMove };
|
||||
// 避免 minMove 浮点导致 lightweight-charts 报 "unexpected base"
|
||||
if (minMove > 0 && minMove < 1) {
|
||||
const inv = 1 / minMove;
|
||||
if (Number.isFinite(inv) && inv >= 1 && inv <= 1e15) {
|
||||
const base = Math.round(inv);
|
||||
if (base > 0 && Math.abs(inv - base) / Math.max(inv, 1) < 1e-6) {
|
||||
fmt.base = base;
|
||||
}
|
||||
try {
|
||||
if (tick == null || !Number.isFinite(Number(tick)) || Number(tick) <= 0) {
|
||||
return { type: "price", precision: 2, minMove: 0.01, base: 100 };
|
||||
}
|
||||
const raw = Number(tick);
|
||||
if (raw >= 1) {
|
||||
return { type: "price", precision: 0, minMove: 1 };
|
||||
}
|
||||
let prec = decimalsFromTick(raw);
|
||||
if (prec == null || prec < 0) prec = 4;
|
||||
prec = Math.min(8, Math.max(0, Math.floor(prec)));
|
||||
const base = Math.pow(10, prec);
|
||||
if (!Number.isFinite(base) || base < 1 || base > 1e12) {
|
||||
return SAFE_PRICE_FORMAT;
|
||||
}
|
||||
return { type: "price", precision: prec, minMove: 1 / base, base: base };
|
||||
} catch (e) {
|
||||
return SAFE_PRICE_FORMAT;
|
||||
}
|
||||
}
|
||||
|
||||
function applyPriceFormatToSeries(series, pf) {
|
||||
if (!series || !series.applyOptions) return;
|
||||
try {
|
||||
series.applyOptions({ priceFormat: pf });
|
||||
} catch (e) {
|
||||
series.applyOptions({ priceFormat: SAFE_PRICE_FORMAT });
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
function applyChartPriceFormat() {
|
||||
const pf = tickToPriceFormat(priceTick);
|
||||
if (candleSeries && candleSeries.applyOptions) {
|
||||
candleSeries.applyOptions({ priceFormat: pf });
|
||||
}
|
||||
if (indSeries.ema21 && indSeries.ema21.applyOptions) {
|
||||
indSeries.ema21.applyOptions({ priceFormat: pf });
|
||||
}
|
||||
if (indSeries.ema55 && indSeries.ema55.applyOptions) {
|
||||
indSeries.ema55.applyOptions({ priceFormat: pf });
|
||||
let pf = SAFE_PRICE_FORMAT;
|
||||
try {
|
||||
pf = tickToPriceFormat(priceTick);
|
||||
} catch (e) {
|
||||
pf = SAFE_PRICE_FORMAT;
|
||||
}
|
||||
applyPriceFormatToSeries(candleSeries, pf);
|
||||
applyPriceFormatToSeries(indSeries.ema21, pf);
|
||||
applyPriceFormatToSeries(indSeries.ema55, pf);
|
||||
if (chart) {
|
||||
chart.applyOptions({
|
||||
localization: {
|
||||
@@ -1288,7 +1302,7 @@
|
||||
wickDownColor: "#ff4d6d",
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
priceFormat: tickToPriceFormat(priceTick),
|
||||
priceFormat: SAFE_PRICE_FORMAT,
|
||||
};
|
||||
|
||||
if (typeof chart.addCandlestickSeries === "function") {
|
||||
@@ -1515,18 +1529,16 @@
|
||||
}
|
||||
|
||||
priceTick = data.price_tick;
|
||||
applyChartPriceFormat();
|
||||
lastCandles = data.candles;
|
||||
indexCandles(lastCandles);
|
||||
try {
|
||||
candleSeries.setData(lastCandles);
|
||||
volumeSeries.setData(buildVolumeData(lastCandles));
|
||||
} catch (setErr) {
|
||||
applyChartPriceFormat();
|
||||
} catch (fmtErr) {
|
||||
priceTick = null;
|
||||
applyChartPriceFormat();
|
||||
candleSeries.setData(lastCandles);
|
||||
volumeSeries.setData(buildVolumeData(lastCandles));
|
||||
}
|
||||
lastCandles = data.candles;
|
||||
indexCandles(lastCandles);
|
||||
candleSeries.setData(lastCandles);
|
||||
volumeSeries.setData(buildVolumeData(lastCandles));
|
||||
applyChartRightGap();
|
||||
if (resetView) {
|
||||
lastViewKey = vKey;
|
||||
@@ -1538,7 +1550,11 @@
|
||||
updateVisibleRangeMarkers();
|
||||
syncPosContextForView(exKey, sym);
|
||||
showLatestOhlcv();
|
||||
updateIndicators();
|
||||
try {
|
||||
updateIndicators();
|
||||
} catch (indErr) {
|
||||
/* 指标序列 priceFormat 异常时不阻断主图 */
|
||||
}
|
||||
scheduleChartResize();
|
||||
|
||||
const limit = data.limit || lastCandles.length;
|
||||
|
||||
Reference in New Issue
Block a user