From 8e0545cc6f2f2935d4a7cb284b7d995a333a1a59 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Fri, 10 Jul 2026 16:36:24 +0200 Subject: [PATCH] =?UTF-8?q?CRITICAL=20FIX:=20LOT=5FSIZE=20Filter=20+=20Qua?= =?UTF-8?q?ntity=20Precision=20-=20nutze=20Decimal=20f=C3=BCr=20exakte=20R?= =?UTF-8?q?ounding=20(ROUND=5FDOWN),=20validiere=20Qty=20vor=20BUY=20+=20S?= =?UTF-8?q?ELL=20(2026-07-10=2016:35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main_ml.py | 51 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/main_ml.py b/src/main_ml.py index f8c4cb2..9a2cb87 100644 --- a/src/main_ml.py +++ b/src/main_ml.py @@ -8,6 +8,7 @@ import os import time import logging from datetime import datetime +from decimal import Decimal, ROUND_DOWN from dotenv import load_dotenv from binance.client import Client from binance.exceptions import BinanceAPIException @@ -92,7 +93,7 @@ class TradingBotV03: return None def calculate_valid_quantity(self, symbol, usdt_amount): - """Calculate valid order quantity respecting LOT_SIZE""" + """Berechne korrekte Qty mit Decimal precision für LOT_SIZE""" try: price = self.get_current_price(symbol) if not price: @@ -102,25 +103,50 @@ class TradingBotV03: if not info: return 0 - step_size = 0.00001 # default + # Hole LOT_SIZE filter + lot_size_info = None for filt in info.get('filters', []): if filt['filterType'] == 'LOT_SIZE': - step_size = float(filt['stepSize']) + lot_size_info = filt break - qty = (usdt_amount / price) - qty = int(qty / step_size) * step_size # Round to step_size - notional = qty * price + if not lot_size_info: + return 0 + + step_size = Decimal(lot_size_info.get('stepSize', '0.00001')) + min_qty = Decimal(lot_size_info.get('minQty', '0')) + max_qty = Decimal(lot_size_info.get('maxQty', '10000')) + + # Berechne Qty mit Decimal (kein floating-point Fehler!) + qty_decimal = Decimal(str(usdt_amount)) / Decimal(str(price)) + + # Runde auf step_size (immer abrunden, nie aufrunden) + qty_rounded = (qty_decimal / step_size).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_size + + # Prüfe Min/Max Grenzen + if qty_rounded < min_qty: + logger.debug(f"Qty zu klein: {symbol} {qty_rounded} < {min_qty}") + return 0 + + if qty_rounded > max_qty: + logger.debug(f"Qty zu groß: {symbol} {qty_rounded} > {max_qty}") + qty_rounded = max_qty + + # Konvertiere zu float mit gerader Präzision + qty_float = float(qty_rounded) + notional = qty_float * price if notional < MIN_USDT: logger.debug(f"Order too small: {symbol} ${notional:.2f}") return 0 - return qty + logger.debug(f"Qty valid: {symbol} {qty_float} (step={step_size})") + return qty_float + except Exception as e: logger.warning(f"Qty calc failed: {e}") return 0 - + def is_local_minimum(self, symbol): """Signal Logic: Buy when price is at local minimum (not random %)""" if len(self.price_history[symbol]) < 5: @@ -187,7 +213,9 @@ class TradingBotV03: if pnl_pct >= TAKE_PROFIT_PCT: logger.info(f"TAKE PROFIT: {symbol} +{pnl_pct*100:.2f}% (${pnl_usdt:.2f})") try: - self.client.order_market_sell(symbol=symbol, quantity=qty) + # Validiere Qty vor Verkauf (rund ab für LOT_SIZE) + qty_sell = float(Decimal(str(qty)).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)) + self.client.order_market_sell(symbol=symbol, quantity=qty_sell) del self.active_trades[symbol] except Exception as e: logger.error(f"Sell failed: {e}") @@ -197,7 +225,9 @@ class TradingBotV03: if pnl_pct <= STOP_LOSS_PCT: logger.warning(f"STOP LOSS: {symbol} {pnl_pct*100:.2f}% (${pnl_usdt:.2f})") try: - self.client.order_market_sell(symbol=symbol, quantity=qty) + # Validiere Qty vor Verkauf (rund ab für LOT_SIZE) + qty_sell = float(Decimal(str(qty)).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)) + self.client.order_market_sell(symbol=symbol, quantity=qty_sell) del self.active_trades[symbol] except Exception as e: logger.error(f"Sell failed: {e}") @@ -286,3 +316,4 @@ if __name__ == '__main__': +