A Rookie Guide to Getting Started with Backtesting in Python!

A Rookie Guide to Getting Started with Backtesting in Python!
Shreyans Jain's photo
Shreyans Jain

Published on Nov 24, 2021

6 min read

Subscribe to our newsletter and never miss any upcoming articles

Every Algorithmic trading rookie starts to wonder what Backtesting is and how it is implemented, yet many people ignore it based on their individual biases. But let me tell you frankly, it is a crucial step in building your algo trading robot.

To put it simply, your idea or strategy can be great in your head, but data never lies, and Backtesting is merely getting an indication as to whether your system will likely work or not.

To give you another example, think about the time when you have to decide which Mutual Fund or PMS service to invest in; you will always look at 3-Year, 5-Year Returns to arrive at a decision simply because data speaks for itself and "Sab Mutual Funds Sahi Nahi Hai"

Let's start Learning the Backtesting framework by creating and backtesting a simple strategy. We will be demonstrating a straightforward strategy to give a notion and introduce the library; the real-world strategy is much more complex. It needs various other factors to be considered, but the article is aimed at beginners.

Our sample strategy

  1. Quantity = 100
  2. Start Cash = 1,00,000
  3. Commission = 0.2%
  4. Position = Long
  5. Frequency = Daily
  6. Start Date = 1st Oct, 2021
  7. End Date = 15th Nov, 2021
  8. Buy Condition: When 21 RSI crosses above 30 and 50 SMA crosses above 100 SMA
  9. Sell Condition: When 21 RSI crosses below 70

While there are various open-source Python backtesting libraries, we have chosen backtrader for this article. Every library has its pros and cons; if you want to check out some more options, we wrote this article a while back; check it out.

-> Installing backtrader -

pip install backtrader

-> Installing Yahoo Finance for Getting Data -

pip install yfinance

yfinance needs no introduction in the algo-trading world; everyone starts from this library and probably one of the most straightforward libraries to download global stock market data. We will be uploading our data in this example, but we want to let you know that yfinance is also a worthy option.

-> Now let's import the Libraries-

import backtrader as bt
import yfinance as yf
from datetime import datetime

-> Creating class where we will define our strategy -

class firstStrategy(bt.Strategy):
    def __init__(self):
        # initializing rsi, slow and fast sma
        self.rsi = bt.indicators.RSI(self.data.close, period=21)
        self.fast_sma = bt.indicators.SMA(self.data.close, period=50)
        self.slow_sma = bt.indicators.SMA(self.data.close, period=100)
        self.crossup = bt.ind.CrossUp(self.fast_sma, self.slow_sma)

    def next(self):
        if not self.position:
            if self.rsi > 30 and self.fast_sma > self.slow_sma:  # when rsi > 30 and fast_sma cuts slow_sma
                self.buy(size=100)  # buying 100 quantities 
        else:
            if self.rsi < 70:  # when rsi is below 70 line
                self.sell(size=100)  # selling 100 quantities

-> Now let's look at each function in Class firstStrategy separately-

  • def __init__(self)
    def __init__(self):
          # initializing rsi, slow and fast sma
          self.rsi = bt.indicators.RSI(self.data.close, period=21)
          self.fast_sma = bt.indicators.SMA(self.data.close, period=50)
          self.slow_sma = bt.indicators.SMA(self.data.close, period=100)
          self.crossup = bt.ind.CrossUp(self.fast_sma, self.slow_sma)
    

In this Function, we define the required elements for our strategy -

self.rsi contains our rsi indicator built on close data, and it's period = 21 days. self.fast_sma - It is the Simple Moving Average indicator with 50 days period. self.slow_sma - It is the Simple Moving Average indicator with 100 days period. self.crossup - It is when 50 days(fast SMA) cross above 100 days(slow SMA).

-> Now let's look at the other function in class firstStrategy -

def next(self):
    if not self.position:
        # BUYING Condition
        if self.rsi > 30 and self.fast_sma > self.slow_sma:  # when rsi > 30 and fast_sma cuts slow_sma
            self.buy(size=100)  # buying 100 quantities of equity
    else:
        # SELLING Condition
        if self.rsi < 70:  # when rsi is below 70 line
            self.sell(size=100)  # selling 100 quantities of equity

In the above function, we create our strategy from the variable we created in the init function.

If we have not taken a position we will buy 100 stocks based on the condition in our strategy and similarly if the position is already taken we will sell 100 stocks the stocks based on the condition provided.

-> Now, we have created our strategy for Backtesting. Let's look at other requirements for Backtesting the strategy.

-> Variable for our starting cash

startcash = 100000

-> Create an instance of cerebro

cerebro is the brain of backtrader library.

cerebro = bt.Cerebro() # It is the main class in backtrader.

To read more about Cerebro, Click here.

-> Adding our strategy

cerebro.addstrategy(firstStrategy) # adding strategy in Cerebro engine

-> Uploading the CSV file containing OHCLV data for backtesting in Google Colab.

from google.colab import files
uploaded = files.upload()

Note 1:- In the above code, we add CSV files from our local machine.

Note 2:- You can skip the above code block when running on the local machine.

We have the sample data for HDFCBANK.NS which you can download from here if needed.

-> Getting data for backtesting our strategy

# Get HDFCBANK data from Yahoo Finance.
# ----- Use below code to fetch data from Yahoo Finance CSV  -------
data = bt.feeds.YahooFinanceCSVData(
    dataname="HDFCBANK.NS.csv",
    fromdate=datetime(2020,11,1),
    todate =datetime(2021,11,1))

Note 3:- The field dataname should be replaced with the data file path if you are running this on the local machine.

-> Add the data to Cerebro

cerebro.adddata(data)

-> Setting the broker commission to 0.2%

cerebro.broker.setcommission(commission=0.002)

-> Setting our desired cash start

cerebro.broker.setcash(startcash)

-> Run over everything

cerebro.run()

-> Get final portfolio Value and pnl and printing them

portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash

# Printing out the final result
print('Final Portfolio Value: ${}'.format(portvalue))
print('P/L: ${}'.format(pnl))

Output ->

Screen Shot 2021-11-21 at 6.09.41 PM.png

Note:- The currency in the output above is in USD. We can convert the currency to INR by using appropriate conversion rates.

-> There are various strategies, methods to analyze our output. We will discuss them in our upcoming blogs.

Click here to get complete code for reference.

That's it. I hope you like the content.

Constructive criticism is appreciated.

Feel free to reach out to me on Linkedin. If you have any suggestions about the blog, you can use the Feedback widget on the right hand of your screen, and if you wish to Contact Us, you can fill in the form there.

P.s- We hope to connect with you via LinkedIn or any social media platform to discuss ideas, and feel free to subscribe to the newsletter at the top of this article. You could also Buy me a Book to show some appreciation. 😇

 
Share this

Impressum

Any information present on this blog does not constitute any form of investment advice or recommendation by Trade With Python.