Fine-Tuning the Entropy Alpha Strategy: Incorporating Micro Conditions for Optimal Backtesting

Micro Conditions For Stabalizing the Strategy

At the time of market opening, the market remains too volatile because of liquidity. It disturbs the very fabric of this strategy which relies on the fact that the Market conforms to Log Normal Distribution. So, We start our trade at 9:20 AM. Similarly, We auto square off at 14:20 PM to avoid day end’s volatility!

Similarly, Apart from the initial conditions of entry, exit and trade management, Here are two set of micro conditions that govern the strategy for the purpose of better stability and ease of exeuction. 

Changing the parameters of these conditions does not alter the core startegy that much –

# Global Conditions

  • Entry Time Condition: It is an intraday strategy. So, if the trade is placed only when if the stock name comes between 9:30 AM to 14:20 PM.
  • Trade type and Stop Loss: It will long the stock with day’s high as target. If the stop loss does not hit, it will square off the trade anyways at 14:50 PM.

# Trade Specific Conditions

  • False Positives: If a trade is triggered within last 15 mins and it again pops up in the scanner, then it will take the first_trigger only i.e. A stock can not trigger again within 15 minutes.
  • Max Occurrence: A stock can trigger maximum two times in a day i.e if it triggers for the second time, second_trigger will have minimum 15 minutes of gap between first_trigger.

The KiteConnect APIs

We shall be using Zerodha’s Kite Connect API to back test the given dataset. We’re a Zerodha Partner and if you’ve signed up with Unofficed, then You’ll get access to the raw file used to make this tutorial. 

Kite Connect offers REST-like HTTP APIs with a wide range of capabilities which will suffice our needs.

Step 1 - Initialization of Kite Object

As You can see from the example of KiteConnect Python API from Zerodha’s GitHub, the Step 1 is to create the Kite object

					import logging
from kiteconnect import KiteConnect


kite = KiteConnect(api_key="your_api_key")

# Redirect the user to the login url obtained
# from kite.login_url(), and receive the request_token
# from the registered redirect url after the login flow.
# Once you have the request_token, obtain the access_token
# as follows.

data = kite.generate_session("request_token_here", api_secret="your_secret")

Step 2 - Breaking It Apart

For every programming ethics, the theory is simple. Break apart the data you get to your preference. Do the operations and then clean it up! So, here is the plan –
  1. Initialization of the data in Pandas Dataframe.
  2. Split the Column "Triggered at" into "Trigger Date" and "Trigger Time". It is easier to imagine calculations when things are broken.
  3. Now if you notice the columns like 16 Tue Apr 18 2023, 10:13 am WHIRLPOOL, GODREJPROP, PEL , it has more than one stock name in the line. So, as we treat them as different trades, Lets make them separate rows.
					#Initilization of Pandas Dataframe
df = pd.read_csv("entropy_data.csv")

#Split the Column "Triggered at" into "Trigger Date" and "Trigger Time"
df["Trigger Date"],df["Trigger Time"]=pd.to_datetime(df["Triggered at"]), pd.to_datetime(df["Triggered at"]).dt.time

#Removing the commas in Stock of same timed entries

## create an empty dataframe to store the updated rows
new_df = pd.DataFrame(columns=df.columns)

## iterate over the rows in the original dataframe
for index, row in df.iterrows():
    ## check if the "Stocks (new stocks are highlighted)" column contains a comma
    if "," in row["Stocks (new stocks are highlighted)"]:
        ## split the stock names by comma
        stocks = row["Stocks (new stocks are highlighted)"].split(",")
        ## create a new row for each stock
        for stock in stocks:
            new_row = row.copy()
            new_row["Stocks (new stocks are highlighted)"] = stock.strip() # remove any leading/trailing spaces
            new_df = new_df.append(new_row, ignore_index=True)
        new_df = new_df.append(row, ignore_index=True)

## assign the updated dataframe to the original dataframe
df = new_df

The Output is neat –

					Triggered at	Count	Stocks (new stocks are highlighted)	Trigger Date	Trigger Time
0	Fri Apr 21 2023, 10:07 am	1	APOLLOTYRE	2023-04-21	10:07:00
1	Fri Apr 21 2023, 9:58 am	1	ASIANPAINT	2023-04-21	09:58:00
2	Thu Apr 20 2023, 3:21 pm	1	TATACONSUM	2023-04-20	15:21:00
3	Thu Apr 20 2023, 2:16 pm	1	BAJAJ-AUTO	2023-04-20	14:16:00
4	Thu Apr 20 2023, 12:49 pm	1	CUB	2023-04-20	12:49:00
...	...	...	...	...	...
775	Tue Sep 13 2022, 10:03 am	3	DIXON	2022-09-13	10:03:00
776	Tue Sep 13 2022, 10:03 am	3	DRREDDY	2022-09-13	10:03:00
777	Tue Sep 13 2022, 10:03 am	3	HEROMOTOCO	2022-09-13	10:03:00
778	Tue Sep 13 2022, 10:01 am	2	DRREDDY	2022-09-13	10:01:00
779	Tue Sep 13 2022, 10:01 am	2	HEROMOTOCO	2022-09-13	10:01:00
780 rows × 5 columns

Step 3 - Coding the Trade Specific Conditions

As we have the Trigger Date column, We will scan for the Trade Specific Conditions for each day. Then we will scan for if there is second entry with a time difference of less than 15 minutes from the first entry. That’s it.
					# Convert the "Triggered at" column to datetime format
df['Triggered at'] = pd.to_datetime(df['Triggered at'], format='%a %b %d %Y, %I:%M %p')

# Sort the dataframe by "Triggered at" column
df.sort_values('Triggered at', inplace=True)

# Create an empty list to store the duplicates
duplicates = []

# Iterate over each stock in the dataframe
for stock in df['Stocks (new stocks are highlighted)'].unique():
    # Subset the dataframe for the current stock
    stock_df = df[df['Stocks (new stocks are highlighted)'] == stock]
    # Group the dataframe by "Trigger Date"
    grouped = stock_df.groupby('Trigger Date')
    # Iterate over the groups
    for name, group in grouped:
        # Keep only the first entry of the day
        group = group[~group.duplicated(subset=['Trigger Date'], keep='first')]

        # Get the index of the first entry
        first_idx = group.index[0]

        # Check if there is a second entry with a time difference of less than 15 minutes from the first entry
        if len(group) > 1:
            second_idx = group['Triggered at'].diff().dropna().idxmax()
            time_diff = group.loc[second_idx, 'Triggered at'] - group.loc[first_idx, 'Triggered at']
            if time_diff < timedelta(minutes=15):
                # Append the first and second entries to the list of duplicates
                duplicates.extend(group.loc[[first_idx, second_idx]].to_dict('records'))
                # Append only the first entry to the list of duplicates
            # Append only the first entry to the list of duplicates

# Create a new dataframe from the list of duplicates
duplicates_df = pd.DataFrame(duplicates)

# Drop the duplicate rows from the original dataframe
df.drop_duplicates(subset=['Trigger Date', 'Stocks (new stocks are highlighted)'], keep='first', inplace=True)


The Output is neat. It jumps down from 780 rows to 542 rows –

					Triggered at	Count	Stocks (new stocks are highlighted)	Trigger Date	Trigger Time
779	2022-09-13 10:01:00	2	HEROMOTOCO	2022-09-13	10:01:00
778	2022-09-13 10:01:00	2	DRREDDY	2022-09-13	10:01:00
775	2022-09-13 10:03:00	3	DIXON	2022-09-13	10:03:00
770	2022-09-13 10:06:00	5	ITC	2022-09-13	10:06:00
771	2022-09-13 10:06:00	5	SBICARD	2022-09-13	10:06:00
...	...	...	...	...	...
4	2023-04-20 12:49:00	1	CUB	2023-04-20	12:49:00
3	2023-04-20 14:16:00	1	BAJAJ-AUTO	2023-04-20	14:16:00
2	2023-04-20 15:21:00	1	TATACONSUM	2023-04-20	15:21:00
1	2023-04-21 09:58:00	1	ASIANPAINT	2023-04-21	09:58:00
0	2023-04-21 10:07:00	1	APOLLOTYRE	2023-04-21	10:07:00
542 rows × 5 columns

Step 4 - Coding the Global Conditions

The current limitation of Stock Broker APIs of Indian market is that the instrument token of a derivative keeps changing every month. So, We are taking the April Futures and hence, the trades of April only for this context –

					# Filter the data to take the Apr
df_filtered = df[(df['Triggered at'].dt.time >= pd.Timestamp('09:30').time()) & (df['Triggered at'].dt.time <= pd.Timestamp('14:20').time())]
df_filtered = df.loc[df['Triggered at'].dt.strftime('%Y-%m') == '2023-04']
df= df_filtered
Post a comment

Leave a Comment

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