Back to MetaTrader 5 MQL5 for CFD Traders
Part 2

Functions, Includes, and the Standard Library

Stop repeating yourself. Meet CTrade, the class that saves you fifty lines of boilerplate.

Intermediate9 min readBeginner → AdvancedLesson 08 / 17
Try it in MetaEditor
Reusable helpers plus your first CTrade call

Save as MQL5/Scripts/Lesson8Functions.mq5. It demonstrates volume normalisation and point-to-price conversion — the two helpers every EA in Part 3 and 4 will reuse.

//+------------------------------------------------------------------+
//| Lesson 8 - Reusable helpers and the standard library             |
//+------------------------------------------------------------------+
#property copyright "Strategist Academy"
#property version   "1.00"

#include <Trade/Trade.mqh>

//+------------------------------------------------------------------+
//| Round a volume to what the broker actually accepts               |
//+------------------------------------------------------------------+
double NormalizeVolumeFor(const string sym, double vol)
{
   double vMin  = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
   double vMax  = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
   double vStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);

   vol = MathMax(vMin, MathMin(vMax, vol));

   if(vStep > 0)
      vol = vMin + MathFloor((vol - vMin) / vStep + 0.0000001) * vStep;

   return NormalizeDouble(vol, 2);
}

//+------------------------------------------------------------------+
//| Convert a distance in points into an absolute price              |
//+------------------------------------------------------------------+
double PointsToPrice(const double points, const double fromPrice, const bool below)
{
   double offset = points * _Point;
   return below ? NormalizeDouble(fromPrice - offset, _Digits)
                : NormalizeDouble(fromPrice + offset, _Digits);
}

//+------------------------------------------------------------------+
//| Is there already a position opened by this magic number?         |
//+------------------------------------------------------------------+
bool HasPosition(const string sym, const ulong magic)
{
   if(!PositionSelect(sym))
      return false;
   return ((ulong)PositionGetInteger(POSITION_MAGIC) == magic);
}

void OnStart()
{
   string sym = _Symbol;

   Print("Volume normalisation on ", sym);
   Print("  0.137  -> ", DoubleToString(NormalizeVolumeFor(sym, 0.137), 2));
   Print("  1.005  -> ", DoubleToString(NormalizeVolumeFor(sym, 1.005), 2));
   Print("  999.0  -> ", DoubleToString(NormalizeVolumeFor(sym, 999.0), 2), "  (clamped to max)");

   double bid = SymbolInfoDouble(sym, SYMBOL_BID);
   double ask = SymbolInfoDouble(sym, SYMBOL_ASK);

   Print("Stop 200 points below bid: ", DoubleToString(PointsToPrice(200, bid, true),  _Digits));
   Print("Target 400 points above ask: ", DoubleToString(PointsToPrice(400, ask, false), _Digits));

   Print("Has position with magic 12345? ", HasPosition(sym, 12345) ? "yes" : "no");

   // CTrade is ready to use - Part 3 will actually send orders with it.
   CTrade trade;
   trade.SetExpertMagicNumber(12345);
   trade.SetDeviationInPoints(10);
   trade.SetTypeFillingBySymbol(sym);
   Print("CTrade configured. Filling mode selected by symbol: ",
         EnumToString((ENUM_ORDER_TYPE_FILLING)SymbolInfoInteger(sym, SYMBOL_FILLING_MODE)));
}
To run it
  1. 1. Open MetaEditor from MT5 (press F4), and pick the folder this program belongs in — Experts, Indicators, or Scripts.
  2. 2. Create a new file, paste this over it, then press F7 to compile. Fix anything the Errors tab reports.
  3. 3. Back in MT5, drag it onto a chart — or open the Strategy Tester if it is an Expert Advisor.

Why functions matter here

Volume normalisation, point-to-price conversion, and position lookup appear in every EA you will ever write. Write them once, correctly, and reuse them. When you find a bug in one, you fix it in one place.

Pass by value versus by reference

By default MQL5 copies arguments. Add & to pass by reference so the function can write back into the caller's variable — this is how you return several values at once, and it is what CopyBuffer does with your arrays. Mark read-only reference parameters const & so the compiler protects you from accidental writes.

#include and the Standard Library

#include <Trade/Trade.mqh> pulls in CTrade, a wrapper that handles the fiddly parts of OrderSend: filling mode selection, deviation, retcode logging, and symbol checks. Under the hood it still builds a MqlTradeRequest — you will do that by hand in Part 3 so you understand what it is doing — but for production code, use CTrade.

The three CTrade settings that prevent most rejections

  • SetExpertMagicNumber(n) — tags every order so your EA only manages its own positions.
  • SetDeviationInPoints(n) — slippage tolerance. Too tight and you get requotes; too loose and you accept bad fills.
  • SetTypeFillingBySymbol(sym) — asks the symbol which filling mode it supports instead of you guessing between FOK, IOC, and RETURN.

Volume normalisation is not optional

Brokers accept volume in fixed steps with a minimum and maximum. Send 0.137 lots when the step is 0.01 and the order is rejected. Always round down to the step and clamp to the min/max — the helper in this lesson does exactly that.

What you just did

Lesson 08 of 17 in MetaTrader 5 MQL5 for CFD Traders. When you have run the examples or read the section, tick it off and move to the next lesson.