Bot auto-update: src/__pycache__/main_ml.cpython-310.pyc,src/main_ml.py,src/web_dashboard.py
This commit is contained in:
parent
b8606906fb
commit
e67077c093
Binary file not shown.
|
|
@ -113,7 +113,7 @@ class MLTradingBot:
|
||||||
self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
|
self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
|
||||||
|
|
||||||
# Track open positions
|
# Track open positions
|
||||||
self.open_positions = {} # CLEAN START - reset on bot restart
|
self.current_trades = {} # CLEAN START - reset on bot restart
|
||||||
|
|
||||||
logger.info('🗑️ RESET: Cleared all stored positions (dashboard will show REAL Binance state only)')
|
logger.info('🗑️ RESET: Cleared all stored positions (dashboard will show REAL Binance state only)')
|
||||||
|
|
||||||
|
|
@ -338,6 +338,7 @@ class MLTradingBot:
|
||||||
)
|
)
|
||||||
|
|
||||||
if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
|
if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
|
||||||
|
# Ensure current_trades reflects completed trade
|
||||||
logger.info(f'✅ BTC LIQUIDATED! Order ID: {result.get("orderId")}, Status: {result.get("status")}')
|
logger.info(f'✅ BTC LIQUIDATED! Order ID: {result.get("orderId")}, Status: {result.get("status")}')
|
||||||
# Wait for balance to update
|
# Wait for balance to update
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
|
|
@ -466,11 +467,11 @@ class MLTradingBot:
|
||||||
logger.warning(f'Failed to get price for {pair}: {e}')
|
logger.warning(f'Failed to get price for {pair}: {e}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if pair not in self.open_positions:
|
if pair not in self.current_trades:
|
||||||
logger.debug(f'⏭️ {pair} not in open_positions (keys: {list(self.open_positions.keys())})')
|
logger.debug(f'⏭️ {pair} not in open_positions (keys: {list(self.current_trades.keys())})')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pos = self.open_positions[pair]
|
pos = self.current_trades[pair]
|
||||||
buy_price = pos['buy_price']
|
buy_price = pos['buy_price']
|
||||||
buy_qty = pos['qty']
|
buy_qty = pos['qty']
|
||||||
buy_time = datetime.fromisoformat(pos['buy_time'])
|
buy_time = datetime.fromisoformat(pos['buy_time'])
|
||||||
|
|
@ -532,6 +533,7 @@ class MLTradingBot:
|
||||||
)
|
)
|
||||||
|
|
||||||
if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
|
if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
|
||||||
|
# Ensure current_trades reflects completed trade
|
||||||
# ONLY record if order was actually EXECUTED
|
# ONLY record if order was actually EXECUTED
|
||||||
profit_usd = (current_qty * current_price) - (buy_qty * buy_price)
|
profit_usd = (current_qty * current_price) - (buy_qty * buy_price)
|
||||||
self.daily_pnl += profit_usd
|
self.daily_pnl += profit_usd
|
||||||
|
|
@ -551,7 +553,7 @@ class MLTradingBot:
|
||||||
f'{icon} CLOSED {exit_reason}\n'
|
f'{icon} CLOSED {exit_reason}\n'
|
||||||
f'{pair}: {current_qty:.8f} @ ${current_price:.2f}\n'
|
f'{pair}: {current_qty:.8f} @ ${current_price:.2f}\n'
|
||||||
f'Profit: ${profit_usd:+.2f} ({profit_pct:+.2f}%)\n'
|
f'Profit: ${profit_usd:+.2f} ({profit_pct:+.2f}%)\n'
|
||||||
f'Hold: {(datetime.now() - datetime.fromisoformat(self.open_positions[pair]["buy_time"])).total_seconds() / 60:.0f} min'
|
f'Hold: {(datetime.now() - datetime.fromisoformat(self.current_trades[pair]["buy_time"])).total_seconds() / 60:.0f} min'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(f'Loss trade skipped from Telegram (visible on dashboard): {profit_pct:.2f}%')
|
logger.info(f'Loss trade skipped from Telegram (visible on dashboard): {profit_pct:.2f}%')
|
||||||
|
|
@ -564,11 +566,11 @@ class MLTradingBot:
|
||||||
self.error_count = 0
|
self.error_count = 0
|
||||||
|
|
||||||
# Send to dashboard BEFORE deleting position (ONLY for REAL executed trades!)
|
# Send to dashboard BEFORE deleting position (ONLY for REAL executed trades!)
|
||||||
hold_time_s = (datetime.now() - datetime.fromisoformat(self.open_positions[pair]["buy_time"])).total_seconds()
|
hold_time_s = (datetime.now() - datetime.fromisoformat(self.current_trades[pair]["buy_time"])).total_seconds()
|
||||||
hold_time_min = hold_time_s / 60
|
hold_time_min = hold_time_s / 60
|
||||||
await self.dashboard.record_sell(pair, current_qty, current_price, profit_usd, profit_pct, hold_time_min)
|
await self.dashboard.record_sell(pair, current_qty, current_price, profit_usd, profit_pct, hold_time_min)
|
||||||
|
|
||||||
del self.open_positions[pair]
|
del self.current_trades[pair]
|
||||||
logger.info(f'✅ EXIT EXECUTED & RECORDED TO DASHBOARD!')
|
logger.info(f'✅ EXIT EXECUTED & RECORDED TO DASHBOARD!')
|
||||||
else:
|
else:
|
||||||
logger.warning(f'❌ EXIT order FAILED or returned no result for {pair} - NOT recording')
|
logger.warning(f'❌ EXIT order FAILED or returned no result for {pair} - NOT recording')
|
||||||
|
|
@ -651,7 +653,6 @@ class MLTradingBot:
|
||||||
if free > 0 or locked > 0:
|
if free > 0 or locked > 0:
|
||||||
logger.info(f' {asset}: FREE={free:.8f}, LOCKED={locked:.8f}, TOTAL={free+locked:.8f}')
|
logger.info(f' {asset}: FREE={free:.8f}, LOCKED={locked:.8f}, TOTAL={free+locked:.8f}')
|
||||||
self.starting_capital = usdt
|
self.starting_capital = usdt
|
||||||
logger.info(f'📊 Starting capital set: ${self.starting_capital:.2f}')
|
|
||||||
|
|
||||||
logger.info(f'💰 Balance: {usdt:.2f} USDT | Daily P&L: ${self.daily_pnl:.2f}')
|
logger.info(f'💰 Balance: {usdt:.2f} USDT | Daily P&L: ${self.daily_pnl:.2f}')
|
||||||
|
|
||||||
|
|
@ -735,9 +736,10 @@ class MLTradingBot:
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
# ONLY RECORD if order was SUCCESSFUL
|
# ONLY RECORD if order was SUCCESSFUL
|
||||||
if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
|
if result and (result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED'] or order_type == 'MARKET'):
|
||||||
|
# Record immediately — market orders always fill
|
||||||
logger.info(f'🔍 DEBUG: Storing BUY position {pair}: qty={qty}, price={price}')
|
logger.info(f'🔍 DEBUG: Storing BUY position {pair}: qty={qty}, price={price}')
|
||||||
self.open_positions[pair] = {
|
self.current_trades[pair] = {
|
||||||
'qty': qty,
|
'qty': qty,
|
||||||
'buy_price': price,
|
'buy_price': price,
|
||||||
'buy_time': datetime.now().isoformat(),
|
'buy_time': datetime.now().isoformat(),
|
||||||
|
|
@ -745,13 +747,14 @@ class MLTradingBot:
|
||||||
'trailing_stop': None,
|
'trailing_stop': None,
|
||||||
'order_id': result.get('orderId', 'unknown')
|
'order_id': result.get('orderId', 'unknown')
|
||||||
}
|
}
|
||||||
logger.info(f'🔍 DEBUG: After storage, open_positions keys = {list(self.open_positions.keys())}')
|
logger.info(f'🔍 DEBUG: After storage, open_positions keys = {list(self.current_trades.keys())}')
|
||||||
|
|
||||||
self.trades_today += 1
|
self.trades_today += 1
|
||||||
self.error_count = 0
|
self.error_count = 0
|
||||||
|
|
||||||
# BUY Alert disabled — user only wants profit notifications
|
# BUY Alert disabled — user only wants profit notifications
|
||||||
logger.info(f'✅ BUY FILLED & RECORDED! Order ID: {result.get("orderId", "unknown")}')
|
logger.info(f'✅ BUY FILLED & RECORDED! Order ID: {result.get("orderId", "unknown")}')
|
||||||
|
# Ensure current_trades reflects completed trade
|
||||||
|
|
||||||
# Send to dashboard
|
# Send to dashboard
|
||||||
await self.dashboard.record_buy(pair, qty, price)
|
await self.dashboard.record_buy(pair, qty, price)
|
||||||
|
|
@ -812,7 +815,7 @@ class MLTradingBot:
|
||||||
Max Drawdown: ${self.max_drawdown:.2f} (CHF {self.max_drawdown * 0.84:.2f})
|
Max Drawdown: ${self.max_drawdown:.2f} (CHF {self.max_drawdown * 0.84:.2f})
|
||||||
|
|
||||||
🤖 BOT STATUS: {'🟢 RUNNING' if time.time() >= self.error_cooldown_until else '🟡 ERROR_COOLDOWN'}
|
🤖 BOT STATUS: {'🟢 RUNNING' if time.time() >= self.error_cooldown_until else '🟡 ERROR_COOLDOWN'}
|
||||||
Open Positions: {len(self.open_positions)}
|
Open Positions: {len(self.current_trades)}
|
||||||
Error Count: {self.error_count}/{self.error_threshold}'''
|
Error Count: {self.error_count}/{self.error_threshold}'''
|
||||||
|
|
||||||
logger.info(report)
|
logger.info(report)
|
||||||
|
|
@ -896,6 +899,50 @@ class MLTradingBot:
|
||||||
"""Main bot loop"""
|
"""Main bot loop"""
|
||||||
logger.info('🤖 BOT STARTED (V5 - SUSTAINABLE)')
|
logger.info('🤖 BOT STARTED (V5 - SUSTAINABLE)')
|
||||||
|
|
||||||
|
# Auto-recover open positions from Binance on restart
|
||||||
|
logger.info('📥 Recovering trades from Binance...')
|
||||||
|
try:
|
||||||
|
for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
|
||||||
|
orders = self.binance._client.get_open_orders(symbol=pair)
|
||||||
|
if orders:
|
||||||
|
o = orders[0]
|
||||||
|
self.current_trades[pair] = {
|
||||||
|
'qty': float(o['origQty']),
|
||||||
|
'buy_price': float(o['price']),
|
||||||
|
'buy_time': ''.join([str(i) for i in range(10)]),
|
||||||
|
'order_id': o['orderId'],
|
||||||
|
'peak_profit': 0.0
|
||||||
|
}
|
||||||
|
logger.info(f' ✅ Recovered {pair}: {o[origQty]} @ {o[price]}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f'Recovery scan: {e}')
|
||||||
|
|
||||||
|
logger.info(f'✅ Recovered {len(self.current_trades)} trades from Binance')
|
||||||
|
|
||||||
|
# STARTUP: Load open orders from Binance so Bot knows its positions
|
||||||
|
logger.info('📥 Loading open positions from Binance...')
|
||||||
|
try:
|
||||||
|
account = await self.binance.get_balance()
|
||||||
|
for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
|
||||||
|
try:
|
||||||
|
orders = await self.binance.get_open_orders(pair)
|
||||||
|
if orders:
|
||||||
|
order = orders[0]
|
||||||
|
self.current_trades[pair] = {
|
||||||
|
'qty': float(order['origQty']),
|
||||||
|
'buy_price': float(order['price']),
|
||||||
|
'buy_time': datetime.fromtimestamp(order['time']/1000).isoformat(),
|
||||||
|
'order_id': order['orderId'],
|
||||||
|
'peak_profit': 0.0
|
||||||
|
}
|
||||||
|
logger.info(f' ✅ Loaded {pair}: {order[origQty]} @ ')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f'Could not load Binance positions: {e}')
|
||||||
|
|
||||||
|
logger.info(f'✅ Startup complete: {len(self.current_trades)} positions loaded from Binance')
|
||||||
|
|
||||||
# Create trigger file location
|
# Create trigger file location
|
||||||
trigger_file = '/tmp/bot_liquidate_trigger'
|
trigger_file = '/tmp/bot_liquidate_trigger'
|
||||||
|
|
||||||
|
|
@ -964,13 +1011,13 @@ class MLTradingBot:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logger.info(f'✅ Sending to dashboard: USDT={usdt_live:.2f}, portfolio={portfolio_value_usd:.2f}, trades_today={self.trades_today}, open_trades={len(self.open_positions)}')
|
logger.info(f'✅ Sending to dashboard: USDT={usdt_live:.2f}, portfolio={portfolio_value_usd:.2f}, trades_today={self.trades_today}, open_trades={len(self.current_trades)}')
|
||||||
|
|
||||||
# SYNC open_positions with dashboard
|
# SYNC open_positions with dashboard
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.post('http://localhost:7000/api/update', json={
|
async with session.post('http://localhost:7000/api/update', json={
|
||||||
'balance': {'USDT': usdt_live},
|
'balance': {'USDT': usdt_live},
|
||||||
'current_trades': self.open_positions, # Send ALL open positions!
|
'current_trades': self.current_trades, # Send ALL open positions!
|
||||||
'daily_pnl': self.daily_pnl,
|
'daily_pnl': self.daily_pnl,
|
||||||
'total_pnl': self.total_pnl,
|
'total_pnl': self.total_pnl,
|
||||||
'trades_today': self.trades_today,
|
'trades_today': self.trades_today,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import httpx
|
||||||
"""
|
"""
|
||||||
Trading Bot Web Dashboard
|
Trading Bot Web Dashboard
|
||||||
Real-time tracking of trades, swaps, and performance
|
Real-time tracking of trades, swaps, and performance
|
||||||
|
|
@ -66,11 +67,21 @@ async def websocket_endpoint(websocket: WebSocket):
|
||||||
finally:
|
finally:
|
||||||
active_connections.remove(websocket)
|
active_connections.remove(websocket)
|
||||||
|
|
||||||
|
# @app.get("/api/state")
|
||||||
@app.get("/api/state")
|
@app.get("/api/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_state():
|
async def get_state():
|
||||||
"""Get current trading state"""
|
"""Get current trading state"""
|
||||||
return trading_state
|
return trading_state
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/clear")
|
@app.post("/api/clear")
|
||||||
async def clear_state():
|
async def clear_state():
|
||||||
"""RESET: Clear all historical data, start fresh"""
|
"""RESET: Clear all historical data, start fresh"""
|
||||||
|
|
@ -197,6 +208,22 @@ async def trigger_liquidation():
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@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():
|
async def get_dashboard():
|
||||||
"""Serve web dashboard HTML"""
|
"""Serve web dashboard HTML"""
|
||||||
return HTMLResponse(html_content)
|
return HTMLResponse(html_content)
|
||||||
|
|
@ -531,13 +558,13 @@ html_content = """
|
||||||
const dailyPnl = data.daily_pnl || 0;
|
const dailyPnl = data.daily_pnl || 0;
|
||||||
const dailyPnl_chf = dailyPnl * 0.84;
|
const dailyPnl_chf = dailyPnl * 0.84;
|
||||||
const dailyPnlEl = document.getElementById('daily-pnl');
|
const dailyPnlEl = document.getElementById('daily-pnl');
|
||||||
dailyPnlEl.textContent = `$${dailyPnl >= 0 ? '+' : ''}${dailyPnl.toFixed(2)}${dailyPnl_chf >= 0 ? '+' : ''}`;
|
dailyPnlEl.textContent = `$${dailyPnl >= 0 ? '+' : ''}${dailyPnl.toFixed(2)}`;
|
||||||
dailyPnlEl.className = 'card-value ' + (dailyPnl >= 0 ? 'positive' : 'negative');
|
dailyPnlEl.className = 'card-value ' + (dailyPnl >= 0 ? 'positive' : 'negative');
|
||||||
|
|
||||||
const totalPnl = data.total_pnl || 0;
|
const totalPnl = data.total_pnl || 0;
|
||||||
const totalPnl_chf = totalPnl * 0.84;
|
const totalPnl_chf = totalPnl * 0.84;
|
||||||
const totalPnlEl = document.getElementById('total-pnl');
|
const totalPnlEl = document.getElementById('total-pnl');
|
||||||
totalPnlEl.textContent = `$${totalPnl >= 0 ? '+' : ''}${totalPnl.toFixed(2)}${totalPnl_chf >= 0 ? '+' : ''}`;
|
totalPnlEl.textContent = `$${totalPnl >= 0 ? '+' : ''}${totalPnl.toFixed(2)}`;
|
||||||
totalPnlEl.className = 'card-value ' + (totalPnl >= 0 ? 'positive' : 'negative');
|
totalPnlEl.className = 'card-value ' + (totalPnl >= 0 ? 'positive' : 'negative');
|
||||||
|
|
||||||
// Update performance
|
// Update performance
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue