Crypto Bot Risk Management: The 10 Rules That Separate Winners From Losers
Most crypto trading bots fail not because of bad strategies, but because of bad risk management. These 10 rules have been battle-tested by algorithmic traders who've survived multiple market crashes.
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.
Why Bots Blow Up
A bot running a profitable strategy can still lose everything. The most common causes:
- No position size limits โ one trade takes 50% of capital
- No drawdown circuit breakers โ bot keeps trading through a 60% loss
- Ignoring exchange risk โ FTX-style exchange insolvency
- Over-leveraging โ 10x leverage + 10% move = liquidation
- Correlated positions โ "diversified" portfolio that all falls together
Every rule below addresses a real failure mode that has blown up real bots.
Rule 1: Never Risk More Than 2% Per Trade
The Kelly Criterion tells you the mathematically optimal bet size. For most strategies, it's 1-5% of capital. Start at 1-2% until you have 100+ live trades of performance data.
def calculate_position_size(
capital: float,
win_rate: float,
avg_win_pct: float,
avg_loss_pct: float,
max_risk_pct: float = 0.02 # 2% max risk
) -> float:
"""Calculate position size using Kelly Criterion, capped at max_risk"""
# Kelly fraction
b = avg_win_pct / avg_loss_pct # Win/loss ratio
p = win_rate
q = 1 - p
kelly = (b * p - q) / b
# Use half-Kelly for safety
half_kelly = kelly / 2
# Cap at max_risk_pct
safe_fraction = min(half_kelly, max_risk_pct)
safe_fraction = max(safe_fraction, 0) # Never negative
return capital * safe_fraction
# Example: 60% win rate, 2% avg win, 1.5% avg loss
size = calculate_position_size(10000, 0.60, 0.02, 0.015)
print(f"Position size: ${size:.2f}") # โ ~$200 (2% of $10,000)
Rule 2: Implement a Daily Loss Limit
If your bot loses more than X% in a day, it stops trading automatically. This prevents a bad day from turning into a catastrophic week.
class RiskGuard:
def __init__(self, daily_loss_limit_pct: float = 0.05):
self.daily_limit = daily_loss_limit_pct
self.starting_balance = None
self.trade_date = None
self.halted = False
def check_daily_limit(self, current_balance: float) -> bool:
"""Returns True if trading should continue, False if halted"""
today = datetime.date.today()
# Reset at start of each day
if self.trade_date != today:
self.starting_balance = current_balance
self.trade_date = today
self.halted = False
return True
if self.starting_balance is None:
self.starting_balance = current_balance
return True
daily_loss = (self.starting_balance - current_balance) / self.starting_balance
if daily_loss >= self.daily_limit:
if not self.halted:
print(f"๐ DAILY LOSS LIMIT HIT: {daily_loss*100:.1f}% loss โ stopping for today")
self.halted = True
return False
return True
Rule 3: Maximum Drawdown Circuit Breaker
If your total portfolio drops more than 20% from peak, the bot pauses and alerts you.
class DrawdownMonitor:
def __init__(self, max_drawdown_pct: float = 0.20):
self.max_drawdown = max_drawdown_pct
self.peak_balance = 0
def update(self, current_balance: float) -> bool:
"""Returns False if max drawdown exceeded"""
self.peak_balance = max(self.peak_balance, current_balance)
drawdown = (self.peak_balance - current_balance) / self.peak_balance
if drawdown >= self.max_drawdown:
print(f"๐จ MAX DRAWDOWN EXCEEDED: {drawdown*100:.1f}% from peak")
print(f" Peak: ${self.peak_balance:,.2f} | Current: ${current_balance:,.2f}")
send_telegram_alert(f"BOT HALTED โ {drawdown*100:.1f}% drawdown")
return False
return True
Rule 4: Diversify Across Exchanges (Not Just Coins)
FTX had $32B in customer deposits. In 72 hours it was bankrupt. Never keep more than 30% of your total bot capital on any single exchange.
EXCHANGE_LIMITS = {
'binance': 0.30, # Max 30% โ largest but still risky
'coinbase': 0.25,
'kraken': 0.20,
'okx': 0.15,
'bybit': 0.10,
}
def check_exchange_concentration(balances: dict, total: float) -> list[str]:
"""Alert if any exchange exceeds its allocation limit"""
alerts = []
for exchange, balance in balances.items():
pct = balance / total
limit = EXCHANGE_LIMITS.get(exchange, 0.20)
if pct > limit:
alerts.append(f"{exchange}: {pct*100:.0f}% (limit: {limit*100:.0f}%)")
return alerts
Rule 5: Correlation Check Before Adding Positions
BTC at 60% of portfolio + ETH at 30% + SOL at 10% looks diversified. In a crypto crash, all three fall 50%+ simultaneously. True diversification requires low correlation assets.
import numpy as np
import pandas as pd
def check_portfolio_correlation(returns: pd.DataFrame, threshold: float = 0.8) -> list:
"""Warn if any two holdings are too correlated"""
corr_matrix = returns.corr()
alerts = []
for i in range(len(corr_matrix.columns)):
for j in range(i+1, len(corr_matrix.columns)):
corr = corr_matrix.iloc[i, j]
if corr > threshold:
coin1 = corr_matrix.columns[i]
coin2 = corr_matrix.columns[j]
alerts.append(f"{coin1}/{coin2}: {corr:.2f} correlation (>0.8 = highly correlated)")
return alerts
Rule 6: Use Stop Losses on Every Position
No exceptions. A stop loss at -5% feels painful until the day you're down 50% on a position and wishing you'd had one.
def place_order_with_stop(exchange, symbol: str, direction: str, size: float,
entry: float, stop_pct: float = 0.05):
"""Place order and immediately set stop loss"""
# Enter position
order = exchange.create_market_order(symbol, direction, size)
fill_price = order['average']
# Calculate stop loss price
if direction == 'buy':
stop_price = fill_price * (1 - stop_pct)
stop_side = 'sell'
else:
stop_price = fill_price * (1 + stop_pct)
stop_side = 'buy'
# Place stop loss immediately
stop_order = exchange.create_order(
symbol, 'stop_market', stop_side, size,
params={'stopPrice': stop_price, 'reduceOnly': True}
)
print(f"Entry: ${fill_price:.2f} | Stop: ${stop_price:.2f} ({stop_pct*100:.0f}% risk)")
return order, stop_order
Rule 7: Never Trade More Than 3 Correlated Coins Simultaneously
If BTC drops 10% in an hour, your long ETH, SOL, and AVAX positions are also in trouble. Cap correlated simultaneous positions.
Rule 8: Log Everything
You can't improve what you don't measure:
import json
from pathlib import Path
class TradeLogger:
def __init__(self, log_file: str = "trades.jsonl"):
self.log_file = Path(log_file)
def log_trade(self, trade: dict):
trade['timestamp'] = datetime.datetime.now().isoformat()
with open(self.log_file, 'a') as f:
f.write(json.dumps(trade) + '\n')
def get_stats(self) -> dict:
trades = [json.loads(l) for l in self.log_file.read_text().strip().split('\n') if l]
pnls = [t['pnl'] for t in trades if 'pnl' in t]
wins = [p for p in pnls if p > 0]
losses = [p for p in pnls if p < 0]
return {
'total_trades': len(trades),
'win_rate': len(wins) / len(pnls) if pnls else 0,
'total_pnl': sum(pnls),
'avg_win': sum(wins)/len(wins) if wins else 0,
'avg_loss': sum(losses)/len(losses) if losses else 0,
'profit_factor': abs(sum(wins)/sum(losses)) if losses else float('inf'),
}
Rule 9: Paper Trade for 30 Days Minimum Before Going Live
No exceptions for new strategies. Past backtest performance does not guarantee future results. Paper trade:
class PaperTrader:
def __init__(self, starting_balance: float = 10000):
self.balance = starting_balance
self.positions = {}
self.trade_log = []
def buy(self, symbol: str, usd_amount: float):
price = self.get_price(symbol)
qty = usd_amount / price
self.positions[symbol] = {'qty': qty, 'entry': price, 'usd': usd_amount}
self.balance -= usd_amount
print(f"[PAPER] BUY {qty:.4f} {symbol} @ ${price:,.2f}")
def sell(self, symbol: str):
if symbol not in self.positions:
return
pos = self.positions[symbol]
current_price = self.get_price(symbol)
current_value = pos['qty'] * current_price
pnl = current_value - pos['usd']
self.balance += current_value
del self.positions[symbol]
print(f"[PAPER] SELL {symbol} | P&L: ${pnl:+.2f}")
Rule 10: Have an Emergency Stop Button
Build a way to immediately stop all bots and cancel all orders:
async def emergency_shutdown(exchanges: list, reason: str):
"""IMMEDIATELY stop all bots and cancel all open orders"""
print(f"๐จ EMERGENCY SHUTDOWN: {reason}")
send_telegram_alert(f"๐จ EMERGENCY SHUTDOWN\nReason: {reason}")
for exchange in exchanges:
try:
# Cancel all open orders
markets = exchange.load_markets()
for symbol in markets:
try:
exchange.cancel_all_orders(symbol)
except Exception:
pass
print(f"โ
{exchange.id}: all orders cancelled")
except Exception as e:
print(f"โ {exchange.id}: shutdown failed โ {e}")
# Write stop flag
Path("EMERGENCY_STOP").write_text(reason)
print("Emergency stop flag written. Restart manually after investigation.")
Risk management isn't exciting. No one brags about the losses they avoided. But every experienced algorithmic trader will tell you the same thing: the strategy gets you into trades, risk management keeps you in the game long enough to profit from them.
Tagged in
Related Articles
Leverage Trading Bots: Risk Management Strategies That Actually Work
5 min read
Crypto BotsStop-Loss and Take-Profit Automation for Crypto Bots
4 min read
Crypto BotsHow to Build a Self-Healing Trading Bot That Fixes Its Own Errors
5 min read
Crypto BotsPump.fun and Solana Meme Coin Bots: How to Automate the Hottest Trend
5 min read