Page 1 of 2

Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Jul 26, 2026 3:34 pm
by PTScalper
Are you still using fixed pip stop losses for your scalp trades? In fast-paced forex scalping, relying on static 5 or 10-pip stops is the fastest way to get stopped out by normal market noise. Volatility shifts constantly between trading sessions. That is why I prepared this lightweight, dynamic Average True Range (ATR) Stop Loss Calculator for both MT4 and MT5.

Why is an ATR-based stop essential for scalping? ATR measures true market volatility over a specified number of bars. By setting your stop loss as a multiple of the current ATR (like 1.5x ATR), your protective stop automatically widens during volatile market bursts and tightens during quiet consolidation. This prevents noise from closing valid setups while ensuring you do not over-risk when ranges compress.

How this calculator functions:
The script queries the live ATR indicator value on your execution timeframe and multiplies it by your chosen risk multiplier. It then calculates the exact point distance and price level required for your buy or sell stop loss.

Key advantages include:
• Universal Support: Native functions formatted for both MQL4 and MQL5 codebases.
• Noise Immunity: Keeps scalp trades alive during spread fluctuations and volatility spikes.
• Seamless EA Integration: Easily embeds into custom execution scripts or automated risk managers.

Grab the code above, integrate it into your scalping workflow, and stop giving capital to market noise! Let me know your favorite ATR multipliers below.

MQL4
//+------------------------------------------------------------------+
//| Function: CalculateATRStopLoss |
//| Outputs: exact stop loss price and point distance for scalping |
//+------------------------------------------------------------------+
void CalculateATRStopLoss(int orderType, int atrPeriod, double multiplier,
double &slPrice, int &slPoints)
{
// Retrieve ATR value from the previous closed bar (index 1 to avoid intrabar noise)
double atrValue = iATR(Symbol(), Period(), atrPeriod, 1);

// Calculate stop loss distance in points
slPoints = (int)MathRound((atrValue * multiplier) / Point);

// Refresh market rates to ensure current Ask/Bid
RefreshRates();

if(orderType == OP_BUY)
{
slPrice = Ask - (atrValue * multiplier);
slPrice = NormalizeDouble(slPrice, Digits);
}
else if(orderType == OP_SELL)
{
slPrice = Bid + (atrValue * multiplier);
slPrice = NormalizeDouble(slPrice, Digits);
}
}

//+------------------------------------------------------------------+
//| Example usage inside OnTick() or custom execution script |
//+------------------------------------------------------------------+
void OnTick()
{
double slPrice = 0.0;
int slPoints = 0;

// Example: Calculate 1.5x ATR(14) Stop Loss for a BUY position
CalculateATRStopLoss(OP_BUY, 14, 1.5, slPrice, slPoints);

Comment(StringFormat("Scalping ATR SL | SL Price: %.\%df | Distance: %d points",
Digits, slPrice, slPoints));
}
MQL5:
//--- Global variable to store indicator handle
int g_atrHandle = INVALID_HANDLE;

//+------------------------------------------------------------------+
//| Function: CalculateATRStopLoss |
//| Outputs: exact stop loss price and point distance for scalping |
//+------------------------------------------------------------------+
bool CalculateATRStopLoss(ENUM_ORDER_TYPE orderType, int atrPeriod, double multiplier,
double &slPrice, int &slPoints)
{
// Initialize ATR handle if invalid
if(g_atrHandle == INVALID_HANDLE)
{
g_atrHandle = iATR(_Symbol, _Period, atrPeriod);
if(g_atrHandle == INVALID_HANDLE) return false;
}

// Copy the latest closed bar ATR value (index 1)
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
if(CopyBuffer(g_atrHandle, 0, 1, 1, atrBuffer) <= 0) return false;

double atrValue = atrBuffer[0];
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);

// Calculate stop loss distance in points
slPoints = (int)MathRound((atrValue * multiplier) / point);

// Calculate exact price level based on order direction
if(orderType == ORDER_TYPE_BUY)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
slPrice = NormalizeDouble(ask - (atrValue * multiplier), digits);
}
else if(orderType == ORDER_TYPE_SELL)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
slPrice = NormalizeDouble(bid + (atrValue * multiplier), digits);
}

return true;
}

//+------------------------------------------------------------------+
//| Example usage inside OnTick() |
//+------------------------------------------------------------------+
void OnTick()
{
double slPrice = 0.0;
int slPoints = 0;

// Example: Calculate 1.5x ATR(14) Stop Loss for a BUY position
if(CalculateATRStopLoss(ORDER_TYPE_BUY, 14, 1.5, slPrice, slPoints))
{
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
Comment(StringFormat("Scalping ATR SL | SL Price: %.\%df | Distance: %d points",
digits, slPrice, slPoints));
}
}

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Fri Sep 11, 2026 7:11 pm
by LondonScalper
PTScalper wrote:fixed pip stop losses... dynamic Average True Range (ATR) Stop Loss Calculator
ATR-based stops are the right instinct for anyone who’s been murdered by a fixed 5-pip stop on a volatile morning.

I use ATR (or a close cousin) to size initial risk distance, then convert that to position size so R stays constant. Widening the stop without cutting size is how “dynamic” quietly becomes “larger gamble.” The calculator idea is useful if it outputs both stop distance and suggested lots for a fixed R.

Session caveat: ATR on M1 into London open can spike and then mean-revert; I often compute permission from a slightly higher timeframe ATR so I’m not whipped by one explosive candle.

If you’re sharing the tool, the community will get more value from the defaults you trust (period, multiplier, TF) and a note on news minutes. What multiplier are you actually using live on EURUSD London — 1.0×, 1.5×, something else?

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 1:55 am
by PropScalpDesk
ATR stops need an R budget

Dynamic ATR stops are sensible when volatility shifts between sessions. The failure mode is letting ATR dictate a stop so wide that position size becomes fantasy — or so tight that normal noise harvests you before the idea can work.

How I use ATR as a full-time scalper in Frankfurt:
  • ATR suggests a stop-distance region; R and the daily loss limit still cap size.
  • If the ATR-implied stop is wider than my max stop for that pair, I skip the trade.
  • Recalibrate by session — cash-open ATR is not lunch ATR, and treating them as equal is how size drifts.
A calculator helps. A daily loss limit still bosses the calculator. Tools do not replace the budget.

I also record ATR multiple used by session in the journal. If the multiple drifts upward every bad week, that is stop widening in disguise. Cap the multiple. Skip when the market wants more room than your budget allows.

Do you size from the ATR stop, or cap stop first and only trade when ATR fits inside the cap?

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:14 am
by FTtrader
PropScalpDesk wrote: Sun Sep 20, 2026 1:55 am ATR stops need an R budget

Dynamic ATR stops are sensible when volatility shifts between sessions. The failure mode is letting ATR dictate a stop so wide that position size becomes fantasy — or so tight that normal noise harvests you before the idea can work.

How I use ATR as a full-time scalper in Frankfurt:
  • ATR suggests a stop-distance region; R and the daily loss limit still cap size.
  • If the ATR-implied stop is wider than my max stop for that pair, I skip the trade.
  • Recalibrate by session — cash-open ATR is not lunch ATR, and treating them as equal is how size drifts.
A calculator helps. A daily loss limit still bosses the calculator. Tools do not replace the budget.

I also record ATR multiple used by session in the journal. If the multiple drifts upward every bad week, that is stop widening in disguise. Cap the multiple. Skip when the market wants more room than your budget allows.

Do you size from the ATR stop, or cap stop first and only trade when ATR fits inside the cap?
Hi,

I cap the stop first, absolutely. When trading raw price action and hunting liquidity sweeps on 15-minute charts, the market structure has to dictate the initial stop placement. If the ATR-implied stop at my entry requires more room than my hard cap allows, I simply pass on the setup. No trade is worth breaking the math.

Letting an unbounded ATR dictate your position sizing is essentially letting market noise manage your risk. As you noted, session drift is a massive trap—the liquidity and volatility at the Frankfurt open require a completely different baseline than the midday lull. If you don't cap the multiple, you aren't managing risk; you are just mathematically justifying taking a sub-par trade when the market is too erratic. The R budget must always be the absolute ceiling.

Since automating these visual checks helps keep discipline intact during fast price action, here is a custom Pine Script that enforces this exact logic directly on the chart. It plots the ATR trailing stops but dynamically color-codes them: standard when the stop fits your R budget, and a visual warning when the volatility expands beyond your hard tick cap.

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:14 am
by FTtrader
Pine Script

Code: Select all

//@version=5
indicator("ATR Risk Budget Stop", overlay=true, max_labels_count=50)

// =====================
// INPUTS
// =====================
atrLength = input.int(14, title="ATR Length", group="ATR Settings")
atrMult   = input.float(1.5, title="ATR Multiplier", step=0.1, group="ATR Settings")

// The hard R budget cap. 
// If the ATR stop requires more ticks/points than this, the trade is rejected.
maxStopTicks = input.int(150, title="Max Stop Cap (Ticks/Points)", group="Risk Budget", 
   tooltip="If the ATR stop distance exceeds this many ticks, the bands will flag as a budget violation.")

showHud = input.bool(true, title="Show Budget HUD", group="UI Settings")

// =====================
// CALCULATIONS
// =====================
atrVal   = ta.atr(atrLength)
stopDist = atrVal * atrMult

// Convert max ticks to raw price distance
maxAllowedDist = maxStopTicks * syminfo.mintick

// Check if current volatility fits inside the budget
isWithinBudget = stopDist <= maxAllowedDist

// Calculate implied stop levels relative to closing price
longStop  = close - stopDist
shortStop = close + stopDist

// =====================
// PLOTTING & VISUALS
// =====================
// Colors shift to Red if the ATR requires a stop wider than the R budget
color validColor   = color.new(color.teal, 30)
color invalidColor = color.new(color.red, 30)
color currentColor = isWithinBudget ? validColor : invalidColor

plot(longStop, title="Implied Long Stop", color=currentColor, style=plot.style_stepline, linewidth=2)
plot(shortStop, title="Implied Short Stop", color=currentColor, style=plot.style_stepline, linewidth=2)

// Background highlight when budget is violated to prevent entry
bgcolor(not isWithinBudget ? color.new(color.red, 95) : na, title="Budget Violation Alert")

// =====================
// HEADS UP DISPLAY (HUD)
// =====================
if showHud and barstate.islast
    var table riskTable = table.new(position.bottom_right, 2, 2, border_width=2, border_color=color.black)
    
    // Row 1: Current ATR size
    table.cell(riskTable, 0, 0, "Implied Stop Size:", text_color=color.white, bgcolor=color.rgb(40, 40, 40), text_halign=text.align_left)
    table.cell(riskTable, 1, 0, str.tostring(stopDist / syminfo.mintick, "#.#") + " ticks", text_color=color.white, bgcolor=color.rgb(40, 40, 40))
    
    // Row 2: Status against Cap
    table.cell(riskTable, 0, 1, "Budget Status:", text_color=color.white, bgcolor=currentColor, text_halign=text.align_left)
    table.cell(riskTable, 1, 1, isWithinBudget ? "WITHIN CAP" : "EXCEEDS CAP", text_color=color.white, bgcolor=currentColor)

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:15 am
by FTtrader
Professional-Grade Pine Script Implementation

To elevate this to an institutional standard, this updated script introduces explicit state management, session filtering, and dynamic position sizing calculations. It calculates your exact unit size based on a defined account risk percentage, strictly rejecting the setup if the ATR breaches your maximum tick budget or falls outside your active trading session.

Code: Select all

//@version=5
indicator("Institutional Risk Desk: ATR & Budget", overlay=true, max_labels_count=50)

// =========================================================================
// 1. INPUTS & CONFIGURATION
// =========================================================================
groupRisk    = "Risk Management (R-Budget)"
accountSize  = input.float(100000, title="Account Balance", group=groupRisk)
riskPerTrade = input.float(1.0, title="Risk Per Trade (%)", step=0.1, group=groupRisk)
maxStopTicks = input.int(150, title="Max Stop Cap (Ticks)", tooltip="Hard reject if ATR stop exceeds this", group=groupRisk)

groupATR     = "Volatility Engine"
atrLength    = input.int(14, title="ATR Length", group=groupATR)
atrMult      = input.float(1.5, title="ATR Multiplier", step=0.1, group=groupATR)

groupSession = "Session Constraints"
useSession   = input.bool(true, title="Enforce Trading Session", group=groupSession)
sessionTime  = input.session("0800-1200", title="Active Session (e.g., Frankfurt/London)", group=groupSession)

// =========================================================================
// 2. CORE CALCULATIONS & STATE MANAGEMENT
// =========================================================================
// Session handling
inSession = not useSession or not na(time(timeframe.period, sessionTime))

// Volatility measurements
tickSize  = syminfo.mintick
tickValue = syminfo.pointvalue
atrVal    = ta.atr(atrLength)
stopDist  = atrVal * atrMult
stopTicks = stopDist / tickSize

// Budget validation
isWithinCap = stopTicks <= maxStopTicks
isValid     = inSession and isWithinCap

// Stop Loss Levels
longStop  = close - stopDist
shortStop = close + stopDist

// Position Sizing (Risk = Account * Risk% / Stop Distance in Base Currency)
riskAmount = accountSize * (riskPerTrade / 100)
unitSize   = riskAmount / (stopTicks * tickValue)

// =========================================================================
// 3. VISUALIZATION & UI
// =========================================================================
// State Colors
color validCol   = color.new(#00BFA5, 20) // Teal for valid setups
color breachCol  = color.new(#FF5252, 20) // Red for budget breaches
color sessionCol = color.new(#757575, 50) // Grey for out-of-session
color currentCol = not inSession ? sessionCol : (isWithinCap ? validCol : breachCol)

// Plotting Trailing Bands
p1 = plot(longStop, title="Long Invalidation", color=currentCol, style=plot.style_stepline, linewidth=2)
p2 = plot(shortStop, title="Short Invalidation", color=currentCol, style=plot.style_stepline, linewidth=2)

// Subtly fill the channel during valid conditions
fill(p1, p2, color=isValid ? color.new(validCol, 90) : na, title="Valid Trading Zone")

// Background warning for budget breaches during active sessions
bgcolor(inSession and not isWithinCap ? color.new(#FF5252, 95) : na, title="Risk Breach Warning")

// =========================================================================
// 4. TELEMETRY DASHBOARD (HUD)
// =========================================================================
if barstate.islast
    var table hud = table.new(position.bottom_right, 2, 5, border_width=1, border_color=color.new(color.gray, 50))
    
    // Header
    table.cell(hud, 0, 0, "RISK DESK", text_color=color.white, bgcolor=#1E1E1E, text_halign=text.align_left, text_size=size.small)
    table.cell(hud, 1, 0, syminfo.ticker, text_color=color.white, bgcolor=#1E1E1E, text_halign=text.align_right, text_size=size.small)
    
    // Session Status
    table.cell(hud, 0, 1, "Session:", text_color=color.white, bgcolor=#2D2D2D, text_halign=text.align_left, text_size=size.small)
    table.cell(hud, 1, 1, inSession ? "ACTIVE" : "CLOSED", text_color=inSession ? #00BFA5 : color.gray, bgcolor=#2D2D2D, text_halign=text.align_right, text_size=size.small)

    // ATR / Tick Distance
    table.cell(hud, 0, 2, "Stop Distance:", text_color=color.white, bgcolor=#2D2D2D, text_halign=text.align_left, text_size=size.small)
    table.cell(hud, 1, 2, str.tostring(math.round(stopTicks)) + " ticks", text_color=color.white, bgcolor=#2D2D2D, text_halign=text.align_right, text_size=size.small)

    // Position Size (Units)
    table.cell(hud, 0, 3, "Suggested Size:", text_color=color.white, bgcolor=#2D2D2D, text_halign=text.align_left, text_size=size.small)
    table.cell(hud, 1, 3, isValid ? str.tostring(unitSize, "#.##") + " Units" : "--", text_color=color.white, bgcolor=#2D2D2D, text_halign=text.align_right, text_size=size.small)

    // Final Verdict
    string statusText = not inSession ? "WAIT: OUT OF SESSION" : (isWithinCap ? "EXECUTE: IN BUDGET" : "REJECT: OVER CAP")
    color statusBg = not inSession ? #757575 : (isWithinCap ? #00BFA5 : #FF5252)
    
    table.cell(hud, 0, 4, "STATUS:", text_color=color.white, bgcolor=statusBg, text_halign=text.align_left, text_size=size.normal)
    table.cell(hud, 1, 4, statusText, text_color=color.white, bgcolor=statusBg, text_halign=text.align_right, text_size=size.normal)

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:16 am
by FTtrader
Porting this logic to MetaTrader requires shifting from Pine's reactive execution to MQL’s compiled, strict-type environment. Because MQL is C++ based, we handle state explicitly on every tick and manage our own UI object cleanup in OnDeinit to prevent memory leaks.

To give you the most accurate position sizing, these scripts dynamically query the broker for the exact TICK_VALUE and TICK_SIZE for the current symbol.

Here are the professional-grade implementations for both MQL5 and MQL4.

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:16 am
by FTtrader
1. MQL5 Implementation (Native Color Buffers)

MQL5 natively supports DRAW_COLOR_LINE, allowing us to use paired buffers (one for price data, one for color state) to cleanly swap the line color when the ATR stop breaches the budget.

Code: Select all

//+------------------------------------------------------------------+
//|                                              RiskDesk_Budget.mq5 |
//|                             Institutional ATR Risk Budget Filter |
//+------------------------------------------------------------------+
#property copyright "Custom Algorithmic Execution"
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   2

// Long Invalidation Line
#property indicator_label1  "Long Stop"
#property indicator_type1   DRAW_COLOR_LINE
#property indicator_color1  clrTeal, clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

// Short Invalidation Line
#property indicator_label2  "Short Stop"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_color2  clrTeal, clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//--- Inputs
input string   GroupName       = "--- Risk & Budget ---";
input double   InpRiskPercent  = 1.0;      // Risk Per Trade (%)
input int      InpMaxStopPts   = 150;      // Max Stop Cap (Points)
input string   GroupATR        = "--- Volatility ---";
input int      InpAtrPeriod    = 14;       // ATR Period
input double   InpAtrMult      = 1.5;      // ATR Multiplier
input string   GroupSession    = "--- Session ---";
input bool     InpUseSession   = true;     // Enforce Session Time
input int      InpStartHour    = 8;        // Session Start Hour (Broker Time)
input int      InpEndHour      = 12;       // Session End Hour (Broker Time)

//--- Buffers
double         LongBuffer[];
double         LongColorBuffer[];
double         ShortBuffer[];
double         ShortColorBuffer[];

//--- Global Variables
int            atrHandle;
double         atrArray[];
string         hudName = "RiskDeskHUD";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, LongBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, LongColorBuffer, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, ShortBuffer, INDICATOR_DATA);
   SetIndexBuffer(3, ShortColorBuffer, INDICATOR_COLOR_INDEX);
   
   ArraySetAsSeries(LongBuffer, true);
   ArraySetAsSeries(LongColorBuffer, true);
   ArraySetAsSeries(ShortBuffer, true);
   ArraySetAsSeries(ShortColorBuffer, true);
   ArraySetAsSeries(atrArray, true);
   
   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE) return(INIT_FAILED);
   
   CreateHUD();
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated,
                const datetime &time[], const double &open[],
                const double &high[], const double &low[],
                const double &close[], const long &tick_volume[],
                const long &volume[], const int &spread[])
  {
   if(rates_total < InpAtrPeriod) return(0);
   
   int copied = CopyBuffer(atrHandle, 0, 0, rates_total - prev_calculated + 1, atrArray);
   if(copied <= 0) return(0);
   
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(time, true);

   int limit = prev_calculated == 0 ? rates_total - InpAtrPeriod - 1 : rates_total - prev_calculated;

   for(int i = limit; i >= 0; i--)
     {
      double atrDist = atrArray[i] * InpAtrMult;
      double stopPts = atrDist / _Point;
      
      bool withinCap = (stopPts <= InpMaxStopPts);
      
      MqlDateTime dt;
      TimeToStruct(time[i], dt);
      bool inSession = (!InpUseSession) || (dt.hour >= InpStartHour && dt.hour < InpEndHour);
      
      bool isValid = withinCap && inSession;
      double colorState = isValid ? 0.0 : 1.0; // 0 = clrTeal, 1 = clrRed
      
      LongBuffer[i] = close[i] - atrDist;
      LongColorBuffer[i] = colorState;
      
      ShortBuffer[i] = close[i] + atrDist;
      ShortColorBuffer[i] = colorState;
      
      // Update HUD on current bar
      if(i == 0) UpdateHUD(stopPts, isValid, inSession, withinCap);
     }
     
   return(rates_total);
  }

//+------------------------------------------------------------------+
//| HUD Management                                                   |
//+------------------------------------------------------------------+
void CreateHUD()
  {
   ObjectCreate(0, hudName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, hudName, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
   ObjectSetInteger(0, hudName, OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0, hudName, OBJPROP_YDISTANCE, 40);
   ObjectSetString(0, hudName, OBJPROP_FONT, "Consolas");
   ObjectSetInteger(0, hudName, OBJPROP_FONTSIZE, 10);
  }

void UpdateHUD(double stopPts, bool isValid, bool inSession, bool withinCap)
  {
   double accountBal = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmt = accountBal * (InpRiskPercent / 100.0);
   
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double pointValue = tickValue * (_Point / tickSize); // Normalize point value
   
   double lotSize = 0;
   if(stopPts > 0 && pointValue > 0)
      lotSize = riskAmt / (stopPts * pointValue);
      
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   lotSize = MathFloor(lotSize / stepLot) * stepLot;

   string status = !inSession ? "OUT OF SESSION" : (withinCap ? "EXECUTE BUDGET" : "BUDGET EXCEEDED");
   color col = !inSession ? clrGray : (withinCap ? clrTeal : clrRed);
   
   string text = StringFormat("--- RISK DESK ---\nStop Dist: %.0f pts\nVol Sizing: %.2f Lots\nStatus: %s", 
                              stopPts, (isValid && lotSize >= minLot ? lotSize : 0), status);
                              
   ObjectSetString(0, hudName, OBJPROP_TEXT, text);
   ObjectSetInteger(0, hudName, OBJPROP_COLOR, col);
  }

void OnDeinit(const int reason)
  {
   ObjectDelete(0, hudName);
  }

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:17 am
by FTtrader
2. MQL4 Implementation (4-Buffer Workaround)

MQL4 lacks native color arrays for lines (DRAW_COLOR_LINE). To achieve the visual shift without repainting the entire line history to a single color, this script uses four standard DRAW_LINE buffers. It writes the price level to the "Valid" buffers when conditions are met, and to the "Invalid" buffers when the budget is breached, assigning EMPTY_VALUE to the inactive buffer.

Code: Select all

//+------------------------------------------------------------------+
//|                                              RiskDesk_Budget.mq4 |
//+------------------------------------------------------------------+
#property copyright "Custom Algorithmic Execution"
#property strict
#property indicator_chart_window
#property indicator_buffers 4

#property indicator_label1  "Long Valid"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrTeal
#property indicator_width1  2

#property indicator_label2  "Long Invalid"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_width2  2

#property indicator_label3  "Short Valid"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrTeal
#property indicator_width3  2

#property indicator_label4  "Short Invalid"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrRed
#property indicator_width4  2

//--- Inputs
extern double  InpRiskPercent  = 1.0;      // Risk Per Trade (%)
extern int     InpMaxStopPts   = 150;      // Max Stop Cap (Points)
extern int     InpAtrPeriod    = 14;       // ATR Period
extern double  InpAtrMult      = 1.5;      // ATR Multiplier
extern bool    InpUseSession   = true;     // Enforce Session Time
extern int     InpStartHour    = 8;        // Session Start Hour (Broker Time)
extern int     InpEndHour      = 12;       // Session End Hour (Broker Time)

//--- Buffers
double LongValid[];
double LongInvalid[];
double ShortValid[];
double ShortInvalid[];

string hudName = "RiskDeskHUD";

//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, LongValid);
   SetIndexBuffer(1, LongInvalid);
   SetIndexBuffer(2, ShortValid);
   SetIndexBuffer(3, ShortInvalid);
   
   for(int i=0; i<4; i++) SetIndexEmptyValue(i, EMPTY_VALUE);
   
   CreateHUD();
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   int limit = rates_total - prev_calculated;
   if(limit > rates_total - InpAtrPeriod - 1) 
      limit = rates_total - InpAtrPeriod - 1;

   for(int i = limit; i >= 0; i--)
     {
      double atr = iATR(NULL, 0, InpAtrPeriod, i);
      double atrDist = atr * InpAtrMult;
      double stopPts = atrDist / Point;
      
      bool withinCap = (stopPts <= InpMaxStopPts);
      
      int currentHour = TimeHour(time[i]);
      bool inSession = (!InpUseSession) || (currentHour >= InpStartHour && currentHour < InpEndHour);
      bool isValid = withinCap && inSession;
      
      // Route data to the correct buffer for coloring
      if(isValid)
        {
         LongValid[i] = close[i] - atrDist;
         LongInvalid[i] = EMPTY_VALUE;
         ShortValid[i] = close[i] + atrDist;
         ShortInvalid[i] = EMPTY_VALUE;
        }
      else
        {
         LongValid[i] = EMPTY_VALUE;
         LongInvalid[i] = close[i] - atrDist;
         ShortValid[i] = EMPTY_VALUE;
         ShortInvalid[i] = close[i] + atrDist;
        }
        
      if(i == 0) UpdateHUD(stopPts, isValid, inSession, withinCap);
     }
   return(rates_total);
  }

//+------------------------------------------------------------------+
void CreateHUD()
  {
   ObjectCreate(hudName, OBJ_LABEL, 0, 0, 0);
   ObjectSet(hudName, OBJPROP_CORNER, 3); // Lower Right
   ObjectSet(hudName, OBJPROP_XDISTANCE, 20);
   ObjectSet(hudName, OBJPROP_YDISTANCE, 40);
   ObjectSetString(0, hudName, OBJPROP_FONT, "Consolas");
   ObjectSet(hudName, OBJPROP_FONTSIZE, 10);
  }

void UpdateHUD(double stopPts, bool isValid, bool inSession, bool withinCap)
  {
   double accountBal = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskAmt = accountBal * (InpRiskPercent / 100.0);
   
   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize  = MarketInfo(Symbol(), MODE_TICKSIZE);
   double pointValue = tickValue * (Point / tickSize);
   
   double lotSize = 0;
   if(stopPts > 0 && pointValue > 0)
      lotSize = riskAmt / (stopPts * pointValue);
      
   double minLot = MarketInfo(Symbol(), MODE_MINLOT);
   double stepLot = MarketInfo(Symbol(), MODE_LOTSTEP);
   if(stepLot > 0)
       lotSize = MathFloor(lotSize / stepLot) * stepLot;

   string status = !inSession ? "OUT OF SESSION" : (withinCap ? "EXECUTE BUDGET" : "BUDGET EXCEEDED");
   color col = !inSession ? clrGray : (withinCap ? clrTeal : clrRed);
   
   string text = StringFormat("Risk Desk | Stop: %.0f pts | Size: %.2f Lots | %s", 
                              stopPts, (isValid && lotSize >= minLot ? lotSize : 0), status);
                              
   ObjectSetString(0, hudName, OBJPROP_TEXT, text);
   ObjectSet(hudName, OBJPROP_COLOR, col);
  }

void OnDeinit(const int reason)
  {
   ObjectDelete(hudName);
  }

Re: Free Tool: Dynamic ATR Stop Loss Calculator for MT4 & MT5 Scalpers

Posted: Sun Sep 20, 2026 9:18 am
by FTtrader
Because cTrader’s API (cAlgo) is built on C#, it allows for a highly structured, object-oriented approach to state management. Like MQL4, cTrader does not natively support multi-color output buffers for a single line, so this implementation routes the valid and invalid price levels to separate IndicatorDataSeries outputs, printing double.NaN for the inactive states to create a seamless visual shift.

This script also utilizes cTrader's native Symbol.NormalizeVolumeInUnits to perfectly round your calculated position size to the broker's required step limits for the specific asset.

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class InstitutionalRiskDesk : Indicator
    {
        // =========================================================================
        // 1. INPUTS & CONFIGURATION
        // =========================================================================
        [Parameter("Risk Per Trade (%)", Group = "Risk Management", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1)]
        public double RiskPercent { get; set; }

        [Parameter("Max Stop Cap (Pips)", Group = "Risk Management", DefaultValue = 15.0)]
        public double MaxStopPips { get; set; }

        [Parameter("ATR Period", Group = "Volatility Engine", DefaultValue = 14)]
        public int AtrPeriod { get; set; }

        [Parameter("ATR Multiplier", Group = "Volatility Engine", DefaultValue = 1.5)]
        public double AtrMult { get; set; }

        [Parameter("Enforce Session", Group = "Session Constraints", DefaultValue = true)]
        public bool UseSession { get; set; }

        [Parameter("Start Hour (UTC)", Group = "Session Constraints", DefaultValue = 8)]
        public int StartHour { get; set; }

        [Parameter("End Hour (UTC)", Group = "Session Constraints", DefaultValue = 12)]
        public int EndHour { get; set; }

        // =========================================================================
        // 2. OUTPUT BUFFERS
        // =========================================================================
        [Output("Long Valid", LineColor = "#00BFA5", Thickness = 2)]
        public IndicatorDataSeries LongValid { get; set; }

        [Output("Long Invalid", LineColor = "#FF5252", Thickness = 2)]
        public IndicatorDataSeries LongInvalid { get; set; }

        [Output("Short Valid", LineColor = "#00BFA5", Thickness = 2)]
        public IndicatorDataSeries ShortValid { get; set; }

        [Output("Short Invalid", LineColor = "#FF5252", Thickness = 2)]
        public IndicatorDataSeries ShortInvalid { get; set; }

        private AverageTrueRange _atr;

        // =========================================================================
        // 3. INITIALIZATION
        // =========================================================================
        protected override void Initialize()
        {
            // Initialize the built-in ATR indicator
            _atr = Indicators.AverageTrueRange(AtrPeriod, MovingAverageType.Simple);
        }

        // =========================================================================
        // 4. CORE CALCULATION LOOP
        // =========================================================================
        public override void Calculate(int index)
        {
            if (index < AtrPeriod) return;

            // Volatility measurements
            double atrValue = _atr.Result[index];
            double stopDist = atrValue * AtrMult;
            double stopPips = stopDist / Symbol.PipSize;

            // Budget validation
            bool withinCap = stopPips <= MaxStopPips;
            
            // Session handling
            DateTime barTime = Bars.OpenTimes[index];
            bool inSession = !UseSession || (barTime.Hour >= StartHour && barTime.Hour < EndHour);
            
            bool isValid = withinCap && inSession;
            double closePrice = Bars.ClosePrices[index];

            // State routing: Print to active buffer, send NaN to inactive buffer
            if (isValid)
            {
                LongValid[index] = closePrice - stopDist;
                LongInvalid[index] = double.NaN;
                ShortValid[index] = closePrice + stopDist;
                ShortInvalid[index] = double.NaN;
            }
            else
            {
                LongValid[index] = double.NaN;
                LongInvalid[index] = closePrice - stopDist;
                ShortValid[index] = double.NaN;
                ShortInvalid[index] = closePrice + stopDist;
            }

            // Update HUD on the live tick
            if (IsLastBar)
            {
                UpdateHUD(stopPips, isValid, inSession, withinCap);
            }
        }

        // =========================================================================
        // 5. TELEMETRY DASHBOARD (HUD)
        // =========================================================================
        private void UpdateHUD(double stopPips, bool isValid, bool inSession, bool withinCap)
        {
            double riskAmount = Account.Balance * (RiskPercent / 100.0);
            
            // Symbol.PipValue represents the value of 1 pip for 1 unit of volume
            double pipValuePerUnit = Symbol.PipValue;
            
            // Calculate raw exact volume based on risk and pip distance
            double exactVolume = 0;
            if (stopPips > 0 && pipValuePerUnit > 0)
            {
                exactVolume = riskAmount / (stopPips * pipValuePerUnit);
            }

            // Normalize volume to broker's required step limits and minimums
            double normalizedVolume = Symbol.NormalizeVolumeInUnits(exactVolume, RoundingMode.Down);
            if (normalizedVolume < Symbol.VolumeInUnitsMin || !isValid) 
            {
                normalizedVolume = 0;
            }

            // UI State strings and colors
            string status = !inSession ? "WAIT: OUT OF SESSION" : (withinCap ? "EXECUTE: IN BUDGET" : "REJECT: OVER CAP");
            Color hudColor = !inSession ? Color.Gray : (withinCap ? Color.FromHex("#00BFA5") : Color.FromHex("#FF5252"));

            string hudText = 
                $"--- RISK DESK ---\n" +
                $"Stop Dist  : {Math.Round(stopPips, 1)} pips\n" +
                $"Ideal Size : {normalizedVolume} Units\n" +
                $"Status     : {status}";

            // Draw HUD to the bottom right of the chart
            Chart.DrawStaticText("RiskDeskHUD", hudText, VerticalAlignment.Bottom, HorizontalAlignment.Right, hudColor);
        }
    }
}