+✅ All prices updated every API call
+✅ Matches Binance Spot portfolio exactly
+
diff --git a/src/main_ml_enhanced.py b/src/main_ml_enhanced.py new file mode 100644 index 0000000..d44a05b --- /dev/null +++ b/src/main_ml_enhanced.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +Trading Bot V5 ENHANCED - Mit kritischen Risk Management Fixes +Implementiert: SL, TP Anpassung, Daily Limit, R:R Ratio +""" +import os, asyncio, logging, random, json, time +from datetime import datetime, timedelta +from binance.client import Client +from binance.exceptions import BinanceAPIException + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# Load config +env = {} +with open('/home/marc/bot-deploy/.env') as f: + for line in f: + k, _, v = line.partition('=') + env[k.strip()] = v.strip() + +class TradingBotV5Enhanced: + def __init__(self): + self.binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE')) + self.state_file = '/home/marc/bot-deploy/trades.json' + self.load_state() + + # NEW: Risk Management Settings + self.STOP_LOSS_PERCENT = 2.5 # 2.5% SL (-2.5%) + self.TAKE_PROFIT_PERCENT = 3.0 # 3.0% TP (+3%) - was +1% + self.DAILY_LOSS_LIMIT = 5.0 # Max -5% daily + self.MIN_RISK_REWARD = 1.5 # Min R:R ratio + self.MAX_POSITION_PERCENT = 25 # Max 25% per trade + + logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)") + + def load_state(self): + if os.path.exists(self.state_file): + with open(self.state_file) as f: + self.state = json.load(f) + else: + self.state = {'current': {}, 'completed': [], 'daily_start_balance': 0} + + def save_state(self): + with open(self.state_file, 'w') as f: + json.dump(self.state, f, indent=2) + + def check_and_place_sl_orders(self, pair, qty, entry_price): + """ + NEW: Automatically place Stop Loss orders for existing positions + SL = Entry - 2.5% + """ + sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100) + + try: + # Check if already has SL order + orders = self.binance.get_open_orders(symbol=pair) + has_sl = any(o['side'] == 'SELL' and float(o['price']) < entry_price for o in orders) + + if not has_sl: + # Place SL order + order = self.binance.order_limit_sell( + symbol=pair, + quantity=qty, + price=round(sl_price, 8) + ) + logger.info(f"🛡️ Stop Loss set: {pair} {qty} @ ${sl_price:.4f}") + return True + except Exception as e: + logger.error(f"SL Error {pair}: {e}") + + return False + + def place_buy(self, pair): + """Place market buy with Risk Management checks""" + try: + # Get balance + balance = self.binance.get_account() + usdt_free = float([a['free'] for a in balance['balances'] if a['asset'] == 'USDT'][0]) + + # NEW: Daily loss check + daily_loss = self.calculate_daily_loss() + if daily_loss <= -self.DAILY_LOSS_LIMIT: + logger.warning(f"⛔ Daily loss limit hit: {daily_loss:.2f}% (limit: -{self.DAILY_LOSS_LIMIT}%)") + return None + + # Calculate position size (25% of USDT) + qty_usdt = usdt_free * (self.MAX_POSITION_PERCENT / 100) + + if qty_usdt < 10: # Binance minimum + return None + + # Get current price + ticker = self.binance.get_symbol_info(pair) + price = float(self.binance.get_ticker(symbol=pair)['lastPrice']) + + # Calculate quantity with LOT_SIZE filter + lot_filter = next(f for f in ticker['filters'] if f['filterType'] == 'LOT_SIZE') + step_size = float(lot_filter['stepSize']) + qty = float(int(qty_usdt / price / step_size) * step_size) + + if qty < float(lot_filter['minQty']): + return None + + # Place market buy + order = self.binance.order_market_buy(symbol=pair, quantity=qty) + logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${price:.4f}") + + # NEW: Auto-place Stop Loss + self.check_and_place_sl_orders(pair, qty, price) + + return order + + except Exception as e: + logger.error(f"Buy Error {pair}: {e}") + return None + + def check_take_profit(self): + """NEW: Check and close at +3% TP with SL protection""" + try: + balance = self.binance.get_account() + + for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']: + ticker = self.binance.get_ticker(symbol=pair) + current_price = float(ticker['lastPrice']) + + # Check if we have open trade + if pair in self.state['current']: + entry_price = self.state['current'][pair]['buy_price'] + gain_percent = (current_price - entry_price) / entry_price * 100 + + # TP at +3% + if gain_percent >= self.TAKE_PROFIT_PERCENT: + qty = self.state['current'][pair]['qty'] + try: + order = self.binance.order_market_sell(symbol=pair, quantity=qty) + profit_usd = (current_price - entry_price) * qty + logger.info(f"💰 TP HIT: {pair} +{gain_percent:.2f}% = ${profit_usd:.2f}") + + # Record completion + self.state['completed'].append({ + 'pair': pair, + 'qty': qty, + 'buy_price': entry_price, + 'sell_price': current_price, + 'profit_percent': gain_percent, + 'profit_usd': profit_usd + }) + del self.state['current'][pair] + self.save_state() + except Exception as e: + logger.error(f"TP sell error {pair}: {e}") + + # SL at -2.5% (auto-cancelled by limit order but check anyway) + elif gain_percent <= -self.STOP_LOSS_PERCENT: + qty = self.state['current'][pair]['qty'] + try: + order = self.binance.order_market_sell(symbol=pair, quantity=qty) + loss_usd = (current_price - entry_price) * qty + logger.warning(f"🛑 SL HIT: {pair} {gain_percent:.2f}% = ${loss_usd:.2f}") + + self.state['completed'].append({ + 'pair': pair, + 'qty': qty, + 'buy_price': entry_price, + 'sell_price': current_price, + 'profit_percent': gain_percent, + 'profit_usd': loss_usd + }) + del self.state['current'][pair] + self.save_state() + except Exception as e: + logger.error(f"SL sell error {pair}: {e}") + + except Exception as e: + logger.error(f"TP check error: {e}") + + def calculate_daily_loss(self): + """Calculate daily loss percentage""" + try: + if not self.state['completed']: + return 0 + + today_trades = [t for t in self.state['completed'] + if datetime.fromisoformat(t.get('timestamp', datetime.now().isoformat())).date() == datetime.now().date()] + + daily_loss = sum(t.get('profit_usd', 0) for t in today_trades) + + balance = self.binance.get_account() + portfolio = sum(float(a['free']) for a in balance['balances']) + + loss_percent = (daily_loss / portfolio * 100) if portfolio > 0 else 0 + return loss_percent + except: + return 0 + + async def run(self): + """Main trading loop""" + logger.info("🚀 Trading Bot V5 ENHANCED started (SL+TP+DailyLimit)") + + while True: + try: + # Check exits first (TP/SL) + self.check_take_profit() + + # Generate signal (5% probability) + if random.random() < 0.05: + pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'] + for pair in pairs: + if pair not in self.state['current']: + self.place_buy(pair) + + await asyncio.sleep(5) + + except Exception as e: + logger.error(f"Loop error: {e}") + await asyncio.sleep(5) + +if __name__ == "__main__": + bot = TradingBotV5Enhanced() + asyncio.run(bot.run()) diff --git a/src/main_ml_v4_backup.py b/src/main_ml_v4_backup.py new file mode 100644 index 0000000..657417f --- /dev/null +++ b/src/main_ml_v4_backup.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +import os, asyncio, aiohttp, logging, random +from datetime import datetime +from binance.client import Client +from decimal import Decimal + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +with open("/home/marc/bot-deploy/.env") as f: + env = {} + for line in f: + k, _, v = line.partition("=") + env[k.strip()] = v.strip() + +class Bot: + def __init__(self): + self.binance = Client(env.get("BINANCE_API_KEY_LIVE"), env.get("BINANCE_API_SECRET_LIVE")) + self.current_trades = {} + self.completed_trades = [] + self.balance = {} + self.trades_today = 0 + self.daily_pnl = 0.0 + self.dashboard = "http://localhost:7000/api/update" + logger.info("🤖 Bot initialized") + + def get_balance(self): + try: + acc = self.binance.get_account() + self.balance = {} + for a in acc["balances"]: + free, locked = float(a["free"]), float(a["locked"]) + if free + locked > 0: + self.balance[a["asset"]] = {"free": free, "locked": locked, "total": free+locked} + logger.info(f"💰 Balance updated: USDT") + except Exception as e: + logger.error(f"Balance error: {e}") + + def place_buy(self, pair): + try: + usdt_free = self.balance.get("USDT", {}).get("free", 0) + if usdt_free < 5: + return None + + # Use 25% per trade + qty_usdt = usdt_free * 0.25 + + ticker = self.binance.get_symbol_ticker(symbol=pair) + price = float(ticker["price"]) + + # Get symbol info for filters + info = self.binance.get_symbol_info(pair) + filters = {f["filterType"]: f for f in info["filters"]} + + # LOT_SIZE check + if "LOT_SIZE" in filters: + lot = filters["LOT_SIZE"] + min_qty = float(lot["minQty"]) + step = float(lot["stepSize"]) + + # Calculate quantity + qty_calc = qty_usdt / price + + # Round down to step + qty = round(qty_calc / step) * step + + if qty < min_qty or qty <= 0: + return None + else: + qty = float(round(qty_usdt / price, 6)) + + # Format as string to avoid scientific notation + qty_str = f"{qty:.8f}".rstrip("0").rstrip(".") + + try: + order = self.binance.order_market_buy(symbol=pair, quantity=qty_str) + logger.info(f"🟢 BUY: {pair} x{qty_str}") + + self.current_trades[pair] = { + "qty": float(qty_str), + "buy_price": price, + "buy_time": datetime.now().isoformat(), + "order_id": order["orderId"] + } + self.trades_today += 1 + return order + except Exception as e: + logger.error(f"Buy {pair} error: {e}") + return None + except Exception as e: + logger.error(f"place_buy error: {e}") + return None + + def check_tp(self): + remove = [] + for pair in list(self.current_trades.keys()): + try: + trade = self.current_trades[pair] + ticker = self.binance.get_symbol_ticker(symbol=pair) + current = float(ticker["price"]) + + profit_pct = (current / trade["buy_price"]) - 1 + + if profit_pct >= 0.01: + logger.info(f"🎯 TP HIT: {pair} +{profit_pct*100:.2f}%") + + sell = self.binance.order_market_sell(symbol=pair, quantity=trade["qty"]) + sell_price = float(sell["fills"][0]["price"]) if sell.get("fills") else current + profit = (sell_price - trade["buy_price"]) * trade["qty"] + + self.completed_trades.append({ + "pair": pair, + "buy_price": trade["buy_price"], + "sell_price": sell_price, + "qty": trade["qty"], + "profit_usd": profit, + "profit_pct": profit_pct, + "buy_time": trade["buy_time"], + "sell_time": datetime.now().isoformat() + }) + + self.daily_pnl += profit + remove.append(pair) + except Exception as e: + pass + + for p in remove: + del self.current_trades[p] + + async def send_dashboard(self): + try: + state = { + "current_trades": self.current_trades, + "completed_trades": self.completed_trades[-20:], + "balance": self.balance, + "trades_today": self.trades_today, + "daily_pnl": self.daily_pnl, + "total_pnl": self.daily_pnl, + "wins_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) > 0]), + "losses_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) < 0]), + "last_update": datetime.now().isoformat() + } + async with aiohttp.ClientSession() as s: + async with s.post(self.dashboard, json=state, timeout=2) as r: + pass + except: + pass + + async def run(self): + logger.info("🎯 Bot started") + + while True: + try: + self.get_balance() + self.check_tp() + + pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'] + + for pair in pairs: + if pair not in self.current_trades and random.random() < 0.05: + logger.info(f"🟢 Signal: {pair}") + self.place_buy(pair) + + await self.send_dashboard() + await asyncio.sleep(5) + + except Exception as e: + logger.error(f"Run error: {e}") + await asyncio.sleep(10) + +if __name__ == "__main__": + bot = Bot() + asyncio.run(bot.run()) diff --git a/src/web_dashboard.py b/src/web_dashboard.py index edddb6a..8c7ae5c 100644 --- a/src/web_dashboard.py +++ b/src/web_dashboard.py @@ -1,605 +1,206 @@ #!/usr/bin/env python3 -from fastapi import FastAPI -from fastapi.responses import HTMLResponse -from fastapi.middleware.cors import CORSMiddleware +from fastapi import FastAPI, Response +from binance.client import Client +import json, os +from datetime import datetime app = FastAPI() -app.add_middleware( - CORSMiddleware, - allow_origins=['*'], - allow_credentials=True, - allow_methods=['*'], - allow_headers=['*'], -) +env = {} +with open('/home/marc/bot-deploy/.env') as f: + for line in f: + k,_,v = line.partition('=') + env[k.strip()] = v.strip() -state = { - 'current_trades': {}, - 'completed_trades': [], - 'balance': {'USDT': {'free': 0.0}}, - 'trades_today': 0, - 'daily_pnl': 0.0, - 'wins_today': 0, - 'losses_today': 0, - 'last_update': '', -} +binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE')) -@app.post('/api/update') -async def update_state(data: dict): - global state - state.update(data) - return {'status': 'ok'} +def get_live_prices(): + """Get LIVE prices from Binance API""" + prices = {'USDT': 1.0} + + pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'] + for pair in pairs: + try: + ticker = binance.get_ticker(symbol=pair) + asset = pair.replace('USDT', '') + prices[asset] = float(ticker['lastPrice']) + except: + pass + + return prices + +def load_bot_state(): + """Load bot state from file""" + state_file = '/home/marc/bot-deploy/trades.json' + if os.path.exists(state_file): + with open(state_file) as f: + return json.load(f) + return {'current': {}, 'completed': [], 'balance': {}} @app.get('/api/state') async def get_state(): - return state + """Return complete bot state with LIVE prices from Binance""" + try: + # Get balance from Binance (LIVE) + account = binance.get_account() + balance = {} + + for asset_data in account['balances']: + asset = asset_data['asset'] + free = float(asset_data['free']) + locked = float(asset_data['locked']) + total = free + locked + + if total > 0: + balance[asset] = { + 'free': free, + 'locked': locked, + 'total': total + } + + # Get LIVE prices from Binance (CRITICAL FIX) + prices = get_live_prices() + + # Calculate portfolio value with LIVE prices + portfolio_value = 0 + for asset, data in balance.items(): + price = prices.get(asset, 0) + portfolio_value += data['total'] * price + + # Load trade state + trades = load_bot_state() + + return { + 'balance': balance, + 'portfolio_value': portfolio_value, + 'current_trades': trades.get('current', {}), + 'completed_trades': trades.get('completed', []), + 'prices': prices, + 'timestamp': datetime.now().isoformat(), + 'note': 'Portfolio calculated with LIVE Binance prices' + } + except Exception as e: + return {'error': str(e), 'portfolio_value': 0} -@app.get('/', response_class=HTMLResponse) -async def dashboard(): - return ''' - +@app.get('/') +async def root(): + state = await get_state() + portfolio_val = state.get('portfolio_value', 0) + usdt_free = state.get('balance', {}).get('USDT', {}).get('free', 0) + trades_count = len(state.get('current_trades', {})) + prices = state.get('prices', {}) + + html = f''' +
- - -