MQL4: How to Control the Number of Digits in Your Forex Indicators

Developing robust and accurate custom indicators in MetaTrader 4 (MQL4) requires meticulous attention to detail, especially concerning price representation and precision. One fundamental aspect that often trips up less experienced developers is properly handling the varying number of decimal places (digits) used by different currency pairs and brokers. This article delves into how to understand, determine, and control digits in your MQL4 indicators to ensure clarity, accuracy, and adaptability.

Introduction to Digits and Point in Forex

Understanding the structure of forex quotes is crucial for any MQL programmer. Prices are quoted with a certain number of decimal places, and the smallest unit of price movement is known as a ‘Point’ or ‘Pipette’.

Understanding ‘Point’ in Forex Trading

The ‘Point’ in MQL refers to the minimum possible price change for a trading instrument. For most major currency pairs like EURUSD, GBPUSD, and AUDUSD quoted with five decimal places (e.g., 1.12345), a ‘Point’ is 0.00001. For pairs involving the Japanese Yen (JPY), typically quoted with three decimal places (e.g., USDJPY 109.876), a ‘Point’ is 0.001. The Point variable in MQL provides this value for the current symbol.

The Significance of Digits in Price Representation

‘Digits’ represents the total number of decimal places displayed for a currency pair’s price. This is a critical piece of information because it directly influences how price changes are measured and how indicator values, which are often derived from price, should be displayed or used in calculations. Forex pairs historically used 4 digits (2 for JPY pairs), but now 5 digits (3 for JPY pairs) is standard with most brokers. Your indicator logic must be adaptable to either scenario.

Impact of Broker’s Digit Precision on Indicators

Different brokers, while mostly standardizing to 5-digit pricing, can sometimes still use 4-digit quotes or have specific instruments with unique precision requirements. An indicator designed assuming 5 digits will display incorrectly or calculate erroneously if run on a chart with 4-digit quotes without proper handling. For example, a calculated value of 0.00025 might need to be rounded to 0.0003 for a 4-digit display, but left as is for a 5-digit display. Using the correct number of digits ensures your indicator outputs meaningful values aligned with the chart’s price scale and avoids misinterpretation.

Methods to Determine the Number of Digits

MQL4 provides straightforward ways to determine the number of digits for the current symbol.

Using the ‘Digits’ Variable in MQL4

The simplest and most common method in MQL4 is using the built-in Digits variable. This integer variable automatically holds the number of decimal places for the financial instrument associated with the chart where the indicator is running. You can access it directly within your indicator code.

int currentDigits = Digits;

This value will be 4 or 5 for most non-JPY pairs, and 2 or 3 for JPY pairs, depending on the broker’s feed.

Understanding NormalizeDouble() Function

While Digits tells you how many decimal places are displayed, the NormalizeDouble() function is used to round a floating-point number (like a double variable) to a specified number of decimal places. This is crucial for calculations and ensuring that subsequent operations or displays use values with the correct precision, mitigating potential floating-point arithmetic errors.

double NormalizeDouble(double value, int digits);

The value is the number to round, and digits is the number of decimal places to round to. Often, you’ll use the built-in Digits variable as the digits parameter here.

Practical Examples: Identifying Digits Dynamically

Here’s a basic example showing how to retrieve and display the number of digits and the point value for the current symbol using the built-in variables:

void OnInit()
{
   // Print the number of digits and the point value
   Print("Symbol: ", Symbol(), " Digits: ", Digits, " Point: ", Point);

   // Example calculation: How many points is 1 pip?
   // A standard pip is 0.0001 for 4/5 digit pairs.
   // For a 5-digit pair, 1 pip = 10 points (0.00010 / 0.00001)
   // For a 4-digit pair, 1 pip = 1 point (0.0001 / 0.0001)
   // The Point variable already accounts for Digits.

   double pipValue = 0.0001; // Standard definition of 1 pip
   double pointsPerPip = NormalizeDouble(pipValue / Point, 1); // Use NormalizeDouble for robustness

   Print("1 standard pip is equal to ", pointsPerPip, " points.");
}

This demonstrates how Digits and Point dynamically provide symbol information needed for calculations.

Controlling Digits in Indicator Calculations and Output

Properly controlling the number of digits is essential for both the accuracy of calculations and the clarity of the indicator’s visual output.

Rounding Indicator Values with NormalizeDouble()

When performing calculations that result in double values, especially those based on price differences or ratios, it’s often necessary to round the result to the appropriate number of decimal places using NormalizeDouble(). This prevents tiny floating-point inaccuracies from accumulating or causing unexpected behavior in comparisons or subsequent calculations.

For instance, calculating the difference between two prices:

double priceDiff = Ask - Bid;
double spread = NormalizeDouble(priceDiff, Digits);

Or calculating a level that should align with price ticks:

double entryPrice = Ask + 50 * Point;
double normalizedEntryPrice = NormalizeDouble(entryPrice, Digits);

Always normalize critical price-related calculations to Digits or potentially Digits + 1 if you need Pipette precision.

Formatting Output for Clarity: IntegerToString(), DoubleToString()

While NormalizeDouble() is for numeric rounding, DoubleToString() is specifically for converting a double value into a string with a specified number of decimal places for display purposes (e.g., on the chart using Comment(), Object Labels, or in the indicator’s data window).

string DoubleToString(double value, int digits);

The digits parameter here controls the number of decimal places in the resulting string. You would typically use the Digits variable here to match the chart’s price precision.

Similarly, IntegerToString() converts an integer to a string.

Displaying Custom Messages with Correct Precision

When using Comment() or creating text objects to display indicator values or messages on the chart, use DoubleToString() to format any double values with the correct precision based on Digits.

void 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 < 1) return(0);

   double latestClose = close[rates_total - 1];
   double roundedClose = NormalizeDouble(latestClose, Digits); // Normalize for potential calculations

   // Format for display using DoubleToString
   string displayClose = DoubleToString(latestClose, Digits);
   string displaySpread = DoubleToString(Ask - Bid, Digits);

   Comment("Current Price: ", displayClose, "\n",
           "Spread: ", displaySpread, "\n",
           "Chart Digits: ", IntegerToString(Digits));

   return(rates_total);
}

This ensures that the displayed price and spread values align with the precision users expect based on the chart they are viewing.

Handling Different Currency Pairs and Brokers

The beauty of using the built-in Digits and Point variables is that your indicator becomes inherently adaptable to different instruments and broker feeds, provided you use these variables correctly.

Adapting Indicators to Various Digit Formats

By consistently using Digits and Point in calculations (often via NormalizeDouble) and using Digits with DoubleToString for output, the same indicator code can function correctly on EURUSD (5-digit), USDJPY (3-digit), or even exotic pairs with different precision, regardless of whether the broker uses 4 or 5 digit quotes for major pairs.

Considerations for Cross-Platform Compatibility

While this article focuses on MQL4, the concepts and the built-in Digits and Point variables exist in MQL5 as well. In MQL5, you might also explicitly fetch symbol information using SymbolInfoInteger(Symbol(), SYMBOL_DIGITS) or SymbolInfoDouble(Symbol(), SYMBOL_POINT), but the Digits and Point variables are still available and behave similarly. Code using Digits, Point, NormalizeDouble, and DoubleToString for precision control is highly portable between MQL4 and MQL5 in this regard.

Best Practices for Maintaining Accuracy

  • Always use Point to calculate price movements (e.g., stop loss distance in points, not absolute price values). A 50-point stop loss is 50 * Point, which correctly calculates to 0.00500 on a 5-digit pair and 0.0050 on a 4-digit pair.
  • Use NormalizeDouble() for any calculated price level or value that needs to align with tick precision before using it in comparisons or order operations.
  • Use DoubleToString(..., Digits) for formatting all price-related output displayed on the chart or in messages.
  • Avoid hardcoding price precision (e.g., dividing by 0.00001 or multiplying by 100000). Always use Point and Digits.

Advanced Techniques and Considerations

Beyond the basics, understanding the underlying symbol properties and optimizing code are part of professional MQL development.

Using ENUMSYMBOLINFO_INTEGER to fetch digits

In MQL5, you have explicit functions like SymbolInfoInteger() to retrieve various symbol properties. SymbolInfoInteger(Symbol(), SYMBOL_DIGITS) is the MQL5 equivalent of the MQL4 Digits variable. Similarly, SymbolInfoDouble(Symbol(), SYMBOL_POINT) is the equivalent of the Point variable. While Digits and Point variables exist for convenience in both languages, using the SymbolInfo* functions in MQL5 is often preferred in complex programs or when needing info about other symbols, not just the current one.

int currentDigits = SymbolInfoInteger(Symbol(), SYMBOL_DIGITS);
double currentPoint = SymbolInfoDouble(Symbol(), SYMBOL_POINT);

In MQL4, you are generally limited to the built-in Digits and Point variables for the current symbol.

Optimizing Indicator Performance with Digit Control

While digit control itself isn’t typically a major performance bottleneck, incorrect handling can lead to inefficient code. For example, repeatedly performing calculations without normalization might lead to tiny errors that require complex workarounds later. Consistently applying NormalizeDouble() where necessary simplifies logic and can prevent harder-to-debug issues. Ensuring that display formatting with DoubleToString() is only done when necessary (e.g., not on every single tick in OnCalculate unless required for visual updates) is also good practice.

Troubleshooting Common Issues Related to Digit Precision

Common problems related to digit precision include:

  • Incorrectly calculated Stop Loss/Take Profit levels: Often caused by hardcoding pip values instead of using Point.
  • Indicator values not aligning with price ticks: Usually due to missing NormalizeDouble() calls on calculated levels.
  • Displayed values having too many or too few decimal places: Incorrect usage of the digits parameter in DoubleToString() or not using DoubleToString() at all.
  • Comparison errors: Floating-point inaccuracies when comparing doubles that should be equal but differ by a tiny amount. Always compare doubles with a tolerance or ensure they are normalized to the correct precision using NormalizeDouble() before comparison if exact equality is expected after calculation based on price.

Mastering the use of Digits, Point, NormalizeDouble(), and DoubleToString() is fundamental for any MQL programmer aiming to write reliable and adaptable custom indicators. By correctly implementing digit control, you ensure your indicator provides accurate calculations and clear information, regardless of the currency pair or broker feed.


Leave a Reply