Bot auto-update: src/web_dashboard.py

This commit is contained in:
Marc Blatter 2026-07-04 16:45:01 +02:00
parent a8a44cf54a
commit a6a832a4e7
1 changed files with 153 additions and 94 deletions

View File

@ -1,107 +1,166 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, FileResponse
from datetime import datetime from fastapi.staticfiles import StaticFiles
import asyncio from fastapi.middleware.cors import CORSMiddleware
import json
app = FastAPI() app = FastAPI()
trading_state = { app.add_middleware(
"current_trades": {}, CORSMiddleware,
"completed_trades": [], allow_origins=['*'],
"balance": {"USDT": 0.0}, allow_credentials=True,
"trades_today": 0, allow_methods=['*'],
"daily_pnl": 0.0, allow_headers=['*'],
"wins_today": 0, )
"last_update": datetime.now().isoformat()
# Global state
state = {
'current_trades': {},
'completed_trades': [],
'balance': {'USDT': {'free': 0.0}},
'trades_today': 0,
'daily_pnl': 0.0,
'wins_today': 0,
'losses_today': 0,
'last_update': '',
} }
@app.post("/api/update") @app.post('/api/update')
async def update(data: dict): async def update_state(data: dict):
global trading_state global state
trading_state = data state.update(data)
trading_state["last_update"] = datetime.now().isoformat() return {'status': 'ok'}
return {"status": "ok"}
@app.get("/api/state") @app.get('/api/state')
async def get_state(): async def get_state():
return trading_state return state
@app.get("/") @app.get('/', response_class=HTMLResponse)
async def dashboard(): async def dashboard():
html = """<!DOCTYPE html><html><head><title>Bot</title><style> html = '''<!DOCTYPE html>
body{background:#0a0e27;color:#fff;font-family:monospace;margin:0;padding:20px} <html>
h1{color:#00ff88} <head>
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;margin:30px 0} <title>Bot Dashboard</title>
.metric{background:rgba(255,255,255,0.1);border:1px solid #00ff88;padding:20px;border-radius:5px} <style>
.metric-label{color:#aaa;font-size:12px} * { box-sizing: border-box; }
.metric-value{font-size:24px;color:#00ff88;margin-top:10px} body { background: #0a0e27; color: #fff; font-family: monospace; margin: 0; padding: 20px; }
.trade-card{background:rgba(255,255,255,0.08);border:1px solid #444;padding:15px;margin:10px 0;border-radius:5px} h1 { color: #00ff88; margin: 0 0 30px 0; }
.trade-pair{font-size:16px;color:#00ff88;font-weight:bold} .metrics { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; margin-bottom: 40px; }
.profit-pos{color:#00ff88} @media (max-width: 1200px) { .metrics { grid-template-columns: repeat(3, 1fr); } }
.profit-neg{color:#ff4444} @media (max-width: 768px) { .metrics { grid-template-columns: repeat(2, 1fr); } }
</style></head><body><div style="max-width:1200px;margin:0 auto"> .metric { background: rgba(255,255,255,0.1); border: 2px solid #00ff88; padding: 20px; border-radius: 8px; text-align: center; }
.metric-label { color: #aaa; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }
.metric-value { font-size: 28px; color: #00ff88; margin-top: 12px; font-weight: bold; }
.section { margin: 40px 0; }
.section h2 { color: #00ff88; font-size: 18px; margin: 0 0 15px 0; border-bottom: 1px solid #333; padding-bottom: 10px; }
.trade-card { background: rgba(255,255,255,0.05); border-left: 3px solid #00ff88; padding: 15px; margin: 10px 0; border-radius: 4px; }
.trade-pair { color: #00ff88; font-weight: bold; margin-bottom: 5px; }
.trade-detail { color: #aaa; font-size: 12px; }
.empty { color: #666; padding: 20px; text-align: center; }
.footer { margin-top: 50px; padding-top: 20px; border-top: 1px solid #333; color: #666; font-size: 12px; }
</style>
</head>
<body>
<h1>Trading Bot Dashboard</h1> <h1>Trading Bot Dashboard</h1>
<div class="metrics"> <div class=metrics>
<div class="metric"><div class="metric-label">LIQUID USDT</div><div class="metric-value">$<span id="usdt">0.00</span></div></div> <div class=metric><div class=metric-label>Liquid USDT</div><div class=metric-value id=usdt>bash.00</div></div>
<div class="metric"><div class="metric-label">TRADES TODAY</div><div class="metric-value"><span id="trades">0</span></div></div> <div class=metric><div class=metric-label>Trades Today</div><div class=metric-value id=trades>0</div></div>
<div class="metric"><div class="metric-label">DAILY P&L</div><div class="metric-value"><span id="pnl">$0.00</span></div></div> <div class=metric><div class=metric-label>Daily P&L</div><div class=metric-value id=pnl>bash.00</div></div>
<div class="metric"><div class="metric-label">WIN RATE</div><div class="metric-value"><span id="wr">0%</span></div></div> <div class=metric><div class=metric-label>Win Rate</div><div class=metric-value id=wr>0%</div></div>
<div class="metric"><div class="metric-label">OPEN</div><div class="metric-value"><span id="open">0</span></div></div> <div class=metric><div class=metric-label>Open Positions</div><div class=metric-value id=open>0</div></div>
</div> </div>
<h2>Open Trades</h2><div id="open_trades"><div class="trade-card">No open trades</div></div>
<h2>Closed Trades</h2><div id="closed_trades"><div class="trade-card">No closed trades</div></div> <div class=section>
<div style="margin-top:30px;font-size:12px;color:#666">Last update: <span id="last">-</span></div> <h2>Open Trades</h2>
<div id=open_trades><div class=empty>No open trades</div></div>
</div> </div>
<div class=section>
<h2>Closed Trades</h2>
<div id=closed_trades><div class=empty>No closed trades</div></div>
</div>
<div class=footer>
Last update: <span id=last>-</span>
</div>
<script> <script>
async function refresh() { async function refresh() {
try { try {
const r = await fetch("http://172.16.1.168:7000/api/state"); const resp = await fetch('/api/state');
const d = await r.json(); const d = await resp.json();
const usdt_data = d.balance && d.balance.USDT ? d.balance.USDT : {free: 0}; // Update metrics
const usdt = parseFloat(usdt_data.free) || 0; if (d.balance && d.balance.USDT) {
console.log("USDT from API:", usdt_data, "parsed:", usdt); const usdt = parseFloat(d.balance.USDT.free) || 0;
document.getElementById("usdt").textContent = usdt.toFixed(2); document.getElementById('usdt').textContent = '$' + usdt.toFixed(2);
document.getElementById("trades").textContent = d.trades_today || 0; }
const wr = d.trades_today > 0 ? Math.round((d.wins_today || 0) / d.trades_today * 100) : 0; document.getElementById('trades').textContent = (d.trades_today || 0);
document.getElementById("wr").textContent = wr + "%";
const pnl = d.daily_pnl || 0; const pnl = parseFloat(d.daily_pnl) || 0;
document.getElementById("pnl").textContent = (pnl >= 0 ? "$" : "-$") + Math.abs(pnl).toFixed(2); const pnlText = (pnl >= 0 ? '$' : '-$') + Math.abs(pnl).toFixed(2);
document.getElementById('pnl').textContent = pnlText;
document.getElementById("open").textContent = Object.keys(d.current_trades || {}).length; const wr = d.trades_today > 0 ? Math.round(((d.wins_today || 0) / d.trades_today) * 100) : 0;
document.getElementById("last").textContent = new Date(d.last_update).toLocaleTimeString(); document.getElementById('wr').textContent = wr + '%';
let open_html = ""; const openCount = Object.keys(d.current_trades || {}).length;
if(Object.keys(d.current_trades || {}).length === 0){ document.getElementById('open').textContent = openCount;
open_html = "<div class=\"trade-card\">No open trades</div>";
if (d.last_update) {
const t = new Date(d.last_update);
document.getElementById('last').textContent = t.toLocaleTimeString();
}
// Open trades
const openDiv = document.getElementById('open_trades');
const trades = d.current_trades || {};
if (Object.keys(trades).length === 0) {
openDiv.innerHTML = '<div class=empty>No open trades</div>';
} else { } else {
for(const p in d.current_trades){ let html = '';
const t = d.current_trades[p]; for (const pair in trades) {
open_html += "<div class=\"trade-card\"><div class=\"trade-pair\">" + p + "</div><div>Qty: " + t.qty.toFixed(4) + " @ $" + t.buy_price.toFixed(2) + "</div></div>"; const t = trades[pair];
html += '<div class=trade-card>';
html += '<div class=trade-pair>' + pair + '</div>';
html += '<div class=trade-detail>Qty: ' + parseFloat(t.qty).toFixed(4) + ' @ $' + parseFloat(t.buy_price).toFixed(2) + '</div>';
html += '</div>';
} }
openDiv.innerHTML = html;
} }
document.getElementById("open_trades").innerHTML = open_html;
let closed_html = ""; // Closed trades
if(!d.completed_trades || d.completed_trades.length === 0){ const closedDiv = document.getElementById('closed_trades');
closed_html = "<div class=\"trade-card\">No closed trades</div>"; const closed = d.completed_trades || [];
if (closed.length === 0) {
closedDiv.innerHTML = '<div class=empty>No closed trades</div>';
} else { } else {
for(const t of (d.completed_trades || []).slice(-10).reverse()){ let html = '';
const p_class = t.profit_usd >= 0 ? "profit-pos" : "profit-neg"; for (const trade of closed.slice(-5)) {
closed_html += "<div class=\"trade-card\"><div class=\"trade-pair\">" + t.pair + "</div><div><span class=\"" + p_class + "\">" + t.profit_usd.toFixed(2) + "</span> (" + (t.profit_pct*100).toFixed(2) + "%)</div></div>"; const profit = parseFloat(trade.profit_usd) || 0;
const color = profit > 0 ? '#00ff88' : '#ff0047';
html += '<div class=trade-card style=border-left-color: + color + >';
html += '<div class=trade-pair>' + trade.pair + '</div>';
html += '<div class=trade-detail>' + profit.toFixed(2) + '$ (' + (parseFloat(trade.profit_pct) * 100).toFixed(1) + '%)</div>';
html += '</div>';
}
closedDiv.innerHTML = html;
}
} catch (e) {
console.error('Error:', e);
} }
} }
document.getElementById("closed_trades").innerHTML = closed_html;
}catch(e){}
setTimeout(refresh, 1000);
}
refresh(); refresh();
</script></body></html>"""; setInterval(refresh, 1000);
return HTMLResponse(html) </script>
</body>
</html>'''
return html
if __name__ == "__main__": if __name__ == '__main__':
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7000) uvicorn.run(app, host='0.0.0.0', port=7000)