← Back to Blog
RISK MANAGEMENT

Stop Loss and Take Profit Automation: Risk Management for Trading Bots

June 15, 2026 · 7 min read · LMEX.AI

Most retail trading bots don't have proper exit logic. They have entry signals and hope. Automating stops and take-profits is what separates bots that survive from bots that don't.


This article walks through the basic exit toolkit, how to implement it on LMEX in Python, and the placement decisions that matter.


Why automated exits matter


A trader who manually manages exits has two problems: they sleep, and they have emotions. Both produce worse outcomes than a bot that follows fixed rules without negotiation.


A 24/7 crypto market doesn't care about your sleep schedule. A position you open at 9pm can lose half its value by 6am if you're not watching. Automated stops handle this.


Manual exits also tend to be biased: cutting winners early (taking small profits), letting losers run (hoping for recovery). Automation removes this bias entirely. The rule fires regardless of how you feel about the trade.


The basic toolkit


Four exit mechanisms cover 95% of use cases:


**Stop loss** — hard exit at a worse price than entry. Bounds maximum loss per trade. The most important exit.


**Take profit** — hard exit at a better price than entry. Locks in gains at a predetermined level.


**Trailing stop** — moves with price in favour of the position, never against. Captures unrealised gains while letting winners run.


**OCO (One-Cancels-Other)** — places both stop and take profit; when either fills, the other cancels automatically. Eliminates "double exit" risk.


A well-designed bot uses all four depending on the strategy. Some strategies use only stops (momentum). Some use trailing stops (trend-following). Some use OCO (mean reversion with defined targets).


Implementing on LMEX


LMEX supports stop orders, take profits, and OCO natively. Through CCXT:


import ccxt

exchange = ccxt.lmex({'apiKey': '...', 'secret': '...'})

def enter_with_stops(symbol, side, qty, entry_price, stop_loss, take_profit):
    # Market entry
    entry_order = exchange.create_order(symbol, 'market', side, qty)
    
    # Stop loss
    exit_side = 'sell' if side == 'buy' else 'buy'
    stop_order = exchange.create_order(
        symbol, 'stop', exit_side, qty, None,
        {'stopPrice': stop_loss, 'reduceOnly': True}
    )
    
    # Take profit
    tp_order = exchange.create_order(
        symbol, 'limit', exit_side, qty, take_profit,
        {'reduceOnly': True}
    )
    
    return {
        'entry': entry_order['id'],
        'stop': stop_order['id'],
        'take_profit': tp_order['id']
    }

`reduceOnly: True` ensures the exit orders only close existing positions, never open new ones. This prevents stops from accidentally creating positions in volatile conditions.


The above places stop and take profit as separate orders. If the stop fills, you need to manually cancel the take profit (and vice versa) to avoid having a stray order sitting in the book.


True OCO via CCXT:


def enter_oco(symbol, side, qty, entry_price, stop_loss, take_profit):
    entry_order = exchange.create_order(symbol, 'market', side, qty)
    
    exit_side = 'sell' if side == 'buy' else 'buy'
    
    # OCO bundles stop and take profit — either fill cancels the other
    oco_order = exchange.create_order(
        symbol, 'limit', exit_side, qty, take_profit,
        {
            'stopPrice': stop_loss,
            'type': 'oco',
            'reduceOnly': True,
        }
    )
    
    return {'entry': entry_order['id'], 'exit': oco_order['id']}

Always prefer OCO when supported. It removes the manual-cancellation step that's a common source of bugs.


Stop placement


The wrong stop placement is worse than no stop. Three common approaches:


**ATR-based** — use 2× the Average True Range as the stop distance. Adapts to current volatility automatically. When markets are calm, stops are tight; when volatile, stops are wider. Most robust default.


def calculate_atr_stop(df, multiplier=2):
    high_low = df['h'] - df['l']
    high_close = abs(df['h'] - df['c'].shift())
    low_close = abs(df['l'] - df['c'].shift())
    tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
    atr = tr.ewm(span=14).mean().iloc[-1]
    return multiplier * atr

**Percentage-based** — fixed percentage from entry (e.g., 2% below for longs). Simple but doesn't adapt to volatility. Works fine for low-volatility assets, terrible for crypto.


**Structure-based** — place stop just beyond the most recent swing low (for longs) or swing high (for shorts). Respects market structure. Better for swing trading than HFT.


For most retail bots, ATR-based with a 2× multiplier is the sweet spot. Tight enough to bound risk, wide enough to avoid noise stops.


Take profit ladders


A single take profit at a fixed target is suboptimal. Better: ladder partial exits at multiple targets.


A typical ladder for a long position:

  • Exit 33% at 1.5× ATR profit
  • Exit 33% at 3× ATR profit
  • Trail the remaining 33% with a trailing stop

  • This captures partial gains early (psychological win), leaves room for big winners, and lets the trailing stop handle the runner.


    def setup_laddered_exit(symbol, side, total_qty, atr):
        chunk = total_qty / 3
        chunk = exchange.amount_to_precision(symbol, chunk)
        
        exit_side = 'sell' if side == 'buy' else 'buy'
        current_price = exchange.fetch_ticker(symbol)['last']
        direction = 1 if side == 'buy' else -1
        
        # First target
        exchange.create_order(symbol, 'limit', exit_side, chunk,
            current_price + direction * 1.5 * atr,
            {'reduceOnly': True})
        
        # Second target
        exchange.create_order(symbol, 'limit', exit_side, chunk,
            current_price + direction * 3 * atr,
            {'reduceOnly': True})
        
        # Trailing stop handled separately on the remaining chunk

    Ladders work best for strategies with high reward potential and lower win rate (trend following, breakouts). For high win-rate strategies (mean reversion), simpler single-target exits are usually better.


    Frequently Asked Questions


    Q: How tight should my stops be?

    Tight enough to bound risk, wide enough to avoid noise. 2× ATR is the standard default. Tighter than 1× ATR produces excessive whipsaws. Wider than 3× ATR exposes too much capital per trade.


    Q: Should I use stop-market or stop-limit orders?

    Stop-market for protection (guaranteed fill at any price, may slip during flash crashes). Stop-limit for precision (fills only at your specified price, may not fill at all in extreme moves). For risk management, prefer stop-market — slippage is better than not exiting.


    Q: How do I handle stops being hunted during volatile periods?

    Wider stops, smaller positions. The position sizing math means risking the same dollar amount per trade regardless of stop width. A wider stop with smaller position size has the same risk as a tight stop with larger position — but avoids being stopped on noise.


    Q: Can I move stops as the trade progresses?

    Only in your favour. Move stops to breakeven when the trade has run 1× ATR in profit. Move to lock in partial profit at 2× ATR. Never widen a stop because the trade went against you — that's the path to large losses.


    Related Articles


    → Portfolio Risk Management for Algorithmic Traders on LMEX
    → Kelly Criterion: Mathematically Optimal Position Sizing for LMEX Traders
    → The Math of Drawdown Recovery (And Why It Should Terrify You)
    ← All ArticlesBuild a Bot →