Backtest 01

This code backtests a long only strategy using the lower Bollinger Band for entry and mean for exit. It follows the value of one unit of capital over multiple positions. No consideration is given to position size, just all in. The entry/exit is made on the close price so the position column is shifted so the position is applied to following days prices. Lookback period (for the mean and std rolling windows) and threshold (for the number of stds deviation for entry) are set at the top of the script.

''' 
    Backtest of long only strategy using Bollinger Band(s).
    Follows subsequent value of 1 unit of capital in opening position.
'''
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

lookback = 20
threshold = 1.0

# Read data and calculate statistics/indicators

df= pd.read_csv('data/ADA.csv', header=0, index_col=0, parse_dates=True)
df['mean'] = df['close'].rolling(lookback).mean()
df['stdev'] = df['close'].rolling(lookback).std()
df['bband'] = df['mean'] - threshold * df['stdev']

# Position - calculated on day's close so applies to following days

df['entry'] = df['close'] < df['bband']   # condition - T/F
df['exit'] = df['close'] >= df['mean']    # condition - T/F

df['position'] = np.nan   # create and initialise the column

df.loc[df['entry'], 'position'] = 1   # set value according to condition
df.loc[df['exit'], 'position'] = 0    # set value according to condition

df = df.fillna(method='ffill')

# Returns

df['diff'] = df['close'] - df['close'].shift(1)
df['daily_returns'] = df['diff'] / df['close'].shift(1)

# position was entered at END of previous day
df['strategy_returns'] = df['position'].shift(1) * df['daily_returns']

df['cumret'] = (df['strategy_returns'] + 1).cumprod()

# Plot

df.cumret.plot(label='ADAUSDT', figsize=(12, 6))
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.show()

Here’s a slightly different version using BBANDS from ta-lib

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import talib.abstract as ta

lookback = 20
threshold = 1.0

# Read data and calculate statistics/indicators

df= pd.read_csv('data/ADA.csv', header=0, index_col=0, parse_dates=True)
bb = ta.BBANDS(df, timeperiod=lookback, nbdevdn=threshold)
df['mean'] = bb.middleband
df['bband'] = bb.lowerband

# Position - calculated on day's close so applies to following days

df['entry'] = df['close'] < df['bband']   # condition - T/F
df['exit'] = df['close'] >= df['mean']    # condition - T/F

df['position'] = np.nan   # create and initialise the column

df.loc[df['entry'], 'position'] = 1   # set value according to condition
df.loc[df['exit'], 'position'] = 0    # set value according to condition

df = df.fillna(method='ffill')

# Returns

df['diff'] = df['close'] - df['close'].shift(1)
df['daily_returns'] = df['diff'] / df['close'].shift(1)

# position was entered at END of previous day
df['strategy_returns'] = df['position'].shift(1) * df['daily_returns']

df['cumret'] = (df['strategy_returns'] + 1).cumprod()

# Plot

df.cumret.plot(label='ADAUSDT', figsize=(12, 6))
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.show()

Leave a Reply

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