BrainDock/src/web_dashboard.OLD

93 lines
9.0 KiB
Python

#!/usr/bin/env python3
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from binance.client import Client
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:
k, _, v = line.partition('=')
env[k.strip()] = v.strip()
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
DB = '/home/marc/bot-deploy/pnl_charts.db'
def init_db():
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()
@app.get('/api/state')
async def state():
try:
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}
prices = {'USDT': 1.0}
for p in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
try:
t = binance.get_ticker(symbol=p)
prices[p.replace('USDT', '')] = float(t['lastPrice'])
except:
pass
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("""<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><title>Trading Bot v0.6</title><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:Segoe UI,Arial;background:#1e1e1e;color:#d0d0d0;min-height:100vh;padding:20px}.container{max-width:1400px;margin:0 auto}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:30px;padding:20px;background:rgba(0,255,136,.05);border:1px solid rgba(0,255,136,.2);border-radius:10px}.header h1{font-size:28px;color:#00ff88}.status{padding:8px 16px;background:rgba(0,255,136,.1);border:2px solid #00ff88;border-radius:20px;font-weight:700}.tabs{display:flex;gap:10px;margin-bottom:20px}.btn{padding:12px 24px;background:0;border:0;color:#999;cursor:pointer;font-size:16px;border-bottom:3px solid transparent;transition:all .3s}.btn:hover{color:#00ff88}.btn.active{color:#00ff88;border-bottom-color:#00ff88}.tab{display:none}.tab.active{display:block}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;margin-bottom:30px}.card{background:rgba(255,255,255,.03);border:1px solid rgba(0,255,136,.2);border-radius:10px;padding:20px;transition:all .3s}.card:hover{border-color:rgba(0,255,136,.5)}.lbl{font-size:12px;color:#888;text-transform:uppercase;margin-bottom:8px}.val{font-size:24px;color:#00ff88;font-weight:700}.sub{font-size:14px;color:#999}.chart-box{background:rgba(255,255,255,.03);border:1px solid rgba(0,255,136,.2);border-radius:10px;padding:20px}.title{font-size:18px;color:#00ff88;margin-bottom:20px;font-weight:700}.times{display:flex;gap:10px;margin-bottom:20px}.time{padding:8px 16px;background:rgba(0,255,136,.1);border:1px solid rgba(0,255,136,.3);color:#00ff88;border-radius:5px;cursor:pointer}.time:hover{background:rgba(0,255,136,.2)}.time.active{background:rgba(0,255,136,.3)}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:15px;margin-top:20px}.stat{background:rgba(0,255,136,.05);border:1px solid rgba(0,255,136,.15);padding:15px;border-radius:8px;text-align:center}.stat-l{font-size:11px;color:#888;text-transform:uppercase;margin-bottom:5px}.stat-v{font-size:18px;color:#00ff88;font-weight:700}</style></head><body><div class="container"><div class="header"><div><h1>🤖 Trading Bot v0.6</h1><p>P&L Analytics</p></div><div class="status" id="st">● LOADING</div></div><div class="tabs"><button class="btn active" onclick="t(event,'p')">📊 Portfolio</button><button class="btn" onclick="t(event,'a')">📈 Analytics</button></div><div id="p" class="tab active"><div class="grid"><div class="card"><div class="lbl">Portfolio</div><div class="val" id="pv">-</div></div><div class="card"><div class="lbl">P&L</div><div class="val" id="pl">-</div><div class="sub" id="pp">-</div></div><div class="card"><div class="lbl">USDT</div><div class="val" id="uf">-</div></div><div class="card"><div class="lbl">Trades</div><div class="val" id="tr">-</div></div></div><h3 style="color:#00ff88;margin:30px 0 20px 0">Holdings</h3><div class="grid" id="h"></div></div><div id="a" class="tab"><div class="chart-box"><div class="title">📈 P&L Performance (Live)</div><div class="times"><button class="time active" onclick="c(24,event)">1 Day</button><button class="time" onclick="c(168,event)">1 Week</button><button class="time" onclick="c(720,event)">1 Month</button></div><canvas id="ch" height="100"></canvas><div class="stats"><div class="stat"><div class="stat-l">Current</div><div class="stat-v" id="cur">-</div></div><div class="stat"><div class="stat-l">Min</div><div class="stat-v" id="mn">-</div></div><div class="stat"><div class="stat-l">Max</div><div class="stat-v" id="mx">-</div></div><div class="stat"><div class="stat-l">Avg</div><div class="stat-v" id="av">-</div></div></div></div></div></div><script>let ch_o=null;function t(e,d){document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active')),document.querySelectorAll('.btn').forEach(x=>x.classList.remove('active')),document.getElementById(d).classList.add('active'),e.target.classList.add('active')}async function u(){const r=await fetch('/api/state'),d=await r.json();if(d.error)return;document.getElementById('pv').textContent='$'+d.portfolio_value.toFixed(2),document.getElementById('pl').textContent='$'+d.pnl_usdt.toFixed(2),document.getElementById('pp').textContent=d.pnl_pct.toFixed(2)+'%',document.getElementById('uf').textContent='$'+d.usdt_free.toFixed(2),document.getElementById('tr').textContent=d.active_positions,document.getElementById('st').textContent='● LIVE';const h=document.getElementById('h');h.innerHTML='';for(const[a,i]of Object.entries(d.balance))'USDT'!==a&&i.total>1e-4&&(h.innerHTML+=`<div class="card"><div class="lbl">${a}</div><div class="val">${i.total.toFixed(8)}</div></div>`)}async function c(h,e){e&&(document.querySelectorAll('.time').forEach(x=>x.classList.remove('active')),e.target.classList.add('active'));const r=await fetch('/api/pnl-history?hours='+h),d=await r.json(),x=document.getElementById('ch').getContext('2d');ch_o&&ch_o.destroy();const l=d.current_pct>=0?'#00ff88':'#ff4444',b=d.current_pct>=0?'rgba(0,255,136,0.1)':'rgba(255,68,68,0.1)';ch_o=new Chart(x,{type:'line',data:{labels:d.timestamps,datasets:[{label:'P&L %',data:d.pnl_pcts,borderColor:l,backgroundColor:b,fill:!0,tension:.4,pointRadius:2,pointBackgroundColor:l,borderWidth:2}]},options:{responsive:!0,maintainAspectRatio:!0,plugins:{legend:{labels:{color:'#888'}}},scales:{y:{grid:{color:'rgba(0,255,136,0.1)'},ticks:{color:'#888'}},x:{grid:{color:'rgba(0,255,136,0.1)'},ticks:{color:'#888'}}}}}),document.getElementById('cur').textContent=d.current_pct.toFixed(2)+'%',document.getElementById('mn').textContent=d.min_pct.toFixed(2)+'%',document.getElementById('mx').textContent=d.max_pct.toFixed(2)+'%',document.getElementById('av').textContent=d.avg_pct.toFixed(2)+'%'}setInterval(u,1e4),u()</script></body></html>""")
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=7000)