From df2d0b4883aedbffb612e4593209fef2c516c00a Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Wed, 29 Jul 2026 11:00:01 +0200 Subject: [PATCH] Bot auto-update: src/__pycache__/web_dashboard.cpython-310.pyc,src/web_dashboard.OLD,src/web_dashboard.py,src/web_dashboard_REAL.py --- src/__pycache__/web_dashboard.cpython-310.pyc | Bin 13721 -> 9406 bytes src/web_dashboard.OLD | 92 ++++++++++++++++ src/web_dashboard.py | 101 +++++++----------- src/web_dashboard_REAL.py | 92 ++++++++++++++++ 4 files changed, 221 insertions(+), 64 deletions(-) create mode 100644 src/web_dashboard.OLD create mode 100644 src/web_dashboard_REAL.py diff --git a/src/__pycache__/web_dashboard.cpython-310.pyc b/src/__pycache__/web_dashboard.cpython-310.pyc index 152746a78b83e188a266183719e315c41d89f09e..8d7094d4ee43beaccf48a07ca1d0572c1053db1f 100644 GIT binary patch delta 4248 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 ik5|5VIfiyA9z!y(n9L%(@U@gE{4A*@k>nfw 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("""Trading Bot v0.6

🤖 Trading Bot v0.6

P&L Analytics

● LOADING
Portfolio
-
P&L
-
-
USDT
-
Trades
-

Holdings

📈 P&L Performance (Live)
Current
-
Min
-
Max
-
Avg
-
""") + +if __name__ == '__main__': + import uvicorn + uvicorn.run(app, host='0.0.0.0', port=7000) diff --git a/src/web_dashboard.py b/src/web_dashboard.py index 78d8af6..87e4a96 100644 --- a/src/web_dashboard.py +++ b/src/web_dashboard.py @@ -16,76 +16,49 @@ with open('/home/marc/bot-deploy/.env') as f: 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() +def get_chart_data(days=1): + conn = sqlite3.connect(DB) + rows = conn.execute(f"""SELECT ts, pv, pp FROM history WHERE ts > {int(time.time()) - days*86400} ORDER BY ts""").fetchall() + conn.close() + return [(datetime.fromtimestamp(r[0]).strftime('%H:%M'), r[2]) for r in rows] -init_db() +@app.get('/') +async def root(): + chart_1d = get_chart_data(1) + chart_7d = get_chart_data(7) + chart_30d = get_chart_data(30) + + html = f''' +Trading Bot v0.6 + +
+

🤖 Trading Bot v0.6

Contrarian Mean Reversion Strategy

+ +

Analytics

+

1-Day Chart: {len(chart_1d)} points

+

7-Day Chart: {len(chart_7d)} points

+

30-Day Chart: {len(chart_30d)} points

+
+ +
+
+
1D Performance
+{chart_1d[-1][1] if chart_1d else 0:.2f}%
+
7D Performance
+{chart_7d[-1][1] if chart_7d else 0:.2f}%
+
30D Performance
+{chart_30d[-1][1] if chart_30d else 0:.2f}%
+
+
+ +
''' + return HTMLResponse(html) @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("""Trading Bot v0.6

🤖 Trading Bot v0.6

P&L Analytics

● LOADING
Portfolio
-
P&L
-
-
USDT
-
Trades
-

Holdings

📈 P&L Performance (Live)
Current
-
Min
-
Max
-
Avg
-
""") + portfolio = sum(float(b['free']) * (1.0 if b['asset']=='USDT' else 0) for b in acc['balances']) + return {'portfolio': portfolio, 'status': 'ok'} + except: + return {'error': 'failed'} if __name__ == '__main__': import uvicorn diff --git a/src/web_dashboard_REAL.py b/src/web_dashboard_REAL.py new file mode 100644 index 0000000..78d8af6 --- /dev/null +++ b/src/web_dashboard_REAL.py @@ -0,0 +1,92 @@ +#!/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("""Trading Bot v0.6

🤖 Trading Bot v0.6

P&L Analytics

● LOADING
Portfolio
-
P&L
-
-
USDT
-
Trades
-

Holdings

📈 P&L Performance (Live)
Current
-
Min
-
Max
-
Avg
-
""") + +if __name__ == '__main__': + import uvicorn + uvicorn.run(app, host='0.0.0.0', port=7000)