MQL5 Risk Management: Position Sizing Code Templates & Guide
This page provides educational MQL5 code for estimating position size from a requested entry and stop, rounding volume down, and blocking new entries after an initialization-baseline loss threshold. These are testable controls, not a guarantee of a fixed loss or broker portability.
Implementation details & mechanics
Core implementation pattern
The EA uses the current bid relative to the last completed MA_TF moving average as a simple demonstration signal, checked once per chart bar. OrderCalcProfit estimates the loss at the requested stop for a reference volume; the calculated size rounds down to the symbol step and skips a trade below minimum volume. Only one matching symbol-and-magic position is allowed.
Error handling & safety rules
Orders request absolute initial SL and TP prices. An account-equity decline from the balance captured at initialization latches an entry block; it neither closes existing positions nor resets at midnight. Slippage, gaps, costs, rejected requests and other positions can exceed the estimate. Restarting resets this in-memory guard; it is not a funded-account daily-loss implementation.
MQL5 Code Implementation
//+------------------------------------------------------------------+
//| RiskManagementEA.mq5 |
//| Tutorial: Illustrative position sizing and entry controls |
//| For educational purposes — test thoroughly before live use |
//+------------------------------------------------------------------+
#property copyright "Pineify Educational Series"
#property version "1.00"
#property strict
//--- Input parameters
input double RiskPercent = 1.0; // Risk per trade (% of account balance)
input double RewardRatio = 2.0; // Reward-to-risk ratio (R:R)
input int StopLossPips = 30; // Stop-loss distance in pips
input int MagicNumber = 202401; // Unique EA identifier
input double MaxBaselineLossPct = 3.0; // Entry block from initialization balance (%)
input int MA_Period = 50; // Moving average period for trend filter
input ENUM_TIMEFRAMES MA_TF = PERIOD_H1; // MA timeframe
//--- Global variables
int g_maHandle = INVALID_HANDLE;
double g_startBalance = 0.0;
bool g_halted = false;
datetime g_lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialisation |
//+------------------------------------------------------------------+
int OnInit()
{
if(RiskPercent<=0 || RiskPercent>100 || RewardRatio<=0 || StopLossPips<=0
|| MaxBaselineLossPct<=0 || MaxBaselineLossPct>=100 || MA_Period<=0 || MagicNumber<=0)
return INIT_PARAMETERS_INCORRECT;
if(AccountInfoDouble(ACCOUNT_BALANCE)<=0) return INIT_FAILED;
// Create MA handle for simple trend filter
g_maHandle = iMA(_Symbol, MA_TF, MA_Period, 0, MODE_EMA, PRICE_CLOSE);
if(g_maHandle == INVALID_HANDLE)
{
Print("ERROR: Failed to create MA indicator handle");
return INIT_FAILED;
}
// Record initialization balance; this is not a daily reset
g_startBalance = AccountInfoDouble(ACCOUNT_BALANCE);
Print("RiskManagementEA initialised on ", _Symbol,
" | Risk: ", RiskPercent, "% | SL: ", StopLossPips, " pips");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_maHandle != INVALID_HANDLE)
IndicatorRelease(g_maHandle);
Print("RiskManagementEA removed. Reason code: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Guard is evaluated on ticks and remains latched until reinitialization.
if(IsBaselineLossBreached()) g_halted=true;
if(g_halted) return;
//--- Only act on new bar to avoid re-entry noise
datetime currentBar = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBar == g_lastBarTime)
return;
if(currentBar<=0 || CountOpenPositions()>0) return;
//--- Read MA for trend direction
double maBuffer[];
ArraySetAsSeries(maBuffer, true);
if(CopyBuffer(g_maHandle, 0, 0, 3, maBuffer) < 3)
return;
g_lastBarTime = currentBar;
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double maValue = maBuffer[1]; // confirmed previous bar value
//--- Simple demo entry: long above MA, short below MA
if(price > maValue && CountOpenPositions() == 0)
OpenTrade(ORDER_TYPE_BUY);
else if(price < maValue && CountOpenPositions() == 0)
OpenTrade(ORDER_TYPE_SELL);
}
//+------------------------------------------------------------------+
//| Calculate lot size based on account risk percentage |
//+------------------------------------------------------------------+
double CalculateLotSize(ENUM_ORDER_TYPE side,double entry,double stop)
{
double riskAmount=AccountInfoDouble(ACCOUNT_BALANCE)*RiskPercent/100.0;
double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
double minimum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
double maximum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
if(step<=0 || minimum<=0 || maximum<=0 || riskAmount<=0) return 0;
double referenceProfit=0;
if(!OrderCalcProfit(side,_Symbol,minimum,entry,stop,referenceProfit) || referenceProfit>=0)
return 0;
double raw=riskAmount*minimum/MathAbs(referenceProfit);
double volume=NormalizeDouble(MathFloor(MathMin(raw,maximum)/step)*step,8);
return volume>=minimum?volume:0;
}
//+------------------------------------------------------------------+
//| Open a trade with risk-managed SL and TP |
//+------------------------------------------------------------------+
bool OpenTrade(ENUM_ORDER_TYPE orderType)
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double pipPoints = (_Digits==3 || _Digits==5)?10.0*point:point;
double slDistance = StopLossPips * pipPoints;
double tpDistance = slDistance * RewardRatio;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double entryPrice, sl, tp;
if(orderType == ORDER_TYPE_BUY)
{
entryPrice = ask;
sl = NormalizeDouble(entryPrice - slDistance, _Digits);
tp = NormalizeDouble(entryPrice + tpDistance, _Digits);
}
else
{
entryPrice = bid;
sl = NormalizeDouble(entryPrice + slDistance, _Digits);
tp = NormalizeDouble(entryPrice - tpDistance, _Digits);
}
double lots=CalculateLotSize(orderType,entryPrice,sl);
if(lots<=0) { Print("Cannot size within budget and volume bounds"); return false; }
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = lots;
request.type = orderType;
request.price = entryPrice;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.magic = MagicNumber;
request.comment = StringFormat("Risk %.1f%% SL%dpips", RiskPercent, StopLossPips);
request.type_filling = ORDER_FILLING_FOK;
bool sent = OrderSend(request, result);
if(!sent || result.retcode != TRADE_RETCODE_DONE)
{
Print("OrderSend failed. Retcode: ", result.retcode,
" | Lots: ", lots, " | SL: ", sl, " | TP: ", tp);
return false;
}
Print("Trade opened | ", EnumToString(orderType),
" | Lots: ", lots,
" | Entry: ", entryPrice,
" | SL: ", sl,
" | TP: ", tp,
" | Risk$: ", NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0, 2));
return true;
}
//+------------------------------------------------------------------+
//| Count open positions for this EA on this symbol |
//+------------------------------------------------------------------+
int CountOpenPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 &&
PositionGetString(POSITION_SYMBOL) == _Symbol &&
(int)PositionGetInteger(POSITION_MAGIC) == MagicNumber)
count++;
}
return count;
}
//+------------------------------------------------------------------+
//| Check the initialization-baseline entry block |
//+------------------------------------------------------------------+
bool IsBaselineLossBreached()
{
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
double lossPercent = ((g_startBalance - currentEquity) / g_startBalance) * 100.0;
return lossPercent >= MaxBaselineLossPct;
}
//+------------------------------------------------------------------+Copy this code into MetaEditor, save it in your MQL5 folder (Experts, Scripts, or Include), and compile with F7.
Generate custom MQL5 code with Pineify AI
Describe the trade execution, indicator calculations, risk management, or data processing logic you need. Pineify generates editable MQL5 source code. Review its logic, request a syntax check, and compile and test the final file in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Strategy orders are simulated; live automation needs a separate integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.