4
\$\begingroup\$
import time
import csv
import argparse
from nsetools import Nse
from colorama import Fore

nse = Nse()
t = time.time()

CSV file contains the following data, I added it so that others may know the contents of the file. In the actual program I use a real CSV file in FILE_LOCATION variable.

CSV file contents:

Instrument,Qty,Avg price
APLAPOLLO,3,949.95
AVANTIFEED,6,554.55
BALAMINES,9,337.72
BANCOINDIA,15,217
DCMSHRIRAM,12,261.4
GHCL,12,267.2
GIPCL,27,101.95
JAMNAAUTO,15,182.1
JBCHEPHARM,15,344.85
KEI,24,143.95
KPRMILL,6,569.65
KRBL,9,312
MPHASIS,6,533.95
SHEMAROO,2,413.25

Code:

FILE_LOCATION = '' # empty because path not relevant to others

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--realtime', help='Obtain realtime stock\
                    quotes', action='store_true')
args = parser.parse_args()


def get_closing(stock):
    """Function to obtain closePrice or buyPrice1 of stocks"""
    if args.realtime:
        return nse.get_quote(stock)['buyPrice1']
    else:
        return nse.get_quote(stock)['closePrice']


def expenses_calc(buy, sell, qty):
    """Function to calculate the relevant expenses"""
    stt = 0.10
    ttc = 0.00325
    service_tax = 15.0
    sebi_charges = 0.0002
    stamp_duty = 0.01
    cdsl = 13.5
    turnover = (buy * qty) + (sell * qty)
    stt_total = (stt / 100) * turnover
    total_tran_charge = (ttc / 100) * turnover
    service_tax = (service_tax / 100) * total_tran_charge
    sebi_charges = (sebi_charges / 100) * turnover
    stamp_duty = (stamp_duty / 100) * turnover
    total_tax_and_charges = cdsl + stt_total + total_tran_charge + service_tax
    + sebi_charges + stamp_duty
    net_profit = (sell * qty) - (buy * qty) - total_tax_and_charges
    return net_profit


# nifty
nifty_buy = 8262.70
nifty = nse.get_index_quote('nifty 50')
nifty_close = nifty['lastPrice']
nifty_percent = ((nifty_close - nifty_buy) / nifty_buy) * 100


# calculating total investment
with open(FILE_LOCATION, 'r') as f:
    next(f)
    total_investment = sum(int(row[1]) * float(row[2]) for row in
                           csv.reader(f))

# calculating current value of investment
with open(FILE_LOCATION, 'r') as f:
    next(f)
    current_value = sum(get_closing(row[0]) * int(row[1]) for row in
                        csv.reader(f))

gross_profit = current_value - total_investment
profit_loss_percent = (
    (current_value - total_investment) / total_investment) * 100

# calculating net profit post expenses/taxes
with open(FILE_LOCATION, 'r') as f:
    next(f)
    net_profit = sum((expenses_calc(float(row[2]), get_closing(
        row[0]), int(row[1]))) for row in csv.reader(f))

print('-' * 80)
# printing total investment
print('Total Investment: ' + unicode(u"\u20B9") +
      '{:,.2f}'.format(total_investment))

# checking if my portfolio is in profit
if current_value > total_investment:
    print('Current Value: ' + unicode(u"\u20B9") + Fore.GREEN +
          '{:,.2f}'.format(current_value) + Fore.RESET)
else:
    print('Current Value: ' + unicode(u"\u20B9") + Fore.RED +
          '{:,.2f}'.format(current_value) + Fore.RESET)

# printing gross profit
if current_value > total_investment:
    print('Gross Profit: ' + unicode(u"\u20B9") + Fore.GREEN +
          '{:.2f}'.format(gross_profit) + Fore.RESET)
else:
    print('Gross Profit: ' + unicode(u"\u20B9") + Fore.RED +
          '{:.2f}'.format(gross_profit) + Fore.RESET)

# printing net profit
if net_profit > gross_profit:
    print('Net Profit: ' + unicode(u"\u20B9") + Fore.GREEN +
          '{:.2f}'.format(net_profit) + Fore.RESET)
else:
    print('Net Profit: ' + unicode(u"\u20B9") + Fore.RED +
          '{:.2f}'.format(net_profit) + Fore.RESET)

print('-' * 80)

# printing protfolio profit/loss
if current_value > total_investment:
    print('Portfolio P/L: ' + Fore.GREEN +
          '{:.2f}%'.format(profit_loss_percent) + Fore.RESET)
else:
    print('Portfolio P/L: ' + Fore.RED +
          '{:.2f}%'.format(profit_loss_percent) + Fore.RESET)

# checking if market is in profit
if nifty_close > nifty_buy:
    print('Nifty P/L: ' + Fore.GREEN +
          '{:.2f}%'.format(nifty_percent) + Fore.RESET)
else:
    print('Nifty P/L: ' + Fore.RED +
          '{:,.2f}%'.format(nifty_percent) + Fore.RESET)

# Mocking or Congratulating myself if in loss or profit
if nifty_percent > profit_loss_percent:
    print(Fore.RED + "Failure, the market beat you" + Fore.RESET)
else:
    print(Fore.GREEN + "Congratulations, you beat the market!" + Fore.RESET)

print('-' * 80)

print("Completed in ", time.time() - t)

Average Speed of calculating Total Investment, Current Value and Net Profit:

Total Investment: ₹43182.63
completed in 0.00 seconds

Current Value: ₹43478.05
completed in 17.86 seconds

Net Profit: ₹16.52
completed in 12.31 seconds

As of now the script does its job, but takes anything from 26-34 seconds to finish running. To speed things up I used generator expressions where applicable instead of for loops, but did not notice any major speed gains.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ It looks like you have three 'with open(..)' statements in your code, but is there any reason they need to run sequentially instead of in parallel? If the answer is no then you could wrap them as individual functions and call them separately and simultaneously. That should cut down the runtime drastically. I can write a better response once you have replied. Thanks! \$\endgroup\$
    – Projski
    Commented Jan 11, 2017 at 6:28
  • \$\begingroup\$ I tried calculating Total Investment, Current Value and Net Profit using a single 'with open(..)' statement. It only calculates Total Investment because I assume the file gets closed after its calculated. \$\endgroup\$
    – Dana
    Commented Jan 11, 2017 at 6:51
  • 1
    \$\begingroup\$ I modified the code to make it easy to copy/paste my csv file contents. \$\endgroup\$
    – Dana
    Commented Jan 13, 2017 at 14:01

1 Answer 1

1
\$\begingroup\$

I downloaded your code and ran a version of it on my own machine. It appears that the slowest part of the code is the individual calls to query the stock price using the nse library.

There are two approaches I would recommend pursuing to solve this. One is that you could run your script for gathering the stock prices as a separate, continuous process. For instance, you keep a database or file that uses the nse library to look up and update the stock price for each ticker symbol, and it runs every 10 minutes or so (depending on how frequently or fresh you need that data).

The second approach I would recommend is to explore how to use the threading library. In Introducing Python author Bill Lubanovic elaborates:

... for Python, the recommendations are as follows:

  • Use threads for I/O bound problems

  • Use processes, networking, or events...for CPU-bound problems

Since you are definitely running into an I/O issue here, threading is your best approach to handle it. I recommend the Lubanovic book as a tool for getting a handle on threading, or something similar that will warn you of the pitfalls and challenges. For instance, I checked the documentation for the nse library and while I couldn't find any rate limiting warnings, that doesn't mean there isn't one. It's generally not considered polite to hit an API as hard and as quickly as possible to get results as fast as you can. The solution is to limit the number of threads you generate.

Since this is code review, if I may be so bold as to make a few other suggestions to your code:

  • It's good practice and may give you a performance boost if you wrap your code in a def main() function and then run it by having if __name__ == "__main__:" at the bottom of your file.

  • Using with open(FILE_LOCATION) as f to access the same file repeatedly in the same script seems awkward to me. When you see code repeated three times, it starts to give off a "bad code smell" that suggests it is time to refactor. You may find your code more readable if you have a single function for reading that file and then converting it into a dictionary that your other functions can access.

  • Adding a from __future__ import print_function, division will make it easier for you to port your code into Python3 in the future, which you may want to consider doing, since there have been updates made to how concurrency can be handled.

  • I found the print statement section at the bottom of your code hard to read. I don't have any specific advice for improvements, other than there are ways to decouple the content from the code (templates for instance) that would make it easier to read and also easier to maintain.

Hope that helps, let me know if you have questions and good luck!

\$\endgroup\$
2
  • \$\begingroup\$ I too realized that individually fetching the stock quotes two separate times, once for calculating current value and once for calculating net profit was the culprit. I had asked a fresh question on stackoverflow to obtain help on fetching quotes in parallel. stackoverflow.com/questions/41693675/…. Could you post an example there on using threads to fetch all the quotes in parallel? I'll look into that book when I have more time. \$\endgroup\$
    – Dana
    Commented Jan 20, 2017 at 4:14
  • \$\begingroup\$ I'll take a look at the stackoverflow link, but for this specific post on code review is this answer acceptable to resolve this question? \$\endgroup\$
    – Projski
    Commented Jan 22, 2017 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.