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:
parent
70e58ac0a7
commit
8e0545cc6f
|
|
@ -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,21 +103,46 @@ 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
|
||||
|
|
@ -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__':
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue