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. Open MetaEditor from MT5 (press F4), and pick the folder this program belongs in — Experts, Indicators, or Scripts.
2. Create a new file, paste this over it, then press F7 to compile. Fix anything the Errors tab reports.
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.