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

This commit is contained in:
Marc Blatter 2026-07-04 14:07:22 +02:00
parent 4e8d268e35
commit 882e5adaaf
5 changed files with 73 additions and 128 deletions

View File

@ -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)')
@ -467,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'])
@ -553,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}%')
@ -566,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')
@ -740,7 +740,7 @@ class MLTradingBot:
if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
# Ensure current_trades reflects completed trade
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(),
@ -748,7 +748,7 @@ 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
@ -816,7 +816,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)
@ -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,

View File

@ -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")

View File

@ -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