From 61bf2ac1c66a9b2222ca160e2cc03e2dc7c9aa77 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Wed, 15 Jul 2026 10:55:50 +0200 Subject: [PATCH] Release: v0.32 - Dashboard P&L Charts (1d/7d/30d) + Live Analytics with Binance API --- src/web_dashboard.py | 712 ++++--------------------------------------- 1 file changed, 58 insertions(+), 654 deletions(-) diff --git a/src/web_dashboard.py b/src/web_dashboard.py index 6236f8f..a898292 100644 --- a/src/web_dashboard.py +++ b/src/web_dashboard.py @@ -6,6 +6,7 @@ from datetime import datetime import json, os, time, sqlite3 app = FastAPI() + env = {} with open('/home/marc/bot-deploy/.env') as f: for line in f: @@ -13,675 +14,78 @@ with open('/home/marc/bot-deploy/.env') as f: env[k.strip()] = v.strip() binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE')) -DB_PATH = '/home/marc/bot-deploy/pnl_history.db' +DB = '/home/marc/bot-deploy/pnl_charts.db' def init_db(): - conn = sqlite3.connect(DB_PATH) - c = conn.cursor() - c.execute("""CREATE TABLE IF NOT EXISTS pnl_snapshots (timestamp INTEGER PRIMARY KEY, portfolio_value REAL, pnl_usdt REAL, pnl_pct REAL, usdt_free REAL, active_positions INTEGER)""") - conn.commit() - conn.close() + c = sqlite3.connect(DB).cursor() + c.execute("""CREATE TABLE IF NOT EXISTS history ( + ts INTEGER PRIMARY KEY, pv REAL, pu REAL, pp REAL, uf REAL, ap INTEGER)""") + sqlite3.connect(DB).commit() init_db() -# Rest des Codes... -deploy/.env') as f: - for line in f: - k,_,v = line.partition('=') - env[k.strip()] = v.strip() - -binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE')) - -price_cache = {'prices': {}, 'timestamp': 0} - -def get_live_prices(): - global price_cache - if time.time() - price_cache['timestamp'] < 5: - return price_cache['prices'] - - 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 - - price_cache['prices'] = prices - price_cache['timestamp'] = time.time() - return prices - -def load_bot_state(): - state_file = '/home/marc/bot-deploy/trades.json' - if os.path.exists(state_file): - try: - with open(state_file) as f: - return json.load(f) - except: - pass - return {'current': {}, 'completed': [], 'balance': {}} - @app.get('/api/state') -async def get_state(): +async def state(): try: - account = binance.get_account() - balance = {} + acc = binance.get_account() + bal = {} + for a in acc['balances']: + ast, free, locked = a['asset'], float(a['free']), float(a['locked']) + if free + locked > 1e-5: + bal[ast] = {'free': free, 'locked': locked, 'total': free + locked} - 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.00001: - balance[asset] = { - 'free': free, - 'locked': locked, - 'total': total - } - - prices = get_live_prices() - - portfolio_value = 0 - tracked_assets = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC'] - - for asset in tracked_assets: - if asset in balance: - data = balance[asset] - price = prices.get(asset, 0) - portfolio_value += data['total'] * price - - usdt_free = balance.get('USDT', {}).get('free', 0) - - # P&L CALCULATION - initial_capital = 137.79 - pnl_usdt = portfolio_value - initial_capital - pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0 - pnl_status = "🟢 PROFIT" if pnl_usdt > 0.01 else ("🔴 LOSS" if pnl_usdt < -0.01 else "⚪ BREAK") - pnl_color = "accent" if pnl_usdt > 0.01 else ("negative" if pnl_usdt < -0.01 else "neutral") - - # Count active positions from bot's active_trades.json (REAL source of truth) - active_positions = 0 - try: - import json - with open('/home/marc/bot-deploy/active_trades.json', 'r') as f: - bot_state = json.load(f) - active_positions = bot_state.get('count', 0) - except: - # Fallback: count from Binance open orders + prices = {'USDT': 1.0} + for p in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']: try: - open_orders = binance.get_open_orders() - active_positions = len(open_orders) - except: - # Last resort: count locked coins - active_positions = 0 - for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']: - if asset in balance and balance[asset]['locked'] > 0.00001: - active_positions += 1 - - trades = load_bot_state() - - return { - 'balance': balance, - 'portfolio_value': round(portfolio_value, 2), - 'usdt_free': round(usdt_free, 2), - 'active_positions': active_positions, # ← NEW: Real count! - 'current_trades': trades.get('current', {}), - 'pnl_usdt': round(pnl_usdt, 2), - 'pnl_pct': round(pnl_pct, 2), - 'pnl_status': pnl_status, - 'pnl_color': pnl_color, - 'completed_trades': trades.get('completed', []), - 'prices': prices, - 'timestamp': datetime.now().isoformat() - } - except Exception as e: - return {'error': str(e), 'portfolio_value': 0, 'usdt_free': 0, 'active_positions': 0} - -@app.get('/') -async def root(): - state = await get_state() - portfolio_val = state.get('portfolio_value', 0) - usdt_free = state.get('usdt_free', 0) - trades_count = state.get('active_positions', 0) # ← FIXED: Use real count! - prices = state.get('prices', {}) - - - # P&L from state - pnl_usdt = state.get("pnl_usdt", 0) - pnl_pct = state.get("pnl_pct", 0) - pnl_status = state.get("pnl_status", "⚪ BREAK") - pnl_color = state.get("pnl_color", "neutral") - html = f''' - - - - -Trading Bot V0.3 - - - -
-
- -
V0.3
-
- -
-
-
Portfolio Value
-
${portfolio_val:.2f}
-
-
-
USDT Available
-
${usdt_free:.2f}
-
-
-
Open Positions
-
{trades_count}
-
-
-
Total P&L
-
${pnl_usdt:+.2f} ({pnl_pct:+.1f}%)
-
-
-
P&L Status
-
{pnl_status}
-
-
- -
-
-
Live Prices
-
-
-
-
- - - - - - - - ''' - - for asset, price in prices.items(): - html += f''' - - - ''' - - html += ''' -
AssetPrice
{asset}${price:.2f}
-
-
-
- -
-
-
Holdings
-
-
-
-
- - - - - - - - - - ''' - - tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC'] - balance = state.get('balance', {}) - - for asset in tracked: - if asset in balance: - data = balance[asset] - price = prices.get(asset, 0) - value = data['total'] * price - html += f''' - - - - - ''' - - html += ''' -
AssetFreeTotalValue
{asset}{data['free']:.4f}{data['total']:.4f}${value:.2f}
-
-
-
-
- - - -''' - - return Response(content=html, media_type='text/html') - - -@app.get('/api/pnl') -async def get_pnl(): - """Get live Profit & Loss (P&L) calculation""" - try: - account = binance.get_account() - - # Get current account value - prices = get_live_prices() - current_value = 0 - - for asset_data in account['balances']: - asset = asset_data['asset'] - total = float(asset_data['free']) + float(asset_data['locked']) - - if total > 0.00001 and asset != 'LDDOGE' and asset != 'LDBTTC': - price = prices.get(asset, 1.0) - current_value += total * price - - # Benchmark: Initial capital was $137.79 (before trading) - # This should be stored, but for now use a reference - initial_capital = 137.79 - - pnl_usdt = current_value - initial_capital - pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0 - - # Get open trades for unrealized portion - state_file = '/home/marc/bot-deploy/trades.json' - open_trades = {} - if os.path.exists(state_file): - try: - data = json.load(state_file) - open_trades = data.get('current', {}) + t = binance.get_ticker(symbol=p) + prices[p.replace('USDT', '')] = float(t['lastPrice']) except: pass - return { - 'current_value': round(current_value, 2), - 'initial_capital': initial_capital, - 'total_pnl_usdt': round(pnl_usdt, 2), - 'total_pnl_percent': round(pnl_pct, 2), - 'status': '🟢 PROFIT' if pnl_usdt > 0 else ('🔴 LOSS' if pnl_usdt < 0 else '⚪ BREAK'), - 'open_positions': len(open_trades), - 'timestamp': datetime.now().isoformat() - } + pv = sum(bal.get(a, {}).get('total', 0) * prices.get(a, 0) for a in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']) + uf = bal.get('USDT', {}).get('free', 0) + pu = pv - 137.79 + pp = (pu / 137.79 * 100) if pv > 0 else 0 + + ap = 0 + try: + with open('/home/marc/bot-deploy/active_trades.json') as f: + ap = json.load(f).get('count', 0) + except: + pass + + conn = sqlite3.connect(DB) + conn.execute("INSERT OR REPLACE INTO history VALUES (?, ?, ?, ?, ?, ?)", + (int(time.time()), pv, pu, pp, uf, ap)) + conn.commit() + conn.close() + + return {'portfolio_value': round(pv, 2), 'pnl_usdt': round(pu, 2), 'pnl_pct': round(pp, 2), + 'usdt_free': round(uf, 2), 'active_positions': ap, 'balance': bal, 'prices': prices} except Exception as e: return {'error': str(e)} +@app.get('/api/pnl-history') +async def history(hours: int = 24): + conn = sqlite3.connect(DB) + cutoff = int(time.time()) - hours * 3600 + rows = conn.execute("SELECT ts, pp, pu FROM history WHERE ts > ? ORDER BY ts", (cutoff,)).fetchall() + conn.close() + + ts_list, pcts, usdts = [], [], [] + for t, p, u in rows: + dt = datetime.fromtimestamp(t) + ts_list.append(dt.strftime('%H:%M' if hours <= 24 else '%m-%d')) + pcts.append(round(p, 2)) + usdts.append(round(u, 2)) + + return {'timestamps': ts_list, 'pnl_pcts': pcts, 'pnl_usdts': usdts, + 'current_pct': pcts[-1] if pcts else 0, 'min_pct': min(pcts) if pcts else 0, + 'max_pct': max(pcts) if pcts else 0, 'avg_pct': sum(pcts)/len(pcts) if pcts else 0} + +@app.get('/') +async def dashboard(): + return HTMLResponse("""Trading Bot v0.32

🤖 Trading Bot v0.32

P&L Analytics

● LOADING
Portfolio
-
P&L
-
-
USDT
-
Trades
-

Holdings

📈 P&L Performance (Live)
Current
-
Min
-
Max
-
Avg
-
""") if __name__ == '__main__': import uvicorn