From ed9256899a5c32f469cff3f67284243fbe79ee42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Mon, 19 Jan 2026 16:07:05 +0800 Subject: [PATCH] a --- trading_system/binance_client.py | 118 +++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 12 deletions(-) diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index fb1d3cd..9484ade 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -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) # 提交订单;若遇到: