In Previous article, we collected past data of FX using OANDA REST API. This time, based on that data, we will describe the method of strategic evaluation of investment automatic trading bots. TL; DR → backtesting documentation
It's just ↓
pip install backtesting
It is assumed that the DB called ʻusd_jpy_4hcontains the data acquired in [Previous article](https://qiita.com/k_0/items/e36162b9c623d27583c9). We also use
talib`, a package for technical analysis. This can also be easily dropped with pip.
The talib documentation is here [https://mrjbq7.github.io/ta-lib/doc_index.html)
import talib as ta
import pandas as pd
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import db_access
def get_raw_data(query):
engine = db_access.get_engine()
return pd.read_sql(query, engine)
def get_macd(close, fastperiod=6, slowperiod=13, signalperiod=4):
macd, macdsignal, macdhist = ta.MACD(close, fastperiod=fastperiod, slowperiod=slowperiod, signalperiod=signalperiod)
return macd, macdsignal, macdhist
query = "select distinct * from usd_jpy_4h where datetime between '2019/09/01' and '2020/03/01' order by datetime"
raw_data = get_raw_data(query)
df = raw_data[["datetime","open","high","low","close","volume"]].copy()
df.columns = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Datetime'] = pd.to_datetime(df['Datetime'])
df = df.reset_index().set_index('Datetime')
class MacdStrategy(Strategy):
macd_fastperiod = 6
macd_slowperiod = 13
macd_signalperiod = 4
profit_pips = 50
loss_pips = 50
profit = profit_pips * 0.01
loss = loss_pips * 0.01
def init(self):
self.macd, self.signal, self.hist = self.I(get_macd, self.data.Close, self.macd_fastperiod, self.macd_slowperiod, self.macd_signalperiod)
def next(self):
if (crossover(self.signal, self.macd)):
self.buy(sl=self.data.Close[-1] - self.loss, tp=self.data.Close[-1] + self.profit)
elif (crossover(self.macd, self.signal)):
self.sell(sl=self.data.Close[-1] + self.loss, tp=self.data.Close[-1] - self.profit)
bt = Backtest(df, MacdStrategy, cash=100000, commission=.00004)
output = bt.run()
print(output)
output
Start 2019-09-01 20:00:00
End 2020-02-28 20:00:00
Duration 180 days 00:00:00
Exposure [%] 74.9074
Equity Final [$] 98647
Equity Peak [$] 101404
Return [%] -1.35297
Buy & Hold Return [%] 1.81654
Max. Drawdown [%] -2.7724
Avg. Drawdown [%] -0.518637
Max. Drawdown Duration 162 days 00:00:00
Avg. Drawdown Duration 21 days 18:00:00
# Trades 66
Win Rate [%] 50
Best Trade [%] 0.457344
Worst Trade [%] -0.68218
Avg. Trade [%] -0.0215735
Max. Trade Duration 10 days 00:00:00
Avg. Trade Duration 2 days 02:00:00
Expectancy [%] 0.275169
SQN -0.538945
Sharpe Ratio -0.0655224
Sortino Ratio -0.107821
Calmar Ratio -0.00778153
_strategy MacdStrategy
dtype: object
raw_data = get_raw_data(query)
df = raw_data[["datetime","open","high","low","close","volume"]].copy()
df.columns = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Datetime'] = pd.to_datetime(df['Datetime'])
df = df.reset_index().set_index('Datetime')
Here, the data in the DB is acquired and formatted for backtesting. In backtestin, I expect the column names to be'Open','High','Low','Close','Volume' (OK without it) in the pandas Dataframe. It is also recommended to add an index with the datetime type. https://kernc.github.io/backtesting.py/doc/examples/Quick%20Start%20User%20Guide.html
You bring your own data. Backtesting ingests data as a pandas.DataFrame with columns 'Open', 'High', 'Low', 'Close', and (optionally) 'Volume'. Such data is easily obtainable (see e.g. pandas-datareader, Quandl, findatapy, ...). Your data frames can have other columns, but these are necessary. DataFrame should ideally be indexed with a datetime index (convert it with pd.to_datetime()), otherwise a simple range index will do.
class MacdStrategy(Strategy):
macd_fastperiod = 6
macd_slowperiod = 13
macd_signalperiod = 4
profit_pips = 50
loss_pips = 50
profit = profit_pips * 0.01
loss = loss_pips * 0.01
def init(self):
self.macd, self.signal, self.hist = self.I(get_macd, self.data.Close, self.macd_fastperiod, self.macd_slowperiod, self.macd_signalperiod)
def next(self):
if (crossover(self.signal, self.macd)):
self.buy(sl=self.data.Close[-1] - self.loss, tp=self.data.Close[-1] + self.profit)
elif (crossover(self.macd, self.signal)):
self.sell(sl=self.data.Close[-1] + self.loss, tp=self.data.Close[-1] - self.profit)
The trading strategy is created by inheriting from backtesting import Strategy
.
This time, I am writing a program with a strategy of buying and selling when Macd, which is one of the technical indicators, crosses.
In investment strategies, there are many strategies that fire when one time series curve intersects another. You can easily describe the strategy by using crossover ()
.
https://kernc.github.io/backtesting.py/doc/backtesting/lib.html#backtesting.lib.crossover
There are many other useful features, so please take a look at the documentation.
stats = bt.optimize(
macd_fastperiod = [6, 8, 10, 12],
macd_slowperiod = [13, 19, 26],
macd_signalperiod = [4, 7, 9],
profit_pips = [30, 50, 70, 90],
loss_pips = [20, 30, 40, 50]
)
By using Backtest.optimize ()
, you can test the optimum value / combination for the variables used in the trading strategy.
Here, the value decided by hand is defined to some extent, but it may be defined in the form of [i for i in range (10)]
(although it seems to be heavy processing).
Optimal parameters can be confirmed in stats._strategy
after executing optimize ().
Recommended Posts