Insights/Insights & Articles/Risk Management in Algo Trading: 10 Techniques Every Bot Must Have

Risk Management in Algo Trading: 10 Techniques Every Bot Must Have

Let us tell you how accounts actually die.

Mar 6, 2026

Risk Management in Algo Trading: 10 Techniques Every Bot Must Have

Risk Management in Algo Trading: 10 Techniques Every Bot Must Have

Let us tell you how accounts actually die.

It's almost never the strategy. We've watched solid, backtested-to-death strategies blow up accounts because someone forgot a drawdown check. Or ran without a spread filter and got eaten alive by widening spreads at midnight. Or stacked ten positions in the same direction because nothing said stop.

After building over a hundred trading algorithms, we can tell you this with certainty: risk management is not a section of your code. It is the code. Everything else is just deciding when to click buy.

Here are ten techniques. All battle-tested. All from production.

Technique 1: ATR-Based Stop Losses

Fixed stops are lazy. Thirty pips might be generous on a sleepy Tuesday. During NFP? That's pocket change – you'll get stopped out by the noise alone.

ATR solves this. Volatile market? Wider stop. Calm market? Tighter stop. The market tells you what's appropriate.

MQL4 Implementation

// Calculate ATR-based stop loss
double atr = iATR(NULL, 0, 14, 1);  // 14-period ATR on the last completed bar
double sl_distance = atr * 1.5;      // SL = 1.5 × ATR
 
// For a BUY order:
double stop_loss = Ask - sl_distance;
double take_profit = Ask + (sl_distance * 2.0);  // TP = 2:1 reward-to-risk
 
// Ensure minimum stop distance
double min_sl = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
if (sl_distance < min_sl) sl_distance = min_sl;

Python Equivalent

import pandas as pd
 
def atr_stop_loss(df, period=14, multiplier=1.5):
    """Calculate ATR-based stop loss distance."""
    high_low = df['high'] - df['low']
    high_close = abs(df['high'] - df['close'].shift(1))
    low_close = abs(df['low'] - df['close'].shift(1))
 
    true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
    atr = true_range.rolling(window=period).mean()
 
    stop_distance = atr * multiplier
    return stop_distance
 
# Usage
df['sl_distance'] = atr_stop_loss(df)
df['buy_sl'] = df['close'] - df['sl_distance']
df['buy_tp'] = df['close'] + df['sl_distance'] * 2.0  # 2:1 R:R

The math: $SL_{\text{distance}} = ATR(14) \times 1.5$

A 1.5× multiplier is the sweet spot for most strategies we've built. Go tighter (1.0×) and random wicks eat you alive. Go wider (3.0+) and your risk-per-trade math starts looking ugly.

Technique 2: Risk-Based Position Sizing

Here's a mistake we see constantly: fixed lot sizes. Someone trades 1.0 lots with a 20-pip stop (risking $200) and also 1.0 lots with a 50-pip stop (risking $500). Same lot size, completely different risk. That's not managing risk – that's pretending to.

The fix is brutally simple. Calculate your lot size so that every single trade risks the same dollar amount, regardless of how far away your stop is.

MQL4 Implementation (from production EAs)

double LotsByRisk(double riskPercent, double slPrice)
{
    double riskMoney = AccountBalance() * riskPercent / 100.0;
 
    // Guard against bad data
    if (Bid == 0 || Ask == 0) return MarketInfo(Symbol(), MODE_MINLOT);
 
    double pipValue = MarketInfo(Symbol(), MODE_TICKVALUE);
    double slPips = MathAbs((Ask - slPrice) / Point);
 
    if (pipValue <= 0) pipValue = 1.0;
    if (slPips <= 0) return MarketInfo(Symbol(), MODE_MINLOT);
 
    double lots = riskMoney / (slPips * pipValue);
 
    // Round down to broker's lot step (don't round up – that increases risk)
    double step = MarketInfo(Symbol(), MODE_LOTSTEP);
    lots = MathFloor(lots / step) * step;
 
    // Clamp to broker limits
    lots = MathMax(lots, MarketInfo(Symbol(), MODE_MINLOT));
    lots = MathMin(lots, MarketInfo(Symbol(), MODE_MAXLOT));
 
    return NormalizeDouble(lots, 2);
}

Python Equivalent

def calculate_lot_size(account_balance, risk_pct, stop_distance_pips, pip_value):
    """
    Calculate position size for consistent risk per trade.
 
    Args:
        account_balance: Current account balance
        risk_pct: Risk percentage (e.g., 1.0 for 1%)
        stop_distance_pips: Stop loss distance in pips
        pip_value: Value of 1 pip for 1 standard lot
    """
    risk_amount = account_balance * risk_pct / 100
    lot_size = risk_amount / (stop_distance_pips * pip_value)
    return round(lot_size, 2)
 
# Example: $10,000 account, 1% risk, 25 pip stop, $10/pip for EURUSD
lots = calculate_lot_size(10000, 1.0, 25, 10)  # = 0.40 lots

The formula: $\text{Lots} = \frac{\text{Balance} \times \text{Risk%} / 100}{\text{SL}_{\text{pips}} \times \text{PipValue}}$

At 1% risk per trade, you can be wrong fifty times consecutively and still have over 60% of your account left. That's not optimism. That's compound math working in your favor for once.

Technique 3: Maximum Drawdown Limit

Even strategies with a genuine edge go through ugly stretches. The question isn't will you have a drawdown. It's how bad do you let it get before the machine shuts itself off?

Track peak equity. When the gap between peak and current gets too wide, stop trading. Walk away. Let the market do its thing while your capital stays intact.

MQL4 Implementation

bool ExceededDrawdown(double maxDrawdownPercent)
{
    static double peakEquity = 0.0;
    double currentEquity = AccountEquity();
 
    // Track the highest equity we've reached
    if (peakEquity < currentEquity) peakEquity = currentEquity;
 
    // Calculate current drawdown percentage
    double drawdownPct = (peakEquity - currentEquity) / peakEquity * 100.0;
 
    if (drawdownPct >= maxDrawdownPercent)
    {
        Print("DRAWDOWN LIMIT HIT: ", DoubleToString(drawdownPct, 2),
              "% >= ", maxDrawdownPercent, "%. Trading paused.");
        return true;
    }
    return false;
}
 
// Usage in OnTick():
void OnTick()
{
    if (ExceededDrawdown(10.0)) return;  // Stop trading at 10% drawdown
    // ... rest of trading logic
}

Python Equivalent

class DrawdownMonitor:
    def __init__(self, max_drawdown_pct=10.0):
        self.peak_equity = 0
        self.max_dd_pct = max_drawdown_pct
 
    def check(self, current_equity):
        self.peak_equity = max(self.peak_equity, current_equity)
        dd_pct = (self.peak_equity - current_equity) / self.peak_equity * 100
        if dd_pct >= self.max_dd_pct:
            print(f"DRAWDOWN LIMIT: {dd_pct:.2f}% – trading paused")
            return False  # Don't trade
        return True  # OK to trade

We've seen people set this at 30%. Don't. By the time you're down 30%, the psychological damage alone makes recovery almost impossible – even if the math says otherwise. Five to ten percent for conservative strategies. Fifteen to twenty for aggressive ones.

Technique 4: Daily Trade Limit

Choppy markets are a bot's worst enemy. Whipsaw after whipsaw, your algorithm enters and exits and enters again, bleeding commissions and slippage with every round trip. We've watched EAs rack up 50+ trades in a single session during range-bound days. Not one of those trades needed to happen.

Put a cap on it.

MQL4 Implementation

int CountTradesToday()
{
    int count = 0;
    datetime startOfDay = iTime(NULL, PERIOD_D1, 0);
 
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
        if (OrderSymbol() != Symbol()) continue;
        if (OrderMagicNumber() != MagicNumber) continue;
        if (OrderOpenTime() >= startOfDay) count++;
    }
 
    // Also check closed orders in history
    for (int j = OrdersHistoryTotal() - 1; j >= 0; j--)
    {
        if (!OrderSelect(j, SELECT_BY_POS, MODE_HISTORY)) continue;
        if (OrderSymbol() != Symbol()) continue;
        if (OrderMagicNumber() != MagicNumber) continue;
        if (OrderOpenTime() >= startOfDay) count++;
    }
    return count;
}
 
// Usage:
if (CountTradesToday() >= MaxTradesPerDay) return;  // Skip new entries

Three to five for swing strategies. Ten to twenty for scalpers. Beyond that, you'd better be running true HFT with sub-millisecond infrastructure – and if you are, you're probably not reading this article.

Technique 5: Spread Filter

Spreads widen at the worst possible moments – thin liquidity, news bombs, session transitions. If your stop loss is 10 pips and you enter with a 5-pip spread, congratulations: you started the trade already halfway to being stopped out.

MQL4 Implementation

bool IsSpreadAcceptable(double maxSpreadPoints)
{
    double currentSpread = (Ask - Bid) / Point;
    if (currentSpread > maxSpreadPoints)
    {
        Print("Spread too wide: ", DoubleToString(currentSpread, 1),
              " > ", maxSpreadPoints, " – skipping trade");
        return false;
    }
    return true;
}
 
// Usage:
if (!IsSpreadAcceptable(20.0)) return;  // Max 20 points (2 pips on 5-digit broker)

If your average stop is 30 pips, keep your max spread under 6. Twenty percent is the ceiling.

Technique 6: Trading Hours Filter

Not all hours are created equal. EUR pairs during the Asian session? Thin as paper. The last 15 minutes before US close? Chaos. Your strategy might have an edge – but only during London. Or only during the New York overlap. Code accordingly.

MQL4 Implementation

bool IsTradingHour(int startHour, int endHour)
{
    int currentHour = TimeHour(TimeCurrent());
 
    if (startHour <= endHour)
        return (currentHour >= startHour && currentHour <= endHour);
    else  // Handles overnight windows (e.g., 22 to 06)
        return (currentHour >= startHour || currentHour <= endHour);
}
 
bool IsAllowedWeekday(bool allowMonday, bool allowFriday)
{
    int weekday = TimeDayOfWeek(TimeCurrent());  // 0=Sunday
    if (weekday == 0) return false;               // Never trade Sunday
    if (weekday == 1 && !allowMonday) return false;
    if (weekday == 5 && !allowFriday) return false;
    return true;
}

Monday opens carry weekend gap risk. Friday trades carry over-the-weekend exposure. Both are optional hazards. Every production EA we build lets you toggle them off.

Technique 7: Trailing Stop Losses

Here's a scenario that hurts every time: you catch a real move. Price runs 200 pips in your direction. But your take profit was set at 50 pips because that's what the backtest said was optimal. Or worse – price runs 150 pips, you don't take profit, and it reverses all the way back to your entry.

Trailing stops fix both problems. Lock in profit as price moves. Let winners breathe. Don't give it all back.

MQL4 Implementation (from production code)

void ManageTrailingStops(int trailStartPoints, int trailStepPoints)
{
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
        if (OrderMagicNumber() != MagicNumber) continue;
        if (OrderSymbol() != Symbol()) continue;
 
        double profitPoints;
        if (OrderType() == OP_BUY)
            profitPoints = (Bid - OrderOpenPrice()) / Point;
        else if (OrderType() == OP_SELL)
            profitPoints = (OrderOpenPrice() - Ask) / Point;
        else continue;
 
        // Only start trailing after minimum profit reached
        if (profitPoints < trailStartPoints) continue;
 
        double newSL;
        if (OrderType() == OP_BUY)
        {
            newSL = NormalizeDouble(Bid - trailStepPoints * Point, Digits);
            // Only move stop UP, never down
            if (newSL > OrderStopLoss())
                OrderModify(OrderTicket(), OrderOpenPrice(), newSL,
                           OrderTakeProfit(), 0, clrBlue);
        }
        else // OP_SELL
        {
            newSL = NormalizeDouble(Ask + trailStepPoints * Point, Digits);
            // Only move stop DOWN, never up
            if (newSL < OrderStopLoss() || OrderStopLoss() == 0)
                OrderModify(OrderTicket(), OrderOpenPrice(), newSL,
                           OrderTakeProfit(), 0, clrRed);
        }
    }
}

Start trailing after 30 pips of profit, keep the stop 5 pips behind. Adjust to taste and your strategy's ATR.

Technique 8: Minimum Free Margin Check

Opening positions near margin call is like gunning the engine with the oil light on. The broker will liquidate you at the worst possible price. Check free margin before every entry. We use a 2× buffer – if you can't afford twice the margin required, sit this one out.

MQL4 Implementation

bool HasSufficientMargin(double lots)
{
    double freeMargin = AccountFreeMargin();
    double requiredMargin = MarketInfo(Symbol(), MODE_MARGINREQUIRED) * lots;
 
    // Require at least 2× the margin needed (safety buffer)
    if (freeMargin < requiredMargin * 2)
    {
        Print("Insufficient margin. Free: ", freeMargin,
              " Required: ", requiredMargin);
        return false;
    }
    return true;
}

Technique 9: Equity-Based Exit (The Nuclear Option)

Individual stops protect individual trades. But what about correlated losses across five positions? Or a flash crash that gaps through your stops entirely?

This is the emergency brake. Monitor total account equity. If it drops below a floor – or hits a daily profit target – flatten everything. No exceptions.

Nearly every professional EA we've built uses some variation of this:

void CheckEquityLimits()
{
    double balance = AccountBalance();
    double equity = AccountEquity();
 
    // Emergency exit: close everything if equity drops too far
    double maxLoss = balance * 0.10;  // 10% of balance
    if (equity < balance - maxLoss)
    {
        Print("EQUITY EMERGENCY: Closing all positions");
        CloseAllOrders();
        return;
    }
 
    // Profit target: lock in gains
    double profitTarget = balance * 0.05;  // 5% profit target
    if (equity > balance + profitTarget)
    {
        Print("PROFIT TARGET HIT: Closing all positions");
        CloseAllOrders();
    }
}

Technique 10: Maximum Concurrent Positions

Strong trends generate signal after signal. Without a cap, your bot piles in – five trades, ten trades, twenty – all pointing the same direction. And when the reversal comes, it takes the entire stack down at once.

Three to five concurrent positions is a sane default. More than that and you're not diversifying; you're concentrating.

MQL4 Implementation

int CountOpenPositions()
{
    int count = 0;
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
        if (OrderMagicNumber() != MagicNumber) continue;
        if (OrderType() == OP_BUY || OrderType() == OP_SELL) count++;
    }
    return count;
}
 
// Usage in trade entry:
if (CountOpenPositions() >= MaxPositions) return;  // Don't stack trades

The Master Risk Check Function

In production, we collapse all ten checks into a single gatekeeper. One function call before any entry logic. If it returns false, the EA goes back to sleep. Clean. Simple. Saves accounts.

bool PassesRiskChecks()
{
    // 1. Drawdown check
    if (ExceededDrawdown(10.0)) return false;
 
    // 2. Daily trade limit
    if (CountTradesToday() >= MaxTradesPerDay) return false;
 
    // 3. Spread filter
    if (!IsSpreadAcceptable(20.0)) return false;
 
    // 4. Trading hours
    if (!IsTradingHour(TradingStartHour, TradingEndHour)) return false;
 
    // 5. Weekday filter
    if (!IsAllowedWeekday(AllowMonday, AllowFriday)) return false;
 
    // 6. Margin check
    if (AccountFreeMargin() < MinimumFreeMargin) return false;
 
    // 7. Max positions
    if (CountOpenPositions() >= MaxPositions) return false;
 
    return true;  // All checks passed – safe to trade
}

One line. All ten checks. That's the whole point – building it once so you never have to think about it again.

void OnTick()
{
    if (!PassesRiskChecks()) return;  // One line. All risk checks. Done.
    // ... entry logic
}

Risk Management Cheat Sheet

TechniquePreventsRecommended Default
ATR Stop LossStops too tight/wide for conditions1.5 × ATR(14)
Risk-Based SizingInconsistent risk per trade1-2% per trade
Max DrawdownAccount devastation10% pause, 20% stop
Daily Trade LimitOvertrading in chop3-5 (swing), 10-20 (scalp)
Spread FilterBad entries in thin markets< 20% of avg stop distance
Trading HoursLow-liquidity trapsActive sessions only
Trailing StopsGiving back winnersStart at 300pts, trail at 50pts
Margin CheckMargin calls2× required margin minimum
Equity ExitCatastrophic multi-position loss±5-10% of balance
Max PositionsConcentration risk3-5 concurrent positions

Need Someone to Audit Your Risk Logic?

Every algorithm we build ships with all ten of these baked in. Not because we're cautious by nature – because we've seen what happens without them.

Get Your Strategy Risk-Audited →

Algorithmic Trading Insights by Quantumcona.

2of4