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:
- We start by dropping the
'strategy'
column from the DataFrame. - 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'
.
- 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
A Note from Unofficed
Thank you for being a valued member of our community! Before you depart:
👏 Applaud the story and give a follow to the author 👉
📰 Explore additional content on the Unofficed
📚 Join our FREE Masterclass
📈 Unlock potent trading tools