When optimizing EAs with the MetaTrader 5 genetic optimizer, one critical inefficiency is rarely discussed: the optimizer wastes generations testing invalid parameter combinations. Every time the EA rejects parameters in OnInit (e.g., Fast MA period > Slow MA period), that pass becomes dead genetic material, reducing population diversity and optimization quality .
1. The Hidden Cost Of Parameter Rejection
Standard practice for handling invalid combinations is simple rejection:
```cpp
int OnInit() {
if(FastMAPeriod >= SlowMAPeriod)
return INIT_PARAMETERS_INCORRECT; // Pass wasted
return INIT_SUCCEEDED;
}
```
This creates a cascade of problems for genetic algorithms :
2. The Solution: OnTesterInit + ParameterSetRange
MQL5 provides two powerful functions for dynamic parameter control during optimization setup :
```cpp
// Get current parameter ranges
bool ParameterGetRange(string name, bool &enable,
long &value, long &start, long &step, long &stop);
bool ParameterGetRange(string name, bool &enable,
double &value, double &start, double &step, double &stop);
// Set new parameter ranges dynamically
bool ParameterSetRange(string name, bool enable,
long value, long start, long step, long stop);
bool ParameterSetRange(string name, bool enable,
double value, double start, double step, double stop);
```
Critical limitation: `ParameterSetRange` can ONLY be called from `OnTesterInit()`, before optimization begins .
3. Complete Dynamic Restructuring Implementation
Problem: Two parameters `FastMA` and `SlowMA` where `FastMA` must always be less than `SlowMA`. Traditional optimization would waste ~50% of passes.
Solution: Create a shadow parameter that represents valid combinations only:
```cpp
// Input parameters visible to optimizer
input int FastMA = 5; // Will be disabled in optimization
input int SlowMA = 20; // Will be disabled in optimization
// Shadow parameter - the optimizer sees only this
sinput int ValidComboIndex = 0; // Index into valid combinations
struct SValidPair {
int fast;
int slow;
};
// Pre-calculate all valid combinations
SValidPair GetValidPair(int index, int fastStart, int fastStop, int fastStep,
int slowStart, int slowStop, int slowStep) {
int count = 0;
for(int f = fastStart; f <= fastStop; f += fastStep) {
for(int s = slowStart; s <= slowStop; s += slowStep) {
if(f < s) {
if(count == index) {
SValidPair result = {f, s};
return result;
}
count++;
}
}
}
SValidPair empty = {-1, -1};
return empty;
}
int GetTotalValidCount(int fastStart, int fastStop, int fastStep,
int slowStart, int slowStop, int slowStep) {
int count = 0;
for(int f = fastStart; f <= fastStop; f += fastStep) {
for(int s = slowStart; s <= slowStop; s += slowStep) {
if(f < s) count++;
}
}
return count;
}
void OnTesterInit() {
// Read original parameter ranges from optimizer settings
bool fastEnabled, slowEnabled;
long fastValue, fastStart, fastStep, fastStop;
long slowValue, slowStart, slowStep, slowStop;
if(!ParameterGetRange("FastMA", fastEnabled, fastValue, fastStart, fastStep, fastStop) ||
!ParameterGetRange("SlowMA", slowEnabled, slowValue, slowStart, slowStep, slowStop)) {
Print("Failed to get parameter ranges");
return;
}
// Calculate total valid combinations count
int totalValid = GetTotalValidCount((int)fastStart, (int)fastStop, (int)fastStep,
(int)slowStart, (int)slowStop, (int)slowStep);
// Disable original parameters from optimization
ParameterSetRange("FastMA", false, fastValue, fastStart, fastStep, fastStop);
ParameterSetRange("SlowMA", false, slowValue, slowStart, slowStep, slowStop);
// Enable shadow parameter with range 0..totalValid-1
ParameterSetRange("ValidComboIndex", true, 0, 0, 1, totalValid - 1);
PrintFormat("Dynamic restructuring: %d valid combinations, original space would be %d passes",
totalValid, ((fastStop - fastStart)/fastStep + 1) * ((slowStop - slowStart)/slowStep + 1));
}
```
4. Reconstructing Parameters Inside OnInit
During actual backtest passes, reconstruct the real parameters from the index:
```cpp
// Global variables to store reconstructed values
int g_fastMA = 5;
int g_slowMA = 20;
int OnInit() {
// Retrieve the shadow parameter value
int comboIndex = ValidComboIndex;
// Need to know original ranges - either hardcode or pass via additional shadow params
int fastStart = 3, fastStop = 30, fastStep = 1;
int slowStart = 5, slowStop = 100, slowStep = 1;
SValidPair pair = GetValidPair(comboIndex, fastStart, fastStop, fastStep,
slowStart, slowStop, slowStep);
if(pair.fast == -1) {
Print("Invalid combo index: ", comboIndex);
return INIT_PARAMETERS_INCORRECT;
}
g_fastMA = pair.fast;
g_slowMA = pair.slow;
return INIT_SUCCEEDED;
}
```
5. Passing Original Ranges Via Shadow Parameters
A more robust approach: store original range information in additional shadow parameters :
```cpp
// Add these to the input section
sinput ulong FastShadow = 0; // Packed: start|(stop<<16)|(step<<24)
sinput ulong SlowShadow = 0; // Packed: start|(stop<<16)|(step<<24)
void OnTesterInit() {
// ... previous code ...
// Pack range information into 64-bit integers
ulong fastPacked = (ulong)fastStart | ((ulong)fastStop << 16) | ((ulong)fastStep << 32);
ulong slowPacked = (ulong)slowStart | ((ulong)slowStop << 16) | ((ulong)slowStep << 32);
// Set shadow parameters (disabled, fixed values)
ParameterSetRange("FastShadow", false, fastPacked, fastPacked, 1, fastPacked);
ParameterSetRange("SlowShadow", false, slowPacked, slowPacked, 1, slowPacked);
// ... rest of code ...
}
// In OnInit, unpack ranges
void UnpackRange(ulong packed, int &start, int &stop, int &step) {
start = (int)(packed & 0xFFFF);
stop = (int)((packed >> 16) & 0xFFFF);
step = (int)((packed >> 32) & 0xFFFF);
}
```
6. Performance Impact Quantification
| Optimization Method | Passes Tested | Valid Passes | GA Efficiency |
|---------------------|---------------|--------------|----------------|
| Standard (with rejection) | 1000 | ~500 | 50% |
| Dynamic Restructuring | 500 | 500 | 100% |
By eliminating invalid combinations entirely, the genetic algorithm explores only viable parameter space, achieving equivalent or better results in half the time .
7. Extension To Multi-Parameter Constraints
The same technique handles complex constraints:
```cpp
// Example: Three parameters where A + B < C
int GetValidCount(int aStart, int aStop, int aStep,
int bStart, int bStop, int bStep,
int cStart, int cStop, int cStep) {
int count = 0;
for(int a = aStart; a <= aStop; a += aStep)
for(int b = bStart; b <= bStop; b += bStep)
for(int c = cStart; c <= cStop; c += cStep)
if(a + b < c) count++;
return count;
}
```
Reference: MQL5 Documentation, "ParameterGetRange and ParameterSetRange" (mql5.com/docs); MQL5 Programming Guide, Chapter 6 "Tester Automation" .