- 1 1. What Is iBands in MQL5?
- 2 2. iBands Syntax and Parameters
- 3 3. How to Get Bollinger Bands Values with CopyBuffer
- 4 4. Implementation Examples Using iBands in an EA
- 5 5. Notes and Common Real-World Errors When Using iBands
- 6 6. Common Mistakes and Stumbling Blocks
- 7 7. FAQ
- 7.1 7.1 What does iBands return?
- 7.2 7.2 How are iBands buffer numbers mapped?
- 7.3 7.3 Where should iBands be created?
- 7.4 7.4 What does start_pos in CopyBuffer mean?
- 7.5 7.5 Why does CopyBuffer fail?
- 7.6 7.6 Does price always reverse after touching a Bollinger Band?
- 7.7 7.7 How is band width calculated?
- 7.8 7.8 What is the difference between iBands and iCustom?
1. What Is iBands in MQL5?
iBands is the standard MQL5 function used to access Bollinger Bands.
In MetaTrader 5 (MT5), when an EA (Expert Advisor) or script needs values from a technical indicator, it first creates an indicator handle, which is an ID used to access the indicator calculation results. The EA then reads data through that handle.
The usual workflow is as follows.
- Create an indicator handle for Bollinger Bands with
iBands - Retrieve band values with
CopyBuffer - Use the retrieved values in trading logic
This structure is one of the major differences from how indicators are retrieved in MQL4.
In MQL4, many indicator functions could return values directly. In MQL5, the process uses the following two-step model.
- Create a handle
- Read values from buffers
If beginners do not understand this difference, they often run into the following problems.
Common mistakes
- Assuming that simply calling
iBandsreturns a band value - Trying to get band values without using
CopyBuffer - Creating
iBandson every call insideOnTick(), which makes the EA heavier
The third mistake is especially important because it can reduce EA performance.
The basic rule is to create the indicator handle only once, usually in OnInit().
1.1 Basics of Bollinger Bands
Bollinger Bands are a technical indicator that visualizes price volatility.
They were developed by John Bollinger and are widely used in trading.
Bollinger Bands consist of the following three lines.
| Line | Description |
|---|---|
| Middle line | Moving average, usually SMA |
| Upper band | Moving average + standard deviation |
| Lower band | Moving average – standard deviation |
Here, standard deviation is a statistical value that shows how far price is from its average.
In other words, Bollinger Bands have the following behavior.
- When market volatility is high, the bands widen
- When the market is quiet, the bands narrow
Traders use this behavior to make the following kinds of decisions.
Typical uses
- Near the upper band: potentially overbought
- Near the lower band: potentially oversold
- Band expansion: possible trend development
- Band contraction: range-bound market conditions
However, these are not absolute buy or sell signals.
Behavior changes greatly depending on the market environment and timeframe, so in EA development Bollinger Bands are commonly combined with other indicators.
1.2 Main Uses in EA Development
The purpose of using iBands in MQL5 is to implement trading logic based on Bollinger Bands.
In EAs, it is mainly used for the following purposes.
Mean-Reversion Strategy
When price reaches the outside of a band, this method treats the move as possibly overextended and looks for a reversal.
Example
- Price reaches the upper band: sell
- Price reaches the lower band: buy
This strategy often works better in range-bound markets, but it may fail during strong trends.
Breakout Strategy
When price breaks outside a band, this method treats it as a possible start of a trend.
Example
- Price breaks above the upper band: buy
- Price breaks below the lower band: sell
This approach is often used in trend-following EAs.
Volatility Detection
By calculating band width, an EA can judge the market state.
Example
- Narrow band width: quiet market
- Expanding band width: possible trend start
This information can be used for the following purposes.
- Trade entry timing
- Lot size adjustment
- Trade suspension filters
Important Notes for Beginners
Bollinger Bands are not a directional indicator.
They indicate the range of price movement.
The following misunderstandings are common.
Common misunderstandings
- Touching the upper band means price will always fall
- Touching the lower band means price will always rise
In reality, during a strong trend, price can continue moving along the band.
EA logic often combines Bollinger Bands with other indicators such as RSI or ATR.

2. iBands Syntax and Parameters
iBands is a function that creates a handle, or ID, for the Bollinger Bands indicator.
When this function runs, MetaTrader internally creates the Bollinger Bands calculation indicator and returns its handle as an integer value.
This handle is then used with CopyBuffer to retrieve band values.
The basic MQL5 syntax is as follows.
int iBands(
string symbol,
ENUM_TIMEFRAMES period,
int bands_period,
int bands_shift,
double deviation,
ENUM_APPLIED_PRICE applied_price
);
Return value
- Success: indicator handle, an integer of 0 or greater
- Failure:
INVALID_HANDLE
For this reason, EAs usually check for errors like this.
int bandsHandle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
if(bandsHandle == INVALID_HANDLE)
{
Print("Failed to create iBands");
}
If you skip this error check, a later CopyBuffer call may produce an unclear error.
2.1 Meaning of Each Parameter
iBands has six parameters.
Understanding each one is very important in EA development.
| Parameter | Description |
|---|---|
| symbol | Currency pair, for example EURUSD |
| period | Timeframe |
| bands_period | Moving average period |
| bands_shift | Band shift |
| deviation | Standard deviation |
| applied_price | Price type to use |
Each parameter is explained below.
symbol
This specifies the currency pair used for indicator calculation.
Example
_Symbol
This means the currency pair of the current chart.
Example
iBands("EURUSD", PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
In multi-currency EAs, you can also specify a different currency pair.
Notes
- If the symbol name is wrong, the result may be INVALID_HANDLE
- Some brokers add suffixes such as EURUSD.m
period
This is the timeframe used to calculate Bollinger Bands.
Main options
PERIOD_CURRENT
PERIOD_M1
PERIOD_M5
PERIOD_M15
PERIOD_H1
PERIOD_D1
Example
PERIOD_H1
This means Bollinger Bands on the 1-hour timeframe.
Common mistakes
- Confusing the EA chart timeframe with the indicator timeframe
- Using multi-timeframe processing without understanding it
For beginners, using
PERIOD_CURRENT
is a safer starting point.
bands_period
This is the moving average period for the middle line.
Example
20
This is a common Bollinger Bands setting.
Typical settings
| Period | Use |
|---|---|
| 20 | General use |
| 14 | Short-term trading |
| 50 | Medium-term trends |
The smaller the period, the faster the response, but the more noise it can include.
bands_shift
This is the value that shifts the indicator display position to the right.
Normally, use
0
This value is mainly for chart display purposes and is rarely used in EA logic.
Beginners can usually keep it fixed at 0.
deviation
This is the standard deviation multiplier that determines band width.
Typical value
2.0
This is the most common setting.
Example
| deviation | Feature |
|---|---|
| 1.0 | Narrow bands |
| 2.0 | Standard |
| 3.0 | Very wide |
The larger the value, the wider the bands become.
applied_price
This is the type of price used for indicator calculation.
Main values
PRICE_CLOSE
PRICE_OPEN
PRICE_HIGH
PRICE_LOW
PRICE_MEDIAN
PRICE_TYPICAL
PRICE_WEIGHTED
Beginners usually use
PRICE_CLOSE
This means Bollinger Bands based on closing prices.
2.2 Basic Code Using iBands
In an EA, you usually create the handle in OnInit().
Example
int bandsHandle;
int OnInit()
{
bandsHandle = iBands(
_Symbol,
PERIOD_CURRENT,
20,
0,
2.0,
PRICE_CLOSE
);
if(bandsHandle == INVALID_HANDLE)
{
Print("iBands creation error");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
With this method, the EA creates the indicator only once when it starts.
Common Mistakes
The following are mistakes beginners often make.
Creating iBands on Every OnTick()
// Bad example
void OnTick()
{
int handle = iBands(...);
}
This creates an indicator on every tick, which makes the EA heavier.
Skipping Error Checks
int handle = iBands(...);
With only this line, you will not notice if creation fails.
Not Checking the Handle Before CopyBuffer
If the handle is invalid, it can cause errors such as
array out of range
3. How to Get Bollinger Bands Values with CopyBuffer
iBands creates the Bollinger Bands calculation indicator, but this function alone does not retrieve band values.
To get actual values, you must use CopyBuffer to copy data from the indicator buffer.
In MQL5, indicators internally have multiple buffers, or data arrays.
For Bollinger Bands, the following three lines are stored in separate buffers.
| Buffer number | Description |
|---|---|
| 0 | Upper Band |
| 1 | Middle Band / Moving Average |
| 2 | Lower Band |
By specifying this buffer number when calling CopyBuffer, an EA can retrieve any desired band value.
3.1 Basic Syntax of CopyBuffer
The basic syntax of CopyBuffer is as follows.
int CopyBuffer(
int indicator_handle,
int buffer_num,
int start_pos,
int count,
double buffer[]
);
Each parameter means the following.
| Parameter | Description |
|---|---|
| indicator_handle | The handle obtained from iBands |
| buffer_num | Buffer number |
| start_pos | Start position for retrieval |
| count | Number of data items to retrieve |
| buffer | Array that stores the copied data |
Return value
- Number of copied elements
- -1 on failure
For this reason, checking the return value is important in real EA code.
3.2 Code Example for Getting the Upper Band
First, here is an example that retrieves the Upper Band.
double upperBand[];
if(CopyBuffer(bandsHandle, 0, 0, 1, upperBand) < 0)
{
Print("CopyBuffer error");
}
This code means the following.
| Parameter | Meaning |
|---|---|
| bandsHandle | iBands handle |
| 0 | Upper band |
| 0 | Latest bar |
| 1 | Retrieve one item |
You can then read the result like this.
double upper = upperBand[0];
3.3 How to Get the Lower Band and Middle Line
You can retrieve the middle line and lower band in the same way.
Middle Line (Moving Average)
double middleBand[];
CopyBuffer(bandsHandle, 1, 0, 1, middleBand);
Lower Band
double lowerBand[];
CopyBuffer(bandsHandle, 2, 0, 1, lowerBand);
3.4 Practical Code Example
In an EA, you usually retrieve all three bands at the same time.
double upper[1];
double middle[1];
double lower[1];
if(CopyBuffer(bandsHandle,0,0,1,upper) < 0) return;
if(CopyBuffer(bandsHandle,1,0,1,middle) < 0) return;
if(CopyBuffer(bandsHandle,2,0,1,lower) < 0) return;
double upperBand = upper[0];
double middleBand = middle[0];
double lowerBand = lower[0];
This lets you use all three Bollinger Bands lines inside the EA.
3.5 Common Mistakes and Notes
CopyBuffer is one of the areas where beginners most often get stuck.
The following mistakes are especially common.
Not Preparing the Array Size
The following code is risky.
double upper[];
CopyBuffer(handle,0,0,1,upper);
If the array size is not prepared, an error may occur.
Safer method
double upper[1];
or
ArrayResize(upper,1);
Using the Wrong Buffer Number
This is an easy point for beginners to confuse.
| Buffer | Description |
|---|---|
| 0 | Upper band |
| 1 | Middle line |
| 2 | Lower band |
If you mix up this order, the trading logic can become completely wrong.
Data Is Not Ready Yet
Right after an EA starts, the indicator calculation may not be complete.
In that case, CopyBuffer returns -1.
Safe handling
if(CopyBuffer(...) <= 0)
return;
Confusing the Latest Bar and the Closed Bar
start_pos = 0 means the currently forming bar.
EA logic may use the following distinction.
| start_pos | Meaning |
|---|---|
| 0 | Latest bar |
| 1 | Closed bar |
Many trading rules use the closed bar, position 1.
4. Implementation Examples Using iBands in an EA
iBands is not just a function for displaying Bollinger Bands.
In an EA, it is used as input for mechanically judging trading conditions by comparing retrieved band values with current price.
However, Bollinger Bands are not a complete trading signal by themselves.
In real use, they are often separated into roles such as the following.
- Mean-reversion judgment
- Breakout judgment
- Quiet or volatile market judgment
- Entry prohibition filter
This section covers the two most basic patterns so beginners can understand them clearly.
- A mean-reversion type that looks for reversal after band contact
- A breakout type that follows price after it breaks through a band
4.1 Basic Example of Mean-Reversion Logic
In mean-reversion logic, when price reaches the outside of a band, the EA looks for a return after an overextended move.
A typical example is as follows.
- Current price is at or above the upper band: consider selling
- Current price is at or below the lower band: consider buying
The code can look like this.
double upper[1];
double lower[1];
if(CopyBuffer(bandsHandle, 0, 0, 1, upper) <= 0) return;
if(CopyBuffer(bandsHandle, 2, 0, 1, lower) <= 0) return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(bid >= upper[0])
{
Print("Sell signal candidate: price reached the upper band");
}
if(ask <= lower[0])
{
Print("Buy signal candidate: price reached the lower band");
}
This code does not place orders yet.
At first, it is easier to verify the logic by only printing condition checks to the log.
Notes on Mean-Reversion Logic
Mean-reversion logic is visually easy to understand, but the following failures are common in live use.
- Taking countertrend trades in a strong trend market
- Entering immediately just because price touched a band
- Using an unclosed bar and catching false signals
The key point is that band contact does not mean immediate reversal.
When the trend is strong, price may keep moving along the band.
For this reason, real systems often add supporting conditions such as the following.
- Confirming overbought or oversold conditions with RSI
- Judging only on closed bars
- Preventing trades against the higher-timeframe trend
4.2 Basic Example of Breakout Logic
In breakout logic, price moving outside a band is treated as possible momentum, and the EA follows that direction.
A typical example is as follows.
- Close price breaks above the upper band: buy candidate
- Close price breaks below the lower band: sell candidate
In this case, it is safer to use a closed bar instead of the currently forming bar.
Therefore, start_pos = 1 is often used with CopyBuffer.
double upper[1];
double lower[1];
double closePrice[1];
if(CopyBuffer(bandsHandle, 0, 1, 1, upper) <= 0) return;
if(CopyBuffer(bandsHandle, 2, 1, 1, lower) <= 0) return;
if(CopyClose(_Symbol, PERIOD_CURRENT, 1, 1, closePrice) <= 0) return;
if(closePrice[0] > upper[0])
{
Print("Buy breakout candidate: closed candle closed above the upper band");
}
if(closePrice[0] < lower[0])
{
Print("Sell breakout candidate: closed candle closed below the lower band");
}
The advantage of this method is that it more easily excludes temporary moves outside the band on an unclosed bar.
Notes on Breakout Logic
Breakouts can be effective in trending markets, but they are more likely to fail in range-bound markets.
Common failures include the following.
- Misreading a brief spike as a real breakout
- Reacting to noise when band width is extremely narrow
- Getting caught in sharp moves around economic releases
For this reason, real systems may add conditions such as the following.
- Allow trades only when band width is above a certain level
- Confirm volatility with ATR
- Avoid trading when spreads widen
4.3 Volatility Detection Using Band Width
Bollinger Bands can quantify the size of market movement by taking the difference between the upper and lower bands.
The formula is simple.
double bandWidth = upper[0] - lower[0];
Using this bandWidth, an EA can make judgments such as the following.
- Narrow band width: market is relatively stagnant
- Wide band width: market is active
- Do not enter if the width is below a threshold
- Enable breakout logic if the width is above a threshold
Code example
double upper[1];
double lower[1];
if(CopyBuffer(bandsHandle, 0, 1, 1, upper) <= 0) return;
if(CopyBuffer(bandsHandle, 2, 1, 1, lower) <= 0) return;
double bandWidth = upper[0] - lower[0];
if(bandWidth < 0.0010)
{
Print("Skip: band width is too small");
}
else
{
Print("Trade candidate: band width is sufficient");
}
However, this threshold depends on the currency pair and number of digits.
For example, EURUSD and USDJPY use different price scales, so the same numeric threshold cannot be applied blindly.
Notes on Band Width Filters
- Do not reuse one fixed threshold for every currency pair
- Do not ignore differences between 5-digit and 3-digit brokers
- A value that works in the tester may shift in live trading
For beginners, a safer approach is to first print bandWidth to the log, observe it, and turn it into a condition only after understanding the natural range for the target symbol.
4.4 Common Implementation Mistakes
The following are mistakes beginners often make when adding iBands to an EA.
Connecting Every Tick Signal Directly to Orders
If an order is sent every time a signal condition becomes true, the EA may place repeated orders under the same condition.
Possible countermeasures
- Judge only on a new bar
- Do not place another order while a position is already open
- Store the previous signal state in a variable
Confusing bid and ask
Buy orders usually need awareness of Ask, while sell-side judgment needs awareness of Bid.
If this is unclear, condition checks may not match the actual execution price.
Mixing Closed and Unclosed Bars
start_pos = 0 is the unclosed bar, and start_pos = 1 is the most recent closed bar.
If this distinction changes in the middle of the logic, backtest results can diverge from live results.
Skipping Failure Handling for CopyBuffer
If the EA continues to reference an array after it failed to retrieve values, it can cause logic errors or other errors.
Always check as follows.
if(CopyBuffer(bandsHandle, 0, 1, 1, upper) <= 0)
return;
5. Notes and Common Real-World Errors When Using iBands
iBands is a standard MQL5 function, and its basic usage is not difficult.
However, when it is added to a real EA, beginners often get stuck on handle management, data retrieval timing, and price handling.
Beginners are especially likely to face a situation where the code appears to run, but the result is wrong, rather than a simple syntax error.
This section organizes the main points to watch when using iBands in practice.
5.1 Causes of INVALID_HANDLE
If iBands succeeds, it returns an indicator handle. If it fails, it returns INVALID_HANDLE.
If you skip this check, later CopyBuffer calls can fail and make the cause difficult to isolate.
The basic check code is as follows.
int bandsHandle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
if(bandsHandle == INVALID_HANDLE)
{
Print("Failed to create iBands");
return INIT_FAILED;
}
The main causes of INVALID_HANDLE are as follows.
- Incorrect symbol specification
- Invalid timeframe specification
- Insufficient terminal state or data loading
- Initialization timing problems
Common Failure
For example, if a multi-currency EA directly writes "EURUSD", some brokers may use symbol names such as "EURUSDm" or "EURUSD.". In that case, the intended currency pair may not exist.
For beginners, it is safer to start with
_Symbol
Another typical mistake is creating the handle inside OnTick().
That creates a new indicator on every tick, increases terminal load, and can make behavior unstable.
5.2 Causes of CopyBuffer Failure
Even if iBands succeeds, CopyBuffer does not always succeed.
In MQL5, CopyBuffer can fail when it is called before the indicator calculation is ready.
For example, the following code is risky because it does not check the return value.
double upper[1];
CopyBuffer(bandsHandle, 0, 0, 1, upper);
double value = upper[0];
In this case, even if copying fails, the EA still uses upper[0], which can cause abnormal logic or wrong judgments.
The safer way is as follows.
double upper[1];
if(CopyBuffer(bandsHandle, 0, 0, 1, upper) <= 0)
{
Print("Failed to retrieve upper band");
return;
}
Main Causes of CopyBuffer Failure
- Invalid handle
- Wrong buffer number
- The required number of bars has not been calculated yet
- Insufficient data for the symbol or timeframe
- History data is not fully loaded right after startup
5.3 Mixing Up Buffer Numbers
iBands handles three lines, and beginners most often get confused by the order of the buffer numbers.
This article uses the following mapping.
| Buffer number | Description |
|---|---|
| 0 | Upper band |
| 1 | Middle line |
| 2 | Lower band |
If you remember this in reverse, the code may compile and run, but the trading judgment can be completely wrong.
For example, if you intend to judge the lower band but specify buffer_num = 1, you are actually comparing against the middle line, so the entry condition becomes something else.
Common Situations
- Confusing the upper band with the middle line
- Code comments and actual buffer numbers do not match
- Reusing old code or other articles where the order is mixed
This kind of mistake is troublesome because it is hard to notice visually.
During debugging, print all three retrieved values to the log and confirm that their order is correct.
Print("Upper=", upper[0], " Middle=", middle[0], " Lower=", lower[0]);
5.4 Confusing Unclosed and Closed Bars
When start_pos = 0 is specified in CopyBuffer, it retrieves the currently forming bar.
By contrast, start_pos = 1 is the most recent closed bar.
This difference is very important in EA logic.
Characteristics of start_pos = 0
- High real-time responsiveness
- The value changes constantly
- Signals can appear earlier
- False signals can also increase
Characteristics of start_pos = 1
- Uses values after bar close
- Logic tends to be more stable
- Backtest and live results are less likely to differ
A common beginner mistake is using an unclosed bar in the entry logic while explaining or testing the rule as if it used a closed bar.
This reduces the reproducibility of the logic.
Use them clearly as follows.
// Check the unclosed bar
CopyBuffer(bandsHandle, 0, 0, 1, upper);
// Check the closed bar
CopyBuffer(bandsHandle, 0, 1, 1, upper);
If you want more stable live behavior, it is usually safer to start with a closed-bar basis.
5.5 Heavy Processing on Every OnTick()
iBands is useful, but the EA can become heavy if it is used incorrectly.
The following two patterns are especially common problems.
- Creating
iBandsinsideOnTick()every time - Running many
CopyBuffercalls on every tick
A bad example is the following.
void OnTick()
{
int handle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
double upper[1];
CopyBuffer(handle, 0, 0, 1, upper);
}
This code creates a new indicator each time a tick arrives, causing unnecessary load.
The basic approach is as follows.
- Create the handle once in
OnInit() - Retrieve values only when needed
- If possible, calculate only on a new bar
For example, you can reduce unnecessary calculation by adding new-bar detection.
datetime lastBarTime = 0;
void OnTick()
{
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime == lastBarTime)
return;
lastBarTime = currentBarTime;
// Retrieve iBands values only on a new bar
}
This structure makes it easier to avoid excessive calculation on every tick.
5.6 Ignoring Price Digits and Currency Pair Differences
Bollinger Band values are prices.
Therefore, if you ignore price scale differences between currency pairs, condition settings can become wrong.
For example, EURUSD and USDJPY have very different price ranges.
- EURUSD is around 1.x
- USDJPY is above the 100-yen range
Because of this difference, if you set a fixed band-width threshold, it may work on one pair but not work at all on another.
Common Mistakes
- Using
bandWidth < 0.0010for every symbol - Treating 5-digit and 3-digit symbols the same way
- Ignoring
PointorDigits
A safer method is to handle price differences in points.
double bandWidthPoints = (upper[0] - lower[0]) / _Point;
This format can absorb some symbol differences.
However, the best value still differs by symbol and timeframe, so adjustment through backtesting and log observation is required.
5.7 Summary of Common Mistakes
Here are the typical mistakes when using iBands.
- Assuming a value is available just by calling
iBands - Not checking
INVALID_HANDLE - Ignoring the return value of
CopyBuffer - Mixing up buffer numbers
- Confusing unclosed and closed bars
- Creating the handle on every
OnTick() - Using fixed conditions without considering price scale differences
iBands is not a difficult function, but it can easily cause implementation mistakes if you are not used to MQL5’s handle-based indicator model.
For beginners, the following order is safer.
- Confirm that handle creation succeeds
- Confirm that
CopyBuffersucceeds - Print retrieved values to the log and verify them
- Then add them to trading logic
6. Common Mistakes and Stumbling Blocks
The syntax of iBands is short and looks simple.
However, once it is added to an EA, it often causes problems where the code compiles but does not behave as intended.
Many causes come from implementing the following MQL5-specific mechanisms without fully understanding them.
- Handle creation
- Value retrieval with
CopyBuffer - Management of bar-close timing
This section organizes the beginner mistakes with the highest practical impact.
6.1 Writing It Like MQL4
One of the most common mistakes is confusing MQL4 and MQL5 indicator retrieval methods.
In MQL4, many indicator functions could return values directly when called.
Because of that, using iBands() in MQL5 with the same mindset can lead to misunderstanding.
Beginners often assume the following.
iBands()returns the upper band value itself- The return value of
iBands()can be compared as a price CopyBuffer()is unnecessary
However, MQL5 iBands() returns a handle, not a price.
That means the following code has a different meaning than beginners may expect.
int bandsHandle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
This bandsHandle is not a price such as 1.08452.
It is only an ID used to access the internal indicator.
Common Wrong Implementation
if(Bid > iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE))
{
// This is wrong
}
This does not perform the intended comparison.
The correct flow is as follows.
- Create a handle with
iBands() - Retrieve band values with
CopyBuffer() - Compare the retrieved array element with price
If this basic flow is broken, all later logic becomes unstable.
6.2 Using Buffers Without a Clear Understanding
iBands has three lines, so if you do not correctly understand which buffer belongs to which line, the entire logic can break.
This article uses the following mapping.
| Buffer number | Description |
|---|---|
| 0 | Upper band |
| 1 | Middle line |
| 2 | Lower band |
Common mistakes include the following.
- Confusing the upper band and middle line
- Confusing the middle line and lower band
- Code comments do not match the actual number
The difficult part is that the code can appear to work normally.
It does not cause a compile error, and CopyBuffer() may still succeed, so it is easy to miss.
How to Check in Practice
After retrieval, print the values to the log and confirm the size relationship.
Print("Upper=", upper[0], " Middle=", middle[0], " Lower=", lower[0]);
Normally, for the same bar:
- Upper band > middle line
- Middle line > lower band
If this relationship is broken, you should review the buffer specification or data retrieval method.
6.3 Handling Arrays Too Loosely
CopyBuffer() copies data into an array.
Therefore, if you use it with an unclear understanding of array preparation and references, it can cause errors or incorrect behavior.
Typical mistakes include the following.
- Using arrays without considering their size
- Referencing values without knowing how many items were retrieved
- Assuming
[0]always means the latest closed candle
For example:
double upper[];
CopyBuffer(bandsHandle, 0, 0, 1, upper);
Print(upper[0]);
This may work in some environments, but it is not always safe. Beginners should first specify the size clearly.
double upper[1];
if(CopyBuffer(bandsHandle, 0, 0, 1, upper) <= 0)
return;
Print(upper[0]);
When retrieving multiple bars, you must clearly understand which index corresponds to which bar.
double upper[3];
CopyBuffer(bandsHandle, 0, 0, 3, upper);
In this case, upper[0] is usually the first item from the retrieval start position, and upper[1] is the next item.
However, time-series direction can easily become confusing depending on array settings and retrieval assumptions. For beginners, retrieving one bar at a time explicitly reduces mistakes.
6.4 Misunderstanding start_pos
The start_pos argument of CopyBuffer() indicates which bar position to start from.
If this meaning is unclear, signal judgment becomes unstable.
The typical meanings are as follows.
| start_pos | Meaning |
|---|---|
| 0 | Currently forming bar |
| 1 | Most recent closed bar |
| 2 | The closed bar before that |
A common beginner mistake is explaining the backtest as if it uses closed candles while the implementation uses 0.
For example:
- The article says, “Buy when the close price exceeds the upper band”
- The implementation uses
start_pos = 0
In that case, the EA actually catches temporary price movement before the close is confirmed.
Safer Thinking
- Prioritize signal stability: use
start_pos = 1 - Prioritize early signals: use
start_pos = 0
For beginners, using 1 first is safer.
Unclosed bars can look attractive in real time, but they increase false signals and condition fluctuation.
6.5 Repeating the Same Work on Every OnTick()
In an EA, OnTick() is called very frequently.
If iBands processing is written carelessly inside OnTick(), unnecessary calculation and duplicate judgments can occur.
Typical bad patterns include the following.
- Creating
iBands()on every tick - Calling
CopyBuffer()many times on every tick - Judging the same signal repeatedly within the same bar
These can cause the following problems.
- The terminal becomes heavy
- The log becomes very large
- The EA may enter multiple times under the same condition
- Backtest results and live trading behavior may feel different
Basic Improvement Policy
- Create the handle once in
OnInit() - Retrieve values only as much as needed
- Run signal judgment only on a new bar
A basic new-bar detection example is as follows.
datetime lastBarTime = 0;
void OnTick()
{
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime == lastBarTime)
return;
lastBarTime = currentBarTime;
// Retrieve and judge iBands values here
}
This structure makes it easier to reduce unnecessary repeated judgment.
6.6 Overtrusting Band Contact
A common beginner mistake with Bollinger Bands is assuming that touching the upper or lower band alone is a strong trading reason.
In reality, Bollinger Bands have the following characteristics.
- They do not directly show trend direction
- They do not guarantee reversal
- They do not guarantee breakout continuation
Therefore, the following deterministic logic is risky.
- Upper band contact: always sell
- Lower band contact: always buy
- Band breakout: always trade with the breakout
In reality, the meaning changes with the market environment.
Examples
- Mean reversion tends to work better in range-bound markets
- Countertrend trading is risky in strong trends
- Breakouts may work better after band contraction and expansion
- Noise can increase sharply around economic releases
When adding Bollinger Bands to an EA, stability often improves if you add at least one of the following.
- RSI
- ATR
- Moving average direction
- Higher-timeframe trend
- Spread filter
6.7 Skipping Log Checks
A surprisingly common beginner mistake is connecting retrieved values directly to trading conditions without checking them.
For example, they skip the following sequence.
- Confirm handle creation
- Confirm
CopyBuffer()success - Print retrieved values to the log
- Verify the condition expression
- Add order placement processing
If these checks are skipped, it becomes hard to understand why an entry happened at a particular point.
Example Logs to Check During Initial Implementation
Print(
"Upper=", upper[0],
" Middle=", middle[0],
" Lower=", lower[0],
" Bid=", SymbolInfoDouble(_Symbol, SYMBOL_BID),
" Ask=", SymbolInfoDouble(_Symbol, SYMBOL_ASK)
);
This check helps you separate the following issues.
- Whether price really exceeded the upper band
- Whether the price comparison direction is correct
- Whether the use of
BidandAskis appropriate
6.8 A Safer Process for Beginners
When using iBands, adding it to a complex EA from the beginning often leads to failure.
Beginners should proceed in the following order.
- Create the handle in
OnInit() - Retrieve only one value with
CopyBuffer() - Print the value to the log and confirm it
- Compare price with the upper and lower bands
- If the condition matches, print only a log message
- Add order processing afterward
This order makes it easier to identify where the problem occurred.
By contrast, if you write all of the following at once, troubleshooting becomes difficult.
iBandsCopyBuffer- Entry conditions
- Lot calculation
OrderSend
7. FAQ
7.1 What does iBands return?
iBands returns an indicator handle, or ID, not the Bollinger Bands price itself.
To retrieve actual band values, you must use CopyBuffer to copy data from the indicator buffer.
7.2 How are iBands buffer numbers mapped?
Bollinger Bands consist of three lines, mapped to the following buffer numbers.
| Buffer number | Description |
|---|---|
| 0 | Upper Band |
| 1 | Middle Band / Moving Average |
| 2 | Lower Band |
By specifying this number as the second argument of CopyBuffer, you can retrieve the target line.
7.3 Where should iBands be created?
Usually, it is recommended to create it only once inside OnInit().
If you create iBands on every OnTick(), the EA creates an indicator on every tick, which can make processing heavier and behavior less stable.
7.4 What does start_pos in CopyBuffer mean?
start_pos specifies which bar position to retrieve data from.
| start_pos | Meaning |
|---|---|
| 0 | Currently forming bar |
| 1 | Most recent closed bar |
| 2 | The previous bar before that |
If you want to avoid real-time fluctuation, using the closed bar, position 1, is more stable.
7.5 Why does CopyBuffer fail?
The main causes are as follows.
- The indicator handle is invalid
- Bar history has not loaded yet
- The buffer number is wrong
- The indicator calculation is not complete
For this reason, always check the return value of CopyBuffer and stop processing when retrieval fails.
7.6 Does price always reverse after touching a Bollinger Band?
No, price does not always reverse.
Bollinger Bands are an indicator of price volatility, not an indicator that guarantees trend direction.
During a strong trend, price can continue moving along the band, often called a band walk.
For this reason, EAs commonly combine Bollinger Bands with supporting conditions such as the following.
- RSI
- ATR
- Moving average trend
- Higher-timeframe direction
7.7 How is band width calculated?
Bollinger Band width can be calculated as the difference between the upper band and the lower band.
double bandWidth = upper[0] - lower[0];
This value can support judgments such as the following.
- Narrow band width: the market may be stagnant
- Wide band width: the market may be active
However, the value scale differs by currency pair and price digits, so fixed thresholds must be adjusted for the trading environment.
7.8 What is the difference between iBands and iCustom?
iBands is a function that calls the standard Bollinger Bands indicator built into MetaTrader.
By contrast, iCustom is a function that calls a custom indicator created by the user or provided externally.
For standard Bollinger Bands, iBands is simpler and easier to use. For custom calculation logic, use iCustom.