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 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__':
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue