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__': +