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">
<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>
<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>
<script>
async function refresh(){
try{
const r = await fetch("http://172.16.1.168:7000/api/state");
const d = await r.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;
const wr = d.trades_today > 0 ? Math.round((d.wins_today || 0) / d.trades_today * 100) : 0;
document.getElementById("wr").textContent = wr + "%";
const pnl = d.daily_pnl || 0;
document.getElementById("pnl").textContent = (pnl >= 0 ? "$" : "-$") + Math.abs(pnl).toFixed(2);
document.getElementById("open").textContent = Object.keys(d.current_trades || {}).length;
document.getElementById("last").textContent = new Date(d.last_update).toLocaleTimeString();
let open_html = "";
if(Object.keys(d.current_trades || {}).length === 0){
open_html = "<div class=\"trade-card\">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>";
}
}
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>";
}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>";
}
}
document.getElementById("closed_trades").innerHTML = closed_html;
}catch(e){}
setTimeout(refresh, 1000);
}
refresh();
</script></body></html>""";
return HTMLResponse(html)
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 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>
if __name__ == "__main__":
<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 resp = await fetch('/api/state');
const d = await resp.json();
// Update metrics
if (d.balance && d.balance.USDT) {
const usdt = parseFloat(d.balance.USDT.free) || 0;
document.getElementById('usdt').textContent = '$' + usdt.toFixed(2);
}
document.getElementById('trades').textContent = (d.trades_today || 0);
const pnl = parseFloat(d.daily_pnl) || 0;
const pnlText = (pnl >= 0 ? '$' : '-$') + Math.abs(pnl).toFixed(2);
document.getElementById('pnl').textContent = pnlText;
const wr = d.trades_today > 0 ? Math.round(((d.wins_today || 0) / d.trades_today) * 100) : 0;
document.getElementById('wr').textContent = wr + '%';
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 {
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;
}
// 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 {
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);
}
}
refresh();
setInterval(refresh, 1000);
</script>
</body>
</html>'''
return html
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)