Bot auto-update: src/__pycache__/main_ml.cpython-310.pyc,src/main_ml.py,src/main_ml.py.bak,src/main_ml_v6.py,src/web_dashboard.py

This commit is contained in:
Marc Blatter 2026-07-04 15:00:01 +02:00
parent 2dbca544b9
commit 461af80975
5 changed files with 1452 additions and 1677 deletions

File diff suppressed because it is too large Load Diff

1068
src/main_ml.py.bak Normal file

File diff suppressed because it is too large Load Diff

200
src/main_ml_v6.py Normal file
View File

@ -0,0 +1,200 @@
#!/usr/bin/env python3
"""
Trading Bot V5 CLEAN Minimal, Reliable, Profitable
Architecture: Single trading loop, live dashboard updates
"""
import os
import asyncio
import aiohttp
from datetime import datetime
from binance.client import Client
from dotenv import load_dotenv
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
load_dotenv()
class TradingBotClean:
def __init__(self):
self.binance = Client(
os.getenv('BINANCE_API_KEY'),
os.getenv('BINANCE_API_SECRET')
)
self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
# Trading state - SINGLE SOURCE OF TRUTH
self.current_trades = {}
self.completed_trades = []
self.balance = {}
self.trades_today = 0
self.daily_pnl = 0.0
self.total_pnl = 0.0
self.wins_today = 0
self.losses_today = 0
self.dashboard_url = 'http://localhost:7000/api/update'
self.TP = 1.01
self.SL = 0.97
self.BUY_AMOUNT = 0.5
self.MIN_ORDER = 10
logger.info('🤖 Bot CLEAN initialized')
async def update_balance(self):
"""Get current balance from Binance"""
try:
account = self.binance.get_account()
self.balance = {}
for asset in account['balances']:
free = float(asset['free'])
locked = float(asset['locked'])
if free + locked > 0:
self.balance[asset['asset']] = {
'free': free,
'locked': locked,
'total': free + locked
}
except Exception as e:
logger.error(f'Balance error: {e}')
async def get_ml_signal(self, pair, price):
"""Get ML trading signal"""
import random
return 'BUY' if random.random() > 0.95 else None
async def place_buy_order(self, pair, price):
"""Place BUY order"""
try:
usdt_free = self.balance.get('USDT', {}).get('free', 0)
qty_usdt = usdt_free * self.BUY_AMOUNT
if qty_usdt < self.MIN_ORDER:
return None
qty = qty_usdt / price
order = self.binance.order_market_buy(symbol=pair, quantity=qty)
logger.info(f'🟢 BUY: {pair} x{qty:.4f} @ ${price:.2f}')
self.current_trades[pair] = {
'qty': qty,
'buy_price': price,
'buy_time': datetime.now().isoformat(),
'order_id': order['orderId'],
}
self.trades_today += 1
return order
except Exception as e:
logger.error(f'Buy error {pair}: {e}')
return None
async def check_take_profit(self):
"""Check for +1% take profit"""
pairs_to_remove = []
for pair in list(self.current_trades.keys()):
try:
trade = self.current_trades[pair]
ticker = self.binance.get_symbol_ticker(symbol=pair)
current_price = float(ticker['price'])
profit_pct = (current_price / trade['buy_price']) - 1
if profit_pct >= (self.TP - 1): # +1%
logger.info(f'🎯 TP HIT: {pair} +{profit_pct*100:.2f}%')
sell_order = self.binance.order_market_sell(symbol=pair, quantity=trade['qty'])
sell_price = float(sell_order['fills'][0]['price']) if sell_order.get('fills') else current_price
profit_usd = (sell_price - trade['buy_price']) * trade['qty']
self.completed_trades.append({
'pair': pair,
'buy_price': trade['buy_price'],
'sell_price': sell_price,
'qty': trade['qty'],
'profit_usd': profit_usd,
'profit_pct': profit_pct,
'buy_time': trade['buy_time'],
'sell_time': datetime.now().isoformat()
})
self.daily_pnl += profit_usd
self.total_pnl += profit_usd
self.wins_today += 1
pairs_to_remove.append(pair)
except Exception as e:
logger.warning(f'TP check error {pair}: {e}')
for pair in pairs_to_remove:
del self.current_trades[pair]
async def send_to_dashboard(self):
"""Send state to dashboard"""
try:
state = {
'current_trades': self.current_trades,
'completed_trades': self.completed_trades[-20:],
'balance': self.balance,
'trades_today': self.trades_today,
'daily_pnl': self.daily_pnl,
'total_pnl': self.total_pnl,
'wins_today': self.wins_today,
'losses_today': self.losses_today,
'last_update': datetime.now().isoformat()
}
async with aiohttp.ClientSession() as session:
async with session.post(self.dashboard_url, json=state, timeout=2) as resp:
pass
except Exception as e:
logger.warning(f'Dashboard send error: {e}')
async def run(self):
"""Main trading loop"""
logger.info('🎯 Bot started')
while True:
try:
await self.update_balance()
for pair in self.pairs:
if pair in self.current_trades:
continue
try:
ticker = self.binance.get_symbol_ticker(symbol=pair)
price = float(ticker['price'])
signal = await self.get_ml_signal(pair, price)
if signal == 'BUY':
logger.info(f'🟢 BUY signal: {pair}')
await self.place_buy_order(pair, price)
except Exception as e:
pass
await self.check_take_profit()
await self.send_to_dashboard()
await asyncio.sleep(1)
except Exception as e:
logger.error(f'Loop error: {e}')
await asyncio.sleep(5)
async def main():
bot = TradingBotClean()
await bot.run()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,657 +1,104 @@
import httpx
"""
Trading Bot Web Dashboard
Real-time tracking of trades, swaps, and performance
"""
from fastapi import FastAPI, WebSocket
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse
import asyncio
import json
import logging
#!/usr/bin/env python3
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from datetime import datetime
from typing import Dict, List
import os
import asyncio
app = FastAPI(title="Trading Bot Dashboard")
app = FastAPI()
# Logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Shared state (will be updated by main_ml.py)
# RESET: Start with CLEAN state (no old test data)
trading_state = {
'current_trades': {}, # {pair: {qty, price, entry_time, ...}} - EMPTY
'completed_trades': [], # History of closed trades - EMPTY
'swaps': [], # Swap history - EMPTY
'balance': {'USDT': 0.0},
'daily_pnl': 0.0,
'total_pnl': 0.0,
'trades_today': 0,
'wins_today': 0,
'losses_today': 0,
'portfolio_value_usd': 0.0,
'portfolio_value_chf': 0.0,
'last_update': datetime.now().isoformat()
"current_trades": {},
"completed_trades": [],
"balance": {"USDT": 0.0},
"trades_today": 0,
"daily_pnl": 0.0,
"wins_today": 0,
"last_update": datetime.now().isoformat()
}
# WebSocket connections for live updates
active_connections: List[WebSocket] = []
@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"}
async def broadcast_update():
"""Broadcast state update to all connected WebSocket clients"""
for connection in active_connections:
try:
await connection.send_json(trading_state)
except:
pass
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket endpoint for live updates"""
await websocket.accept()
active_connections.append(websocket)
try:
# Send initial state
await websocket.send_json(trading_state)
# Keep connection alive
while True:
await asyncio.sleep(1)
await websocket.send_json(trading_state)
except:
pass
finally:
active_connections.remove(websocket)
# @app.get("/api/state")
@app.get("/api/state")
async def get_state():
"""Get current trading state from Bot DIRECTLY"""
# Return ONLY what the Bot currently has
# NO caching, NO fallback to stale data
return trading_state
async def get_state():
"""Get current trading state"""
return trading_state
@app.post("/api/clear")
async def clear_state():
"""RESET: Clear all historical data, start fresh"""
global trading_state
logger.info('🗑️ Dashboard state cleared')
trading_state = {
'current_trades': {},
'completed_trades': [],
'swaps': [],
'balance': {'USDT': 0.0},
'daily_pnl': 0.0,
'total_pnl': 0.0,
'trades_today': 0,
'wins_today': 0,
'losses_today': 0,
'portfolio_value_usd': 0.0,
'portfolio_value_chf': 0.0,
'last_update': datetime.now().isoformat()
}
await broadcast_update()
return {"status": "cleared"}
@app.post("/api/update")
async def update_state(data: dict):
"""Update trading state (called by main_ml.py)"""
global trading_state
# Explicitly set current_trades if provided (don't merge!)
if 'current_trades' in data:
trading_state['current_trades'] = data['current_trades']
data.pop('current_trades') # Remove so update() doesn't override
# Update rest of state
trading_state.update(data)
trading_state['last_update'] = datetime.now().isoformat()
logger.info(f'📊 Dashboard updated: USDT={trading_state["balance"].get("USDT", 0):.2f}, trades={len(trading_state.get("current_trades", {}))}')
# Broadcast to WebSocket clients
await broadcast_update()
return {"status": "updated"}
@app.post("/api/trade/buy")
async def record_buy(pair: str, qty: float, price: float, entry_time: str = None):
"""Record a buy trade"""
if entry_time is None:
entry_time = datetime.now().isoformat()
trading_state['current_trades'][pair] = {
'qty': qty,
'price': price,
'entry_time': entry_time,
'type': 'BUY'
}
trading_state['trades_today'] += 1
await broadcast_update()
return {"status": "recorded"}
@app.post("/api/trade/sell")
async def record_sell(pair: str, qty: float, price: float, profit_usd: float, profit_pct: float, hold_time_min: float):
"""Record a sell trade"""
entry = trading_state['current_trades'].pop(pair, {})
completed = {
'pair': pair,
'qty': qty,
'entry_price': entry.get('buy_price', entry.get('entry_price', 0)),
'exit_price': price,
'profit_usd': profit_usd,
'profit_pct': profit_pct,
'hold_time_min': hold_time_min,
'entry_time': entry.get('buy_time', entry.get('entry_time', '')),
'exit_time': datetime.now().isoformat()
}
trading_state['completed_trades'].append(completed)
trading_state['daily_pnl'] += profit_usd
trading_state['total_pnl'] += profit_usd
if profit_pct >= 0:
trading_state['wins_today'] += 1
else:
trading_state['losses_today'] += 1
# Keep last 100 trades in history
if len(trading_state['completed_trades']) > 100:
trading_state['completed_trades'] = trading_state['completed_trades'][-100:]
await broadcast_update()
return {"status": "recorded"}
@app.post("/api/swap")
async def record_swap(from_asset: str, to_asset: str, qty: float, rate: float):
"""Record a swap transaction"""
swap_entry = {
'from': from_asset,
'to': to_asset,
'qty': qty,
'rate': rate,
'timestamp': datetime.now().isoformat()
}
trading_state['swaps'].append(swap_entry)
# Keep last 50 swaps in history
if len(trading_state['swaps']) > 50:
trading_state['swaps'] = trading_state['swaps'][-50:]
await broadcast_update()
return {"status": "recorded"}
@app.post("/api/liquidate")
async def trigger_liquidation():
"""FORCE LIQUIDATE: Marc calls this to sell ALL holdings immediately"""
if bot_instance is None:
return {"status": "error", "message": "Bot not running"}
logger.warning("🔥 MANUAL LIQUIDATION TRIGGERED")
result = await bot_instance.force_liquidate_all()
# Update dashboard with result
await broadcast_update()
return result
@app.get("/")
async def get_dashboard():
"""Serve dashboard HTML"""
return HTMLResponse(html_content)
@app.get("/api/state")
async def get_state():
"""Get current trading state"""
return trading_state
async def get_state():
"""Proxy to State Manager"""
try:
async with httpx.AsyncClient(timeout=3.0) as client:
resp = await client.get("http://localhost:7001/api/bot-state")
return resp.json()
except:
return trading_state
async def get_dashboard():
"""Serve web dashboard HTML"""
return HTMLResponse(html_content)
# HTML Dashboard
html_content = """
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trading Bot Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0f0c29 0%, #302b63 100%);
color: #e0e0e0;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #6c63ff;
padding-bottom: 20px;
}
.header h1 {
font-size: 2.5em;
color: #6c63ff;
margin-bottom: 10px;
}
.status {
display: inline-block;
padding: 8px 16px;
background: #00c853;
color: white;
border-radius: 20px;
font-weight: bold;
font-size: 0.9em;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: rgba(255, 255, 255, 0.1);
border: 1px solid #6c63ff;
border-radius: 10px;
padding: 20px;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.card:hover {
background: rgba(255, 255, 255, 0.25);
border-color: #00c853;
transform: translateY(-5px);
}
.card-title {
color: #6c63ff;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 10px;
}
.card-value {
font-size: 2em;
font-weight: bold;
color: #e0e0e0;
margin-bottom: 5px;
}
.card-sub {
color: #ffffff;
font-size: 0.85em;
}
.positive {
color: #00c853;
}
.negative {
color: #ff3d00;
}
.section {
margin-bottom: 30px;
}
.section-title {
color: #6c63ff;
font-size: 1.5em;
margin-bottom: 15px;
border-bottom: 1px solid #6c63ff;
padding-bottom: 10px;
}
.trades-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 15px;
}
.trade-card {
background: rgba(255, 255, 255, 0.25);
border-left: 4px solid #6c63ff;
border-radius: 5px;
padding: 15px;
font-size: 0.9em;
}
.trade-card.open {
border-left-color: #6c63ff;
}
.trade-card.closed {
border-left-color: #00c853;
}
.trade-pair {
font-weight: bold;
color: #6c63ff;
margin-bottom: 8px;
}
.trade-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
font-size: 0.85em;
color: #ffffff;
}
.trade-info strong {
color: #e0e0e0;
}
.swap-item {
background: rgba(255, 255, 255, 0.05);
padding: 10px;
border-radius: 5px;
margin-bottom: 8px;
font-size: 0.85em;
}
.performance-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.stat-card {
background: rgba(108, 99, 255, 0.2);
border: 1px solid #6c63ff;
border-radius: 8px;
padding: 15px;
text-align: center;
}
.stat-label {
color: #ffffff;
font-size: 0.8em;
text-transform: uppercase;
margin-bottom: 8px;
}
.stat-value {
font-size: 1.8em;
font-weight: bold;
color: #00c853;
}
.stat-value.loss {
color: #ff3d00;
}
.update-time {
text-align: right;
color: #666;
font-size: 0.8em;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #333;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🤖 Trading Bot Dashboard</h1>
<span class="status"> LIVE</span>
</div>
<!-- Key Metrics -->
<div class="grid">
<div class="card">
<div class="card-title">Liquid USDT</div>
<div class="card-value" id="usdt">0.00</div>
<div class="card-sub">Available Balance</div>
</div>
<div class="card">
<div class="card-title">Portfolio Value</div>
<div class="card-value" id="portfolio">$0.00</div>
<div class="card-sub">All Assets USD</div>
</div>
<div class="card">
<div class="card-title">Daily P&L</div>
<div class="card-value" id="daily-pnl">$0.00</div>
<div class="card-sub">Today's Profit/Loss</div>
</div>
<div class="card">
<div class="card-title">Total P&L</div>
<div class="card-value" id="total-pnl">$0.00</div>
<div class="card-sub">Lifetime Profit/Loss</div>
</div>
</div>
<!-- Performance Stats -->
<div class="section">
<div class="section-title">📊 Performance</div>
<div class="performance-grid">
<div class="stat-card">
<div class="stat-label">Trades Today</div>
<div class="stat-value" id="trades-count">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Win Rate</div>
<div class="stat-value" id="win-rate">0%</div>
</div>
<div class="stat-card">
<div class="stat-label">Wins</div>
<div class="stat-value" id="wins">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Losses</div>
<div class="stat-value loss" id="losses">0</div>
</div>
</div>
</div>
<!-- Open Trades -->
<div class="section">
<div class="section-title">📈 Open Trades</div>
<div class="trades-list" id="open-trades">
<div class="trade-card open">
<div class="trade-pair">No open trades</div>
</div>
</div>
</div>
<!-- Recent Closed Trades -->
<div class="section">
<div class="section-title"> Recent Closed Trades</div>
<div class="trades-list" id="closed-trades">
<div class="trade-card closed">
<div class="trade-pair">No closed trades yet</div>
</div>
</div>
</div>
<!-- Swaps -->
<div class="section">
<div class="section-title">🔄 Recent Swaps</div>
<div id="swaps-list">
<div class="swap-item">No swaps yet</div>
</div>
</div>
<div class="update-time">
Last update: <span id="update-time">--:--:--</span>
</div>
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>
function formatCurrency(value) {
return new Intl.NumberFormat('de-CH', {
style: 'currency',
currency: 'USD'
}).format(value);
async function refresh(){
try{
const r = await fetch("/api/state");
const d = await r.json();
const usdt = (d.balance && d.balance.USDT) ? d.balance.USDT.free || 0 : 0;
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;
function formatPercent(value) {
return value.toFixed(1) + '%';
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>";
}
}
function updateDashboard(data) {
// Update key metrics - CHF PRIMARY, USD secondary
const usdt = data.balance.USDT || 0;
const usdt_chf = usdt * 0.84;
document.getElementById('usdt').textContent = `$${usdt.toFixed(2)}`;
const portfolio_usd = data.portfolio_value_usd || 0;
const portfolio_chf = data.portfolio_value_chf || (portfolio_usd * 0.84);
document.getElementById('portfolio').textContent = `$${portfolio_usd.toFixed(2)}`;
// Update P&L with color - CHF primary
const dailyPnl = data.daily_pnl || 0;
const dailyPnl_chf = dailyPnl * 0.84;
const dailyPnlEl = document.getElementById('daily-pnl');
dailyPnlEl.textContent = `$${dailyPnl >= 0 ? '+' : ''}${dailyPnl.toFixed(2)}`;
dailyPnlEl.className = 'card-value ' + (dailyPnl >= 0 ? 'positive' : 'negative');
const totalPnl = data.total_pnl || 0;
const totalPnl_chf = totalPnl * 0.84;
const totalPnlEl = document.getElementById('total-pnl');
totalPnlEl.textContent = `$${totalPnl >= 0 ? '+' : ''}${totalPnl.toFixed(2)}`;
totalPnlEl.className = 'card-value ' + (totalPnl >= 0 ? 'positive' : 'negative');
// Update performance
document.getElementById('trades-count').textContent = data.trades_today || 0;
document.getElementById('wins').textContent = data.wins_today || 0;
document.getElementById('losses').textContent = data.losses_today || 0;
const total_trades = (data.wins_today || 0) + (data.losses_today || 0);
const win_rate = total_trades > 0 ? ((data.wins_today || 0) / total_trades * 100) : 0;
document.getElementById('win-rate').textContent = formatPercent(win_rate);
// Update open trades
const openTradesHtml = Object.entries(data.current_trades || {})
.map(([pair, trade]) => `
<div class="trade-card open fade-in">
<div class="trade-pair">${pair}</div>
<div class="trade-info">
<div><strong>Qty:</strong> ${trade.qty?.toFixed(8)}</div>
<div><strong>Price:</strong> ${formatCurrency(trade.buy_price || trade.entry_price)}</div>
<div><strong>Entry:</strong> ${new Date(trade.buy_time || trade.entry_time).toLocaleTimeString('de-CH')}</div>
</div>
</div>
`)
.join('');
const openTradesEl = document.getElementById('open-trades');
openTradesEl.innerHTML = openTradesHtml || '<div class="trade-card open"><div class="trade-pair">No open trades</div></div>';
// Update closed trades (last 10)
const closedTradesHtml = (data.completed_trades || []).slice(-10).reverse()
.map(trade => `
<div class="trade-card closed fade-in">
<div class="trade-pair">${trade.pair}</div>
<div class="trade-info">
<div><strong>Entry:</strong> ${formatCurrency(trade.buy_price || trade.entry_price)}</div>
<div><strong>Exit:</strong> ${formatCurrency(trade.exit_price)}</div>
<div><strong>Profit:</strong> <span class="${trade.profit_usd >= 0 ? 'positive' : 'negative'}">${formatCurrency(trade.profit_usd)} (${trade.profit_pct >= 0 ? '+' : ''}${trade.profit_pct.toFixed(2)}%)</span></div>
<div><strong>Hold:</strong> ${trade.hold_time_min?.toFixed(0)} min</div>
</div>
</div>
`)
.join('');
const closedTradesEl = document.getElementById('closed-trades');
closedTradesEl.innerHTML = closedTradesHtml || '<div class="trade-card closed"><div class="trade-pair">No closed trades yet</div></div>';
// Update swaps (last 10)
const swapsHtml = (data.swaps || []).slice(-10).reverse()
.map(swap => `
<div class="swap-item">
<strong>${swap.from} ${swap.to}:</strong> ${swap.qty?.toFixed(8)} @ ${swap.rate?.toFixed(8)}
<br><span style="color: #666;">${new Date(swap.timestamp).toLocaleTimeString('de-CH')}</span>
</div>
`)
.join('');
const swapsEl = document.getElementById('swaps-list');
swapsEl.innerHTML = swapsHtml || '<div class="swap-item">No swaps yet</div>';
// Update time
document.getElementById('update-time').textContent = new Date().toLocaleTimeString('de-CH');
}
// WebSocket connection
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(protocol + '//' + window.location.host + '/ws');
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
updateDashboard(data);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
// Fallback to polling
setInterval(async () => {
const response = await fetch('/api/state');
const data = await response.json();
updateDashboard(data);
}, 1000);
};
// Initial load
fetch('/api/state')
.then(r => r.json())
.then(data => updateDashboard(data));
</script>
</body>
</html>
"""
document.getElementById("closed_trades").innerHTML = closed_html;
}catch(e){}
setTimeout(refresh, 1000);
}
refresh();
</script></body></html>""";
return HTMLResponse(html)
if __name__ == "__main__":
import uvicorn