How to Monitor Your Trading Bot's Performance
A trading bot without monitoring is a liability. Build a proper performance dashboard with P&L tracking, trade logs, Telegram alerts, and the key metrics that matter.
Builder of AI agents, crypto trading bots, and open-source automation tools. Sharing practical guides on how to build, deploy, and profit from AI and DeFi technology.
Most developers spend weeks building a bot and minutes setting up monitoring. Then they're surprised when it misbehaves silently for weeks. Here's how to monitor your bot properly.
The 5 Metrics That Matter
1. P&L (Profit and Loss): Obvious but many bots don't track it properly. Calculate in USD, not just asset units.
2. Win rate: % of trades that are profitable. Alone means nothing โ a 90% win rate with tiny wins and huge losses is terrible.
3. Profit factor: Gross profit / Gross loss. Should be > 1.5.
4. Max drawdown: Largest peak-to-trough decline. If it exceeds your threshold, stop the bot and review.
5. Sharpe ratio: (Return - Risk-free rate) / Volatility. > 1.5 is good for live trading.
Building a Trade Logger
import sqlite3
import json
from datetime import datetime
from contextlib import contextmanager
@contextmanager
def db_connection(db_path="bot_trades.db"):
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
finally:
conn.close()
def setup_db():
with db_connection() as db:
db.executescript("""
CREATE TABLE IF NOT EXISTS trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
symbol TEXT,
side TEXT,
entry_price REAL,
exit_price REAL,
quantity REAL,
pnl_usd REAL,
pnl_pct REAL,
duration_minutes REAL,
strategy TEXT,
exchange_order_id TEXT,
notes TEXT
);
CREATE TABLE IF NOT EXISTS bot_status (
id INTEGER PRIMARY KEY,
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
total_trades INTEGER,
winning_trades INTEGER,
total_pnl REAL,
current_capital REAL
);
""")
def log_trade(symbol, side, entry, exit_price, qty, strategy=""):
pnl_usd = (exit_price - entry) * qty if side == 'buy' else (entry - exit_price) * qty
pnl_pct = pnl_usd / (entry * qty) * 100
with db_connection() as db:
db.execute("""
INSERT INTO trades (symbol, side, entry_price, exit_price, quantity, pnl_usd, pnl_pct, strategy)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (symbol, side, entry, exit_price, qty, pnl_usd, pnl_pct, strategy))
return pnl_usd
def get_stats() -> dict:
with db_connection() as db:
rows = db.execute("SELECT * FROM trades ORDER BY timestamp DESC").fetchall()
if not rows:
return {}
pnls = [r['pnl_usd'] for r in rows]
wins = [p for p in pnls if p > 0]
losses = [p for p in pnls if p < 0]
total_pnl = sum(pnls)
win_rate = len(wins) / len(pnls) * 100 if pnls else 0
profit_factor = sum(wins) / abs(sum(losses)) if losses else float('inf')
return {
"total_trades": len(rows),
"win_rate": f"{win_rate:.1f}%",
"total_pnl": f"${total_pnl:+.2f}",
"profit_factor": f"{profit_factor:.2f}",
"avg_win": f"${sum(wins)/len(wins):.2f}" if wins else "$0",
"avg_loss": f"${sum(losses)/len(losses):.2f}" if losses else "$0",
"best_trade": f"${max(pnls):.2f}",
"worst_trade": f"${min(pnls):.2f}",
}
Telegram Bot for Real-Time Alerts
import requests
import os
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
def send_alert(message: str, important: bool = False):
prefix = "๐จ " if important else "๐ "
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
requests.post(url, json={
"chat_id": TELEGRAM_CHAT_ID,
"text": f"{prefix}{message}",
"parse_mode": "HTML"
})
def send_trade_notification(symbol, side, price, qty, pnl=None):
emoji = "๐ข" if side == 'buy' else "๐ด" if pnl and pnl < 0 else "โ
"
msg = (
f"{emoji} <b>{side.upper()} {symbol}</b>\n"
f"Price: ${price:,.2f}\n"
f"Qty: {qty:.6f}\n"
f"Value: ${price * qty:,.2f}"
)
if pnl is not None:
msg += f"\nP&L: ${pnl:+.2f}"
send_alert(msg)
def send_daily_report():
stats = get_stats()
msg = (
f"<b>๐ Daily Bot Report</b>\n"
f"Trades: {stats.get('total_trades', 0)}\n"
f"Win Rate: {stats.get('win_rate', 'N/A')}\n"
f"Total P&L: {stats.get('total_pnl', '$0')}\n"
f"Profit Factor: {stats.get('profit_factor', 'N/A')}"
)
send_alert(msg)
Automated Daily Email Report
import smtplib
from email.mime.text import MIMEText
from datetime import datetime, timedelta
def send_email_report():
stats = get_stats()
html = f"""
<h2>Trading Bot Daily Report โ {datetime.now().strftime('%Y-%m-%d')}</h2>
<table border="1" cellpadding="5">
<tr><th>Metric</th><th>Value</th></tr>
{''.join(f"<tr><td>{k}</td><td><b>{v}</b></td></tr>" for k, v in stats.items())}
</table>
"""
msg = MIMEText(html, 'html')
msg['Subject'] = f"Bot Report: {stats.get('total_pnl', '?')} P&L today"
msg['From'] = os.getenv('EMAIL_FROM')
msg['To'] = os.getenv('EMAIL_TO')
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(os.getenv('EMAIL_FROM'), os.getenv('EMAIL_PASSWORD'))
smtp.send_message(msg)
Emergency Stop Circuit Breaker
def check_circuit_breaker(
max_daily_loss: float = -100, # Stop if down $100 today
max_drawdown: float = -5.0, # Stop if drawdown > 5%
) -> bool:
"""Return True if bot should stop."""
stats = get_stats()
today_pnl = get_today_pnl() # Implement based on your DB
if today_pnl < max_daily_loss:
send_alert(f"๐จ CIRCUIT BREAKER: Daily loss ${today_pnl:.2f} exceeded limit ${max_daily_loss}", important=True)
return True
return False
# Add to main bot loop:
# if check_circuit_breaker():
# sys.exit("Circuit breaker triggered")
Good monitoring catches bugs you'd otherwise miss for days. The 2 hours you spend setting this up will save you from discovering a bug via a significant account loss.
Related Articles
Solana Trading Bots: The Complete Setup Guide for 2026
4 min read
Crypto BotsThe Complete Guide to Crypto Trading Bot Strategies (2025)
9 min read
Crypto BotsHow to Write a Python Script That Trades Crypto Automatically
7 min read
Crypto BotsHow to Use the Binance API: Complete Beginner Guide (2025)
8 min read