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