a
This commit is contained in:
parent
71c9a7fb02
commit
ed9256899a
|
|
@ -640,16 +640,28 @@ class BinanceClient:
|
|||
"""
|
||||
# 1. 先检查内存缓存
|
||||
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 缓存读取
|
||||
cache_key = f"symbol_info:{symbol}"
|
||||
cached = await self.redis_cache.get(cache_key)
|
||||
if cached:
|
||||
logger.debug(f"从Redis缓存获取 {symbol} 交易对信息")
|
||||
# 同时更新内存缓存
|
||||
self._symbol_info_cache[symbol] = cached
|
||||
return cached
|
||||
# 兼容旧缓存:早期版本没有 tickSize/pricePrecision,容易触发 -4014/-1111
|
||||
if isinstance(cached, dict) and ("tickSize" not in cached or "pricePrecision" not in cached):
|
||||
logger.info(f"{symbol} symbol_info 缓存缺少 tickSize/pricePrecision,自动刷新一次")
|
||||
else:
|
||||
# 同时更新内存缓存
|
||||
self._symbol_info_cache[symbol] = cached
|
||||
return cached
|
||||
|
||||
# 3. 缓存未命中,调用 API
|
||||
try:
|
||||
|
|
@ -760,6 +772,92 @@ class BinanceClient:
|
|||
|
||||
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
|
||||
def _adjust_price_to_tick(price: float, tick_size: float, side: str) -> float:
|
||||
"""
|
||||
|
|
@ -1006,7 +1104,8 @@ class BinanceClient:
|
|||
'symbol': symbol,
|
||||
'side': side,
|
||||
'type': order_type,
|
||||
'quantity': adjusted_quantity
|
||||
# 关键:quantityPrecision=0 时必须是 "197" 而不是 "197.0",否则会触发 -1111
|
||||
'quantity': self._format_quantity_str(adjusted_quantity, symbol_info)
|
||||
}
|
||||
|
||||
# 处理持仓模式(解决 -4061:position side 与账户设置不匹配)
|
||||
|
|
@ -1049,13 +1148,8 @@ class BinanceClient:
|
|||
raise ValueError("限价单必须指定价格")
|
||||
params = dict(params)
|
||||
params['timeInForce'] = 'GTC'
|
||||
# LIMIT 价格按 tickSize 修正(避免 -4014 / -1111)
|
||||
tick = float(symbol_info.get("tickSize", 0) or 0) if symbol_info else 0.0
|
||||
p2 = self._adjust_price_to_tick(float(price), tick, side)
|
||||
# 用字符串提交,避免浮点造成的精度问题
|
||||
params['price'] = str(p2)
|
||||
# quantity 也转成字符串提交(同样避免浮点精度)
|
||||
params['quantity'] = str(params.get('quantity'))
|
||||
# LIMIT 价格按 tickSize/pricePrecision 修正(避免 -4014 / -1111)
|
||||
params['price'] = self._format_limit_price_str(float(price), symbol_info, side)
|
||||
return await self.client.futures_create_order(**params)
|
||||
|
||||
# 提交订单;若遇到:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user