How to Speed Up Your Trades Execution using Python?

Using the concepts of Concurrency with Angel Broking SmartAPI

ยท

11 min read

How to Speed Up Your Trades Execution using Python?

A lot of people in the industry talk about Python being one of the slower languages as compared to the others out there, and that is absolutely true as well; we have discussed some pros and cons of various languages used in the financial world here. But a lot of times, retail investors/traders give too much weight to this disadvantage which is completely irrational because, within Python, you have lots of avenues of writing smart code which can execute faster than you expected and unless you are into HFT (High-Frequency Trading), I will tend to disagree that Python is slow. It's not!

And if you think you have dreams of doing HFT and you are a retail investor, choosing a programming language should be the least of your concerns as there are many more headaches. You can read my HFT article to find out more.

In this article, I aim to show you how to execute your trades in parallel by using the concepts of Concurrency/Parallelism. If those words scare you at the moment, don't worry; we will take baby steps to cover them in this article. But beware, this will be a long detailed read but one that you will enjoy and will be mentally stimulating.

For the purposes of this article, we will be using the SmartAPI as an example. SmartAPI is powered by Angel Broking, which is a renowned broking house in India. I have personally used the APIs of most of the brokers out there, but why I chose SmartAPI for this article is because:

  • Free of Charge (unlike Kite API by Zerodha, which charges 2000 Rs a month)

  • Well Maintained Python Client (Available on Github )

  • Decent API Documentation

  • Forum to ask questions and solve issues.

  • 10 Min Account Opening (Only applicable if you not an NRI)

For clarity, this post is not sponsored by Angel Broking; using them for this piece is my personal preference. There are no affiliate links in this article, so you can be sure we are not biased.

Okay, so let's get into the main piece; before we show you how the code will work, let's first understand what Concurrency is; if you are already familiar with the topic, please skip to the next section.

Concurrency in Python

See, you will find lots of definitions of Concurrency on the internet. Still, in very simple terms, Concurrency can execute multiple new requests while waiting for a response on those existing requests. Confusing? I am sure it will be; these are difficult concepts, but let's take a simple multitasking example.

Let's assume you are making a cake and you have made all the dough and everything, and now it's time to put it in the oven, which will take around 45-60 mins for the cake to bake. You can either wait for the whole 45-60 mins and do nothing OR you can utilize this time and prepare your topping/icing, which will go on the cake.

Which one sounds more sensible to you? The second option, right, why waste your precious time waiting and then doing something when you can do that right now. Yes, that is concurrency.

concurrency.webp

Thank You Real Python for an apt image on processing things in a loop.

So how will this fit into our SmartAPI example? Let's say you have 20 trades to execute right away; you can either send the trade request via API one by one 20 times. (This is the example diagram above where you send one request, wait, and then send another)

OR

You can send multiple trade requests (respecting the API limits) in one go. For example, you are buying RELIANCE-EQ, and you are waiting for that 200 milliseconds for the request to go to the API server, get accepted, and return back with an order number; why not utilize that 200 milliseconds to place another buy request on ITC-EQ? Now the below diagram will be easier to understand.

Threading.webp

Thank You Real Python for an apt image on processing things concurrently.

So imagine placing 20 trades in a loop one by one would have taken 10 seconds to perform; the same task concurrently might have taken just 2.5 seconds! Tons of saving in terms of execution time; if you don't believe these, wait until we do a live example.

Python Code Implementation on Concurrency using SmartAPI

Before getting into the code, here are my assumptions if you want to follow all the code below:

  • You know the basics of Python and a little bit of data manipulation using pandas

  • You have registered for SmartAPI and have a broking account with Angel Broking.

  • You are doing this after Indian Market hours to avoid real execution in the markets.

  • You have installed pandas, smartapi-python library. If not please follow instructions here

Disclaimer: All the code is for educational purposes; please do not blame TradeWithPython if you end up losing money and none of this is investment advice.

1. Importing Necessary Libraries

from smartapi import SmartConnect
import pandas as pd
from datetime import datetime #to calculate the execution time
import time

2. Logging into your Broking Account using SmartAPI

obj = SmartConnect(api_key = "your_api_key")
#this obj will be used later on to make all the trade requests.

#Let's login
data = obj.generateSession("Your Client ID","Your Password")

#verifying if the login was successful
print(data)

If all is well, you should see something like below; if you receive failure messages or any error codes, best to contact the Angel Broking Support Team or the SmartAPI Forum. *The image has been redacted below for confidentiality reasons. *

image.png

3. Let's place a sample trade using SmartAPI

WARNING: Do not attempt to run this piece of code in live market hours (9:15 AM - 3:30 PM IST), or else you risk real execution of the trade.

try:
    orderparams = {
        "variety": "NORMAL",
        "tradingsymbol": "SEQUENT-EQ",
        "symboltoken": "14296",
        "transactiontype": "BUY",
        "exchange": "NSE",
        "ordertype": "MARKET",
        "producttype": "DELIVERY",
        "duration": "DAY",
        "price": "256",
        "quantity": "100",
        "triggerprice": "0"
        }
    orderId=obj.placeOrder(orderparams)
    #using the obj we initiated earlier
    print("The order id is: {}".format(orderId))
except Exception as e:
    print("Order placement failed: {}".format(e))

If all goes well and your login was successful, you will see a message in your console or Jupyter notebook saying The order id is: 210611000XXXXXX Now let's decode what we did in the above piece of code; first we created a dict with the variable name orderparams which basically contains all the information about the trade:

  • Is it an Intraday Trade or a Delivery Trade?

  • What is the Trading Symbol?

  • What is the Symbol Token? (You can find this here)

  • Which Exchange are you trading on?

  • When do you want the order to cancel if not executed? (Immediate or wait for the day)

  • What is Price and Quantity?

  • What is Order Type? (MARKET, LIMIT, STOPLOSS, etc.)

You can find the comprehensive list of possible parameters in the SmartAPI Docs. Now these parameters are something that you have to select anyway when you are trading via their Mobile App or their Website, so it's no surprise that the API also needs this data to execute trades.

And then you are using the obj we defined earlier to placeOrder by passing the orderparams, if the order is placed in the account, it will generate an orderId. Hopefully, no doubts here.

4. Creating a List of Trades via an Excel Spreadsheet

Now, let's say you want to execute 20 trades in the market, and you have to create the orderparams dictionary manually for each and every trade, an extremely painful process, right?

Let's simplify this; how about we just give our program an excel spreadsheet like below and then wrangle this data around to convert it into a list of orderparams which we can loop over? Confused? Sorry about that, let's simplify this step by step.

image.png

You can download this sample data HERE.

  • Read this data into Python
df = pd.read_excel("path/to/file.xlsx")
print(df.head())

image.png

  • Create a for loop to generate the orderparams dict by iterating over each row in the dataframe.
trade_list = [] #empty list

#looping over each row in the dataframe and storing
#the value in each column to generate orderparams dict
#we use str to convert to strings
for index, rows in df.iterrows():
    new_dict = {"variety": str(rows['variety']), 
                "tradingsymbol" : str(rows['tradingsymbol']),
                "symboltoken" : str(rows['symboltoken']),
                "transactiontype": str(rows['transactiontype']), 
                "exchange": str(rows['exchange']),
                "ordertype": str(rows['ordertype']), 
                "producttype": str(rows['producttype']),
                "duration": str(rows['duration']), 
                "price": str(rows['price']), 
                "quantity": str(rows['quantity']),
                "triggerprice": str(rows['triggerprice'])}

    trade_list.append(new_dict)

print(trade_list)

image.png

What you see in the above screenshot is basically a list that has the orderparams dictionary for each row of trade we uploaded; now, we just need to pass each and every dict into the main obj we created earlier to place a trade on the account.

5. Creating a Function to Place Orders

Let's create a quick function where we can just pass the orderparams to place the order.

def place_order(orderparams):
    try:
        orderID = obj.placeOrder(orderparams)
        print("The order id is: {}".format(orderID))
    except Exception as e:
        print("Order placement failed: {}".format(e))

6. Placing Orders using the Function in a Normal Loop

start = datetime.now()

for trades in trade_list:
    place_order(trades)

end = datetime.now()
print(start - end)

image.png

As you can see in the above results, we placed around 34 trades in 12.33 seconds in a normal loop where we waited for each request to return an orderId and then make the follow-on request to place another trade. This time taken should be more or less in the range of 11-14 seconds, depending on your internet speed.

7. Creating a Function to Place Order Concurrently

def place_multiple_orders(tradeList):
    with concurrent.futures.ThreadPoolExecutor() as executor:
        executor.map(place_order, tradeList)

The above looks like a very simple function that uses Python's internal library concurrent.futures in the background. What is happening behind the scenes is magic where Python basically creates multiple threads (think of it as an isolated space in your brain to do various tasks), and the executor.map uses the normal place_order function and passes our list of orderparams into it.

place_multiple_orders(trade_list)

Let's see the output of the above and see how quickly we are able to place 34 trades which earlier took ~12.5 seconds to execute.

image.png

Oh Shit ๐Ÿ˜Ÿ, what happened here? Why are we getting these ugly errors? Well, that is because of rate-limiting factors that SmartAPI has; if you visit this page, you will see how many requests you are allowed to make every second for each type of function.

image.png

As you can see, we are only allowed to place 10 trades per second, but in parallel processing, our function is making more than 10 requests within that one second which is why SmartAPI's server is rejecting it and sending an error code. Well, important stuff to keep in mind, but how can we solve this now?

As you can see, the place_multiple_orders function uses the place_order function to actually place orders concurrently, so let's put in a wait time of one second in our place_order function in case of an Exception and then try again, maybe that will solve the problem?

def place_order(orderparams):
    try:
        orderID = obj.placeOrder(orderparams)
        print("The order id is: {}".format(orderID))
    except Exception as e:  #1st error
        time.sleep(1) #ensure you have imported time at the top
        try:
            orderID = obj.placeOrder(orderparams)
            print("The order id is: {}".format(orderID))
        except Exception as e: #2nd error
            time.sleep(1)
            try:
                orderID = obj.placeOrder(orderparams)
                print("The order id is: {}".format(orderID))
            except Exception as e:
                print("Order placement failed: {}".format(e))

def place_multiple_orders(tradeList):
    with concurrent.futures.ThreadPoolExecutor() as executor:
        executor.map(place_order, tradeList)

So in the above place_order function, if while placing an order, we get an error, the code will force a 1-second wait which will help us abide by the rate limits of SmartAPI, if the code fails for the second time, we force another 1-second wait and then execute the trade, if it fails again, it will be raised as an Exception, but again, I don't think you will be executing 35 trades in one single go in one single account. (Think of all the Brokerage charges Angel Broking will make, just kidding ๐Ÿ˜›)

8. Placing Orders Concurrently using place_multiple_orders Function

start = datetime.now()

place_multiple_orders(trade_list)

end = datetime.now()
print(end - start)

image.png

Tada ๐ŸŽ‰, we were able to place the same 34 orders in almost 1/4th of the time (3.42 seconds); that's the power of concurrency. Looking at the image above, when you notice the highlighted parts, you will see some trades were placed faster than other trades; for example, an order ending with 42167 was placed before orders ending with 42165 and 42166.

9. Conclusion

First of all, I hope you liked this article; I recently implemented this code for one of my clients who wanted parallel execution in their strategy, so it was well researched, and the production code is more advanced, but the code in this article has been regularized and made easy to understand.

You will find several articles on Concurrency on the Internet, but none of them shows a live example in this detail.

10. Closing Points

I hope you did understand the concepts and the code; if you do not, please do ping me on Linkedin or you can schedule a call with me via TopMate.

***You can also find the full code on Github by clicking here. ***

If you like the content on Trade With Python, please do consider subscribing to our newsletter (you will find an option at the top of the page), and lastly, if you would like to keep our spirits high and produce more content like this, you can BuyMeACoffee by clicking here or on the button below.

๐Ÿ’ก
Please note we haven't made any new posts since Nov 2021 on this blog, you are free to subscribe to the mailing list, however, you will be auto-added to the new blog's (thealtinvestor.in) mailing list as well.

Did you find this article valuable?

Support Trade With Python by becoming a sponsor. Any amount is appreciated!

ย