Bot auto-update: src/__pycache__/state_manager.cpython-310.pyc,src/__pycache__/web_dashboard.cpython-310.pyc,src/state_manager.py

This commit is contained in:
Marc Blatter 2026-07-04 13:39:25 +02:00
parent de57afabdb
commit 42f4707461
3 changed files with 191 additions and 0 deletions

Binary file not shown.

191
src/state_manager.py Normal file
View File

@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""
Enhanced State Manager with Binance Position Recovery
"""
import json
import asyncio
import logging
import os
from datetime import datetime
from pathlib import Path
from fastapi import FastAPI
import httpx
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
STATE_DIR = Path('/home/marc/bot-deploy/data')
TRADES_FILE = STATE_DIR / 'trades_persistent.json'
app = FastAPI()
class StateManager:
def __init__(self):
self.state_dir = STATE_DIR
self.state_dir.mkdir(parents=True, exist_ok=True)
self.trades = {}
self.completed_trades = []
self.swaps = []
self.last_sync = None
self.load_from_disk()
def load_from_disk(self):
"""Load state from persistent storage"""
if TRADES_FILE.exists():
try:
with open(TRADES_FILE) as f:
data = json.load(f)
self.trades = data.get('current_trades', {})
self.completed_trades = data.get('completed_trades', [])
self.swaps = data.get('swaps', [])
logger.info(f"✅ Loaded {len(self.trades)} trades from disk")
except Exception as e:
logger.error(f"Failed to load trades: {e}")
def save_to_disk(self):
"""Persist state to JSON"""
data = {
'current_trades': self.trades,
'completed_trades': self.completed_trades,
'swaps': self.swaps,
'last_update': datetime.utcnow().isoformat()
}
with open(TRADES_FILE, 'w') as f:
json.dump(data, f, indent=2)
logger.info(f"💾 Persisted state: {len(self.trades)} open trades")
async def sync_bot_state(self):
"""Load current state from bot API"""
try:
async with httpx.AsyncClient(timeout=5.0) as client:
resp = await client.get('http://localhost:7000/api/state')
if resp.status_code == 200:
bot_state = resp.json()
# Merge bot trades with persistent storage
if 'current_trades' in bot_state and bot_state['current_trades']:
self.trades.update(bot_state['current_trades'])
if 'completed_trades' in bot_state:
for trade in bot_state['completed_trades']:
if trade not in self.completed_trades:
self.completed_trades.append(trade)
self.last_sync = datetime.utcnow().isoformat()
logger.info(f"🔄 Synced: {len(self.trades)} open trades")
self.save_to_disk()
return True
except Exception as e:
logger.warning(f"Bot API unavailable: {e}")
return False
async def recover_from_binance(self):
"""Load open positions directly from Binance API on startup"""
logger.info("📊 Attempting to recover positions from Binance...")
try:
async with httpx.AsyncClient(timeout=10.0) as client:
# Get account info from Binance
resp = await client.get('https://api.binance.com/api/v3/account')
if resp.status_code == 200:
account = resp.json()
balances = account.get('balances', [])
new_trades = {}
for bal in balances:
symbol = bal['asset']
free = float(bal['free'])
if symbol != 'USDT' and free > 0.00001:
# Get current price
pair = f'{symbol}USDT'
try:
p_resp = await client.get(
f'https://api.binance.com/api/v3/ticker/price?symbol={pair}'
)
if p_resp.status_code == 200:
price = float(p_resp.json()['price'])
new_trades[pair] = {
'qty': free,
'buy_price': price,
'entry_time': 'recovered-on-restart',
'status': 'open',
'recovered': True
}
logger.info(f"✅ Recovered: {pair} {free} @ ${price}")
except:
pass
if new_trades:
# Merge with existing
for pair, trade in new_trades.items():
if pair not in self.trades:
self.trades[pair] = trade
self.save_to_disk()
logger.info(f"✅ Recovered {len(new_trades)} positions from Binance")
return len(new_trades) > 0
except Exception as e:
logger.warning(f"Binance recovery failed: {e}")
return False
def get_state(self):
"""Get current state for dashboard"""
return {
'current_trades': self.trades,
'completed_trades': self.completed_trades,
'swaps': self.swaps,
'last_sync': self.last_sync,
'timestamp': datetime.utcnow().isoformat()
}
# Global instance
state_mgr = StateManager()
@app.on_event("startup")
async def startup():
"""On startup: Load from disk, sync bot, recover from Binance if needed"""
logger.info("🚀 State Manager startup sequence...")
# 1. Already loaded from disk in __init__
logger.info(f"📁 Loaded {len(state_mgr.trades)} trades from disk")
# 2. Try to sync with bot
await state_mgr.sync_bot_state()
# 3. If still no trades: recover from Binance API
if not state_mgr.trades:
logger.info("⚠️ No trades in storage - attempting Binance recovery...")
await state_mgr.recover_from_binance()
# 4. Start background sync
asyncio.create_task(background_sync())
logger.info("✅ State Manager ready!")
async def background_sync():
"""Periodically sync with bot"""
while True:
try:
await asyncio.sleep(30)
await state_mgr.sync_bot_state()
except Exception as e:
logger.error(f"Sync error: {e}")
await asyncio.sleep(5)
@app.get("/state")
async def get_state():
"""Get current state (used by dashboard)"""
return state_mgr.get_state()
@app.get("/health")
async def health():
"""Health check"""
return {"status": "ok", "trades": len(state_mgr.trades)}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='127.0.0.1', port=8001, log_level='info')