Advertisement IC Markets

Stop-Loss Placement Is Not Optional Decoration

Master exponential money management, position sizing calculators, strict daily stop-loss limits, and overcoming FOMO on micro-timeframes.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Stop-Loss Placement Is Not Optional Decoration

Post by FTtrader »

2. Pro cTrader cBot (C#): Event-Driven Risk Enforcer

If you are executing manually or running algorithmic strategies directly on your broker infrastructure, scanning a loop on every tick wastes CPU cycles. A professional C# implementation hooks into the Positions.Opened event and processes execution asynchronously.

This cBot runs in the background. If you manually open a trade without a stop loss, it instantly calculates a structural stop using an ATR buffer and applies it asynchronously to avoid locking up the main thread.

Code: Select all

using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ProRiskEnforcer : Robot
    {
        [Parameter("ATR Period", DefaultValue = 14, Group = "Risk Structure")]
        public int AtrPeriod { get; set; }

        [Parameter("ATR Multiplier", DefaultValue = 1.5, Group = "Risk Structure")]
        public double AtrMultiplier { get; set; }

        private AverageTrueRange _atr;

        protected override void OnStart()
        {
            // Initialize ATR for dynamic volatility buffering
            _atr = Indicators.AverageTrueRange(MarketData.GetBars(TimeFrame.Minute15), AtrPeriod, MovingAverageType.Exponential);
            
            // Subscribe only to relevant events (highly efficient)
            Positions.Opened += OnPositionOpened;
            
            Print("Pro Risk Enforcer initialized. Monitoring for naked positions.");
        }

        private void OnPositionOpened(PositionOpenedEventArgs args)
        {
            var position = args.Position;

            // Immediately check if the order lacks a Stop Loss
            if (!position.StopLoss.HasValue)
            {
                Print($"[ALERT] Naked position detected on {position.SymbolName} (Ticket: {position.Id}). Calculating structural SL.");
                ApplyStructuralStopLoss(position);
            }
        }

        private void ApplyStructuralStopLoss(Position position)
        {
            var bars = MarketData.GetBars(TimeFrame.Minute15);
            double currentAtr = _atr.Result.Last(1);
            double buffer = currentAtr * AtrMultiplier;
            
            double? newStopLoss = null;

            if (position.TradeType == TradeType.Buy)
            {
                // Find recent swing low over the last 15 periods
                double swingLow = bars.LowPrices.Minimum(15);
                newStopLoss = swingLow - buffer;
            }
            else if (position.TradeType == TradeType.Sell)
            {
                // Find recent swing high over the last 15 periods
                double swingHigh = bars.HighPrices.Maximum(15);
                newStopLoss = swingHigh + buffer;
            }

            if (newStopLoss.HasValue)
            {
                // Execute modification asynchronously so the bot's thread isn't blocked
                ModifyPositionAsync(position, newStopLoss, position.TakeProfit, hasError =>
                {
                    if (hasError.IsSuccessful)
                        Print($"[SUCCESS] Structural SL applied to {position.Id} at {Math.Round(newStopLoss.Value, 5)}");
                    else
                        Print($"[ERROR] Failed to apply SL to {position.Id}: {hasError.Error}");
                });
            }
        }
    }
}
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Stop-Loss Placement Is Not Optional Decoration

Post by FTtrader »

Here are the professional-grade implementations for both MetaTrader 5 (MQL5) and MetaTrader 4 (MQL4).

To make these truly "Pro", they include several critical safeguards that separate amateur scripts from institutional utilities:

StopLevel Protection: Brokers have minimum stop distances. If the calculated structural stop is too close to the current price, the script automatically adjusts it to the broker's minimum allowable distance to prevent Error 130 (Invalid Stops) rejections.

Completed Candle Referencing: The structure array begins at candle index 1 (the last closed candle). This ensures it doesn't calculate a stop based on the currently forming, fluctuating candle.

Digit Normalization: Prices are rounded precisely to your broker’s pip decimal settings (3-digit or 5-digit) to prevent formatting errors.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Stop-Loss Placement Is Not Optional Decoration

Post by FTtrader »

Run these as an Expert Advisor (EA) on the chart you are actively trading. It will quietly monitor your manual entries in the background and instantly bolt a structural stop onto any naked position it finds.

MetaTrader 5 (MQL5) Pro Risk Enforcer

MT5 separates "Orders" (pending) from "Positions" (active trades). This EA iterates strictly through your active positions on the current chart symbol.

Code: Select all

//+------------------------------------------------------------------+
//|                                        ProRiskEnforcer_MT5.mq5   |
//|                                                                  |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>

input int    InpLookback     = 15;    // Structure Lookback (Bars)
input bool   InpUseAtr       = true;  // Use ATR Volatility Buffer?
input int    InpAtrPeriod    = 14;    // ATR Period
input double InpAtrMultiplier= 1.5;   // ATR Multiplier

CTrade trade;
int atrHandle;

int OnInit() 
{
    // Initialize the ATR indicator handle
    atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
    if(atrHandle == INVALID_HANDLE) 
    {
        Print("Failed to create ATR handle");
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

void OnTick() 
{
    // Scan all open positions
    for(int i = PositionsTotal() - 1; i >= 0; i--) 
    {
        ulong ticket = PositionGetTicket(i);
        
        // Only manage positions for the chart's active symbol
        if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol) 
        {
            double sl = PositionGetDouble(POSITION_SL);
            
            // If the position has no Stop Loss (0.0), calculate and apply one
            if(sl == 0.0) 
            {
                ApplyStructuralSL(ticket);
            }
        }
    }
}

void ApplyStructuralSL(ulong ticket) 
{
    long type = PositionGetInteger(POSITION_TYPE);
    double tp = PositionGetDouble(POSITION_TP);
    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    double stoplevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
    
    // Calculate ATR Buffer
    double buffer = 0.0;
    if(InpUseAtr) 
    {
        double atrArray[1];
        if(CopyBuffer(atrHandle, 0, 1, 1, atrArray) > 0) 
            buffer = atrArray[0] * InpAtrMultiplier;
    } 
    else buffer = 10 * point; // Fallback hard buffer
    
    double newSL = 0.0;
    
    if(type == POSITION_TYPE_BUY) 
    {
        double lowArray[];
        ArraySetAsSeries(lowArray, true);
        if(CopyLow(_Symbol, _Period, 1, InpLookback, lowArray) > 0) 
        {
            double swingLow = lowArray[ArrayMinimum(lowArray)];
            newSL = swingLow - buffer;
            
            // Protect against Broker StopLevels
            double minSL = SymbolInfoDouble(_Symbol, SYMBOL_BID) - stoplevel;
            if(newSL > minSL) newSL = minSL;
        }
    } 
    else if(type == POSITION_TYPE_SELL) 
    {
        double highArray[];
        ArraySetAsSeries(highArray, true);
        if(CopyHigh(_Symbol, _Period, 1, InpLookback, highArray) > 0) 
        {
            double swingHigh = highArray[ArrayMaximum(highArray)];
            newSL = swingHigh + buffer;
            
            // Protect against Broker StopLevels
            double minSL = SymbolInfoDouble(_Symbol, SYMBOL_ASK) + stoplevel;
            if(newSL < minSL) newSL = minSL;
        }
    }

    if(newSL > 0.0) 
    {
        newSL = NormalizeDouble(newSL, digits);
        if(trade.PositionModify(ticket, newSL, tp)) 
            Print("[SUCCESS] Structural SL applied to ticket ", ticket, " at ", newSL);
        else 
            Print("[ERROR] Failed to apply SL to ticket ", ticket, " Code: ", GetLastError());
    }
}
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

Re: Stop-Loss Placement Is Not Optional Decoration

Post by FTtrader »

MetaTrader 4 (MQL4) Pro Risk Enforcer

MT4 uses a legacy pool system where all trades (active and pending) are grouped as "Orders". This script selects open market orders sequentially, checks if they belong to the current chart, and verifies their stop status.

Code: Select all

//+------------------------------------------------------------------+
//|                                        ProRiskEnforcer_MT4.mq4   |
//|                                                                  |
//+------------------------------------------------------------------+
#property strict

extern int    InpLookback     = 15;    // Structure Lookback (Bars)
extern bool   InpUseAtr       = true;  // Use ATR Volatility Buffer?
extern int    InpAtrPeriod    = 14;    // ATR Period
extern double InpAtrMultiplier= 1.5;   // ATR Multiplier

int OnInit() 
{
    return(INIT_SUCCEEDED);
}

void OnTick() 
{
    // Iterate over all active orders in the terminal
    for(int i = OrdersTotal() - 1; i >= 0; i--) 
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 
        {
            // Filter for current chart symbol and market execution (Buy/Sell)
            if(OrderSymbol() == Symbol() && (OrderType() == OP_BUY || OrderType() == OP_SELL)) 
            {
                // If StopLoss is zero (naked), calculate and apply
                if(OrderStopLoss() == 0.0) 
                {
                    ApplyStructuralSL();
                }
            }
        }
    }
}

void ApplyStructuralSL() 
{
    int ticket = OrderTicket();
    int type = OrderType();
    double tp = OrderTakeProfit();
    double openPrice = OrderOpenPrice();
    double stoplevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
    
    // Calculate ATR Buffer
    double buffer = 0.0;
    if(InpUseAtr) 
        buffer = iATR(Symbol(), 0, InpAtrPeriod, 1) * InpAtrMultiplier;
    else 
        buffer = 10 * Point; // Fallback hard buffer
        
    double newSL = 0.0;
    
    if(type == OP_BUY) 
    {
        // Find the lowest low index starting from candle 1 (closed candle)
        int lowestIndex = iLowest(Symbol(), 0, MODE_LOW, InpLookback, 1);
        double swingLow = Low[lowestIndex];
        newSL = swingLow - buffer;
        
        // Protect against Broker StopLevels
        double minSL = Bid - stoplevel;
        if(newSL > minSL) newSL = minSL - Point; 
    } 
    else if(type == OP_SELL) 
    {
        // Find the highest high index starting from candle 1
        int highestIndex = iHighest(Symbol(), 0, MODE_HIGH, InpLookback, 1);
        double swingHigh = High[highestIndex];
        newSL = swingHigh + buffer;
        
        // Protect against Broker StopLevels
        double minSL = Ask + stoplevel;
        if(newSL < minSL) newSL = minSL + Point;
    }

    if(newSL > 0.0) 
    {
        // Sanitize digits before server request
        newSL = NormalizeDouble(newSL, Digits);
        
        // Modify order
        bool res = OrderModify(ticket, openPrice, newSL, tp, 0, clrNONE);
        
        if(res) 
            Print("[SUCCESS] Structural SL applied to ticket ", ticket, " at ", newSL);
        else 
            Print("[ERROR] Failed to apply SL to ticket ", ticket, " Code: ", GetLastError());
    }
}
Post Reply