Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add futures pricing quote #99

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -420,5 +420,79 @@ can do the following.
.
}

Getting a Stock Futures Quote
------------------------------

You can get the current Futures quote for any stock, given its stock code and expiry date using the command below. Here, we fetch the current futures quote of State Bank of India. The NSE stock code for
State Bank of India is **SBIN**.

>>> fq = nse.get_futures_quote('sbin',expiry='29OCT2020')
>>> from pprint import pprint
>>> pprint(fq)
{'annualisedVolatility': 54.99,
'bestBuy': 2.84,
'bestSell': 3.57,
'buyPrice1': 197.95,
'buyPrice2': 197.85,
'buyPrice3': 197.0,
'buyPrice4': 196.7,
'buyPrice5': 196.35,
'buyQuantity1': 3000.0,
'buyQuantity2': 3000.0,
'buyQuantity3': 3000.0,
'buyQuantity4': 9000.0,
'buyQuantity5': 3000.0,
'change': '-5.20',
'changeinOpenInterest': 87000.0,
'clientWisePositionLimits': None,
'closePrice': 198.05,
'dailyVolatility': 2.88,
'expiryDate': '29OCT2020',
'highPrice': 205.2,
'impliedVolatility': None,
'instrumentType': 'FUTSTK',
'lastPrice': 198.1,
'lowPrice': 196.0,
'ltp': 3.2,
'marketLot': 3000.0,
'marketWidePositionLimits': 746624593.0,
'numberOfContractsTraded': 134.0,
'openInterest': 591000.0,
'openPrice': 205.05,
'optionType': None,
'pChange': '-2.56',
'pchangeinOpenInterest': 17.26,
'premiumTurnover': None,
'prevClose': 203.3,
'sellPrice1': 198.25,
'sellPrice2': 198.3,
'sellPrice3': 199.45,
'sellPrice4': 199.5,
'sellPrice5': 200.2,
'sellQuantity1': 3000.0,
'sellQuantity2': 3000.0,
'sellQuantity3': 9000.0,
'sellQuantity4': 45000.0,
'sellQuantity5': 6000.0,
'settlementPrice': 198.05,
'strikePrice': None,
'totalBuyQuantity': 78000.0,
'totalSellQuantity': 129000.0,
'turnoverinRsLakhs': 805.81,
'underlying': 'SBIN',
'underlyingValue': 196.8,
'vwap': 200.45}
>>>

.. note::

The argument expiry is optional. You can also pass only Stock code as an argument.
In that case, it will fetch the result for the next upcoming expiry date.

.. warning::

Always pass a valid expiry date. Otherwise the result will be a blank dict.
Also, take care of the proper format of expiry date.


.. disqus::
69 changes: 69 additions & 0 deletions nse.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(self):
self.headers = self.nse_headers()
# URL list
self.get_quote_url = 'https://www1.nseindia.com/live_market/dynaContent/live_watch/get_quote/GetQuote.jsp?'
self.get_derivative_quote_url = 'https://www1.nseindia.com/live_market/dynaContent/live_watch/get_quote/GetQuoteFO.jsp?'
self.stocks_csv_url = 'http://www1.nseindia.com/content/equities/EQUITY_L.csv'
self.top_gainer_url = 'http://www1.nseindia.com/live_market/dynaContent/live_analysis/gainers/niftyGainers1.json'
self.top_loser_url = 'http://www1.nseindia.com/live_market/dynaContent/live_analysis/losers/niftyLosers1.json'
Expand Down Expand Up @@ -143,6 +144,19 @@ def is_valid_code(self, code):
else:
return False

def is_valid_fno_code(self, code):
"""
Returns true if a stock has Futures and Options derivative
:param code: a string stock code
:return: Boolean
"""
if code:
stock_codes = self.get_fno_lot_sizes()
if code.upper() in stock_codes.keys():
return True
else:
return False

def get_quote(self, code, as_json=False):
"""
gets the quote for a given stock code
Expand Down Expand Up @@ -182,6 +196,46 @@ def get_quote(self, code, as_json=False):
else:
return None

def get_futures_quote(self, code, expiry='NODATEGIVEN', as_json=False):
"""
gets the quote of futures derivative for a given stock code
:param code:
:return: dict or None
:raises: HTTPError, URLError
"""
code = code.upper()
expiry = expiry.upper()
if self.is_valid_fno_code(code):
url = self.build_url_for_futures_quote(code, expiry)
req = Request(url, None, self.headers)
# this can raise HTTPError and URLError, but we are not handling it
# north bound APIs should use it for exception handling
res = self.opener.open(req)

# for py3 compat covert byte file like object to
# string file like object
res = byte_adaptor(res)
res = res.read()
# Now parse the response to get the relevant data
match = re.search(\
r'<div\s+id="responseDiv"\s+style="display:none">(.*?)</div>',
res, re.S
)
try:
buffer = match.group(1).strip()
# commenting following two lines because now we are not using ast and instead
# relying on json's ability to do parsing. Should be much faster and more
# reliable.
#buffer = js_adaptor(buffer)
#response = self.clean_server_response(ast.literal_eval(buffer)['data'][0])
response = self.clean_server_response(json.loads(buffer)['data'][0])
except SyntaxError as err:
raise Exception('ill formatted response')
else:
return self.render_response(response, as_json)
else:
return None

def get_top_gainers(self, as_json=False):
"""
:return: a list of dictionaries containing top gainers of the day
Expand Down Expand Up @@ -380,6 +434,21 @@ def build_url_for_quote(self, code):
else:
raise Exception('code must be string')

def build_url_for_futures_quote(self, code, expiry):
"""
builds a url which can be requested for futures dericative of given stock code
:param code: string containing stock code.
:return: a url object
"""
if code is not None and type(code) is str:
if expiry=='NODATEGIVEN':
encoded_args = urlencode([('underlying', code), ('instrument', 'FUTSTK')])
else:
encoded_args = urlencode([('underlying', code), ('instrument', 'FUTSTK'), ('expiry', expiry)])
return self.get_derivative_quote_url + encoded_args
else:
raise Exception('code must be string')

def clean_server_response(self, resp_dict):
"""cleans the server reponse by replacing:
'-' -> None
Expand Down
32 changes: 32 additions & 0 deletions tests/nse_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ def test_negative_build_url_for_quote(self):
for test_code in negative_codes:
url = self.nse.build_url_for_quote(test_code)

def test_build_url_for_futures_quote(self):
test_code = 'infy'
url = self.nse.build_url_for_futures_quote(test_code)
# 'test_code' should be present in the url
self.assertIsNotNone(re.search(test_code, url))

def test_negative_build_url_for_futures_quote(self):
negative_codes = [1, None]
with self.assertRaises(Exception):
for test_code in negative_codes:
url = self.nse.build_url_for_futures_quote(test_code)

def test_response_cleaner(self):
test_dict = {
'a': '10',
Expand Down Expand Up @@ -111,14 +123,34 @@ def test_get_quote(self):
# the price changed in that very moment.
self.assertDictEqual(resp, json.loads(json_resp))

def test_get_futures_quote(self):
code = 'sbin'
resp = self.nse.get_futures_quote(code)
self.assertIsInstance(resp, dict)
# test json response
json_resp = self.nse.get_futures_quote(code, as_json=True)
self.assertIsInstance(json_resp, str)
# reconstruct the original dict from json
# this test may raise false alarms in case the
# the price changed in that very moment.
self.assertDictEqual(resp, json.loads(json_resp))

def test_is_valid_code(self):
code = 'infy'
self.assertTrue(self.nse.is_valid_code(code))

def test_is_valid_fno_code(self):
code = 'infy'
self.assertTrue(self.nse.is_valid_fno_code(code))

def test_negative_is_valid_code(self):
wrong_code = 'in'
self.assertFalse(self.nse.is_valid_code(wrong_code))

def test_negative_is_valid_fno_code(self):
wrong_code = 'in'
self.assertFalse(self.nse.is_valid_fno_code(wrong_code))

def test_get_top_gainers(self):
res = self.nse.get_top_gainers()
self.assertIsInstance(res, list)
Expand Down