Backtest Entropy Bollinger Band Strategy Using Python with Futures Data Part II

Let’s jump directly to the discussion from where we had left in the last part. So far We have a fresh trade log and our goal is – 

  1. We will simulate the trades in our trade log and,
  2. We will checkout the trade performance and, 
  3. We will analyze that details with various popular trading metrics to get an overview of strength of our strategy.

Step 6 - Simulate the Trades

The goal is simple. We will simulate the trades. There will be three new variables – 

  1. square_off_time: It will contain the time of when our trade hits stop loss. If it does not hit stop loss, then it will be marked as 14:50 PM.
  2. square_off_price: If the price of the stock reaches the day high, it means stop loss is hit. It will mark that price. And if the stop loss is not hit, it will close the trade at 14:50 PM and mark that price. 
  3. is_stoploss: A boolean value to check if stop loss is hit or not. 
				
						Triggered at	stocks	Trigger Date	Trigger Time	price	high	square_off_time	square_off_price	is_stoploss
59	2023-04-03 10:01:00	MARUTI	2023-04-03	10:01:00	8558.15	8634.85	2023-04-03 14:50:00+05:30	8554.20	False
57	2023-04-03 10:04:00	M&M	2023-04-03	10:04:00	1178.5	1187.15	2023-04-03 10:22:00+05:30	1187.15	True
54	2023-04-03 10:10:00	ASHOKLEY	2023-04-03	10:10:00	142.6	143.30	2023-04-03 14:50:00+05:30	141.60	False
53	2023-04-03 10:16:00	CHAMBLFERT	2023-04-03	10:16:00	270.65	273.90	2023-04-03 15:19:00+05:30	273.90	True
51	2023-04-03 10:17:00	GMRINFRA	2023-04-03	10:17:00	42.8	43.95	2023-04-03 14:04:00+05:30	43.95	True
...	...	...	...	...	...
4	2023-04-20 12:49:00	CUB	2023-04-20	12:49:00	132.7	134.35	2023-04-20 14:50:00+05:30	133.25	False
3	2023-04-20 14:16:00	BAJAJ-AUTO	2023-04-20	14:16:00	4321	4336.60	2023-04-20 14:50:00+05:30	4317.85	False
2	2023-04-20 15:21:00	TATACONSUM	2023-04-20	15:21:00	705.7	706.50	2023-04-20 14:50:00+05:30	705.00	False
1	2023-04-21 09:58:00	ASIANPAINT	2023-04-21	09:58:00	2854.75	2888.00	2023-04-21 14:23:00+05:30	2888.00	True
0	2023-04-21 10:07:00	APOLLOTYRE	2023-04-21	10:07:00	335.5	337.85	2023-04-21 14:50:00+05:30	334.05	False
60 rows × 9 columns
				
			

Step 7 - Lot Sizes and Cosmetic Changes

Now, Let’s add the Lot Sizes so that we can simulate how much profit and loss happened if the trades are taken with 1 Lot. In derivatives, Lot size of each stock derivative is different! It is available in the same instrument list from where we retrieved our instrument_token. Let’s code a small function for lot sizes and add the value in a new column named lotsize
				
					def get_lotsize(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.lot_size.iloc[0]

df["lotsize"] = df["stocks"].apply(lambda x: get_lotsize(x+"23APRFUT"))
				
			
Now, there is a minor beautification needed. If you notice the values of Triggered at, it will look like “2023-04-20 15:21:00”. While the values of square_off_time looks like “2023-04-20 14:50:00+05:30”. It means it is adding the timezone. Anyways, We need to make the format uniform
				
					df['square_off_time'] = df['square_off_time'].apply(lambda x: datetime.datetime.strptime(str(x)[:-6], '%Y-%m-%d %H:%M:%S'))

				
			

Step 8 - Calculate the Money

Let’s do three things – 
  1. Remove the Trigger Date and Trigger Time column. 
  2. Rename the price column to entry_price .
  3. Calculate the Profit/Loss points i.e. pl_points = square_off_price - entry_price.
  4. Calculate the Net profit by multiplying that points with lot size .
				
					# remove "Trigger Date" column
df = df.drop("Trigger Date", axis=1)

# remove "Trigger Time" column
df = df.drop("Trigger Time", axis=1)

# rename "stoploss" column to "pl"
df = df.rename(columns={"price": "entry_price"})

df["pl_points"] = df["square_off_price"]-df["entry_price"]
df["pl"] = df["pl_points"]*df["lotsize"]
df
				
			

The Final Trade Log

And, We get the Final output with entire trade log with their entry and exit – 

				
					Triggered at	stocks	entry_price	high	square_off_time	square_off_price	is_stoploss	lotsize	pl_points	pl
59	2023-04-03 10:01:00	MARUTI	8558.15	8634.85	2023-04-03 14:50:00	8554.20	False	100	-3.95	-395.0
57	2023-04-03 10:04:00	M&M	1178.5	1187.15	2023-04-03 10:22:00	1187.15	True	700	8.65	6055.0
54	2023-04-03 10:10:00	ASHOKLEY	142.6	143.30	2023-04-03 14:50:00	141.60	False	5000	-1.0	-5000.0
53	2023-04-03 10:16:00	CHAMBLFERT	270.65	273.90	2023-04-03 15:19:00	273.90	True	1500	3.25	4875.0
51	2023-04-03 10:17:00	GMRINFRA	42.8	43.95	2023-04-03 14:04:00	43.95	True	22500	1.15	25875.0
50	2023-04-03 14:48:00	CROMPTON	300.3	301.80	2023-04-03 14:50:00	299.75	False	1500	-0.55	-825.0
49	2023-04-05 10:48:00	TATACOMM	1261.45	1284.40	2023-04-05 12:19:00	1284.40	True	500	22.95	11475.0
48	2023-04-06 09:56:00	POWERGRID	227	228.25	2023-04-06 14:50:00	226.10	False	2700	-0.9	-2430.0
47	2023-04-06 10:31:00	SBIN	531.8	534.95	2023-04-06 10:50:00	534.95	True	1500	3.15	4725.0
46	2023-04-06 10:46:00	BAJFINANCE	5875	5990.00	2023-04-06 14:50:00	5948.60	False	125	73.6	9200.0
45	2023-04-06 12:37:00	IPCALAB	838.2	844.60	2023-04-06 14:50:00	831.65	False	650	-6.55	-4257.5
44	2023-04-10 10:05:00	INDUSTOWER	142.2	142.90	2023-04-10 14:50:00	141.45	False	2800	-0.75	-2100.0
43	2023-04-10 10:17:00	INFY	1433	1437.90	2023-04-10 14:50:00	1432.35	False	400	-0.65	-260.0
41	2023-04-10 10:18:00	DELTACORP	194.65	195.95	2023-04-10 15:16:00	195.95	True	2800	1.3	3640.0
38	2023-04-10 10:19:00	DLF	396.65	409.00	2023-04-10 14:19:00	409.00	True	1650	12.35	20377.5
34	2023-04-10 10:22:00	BSOFT	267.85	269.25	2023-04-10 14:50:00	268.75	False	2000	0.9	1800.0
33	2023-04-11 10:04:00	CANBK	290.45	293.40	2023-04-11 14:50:00	288.65	False	2700	-1.8	-4860.0
32	2023-04-11 11:07:00	AUROPHARMA	546.7	550.50	2023-04-12 10:38:00	550.50	True	1000	3.8	3800.0
31	2023-04-12 10:01:00	JUBLFOOD	429.25	433.00	2023-04-12 14:50:00	430.00	False	1250	0.75	937.5
29	2023-04-12 10:03:00	DIVISLAB	3063	3224.00	2023-04-12 13:51:00	3224.00	True	150	161.0	24150.0
28	2023-04-12 10:17:00	LAURUSLABS	308.8	329.80	2023-04-12 13:07:00	329.80	True	1100	21.0	23100.0
27	2023-04-13 10:03:00	INDIAMART	5516.45	5580.95	2023-04-13 14:50:00	5410.80	False	150	-105.65	-15847.5
25	2023-04-13 10:04:00	HDFCAMC	1804.5	1813.00	2023-04-13 14:50:00	1791.90	False	300	-12.6	-3780.0
24	2023-04-13 10:46:00	GODREJPROP	1269.9	1289.70	2023-04-13 15:20:00	1289.70	True	425	19.8	8415.0
23	2023-04-13 13:49:00	ADANIENT	1878.05	1895.00	2023-04-13 14:50:00	1886.05	False	250	8.0	2000.0
22	2023-04-17 09:16:00	SBIN	533.55	546.70	2023-04-17 14:34:00	546.70	True	1500	13.15	19725.0
20	2023-04-17 10:01:00	CHOLAFIN	833.25	840.75	2023-04-17 10:20:00	840.75	True	1250	7.5	9375.0
21	2023-04-17 10:01:00	ULTRACEMCO	7782	7837.55	2023-04-17 14:50:00	7759.95	False	100	-22.05	-2205.0
19	2023-04-17 10:16:00	SIEMENS	3358.25	3381.10	2023-04-17 11:02:00	3381.10	True	275	22.85	6283.75
18	2023-04-18 09:47:00	PIIND	3108.55	3172.00	2023-04-18 14:50:00	3151.10	False	250	42.55	10637.5
16	2023-04-18 09:53:00	ASHOKLEY	139.35	140.50	2023-04-19 09:29:00	140.50	True	5000	1.15	5750.0
15	2023-04-18 10:01:00	PEL	713.6	729.90	2023-04-18 14:50:00	722.20	False	550	8.6	4730.0
13	2023-04-18 10:02:00	GODREJPROP	1290.6	1317.00	2023-04-18 13:22:00	1317.00	True	425	26.4	11220.0
10	2023-04-18 10:13:00	WHIRLPOOL	1330.5	1342.00	2023-04-18 14:50:00	1323.00	False	350	-7.5	-2625.0
9	2023-04-18 13:31:00	BANDHANBNK	217.2	218.75	2023-04-18 14:50:00	214.05	False	1800	-3.15	-5670.0
8	2023-04-19 09:16:00	COFORGE	3960	3992.95	2023-04-19 14:50:00	3890.00	False	150	-70.0	-10500.0
7	2023-04-19 10:06:00	TATASTEEL	109.8	110.65	2023-04-19 14:50:00	108.50	False	5500	-1.3	-7150.0
6	2023-04-19 10:29:00	COROMANDEL	941.9	957.25	2023-04-19 11:10:00	957.25	True	700	15.35	10745.0
5	2023-04-20 10:02:00	ICICIBANK	897.05	901.00	2023-04-21 09:32:00	901.00	True	700	3.95	2765.0
4	2023-04-20 12:49:00	CUB	132.7	134.35	2023-04-20 14:50:00	133.25	False	5000	0.55	2750.0
3	2023-04-20 14:16:00	BAJAJ-AUTO	4321	4336.60	2023-04-20 14:50:00	4317.85	False	250	-3.15	-787.5
2	2023-04-20 15:21:00	TATACONSUM	705.7	706.50	2023-04-20 14:50:00	705.00	False	900	-0.7	-630.0
1	2023-04-21 09:58:00	ASIANPAINT	2854.75	2888.00	2023-04-21 14:23:00	2888.00	True	200	33.25	6650.0
0	2023-04-21 10:07:00	APOLLOTYRE	335.5	337.85	2023-04-21 14:50:00	334.05	False	3500	-1.45	-5075.0
				
			

Calculate Trading Metrics

Now, here is a patch of code that analyze that details with various popular trading metrics to get an overview of strength of our strategy.

				
					net_pl = round(df["pl"].sum(), 2)
print("Net P&L:", net_pl)

num_stoploss_hits = len(df[df['is_stoploss']])
total_trades = len(df)
num_target_hits = total_trades-num_stoploss_hits

print(f"Number of total trades: {total_trades}")
print(f"Number of times stop loss is hit: {num_stoploss_hits}")
print(f"Number of times target is hit: {num_target_hits}")

win_ratio = round(num_target_hits / total_trades, 2)
print(f"Win Ratio: {win_ratio}")

avg_pl = round(df['pl'].mean(), 2)
max_pl = round(df['pl'].max(), 2)
min_pl = round(df['pl'].min(), 2)

print(f"Avg PL: {avg_pl}")
print(f"Max PL: {max_pl}")
print(f"Min PL: {min_pl}")

gross_pl = round(df['pl'].sum(), 2)
avg_gain = round(df[df['pl'] > 0]['pl'].mean(), 2)
avg_loss = round(df[df['pl'] < 0]['pl'].mean(), 2)
profit_factor = round(-df[df['pl'] < 0]['pl'].sum() / df[df['pl'] > 0]['pl'].sum(), 2)
expected_payoff = round(df['pl'].mean(), 2)
max_drawdown = round((df['pl'].cumsum().cummax() - df['pl'].cumsum()).max(), 2)

risk_free_rate = 0.02
sharpe_ratio = round((expected_payoff - risk_free_rate) / df['pl'].std(), 2)

print(f"Gross P&L: {gross_pl}")
print(f"Average Gain: {avg_gain}")
print(f"Average Loss: {avg_loss}")
print(f"Profit Factor: {profit_factor}")
print(f"Expected Payoff: {expected_payoff}")
print(f"Maximum Drawdown: {max_drawdown}")
print(f"Sharpe Ratio: {sharpe_ratio}")

# calculate gross profit and loss
gross_profit = round(df[df['pl'] > 0]['pl'].sum(), 2)
gross_loss = round(df[df['pl'] < 0]['pl'].sum(), 2)

print(f"Gross Profit: {gross_profit}")
print(f"Gross Loss: {gross_loss}")

# calculate recovery factor and maximal consecutive profit/loss
recovery_factor = round(abs(gross_profit / gross_loss), 2)
print(f"Recovery Factor: {recovery_factor}")

# calculate max consecutive wins and losses
wins = df['pl'] > 0
losses = df['pl'] < 0

max_wins = wins.groupby((wins != wins.shift()).cumsum()).cumsum().max()
max_losses = losses.groupby((losses != losses.shift()).cumsum()).cumsum().min()

print(f"Maximum consecutive wins: {max_wins}")
print(f"Maximum consecutive losses: {abs(max_losses)}")


max_consecutive_profit = df['pl'].rolling(window=2).sum().max()
max_consecutive_loss = abs(df['pl'].rolling(window=2).sum().min())

print(f"Maximal consecutive profit: {max_consecutive_profit:.2f}")
print(f"Maximal consecutive loss: {max_consecutive_loss:.2f}")

# convert the "Triggered at" and "square_off_time" columns to datetime format
df["Triggered at"] = pd.to_datetime(df["Triggered at"])
df["square_off_time"] = pd.to_datetime(df["square_off_time"])

# calculate the holding time for each trade
df["holding_time"] = df["square_off_time"] - df["Triggered at"]

# calculate the average holding time for all trades
avg_holding_time = df["holding_time"].mean()
print(f"Average holding time: {avg_holding_time}")

# calculate the average holding time for profit trades
profit_trades = df[df["pl"] > 0]
avg_profit_holding_time = profit_trades["holding_time"].mean()
print(f"Average holding time for profit trades: {avg_profit_holding_time}")

# calculate the average holding time for loss trades
loss_trades = df[df["pl"] < 0]
avg_loss_holding_time = loss_trades["holding_time"].mean()
print(f"Average holding time for loss trades: {avg_loss_holding_time}")

				
			

The Output shows all the available info you need to know to evaluate a strategy – 

				
					Net P&L: 166658.75
Number of total trades: 44
Number of times stop loss is hit: 19
Number of times target is hit: 25
Win Ratio: 0.57
Avg PL: 3787.7
Max PL: 25875.0
Min PL: -15847.5
Gross P&L: 166658.75
Average Gain: 9271.39
Average Loss: -4133.19
Profit Factor: 0.31
Expected Payoff: 3787.7
Maximum Drawdown: 25945.0
Sharpe Ratio: 0.42
Gross Profit: 241056.25
Gross Loss: -74397.5
Recovery Factor: 3.24
Maximum consecutive wins: 5
Maximum consecutive losses: 0
Maximal consecutive profit: 47250.00
Maximal consecutive loss: 19627.50
Average holding time: 0 days 04:44:50.454545454
Average holding time for profit trades: 0 days 05:29:34.615384615
Average holding time for loss trades: 0 days 03:40:13.333333333
				
			

Calculate the Profit/Loss over the Time

Let’s use the matplotlib library to plot the graph. Here goes the code –
				
					import matplotlib.pyplot as plt

# Set the color of the bars based on positive or negative values
colors = ['g' if pl >= 0 else 'r' for pl in df['pl']]

# Create the bar chart
plt.bar(df.index, df['pl'], color=colors)

# Set the labels and title
plt.xlabel("Trade Number")
plt.ylabel("P&L")
plt.title("P&L Over Time")

# Add a grid
plt.grid()

# Add copyright
plt.text(0.5, 0, "© Copyright 2000-2023, Unofficed Inc",
         horizontalalignment='center', verticalalignment='center',
         transform=plt.gca().transAxes, fontsize=8, color='gray')


# Show the plot
plt.show()

				
			

The graph looks like – 

Plot the Cumulative Profit/Loss Over Time

Here goes code snippet of another fancy graph. Despite the fancy tag, the graphs are good! It tells so much thing in so little space – 

				
					import matplotlib.pyplot as plt

cum_pl = df["pl"].cumsum()[::-1]
trade_num = range(1, len(df)+1)[::-1]

plt.plot(trade_num, cum_pl)
plt.title('Cumulative P&L Over Trades')
plt.xlabel('Trade Number')
plt.ylabel('Cumulative P&L')
# Add copyright
plt.text(0.5, 0, "© Copyright 2000-2023, Unofficed Inc",
         horizontalalignment='center', verticalalignment='center',
         transform=plt.gca().transAxes, fontsize=8, color='gray')

plt.show()

				
			

The graph looks like – 

That concludes this chapter. 

In the next chapter, We will simulate the trades in equity segment instead of derivatives.  

Post a comment

Leave a Comment

Your email address will not be published.

×Close