Backtesting Gann Square of 9 Intraday Strategy Using Python and Zerodha API

Step 1 - Determining Reliance's Last Traded Price (LTP) at 9:25 AM Daily

However, our objective isn’t to collect OHLC data and volume for each day. We specifically require the exact Last Traded Price (LTP) of Reliance at 9:25 AM. How can we obtain this information?

Instead of focusing on daily data, we need to narrow our search to the minute timeframe. Nevertheless, scanning the entire minute timeframe database would impose substantial processing demands on both Zerodha’s servers and our own system.

So, what’s the optimal solution in this scenario? In any case, the output above provides dates when Reliance was traded. If you perform further actions on this data, such as –

				
					zap = zap[["date"]]
zap
				
			
It will just truncate all other columns except column –
				
						date
0	2019-01-01 00:00:00+05:30
1	2019-01-02 00:00:00+05:30
2	2019-01-03 00:00:00+05:30
3	2019-01-04 00:00:00+05:30
4	2019-01-07 00:00:00+05:30
...	...
1159	2023-09-04 00:00:00+05:30
1160	2023-09-05 00:00:00+05:30
1161	2023-09-06 00:00:00+05:30
1162	2023-09-07 00:00:00+05:30
1163	2023-09-08 00:00:00+05:30
1164 rows × 1 columns
				
			

From the word 1164 rows, It is easy to assume that there are 1164 trading days where Reliance has been traded. Now, the trick is We will convert this dates to datetime function and add 9:25:00 to it. Right now the time is set at 00:00:00 because there were no time given.

  • So our starting point will be date + “9:25:00”
  • And, the ending point will be date + “9:26:00”

So basically We are asking for the data of that exact 1 minute.

It will be the most optimized server request. In the world of backtesting, the more optimization on the server request, the more better it is in terms of processing.

Now, lets iterate this theory on each day from each row in the zap dataframe that we got from earlier –

				
					for index, row in zap.iterrows():
    # Extract the date from the 'date' column
    date = row['date']
    
    # Set the fixed time to 09:25am
    time = datetime.time(9, 25)
    
    # Combine the date and time
    from_date = datetime.datetime.combine(date, time)
    
    # Calculate the ending date as from_date + 1 minute
    ending_date = from_date + datetime.timedelta(minutes=1)
    
    # Format from_date and ending_date as strings in "yyyy-mm-dd HH:MM:SS" format
    from_date_str = from_date.strftime("%Y-%m-%d %H:%M:%S")
    
    data = kite.historical_data(token, from_date_str, from_date_str, "minute")
 
    
    if len(data) == 1:        
        # Assign the values to new columns in the DataFrame
        zap.at[index, 'cmp'] = data[0]['open']


zap
				
			
The output will be –
				
					date	cmp
0	2019-01-01 00:00:00+05:30	1056.60
1	2019-01-02 00:00:00+05:30	1058.40
2	2019-01-03 00:00:00+05:30	1041.30
3	2019-01-04 00:00:00+05:30	1028.90
4	2019-01-07 00:00:00+05:30	1049.05
...	...	...
1159	2023-09-04 00:00:00+05:30	2421.05
1160	2023-09-05 00:00:00+05:30	2417.25
1161	2023-09-06 00:00:00+05:30	2428.50
1162	2023-09-07 00:00:00+05:30	2419.00
1163	2023-09-08 00:00:00+05:30	2433.05
1164 rows × 2 columns
				
			

As a matter of convenience, We have used the openprice on the minute candle of 9:25:00 of each day as LTP.

In case, there were no trade in that minute candle, then it will return na. Although it is Reliance and it is an extremely liquid candidate, the chance of it not being traded on that time Is next to impossible. But, still as a standard practice, scan for the na values and drop those rows.

				
					zap.dropna(inplace=True)
				
			
This is drop the rows with na value immediately.

Step 2 - Generating Trade Signals

Now, Lets define a quant size i.e. Let’s say the quant size is 500000 and Reliance is trading at 1000. It will take 500000/1000=500 quantity of Reliance.

Right now, as of the time of making this tutorial, SEBI is allowing 4x leverage for intraday trades for the equities. So, with a 10L fund, One can take trade worth of 40L. And with quant size of 5L, there can be simuntaneos 8 trades running without any margin constraint.

Now, Let’s get the levels from the calculate_gann_values() function. 

We will put the cmp of each day and get the levels.

				
					result = calculate_gann_values(cmp)

buy_above=result["buy_above"]
buy_target=result["buy_target"][0]
buy_sl=result["sell_below"]

sell_below=result["sell_below"]
sell_target=result["sell_target"][0]
sell_sl=result["buy_above"]
				
			
The quant size can be declared like –
				
					quant_size = 500000
quantity = math.floor(quant_size / cmp)
				
			
Now, every day we get two trade signals. One for buy. One for sell. Let’s define them like –
				
					# Create JSON object for 'sell' strategy
    sell_data = {
        "strategy": "GannSq9",
        "symbol": "RELIANCE",
        "exchange": "NSE",
        "quantity": math.floor(quant_size / cmp),
        "entry_time": 0,
        "strategy_type": "sell",
        "entry_price": sell_below,
        "target": sell_target,
        "stoploss": sell_sl,
        "strategy_start": date + " 09:25:00",
        "strategy_end": date + " 15:10:00",
        "results": {
        }
    }
    
    # Create JSON object for 'buy' strategy
    buy_data = {
        "strategy": "GannSq9",
        "symbol": "RELIANCE",
        "exchange": "NSE",
        "quantity": math.floor(quant_size / cmp),
        "entry_time": 0,
        "strategy_type": "buy",
        "entry_price": buy_above,
        "target": buy_target,
        "stoploss": buy_sl,
        "strategy_start": date + " 09:25:00",
        "strategy_end": date + " 15:10:00",
        "results": {
        }
    }
				
			

These two JSONs have all the data that is needed to backtest. We can call these JSONs as an input for our backtesting function. So, after we save our signals into a stream of JSON data like this, We can put each of those signals represented by each of the JSOn into backtesting function and get the results.

Step 3 - Generating JSON Dataset of Trade Signals

So, Let’s create the JSON dataset of the buy signals and sell signals by iterating this logic into each day i.e. each row of the dataframe.

				
					import math
import datetime

quant_size = 500000


data_list = []  # Initialize an empty list to store the JSON objects

# Iterate through each row in the DataFrame
for index, row in zap.iterrows():
    # Extract the date from the 'date' column
    date = row['date'].strftime("%d-%m-%Y")
    cmp = row['cmp']
    
    result = calculate_gann_values(cmp)
    buy_above=result["buy_above"]
    buy_target=result["buy_target"][0]
    buy_sl=result["sell_below"]

    sell_below=result["sell_below"]
    sell_target=result["sell_target"][0]
    sell_sl=result["buy_above"]
    
    # Create JSON object for 'sell' strategy
    sell_data = {
        "strategy": "GannSq9",
        "symbol": "RELIANCE",
        "exchange": "NSE",
        "quantity": math.floor(quant_size / cmp),
        "entry_time": 0,
        "strategy_type": "sell",
        "entry_price": sell_below,
        "target": sell_target,
        "stoploss": sell_sl,
        "strategy_start": date + " 09:25:00",
        "strategy_end": date + " 15:10:00",
        "results": {
        }
    }
    
    # Create JSON object for 'buy' strategy
    buy_data = {
        "strategy": "GannSq9",
        "symbol": "RELIANCE",
        "exchange": "NSE",
        "quantity": math.floor(quant_size / cmp),
        "entry_time": 0,
        "strategy_type": "buy",
        "entry_price": buy_above,
        "target": buy_target,
        "stoploss": buy_sl,
        "strategy_start": date + " 09:25:00",
        "strategy_end": date + " 15:10:00",
        "results": {
        }
    }

    # Append both JSON objects to the data_list
    data_list.extend([sell_data, buy_data])

# The 'data_list' now contains JSON objects for each row with 'sell' and 'buy' strategies
data_list
				
			
The ‘data_list’ is a list which now contains JSON objects for each row with ‘sell’ and ‘buy’ strategies
				
					[{'strategy': 'GannSq9',
  'symbol': 'RELIANCE',
  'exchange': 'NSE',
  'quantity': 473,
  'entry_time': 0,
  'strategy_type': 'sell',
  'entry_price': 1056.25,
  'target': 1048.66,
  'stoploss': 1064.39,
  'strategy_start': '01-01-2019 09:25:00',
  'strategy_end': '01-01-2019 15:10:00',
  'results': {}},
 {'strategy': 'GannSq9',
  'symbol': 'RELIANCE',
  'exchange': 'NSE',
  'quantity': 473,
  'entry_time': 0,
  'strategy_type': 'buy',
  'entry_price': 1064.39,
  'target': 1072.02,
  'stoploss': 1056.25,
  'strategy_start': '01-01-2019 09:25:00',
  'strategy_end': '01-01-2019 15:10:00',
  'results': {}},
 {'strategy': 'GannSq9',
  'symbol': 'RELIANCE',
  'exchange': 'NSE',
  'quantity': 472,
  'entry_time': 0,
....
....
				
			

Each of the 1164 rows representing 1164 days will create 2*1164=2328 signals I.e. 2328 JSON objects.

Now, We will construct priceaction_backtester() function which will take these JSON data as input and output with with another JSON that will contain the results of backtesting.

So, Lets pick out one JSON to construct the function upon it.

				
					{'strategy': 'GannSq9',
  'symbol': 'RELIANCE',
  'exchange': 'NSE',
  'quantity': 473,
  'entry_time': 0,
  'strategy_type': 'sell',
  'entry_price': 1056.25,
  'target': 1048.66,
  'stoploss': 1064.39,
  'strategy_start': '01-01-2019 09:25:00',
  'strategy_end': '01-01-2019 15:10:00',
  'results': {}}
				
			
Post a comment

Leave a Comment

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

×Close