From 882e5adaaf828127fa2cfcc24ce2e80a4ab8e315 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Sat, 4 Jul 2026 14:07:22 +0200 Subject: [PATCH] Bot auto-update: src/__pycache__/main_ml.cpython-310.pyc,src/__pycache__/state_manager.cpython-310.pyc,src/main_ml.py,src/state_manager.py,src/web_dashboard.py --- src/__pycache__/main_ml.cpython-310.pyc | Bin 28097 -> 28086 bytes src/__pycache__/state_manager.cpython-310.pyc | Bin 5413 -> 4352 bytes src/main_ml.py | 24 +-- src/state_manager.py | 173 ++++++------------ src/web_dashboard.py | 4 +- 5 files changed, 73 insertions(+), 128 deletions(-) diff --git a/src/__pycache__/main_ml.cpython-310.pyc b/src/__pycache__/main_ml.cpython-310.pyc index 03369a82d4571a227d536901438e24e0e304e8fc..10a53893ef0d14bebdfc266fa0bee0fb4188eaf9 100644 GIT binary patch delta 51 zcmX?jn{nH1My`BbUM>b8DEPjSD_oE_xwNP#HLoPTq$n{Zb#tCzeGqF=Fi?8)-{d2l E0K5k delta 66 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 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) @@ -968,13 +968,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/state_manager.py b/src/state_manager.py index 301986b..e9ec034 100644 --- a/src/state_manager.py +++ b/src/state_manager.py @@ -1,194 +1,139 @@ #!/usr/bin/env python3 """ -State Manager V2: Direct Binance Integration -- Loads open positions from Binance API (source of truth) -- Computes portfolio value from real prices -- Persists to JSON -- Provides REST API for dashboard +State Manager V3: Ultra-Simple Binance Direct +- Uses environment variables directly +- No .env nonsense, uses os.environ """ import asyncio import json import logging +import os +import sys from datetime import datetime from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware import uvicorn from binance.client import Client -import os -from dotenv import load_dotenv -import time -# Setup -load_dotenv('/home/marc/bot-deploy/.env') +# Read .env directly into os.environ BEFORE importing anything else +env_file = '/home/marc/bot-deploy/.env' +for line in open(env_file).readlines(): + line = line.strip() + if line and not line.startswith('#') and '=' in line: + k, v = line.split('=', 1) + os.environ[k] = v.strip('"').strip("'") + +API_KEY = os.environ.get('BINANCE_API_KEY') +API_SECRET = os.environ.get('BINANCE_API_SECRET') + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +if not API_KEY or not API_SECRET: + logger.error(f"Missing credentials: key={bool(API_KEY)}, secret={bool(API_SECRET)}") + sys.exit(1) + +logger.info(f"✅ API credentials loaded") + +client = Client(API_KEY, API_SECRET) + app = FastAPI() app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) -# Binance client -API_KEY = os.getenv('BINANCE_API_KEY') -API_SECRET = os.getenv('BINANCE_API_SECRET') -client = Client(API_KEY, API_SECRET) - -# Persistence -DATA_DIR = Path('/home/marc/bot-deploy/data') -DATA_DIR.mkdir(exist_ok=True) -TRADES_FILE = DATA_DIR / 'trades_persistent.json' -STATE_FILE = DATA_DIR / 'state.json' - -# Global state state = { 'current_trades': {}, 'completed_trades': [], 'swaps': [], - 'balance': {'USDT': 0.0}, + 'balance': {}, 'portfolio_value_usd': 0.0, 'daily_pnl': 0.0, 'total_pnl': 0.0, - 'trades_today': 0, - 'wins_today': 0, - 'losses_today': 0, - 'last_sync': datetime.now().isoformat(), - 'timestamp': datetime.now().isoformat() + 'last_sync': datetime.now().isoformat() } def load_from_binance(): - """Load REAL open positions from Binance API""" + """Load real data from Binance""" global state - try: logger.info('🔄 Syncing with Binance...') - # 1. Get account balance account = client.get_account() - balances = {b['asset']: float(b['free']) for b in account['balances'] if float(b['free']) > 0} + balances = {b['asset']: float(b['free']) for b in account['balances'] if float(b['free']) > 0.00001} state['balance'] = balances - logger.info(f"✅ Balances: USDT={balances.get('USDT', 0):.2f}") + logger.info(f"Balance: USDT={balances.get('USDT', 0):.2f}") - # 2. Load all open orders by symbol - open_positions = {} - symbols = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'] - - for symbol in symbols: + open_trades = {} + for symbol in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']: try: orders = client.get_open_orders(symbol=symbol) if orders: - # Get the first order (BUY order) - order = orders[0] - - # Get current price for profit calculation - ticker = client.get_symbol_info(symbol) + o = orders[0] + qty = float(o['origQty']) + buy_price = float(o['price']) current_price = float(client.get_symbol_ticker(symbol=symbol)['price']) - qty = float(order['origQty']) - buy_price = float(order['price']) - notional = qty * buy_price - current_value = qty * current_price - profit = current_value - notional - profit_pct = (profit / notional * 100) if notional > 0 else 0 + profit = (current_price - buy_price) * qty + profit_pct = ((current_price - buy_price) / buy_price * 100) if buy_price > 0 else 0 - open_positions[symbol] = { + open_trades[symbol] = { 'qty': qty, 'buy_price': buy_price, 'current_price': current_price, - 'buy_time': datetime.fromtimestamp(order['time']/1000).isoformat(), - 'current_value': current_value, - 'entry_value': notional, + 'buy_time': datetime.fromtimestamp(o['time']/1000).isoformat(), 'profit': profit, 'profit_pct': profit_pct, - 'peak_profit': profit_pct, - 'trailing_stop': None, - 'order_id': order['orderId'] + 'order_id': o['orderId'] } - logger.info(f" {symbol}: {qty:.8f} @ ${buy_price:.2f} → Current: ${current_price:.2f} (P&L: ${profit:.2f} / {profit_pct:.2f}%)") - + logger.info(f" {symbol}: {qty:.8f} → ${current_price:.2f} P&L: {profit_pct:.2f}%") except Exception as e: - logger.debug(f"No open orders for {symbol}: {e}") - continue + logger.debug(f"Error {symbol}: {e}") - state['current_trades'] = open_positions - logger.info(f"✅ Found {len(open_positions)} open positions") + state['current_trades'] = open_trades - # 3. Calculate portfolio value - usdt_balance = balances.get('USDT', 0) - portfolio_value = usdt_balance + usdt = balances.get('USDT', 0) + portfolio = usdt + sum(t['qty']*t['current_price'] for t in open_trades.values()) + pnl = sum(t['profit'] for t in open_trades.values()) - for symbol, trade in open_positions.items(): - portfolio_value += trade['current_value'] - - state['portfolio_value_usd'] = portfolio_value - - # 4. Calculate P&L - daily_pnl = sum(t.get('profit', 0) for t in open_positions.values()) - state['daily_pnl'] = daily_pnl - state['total_pnl'] = daily_pnl - - # 5. Update metadata + state['portfolio_value_usd'] = portfolio + state['daily_pnl'] = pnl + state['total_pnl'] = pnl state['last_sync'] = datetime.now().isoformat() - state['timestamp'] = datetime.now().isoformat() - - logger.info(f"✅ Portfolio Value: USD ${portfolio_value:.2f}") - logger.info(f"✅ Daily P&L: ${daily_pnl:.2f}") - - # 6. Persist - save_state() + logger.info(f"✅ Portfolio: ${portfolio:.2f}, Trades: {len(open_trades)}, P&L: ${pnl:.2f}") return True except Exception as e: - logger.error(f"❌ Binance sync failed: {e}") + logger.error(f"❌ Error: {e}") + import traceback + traceback.print_exc() return False -def save_state(): - """Save state to disk""" - try: - with open(STATE_FILE, 'w') as f: - json.dump(state, f, indent=2) - logger.info(f"💾 State persisted to {STATE_FILE}") - except Exception as e: - logger.error(f"Failed to save state: {e}") - async def background_sync(): - """Periodically sync with Binance""" while True: try: load_from_binance() - await asyncio.sleep(10) # Sync every 10 seconds + await asyncio.sleep(10) except Exception as e: - logger.error(f"Sync loop error: {e}") + logger.error(f"Sync loop: {e}") await asyncio.sleep(10) @app.on_event("startup") async def startup(): - logger.info("🚀 State Manager starting...") + logger.info("🚀 Starting State Manager...") load_from_binance() asyncio.create_task(background_sync()) - logger.info("✅ Background sync active") + logger.info("✅ Sync active") @app.get("/state") async def get_state(): - """Return current state (loaded from Binance)""" return state @app.get("/health") async def health(): - """Health check""" - return { - "status": "ok", - "trades": len(state['current_trades']), - "portfolio_usd": state['portfolio_value_usd'], - "last_sync": state['last_sync'] - } - -@app.post("/sync") -async def manual_sync(): - """Force immediate sync with Binance""" - load_from_binance() - return {"status": "synced", "trades": len(state['current_trades'])} + return {"status": "ok", "trades": len(state['current_trades']), "portfolio": state['portfolio_value_usd']} if __name__ == "__main__": - logger.info("Starting State Manager on port 8001...") - uvicorn.run(app, host="0.0.0.0", port=8001, log_level="info") + logger.info("Starting on :8001") + uvicorn.run(app, host="0.0.0.0", port=8001, log_level="error") diff --git a/src/web_dashboard.py b/src/web_dashboard.py index d8cadd7..1d7c37a 100644 --- a/src/web_dashboard.py +++ b/src/web_dashboard.py @@ -73,7 +73,7 @@ async def get_state(): """Proxy to State Manager""" try: async with httpx.AsyncClient(timeout=3.0) as client: - resp = await client.get("http://localhost:8001/state") + resp = await client.get("http://localhost:7001/api/bot-state") return resp.json() except: return trading_state @@ -213,7 +213,7 @@ async def get_state(): """Proxy to State Manager""" try: async with httpx.AsyncClient(timeout=3.0) as client: - resp = await client.get("http://localhost:8001/state") + resp = await client.get("http://localhost:7001/api/bot-state") return resp.json() except: return trading_state