From 51ac330a2d8b259a2c58176a4e272920e65fa8b2 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Mon, 6 Jul 2026 22:08:39 +0200 Subject: [PATCH] Dashboard: Integrate live P&L display into bot.bizmark.cloud (V10) --- web_dashboard.py | 646 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 646 insertions(+) create mode 100644 web_dashboard.py diff --git a/web_dashboard.py b/web_dashboard.py new file mode 100644 index 0000000..052051d --- /dev/null +++ b/web_dashboard.py @@ -0,0 +1,646 @@ +#!/usr/bin/env python3 +from fastapi import FastAPI, Response +from binance.client import Client +import json, os, time +from datetime import datetime + +app = FastAPI() + +env = {} +with open('/home/marc/bot-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(): + try: + account = binance.get_account() + balance = {} + + 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) + + # Count active positions = locked coins (NOT trades.json) + 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', {}), + '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 V10 + + + +
+
+ +
V10 — Real-time Portfolio Dashboard
+
+ +
+
+
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', {}) + 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() + } + except Exception as e: + return {'error': str(e)} + + +if __name__ == '__main__': + import uvicorn + uvicorn.run(app, host='0.0.0.0', port=7000)