This commit is contained in:
薇薇安 2026-01-19 16:07:05 +08:00
parent 71c9a7fb02
commit ed9256899a

View File

@ -640,16 +640,28 @@ class BinanceClient:
""" """
# 1. 先检查内存缓存 # 1. 先检查内存缓存
if symbol in self._symbol_info_cache: if symbol in self._symbol_info_cache:
return self._symbol_info_cache[symbol] cached_mem = self._symbol_info_cache[symbol]
# 兼容旧缓存:早期版本没有 tickSize/pricePrecision容易触发 -4014/-1111
if isinstance(cached_mem, dict) and ("tickSize" not in cached_mem or "pricePrecision" not in cached_mem):
try:
self._symbol_info_cache.pop(symbol, None)
except Exception:
pass
else:
return cached_mem
# 2. 从 Redis 缓存读取 # 2. 从 Redis 缓存读取
cache_key = f"symbol_info:{symbol}" cache_key = f"symbol_info:{symbol}"
cached = await self.redis_cache.get(cache_key) cached = await self.redis_cache.get(cache_key)
if cached: if cached:
logger.debug(f"从Redis缓存获取 {symbol} 交易对信息") logger.debug(f"从Redis缓存获取 {symbol} 交易对信息")
# 同时更新内存缓存 # 兼容旧缓存:早期版本没有 tickSize/pricePrecision容易触发 -4014/-1111
self._symbol_info_cache[symbol] = cached if isinstance(cached, dict) and ("tickSize" not in cached or "pricePrecision" not in cached):
return cached logger.info(f"{symbol} symbol_info 缓存缺少 tickSize/pricePrecision自动刷新一次")
else:
# 同时更新内存缓存
self._symbol_info_cache[symbol] = cached
return cached
# 3. 缓存未命中,调用 API # 3. 缓存未命中,调用 API
try: try:
@ -760,6 +772,92 @@ class BinanceClient:
return adjusted return adjusted
@staticmethod
def _format_decimal_str(d) -> str:
try:
s = format(d, 'f')
except Exception:
s = str(d)
if '.' in s:
s = s.rstrip('0').rstrip('.')
return s
@staticmethod
def _format_quantity_str(quantity: float, symbol_info: Optional[Dict]) -> str:
"""
把数量格式化为币安可接受的字符串避免 quantityPrecision=0 时发送 "197.0" 导致 -1111
"""
try:
from decimal import Decimal, ROUND_DOWN
except Exception:
# fallback尽量去掉浮点尾巴
q = float(quantity or 0)
if not symbol_info:
return str(q)
qp = int(symbol_info.get("quantityPrecision", 8) or 8)
if qp <= 0:
return str(int(q))
return str(round(q, qp))
qp = 8
try:
qp = int(symbol_info.get("quantityPrecision", 8) or 8) if symbol_info else 8
except Exception:
qp = 8
qd = Decimal(str(quantity))
if qp <= 0:
q2 = qd.to_integral_value(rounding=ROUND_DOWN)
return BinanceClient._format_decimal_str(q2)
q2 = qd.quantize(Decimal(f"1e-{qp}"), rounding=ROUND_DOWN)
return BinanceClient._format_decimal_str(q2)
@staticmethod
def _format_limit_price_str(price: float, symbol_info: Optional[Dict], side: str) -> str:
"""
LIMIT 价格格式化为币安可接受的字符串tickSize/pricePrecision 对齐避免
-4014 Price not increased by tick size
-1111 Precision is over the maximum defined for this asset
"""
try:
from decimal import Decimal, ROUND_DOWN, ROUND_UP
except Exception:
return str(round(float(price), 8))
tick = 0.0
pp = 8
try:
tick = float(symbol_info.get("tickSize", 0) or 0) if symbol_info else 0.0
except Exception:
tick = 0.0
try:
pp = int(symbol_info.get("pricePrecision", 8) or 8) if symbol_info else 8
except Exception:
pp = 8
p = Decimal(str(price))
rounding = ROUND_DOWN if (side or "").upper() == "BUY" else ROUND_UP
# 1) tickSize 优先(最严格)
try:
t = Decimal(str(tick))
if t > 0:
q = p / t
q2 = q.to_integral_value(rounding=rounding)
p2 = q2 * t
return BinanceClient._format_decimal_str(p2)
except Exception:
pass
# 2) 没有 tickSize 时,用 pricePrecision 兜底
try:
if pp <= 0:
return BinanceClient._format_decimal_str(p.to_integral_value(rounding=rounding))
p2 = p.quantize(Decimal(f"1e-{pp}"), rounding=rounding)
return BinanceClient._format_decimal_str(p2)
except Exception:
return BinanceClient._format_decimal_str(p)
@staticmethod @staticmethod
def _adjust_price_to_tick(price: float, tick_size: float, side: str) -> float: def _adjust_price_to_tick(price: float, tick_size: float, side: str) -> float:
""" """
@ -1006,7 +1104,8 @@ class BinanceClient:
'symbol': symbol, 'symbol': symbol,
'side': side, 'side': side,
'type': order_type, 'type': order_type,
'quantity': adjusted_quantity # 关键quantityPrecision=0 时必须是 "197" 而不是 "197.0",否则会触发 -1111
'quantity': self._format_quantity_str(adjusted_quantity, symbol_info)
} }
# 处理持仓模式(解决 -4061position side 与账户设置不匹配) # 处理持仓模式(解决 -4061position side 与账户设置不匹配)
@ -1049,13 +1148,8 @@ class BinanceClient:
raise ValueError("限价单必须指定价格") raise ValueError("限价单必须指定价格")
params = dict(params) params = dict(params)
params['timeInForce'] = 'GTC' params['timeInForce'] = 'GTC'
# LIMIT 价格按 tickSize 修正(避免 -4014 / -1111 # LIMIT 价格按 tickSize/pricePrecision 修正(避免 -4014 / -1111
tick = float(symbol_info.get("tickSize", 0) or 0) if symbol_info else 0.0 params['price'] = self._format_limit_price_str(float(price), symbol_info, side)
p2 = self._adjust_price_to_tick(float(price), tick, side)
# 用字符串提交,避免浮点造成的精度问题
params['price'] = str(p2)
# quantity 也转成字符串提交(同样避免浮点精度)
params['quantity'] = str(params.get('quantity'))
return await self.client.futures_create_order(**params) return await self.client.futures_create_order(**params)
# 提交订单;若遇到: # 提交订单;若遇到: