It’s been a while since machine trading got the trend in trading markets such as stock, forex, options and especially in crypto currency markets for individual traders. I’d like to highlight how easy we can do backtesting in simple Python coding and leverage the results to find the next trading opportunities. We can utilize the results and evaluate your trading strategy periodically. Backtesting assesses the viability of your trading strategy by discovering how it would play out using historical data. So it’s quite exciting and crucial for every user to do own backtesting.
There are several Python libraries for backtesting. We have to consider which one is adequate for the purpose however I will introduce backtesting.py today because it requires simple coding with OHLCV data in Pandas DataFrame, which looks straightforward.
In this library what you need are OHLC (or OHLCV) formatted DataFrame and a strategy function you will define accordingly. You can get the result easily in stats information and visualization. Let’s take a look.
Backtesting.py is a lightweight, fast, user-friendly, intuitive, interactive, intelligent backtesting tool with a handful of tutorials. It supports time-series data with certain intervals such as OHLCV data and it is library-agnostic to create technical indicators for backtestings. Also it has built-in visualization and optimization. I recommend using backtesting.py for beginners for both in Python and backtesting.
PyAlgoTrade is a rich-featured trading and backtesting tool that supports an event driven algorithmic trading. The library supports market, limit, stop and stopLimit orders with any type of time-series data in CSV format like Yahoo! Finance, Google Finance, Quandl however it seems to have the limited technical indicators for use at a glance.
Backtrader is a trading and backtesting tool that supports an event driven algorithmic trading with Interactive Brokers, Oanda v1, VisualChart and also with the external third party brokers (alpaca, Oanda v2, ccxt). You can use a lot of technical indicators and Ta-Lib. This library is amazing but looks complicated a little.
backtesting.py will be your first choice if you need only backtesting feature in Python library. We need to do two things 1) Prepare your data 2) Write a strategy class and boom 3) Run your backtesting. We have to be careful that past performance does not mean indicative of future results, but a strategy that proves itself resilient in a multitude of market conditions can remain just as reliable in the future.
I’m using backtesting.py version 0.1.7 and it’s not the latest version 0.2.x as of writing (Sep 2020). You need to install backtesting.py and pandas libraries for doing the same in this article. Sorry I didn’t cover how to install those libraries but you can just use pip or pip3 commands in your environment. I recommend using a data science package such as Anaconda.
Prepare your data
First of all we need data. I use Pandas DataReader or ccxt library to retrieve financial data. Here’s an example how to obtain DawJones daily OHLCV data by using Pandas DataReader.
Please make sure that you’ve obtained the column names exactly the same “Open” “High” “Low” “Close” and “Volume” (Volume is optional for backtesting.py library). Each column’s first character must be capital letter for this library to recognize it. DataFrame can contain other columns but those four columns “Open” “High” “Low” “Close” are must to have. DataFrame should ideally be indexed with a datetime index (convert it with pd.to_datetime() function), otherwise a simple range index will do.
Write a strategy class
The method init() is invoked before your strategy is run. Within it, one ideally precomputes in efficient, vectorized manner whatever indicators and signals the strategy depends on. Ta-Lib can be used to precompute various technical indicators.
The method next() is iteratively called by a backtesting instance, once for each data point (DataFrame row), simulating the incremental availability of each new full candlestick bar.
Here’s an example how to code EMA strategy overriding the Strategy class. Init function precomputes 5 data points EMA (the faster moving average) and 10 data points EMA (the slower moving average) and the strategy signals when a golden cross or a dead cross appears. Your data is passed to the strategy and becomes available as an instance variable self.data. If you need to refer “Close” column in your data you can write self.data.Close in your class.
We declare and compute indicators indirectly by wrapping them in self.I(). The wrapper is passed to the function (in this case EMA_Backtesting) along with any arguments. So the idea is to control arguments as class variables in the overrided strategy.
Please note that defining class variable (in this case two EMA lags) is important. We can utilize these class variable afterwards for optimizing the parameters to maximize the result. For example the optimizer can find the best parameters to maximize its return in the period or SQN value, which can be specified attribute of Series . If you use some variables in your strategy it’s better to wrap those parameters as class variables as much as possible in first place. You can call self.buy() and self.sell() to place a simple order in your strategy class.
Run your backtesting
We’re ready to run backtesting and get results with preparing data and the defined strategy class. Here’s the example code and the result I ran for Bitcoin daily OHLC data. First initiate a Backtest instance with the prepared DataFrame object and the defined strategy, cash, commission, a few options you’re like to pass to. Now you’re able to run your backtesting with Run() function.
This result looks quite good with more than 8000% return for 5 years Bitcoin tradings. But wait there would have been more better combination of the strategy parameters than we passed. We hardcoded the lags 5 data point EMA and 10 data point EMA for the cross-over strategy. Now what we have to do is to find the best parameters for n1 (the faster moving average) and n2 (the slower moving average) to maximize the return.
You can call optimize function of the instance to search an optimal combination using the built-in parallel exhaustive search. Returns result in pd.Series of the best run. The best result maximizing Return is n1=6 and n2=16 combination, which profits 10175% returns for this case. Sweet.
Finally you can visualize all backtesting insights in an interactive image with Boker. You can call run function of the instance and you will get a similar image below.
To wrap up backtesting.py is one of the powerful tools for backtesting and it is lightweight, easy-to-use if you use Python. It is library agnostic so you can use financial libraries for creating your signals and technical indicators easily. It has the built-in visualization and optimization features to get great insights for trade analysis and finding opportunities.
I push some sample Jupyter notebook of backtesting on github. I hope this does help for you.