With our trade trigger points identified, the next step is to simulate the execution of these trades. To build a preliminary trade log for a long-only strategy, we need to fetch two key pieces of data for each trigger:
This process requires querying historical data from the broker’s API for each specific trigger time.
Brokers like Zerodha identify each scrip, and particularly each derivative contract, with a unique instrument_token. Before we can request historical data, we must find this token. The following function searches a master list of instruments (fetched once from kite.instruments()) to find the token for a given trading symbol and exchange.
instrumentList = pd.DataFrame(kite.instruments())
def get_insToken(tradesymbol,exchange="NFO"):
if(exchange=="NSE"):
if(tradesymbol=="NIFTY"):tradesymbol="NIFTY 50"
if(tradesymbol=="BANKNIFTY"):tradesymbol="NIFTY BANK"
#print(tradesymbol)
#print(exchange)
dataToken = instrumentList[(instrumentList['tradingsymbol'] == tradesymbol)&(instrumentList['exchange']==exchange)]
return dataToken.instrument_token.iloc[0]
Now, we iterate through each row of our filtered DataFrame. For each triggered stock, we call a function get_stock_data that uses the KiteConnect API’s historical_data method. We request the 1-minute candle data corresponding to the trigger time and use the opening price of that candle (data[0]['open']) as our simulated entry price.
Note the hardcoded futures expiry string +"23APRFUT". This is a critical detail that must be managed carefully.
#Price At The Particular Time
import datetime
# Create a function to get the price and high of a stock at a specific time
def get_stock_data(symbol, time):
# Define the start and end date
start_date = pd.Timestamp(time)
end_date = start_date + datetime.timedelta(minutes=5)
# Convert start and end date to string format
from_date = start_date.strftime('%Y-%m-%d %H:%M:%S')
# Fetch historical data for the stock
data = kite.historical_data(instrument_token=get_insToken(symbol+"23APRFUT"),
from_date=from_date,
to_date=from_date,
interval='minute')
# Return the price and high of the stock at that time
return data[0]['open']
# Add new columns to the DataFrame
df['price'] = ''
# Iterate over each row of the DataFrame
for index, row in df.iterrows():
# Get the stock symbol and triggered time
symbol = row['Stocks (new stocks are highlighted)']
time = row['Triggered at']
# Get the price and high of the stock at the triggered time
price= get_stock_data(symbol, time)
# Update the price and high columns
df.at[index, 'price'] = price
df
IndexError and Futures Expiry Mismatch. You will likely encounter an IndexError: list index out of range when running this code.
---> 22 return data[0]['open']
IndexError: list index out of range
This error occurs because the kite.historical_data call returned an empty list. The most common reason is a mismatch between the futures contract you are requesting (e.g., "23APRFUT" for April 2023) and the date of the data you are backtesting. The KiteConnect API does not provide historical intraday data for expired futures contracts.
Solution: Ensure the hardcoded expiry string in your code (e.g., "23APRFUT") and the month filter for your DataFrame (e.g., '2023-04') both correspond to the current or a very recent month for which intraday data is available.
Next, we fetch the highest price the contract reached on the day of the trigger. This serves as our best-case scenario target for a long trade. We use the 'day' interval in the historical_data call to get the complete data for the trigger date and extract the 'high' value.
def get_high_of_day(kite, df):
high_list = []
for i, row in df.iterrows():
# Get historical data for the trigger date of the stock
symbol = row['Stocks (new stocks are highlighted)']
data = kite.historical_data(instrument_token=get_insToken(symbol+"23APRFUT"),
from_date=row['Trigger Date'],
to_date=row['Trigger Date'],
interval='day')
# Get the high of the day
high = data[0]['high']
high_list.append(high)
df['high'] = high_list
return df
df=get_high_of_day(kite, df)
df
After performing some minor data cleaning, such as dropping unnecessary columns and renaming others for clarity, we have our preliminary trade log. It contains the entry time, stock symbol, entry price, and potential exit price for each trade signal generated by the scanner.
| Triggered at | stocks | Trigger Date | Trigger Time | price | high |
|---|---|---|---|---|---|
| 2023-04-03 10:01:00 | MARUTI | 2023-04-03 | 10:01:00 | 8558.15 | 8634.85 |
| 2023-04-03 10:04:00 | M&M | 2023-04-03 | 10:04:00 | 1178.50 | 1187.15 |
| 2023-04-03 10:10:00 | ASHOKLEY | 2023-04-03 | 10:10:00 | 142.60 | 143.30 |
| 2023-04-03 10:16:00 | CHAMBLFERT | 2023-04-03 | 10:16:00 | 270.65 | 273.90 |
| 2023-04-20 10:02:00 | ICICIBANK | 2023-04-20 | 10:02:00 | 897.05 | 901.00 |
| 2023-04-20 12:49:00 | CUB | 2023-04-20 | 12:49:00 | 132.70 | 134.35 |
| 2023-04-21 09:58:00 | ASIANPAINT | 2023-04-21 | 09:58:00 | 2854.75 | 2888.00 |
| 2023-04-21 10:07:00 | APOLLOTYRE | 2023-04-21 | 10:07:00 | 335.50 | 337.85 |
This table forms the basis of our backtest analysis. For each trade, we have a simulated entry price (price) and a theoretical maximum exit price (high). The difference between these two values represents the gross profit potential for each trade before considering transaction costs, slippage, or a more realistic exit strategy (which may not capture the full day’s high).