fix(hub): align Binance chart ticks and improve monitor mark price

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-03 22:33:59 +08:00
parent c1fda1e7d5
commit 2a9602610e
7 changed files with 275 additions and 30 deletions
+36
View File
@@ -416,9 +416,43 @@ def _position_mark_price(p: dict[str, Any]) -> float | None:
for key in (
p.get("markPrice"),
p.get("mark_price"),
p.get("mark"),
info.get("markPx"),
info.get("mark_price"),
info.get("markPrice"),
info.get("last"),
info.get("lastPrice"),
):
px = _finite_or_none(key)
if px is not None and px > 0:
return px
contracts = _position_contracts(p)
if abs(contracts) >= 1e-12:
notional = _finite_or_none(p.get("notional"))
if notional is not None and abs(notional) > 0:
return abs(notional) / abs(contracts)
return None
def _ticker_mark_price(ex: Any, symbol: str) -> float | None:
"""持仓行无 mark 时,用 ticker 补标记价(last/mark)。"""
sym = (symbol or "").strip()
if not sym:
return None
try:
t = ex.fetch_ticker(sym)
except Exception:
return None
if not isinstance(t, dict):
return None
info = t.get("info") if isinstance(t.get("info"), dict) else {}
for key in (
t.get("mark"),
t.get("last"),
t.get("close"),
info.get("markPrice"),
info.get("mark_price"),
info.get("markPx"),
):
px = _finite_or_none(key)
if px is not None and px > 0:
@@ -602,6 +636,8 @@ def _status_inner(x_control_token: str | None) -> Any:
entry_f = _position_entry_price(p)
_, entry_fmt, price_tick = _position_price_fmt(ex, sym, entry_f)
mark_f = _position_mark_price(p)
if mark_f is None and sym:
mark_f = _ticker_mark_price(ex, sym)
_, mark_fmt, mark_tick = _position_price_fmt(ex, sym, mark_f)
if price_tick is None and mark_tick is not None:
price_tick = mark_tick
+44 -19
View File
@@ -949,9 +949,11 @@
}
specs.forEach(function (s) {
if (s.price == null || !Number.isFinite(Number(s.price))) return;
const px = roundToTick(s.price);
if (px == null || !Number.isFinite(Number(px))) return;
positionLines.push(
candleSeries.createPriceLine({
price: Number(s.price),
price: Number(px),
color: s.color,
lineWidth: 1,
lineStyle: 2,
@@ -1009,30 +1011,52 @@
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 };
const SAFE_PRICE_FORMAT = { type: "price", precision: 4, minMove: 0.0001 };
function tickToPriceFormat(tick) {
try {
if (tick == null || !Number.isFinite(Number(tick)) || Number(tick) <= 0) {
return { type: "price", precision: 2, minMove: 0.01, base: 100 };
return { type: "price", precision: 2, minMove: 0.01 };
}
const raw = Number(tick);
if (raw >= 1) {
return { type: "price", precision: 0, minMove: 1 };
}
let prec = decimalsFromTick(raw);
const minMove = Number(tick);
let prec = decimalsFromTick(minMove);
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 };
prec = Math.min(12, Math.max(0, Math.floor(prec)));
return { type: "price", precision: prec, minMove: minMove };
} catch (e) {
return SAFE_PRICE_FORMAT;
}
}
function roundToTick(v) {
if (v == null || Number.isNaN(Number(v))) return v;
const n = Number(v);
const tick = priceTick;
if (tick == null || !Number.isFinite(Number(tick)) || Number(tick) <= 0) return n;
const t = Number(tick);
const rounded = Math.round(n / t) * t;
const dec = decimalsFromTick(t);
if (dec == null) return rounded;
return parseFloat(rounded.toFixed(dec));
}
function alignCandlesToTick(candles) {
if (!Array.isArray(candles) || !candles.length) return candles || [];
if (priceTick == null || !Number.isFinite(Number(priceTick)) || Number(priceTick) <= 0) {
return candles;
}
return candles.map(function (c) {
return {
time: c.time,
open: roundToTick(c.open),
high: roundToTick(c.high),
low: roundToTick(c.low),
close: roundToTick(c.close),
volume: c.volume,
};
});
}
function applyPriceFormatToSeries(series, pf) {
if (!series || !series.applyOptions) return;
try {
@@ -1065,7 +1089,8 @@
function fmtPrice(v) {
if (v == null || Number.isNaN(Number(v))) return "-";
const n = Number(v);
const aligned = roundToTick(v);
const n = Number(aligned);
if (n === 0) return "0";
const dec = decimalsFromTick(priceTick);
if (dec != null) return n.toFixed(dec);
@@ -1172,7 +1197,7 @@
if (!bar || bar.close == null) return;
const up = Number(bar.close) >= Number(bar.open);
currentPriceLine = candleSeries.createPriceLine({
price: Number(bar.close),
price: Number(roundToTick(bar.close)),
color: up ? "#00ff9d" : "#ff4d6d",
lineWidth: 1,
lineStyle: 2,
@@ -1416,7 +1441,7 @@
rangeMarkers.push(
candleSeries.createPriceLine({
price: Number(hi.high),
price: Number(roundToTick(hi.high)),
color: "#ffb84d",
lineWidth: 1,
lineStyle: 2,
@@ -1426,7 +1451,7 @@
);
rangeMarkers.push(
candleSeries.createPriceLine({
price: Number(lo.low),
price: Number(roundToTick(lo.low)),
color: "#4cd97f",
lineWidth: 1,
lineStyle: 2,
@@ -1535,7 +1560,7 @@
priceTick = null;
applyChartPriceFormat();
}
lastCandles = data.candles;
lastCandles = alignCandlesToTick(data.candles);
indexCandles(lastCandles);
candleSeries.setData(lastCandles);
volumeSeries.setData(buildVolumeData(lastCandles));
+2 -2
View File
@@ -244,7 +244,7 @@
<div id="toast"></div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script src="/assets/chart.js?v=20260528-hub-no-30m"></script>
<script src="/assets/app.js?v=20260603-hub-mark-price"></script>
<script src="/assets/chart.js?v=20260603-hub-binance-tick"></script>
<script src="/assets/app.js?v=20260604-hub-mark-price2"></script>
</body>
</html>