Risk calculation is not working as expected

 

Hello everyone and MetaQuotes Support,

I am writing to express my severe frustration and disappointment as an EA developer. I have been developing a professional trading panel for a long time, and the recent rapid-fire updates are making development unsustainable.

Before the January 30th update (which introduced ONNX improvements and the Blend2D engine), my EA's text rendering, positioning, and logic worked flawlessly.

  1. After the Jan 30th update, I faced multiple visual rendering issues which I had to fix one by one.

  2. Then, another update was pushed, which broke my fixes, forcing me to recode the GUI again.

  3. Today, Feb 5th, I received Build 5577, and now a critical mathematical logic issue has appeared out of nowhere.

With all due respect, when will you stop releasing these updates that only cause problems and finally stabilize the platform once and for all? It is extremely damaging for developers when the graphics engine rules and core function behaviors change weekly without notice.

Environment Details:

  • Software: Official MetaTrader 5 downloaded directly from MQL5.com (Not a broker-branded version).

  • Account: Demo Account.

  • Build: 5577.

The Current Issue (Build 5577): I am facing a critical bug regarding how the terminal reads values from OBJ_EDIT objects. The EA reads the string correctly, but the internal mathematical conversion ( StringToDouble ) seems to have changed its behavior regarding regional settings (Dot vs. Comma) silently.

Visually, the input says "$100.00" (Correct), but upon execution, the terminal interprets this as thousands (10000.0 or 1000.0), multiplying the risk by 10 or 100.

CRITICAL DIAGNOSTIC PERFORMED: I have already ruled out my OS configuration. I changed my Windows Regional Settings to explicitly use Dot (.) as the Decimal Separator and Comma (,) for Thousands. Even within this "native" US-style environment, Build 5577 STILL interprets "100.00" incorrectly during the execution logic. It seems the platform is ignoring the decimal point or treating it as a "thousands separator" regardless of the system locale or forced string replacements.

Here is the precise logic causing issues, illustrating the disconnect between the GUI and the internal calculation:

// Issue happens when reading from an OBJ_EDIT
// Visually the object displays: "100.00" (Correct)

string txtRisk = ObjectGetString(0, "Object_Name", OBJPROP_TEXT); 

// Standard cleaning function (Removes '$', '%', and spaces).
// We even tried forcing StringReplace(s, ",", ".") here.
double valRisk = StringToDoubleClean(txtRisk); 

// RESULT IN BUILD 5577: 
// If txtRisk is "100.00" -> valRisk returns 10000.0 or 1000.0
// The system seems to be ignoring the decimal point or treating it as a thousands separator
// even if we strictly force StringReplace(s, ",", ".") before conversion.

// This results in the trade executing with 10x or 100x the intended risk.

Is anyone else experiencing regressions with StringToDouble or OBJ_EDIT parsing in Build 5577? This is critical for live trading tools.

Regards.

 
Please provide the definition (body) of the StringToDoubleClean() function.
 

Here is the function body. As you can see, it is a very standard cleanup logic that removes currency symbols and spaces before calling the native StringToDouble() .

This exact code worked flawlessly in previous builds.

double StringToDoubleClean(string val)
{
   string clean = val;
   
   // Remove currency symbols and spaces
   StringReplace(clean, "$", "");
   StringReplace(clean, "%", "");
   StringReplace(clean, " ", "");
   
   // Convert to double (This is where Build 5577 fails with Regional Settings)
   return StringToDouble(clean);
}
 
Jonathan Ignacio Belmar Castillo #:

Here is the function body. As you can see, it is a very standard cleanup logic that removes currency symbols and spaces before calling the native StringToDouble() .

This exact code worked flawlessly in previous builds.

I can't reproduce your issue. In my tests, your function works correctly (build 5577).

void OnStart()
  {
   test("100");
   test("$100.00");
   test("$ 100 $");
  }

void test(string str)
  {
   PrintFormat("\"%s\" --> %.2f", str, StringToDoubleClean(str));
  }

double StringToDoubleClean(string val)
{
   string clean = val;
   
   // Remove currency symbols and spaces
   StringReplace(clean, "$", "");
   StringReplace(clean, "%", "");
   StringReplace(clean, " ", "");
   
   // Convert to double (This is where Build 5577 fails with Regional Settings)
   return StringToDouble(clean);
}
 

Thanks for the script and the assistance. I have identified the root cause of the issue.

It is NOT a code error. The problem stems from a mathematical inconsistency in the MetaQuotes-Demo server specifications for XAUUSD (Build 5577), which causes standard risk formulas to calculate 10x the intended Lot Size.

The Diagnosis: I analyzed the symbol specification on the Demo server:

The Math Error: Mathematically, for a 100oz Contract, a price movement of 0.01 should equal a profit/loss of $1.00 ( 100 * 0.01 ). However, the server reports a Tick Value of 0.1 (10 times lower than the physical value).

The Consequence: Because the server under-reports the Tick Value (0.1 instead of 1.0), the standard lot calculation formula (RiskMoney / (Distance * TickValue)) produces a result 10 times larger than necessary. When the trade is actually executed, the full Contract Size (100) applies, resulting in 10x the monetary risk.

I verified this by running the exact same EA on a proprietary broker feed (FTMO) where the Data Feed is consistent, and the risk calculation works perfectly.

 
Jonathan Ignacio Belmar Castillo #:
the standard lot calculation formula (RiskMoney / (Distance * TickValue)) produces a result 10 times larger than necessary.

Your formula looks strange. There's a good chance it's the problem. But I haven't checked.

Forum on trading, automated trading systems and testing trading strategies

How to properly calculate target price for a profit value`

Fernando Carreiro, 2024.12.13 13:53

For MQL5, just use the OrderCalcProfit()MQL5 Book: Trading automation / Creating Expert Advisors / Estimating the profit of a trading operation: OrderCalcProfit

For many symbols, the maths is usually as follows, but not always, so it is best to use OrderCalcProfit().

Forum on trading, automated trading systems and testing trading strategies

How to calculate take profit from currency

Fernando Carreiro, 2022.09.05 17:00

These are all the same equation written in different ways ...

[Volume]      = [Money Value] * [Tick Size] / ( [Tick Value] * [Stop Size] )
[Stop Size]   = [Money Value] * [Tick Size] / ( [Tick Value] * [Volume]    )
[Money Value] = [Volume]      * [Stop Size] * [Tick Value]   / [Tick Size]

[Volume] in lots
[Stop Size] in quote price change
[Money Value] in account currency

Forum on trading, automated trading systems and testing trading strategies

SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE) sometimes zero

Fernando Carreiro, 2022.08.23 17:41

You can! These are the steps I take. I supply the function with a lot size equal to the “Max Lot Size” allowed for the symbol in question, then calculate the ratio needed to achieve the fractional risk that I wish to apply, to get the correct volume for the order. I then align that with the “Lot Step” and finally check it against both the maximum and minimum allowed lots for the symbol.

The reason I use the “maximum” lots instead of just “1.0” lots as a reference value is because there is no guarantee that the value of 1.0 is within the minimum and maximum values allowed. Given that using 1.0, or the maximum, gives equivalent results anyway (by using the ratio method), I choose to use the “max lots” as the reference point which also offers the most precision for the calculation.

Something like this ...

// This code will not compile. It is only a example reference

if( OrderCalcProfit( eOrderType, _Symbol, dbLotsMax, dbPriceOpen, dbPriceStopLoss, dbProfit ) )
{
   dbOrderLots = fmin( fmax( round( dbRiskMax * dbLotsMax / ( -dbProfit * dbLotsStep ) )
               * dbLotsStep, dbLotsMin ), dbLotsMax ); 
      
   // the rest of the code ...
};

 
I changed the topic title. The original title was "Critical Regression in Build 5577: StringToDouble & OBJ_EDIT parsing ignores Regional Settings."
 
Jonathan Ignacio Belmar Castillo #:

Thanks for the script and the assistance. I have identified the root cause of the issue.

It is NOT a code error. The problem stems from a mathematical inconsistency in the MetaQuotes-Demo server specifications for XAUUSD (Build 5577), which causes standard risk formulas to calculate 10x the intended Lot Size.

The Diagnosis: I analyzed the symbol specification on the Demo server:

The Math Error: Mathematically, for a 100oz Contract, a price movement of 0.01 should equal a profit/loss of $1.00 ( 100 * 0.01 ). However, the server reports a Tick Value of 0.1 (10 times lower than the physical value).

The Consequence: Because the server under-reports the Tick Value (0.1 instead of 1.0), the standard lot calculation formula (RiskMoney / (Distance * TickValue)) produces a result 10 times larger than necessary. When the trade is actually executed, the full Contract Size (100) applies, resulting in 10x the monetary risk.

I verified this by running the exact same EA on a proprietary broker feed (FTMO) where the Data Feed is consistent, and the risk calculation works perfectly.

MetaQuotes-Demo server is NOT for trading. It's for technical test and beta testing.
 
This is the code I use
double calcLots(double entry, double sl)
  {
   double ticksize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);       // check for tick size
   double tickvalue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);     // check tick value

   double rangeSize = MathAbs(entry- sl);                                     // check for sl distance
   double riskPerLot = rangeSize /  ticksize * tickvalue;                     // calculate risk per lot
   double lots = RiskMoney / riskPerLot;                                      // calculate lots

   lots = NormalizeDouble(lots, 2);                                           // normalize lots

   return lots;                                                               // return lots

  }
 

Osmar Sandoval Espinosa #:
This is the code I use

lots = NormalizeDouble(lots, 2);                                           // normalize lots

I haven't analyzed your formula, but the normalization can definitely be improved.

The post below is 9 years old, but the information in it is relevant (and it is in the top 3 of Google search results).

Forum on trading, automated trading systems and testing trading strategies

How do you solve the problem of invalid volume?

Alain Verleyen, 2017.05.27 19:40

Your way to normalize volume is incorrect, even if it runs most of the time for you. The problem is not using NormalizeDouble() which is just a rounding function. The problem is you need to have a multiple of lot step  (or volume step).

The code provided by the OP is correct, he was just not knowing how to use it. (This code comes from a Metaquotes article).

Only and simple correct way to normalize volume is :

         double lotStep=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
         volume=MathFloor(volume/lotStep)*lotStep;
MathFloor() could be replaced by MathRound() or MathCeil(), or even NormalizeDouble()  . The principle is the same.
 

Building on what @Vladislav Boyko and @Alain Verleyen correctly pointed out, I'd like to add a few practical considerations, especially for those running EAs on prop firm since maybe this could be your case too with your EA.

1. SYMBOL_TRADE_TICK_VALUE can return 0

This is a silent killer. On weekends, during market rollover, or if the terminal hasn't received quotes yet, SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE) returns 0. If your formula divides by this, you get inf lots then you'll get rejected order or catastrophic position.

2. OrderCalcProfit() is the safest approach (as Vladislav mentioned)

It handles cross-currency conversions, non-standard contract sizes, and broker-specific tick value configurations internally:

double CalcLotSize(string symbol, ENUM_ORDER_TYPE orderType, double entry, double sl, double riskMoney)
{
   // --- Validate inputs
   if(entry == sl || riskMoney <= 0)
      return 0.0;

   // --- Use OrderCalcProfit for accurate cross-broker calculation
   double maxLots = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double profit  = 0.0;
   
   if(!OrderCalcProfit(orderType, symbol, maxLots, entry, sl, profit) || profit >= 0.0)
   {
      // Fallback: manual calculation
      double tickSize  = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
      double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
      
      if(tickSize == 0 || tickValue == 0)
         return 0.0;   // Cannot calculate safely — no quote data
         
      double riskPerLot = MathAbs(entry - sl) / tickSize * tickValue;
      if(riskPerLot == 0)
         return 0.0;
         
      double lots = riskMoney / riskPerLot;
      return NormalizeLots(symbol, lots);
   }
   
   // --- Calculate from OrderCalcProfit result
   double lossPerMaxLot = MathAbs(profit);
   double lots = riskMoney * maxLots / lossPerMaxLot;
   
   return NormalizeLots(symbol, lots);
}

double NormalizeLots(string symbol, double lots)
{
   double minLots  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   double maxLots  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   double lotStep  = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   
   if(lotStep == 0)
      return minLots;
   
   lots = MathFloor(lots / lotStep) * lotStep;   // Align to lot step
   lots = MathMax(lots, minLots);                 // Clamp minimum
   lots = MathMin(lots, maxLots);                 // Clamp maximum
   
   return NormalizeDouble(lots, (int)MathCeil(-MathLog10(lotStep)));
}

As OP discovered, the MetaQuotes-Demo server reports a Tick Value of 0.1 for XAUUSD while FTMO reports 1.0 for the same symbol. This is not a bug though, in fact different brokers/servers configure contract specifications differently.
If you're developing an EA that will run on multiple prop firm accounts (FTMO, MyForexFunds, The5ers, etc.), hardcoding tick values or assuming consistent specs across servers will eventually blow an account.

OrderCalcProfit() abstracts all of this away and uses the server's own calculation engine, making it the only truly portable solution.

Hope this helps!