Bollinger Bands Backtest using Python and REST API | Part 1

Welcome to this tutorial on a Bollinger Bands strategy using REST API and Python. We will be using a Jupyter notebook to do a simple backtest of a strategy that will trigger trades based on the lower band of the Bollinger Bands indicator.

One important note to consider before jumping into the material is that backtested results are hypothetical and may not reflect the true performance of a system, as past performance is not indicative of future results.  Additionally, the backtester we will create today is meant to give us a quick understanding of a strategy’s behavior overall and not to simulate every aspect of a strategy’s execution (such as transaction costs, market impact, price slippage, etc).

Import the Packages and Libraries

To get started on building the strategy, we begin by importing the necessary packages, each of which can quickly be installed using the “pip install” command from your command prompt if you don’t already have them.  We will be using fxcmpy to pull historical prices, pandas and numpy for analyzing our time series data, pyti for quick access to technical indicators, matplotlib for visualizing our results, and lastly datetime organize the date and time in an easy to read format.

import fxcmpy
import pandas as pd
import numpy as np
import datetime as dt
#import funcs
from pyti.bollinger_bands import upper_bollinger_band as ubb
from pyti.bollinger_bands import middle_bollinger_band as mbb
from pyti.bollinger_bands import lower_bollinger_band as lbb
from pyti.bollinger_bands import percent_bandwidth as percent_b
#import plots and styling
import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib import style
style.use('ggplot')
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

You will need access to REST API. If you do not have it set up yet, you can use this link to access. Once complete, we will connect to FXCM’s REST API which will allow us to stream historical prices, live prices, and place live trades. We will use a config file to store the account access token for a secure connection to the API. To learn how to create a token, watch this tutorial.

socket = fxcmpy.fxcmpy(config_file = 'fxcm.cfg')
print (socket.get_instruments_for_candles())

Now we know we are connected to the demo account using REST API. When we print it out, it will look something like this:

Great! Now that we have confirmed that we are connected to the rest API, we will retrieve the historical data.

data = socket.get_candles(instrument = 'EUR/GBP', period = 'D1', start = dt.datetime(2016,1,1), end = dt.datetime(2018, 6, 10))

Define the Strategy Variables

The next step is to define the variables we will need for the strategy. We will define the four parts of the Bollinger Bands indicator (Upper band, Mid Band, Lower Band, and Percent B) using the Ask Close rate for the last 20 periods.

data['upper_band'] = ubb(data['askclose'], period = 20)
data['mid_band'] = mbb(data['askclose'], period = 20 )
data['lower_band'] = lbb(data['askclose'], period = 20 )
data['percent_b'] = percent_b(data['askclose'], period =20)
data

All we need to do is enter data below and we can visualize the information in a dataframe so that it is in an easy to read format:

Now we can take this dataframe and plot this on a graph to visualize our results using this code:

fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(111, xlabel = 'Date',ylabel='Close')

data['askclose'].plot(ax=ax1, color='r', lw=1)
data['upper_band'].plot(ax=ax1, color = 'b', lw= 1)
data['mid_band'].plot(ax=ax1, color = 'g', lw= 1)
data['lower_band'].plot(ax=ax1, color = 'y', lw= 1)

We can isolate the percent B line and plot it on a graph by using this code:

band_fig = plt.figure(figsize=(12,8))
ax2 = band_fig.add_subplot(111,  ylabel='%B')
data['percent_b'].plot(ax=ax2, color = 'b', lw= 1)

And our output looks like this:

The important variable that we will want to consider in this strategy is the %B line as we will be entering a long position based on this. Commonly, traders will enter a long trade when Percent B is less than 20% or take a short trade when Percent B is above 80%. For our strategy, the logic is to enter long when Percent B is greater than .2 or 20%. Note that according to our strategy, when %B is above .80 then price is nearing the upper band and when %B below .20 then price is nearing the lower band.

We can backtest one of two ways: The first option is to run a backtest that will print out all the rates that you either bought or sold the price. (For example, we sold at 1.2242 when percent B was over 80%).  We would then take those rates and calculate the profit or loss by taking the difference in pips between the buy and sell rates. This method would be useful when we want to see the actual rates of the signals and the corresponding profit or loss of each trade.

On the other hand, method two is somewhat of the “shorthand” version and takes less steps. With this method, you would just print out a dataframe showing when a signal was produced. From there, you can calculate the number of pips that the market moved during the day and record the running profit / loss for the strategy.

In today’s example, we will be using the second method but stayed tuned for the next edition of this tutorial that will be published with a tutorial for the first method of backtesting.

As mentioned before, we will enter a long when Percent B is greater than .2 or 20%. The code is simple and looks like this:

data['signal'] = np.where((data['percent_b'] <=.2),1,0)
data['position'] = data['signal'].diff()
data

The output is a dataframe that includes a column for Signal. If there was a signal, you will see “1”, if not, you will see a “0”.

Now that we have the signal printed, we can take it step further to add the total profit or loss in monetary value.  We can add the total column using the following code:

pip_cost = 1
lot_size = 10

returns = 0

# Gets the number of pips that the market moved during the day
data['difference (pips)'] = (data['askclose'] - data['askopen']) * 100
#df['p/l'] = df['difference'] * pip_cost * lot_size

# Calculates the daily return while a position is active
# 'Total' column records our running profit / loss for the strategy
CountPL=False
for i, row in data.iterrows():
  if CountPL==True:
    returns += (row['difference (pips)'] * pip_cost * lot_size)
    data.loc[i,'total'] = returns
  else:
    data.loc[i,'total'] = returns

  if row['signal'] == 1:
    CountPL=True
  else:
    CountPL=False
data

Visualize the Backtest Results

Now let’s take the data from the dataframe and plot the profits or losses on a graph. We need to import matplotlib if we have not already. Then create the settings for the graph including the markers for entry and exit points and the colors.

import matplotlib.pyplot as plt
%matplotlib inline

fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(111, ylabel='Profits in $')

data['total'].plot(ax=ax1, color='r', lw=1.)

# Placing markers for our position entry
ax1.plot(data.loc[data.position == 1.0].index,
    data.total[data.position == 1.0],
    '^', markersize=10, color='m')

# Placing markers for our position exit

ax1.plot(data.loc[data.position == -1.0].index,
    data.total[data.position == -1.0],
    'v', markersize=10, color='k')

plt.show()


We can see the profits of the Bollinger band strategy we created in dollars. We triggered trades using the Bollinger band indicator on the EUR/GBP. You can use this same method to easily backtest other strategies using various indicators. Be sure to check out the functions of the fxcmpy Python wrapper to make backtesting with Python even easier.

Want to access the full code? Check it out on our GitHub page.


Risk Warning: The FXCM Group does not guarantee accuracy and will not accept liability for any loss or damage which arise directly or indirectly from use of or reliance on information contained within the webinars. The FXCM Group may provide general commentary which is not intended as investment advice and must not be construed as such. FX/CFD trading carries a risk of losses in excess of your deposited funds and may not be suitable for all investors. Please ensure that you fully understand the risks involved.