Bot auto-update: src/web_dashboard.py
This commit is contained in:
parent
a8a44cf54a
commit
a6a832a4e7
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue