Trading Trends with Bollinger Bands

Determining the trend of a stock price is very powerful when devising a technical trading system. If we are able to determine when a trend is reversing, that is reaching a top or a bottom, we’re able to determine good buy or sell points for our securities. In most of technical analysis, a single indicator is usually insufficient to develop a trading system and actual price movement predictions depend on multiple different indicators “firing” at the same time. Bollinger bands are an interesting trend analysis tool that forms bands around a stock price to determine when the stock’s price is about to change direction. In this post, I will describe a trading strategy based on Bollinger bands alone, implement the strategy in Python, backtest it on the stocks in the S&P 500, and analyze the results.

One thing to keep in mind when programming this trading tool (or any tool) is that it’s easy to get ahead of ourselves and try to devise a strategy based on future information. In the real world, we don’t have future information. Therefore our trading strategy can only use current and past close prices. For example, if a stock’s price is in a decline and breaks through the lower Bollinger band more than once before the “sell” criteria are met, we can’t program the trading tool to only buy after the second band break. When implementing these strategies we have to act like we’re waking up on any given day, running our strategy (or analyzing the price chart), and making a buy or sell decision. We could wait for multiple lower- or upper-band breaks before deciding to buy or sell a security or wait for multiple days above or below the Bollinger bands, but that would mean we’re using a different strategy than the one presented here (although these strategies are just as valid).

Bollinger Bands

Bollinger bands are moving average lines plotted above and below a given moving average. Unlike other trend indicators, the bands are not constructed based on a given percentage above or below a moving average line. Instead, Bollinger bands are plotted as standard deviations above the moving average line. Typically, a “middle band” is determined by taking an n-period moving average of the closing prices of a stock. The “upper band” can then be plotted by adding two standard deviations to the middle band and, similarly, the “lower band” is plotted by subtracting two standard deviations from the middle band. Trading indicators (mostly reversals) are formed when the stock’s closing price moves with respect to these lines, e.g. falls below the lower band or moves above the upper band.

Trading Strategy

Although there are many trading strategies, such as these listed on iForex, this post will use a simple trading strategy with the following assumptions:

  1. There is no short-selling, i.e. we can only sell stocks that we own
  2. It’s assumed we’re making the the decision in real-time on the day that an event occurs. That is, if the stock price triggers one of our rules a trade will be executed, we won’t speculate for higher highs or lower lows.
  3. Many shares can be bought before having to sell (the algorithm “buys” one share but multiply the results by any number to change the lot size).
  4. When selling we sell all of the previously purchased shares.

Given these assumptions, the trading strategy is this: buy the stock whenever the closing price first breaks below the lower band and sell all currently owned shares (if any) whenever the closing price rises above the upper band.

This trading strategy will be implemented in Python and performed on the stocks in the S&P 500 over 10-year, 5-year, and 1-year time periods. Unfortunately, the 1 year time period might be misleading due to the “covid crash” of 2020, but we’ll use it nonetheless.

Implementation

Implementing this strategy in Python is pretty simple provided the Pandas library and my Yahoo Finance stock price retrieval tool that I wrote about in a previous post (this tool can be installed via pip). First, we’ll need to include all of the dependencies for fetching, processing, and analyzing (plotting) the data:

Two simple functions are implemented to calculate moving averages and to determine the Bollinger bands based on the standard deviation from the moving average. In this post, the 20-day moving average is used across the board.

Plotting the Bollinger bands with the closing price for Apple (ticker: AAPL) over a 1-year time period produces the graph below (I will provide the code to do so later).

Bollinger bands around Apple stock. It can be seen how the price breaks above and below the upper and lower bands, respectively, which usually signals reversals.

As seen here, the price touches or passes through the upper and lower bands multiple times in just a 1-year time horizon. For our strategy, these breaks will signal buying and selling opportunities. Some potential trading points are shown below.

Potential trades for the simple strategy implemented here. Red circles are potential sell signals and green circles are potential buy signals.

Let’s actually implement the strategy that is defined above and see where the buy/sell points would land. To do so, the get_buy_sell_points function is created that takes as input the historical stock price data with the upper, lower, and middle bands added to the dataset.

This function produces a dictionary that records the index (of the pandas dataframe) where we will buy and sell the stock along with the price of the stock at those points in time. Plotting these buy/sell decisions on the same 1-year time horizon for Apple gives the following trades.

Buys (green triangles) and sells (red triangles) are plotted on the Apple closing price chart with Bollinger bands. At first glance, this looks to be a profitable strategy over this time period for this stock.

With a few trades in the last year, this looks to be a profitable strategy for Apple during this time period. To be sure, we take the previously calculated trade information and determine the profits from actually trading this way. This is done in the function below.

Applying this function to the previously generated trades shows an average return per trade of 6.3% and a total profit for trading one share of $21.76. In this particular case, you would have been better off (and probably taken less risk) by just buying one share of Apple and holding it for the entire time period. Having perfect information on the price over the time period allows us to see this. However, what if Apple had not experienced a huge run-up near the end of this time period? That is, what if the chart looked more like this:

It’s unlikely (albeit ideal) that this would have happened right after our last trade point which would maximize our profit but it could have happened almost any time after that point and the story would be the same: buying the stock would have produced a worse return than trading the Bollinger band crosses. The main point is that we don’t get to know what will happen to the stock price going forward so it’s not wise to discount a trading strategy just because something did happen since it didn’t have to happen. (That being said, I’m not promoting trading on technical analysis alone [or at all] I’m personally a long-term buy and hold investor myself.)

One last thing to note, pertaining to the benefits of the strategy, is that it generates cash flow for your portfolio which can be spent (or reinvested) right away. Holding stock over a long period of time (although a good idea) only generates any income on the sale of the stock (and the payment of dividends).

Backtesting

To test the strategy, a list of tickers in the S&P 500 is used to apply this technique to each stock in the index (filename in the code: sp500tickers.csv). This is done for the 1-, 5-, and 10-year periods as mentioned above. The results (profits/losses) are discussed in the Results section below. The code to run this over all of the tickers is also seen below. The charts which were created above are and the trades that would be executed are generated for each ticker. They can also be found in the GitHub repository for this project along with the source code.

Results

Using the results.dat files generated by each of the three runs for the three time periods, the results of the backtesting can be analyzed. For starters, it would be interesting to know what percentage of the S&P 500 tickers were profitable when trading this strategy. To determine this, the number of profitable entries in the dataset (profit column > 0) is divided by the total number of rows:

Doing this for all three time periods shows that the strategy is profitable 98.98% of the time in one year, 84.71% of the time in 5 years, and 90.54% of the time in 10 years. These results filter out the 0 profit tickers as these were likely tickers where there were no trades or where data was not available from Yahoo Finance.

It’s nice to see that most of the index was filled with winners when trading this way but it is also interesting to look at which stocks were the losers. This can be done via:

Losers in one year:

Losers in five years:

Losers in 10 years:

Chances are that choosing good stocks out of the S&P 500 will result in profits. The next thing I did was determine which stocks had the most profit per trade

and the most profit based on the total profit over the time period divided by its starting price which would give some idea of a total percentage return (not a great metric here).

When looking at the stock prices of these top 5 tickers of these periods, they seemed to be the stocks that had the largest run-up in price. This makes sense but it’s almost useless when determining which stocks to use for this strategy (which was my goal when looking at this analysis) since we don’t get to know which stocks will experience the best price increases in the next 1-10 years. Therefore I will exclude the results from this post.

For the sake of keeping the post short, I won’t do a more in-depth analysis. However, if looking for stocks to use to trade this strategy a person could consider different technical indicators, trading statistics (e.g. high/low volume, high/low beta), and/or different fundamentals (e.g. revenue growth, cash on hand, quick/current ratio) to determine if there is some link between performance using this strategy and these indicators. That being said, choosing a well-diversified mix of blue-chip stocks (for capital security) might lead to a reasonable amount of cash flow for your portfolio as well.

Conclusion

In this post, I have presented Bollinger bands, explained a simple strategy based on prices “breaking” bands, provided source code, backtested the algorithm, and gave a brief discussion of the results. It was shown that in most cases the strategy would have been profitable over one, five, and ten years. It is important to remember that past performance is not an indicator of future performance and great caution should be taken when investing money in this way as technical strategies such as this can be very risky (especially if they’re simple). However, Bollinger bands can be used as part of a larger trading strategy (or on their own if you’re willing to take the risk) and can easily be automated with brokers that provide trading APIs. Hopefully after reading this post Bollinger bands are at least one more tool for your toolkit that you might consider using in the future.

Full Code

bollingerbot.py

analysis.py

Leave a Reply

Your email address will not be published. Required fields are marked *