Dashboard V7: Complete Redesign with Grayscale Theme
- NEW: Sidebar navigation (left panel with menu) - NEW: Grayscale color scheme (#1e1e1e, #2a2a2a, #404040) - NEW: Green accents only for key metrics (Portfolio, USD Values) - REMOVED: Footer text entirely - IMPROVED: Professional spacing and layout - STYLE: Monospace font, clean typography - RESPONSIVE: Mobile-optimized layout - PRICES: 2 decimal precision across all assets - HOLDINGS: Detailed breakdown with USD values in green
This commit is contained in:
parent
6e6c9af51a
commit
05fcb8ad9a
|
|
@ -14,36 +14,28 @@ with open('/home/marc/bot-deploy/.env') as f:
|
||||||
|
|
||||||
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
|
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
|
||||||
|
|
||||||
# CACHE for prices (update every 5 seconds)
|
|
||||||
price_cache = {'prices': {}, 'timestamp': 0}
|
price_cache = {'prices': {}, 'timestamp': 0}
|
||||||
|
|
||||||
def get_live_prices():
|
def get_live_prices():
|
||||||
"""Get LIVE prices from Binance API with 5-second cache"""
|
|
||||||
global price_cache
|
global price_cache
|
||||||
|
|
||||||
# Return cached if less than 5 seconds old
|
|
||||||
if time.time() - price_cache['timestamp'] < 5:
|
if time.time() - price_cache['timestamp'] < 5:
|
||||||
return price_cache['prices']
|
return price_cache['prices']
|
||||||
|
|
||||||
prices = {'USDT': 1.0}
|
prices = {'USDT': 1.0}
|
||||||
|
|
||||||
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
|
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
try:
|
try:
|
||||||
ticker = binance.get_ticker(symbol=pair)
|
ticker = binance.get_ticker(symbol=pair)
|
||||||
asset = pair.replace('USDT', '')
|
asset = pair.replace('USDT', '')
|
||||||
prices[asset] = float(ticker['lastPrice'])
|
prices[asset] = float(ticker['lastPrice'])
|
||||||
except Exception as e:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Update cache
|
|
||||||
price_cache['prices'] = prices
|
price_cache['prices'] = prices
|
||||||
price_cache['timestamp'] = time.time()
|
price_cache['timestamp'] = time.time()
|
||||||
|
|
||||||
return prices
|
return prices
|
||||||
|
|
||||||
def load_bot_state():
|
def load_bot_state():
|
||||||
"""Load bot state from file"""
|
|
||||||
state_file = '/home/marc/bot-deploy/trades.json'
|
state_file = '/home/marc/bot-deploy/trades.json'
|
||||||
if os.path.exists(state_file):
|
if os.path.exists(state_file):
|
||||||
try:
|
try:
|
||||||
|
|
@ -55,9 +47,7 @@ def load_bot_state():
|
||||||
|
|
||||||
@app.get('/api/state')
|
@app.get('/api/state')
|
||||||
async def get_state():
|
async def get_state():
|
||||||
"""Return complete bot state with LIVE prices"""
|
|
||||||
try:
|
try:
|
||||||
# Get balance from Binance ONCE (not per asset)
|
|
||||||
account = binance.get_account()
|
account = binance.get_account()
|
||||||
balance = {}
|
balance = {}
|
||||||
|
|
||||||
|
|
@ -67,7 +57,6 @@ async def get_state():
|
||||||
locked = float(asset_data['locked'])
|
locked = float(asset_data['locked'])
|
||||||
total = free + locked
|
total = free + locked
|
||||||
|
|
||||||
# Only include meaningful balances
|
|
||||||
if total > 0.00001:
|
if total > 0.00001:
|
||||||
balance[asset] = {
|
balance[asset] = {
|
||||||
'free': free,
|
'free': free,
|
||||||
|
|
@ -75,11 +64,8 @@ async def get_state():
|
||||||
'total': total
|
'total': total
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get cached prices (5-second update)
|
|
||||||
prices = get_live_prices()
|
prices = get_live_prices()
|
||||||
|
|
||||||
# Calculate portfolio value with LIVE prices
|
|
||||||
# ONLY for known trading pairs (exclude junk tokens)
|
|
||||||
portfolio_value = 0
|
portfolio_value = 0
|
||||||
tracked_assets = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
|
tracked_assets = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
|
||||||
|
|
||||||
|
|
@ -89,10 +75,7 @@ async def get_state():
|
||||||
price = prices.get(asset, 0)
|
price = prices.get(asset, 0)
|
||||||
portfolio_value += data['total'] * price
|
portfolio_value += data['total'] * price
|
||||||
|
|
||||||
# Get USDT free specifically
|
|
||||||
usdt_free = balance.get('USDT', {}).get('free', 0)
|
usdt_free = balance.get('USDT', {}).get('free', 0)
|
||||||
|
|
||||||
# Load trade state
|
|
||||||
trades = load_bot_state()
|
trades = load_bot_state()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -102,8 +85,7 @@ async def get_state():
|
||||||
'current_trades': trades.get('current', {}),
|
'current_trades': trades.get('current', {}),
|
||||||
'completed_trades': trades.get('completed', []),
|
'completed_trades': trades.get('completed', []),
|
||||||
'prices': prices,
|
'prices': prices,
|
||||||
'timestamp': datetime.now().isoformat(),
|
'timestamp': datetime.now().isoformat()
|
||||||
'note': 'Portfolio calculated with LIVE Binance prices (tracked assets only, excludes junk tokens)'
|
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {'error': str(e), 'portfolio_value': 0, 'usdt_free': 0}
|
return {'error': str(e), 'portfolio_value': 0, 'usdt_free': 0}
|
||||||
|
|
@ -121,76 +103,269 @@ async def root():
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Trading Bot Dashboard V6</title>
|
<title>Trading Bot V7</title>
|
||||||
<style>
|
<style>
|
||||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||||
body {{ font-family: monospace; background: #0a0a0a; color: #00ff88; padding: 20px; }}
|
|
||||||
.container {{ max-width: 1200px; margin: 0 auto; }}
|
body {{
|
||||||
h1 {{ font-size: 28px; margin-bottom: 20px; }}
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
.metrics {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin: 20px 0; }}
|
background: #1e1e1e;
|
||||||
.metric {{ padding: 15px; background: #1a1a1a; border: 1px solid #00ff88; border-left: 3px solid #00ff88; }}
|
color: #d0d0d0;
|
||||||
.metric-value {{ font-size: 20px; font-weight: bold; }}
|
padding: 0;
|
||||||
.metric-label {{ font-size: 11px; color: #666; margin-top: 5px; }}
|
margin: 0;
|
||||||
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
|
}}
|
||||||
th {{ background: #00ff88; color: #000; padding: 10px; text-align: left; }}
|
|
||||||
td {{ padding: 10px; border-bottom: 1px solid #333; }}
|
.container {{
|
||||||
tr:nth-child(even) {{ background: #0d0d0d; }}
|
display: flex;
|
||||||
.status {{ color: #00ff88; font-weight: bold; }}
|
min-height: 100vh;
|
||||||
.note {{ color: #666; font-size: 12px; margin-top: 20px; padding: 10px; background: #1a1a1a; border-left: 2px solid #00ff88; }}
|
}}
|
||||||
|
|
||||||
|
.sidebar {{
|
||||||
|
width: 240px;
|
||||||
|
background: #252525;
|
||||||
|
border-right: 1px solid #404040;
|
||||||
|
padding: 30px 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.sidebar h1 {{
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #00ff88;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.sidebar-item {{
|
||||||
|
padding: 12px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #a0a0a0;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
padding-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.sidebar-item:hover {{
|
||||||
|
color: #00ff88;
|
||||||
|
border-left-color: #00ff88;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.sidebar-item.active {{
|
||||||
|
color: #00ff88;
|
||||||
|
border-left-color: #00ff88;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.main {{
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.header {{
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.logo {{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #00ff88;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.version {{
|
||||||
|
font-size: 12px;
|
||||||
|
color: #808080;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metrics-grid {{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metric-card {{
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid #404040;
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metric-card:hover {{
|
||||||
|
background: #303030;
|
||||||
|
border-color: #505050;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metric-label {{
|
||||||
|
font-size: 11px;
|
||||||
|
color: #808080;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metric-value {{
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metric-value.accent {{
|
||||||
|
color: #00ff88;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.section-title {{
|
||||||
|
font-size: 13px;
|
||||||
|
color: #808080;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-top: 40px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #404040;
|
||||||
|
}}
|
||||||
|
|
||||||
|
table {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
th {{
|
||||||
|
background: #2a2a2a;
|
||||||
|
color: #a0a0a0;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 1px solid #404040;
|
||||||
|
}}
|
||||||
|
|
||||||
|
td {{
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #303030;
|
||||||
|
font-size: 13px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
tr:hover {{
|
||||||
|
background: #262626;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.price-positive {{
|
||||||
|
color: #00ff88;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.price-neutral {{
|
||||||
|
color: #d0d0d0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {{
|
||||||
|
.container {{
|
||||||
|
flex-direction: column;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.sidebar {{
|
||||||
|
width: 100%;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid #404040;
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metrics-grid {{
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.main {{
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {{
|
||||||
|
.metrics-grid {{
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.metric-value {{
|
||||||
|
font-size: 20px;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>💰 Trading Bot V6 Dashboard</h1>
|
<div class="sidebar">
|
||||||
|
<h1>⚙️ Bot Status</h1>
|
||||||
|
<div class="sidebar-item active">
|
||||||
|
<span>🟢 Running</span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-item">
|
||||||
|
<span>📊 Analytics</span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-item">
|
||||||
|
<span>⚡ Performance</span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-item">
|
||||||
|
<span>🔧 Settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="metrics">
|
<div class="main">
|
||||||
<div class="metric">
|
<div class="header">
|
||||||
<div class="metric-label">PORTFOLIO VALUE</div>
|
<div class="logo">💰 Trading Bot</div>
|
||||||
<div class="metric-value">${portfolio_val:.2f}</div>
|
<div class="version">V7 — Real-time Portfolio</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
|
||||||
<div class="metric-label">USDT FREE</div>
|
|
||||||
<div class="metric-value">${usdt_free:.2f}</div>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<div class="metric-label">OPEN TRADES</div>
|
|
||||||
<div class="metric-value">{trades_count}</div>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<div class="metric-label">STATUS</div>
|
|
||||||
<div class="status">🟢 LIVE</div>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<div class="metric-label">BOT VERSION</div>
|
|
||||||
<div class="metric-value">V6 ENHANCED</div>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<div class="metric-label">LAST UPDATE</div>
|
|
||||||
<div class="metric-value">REAL-TIME</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>📊 CURRENT PRICES (LIVE from Binance)</h2>
|
<div class="metrics-grid">
|
||||||
<table>
|
<div class="metric-card">
|
||||||
<tr>
|
<div class="metric-label">Portfolio Value</div>
|
||||||
<th>Asset</th>
|
<div class="metric-value">${portfolio_val:.2f}</div>
|
||||||
<th>Price USD</th>
|
</div>
|
||||||
</tr>'''
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">USDT Available</div>
|
||||||
|
<div class="metric-value accent">${usdt_free:.2f}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">Open Positions</div>
|
||||||
|
<div class="metric-value">{trades_count}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Live Prices</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Asset</th>
|
||||||
|
<th>Price (USD)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>'''
|
||||||
|
|
||||||
for asset, price in prices.items():
|
for asset, price in prices.items():
|
||||||
html += f'<tr><td>{asset}</td><td>${price:.2f}</td></tr>'
|
html += f'''<tr>
|
||||||
|
<td>{asset}</td>
|
||||||
|
<td class="price-neutral">${price:.2f}</td>
|
||||||
|
</tr>'''
|
||||||
|
|
||||||
html += '''</table>
|
html += '''</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<h2>💰 BALANCE BREAKDOWN</h2>
|
<div class="section-title">Holdings</div>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<thead>
|
||||||
<th>Asset</th>
|
<tr>
|
||||||
<th>Free</th>
|
<th>Asset</th>
|
||||||
<th>Locked</th>
|
<th>Free</th>
|
||||||
<th>Total</th>
|
<th>Locked</th>
|
||||||
<th>Value</th>
|
<th>Total</th>
|
||||||
</tr>'''
|
<th>USD Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>'''
|
||||||
|
|
||||||
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
|
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
|
||||||
balance = state.get('balance', {})
|
balance = state.get('balance', {})
|
||||||
|
|
@ -201,26 +376,16 @@ tr:nth-child(even) {{ background: #0d0d0d; }}
|
||||||
price = prices.get(asset, 0)
|
price = prices.get(asset, 0)
|
||||||
value = data['total'] * price
|
value = data['total'] * price
|
||||||
html += f'''<tr>
|
html += f'''<tr>
|
||||||
<td>{asset}</td>
|
<td>{asset}</td>
|
||||||
<td>{data['free']:.6f}</td>
|
<td>{data['free']:.6f}</td>
|
||||||
<td>{data['locked']:.6f}</td>
|
<td>{data['locked']:.6f}</td>
|
||||||
<td>{data['total']:.6f}</td>
|
<td>{data['total']:.6f}</td>
|
||||||
<td>${value:.2f}</td>
|
<td class="price-positive">${value:.2f}</td>
|
||||||
</tr>'''
|
</tr>'''
|
||||||
|
|
||||||
html += '''</table>
|
html += '''</tbody>
|
||||||
|
</table>
|
||||||
<div class="note">
|
</div>
|
||||||
✅ <strong>Portfolio:</strong> Calculated from LIVE Binance prices (tracked assets only)<br>
|
|
||||||
✅ <strong>Cache:</strong> Prices cached for 5 seconds (fast response)<br>
|
|
||||||
✅ <strong>Accuracy:</strong> Real Binance Spot balance<br>
|
|
||||||
<br>
|
|
||||||
Tracked assets: BTC, ETH, SOL, BNB, XRP, USDT, USDC<br>
|
|
||||||
(Excludes junk/dust tokens)<br>
|
|
||||||
<br>
|
|
||||||
<strong>V6:</strong> 2 decimal precision for all prices
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>'''
|
</html>'''
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue