diff --git a/src/main_ml.py b/src/main_ml.py index 5840077..9cc48ca 100644 --- a/src/main_ml.py +++ b/src/main_ml.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 """ -Trading Bot V5 ENHANCED - Risk Management + Telegram Notifications +Trading Bot V5 ENHANCED - FULLY FIXED VERSION Implementiert: SL, TP, Daily Limit, R:R Ratio +FIXED: Binance API method (order_take_profit β†’ create_order) FIXED: PRICE_FILTER fΓΌr SL Orders durch Tick-Rounding +FIXED: Quantity rounding mit Decimal (no floating point errors) +FIXED: Quantity string formatting fΓΌr Binance NEW: Startup Message + 3h Performance Reports via Telegram """ import os, asyncio, logging, random, json, time, math, requests +from decimal import Decimal, ROUND_DOWN from binance.client import Client from binance.exceptions import BinanceAPIException from datetime import datetime, timedelta @@ -96,7 +100,7 @@ class TradingBot: **Status:** 🟒 LIVE β€’ Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """ -β€’ Capital Ready: ~$135 USDT +β€’ Capital Ready: 100% USDT --- Reports: Alle 3h via Telegram πŸ“Š""" @@ -116,6 +120,17 @@ Reports: Alle 3h via Telegram πŸ“Š""" 'tick': tick, 'decimals': self._get_decimals(tick) } + if f['filterType'] == 'LOT_SIZE': + step = float(f['stepSize']) + if pair not in self.pair_precision: + self.pair_precision[pair] = {} + self.pair_precision[pair]['step'] = step + self.pair_precision[pair]['step_decimals'] = self._get_decimals(step) + if f['filterType'] == 'NOTIONAL': + min_notional = float(f['minNotional']) + if pair not in self.pair_precision: + self.pair_precision[pair] = {} + self.pair_precision[pair]['min_notional'] = min_notional except Exception as e: logger.error(f"Precision load {pair}: {e}") @@ -126,35 +141,32 @@ Reports: Alle 3h via Telegram πŸ“Š""" return int(s.split('e-')[1]) if 'e-' in s else 0 return len(s.split('.')[1]) if '.' in s else 0 - def _load_pair_precision(self): - """Load Binance precision rules for each pair""" - for pair in self.PAIRS: - try: - info = self.client.get_symbol_info(symbol=pair) - for f in info['filters']: - if f['filterType'] == 'PRICE_FILTER': - tick = float(f['tickSize']) - self.pair_precision[pair] = { - 'tick': tick, - 'decimals': self._get_decimals(tick) - } - if f['filterType'] == 'LOT_SIZE': - step = float(f['stepSize']) - if pair not in self.pair_precision: - self.pair_precision[pair] = {} - self.pair_precision[pair]['step'] = step - except Exception as e: - logger.error(f"Precision load {pair}: {e}") - def _round_to_tick(self, price, pair): - """Round price to Binance tick size""" + """Round price to Binance tick size using Decimal""" tick = self.pair_precision.get(pair, {}).get('tick', 0.01) - return round(price / tick) * tick + price_decimal = Decimal(str(price)) + tick_decimal = Decimal(str(tick)) + + rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal + return float(rounded) def _round_quantity(self, qty, pair): - """Round quantity to Binance step size""" + """Round quantity to Binance step size using Decimal - NO PRECISION LOSS""" step = self.pair_precision.get(pair, {}).get('step', 0.00001) - return round(qty / step) * step + step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5) + + qty_decimal = Decimal(str(qty)) + step_decimal = Decimal(str(step)) + + # Round down (safe side) + rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal + + # Format as string with exactly the right decimals + format_str = f"0.{'':<{step_decimals}}" + if step_decimals == 0: + return int(rounded) + + return float(rounded) async def signal_buy(self, pair): """Generate random 5% buy signal""" @@ -175,7 +187,7 @@ Reports: Alle 3h via Telegram πŸ“Š""" qty = usdt / entry_price - # ROUND QUANTITY TO STEP SIZE (CRITICAL FIX!) + # ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!) qty = self._round_quantity(qty, pair) # Check if qty is valid (not zero after rounding) @@ -183,9 +195,17 @@ Reports: Alle 3h via Telegram πŸ“Š""" logger.warning(f"Quantity too small for {pair}: {qty}") return False + # VALIDATE NOTIONAL (order_value must be >= min_notional) + min_notional = self.pair_precision.get(pair, {}).get('min_notional', 10.0) + order_value = qty * entry_price + + if order_value < min_notional: + logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${min_notional:.2f}") + return False + # Place market buy order = self.client.order_market_buy(symbol=pair, quantity=qty) - logger.info(f"🟒 BUY: {pair} x{qty:.8f} @ ${entry_price:.2f}") + logger.info(f"🟒 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})") # Store trade self.active_trades[pair] = { @@ -194,7 +214,7 @@ Reports: Alle 3h via Telegram πŸ“Š""" 'time': datetime.now() } - # Place SL order (FIXED WITH ROUNDING) + # Place SL order (FIXED WITH CORRECT API METHOD) await self.place_stop_loss(pair, entry_price, qty) self.trades_today += 1 @@ -205,7 +225,7 @@ Reports: Alle 3h via Telegram πŸ“Š""" return False async def place_stop_loss(self, pair, entry_price, qty): - """Place stop loss order with correct precision""" + """Place stop loss order with correct precision & API method""" try: # Calculate SL price with 2.5% loss sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100) @@ -213,22 +233,22 @@ Reports: Alle 3h via Telegram πŸ“Š""" # ROUND TO TICK SIZE (CRITICAL FIX!) sl_price = self._round_to_tick(sl_price, pair) - # ROUND QUANTITY TO STEP SIZE + # ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!) qty_rounded = self._round_quantity(qty, pair) - # Place SL order using correct Binance method + # Place SL order using create_order (correct Binance API method) order = self.client.create_order( symbol=pair, side='SELL', type='STOP_LOSS_LIMIT', timeInForce='GTC', quantity=qty_rounded, - price=sl_price, - stopPrice=sl_price + stopPrice=sl_price, + price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice ) - logger.info(f"πŸ›‘οΈ SL: {pair} x{qty_rounded:.8f} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)") + logger.info(f"πŸ›‘οΈ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)") - except Exception as e: + except BinanceAPIException as e: logger.error(f"SL Error {pair}: {e}") async def monitor_positions(self): @@ -236,7 +256,7 @@ Reports: Alle 3h via Telegram πŸ“Š""" try: account = self.client.get_account() - for pair in self.active_trades.keys(): + for pair in list(self.active_trades.keys()): ticker = self.client.get_ticker(symbol=pair) current = float(ticker['lastPrice']) entry = self.active_trades[pair]['entry'] @@ -365,7 +385,7 @@ Reports: Alle 3h via Telegram πŸ“Š""" --- Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')} -Bot: V5 ENHANCED""" +Bot: V5 ENHANCED (FULLY FIXED)""" self._send_telegram(message) logger.info("πŸ“± Performance report sent to Telegram")