Why Custom Enums Matter in MQL5
If you've written more than a few Expert Advisors, you've likely ended up with a mess of global integer constants or magic numbers to represent trade signals like BUY, SELL, or NEUTRAL. Maybe you used #define macros or raw integers like 1, -1, 0. That works, but it's fragile — one typo and your logic silently breaks.
Custom enums solve that. They give you a named set of values that the compiler checks for validity. You get autocomplete in MetaEditor, cleaner switch statements, and code that reads like plain English. For state machines — like tracking whether your EA is waiting for a signal, in a trade, or paused — enums are the right tool.
In this guide, I'll walk you through defining trade signal enums and state enums in MQL5, using them in real EA logic, and avoiding the common pitfalls I've seen beginners hit. By the end, you'll have a reusable pattern you can drop into any project.
Prerequisites
Before we dive in, make sure you have:
- MetaTrader 5 (build 2000 or later — any recent build works). MT4 uses a different event model; this guide is MQL5-specific.
- MetaEditor installed (comes with MT5). You'll write and compile code here.
- A demo account for testing. No real money needed.
- Basic familiarity with MQL5 syntax: functions, if/else, and switch statements. If you can write
OnTick()and attach an EA to a chart, you're ready.
Optionally, open a new Expert Advisor file in MetaEditor (File > New > Expert Advisor) to follow along. I'll use that file for all examples.
Step-by-Step: Defining and Using Custom Enums
Step 1: Declare Your Enum
In MQL5, you declare an enum using the enum keyword outside any function, typically at the top of your EA file or in a separate include file. Here's a simple trade signal enum:
//+------------------------------------------------------------------+
// Trade signal enumeration
//+------------------------------------------------------------------+
enum ENUM_TRADE_SIGNAL
{
SIGNAL_NEUTRAL = 0, // No clear signal
SIGNAL_BUY, // Buy signal (auto-increments to 1)
SIGNAL_SELL, // Sell signal (auto-increments to 2)
SIGNAL_EXIT // Close existing position (3)
};
Notice I explicitly set SIGNAL_NEUTRAL = 0. That's a good practice — it ensures the default value is safe. The rest auto-increment. You can also assign explicit values if you need specific numbers for logging or external systems.
For a state machine, you might define something like this:
enum ENUM_EA_STATE
{
STATE_INIT, // EA just started
STATE_WAITING, // Waiting for market conditions
STATE_ANALYZING, // Analyzing indicators
STATE_TRADING, // In a trade
STATE_PAUSED, // Manual pause or error recovery
STATE_ERROR // Fatal error — stop trading
};
State enums are less about signals and more about tracking what your EA is doing at any moment. They make debugging vastly easier because you can log the state name directly.
Step 2: Use Enums in Variables and Parameters
Once declared, you can use the enum as a variable type. In your EA's global scope or within functions:
// Global variables
ENUM_TRADE_SIGNAL currentSignal = SIGNAL_NEUTRAL;
ENUM_EA_STATE eaState = STATE_INIT;
You can also use enums as input parameters, which lets users select from a dropdown in the EA properties window. This is one of the most practical uses:
input ENUM_TRADE_SIGNAL InpManualSignal = SIGNAL_NEUTRAL; // Override signal manually
When you compile and attach the EA, MetaTrader automatically shows a dropdown with the enum names. No messy integer inputs.
Step 3: Switch on Enums for Clean Logic
The real power of enums shines in switch statements. The compiler warns you if you miss a case, which prevents silent bugs. Here's a typical signal handler:
//+------------------------------------------------------------------+
// Handle trade signal
//+------------------------------------------------------------------+
void HandleSignal(ENUM_TRADE_SIGNAL signal)
{
switch(signal)
{
case SIGNAL_NEUTRAL:
Print("No signal — staying flat");
break;
case SIGNAL_BUY:
if(OpenBuy())
Print("Buy order placed");
break;
case SIGNAL_SELL:
if(OpenSell())
Print("Sell order placed");
break;
case SIGNAL_EXIT:
CloseAllPositions();
Print("Exited all positions");
break;
default:
Print("Unknown signal: ", signal);
break;
}
}
For state management, the pattern is similar but you'd call it in OnTick() or a timer function:
//+------------------------------------------------------------------+
// State machine tick
//+------------------------------------------------------------------+
void UpdateStateMachine()
{
switch(eaState)
{
case STATE_INIT:
if(InitializeIndicators())
eaState = STATE_WAITING;
else
eaState = STATE_ERROR;
break;
case STATE_WAITING:
currentSignal = EvaluateMarket();
if(currentSignal != SIGNAL_NEUTRAL)
eaState = STATE_ANALYZING;
break;
case STATE_ANALYZING:
if(ConfirmSignal(currentSignal))
eaState = STATE_TRADING;
else
eaState = STATE_WAITING;
break;
case STATE_TRADING:
if(ShouldExit())
eaState = STATE_WAITING;
break;
case STATE_PAUSED:
// Do nothing until unpaused
break;
case STATE_ERROR:
Print("Fatal error — EA stopped");
ExpertRemove();
break;
}
}
Notice how each state transition is explicit. You can trace exactly what happened by checking logs — no guessing.
Step 4: Log Enum Names Instead of Numbers
A common frustration is that Print() outputs the numeric value of an enum, not its name. For debugging, that's useless. MQL5 provides EnumToString() for exactly this:
Print("Current signal: ", EnumToString(currentSignal));
Print("EA state: ", EnumToString(eaState));
This prints SIGNAL_BUY or STATE_WAITING — readable and searchable. Use it everywhere in your logs. I keep a debug function that dumps both signal and state on every tick during development.
Step 5: Combine Enums with Indicator Buffers (Advanced)
You can also use enums to index indicator buffers. For example, if you have multiple buffers for different signal types:
enum ENUM_BUFFER_INDEX
{
BUF_MAIN, // Main indicator line
BUF_SIGNAL, // Signal line
BUF_UPPER, // Upper band
BUF_LOWER, // Lower band
BUF_TOTAL // Count of buffers (not a real buffer)
};
// In OnInit():
SetIndexBuffer(BUF_MAIN, mainBuffer, INDICATOR_DATA);
SetIndexBuffer(BUF_SIGNAL, signalBuffer, INDICATOR_DATA);
// etc.
// Later:
double value = mainBuffer[BUF_SIGNAL]; // reads signal buffer at index 1
This makes your code self-documenting. Anyone reading it knows BUF_SIGNAL is the signal line, not a magic number 1.
Tips and Best Practices
- Always set the first enum value to 0 — either explicitly or by making it the first entry. Default initialization of global variables gives 0, so your EA starts in a safe state.
- Use
EnumToString()in all logs. I can't stress this enough. It turns cryptic numbers into readable names and saves hours of debugging. - Add a
_TOTALentry at the end of enums used for array sizes. It gives you a compile-time constant for loop bounds:
enum ENUM_MY_BUFFERS
{
BUF_FIRST,
BUF_SECOND,
BUF_THIRD,
BUF_TOTAL
};
double myBuffers[BUF_TOTAL][]; // array size = 3
- Keep enums in separate include files if you use them across multiple EAs or indicators. Create
TradeSignals.mqhandEAStates.mqh, then#includethem. This avoids duplication. - Don't mix signal enums with state enums. They serve different purposes. A signal tells you what to do; a state tells you where in the logic you are. Combining them leads to confusing code.
- Use
switchoverif-elsewhen handling enums. The compiler's exhaustiveness check catches missing cases. Withif-else, you can forget a value and never notice.
Common Mistakes and Troubleshooting
Over the years, I've seen — and made — these mistakes. Here's how to avoid them.
Mistake 1: Using Enums with #define Constants
You might be tempted to mix enums with #define macros for backward compatibility. Don't. They're incompatible types, and the compiler will throw warnings. Stick to one pattern. If you're migrating old code, replace all #define signal constants with enum values and recompile.
Mistake 2: Forgetting to Handle All Cases in switch
MQL5's compiler does warn about missing enum cases in switch statements, but only if you enable warnings. Go to Tools > Options > Expert Advisors and check Show warnings. Then watch the compiler output. If you see "not all enumeration values are handled", add the missing cases or a default branch.
Mistake 3: Assuming Enum Names Are Strings
Enums are integers under the hood. You cannot do string name = currentSignal — that gives you the numeric value. Always use EnumToString() for display. Similarly, you can't parse a string back to an enum easily; MQL5 lacks StringToEnum(). If you need that, write a manual mapping function.
Mistake 4: Overlapping Values Between Enums
If you define two enums with the same numeric value and use them interchangeably, the compiler won't stop you, but your logic will be wrong. For example, SIGNAL_BUY = 1 and STATE_WAITING = 1 look the same to switch. Keep signal and state enums distinct and never assign them to the same variable.
Mistake 5: Not Testing Enum Input Parameters
When you use an enum as an input, the dropdown shows the names, but the user can still select any value. If your enum has 4 entries, the dropdown shows 4 options — that's it. No risk of invalid numbers. But if you later add or remove entries, existing EA setups might break because saved .set files store the numeric index, not the name. Always version your enums carefully or provide migration logic.
Putting It All Together: A Minimal EA Skeleton
Here's a complete, minimal EA that uses both a signal enum and a state enum. You can compile this and attach it to a chart for testing.
//+------------------------------------------------------------------+
// EnumTestEA.mq5
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property version "1.00"
#property strict
//--- Enums
enum ENUM_TRADE_SIGNAL
{
SIGNAL_NEUTRAL = 0,
SIGNAL_BUY,
SIGNAL_SELL,
SIGNAL_EXIT
};
enum ENUM_EA_STATE
{
STATE_INIT,
STATE_WAITING,
STATE_TRADING,
STATE_ERROR
};
//--- Global variables
ENUM_TRADE_SIGNAL currentSignal = SIGNAL_NEUTRAL;
ENUM_EA_STATE eaState = STATE_INIT;
//+------------------------------------------------------------------+
// Expert initialization function
//+------------------------------------------------------------------+
int OnInit()
{
Print("EA started. State: ", EnumToString(eaState));
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
// Expert tick function
//+------------------------------------------------------------------+
void OnTick()
{
// State machine
switch(eaState)
{
case STATE_INIT:
eaState = STATE_WAITING;
break;
case STATE_WAITING:
// Simulate signal detection — replace with real logic
currentSignal = (MathRand() > 16383) ? SIGNAL_BUY : SIGNAL_SELL;
Print("Signal: ", EnumToString(currentSignal));
eaState = STATE_TRADING;
break;
case STATE_TRADING:
// In a real EA, manage position here
Print("Trading...");
eaState = STATE_WAITING;
break;
case STATE_ERROR:
Print("Error state — exiting");
ExpertRemove();
break;
}
}
//+------------------------------------------------------------------+
Compile it (F7), attach to any chart, and watch the Experts tab. You'll see the state and signal names printed each tick. That's the foundation for any serious EA.
FAQ
Q: Can I use enums in MQL4?
A: MQL4 also supports enums, but the syntax is identical. The main difference is that MQL4's switch doesn't warn about missing cases as reliably. Also, EnumToString() exists in MQL4 from build 600+. For state machines, MQL4 works fine, but I recommend MQL5 for new projects due to better debugging and multithreading support.
Q: How do I convert a string back to an enum value?
A: MQL5 doesn't have a built-in StringToEnum(). You need to write a manual mapping. For example: if(name == "SIGNAL_BUY") return SIGNAL_BUY;. Keep this in a separate function to avoid repetition. If you're reading from files or external inputs, consider using integer IDs instead of strings.
Q: Can I have an enum with more than 255 values?
A: Yes, MQL5 enums can hold up to 2^32 values (the underlying type is int). Practical limit is far below that — you'll run into code readability issues long before hitting 255. For signal and state enums, 10-20 values is typical.
Q: Why does my enum dropdown show numbers instead of names?
A: This happens if you declared the enum inside a function or block — it must be in global scope. Also, enum names with spaces or special characters may not display correctly in the input parameters window. Stick to alphanumeric and underscores.






