CRITICAL FIX: LOT_SIZE Filter + Quantity Precision - nutze Decimal für exakte Rounding (ROUND_DOWN), validiere Qty vor BUY + SELL (2026-07-10 16:35)

This commit is contained in:
Marc Blatter 2026-07-10 16:36:24 +02:00
parent 70e58ac0a7
commit 8e0545cc6f
1 changed files with 41 additions and 10 deletions

View File

@ -8,6 +8,7 @@ import os
import time import time
import logging import logging
from datetime import datetime from datetime import datetime
from decimal import Decimal, ROUND_DOWN
from dotenv import load_dotenv from dotenv import load_dotenv
from binance.client import Client from binance.client import Client
from binance.exceptions import BinanceAPIException from binance.exceptions import BinanceAPIException
@ -92,7 +93,7 @@ class TradingBotV03:
return None return None
def calculate_valid_quantity(self, symbol, usdt_amount): 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: try:
price = self.get_current_price(symbol) price = self.get_current_price(symbol)
if not price: if not price:
@ -102,25 +103,50 @@ class TradingBotV03:
if not info: if not info:
return 0 return 0
step_size = 0.00001 # default # Hole LOT_SIZE filter
lot_size_info = None
for filt in info.get('filters', []): for filt in info.get('filters', []):
if filt['filterType'] == 'LOT_SIZE': if filt['filterType'] == 'LOT_SIZE':
step_size = float(filt['stepSize']) lot_size_info = filt
break break
qty = (usdt_amount / price) if not lot_size_info:
qty = int(qty / step_size) * step_size # Round to step_size return 0
notional = qty * price
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: if notional < MIN_USDT:
logger.debug(f"Order too small: {symbol} ${notional:.2f}") logger.debug(f"Order too small: {symbol} ${notional:.2f}")
return 0 return 0
return qty logger.debug(f"Qty valid: {symbol} {qty_float} (step={step_size})")
return qty_float
except Exception as e: except Exception as e:
logger.warning(f"Qty calc failed: {e}") logger.warning(f"Qty calc failed: {e}")
return 0 return 0
def is_local_minimum(self, symbol): def is_local_minimum(self, symbol):
"""Signal Logic: Buy when price is at local minimum (not random %)""" """Signal Logic: Buy when price is at local minimum (not random %)"""
if len(self.price_history[symbol]) < 5: if len(self.price_history[symbol]) < 5:
@ -187,7 +213,9 @@ class TradingBotV03:
if pnl_pct >= TAKE_PROFIT_PCT: if pnl_pct >= TAKE_PROFIT_PCT:
logger.info(f"TAKE PROFIT: {symbol} +{pnl_pct*100:.2f}% (${pnl_usdt:.2f})") logger.info(f"TAKE PROFIT: {symbol} +{pnl_pct*100:.2f}% (${pnl_usdt:.2f})")
try: 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] del self.active_trades[symbol]
except Exception as e: except Exception as e:
logger.error(f"Sell failed: {e}") logger.error(f"Sell failed: {e}")
@ -197,7 +225,9 @@ class TradingBotV03:
if pnl_pct <= STOP_LOSS_PCT: if pnl_pct <= STOP_LOSS_PCT:
logger.warning(f"STOP LOSS: {symbol} {pnl_pct*100:.2f}% (${pnl_usdt:.2f})") logger.warning(f"STOP LOSS: {symbol} {pnl_pct*100:.2f}% (${pnl_usdt:.2f})")
try: 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] del self.active_trades[symbol]
except Exception as e: except Exception as e:
logger.error(f"Sell failed: {e}") logger.error(f"Sell failed: {e}")
@ -286,3 +316,4 @@ if __name__ == '__main__':