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 –
The goal is simple. We will simulate the trades. There will be three new variables –
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
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"))
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'))
Trigger Date
and Trigger Time
column. price
column to entry_price
. pl_points = square_off_price - entry_price.
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
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
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
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 –
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.