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

Passing List of Strategy JSONs to Price Action Backtester Function

Now, As our priceaction_backtester() function is constructed, lets pass each of the JSON objects into the function and get the output as JSON Objects. Now, We will make a new list where we will store all the results.
				
					output_list=[]

for data in data_list:      
    result = priceaction_backtester(data)
    output_list.append(result)

output_list
				
			
It is a basic set of code where we are iterating each of the JSON from the list and passing it to priceaction_backtester() function and store the JSON object in a new list. The output list of JSON looks like –
				
					[{'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_state': 'Squareoff',
   'strategy_start': datetime.datetime(2019, 1, 1, 9, 25, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'strategy_end': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'entry_time': datetime.datetime(2019, 1, 1, 9, 54, tzinfo=tzoffset(None, 19800)),
   'entry_price': 1056.25,
   'exit_price': 1059.75,
   'exit_time': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=tzoffset(None, 19800)),
   'strategy_pl': -1655.45}},
 {'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_state': 'No Entry',
   'strategy_start': datetime.datetime(2019, 1, 1, 9, 25, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'strategy_end': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'entry_time': 0,
   'entry_price': 1064.39,
   'exit_price': 0,
   'exit_time': 0,
   'strategy_pl': 0}},
 {'strategy': 'GannSq9',
  'symbol': 'RELIANCE',
  'exchange': 'NSE',
  'quantity': 472,
.....
.....
.....
				
			
As You can notice the results is stored into the results key of each JSON
				
					'results': {'strategy_state': 'Squareoff',
   'strategy_start': datetime.datetime(2019, 1, 1, 9, 25, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'strategy_end': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'entry_time': datetime.datetime(2019, 1, 1, 9, 54, tzinfo=tzoffset(None, 19800)),
   'entry_price': 1056.25,
   'exit_price': 1059.75,
   'exit_time': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=tzoffset(None, 19800)),
   'strategy_pl': -1655.45}
				
			

There can be four types of strategy states: “No Trigger,” “StopLoss,” “Target,” and “Squareoff.”

If a trade is triggered, then it will check if the trade has hit the stop loss. If the stop loss has not hit, then it will check if the target is hit. If neither the stop loss nor the target is hit, then it will get auto squared off at the end of the strategy time, which is 15:10.

In short, if the trade is triggered:

  • If the stop loss or target is hit, it means the trade has hit either the stop loss or the target.
  • If the target is hit earlier than the stop loss, it means the trade has hit the target.
  • If the stop loss is hit earlier than the target, then the trade has hit the stop loss.

The “results” variable contains the results of each signal, indicating in which state the trade ended (No Trigger, StopLoss, Target, or Squareoff).

Like for the case of No Trigger, it is like –

				
					{'strategy_state': 'No Entry',
   'strategy_start': datetime.datetime(2019, 1, 1, 9, 25, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'strategy_end': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'entry_time': 0,
   'entry_price': 1064.39,
   'exit_price': 0,
   'exit_time': 0,
   'strategy_pl': 0}
				
			

Normalizing the JSON data

Now, if we convert this list of JSON to rows of a new pandas dataframe it will become messy because what will happen to the values which are inside the results key. It goes to the second level right? So will be there another database under each such values?

Why not normalize 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': {'strategy_state': 'Squareoff',
   'strategy_start': datetime.datetime(2019, 1, 1, 9, 25, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'strategy_end': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'entry_time': datetime.datetime(2019, 1, 1, 9, 54, tzinfo=tzoffset(None, 19800)),
   'entry_price': 1056.25,
   'exit_price': 1059.75,
   'exit_time': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=tzoffset(None, 19800)),
   'strategy_pl': -1655.45}}
				
			

The ["results"]["strategy_state"] will become ["results_strategy_state"]

Now it will be all in first level. The above JSON will become –

				
					{
"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_state": "Squareoff",
"results_strategy_start": "2019-01-01 09:25:00+05:30",
"results_strategy_end": "2019-01-01 15:10:00+05:30",
"results_entry_time": "2019-01-01 09:54:00+05:30",
"results_entry_price": 1056.25,
"results_exit_price": 1059.75,
"results_exit_time": "2019-01-01 15:10:00+05:30",
"results_strategy_pl": -1655.45
}
				
			
Now this can be converted to Pandas dataframe easily. However it will also keep the original “results” key. So we need to remove that column to make it clean.
				
					from pandas import json_normalize

df = pd.DataFrame(output_list)

# Normalize the 'results' column
df_normalized = json_normalize(df['results'])
df_normalized = df_normalized.add_prefix('results_')

# Drop the original 'results' column from the original DataFrame
df = df.drop('results', axis=1)

# Concatenate the original DataFrame with the normalized 'results' DataFrame
df = pd.concat([df, df_normalized], axis=1)

df
				
			
This code converts the list to Pandas and then normalize it. The output will be –
				
					strategy	symbol	exchange	quantity	entry_time	strategy_type	entry_price	target	stoploss	strategy_start	strategy_end	results_strategy_state	results_strategy_start	results_strategy_end	results_entry_time	results_entry_price	results_exit_price	results_exit_time	results_strategy_pl
0	GannSq9	RELIANCE	NSE	473	0	sell	1056.25	1048.66	1064.39	01-01-2019 09:25:00	01-01-2019 15:10:00	Squareoff	2019-01-01 09:25:00+05:30	2019-01-01 15:10:00+05:30	2019-01-01 09:54:00+05:30	1056.25	1059.75	2019-01-01 15:10:00+05:30	-1655.45
1	GannSq9	RELIANCE	NSE	473	0	buy	1064.39	1072.02	1056.25	01-01-2019 09:25:00	01-01-2019 15:10:00	No Entry	2019-01-01 09:25:00+05:30	2019-01-01 15:10:00+05:30	0	1064.39	0.00	0	0.00
2	GannSq9	RELIANCE	NSE	472	0	sell	1056.25	1048.66	1064.39	02-01-2019 09:25:00	02-01-2019 15:10:00	Target	2019-01-02 09:25:00+05:30	2019-01-02 15:10:00+05:30	2019-01-02 09:28:00+05:30	1056.25	1048.66	2019-01-02 12:46:00+05:30	3582.50
3	GannSq9	RELIANCE	NSE	472	0	buy	1064.39	1072.02	1056.25	02-01-2019 09:25:00	02-01-2019 15:10:00	No Entry	2019-01-02 09:25:00+05:30	2019-01-02 15:10:00+05:30	0	1064.39	0.00	0	0.00
4	GannSq9	RELIANCE	NSE	480	0	sell	1040.06	1032.54	1048.14	03-01-2019 09:25:00	03-01-2019 15:10:00	Target	2019-01-03 09:25:00+05:30	2019-01-03 15:10:00+05:30	2019-01-03 11:36:00+05:30	1040.06	1032.54	2019-01-03 14:29:00+05:30	3609.60
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
2309	GannSq9	RELIANCE	NSE	205	0	buy	2437.89	2449.02	2425.56	06-09-2023 09:25:00	06-09-2023 15:10:00	No Entry	2023-09-06 09:25:00+05:30	2023-09-06 15:10:00+05:30	0	2437.89	0.00	0	0.00
2310	GannSq9	RELIANCE	NSE	206	0	sell	2413.27	2402.20	2425.56	07-09-2023 09:25:00	07-09-2023 15:10:00	StopLoss	2023-09-07 09:25:00+05:30	2023-09-07 15:10:00+05:30	2023-09-07 12:23:00+05:30	2413.27	2425.56	2023-09-07 14:19:00+05:30	-2531.70
2311	GannSq9	RELIANCE	NSE	206	0	buy	2425.56	2436.67	2413.27	07-09-2023 09:25:00	07-09-2023 15:10:00	StopLoss	2023-09-07 09:25:00+05:30	2023-09-07 15:10:00+05:30	2023-09-07 10:15:00+05:30	2425.56	2413.27	2023-09-07 12:23:00+05:30	-2531.75
2312	GannSq9	RELIANCE	NSE	205	0	sell	2425.56	2414.48	2437.89	08-09-2023 09:25:00	08-09-2023 15:10:00	StopLoss	2023-09-08 09:25:00+05:30	2023-09-08 15:10:00+05:30	2023-09-08 10:34:00+05:30	2425.56	2437.89	2023-09-08 14:19:00+05:30	-2527.60
2313	GannSq9	RELIANCE	NSE	205	0	buy	2437.89	2449.02	2425.56	08-09-2023 09:25:00	08-09-2023 15:10:00	StopLoss	2023-09-08 09:25:00+05:30	2023-09-08 15:10:00+05:30	2023-09-08 09:36:00+05:30	2437.89	2425.56	2023-09-08 10:34:00+05:30	-2527.65
2314 rows × 19 columns
				
			
Now, if we convert this list of JSON to rows of a new pandas dataframe it will become messy because what will happen to the values which are inside the results key. It goes to the second level right? So will be there another database under each such values? Why not normalize 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': {'strategy_state': 'Squareoff',
   'strategy_start': datetime.datetime(2019, 1, 1, 9, 25, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'strategy_end': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=<DstTzInfo 'Asia/Kolkata' IST+5:30:00 STD>),
   'entry_time': datetime.datetime(2019, 1, 1, 9, 54, tzinfo=tzoffset(None, 19800)),
   'entry_price': 1056.25,
   'exit_price': 1059.75,
   'exit_time': datetime.datetime(2019, 1, 1, 15, 10, tzinfo=tzoffset(None, 19800)),
   'strategy_pl': -1655.45}}
				
			

Beautify the Pandas Dataframe

Now, let’s perform some column operations on the DataFrame:

  1. We start by dropping the 'strategy' column from the DataFrame.
  2. Then, we rename several columns:
    • 'symbol' is renamed to 'Stocks'.
    • 'quantity' is renamed to 'Qty.'.
    • 'results_strategy_state' is renamed to 'is_stoploss'.
    • 'results_entry_time' is renamed to 'Entry Time'.
    • 'results_exit_time' is renamed to 'Exit Time'.
    • 'results_entry_price' is renamed to 'Entry Price'.
    • 'results_exit_price' is renamed to 'Exit Price'.
    • 'target' is renamed to 'Target'.
    • 'strategy_type' is renamed to 'Type'.
    • 'results_strategy_pl' is renamed to 'P&L'.
  3. Finally, we drop additional columns:
    • 'exchange'
    • 'entry_time'
    • 'entry_price'
    • 'stoploss'
    • 'strategy_end'
    • 'results_strategy_start'
    • 'results_strategy_end'

Any columns not found in the DataFrame will be ignored during this process.

results_strategy_end and strategy_end have no role as they contain the value 15:10 only. Right?

Also, we know it is NFO!

				
					df = df.drop(columns=['strategy']) \
       .rename(columns={'symbol': 'Stocks',
                        'quantity': 'Qty.',
                        'results_strategy_state': 'is_stoploss',
                        'results_entry_time': 'Entry Time',
                        'results_exit_time': 'Exit Time',
                        'results_entry_price': 'Entry Price',
                        'results_exit_price': 'Exit Price',
                        'target': 'Target',
                        'strategy_type': 'Type',
                        'results_strategy_pl': 'P&L'}) \
       .drop(columns=['exchange', 'entry_time', 'entry_price',  'stoploss', 'strategy_end', 'results_strategy_start', 'results_strategy_end'], errors='ignore')


df
				
			
The Output looks –
				
					Stocks	Qty.	Type	Target	strategy_start	is_stoploss	Entry Time	Entry Price	Exit Price	Exit Time	P&L
0	RELIANCE	473	sell	1048.66	01-01-2019 09:25:00	Squareoff	2019-01-01 09:54:00+05:30	1056.25	1059.75	2019-01-01 15:10:00+05:30	-1655.45
1	RELIANCE	473	buy	1072.02	01-01-2019 09:25:00	No Entry	0	1064.39	0.00	0	0.00
2	RELIANCE	472	sell	1048.66	02-01-2019 09:25:00	Target	2019-01-02 09:28:00+05:30	1056.25	1048.66	2019-01-02 12:46:00+05:30	3582.50
3	RELIANCE	472	buy	1072.02	02-01-2019 09:25:00	No Entry	0	1064.39	0.00	0	0.00
4	RELIANCE	480	sell	1032.54	03-01-2019 09:25:00	Target	2019-01-03 11:36:00+05:30	1040.06	1032.54	2019-01-03 14:29:00+05:30	3609.60
...	...	...	...	...	...	...	...	...	...	...	...
2309	RELIANCE	205	buy	2449.02	06-09-2023 09:25:00	No Entry	0	2437.89	0.00	0	0.00
2310	RELIANCE	206	sell	2402.20	07-09-2023 09:25:00	StopLoss	2023-09-07 12:23:00+05:30	2413.27	2425.56	2023-09-07 14:19:00+05:30	-2531.70
2311	RELIANCE	206	buy	2436.67	07-09-2023 09:25:00	StopLoss	2023-09-07 10:15:00+05:30	2425.56	2413.27	2023-09-07 12:23:00+05:30	-2531.75
2312	RELIANCE	205	sell	2414.48	08-09-2023 09:25:00	StopLoss	2023-09-08 10:34:00+05:30	2425.56	2437.89	2023-09-08 14:19:00+05:30	-2527.60
2313	RELIANCE	205	buy	2449.02	08-09-2023 09:25:00	StopLoss	2023-09-08 09:36:00+05:30	2437.89	2425.56	2023-09-08 10:34:00+05:30	-2527.65
2314 rows × 11 columns
				
			
We need to beautify the strategy_start and strategy_end as it contains the same date twice as it is intraday strategy. So why not make a new column named Date and pull the Date there? Also there is no need to write the dates in the next to the time –
				
					df['Type'] = df['Type'].apply(lambda x: x.title())

df['Date'] = df['strategy_start'].astype(str)
df['Entry Time'] = df['Entry Time'].astype(str)
df['Exit Time'] = df['Exit Time'].astype(str)

df['Date'] = df['Date'].str[:-9]
df['Entry Time'] = df['Entry Time'].str[11:-9]
df['Exit Time'] = df['Exit Time'].str[11:-9]
df = df[['Date', 'Stocks', 'Qty.', 'Entry Time', 'Entry Price', 'Target', 'Exit Time', 'Exit Price', 'is_stoploss', 'P&L', 'Type']]
df['Date'] = df['Date'].str.replace('-', '/')

df
				
			
The output will be –
				
					Date	Stocks	Qty.	Entry Time	Entry Price	Target	Exit Time	Exit Price	is_stoploss	P&L	Type
0	01-01-2019	RELIANCE	473	09:54	1056.25	1048.66	15:10	1059.75	Squareoff	-1655.45	Sell
1	01-01-2019	RELIANCE	473		1064.39	1072.02		0.00	No Entry	0.00	Buy
2	02-01-2019	RELIANCE	472	09:28	1056.25	1048.66	12:46	1048.66	Target	3582.50	Sell
3	02-01-2019	RELIANCE	472		1064.39	1072.02		0.00	No Entry	0.00	Buy
4	03-01-2019	RELIANCE	480	11:36	1040.06	1032.54	14:29	1032.54	Target	3609.60	Sell
...	...	...	...	...	...	...	...	...	...	...	...
2309	06-09-2023	RELIANCE	205		2437.89	2449.02		0.00	No Entry	0.00	Buy
2310	07-09-2023	RELIANCE	206	12:23	2413.27	2402.20	14:19	2425.56	StopLoss	-2531.70	Sell
2311	07-09-2023	RELIANCE	206	10:15	2425.56	2436.67	12:23	2413.27	StopLoss	-2531.75	Buy
2312	08-09-2023	RELIANCE	205	10:34	2425.56	2414.48	14:19	2437.89	StopLoss	-2527.60	Sell
2313	08-09-2023	RELIANCE	205	09:36	2437.89	2449.02	10:34	2425.56	StopLoss	-2527.65	Buy
2314 rows × 11 columns
				
			
Post a comment

Leave a Comment

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

×Close