Why Most Ichimoku EAs Fail — and How This One Doesn't
I've lost count of the Ichimoku EAs I've pulled apart over the years. Most of them are single-timeframe garbage that fire trades the second price touches the cloud, then get stopped out on the next candle. They ignore the entire philosophy behind Ichimoku — which is about confirmation across timeframes, not just a single cross.
This article is for traders who already understand what the Ichimoku Cloud shows but want to automate a specific, repeatable setup: the Kijun-sen cross with Chikou Span confirmation, filtered by a higher timeframe cloud, and managed with an adaptive trailing stop based on Tenkan-sen slope. I'll walk you through the MQL4 code, the logic behind each decision, and the real-world pitfalls you'll hit in backtesting.
You're not getting a copy-paste magic EA. You're getting a blueprint you can adapt, test, and break — then fix. I've been building EAs for seven years, and this is one of the few strategies that survived my own brutal forward testing gauntlet.
What This Strategy Actually Does
The core idea is simple: the Kijun-sen (red line, period 26) represents the market's equilibrium. When price breaks decisively above it on the daily chart, and the Chikou Span (lagging line) is above price from 26 bars ago, you have a high-probability trend signal. But we add two filters that most retail EAs skip:
- Higher timeframe cloud filter: The H4 cloud must be bullish (Senkou Span A above Senkou Span B) for long entries. This prevents buying into counter-trend moves on the daily.
- Tenkan-sen slope trailing stop: Instead of a fixed ATR or pip stop, we trail using the slope of the Tenkan-sen (blue line, period 9). When the slope turns negative, we exit. This adapts to the trend's momentum naturally.
I trade this on EURUSD and GBPJPY primarily. The daily chart for entry signal, H4 for cloud confirmation, and M30 for the Tenkan-sen slope calculation. You could scale it — weekly/H4 for swing trading, or H4/M15 for faster moves — but the ratios matter more than the absolute timeframes. The key is keeping the higher timeframe at least 4x the entry timeframe. Daily to H4 works; H1 to M15 also works, but don't go closer than that or you'll lose the filter's value.
Building the EA: Core Logic in MQL4
Let's get into the code. I'm assuming you know how to create a basic EA skeleton in MetaEditor. If not, start with the default template and replace the OnTick() logic. Open MetaEditor from the MT4 Tools menu, select "New" then "Expert Advisor," and you'll get the template. Delete everything between the opening and closing braces of OnTick() — that's where our code goes.
Step 1: Multi-Timeframe Ichimoku Handles
You need separate handles for each timeframe. MQL4's iIchimoku() returns the indicator values for the current chart timeframe. To fetch data from another timeframe, you use the same function but specify the higher timeframe as the first parameter. This is different from MQL5 where you'd need indicator handles — MQL4 makes it simpler but also slower if you call it every tick.
Here's the input section and the helper function I use:
//+------------------------------------------------------------------+
//| Input parameters |
//+------------------------------------------------------------------+
input int TenkanPeriod = 9;
input int KijunPeriod = 26;
input int SenkouSpanB = 52;
input ENUM_TIMEFRAMES HigherTF = PERIOD_H4; // Cloud filter timeframe
input ENUM_TIMEFRAMES SlopeTF = PERIOD_M30; // Tenkan-sen slope timeframe
input double BufferPips = 10.0; // Buffer for trailing stop
input int MagicNumber = 202401; // Unique EA identifier
//+------------------------------------------------------------------+
//| Get Ichimoku values from multiple timeframes |
//+------------------------------------------------------------------+
double GetIchimokuLine(string symbol, ENUM_TIMEFRAMES tf, int mode, int shift) {
return iIchimoku(symbol, tf, TenkanPeriod, KijunPeriod, SenkouSpanB, mode, shift);
}
// Usage examples:
double dailyKijun = GetIchimokuLine(Symbol(), PERIOD_D1, MODE_KIJUNSEN, 1);
double h4CloudA = GetIchimokuLine(Symbol(), HigherTF, MODE_SENKOUSPANA, 1);
double h4CloudB = GetIchimokuLine(Symbol(), HigherTF, MODE_SENKOUSPANB, 1);
double m30Tenkan = GetIchimokuLine(Symbol(), SlopeTF, MODE_TENKANSEN, 1);
double m30TenkanPrev = GetIchimokuLine(Symbol(), SlopeTF, MODE_TENKANSEN, 2);Critical detail: The shift parameter is 1 for the last completed candle, not 0. Using shift 0 includes the current incomplete candle, which repaints and will destroy your backtest reliability. I learned this the hard way after three weeks of "amazing" backtest results that evaporated in forward testing. The repainting issue is especially nasty with Ichimoku because the Tenkan-sen and Kijun-sen recalculate on every tick within the current candle. If you use shift 0, your EA will see crosses that don't exist on the completed chart.
One more thing: MQL4's iIchimoku() recalculates the indicator each time you call it. For performance, you might want to cache values at the start of each new bar. I'll show that in the OnTick() structure later.
Step 2: Entry Conditions — The Kijun-Sen Cross
We're looking for price to close above the Kijun-sen, not just touch it. And we want the Chikou Span to confirm — meaning the Chikou Span (lagging line shifted 26 bars back) should be above the price that existed 26 bars ago.
Here's the entry check function. Notice I'm using strict comparisons to avoid floating-point equality issues:
//+------------------------------------------------------------------+
//| Check for long entry signal |
//+------------------------------------------------------------------+
bool IsLongEntry() {
double price = Close[1];
double kijun = GetIchimokuLine(Symbol(), PERIOD_D1, MODE_KIJUNSEN, 1);
double kijunPrev = GetIchimokuLine(Symbol(), PERIOD_D1, MODE_KIJUNSEN, 2);
double pricePrev = Close[2];
// Price crossed above Kijun-sen on last completed candle
bool kijunCross = (pricePrev <= kijunPrev && price > kijun);
if(!kijunCross) return false;
// Chikou Span confirmation: Chikou (lagging 26 bars) above price 26 bars ago
double chikou = GetIchimokuLine(Symbol(), PERIOD_D1, MODE_CHIKOUSPAN, 1);
double price26 = iClose(Symbol(), PERIOD_D1, 26);
if(chikou <= price26) return false;
// Higher timeframe cloud filter
double cloudA = GetIchimokuLine(Symbol(), HigherTF, MODE_SENKOUSPANA, 1);
double cloudB = GetIchimokuLine(Symbol(), HigherTF, MODE_SENKOUSPANB, 1);
if(cloudA <= cloudB) return false; // Bearish cloud, skip
return true;
}Notice I'm checking the cross happened on bar 1 (last completed) vs bar 2. This avoids repainting from the current candle. The Chikou Span check uses shift=1 for the current Chikou value, but compares it to iClose(Symbol(), PERIOD_D1, 26) — the price exactly 26 bars ago. This is the correct interpretation: Chikou Span shows today's close plotted 26 bars back, so we compare it to where price was at that time.
A common mistake I see in other EAs: they compare Chikou Span to the current price. That's wrong. The Chikou Span is a lagging line — it's today's close moved backward 26 periods. You must compare it to the price that existed 26 periods ago. If you compare it to current price, you'll get false signals during strong trends because the lagging line will always be behind.
Step 3: Adaptive Trailing Stop Using Tenkan-Sen Slope
Here's where most EAs get rigid. They use a fixed pip trail or an ATR multiple. The Tenkan-sen slope method is more organic: you trail the stop below the most recent Tenkan-sen value, but only move it when the slope is positive (uptrend intact). When the slope flattens or turns negative, you exit.
Think of the Tenkan-sen as the market's short-term pulse. In a strong uptrend, it angles upward. When momentum weakens, the slope flattens. When it turns negative, the trend is likely reversing. By trailing our stop using this slope, we let profits run during strong moves and exit quickly when momentum fades.
//+------------------------------------------------------------------+
//| Calculate trailing stop level based on Tenkan-sen slope |
//+------------------------------------------------------------------+
double GetTrailingStop() {
double tenkan = GetIchimokuLine(Symbol(), SlopeTF, MODE_TENKANSEN, 1);
double tenkanPrev = GetIchimokuLine(Symbol(), SlopeTF, MODE_TENKANSEN, 2);
double slope = tenkan - tenkanPrev;
static double lastValidStop = 0;
// Only update stop when slope is positive (trend alive)
if(slope > 0) {
// Trail stop at Tenkan-sen minus buffer
double buffer = BufferPips * Point * 10; // Adjust for 5-digit brokers
double newStop = tenkan - buffer;
if(newStop > lastValidStop) {
lastValidStop = newStop;
}
}
// Signal exit when slope turns negative — handled in exit logic
return lastValidStop;
}The BufferPips * Point * 10 calculation accounts for 5-digit brokers where 1 pip = 10 points. For 4-digit brokers, you'd use BufferPips * Point. I added an input parameter for this so you can adjust per pair. For GBPJPY, I use 15 pips buffer; for EURUSD, 8 pips works better. The buffer prevents the stop from being triggered by normal Tenkan-sen wiggles.
One edge case: what if the Tenkan-sen slope is positive but the price gaps below it overnight? The GetTrailingStop() function returns the last valid stop, which should be below the gap. But if the gap is huge, your stop might be far above the open. In that case, the exit logic in OnTick() will close the position immediately on the first tick. That's acceptable — gaps are unpredictable and you don't want to hold through them.
Step 4: Complete OnTick() Structure
Here's the skeleton that ties it together. I'm including position management and the IsNewBar() function. This pattern ensures we only check for signals once per bar, which is critical for backtest consistency:
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
if(!IsNewBar()) return; // Only check on new candle
// Check for exit first
if(PositionExists()) {
double trailStop = GetTrailingStop();
if(trailStop > 0 && Bid <= trailStop) {
ClosePosition();
return;
}
// Also exit if Tenkan-sen slope turns negative
double tenkan = GetIchimokuLine(Symbol(), SlopeTF, MODE_TENKANSEN, 1);
double tenkanPrev = GetIchimokuLine(Symbol(), SlopeTF, MODE_TENKANSEN, 2);
if(tenkan < tenkanPrev) {
ClosePosition();
return;
}
}
// Check for entry
if(!PositionExists() && IsLongEntry()) {
double lotSize = CalculateLotSize(); // Your MM logic here
int ticket = OrderSend(Symbol(), OP_BUY, lotSize, Ask, 3, 0, 0, "KijunCross", MagicNumber, 0, Green);
if(ticket > 0) {
// Set initial stop at Kijun minus buffer
double initialStop = GetIchimokuLine(Symbol(), PERIOD_D1, MODE_KIJUNSEN, 1) - BufferPips * Point * 10;
OrderModify(ticket, 0, initialStop, 0, 0, clrNONE);
}
}
}
//+------------------------------------------------------------------+
//| Check if a new candle has formed |
//+------------------------------------------------------------------+
bool IsNewBar() {
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(Symbol(), PERIOD_D1, 0);
if(currentBarTime != lastBarTime) {
lastBarTime = currentBarTime;
return true;
}
return false;
}I use a IsNewBar() function that checks the daily chart's bar time. This prevents multiple signals per bar in volatile markets. Some developers use the current timeframe's bar time, but I prefer the daily because our entry signal is daily-based. If you're using H4 entries, change PERIOD_D1 to PERIOD_H4.
The PositionExists() and ClosePosition() functions are standard MQL4 order management. I won't reproduce them here — they're well-documented in the MQL4 reference. But make sure you check for open orders by magic number, not just symbol, otherwise the EA might close manual trades.
Parameter Optimization — What Actually Matters
I ran a genetic optimization over 3 years of EURUSD M5 data (2019-2021) on this EA. The results surprised me. Most people assume the Tenkan and Kijun periods are the most important, but the data showed otherwise. Here's what I found:
| Parameter | Range Tested | Optimal Value | Impact on Profit Factor |
|---|---|---|---|
| Higher TF Cloud Filter | H1, H4, D1 | H4 |






