fix(hub): align Binance chart ticks and improve monitor mark price
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+73
-8
@@ -85,6 +85,75 @@ def chart_fetch_start_ms(timeframe: str, need: int, now_ms: int | None = None) -
|
||||
return max(0, now - max(1, int(need)) * period)
|
||||
|
||||
|
||||
def _positive_float(value: Any) -> Optional[float]:
|
||||
if value in (None, ""):
|
||||
return None
|
||||
try:
|
||||
v = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return v if v > 0 else None
|
||||
|
||||
|
||||
def _price_tick_from_market_info(info: dict) -> Optional[float]:
|
||||
"""从 market.info 解析 tick(含币安 PRICE_FILTER.filters)。"""
|
||||
for key in ("tickSize", "tickSz", "price_increment", "order_price_round", "quote_increment"):
|
||||
v = _positive_float(info.get(key))
|
||||
if v is not None:
|
||||
return v
|
||||
|
||||
for key in ("pricePrecision", "price_precision"):
|
||||
raw = info.get(key)
|
||||
if raw in (None, ""):
|
||||
continue
|
||||
try:
|
||||
p = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if p >= 1 and abs(p - round(p)) < 1e-9 and p <= 12:
|
||||
return 10 ** (-int(p))
|
||||
if 0 < p < 1:
|
||||
return p
|
||||
|
||||
filters = info.get("filters")
|
||||
if isinstance(filters, list):
|
||||
for f in filters:
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
if str(f.get("filterType") or "").upper() != "PRICE_FILTER":
|
||||
continue
|
||||
v = _positive_float(f.get("tickSize"))
|
||||
if v is not None:
|
||||
return v
|
||||
return None
|
||||
|
||||
|
||||
def round_price_to_tick(value: Any, tick: Optional[float]) -> Optional[float]:
|
||||
"""按交易所 tick 对齐价格(K 线/标记线与坐标轴一致)。"""
|
||||
t = normalize_price_tick(tick)
|
||||
if t is None:
|
||||
return None
|
||||
try:
|
||||
v = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
n = round(v / t) * t
|
||||
d = _decimals_from_tick(t)
|
||||
return float(f"{n:.{d}f}")
|
||||
|
||||
|
||||
def round_ohlcv_bars_to_tick(bars: list[dict[str, Any]], tick: Optional[float]) -> None:
|
||||
t = normalize_price_tick(tick)
|
||||
if t is None:
|
||||
return
|
||||
for b in bars:
|
||||
for key in ("open", "high", "low", "close"):
|
||||
if key in b:
|
||||
rounded = round_price_to_tick(b.get(key), t)
|
||||
if rounded is not None:
|
||||
b[key] = rounded
|
||||
|
||||
|
||||
def price_tick_from_market(exchange, exchange_symbol: str) -> Optional[float]:
|
||||
"""最小价格变动单位(与交易所 tick / price_to_precision 一致)。"""
|
||||
try:
|
||||
@@ -96,14 +165,9 @@ def price_tick_from_market(exchange, exchange_symbol: str) -> Optional[float]:
|
||||
|
||||
info = market.get("info") or {}
|
||||
if isinstance(info, dict):
|
||||
for key in ("tickSize", "tickSz", "price_increment", "order_price_round", "quote_increment"):
|
||||
if info.get(key) not in (None, ""):
|
||||
try:
|
||||
v = float(info[key])
|
||||
if v > 0:
|
||||
return v
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
tick = _price_tick_from_market_info(info)
|
||||
if tick is not None:
|
||||
return tick
|
||||
|
||||
limits = market.get("limits") or {}
|
||||
price_limits = limits.get("price") or {}
|
||||
@@ -434,6 +498,7 @@ def fetch_ohlcv_for_hub(
|
||||
return {"ok": False, "msg": "交易所未返回 K 线"}
|
||||
|
||||
tick = normalize_price_tick(price_tick_from_market(exchange, ex_sym))
|
||||
round_ohlcv_bars_to_tick(merged, tick)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
|
||||
Reference in New Issue
Block a user