Coding Heatmap Opening Range Breakout Strategy in Python – Part 2

Getting the Top Gainers and Top Losers

In our last part of Opening Range, We had used the nsefetch() function to fetch some NSE URLs directly. Although, You can write functions using the broker’s API but We will stick with NSE APIs for now.

In the last upgrade of NSEPython, We got a function named nse_get_top_gainers(). So, Lets replace our previous methodology using this function. 

Now if You do –

				
					gainers = nse_get_top_gainers()
gainers.drop(gainers.columns.difference(["symbol","high_price","low_price","ltp"]), 1, inplace=True)
gainers=gainers.head(5)
print(gainers)
				
			
You will get –
				
					symbol
0 IBULHSGFIN
1 ADANIENT
2 BHARATFORG
3 LICHSGFIN
4 UBL
				
			

It is because of the name of the columns changes in the data we get from this function.

So, just inspect the output of gainers. You may need to use set the max_columns attribute to see all the table names as it will be hidden ... as it is a long output.

Check here – aeron7.github.io/nsepython/documentation/nsefetch.html#parsing-the-data-with-pandas

				
					pd.set_option('display.max_columns', None) # or 1000
				
			
Anyways, the end program will look like –
				
					gainers = nse_get_top_gainers()
gainers.drop(gainers.columns.difference(["symbol","dayHigh","dayLow","lastPrice"]), 1, inplace=True)
gainers=gainers.head(5)
print(gainers) 
				
			
The output –
				
					symbol dayHigh dayLow lastPrice
0 IBULHSGFIN 272.75 227.75 263.15
1 ADANIENT 1713.55 1585.8 1706
2 BHARATFORG 758.65 695.1 749.85
3 LICHSGFIN 537.95 501 529
4 UBL 1378 1264.5 1340.65 
				
			

Now the trick (as discussed already) here is –

The high and low of the symbols at just the end of the 15th minute of market start is the high and low of the first 15 min candle of the day.

So, We need to run this program at 9:30:01 as our market starts at 9:15:00

Adding the loser’s set of stock is similar. You can code out a method from raw APIs as we did in 1st part.
Or, You can see the functions from here – aeron7.github.io/nsepython/documentation/nsetools.html#top-losers-gainers

The input –

				
					losers = nse_get_top_losers()
losers.drop(losers.columns.difference(["symbol","dayHigh","dayLow","lastPrice"]), 1, inplace=True)
losers=losers.head(5)
print(losers)
				
			
The output –
				
					symbol dayHigh dayLow lastPrice
155 NESTLEIND 17706.4 17380 17443
154 MARICO 495.5 485 487
152 TORNTPOWER 440.9 430.25 431.6
153 RBLBANK 220.35 211.25 215
151 ICICIGI 1481 1444 1450.2
				
			
Anyways, Let’s combine both gainers and losers data frames into one to prepare our watchlist.
				
					watchlist = gainers
watchlist = watchlist.append(losers)
print(watchlist)
				
			
The output –
				
					symbol dayHigh dayLow lastPrice
0 IBULHSGFIN 272.75 227.75 263.15
1 ADANIENT 1713.55 1585.8 1706
2 BHARATFORG 758.65 695.1 749.85
3 LICHSGFIN 537.95 501 529
4 UBL 1378 1264.5 1340.65
155 NESTLEIND 17706.4 17380 17443
154 MARICO 495.5 485 487
152 TORNTPOWER 440.9 430.25 431.6
153 RBLBANK 220.35 211.25 215
151 ICICIGI 1481 1444 1450.2
				
			

So, it is taking the index from the last tables too. It looks weird and it will end as problematic in the code if we extend to more complexities right?

So, Like Thanos’s Plan, We need to start with a blank data frame.

				
					watchlist = pd.DataFrame()
				
			
And we need to add the other two data frames ignoring their original index. So, the final input will look like –
				
					watchlist = pd.DataFrame()
watchlist = watchlist.append(gainers, ignore_index=True)
watchlist = watchlist.append(losers, ignore_index=True)
print(watchlist)
				
			
The output is fantastic now –
				
					symbol dayHigh dayLow lastPrice
0 IBULHSGFIN 272.75 227.75 263.15
1 ADANIENT 1713.55 1585.8 1706
2 BHARATFORG 758.65 695.1 749.85
3 LICHSGFIN 537.95 501 529
4 UBL 1378 1264.5 1340.65
5 NESTLEIND 17706.4 17380 17443
6 MARICO 495.5 485 487
7 TORNTPOWER 440.9 430.25 431.6
8 RBLBANK 220.35 211.25 215
9 ICICIGI 1481 1444 1450.2 )
				
			

The goal is simple now, firing 20 orders. (10 Buy orders + 10 Sell orders)

  • Buy Limit orders if Day high is broken with Day low as to stop loss.
  • Sell Limit orders if Day low is broken with Day high as to stop loss.

Now if you do BO order that allows putting the Stop Loss inside the order itself, It will be done within 20 orders.

But, if you do MIS order, then it will be quite a headache as We need to fire 40 orders.

Because every order will be –

  • Entry Order
  • Stop Loss Order

The eerie thing here is –

  • Suppose Your buy trade is triggered in MIS.
  • Now, if the sell trade is also triggered, it means that the buy trade’s SL is hit.

Remember that the SL in case of the Buy trade’s trigger is “Sell trade’s trigger point”.

So, It will “automagically” do its job within 20 orders itself if you add a new rule in the strategy –
It will either trigger one time i.e. If buy trade is triggered, then it will ignore the sell trade’s trigger afterward and vice-versa.

Risk Management and Maximum Loss

We shall use get_fno_lot_sizes() function of NSEPython.

Maximum loss is the difference between the high and low points of the stocks. Correct?

So, calculating the max loss in terms of point by substracting two columns in the Pandas table –

				
					watchlist = pd.DataFrame()
watchlist = watchlist.append(gainers, ignore_index=True)
watchlist = watchlist.append(losers, ignore_index=True)
watchlist["maxLossPt"] = watchlist["dayHigh"].astype(float)-watchlist["dayLow"].astype(float)
print(watchlist)
				
			
The output will be –
				
					symbol dayHigh dayLow lastPrice maxLossPt
0 IBULHSGFIN 272.75 227.75 263.15 45.00
1 ADANIENT 1713.55 1585.8 1706 127.75
2 BHARATFORG 758.65 695.1 749.85 63.55
3 LICHSGFIN 537.95 501 529 36.95
4 UBL 1378 1264.5 1340.65 113.50
5 NESTLEIND 17706.4 17380 17443 326.40
6 MARICO 495.5 485 487 10.50
7 TORNTPOWER 440.9 430.25 431.6 10.65
8 RBLBANK 220.35 211.25 215 9.10
9 ICICIGI 1481 1444 1450.2 37.00
				
			

The next step is adding the lot size.

We shall be using np.vectorize() function.

watchlist["lotsize"] = np.vectorize(nse_get_fno_lot_sizes)(watchlist["symbol"])

It will call the nse_get_fno_lot_sizes() function to each of the elements of symbol column of watchlist.

The output will be –

				
					symbol dayHigh dayLow lastPrice maxLossPt lotsize
0 IBULHSGFIN 272.75 227.75 263.15 45.00 3100
1 ADANIENT 1713.55 1585.8 1706 127.75 1000
2 BHARATFORG 758.65 695.1 749.85 63.55 1500
3 LICHSGFIN 537.95 501 529 36.95 2000
4 UBL 1378 1264.5 1340.65 113.50 700
5 NESTLEIND 17706.4 17380 17443 326.40 50
6 MARICO 495.5 485 487 10.50 2000
7 TORNTPOWER 440.9 430.25 431.6 10.65 1500
8 RBLBANK 220.35 211.25 215 9.10 2900
9 ICICIGI 1481 1444 1450.2 37.00 425
				
			

Now, getting the maxLoss is easy. We just need to multiply the maxLossPt and lotsize.

Now, I often tell you to ignore the trade if the stop loss is more than 6000 INR in our Entropy Setups. You can see how to achieve that.

So, the final code will be –

				
					symbol dayHigh dayLow lastPrice maxLossPt lotsize maxLoss
0 IBULHSGFIN 272.75 227.75 263.15 45.00 3100 139500.0
1 ADANIENT 1713.55 1585.8 1706 127.75 1000 127750.0
2 BHARATFORG 758.65 695.1 749.85 63.55 1500 95325.0
3 LICHSGFIN 537.95 501 529 36.95 2000 73900.0
4 UBL 1378 1264.5 1340.65 113.50 700 79450.0
5 NESTLEIND 17706.4 17380 17443 326.40 50 16320.0
6 MARICO 495.5 485 487 10.50 2000 21000.0
7 TORNTPOWER 440.9 430.25 431.6 10.65 1500 15975.0
8 RBLBANK 220.35 211.25 215 9.10 2900 26390.0
9 ICICIGI 1481 1444 1450.2 37.00 425 15725.0
				
			

Position Sizing

The next part is Position Sizing.

Suppose, You have allocated exposure of 1,00,000 INR to this strategy and hence 10,000 INR to each of the stocks. (There are 10 stocks right?)

So, We define the variable –

				
					quant = 10000
				
			
Now, We need to create a function that does properly round down and gives the output as quantity we will use to buy or sell.
				
					def get_quantity(lastPrice):
return math.floor(quant/float(lastPrice))
				
			
So,
  • If Reliance’s price is 1000. Then with 10000, We can buy 10 stocks of Reliance.
  • If Reliance’s price is 1001. Then with 10000, We can buy 9 stocks of Reliance.
Now, let’s replace the lotsize with quantity and our max loss calculation stays same –
				
					watchlist["lotsize"] = np.vectorize(get_quantity)(watchlist["lastPrice"])
watchlist["maxLoss"] = watchlist["maxLossPt"]*watchlist["lotsize"]
print(watchlist)
				
			
The output –
				
					symbol dayHigh dayLow lastPrice maxLossPt lotsize maxLoss
0 IBULHSGFIN 272.75 227.75 263.15 45.00 38 1710.00
1 ADANIENT 1713.55 1585.8 1706 127.75 5 638.75
2 BHARATFORG 758.65 695.1 749.85 63.55 13 826.15
3 LICHSGFIN 537.95 501 529 36.95 18 665.10
4 UBL 1378 1264.5 1340.65 113.50 7 794.50
5 NESTLEIND 17706.4 17380 17443 326.40 0 0.00
6 MARICO 495.5 485 487 10.50 20 210.00
7 TORNTPOWER 440.9 430.25 431.6 10.65 23 244.95
8 RBLBANK 220.35 211.25 215 9.10 46 418.60
9 ICICIGI 1481 1444 1450.2 37.00 6 222.00
				
			

There is no max loss in Nestle because We are too broke to buy or sell it at all.

You can also fire less than 10 trades based on complex calculations done on this maxLoss part.

In this case, the maxLoss will be attended if all the scrips hit their SL after triggering buy and sell.

If You do –

				
					print(round(watchlist["maxLoss"].sum(),2))
				
			
It will show –
				
					5730.05
				
			

So, that’s like 5.73% of 1L capital. You may want to take the maximum risk on the notion of your net quant on top of all these. Right?

Like I will not lose more than 3% of 1L capital.

Then you can design to take the first few trades and check if maxLoss is more than 3000, skip the rest of the trades.

Fire Orders using Broker APIs

Now all You have to do is to fire the order to the broker using either of the methods discussed above. We will discuss this thoroughly in the next part but here is some content for you so that You can try.

If You have followed Krishna’s AliceAPI code, You can see there are two ways.

MIS Stop Loss Limit Order – 

				
					#TransactionType.Buy, OrderType.StopLossLimit, ProductType.Intraday
print ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%10%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
print(
   alice.place_order(transaction_type = TransactionType.Buy,
                     instrument = alice.get_instrument_by_symbol('NSE', 'INFY'),
                     quantity = 1,
                     order_type = OrderType.StopLossLimit,
                     product_type = ProductType.Intraday,
                     price = 8.0,
                     trigger_price = 8.0,
                     stop_loss = None,
                     square_off = None,
                     trailing_sl = None,
                     is_amo = False)
)
				
			

Bracket Order – 

				
					# TransactionType.Buy, OrderType.StopLossLimit, ProductType.BracketOrder
print ("%%%%%%%%%%%%%%%%%%%%%%%%%%%%11%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
print(
   alice.place_order(transaction_type = TransactionType.Buy,
                     instrument = alice.get_instrument_by_symbol('NSE', 'INFY'),
                     quantity = 1,
                     order_type = OrderType.StopLossLimit,
                     product_type = ProductType.BracketOrder,
                     price = 8.0,
                     trigger_price = 8.0,
                     stop_loss = 1.0,
                     square_off = 1.0,
                     trailing_sl = 20,
                     is_amo = False)
)
				
			

So, All you have to do is implement an efficient usage of for loop or np.array() method of pandas. 

In the next part, We shall discuss about the functions provided by the user and more cases and alternative iterations. 

The Final Code

In case, You are following my code snippets and landed in error, You can use this Final Code Snippet which includes the complete block done till now. 

				
					from nsepython import *

pd.set_option('display.max_columns', None)  # or 1000
pd.set_option('display.max_rows', None)  # or 1000
pd.set_option('display.max_colwidth', -1)  # or 199
pd.options.mode.chained_assignment = None  # default='warn' https://stackoverflow.com/questions/20625582/how-to-deal-with-settingwithcopywarning-in-pandas

gainers = nse_get_top_gainers()
gainers.drop(gainers.columns.difference(["symbol","dayHigh","dayLow","lastPrice"]), 1, inplace=True)
gainers=gainers.head(5)
#print(gainers)

losers = nse_get_top_losers()
losers.drop(losers.columns.difference(["symbol","dayHigh","dayLow","lastPrice"]), 1, inplace=True)
losers=losers.head(5)
#print(losers)

# watchlist = gainers
# watchlist = watchlist.append(losers)
# print(watchlist)

# watchlist = pd.DataFrame()
# watchlist = watchlist.append(gainers, ignore_index=True)
# watchlist = watchlist.append(losers, ignore_index=True)
# print(watchlist)
import numpy as np

watchlist = pd.DataFrame()
watchlist = watchlist.append(gainers, ignore_index=True)
watchlist = watchlist.append(losers, ignore_index=True)
watchlist["maxLossPt"] = watchlist["dayHigh"].astype(float)-watchlist["dayLow"].astype(float)
watchlist["lotsize"] = np.vectorize(nse_get_fno_lot_sizes)(watchlist["symbol"])
watchlist["maxLoss"] = watchlist["maxLossPt"]*watchlist["lotsize"]
#print(watchlist)

import math
quant = 10000

def get_quantity(lastPrice):
    return math.floor(quant/float(lastPrice))

watchlist["lotsize"] = np.vectorize(get_quantity)(watchlist["lastPrice"])
watchlist["maxLoss"] = watchlist["maxLossPt"]*watchlist["lotsize"]
print(watchlist)
print(round(watchlist["maxLoss"].sum(),2))

				
			
The codes that were used in middle and deleted or modified are commented out instead of deletion.
×Close