Bollinger Bands Statistics in Trading
By: Atilla Yurtseven
Tags: Bollinger Bands, Time Series Analysis, Statistics, Data Science, Bell Curve

There are lots of traders use Bollinger Bands. I love Bollinger Bands as well. It uses and brings statistics into the trading world. But how accurate is it? Is it a correct way to use standard deviation in time-series? Let’s find out together!

Bollinger Bands

There is no leading indicators

As traders, most of us use OHLC candle/bar charts (Open-High-Low-Close). This chart shows us what happened in the past. We can clearly see where a bar opened, moved and closed. We use lots of indicators. All of them tell us what happened in the past. To me; there’s no leading indicators. Past is the past! However, there are probabilities. If we have enough data from the past, we will have enough statistics about the underlying asset.

DATA DATA DATA…

We need data! Data is the key. Data is everything. We need to grab the data from a reliable source. In this article, I’m going to show you how can you download data for free as well using Python. I’m going to use AlphaVantage.

First of all; go ahead and register for a free key in AlphaVantage website. Note that, I’m not affiliated with them.

Now let’s download the data using Python.



key = "ENTER_YOUR_KEY"

if dtype == 1: # Download stock

if intraday:

url = "

else:

url = "

elif dtype == 2: # Download forex

print("FX")

if intraday:

url = "

else:

url = "

elif dtype == 3: # Download Crypto

url = "

print("Downloading", url)

print("---")

df = pd.read_csv(url) def load_data(sym="EUR", to_sym="USD", interval="5min", dtype=2, intraday=False, outputsize="full"):key = "ENTER_YOUR_KEY"if dtype == 1: # Download stockif intraday:url = " https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&interval={}&symbol={}&apikey={}&datatype=csv&outputsize={ }".format(interval, sym, key, outputsize)else:url = " https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol={}&apikey={}&datatype=csv&outputsize={ }".format(sym, key, outputsize)elif dtype == 2: # Download forexprint("FX")if intraday:url = " https://www.alphavantage.co/query?function=FX_INTRADAY&interval={}&from_symbol={}&to_symbol={}&apikey={}&datatype=csv&outputsize={ }".format(interval, sym, to_sym, key, outputsize)else:url = " https://www.alphavantage.co/query?function=FX_DAILY&from_symbol={}&to_symbol={}&apikey={}&datatype=csv&outputsize={ }".format(sym, to_sym, key, outputsize)elif dtype == 3: # Download Cryptourl = " https://www.alphavantage.co/query?function=DIGITAL_CURRENCY_DAILY&symbol={}&market={}&apikey={}&datatype=csv&outputsize={ }".format(sym, to_sym, key, outputsize)print("Downloading", url)print("---")df = pd.read_csv(url) # rename columns

if dtype == 3:

df.rename(columns={'timestamp': 'Date', 'open (USD)': 'Open', 'high (USD)': 'High', 'low (USD)': 'Low', 'close (USD)': 'Close', 'volume': 'Volume'}, inplace=True)

else:

df.rename(columns={'timestamp': 'Date', 'open': 'Open', 'high': 'High', 'low': 'Low', 'close': 'Close', 'volume': 'Volume'}, inplace=True)

df.sort_values(by="Date", ascending=True, inplace=True)

print("Data loaded:", len(df), "rows")

return df # Options

sym = "EUR"

to_sym = "USD" # only for fx and cryptocurrency

intraday = False # False: Daily

interval = "5min" # 1min 5min 15min 30min 60min - only if intraday is set to True

dtype = 2 # 1: stock 2: fx 3: crypto # load data

df = load_data(sym, to_sym, interval, dtype, intraday)

We have downloaded our data and here’s how it looks.

df['Close'].plot(figsize=(15, 10))

EURUSD Daily Closing Prices

Here’s the daily chart of EURUSD. Now what we need to do is to add Bollinger Bands on the chart and calculate the statistics.

def add_bb(df, dev=2, lb=20, col="Close", lines=100):

df['MA'] = df[col].rolling(lb).mean()

df['STD'] = df[col].rolling(lb).std()

df['OVB'] = df['MA'] + df['STD'] * dev

df['OVS'] = df['MA'] - df['STD'] * dev

plot_candles(df, lines=lines)

get_stats(df, dev=dev)

Notice that i have added two new functions. First one plots candles and second one calculates and prints out the past statistics of Bollinger Bands. Let’s quickly define these functions.

def plot_candles(df, lines=100):

df['Bar'] = df['High'] - df['Low']

df['Body'] = abs(df['Close'] - df['Open'])

df['Up'] = df['Close'] > df['Open']

df['Color'] = np.where(df['Up'], "g", "r")

if lines > 0:

db = df[-lines:].reset_index(drop=True).reset_index()

else:

db = df.reset_index(drop=True).reset_index()

plt.figure(figsize=(15, 10))

plt.bar(db['index'], bottom=db['Low'], height=db['Bar'], color="#000000", width=0.2)

plt.bar(db['index'], bottom=np.where(db['Up'], db['Open'], db['Close']), height=db['Body'], color=db['Color'], width=0.9)

plt.plot(db['OVB'], color="b")

plt.plot(db['OVS'], color="b")

plt.show() def get_stats(df, high_col="Close", low_col="Close", high_dev="OVB", low_dev="OVS", dev=2):

total = len(df)

inside = len(df[(df[high_col]<=df[high_dev]) & (df[low_col]>=df[low_dev])])

upside = len(df[df[high_col]>=df[high_dev]])

downside = len(df[df[low_col]<=df[low_dev]])

i = np.round(inside / total * 100, 2)

u = np.round(upside / total * 100, 2)

d = np.round(downside / total * 100, 2)

# Print the stats

print("Total bars:", total)

print("Deviation", dev)

print("Inside: ", i, "%", sep="")

print("Up side: ", u, "%", sep="")

print("Down side: ", d, "%", sep="")

Now let’s go ahead and run add_bb() function and see the results

dev = 2 # standard deviation

lb = 20 # simple moving average (SMA) of last 20 bars including the final bar

lines = 500 # plot last 500 bars only

add_bb(df, dev=dev, lb=lb, lines=lines)

EURUSD Candlestick chart with Bollinger Bands

Very nice candlestick chart in a tricky way! And here’s the stats of Bollinger Bands within two standard deviation:

Total bars: 5000

Deviation 2

Inside: 89.96%

Up side: 5.3%

Down side: 4.36%

Inside means, total bars closed between 2 standard deviation; up side means, total bars closed above the upper band and down side means, total bars closed below the lower band.

Now what we see here’s the “PAST” statistics of standard Bollinger Bands. Feel free to change settings. For example, you can set one standard deviation with 200 SMA.

Is Bollinger Bands accurate?

If you have a statistical background, you should know that, standard deviation works better in a normal distributed data. However, Closing prices is not normally distributed. You can easily see that running the code below:

df['Close'].hist(bins=500, figsize=(15, 10))

EURUSD closing prices histogram

You might think it looks like normal distribution but i can easily say that it’s not. Here is how it should look:

Normal distribution

That’s what i call a perfect bell curved shape. If you have such histogram plot, you can have better statistics which tells you more. And the distance between bands wouldn’t be as wide as Bollinger Bands.

What does standard deviation tell us?

Standard deviation states that; 68.26% of the data falls within one standard deviation of the mean, 95.44% of the data falls within two standard deviations of the mean and 99.74% of the data falls within three standard deviations of the mean. However, if we test using one standard deviation, here’s the result:

Total bars: 5000

Deviation 1

Inside: 44.96%

Up side: 28.96%

Down side: 25.7%

You can try stocks, forex, cryptocurrency; they all give you the similar results. This doesn’t look accurate to me at all!