#!/usr/bin/env python3 """ Trading Bot V5 ENHANCED - Risk Management FIXED Implementiert: SL (mit korrekter Precision), TP, Daily Limit, R:R Ratio FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding """ import os, asyncio, logging, random, json, time, math from binance.client import Client from binance.exceptions import BinanceAPIException from datetime import datetime, timedelta # Logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Load env env = {} with open('/home/marc/bot-deploy/.env') as f: for line in f: k,_,v = line.partition('=') env[k.strip()] = v.strip() class TradingBot: def __init__(self): self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE')) self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'] self.SIGNAL_THRESHOLD = 5 # 5% random signal self.INVESTMENT_PERCENT = 25 # 25% per trade self.STOP_LOSS_PERCENT = 2.5 # -2.5% self.TAKE_PROFIT_PERCENT = 3.0 # +3% self.DAILY_LOSS_LIMIT = -5 # -5% max self.active_trades = {} self.daily_pnl = 0 self.paused = False # Precision cache self.pair_precision = {} self._load_pair_precision() logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)") 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) } except Exception as e: logger.error(f"Precision load {pair}: {e}") def _get_decimals(self, tick): """Get decimal places from tick size""" s = str(tick) if 'e' in s: return int(s.split('e-')[1]) if 'e-' in s else 0 return len(s.split('.')[1]) if '.' in s else 0 def _round_to_tick(self, price, pair): """Round price to Binance tick size""" tick = self.pair_precision.get(pair, {}).get('tick', 0.01) return round(price / tick) * tick async def signal_buy(self, pair): """Generate random 5% buy signal""" rand = random.randint(1, 100) return rand <= self.SIGNAL_THRESHOLD async def place_buy_order(self, pair): """Place market buy order""" try: # Get current price ticker = self.client.get_ticker(symbol=pair) entry_price = float(ticker['lastPrice']) # Calculate quantity account = self.client.get_account() usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0) usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100) qty = usdt / entry_price # Place market buy order = self.client.order_market_buy(symbol=pair, quantity=qty) logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${entry_price:.2f}") # Store trade self.active_trades[pair] = { 'entry': entry_price, 'qty': qty, 'time': datetime.now() } # Place SL order (FIXED WITH ROUNDING) await self.place_stop_loss(pair, entry_price, qty) return True except Exception as e: logger.error(f"Buy Error {pair}: {e}") return False async def place_stop_loss(self, pair, entry_price, qty): """Place stop loss order with correct precision""" try: # Calculate SL price with 2.5% loss sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100) # ROUND TO TICK SIZE (CRITICAL FIX!) sl_price = self._round_to_tick(sl_price, pair) # Place SL order order = self.client.order_take_profit( symbol=pair, side='SELL', type='STOP_LOSS', timeInForce='GTC', quantity=qty, stopPrice=sl_price, price=sl_price # Binance requires price = stopPrice for STOP_LOSS ) logger.info(f"🛡️ SL: {pair} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)") except BinanceAPIException as e: logger.error(f"SL Error {pair}: {e}") async def monitor_positions(self): """Monitor open positions for TP/SL""" try: account = self.client.get_account() for pair in self.active_trades.keys(): ticker = self.client.get_ticker(symbol=pair) current = float(ticker['lastPrice']) entry = self.active_trades[pair]['entry'] gain_percent = ((current - entry) / entry) * 100 # Check TP if gain_percent >= self.TAKE_PROFIT_PERCENT: await self.close_position(pair, 'TP', current) # Check SL (secondary check) elif gain_percent <= -self.STOP_LOSS_PERCENT: await self.close_position(pair, 'SL', current) except Exception as e: logger.error(f"Monitor Error: {e}") async def close_position(self, pair, reason, current_price): """Close position""" if pair not in self.active_trades: return qty = self.active_trades[pair]['qty'] entry = self.active_trades[pair]['entry'] pnl = (current_price - entry) * qty logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}") del self.active_trades[pair] self.daily_pnl += pnl # Check daily loss limit if self.daily_pnl <= self.DAILY_LOSS_LIMIT: logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}") self.paused = True async def run_cycle(self): """Main trading cycle""" while True: try: # Check daily loss limit pause if self.paused: logger.info("⏸️ Bot PAUSED (daily loss limit reached)") await asyncio.sleep(60) continue # Signal generation for pair in self.PAIRS: if pair not in self.active_trades and await self.signal_buy(pair): await self.place_buy_order(pair) # Monitor positions await self.monitor_positions() await asyncio.sleep(5) except Exception as e: logger.error(f"Cycle Error: {e}") await asyncio.sleep(5) async def main(): bot = TradingBot() await bot.run_cycle() if __name__ == '__main__': asyncio.run(main())