Crypto Bots

Stop-Loss and Take-Profit Automation for Crypto Bots

The most important risk management tool for any crypto bot. How to implement stop-loss and take-profit orders properly in Python โ€” including trailing stops and time-based exits.

A
AI Agents Hubยท2026-02-16ยท4 min readยท776 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.

Every profitable trader has one thing in common: they cut losses fast. A bot without stop-losses is a bot waiting to blow up. Here's how to implement proper risk management automation.

Why Stops Are Essential

One trade without a stop-loss can wipe out 20 profitable trades. In crypto:

  • A coin can drop 40% in an hour on bad news
  • Exchange outages can prevent manual exits
  • Emotional decision-making at 3 AM loses money

Automated stops remove the human element from loss management.

Basic Stop-Loss Implementation

import ccxt
import time
import os
from dotenv import load_dotenv

load_dotenv()
exchange = ccxt.binance({
    'apiKey': os.getenv('BINANCE_API_KEY'),
    'secret': os.getenv('BINANCE_SECRET'),
    'enableRateLimit': True,
})

class Position:
    def __init__(self, symbol: str, entry_price: float, quantity: float, 
                 stop_loss_pct: float = 3.0, take_profit_pct: float = 6.0):
        self.symbol = symbol
        self.entry_price = entry_price
        self.quantity = quantity
        self.stop_loss_price = entry_price * (1 - stop_loss_pct / 100)
        self.take_profit_price = entry_price * (1 + take_profit_pct / 100)
        self.is_open = True
    
    def check(self, current_price: float) -> str:
        """Check if stop/target has been hit. Returns 'stop', 'target', or 'hold'."""
        if not self.is_open:
            return 'closed'
        
        pnl_pct = (current_price - self.entry_price) / self.entry_price * 100
        
        if current_price <= self.stop_loss_price:
            return 'stop'
        elif current_price >= self.take_profit_price:
            return 'target'
        
        return 'hold'
    
    def close(self):
        """Market sell the position."""
        qty = exchange.amount_to_precision(self.symbol, self.quantity)
        order = exchange.create_market_sell_order(self.symbol, float(qty))
        self.is_open = False
        return order

def monitor_positions(positions: list[Position], interval: int = 15):
    """Monitor all open positions for stop/target hits."""
    while any(p.is_open for p in positions):
        for pos in [p for p in positions if p.is_open]:
            price = float(exchange.fetch_ticker(pos.symbol)['last'])
            action = pos.check(price)
            pnl = (price - pos.entry_price) / pos.entry_price * 100
            
            print(f"{pos.symbol}: ${price:,.2f} | P&L: {pnl:+.2f}% | SL: ${pos.stop_loss_price:,.0f} | TP: ${pos.take_profit_price:,.0f}")
            
            if action == 'stop':
                print(f"๐Ÿ›‘ STOP-LOSS hit for {pos.symbol} at ${price:,.2f} ({pnl:+.2f}%)")
                pos.close()
                
            elif action == 'target':
                print(f"โœ… TAKE-PROFIT hit for {pos.symbol} at ${price:,.2f} (+{pnl:.2f}%)")
                pos.close()
        
        time.sleep(interval)

Trailing Stop-Loss

A trailing stop follows price upward but doesn't move when price falls. It locks in profit while letting winners run:

class TrailingStopPosition:
    def __init__(self, symbol: str, entry_price: float, quantity: float,
                 trail_pct: float = 3.0):
        self.symbol = symbol
        self.entry_price = entry_price
        self.quantity = quantity
        self.trail_pct = trail_pct
        self.highest_price = entry_price
        self.stop_price = entry_price * (1 - trail_pct / 100)
        self.is_open = True
    
    def update(self, current_price: float) -> str:
        if current_price > self.highest_price:
            self.highest_price = current_price
            self.stop_price = current_price * (1 - self.trail_pct / 100)
            print(f"Trailing stop moved up to ${self.stop_price:,.2f}")
        
        if current_price <= self.stop_price:
            return 'stop'
        return 'hold'

# Example: Enter BTC at $60,000 with 3% trailing stop
# If BTC rises to $65,000: stop moves to $63,050
# If BTC then drops to $63,050: stop triggered (locked in +5% gain)

Time-Based Exits

Sometimes the market doesn't reach your target. Holding too long is a risk:

from datetime import datetime, timedelta

class TimedPosition(Position):
    def __init__(self, *args, max_hold_hours: float = 24, **kwargs):
        super().__init__(*args, **kwargs)
        self.entry_time = datetime.now()
        self.expiry_time = self.entry_time + timedelta(hours=max_hold_hours)
    
    def check(self, current_price: float) -> str:
        result = super().check(current_price)
        if result != 'hold':
            return result
        
        if datetime.now() >= self.expiry_time:
            pnl = (current_price - self.entry_price) / self.entry_price * 100
            print(f"โฐ Time-based exit after {self.entry_time} | P&L: {pnl:+.2f}%")
            return 'time_exit'
        
        return 'hold'

Risk Sizing: The 2% Rule

Never risk more than 2% of total capital on a single trade:

def calculate_position_size(
    total_capital: float,
    entry_price: float, 
    stop_price: float,
    risk_pct: float = 2.0
) -> dict:
    """Calculate safe position size using 2% risk rule."""
    risk_amount = total_capital * (risk_pct / 100)
    price_risk_pct = abs(entry_price - stop_price) / entry_price * 100
    position_value = risk_amount / (price_risk_pct / 100)
    quantity = position_value / entry_price
    
    return {
        "risk_amount": f"${risk_amount:.2f}",
        "position_value": f"${position_value:.2f}",
        "quantity": f"{quantity:.6f}",
        "max_loss": f"-${risk_amount:.2f} ({risk_pct}% of capital)",
        "breakeven_fee": f"{price_risk_pct:.2f}% move needed for stop"
    }

result = calculate_position_size(
    total_capital=10000,
    entry_price=60000,
    stop_price=58200,  # 3% below entry
    risk_pct=2.0
)
print(result)
# position_value: $6,666.67 | max_loss: -$200 | quantity: 0.000111 BTC

Putting It All Together

def run_trade(symbol: str, capital: float, entry_price: float):
    """Complete trade with position sizing, stop-loss, and monitoring."""
    # Calculate stop and target (3% SL, 9% TP = 3:1 reward-risk)
    stop = entry_price * 0.97
    target = entry_price * 1.09
    
    # Calculate size
    sizing = calculate_position_size(capital, entry_price, stop, risk_pct=2.0)
    quantity = float(sizing['quantity'])
    
    # Enter
    order = exchange.create_market_buy_order(symbol, quantity)
    print(f"Entered {symbol}: {quantity} @ ${entry_price:,}")
    
    # Create position monitor
    pos = TrailingStopPosition(symbol, entry_price, quantity, trail_pct=3.0)
    
    # Monitor (in real usage, run in a separate thread)
    while pos.is_open:
        price = float(exchange.fetch_ticker(symbol)['last'])
        action = pos.update(price)
        if action == 'stop':
            pos.close()
            print(f"Exited with stop")
        time.sleep(30)

The key insight: stop-losses don't prevent losses โ€” they limit them. A 3% loss is a speed bump. A 40% loss is a disaster. Automate your exits and protect your capital.

Related Articles