Crypto Bots

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.

A
AI Agents Hubยท2026-01-24ยท4 min readยท704 words

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