Bollinger Bands Statistics in Trading | by Atilla Yurtseven | Towards Data Science

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!
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.
def load_data(sym="EUR", to_sym="USD", interval="5min", dtype=2, intraday=False, outputsize="full"):    key = "ENTER_YOUR_KEY"    if dtype == 1: # Download stock        if 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 forex        print("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 Crypto        url = "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# Optionssym = "EUR"to_sym = "USD" # only for fx and cryptocurrencyintraday = False # False: Dailyinterval = "5min" # 1min 5min 15min 30min 60min - only if intraday is set to Truedtype = 2 # 1: stock 2: fx 3: crypto# load datadf = 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))
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 deviationlb = 20 # simple moving average (SMA) of last 20 bars including the final barlines = 500 # plot last 500 bars onlyadd_bb(df, dev=dev, lb=lb, lines=lines)
Very nice candlestick chart in a tricky way! And here’s the stats of Bollinger Bands within two standard deviation:
Total bars: 5000Deviation 2Inside: 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.
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))
You might think it looks like normal distribution but i can easily say that it’s not. Here is how it should look:
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.
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: 5000Deviation 1Inside: 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!
Statistically speaking, Bollinger Bands may not meet your needs. However, you can use your favorite trading strategy and still make profit!
Disclaimer
I’m not a professional financial advisor. This article and codes, shared for educational purposes only and not financial advice. You are responsible your own losses or wins.
The whole code of this article can be found on this repository:
github.com
Ah also; remember to follow me on the following social channels:
MediumTwitterTradingView
Until next time; stay safe, trade safe!!!
Atilla Yurtseven
Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without seeking professional advice. See our Reader Terms for details.