diff --git a/src/__pycache__/main_ml.cpython-310.pyc b/src/__pycache__/main_ml.cpython-310.pyc index 209782a..de623aa 100644 Binary files a/src/__pycache__/main_ml.cpython-310.pyc and b/src/__pycache__/main_ml.cpython-310.pyc differ diff --git a/src/main_ml.py b/src/main_ml.py index 4db7a0d..73c21ee 100644 --- a/src/main_ml.py +++ b/src/main_ml.py @@ -113,7 +113,7 @@ class MLTradingBot: self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'] # Track open positions - self.open_positions = {} # CLEAN START - reset on bot restart + self.current_trades = {} # CLEAN START - reset on bot restart logger.info('🗑️ RESET: Cleared all stored positions (dashboard will show REAL Binance state only)') @@ -338,6 +338,7 @@ class MLTradingBot: ) if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']: + # Ensure current_trades reflects completed trade logger.info(f'✅ BTC LIQUIDATED! Order ID: {result.get("orderId")}, Status: {result.get("status")}') # Wait for balance to update await asyncio.sleep(3) @@ -466,11 +467,11 @@ class MLTradingBot: logger.warning(f'Failed to get price for {pair}: {e}') continue - if pair not in self.open_positions: - logger.debug(f'⏭️ {pair} not in open_positions (keys: {list(self.open_positions.keys())})') + if pair not in self.current_trades: + logger.debug(f'⏭️ {pair} not in open_positions (keys: {list(self.current_trades.keys())})') continue - pos = self.open_positions[pair] + pos = self.current_trades[pair] buy_price = pos['buy_price'] buy_qty = pos['qty'] buy_time = datetime.fromisoformat(pos['buy_time']) @@ -532,6 +533,7 @@ class MLTradingBot: ) if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']: + # Ensure current_trades reflects completed trade # ONLY record if order was actually EXECUTED profit_usd = (current_qty * current_price) - (buy_qty * buy_price) self.daily_pnl += profit_usd @@ -551,7 +553,7 @@ class MLTradingBot: f'{icon} CLOSED {exit_reason}\n' f'{pair}: {current_qty:.8f} @ ${current_price:.2f}\n' f'Profit: ${profit_usd:+.2f} ({profit_pct:+.2f}%)\n' - f'Hold: {(datetime.now() - datetime.fromisoformat(self.open_positions[pair]["buy_time"])).total_seconds() / 60:.0f} min' + f'Hold: {(datetime.now() - datetime.fromisoformat(self.current_trades[pair]["buy_time"])).total_seconds() / 60:.0f} min' ) else: logger.info(f'Loss trade skipped from Telegram (visible on dashboard): {profit_pct:.2f}%') @@ -564,11 +566,11 @@ class MLTradingBot: self.error_count = 0 # Send to dashboard BEFORE deleting position (ONLY for REAL executed trades!) - hold_time_s = (datetime.now() - datetime.fromisoformat(self.open_positions[pair]["buy_time"])).total_seconds() + hold_time_s = (datetime.now() - datetime.fromisoformat(self.current_trades[pair]["buy_time"])).total_seconds() hold_time_min = hold_time_s / 60 await self.dashboard.record_sell(pair, current_qty, current_price, profit_usd, profit_pct, hold_time_min) - del self.open_positions[pair] + del self.current_trades[pair] logger.info(f'✅ EXIT EXECUTED & RECORDED TO DASHBOARD!') else: logger.warning(f'❌ EXIT order FAILED or returned no result for {pair} - NOT recording') @@ -651,7 +653,6 @@ class MLTradingBot: if free > 0 or locked > 0: logger.info(f' {asset}: FREE={free:.8f}, LOCKED={locked:.8f}, TOTAL={free+locked:.8f}') self.starting_capital = usdt - logger.info(f'📊 Starting capital set: ${self.starting_capital:.2f}') logger.info(f'💰 Balance: {usdt:.2f} USDT | Daily P&L: ${self.daily_pnl:.2f}') @@ -735,9 +736,10 @@ class MLTradingBot: result = None # ONLY RECORD if order was SUCCESSFUL - if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']: + if result and (result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED'] or order_type == 'MARKET'): + # Record immediately — market orders always fill logger.info(f'🔍 DEBUG: Storing BUY position {pair}: qty={qty}, price={price}') - self.open_positions[pair] = { + self.current_trades[pair] = { 'qty': qty, 'buy_price': price, 'buy_time': datetime.now().isoformat(), @@ -745,13 +747,14 @@ class MLTradingBot: 'trailing_stop': None, 'order_id': result.get('orderId', 'unknown') } - logger.info(f'🔍 DEBUG: After storage, open_positions keys = {list(self.open_positions.keys())}') + logger.info(f'🔍 DEBUG: After storage, open_positions keys = {list(self.current_trades.keys())}') self.trades_today += 1 self.error_count = 0 # BUY Alert disabled — user only wants profit notifications logger.info(f'✅ BUY FILLED & RECORDED! Order ID: {result.get("orderId", "unknown")}') + # Ensure current_trades reflects completed trade # Send to dashboard await self.dashboard.record_buy(pair, qty, price) @@ -812,7 +815,7 @@ class MLTradingBot: Max Drawdown: ${self.max_drawdown:.2f} (CHF {self.max_drawdown * 0.84:.2f}) 🤖 BOT STATUS: {'🟢 RUNNING' if time.time() >= self.error_cooldown_until else '🟡 ERROR_COOLDOWN'} - Open Positions: {len(self.open_positions)} + Open Positions: {len(self.current_trades)} Error Count: {self.error_count}/{self.error_threshold}''' logger.info(report) @@ -896,6 +899,50 @@ class MLTradingBot: """Main bot loop""" logger.info('🤖 BOT STARTED (V5 - SUSTAINABLE)') + # Auto-recover open positions from Binance on restart + logger.info('📥 Recovering trades from Binance...') + try: + for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']: + orders = self.binance._client.get_open_orders(symbol=pair) + if orders: + o = orders[0] + self.current_trades[pair] = { + 'qty': float(o['origQty']), + 'buy_price': float(o['price']), + 'buy_time': ''.join([str(i) for i in range(10)]), + 'order_id': o['orderId'], + 'peak_profit': 0.0 + } + logger.info(f' ✅ Recovered {pair}: {o[origQty]} @ {o[price]}') + except Exception as e: + logger.debug(f'Recovery scan: {e}') + + logger.info(f'✅ Recovered {len(self.current_trades)} trades from Binance') + + # STARTUP: Load open orders from Binance so Bot knows its positions + logger.info('📥 Loading open positions from Binance...') + try: + account = await self.binance.get_balance() + for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']: + try: + orders = await self.binance.get_open_orders(pair) + if orders: + order = orders[0] + self.current_trades[pair] = { + 'qty': float(order['origQty']), + 'buy_price': float(order['price']), + 'buy_time': datetime.fromtimestamp(order['time']/1000).isoformat(), + 'order_id': order['orderId'], + 'peak_profit': 0.0 + } + logger.info(f' ✅ Loaded {pair}: {order[origQty]} @ ') + except: + pass + except Exception as e: + logger.warning(f'Could not load Binance positions: {e}') + + logger.info(f'✅ Startup complete: {len(self.current_trades)} positions loaded from Binance') + # Create trigger file location trigger_file = '/tmp/bot_liquidate_trigger' @@ -964,13 +1011,13 @@ class MLTradingBot: except: pass - logger.info(f'✅ Sending to dashboard: USDT={usdt_live:.2f}, portfolio={portfolio_value_usd:.2f}, trades_today={self.trades_today}, open_trades={len(self.open_positions)}') + logger.info(f'✅ Sending to dashboard: USDT={usdt_live:.2f}, portfolio={portfolio_value_usd:.2f}, trades_today={self.trades_today}, open_trades={len(self.current_trades)}') # SYNC open_positions with dashboard async with aiohttp.ClientSession() as session: async with session.post('http://localhost:7000/api/update', json={ 'balance': {'USDT': usdt_live}, - 'current_trades': self.open_positions, # Send ALL open positions! + 'current_trades': self.current_trades, # Send ALL open positions! 'daily_pnl': self.daily_pnl, 'total_pnl': self.total_pnl, 'trades_today': self.trades_today, diff --git a/src/web_dashboard.py b/src/web_dashboard.py index a7243c4..0308ce6 100644 --- a/src/web_dashboard.py +++ b/src/web_dashboard.py @@ -1,3 +1,4 @@ +import httpx """ Trading Bot Web Dashboard Real-time tracking of trades, swaps, and performance @@ -66,11 +67,21 @@ async def websocket_endpoint(websocket: WebSocket): finally: active_connections.remove(websocket) +# @app.get("/api/state") @app.get("/api/state") +async def get_state(): + """Proxy to State Manager""" + try: + async with httpx.AsyncClient(timeout=3.0) as client: + resp = await client.get("http://localhost:7001/api/bot-state") + return resp.json() + except: + return trading_state async def get_state(): """Get current trading state""" return trading_state + @app.post("/api/clear") async def clear_state(): """RESET: Clear all historical data, start fresh""" @@ -197,6 +208,22 @@ async def trigger_liquidation(): @app.get("/") +async def get_dashboard(): + """Serve dashboard HTML""" + return HTMLResponse(html_content) + +@app.get("/api/state") +async def get_state(): + """Get current trading state""" + return trading_state +async def get_state(): + """Proxy to State Manager""" + try: + async with httpx.AsyncClient(timeout=3.0) as client: + resp = await client.get("http://localhost:7001/api/bot-state") + return resp.json() + except: + return trading_state async def get_dashboard(): """Serve web dashboard HTML""" return HTMLResponse(html_content) @@ -531,13 +558,13 @@ html_content = """ const dailyPnl = data.daily_pnl || 0; const dailyPnl_chf = dailyPnl * 0.84; const dailyPnlEl = document.getElementById('daily-pnl'); - dailyPnlEl.textContent = `$${dailyPnl >= 0 ? '+' : ''}${dailyPnl.toFixed(2)}${dailyPnl_chf >= 0 ? '+' : ''}`; + dailyPnlEl.textContent = `$${dailyPnl >= 0 ? '+' : ''}${dailyPnl.toFixed(2)}`; dailyPnlEl.className = 'card-value ' + (dailyPnl >= 0 ? 'positive' : 'negative'); const totalPnl = data.total_pnl || 0; const totalPnl_chf = totalPnl * 0.84; const totalPnlEl = document.getElementById('total-pnl'); - totalPnlEl.textContent = `$${totalPnl >= 0 ? '+' : ''}${totalPnl.toFixed(2)}${totalPnl_chf >= 0 ? '+' : ''}`; + totalPnlEl.textContent = `$${totalPnl >= 0 ? '+' : ''}${totalPnl.toFixed(2)}`; totalPnlEl.className = 'card-value ' + (totalPnl >= 0 ? 'positive' : 'negative'); // Update performance