1- from collections import OrderedDict , namedtuple
2- from Crypto import Random
3- from Crypto .Cipher import AES
1+ from collections import namedtuple
2+ from cryptography .hazmat .primitives .ciphers import Cipher , algorithms , modes
43from time import sleep
54from urllib .parse import urlparse , parse_qs
65import base64
@@ -61,6 +60,7 @@ def __pad(data):
6160 def __unpad (data ):
6261 return data [:- (data [- 1 ] if type (data [- 1 ]) == int else ord (data [- 1 ]))]
6362
63+ @staticmethod
6464 def __bytes_to_key (data , salt , output = 48 ):
6565 assert len (salt ) == 8 , len (salt )
6666 data += salt
@@ -73,12 +73,12 @@ def __bytes_to_key(data, salt, output=48):
7373
7474 @staticmethod
7575 def encrypt (message , passphrase ):
76- salt = Random . new (). read (8 )
76+ salt = os . urandom (8 )
7777 key_iv = CryptoJsAES .__bytes_to_key (passphrase , salt , 32 + 16 )
7878 key = key_iv [:32 ]
7979 iv = key_iv [32 :]
80- aes = AES . new (key , AES . MODE_CBC , iv )
81- return base64 .b64encode (b"Salted__" + salt + aes .encrypt ( CryptoJsAES .__pad (message )))
80+ aes = Cipher ( algorithms . AES (key ), modes . CBC ( iv ) )
81+ return base64 .b64encode (b"Salted__" + salt + aes .encryptor (). update ( CryptoJsAES .__pad (message )) + aes . encryptor (). finalize ( ))
8282
8383 @staticmethod
8484 def decrypt (encrypted , passphrase ):
@@ -88,8 +88,8 @@ def decrypt(encrypted, passphrase):
8888 key_iv = CryptoJsAES .__bytes_to_key (passphrase , salt , 32 + 16 )
8989 key = key_iv [:32 ]
9090 iv = key_iv [32 :]
91- aes = AES . new (key , AES . MODE_CBC , iv )
92- return CryptoJsAES .__unpad (aes .decrypt (encrypted [16 :]))
91+ aes = Cipher ( algorithms . AES (key ), modes . CBC ( iv ) )
92+ return CryptoJsAES .__unpad (aes .decryptor . update (encrypted [16 :]) + aes . decryptor (). finalize ( ))
9393
9494class AliceBlue :
9595 """ AliceBlue Class for all operations related to AliceBlue Server"""
@@ -142,20 +142,20 @@ def __init__(self, username, session_id, master_contracts_to_download = None):
142142 self .__order_update_callback = None
143143 self .__market_status_messages_callback = None
144144 self .__exchange_messages_callback = None
145- self .__oi_callback = None
146- self .__dpr_callback = None
147145 self .__subscribers = {}
148146 self .__market_status_messages = []
149147 self .__exchange_messages = []
150148 # Initialize Depth data
151149 self .__depth_data = {}
150+ self .__ltp = {}
152151
153152 try :
154153 self .get_profile ()
155154 except Exception as e :
156155 raise Exception (f"Couldn't get profile info with credentials provided '{ e } '" )
157156 self .__master_contracts_by_token = {}
158157 self .__master_contracts_by_symbol = {}
158+ self .__get_master_contract ("INDICES" )
159159 if (master_contracts_to_download == None ):
160160 for e in self .__enabled_exchanges :
161161 self .__get_master_contract (e )
@@ -248,8 +248,11 @@ def __extract_tick_data(self, data):
248248 data ["instrument" ] = self .get_instrument_by_token (data .pop ("e" ), int (data .pop ("tk" )))
249249 if ("ts" in data ): # Symbol
250250 data .pop ("ts" )
251+ if (data ["instrument" ].symbol not in self .__ltp ):
252+ self .__ltp [data ["instrument" ].symbol ] = 0
251253 if ("lp" in data ): # Last Traded Price
252- data ["ltp" ] = float (data .pop ("lp" ))
254+ self .__ltp [data ["instrument" ].symbol ] = float (data .pop ("lp" ))
255+ data ["ltp" ] = self .__ltp [data ["instrument" ].symbol ]
253256 if ("pc" in data ): # percentage change
254257 data ["percent_change" ] = float (data .pop ("pc" ))
255258 if ("cv" in data ): # change value (absolute change in price)
@@ -399,7 +402,7 @@ def __on_data_callback(self, ws=None, message=None, data_type=None, continue_fla
399402 # message = '{"t":"tf","e":"NSE","tk":"1594","ft":"1662025326","v":"7399482","bp1":"1464.25","sp1":"1464.35","bq1":"460","sq1":"11"}'
400403 # message = '{"t":"df","e":"NSE","tk":"1594","ft":"1662025327","v":"7400196","ltt":"15:12:07","tbq":"510593","tsq":"2364472","bp1":"1464.55","sp1":"1464.90","bp2":"1464.30","sp2":"1464.95","bp3":"1464.25","sp3":"1465.00","bp4":"1464.20","sp4":"1465.05","bp5":"1464.15","sp5":"1465.10","bq1":"1","sq1":"301","bq2":"598","sq2":"61","bq3":"83","sq3":"3573","bq4":"175","sq4":"300","bq5":"482","sq5":"51","bo2":"5","so2":"4","bo3":"2","so3":"42","bo4":"3","so4":"1","bo5":"5","so5":"2"}'
401404 # message = '{"t":"df","e":"NSE","tk":"1594","ft":"1662025324","v":"7397757","ltq":"2","ltt":"15:12:04","tbq":"513958","tsq":"2373240","sp1":"1464.95","sp2":"1465.00","sp3":"1465.05","sp4":"1465.10","sp5":"1465.15","bq1":"275","sq1":"37","sq2":"3472","sq3":"300","sq4":"26","sq5":"110","bo1":"5","so1":"8","so2":"40","so3":"1","so4":"2","so5":"4"}'
402- #logging.info(f"message - {message}")
405+ # logging.info(f"message - {message}")
403406 if (type (ws ) is not websocket .WebSocketApp ): # This workaround is to solve the websocket_client's compatiblity issue of older versions. ie.0.40.0 which is used in upstox. Now this will work in both 0.40.0 & newer version of websocket_client
404407 message = ws
405408 data = json .loads (message )
@@ -409,25 +412,21 @@ def __on_data_callback(self, ws=None, message=None, data_type=None, continue_fla
409412 if (self .__subscribe_callback is not None ):
410413 data .pop ("t" )
411414 data = self .__extract_tick_data (data )
412- logging .info (f"Tick Data Acknowledgement { data } " ) # @TODO remove
413415 self .__subscribe_callback (data )
414416 elif (data ["t" ] == "dk" ): # depth data acknowledgment
415417 if (self .__subscribe_callback is not None ):
416418 data .pop ("t" )
417419 data = self .__extract_depth_data (data )
418- logging .info (f"Depth Data Acknowledgement { data } " ) # @TODO remove
419420 self .__subscribe_callback (data )
420421 elif (data ["t" ] == "tf" ): # tick data feed
421422 if (self .__subscribe_callback is not None ):
422423 data .pop ("t" )
423424 data = self .__extract_tick_data (data )
424- logging .info (f"Tick feed { data } " ) # @TODO remove
425425 self .__subscribe_callback (data )
426426 elif (data ["t" ] == "df" ): # depth data feed
427427 if (self .__subscribe_callback is not None ):
428428 data .pop ("t" )
429429 data = self .__extract_depth_data (data )
430- logging .info (f"depth feed { data } " ) # @TODO remove
431430 self .__subscribe_callback (data )
432431
433432 def __on_close_callback (self , * arguments , ** keywords ):
@@ -483,9 +482,7 @@ def start_websocket(self, subscribe_callback = None,
483482
484483 # Create websocket session
485484 data = {"loginType" : "API" }
486- r = self .__api_call_helper ('createWsSession' , Requests .POST , data )
487- # ws_session_id = r["result"]["wsSess"]
488- # logging.info(f"ws session id {ws_session_id}")
485+ self .__api_call_helper ('createWsSession' , Requests .POST , data )
489486
490487 # Create websocket connection
491488 self .__websocket_connected = False
@@ -520,7 +517,7 @@ def get_profile(self):
520517 "bse_com" : "BCO" }
521518 profile = self .__api_call_helper ('profile' , Requests .GET )
522519 x = profile ['exchEnabled' ].split ("|" )
523- self .__enabled_exchanges = [exch_dt [i ] for i in x ]
520+ self .__enabled_exchanges = [exch_dt [i ] for i in x if i in exch_dt ]
524521 return profile
525522
526523 def get_balance (self ):
@@ -735,7 +732,7 @@ def subscribe(self, instrument, live_feed_type):
735732 for _instrument in instrument :
736733 if not isinstance (_instrument , Instrument ):
737734 raise TypeError ("Required parameter instrument is not of type Instrument" )
738- subscribe_string = f"#{ _instrument .exchange } |{ int (_instrument .token )} "
735+ subscribe_string + = f"#{ _instrument .exchange } |{ int (_instrument .token )} "
739736 self .__subscribers [_instrument ] = live_feed_type
740737 else :
741738 if not isinstance (instrument , Instrument ):
@@ -813,7 +810,7 @@ def get_instrument_for_fno(self, symbol, expiry_date, is_fut=False, strike=None,
813810 return
814811 matches = []
815812 for i in res :
816- sp = i .symbol .split (' ' )
813+ sp = i .name .split (' ' )
817814 if (sp [0 ] == symbol ):
818815 if (i .expiry == expiry_date ):
819816 matches .append (i )
@@ -822,7 +819,7 @@ def get_instrument_for_fno(self, symbol, expiry_date, is_fut=False, strike=None,
822819 if ('FUT' in i .symbol ):
823820 return i
824821 else :
825- sp = i .symbol .split (' ' )
822+ sp = i .name .split (' ' )
826823 if ((sp [- 1 ] == 'CE' ) or (sp [- 1 ] == 'PE' )): # Only option scrips
827824 if (float (sp [- 2 ]) == float (strike )):
828825 if (is_CE == True ):
@@ -912,38 +909,41 @@ def __get_master_contract(self, exchange):
912909 # Write to temp file
913910 with open (tmp_file , 'w' ) as fo :
914911 fo .write (json .dumps (body ))
915- master_contract_by_token = OrderedDict ()
916- master_contract_by_symbol = OrderedDict ()
917- for sub in body :
918- if (sub != "contract_date" ):
919- for scrip in body [sub ]:
912+ for exch in body :
913+ if (exch != "contract_date" ):
914+ for scrip in body [exch ]:
920915 # convert token
921- token = int (scrip [' token' ])
916+ token = int (scrip [" token" ])
922917
923918 # convert symbol to upper
924- symbol = scrip ['trading_symbol' ]
919+ if ("trading_symbol" in scrip ):
920+ symbol = scrip ["trading_symbol" ]
921+ else :
922+ symbol = scrip ["symbol" ]
925923
926924 # convert expiry to none if it's non-existent
927- if (' expiry_date' in scrip ):
928- expiry = datetime .datetime .fromtimestamp (scrip ['expiry_date' ]/ 1000 ).date ()
925+ if (" expiry_date" in scrip ):
926+ expiry = datetime .datetime .fromtimestamp (scrip ['expiry_date' ]/ 1000 , tz = pytz . utc ).date ()
929927 else :
930928 expiry = None
931929
932930 # convert lot size to int
933- if ('lot_size' in scrip ):
934- lot_size = scrip ['lot_size' ]
935- else :
936- lot_size = None
931+ lot_size = None
932+ if ("lot_size" in scrip ):
933+ lot_size = scrip ["lot_size" ]
937934
938- # Name & Exchange
939- name = scrip ['formatted_ins_name' ]
940- exch = scrip ['exch' ]
941-
935+ # Name
936+ name = None
937+ if ("formatted_ins_name" in scrip ):
938+ name = scrip ["formatted_ins_name" ]
939+
942940 instrument = Instrument (exch , token , symbol , name , expiry , lot_size )
943- master_contract_by_token [token ] = instrument
944- master_contract_by_symbol [symbol ] = instrument
945- self .__master_contracts_by_token [exchange ] = master_contract_by_token
946- self .__master_contracts_by_symbol [exchange ] = master_contract_by_symbol
941+ if (exch not in self .__master_contracts_by_token ):
942+ self .__master_contracts_by_token [exch ] = {}
943+ if (exch not in self .__master_contracts_by_symbol ):
944+ self .__master_contracts_by_symbol [exch ] = {}
945+ self .__master_contracts_by_token [exch ][token ] = instrument
946+ self .__master_contracts_by_symbol [exch ][symbol ] = instrument
947947
948948 def __api_call_helper (self , name , http_method , data = None , params = None ):
949949 # helper formats the url and reads error codes nicely
@@ -956,7 +956,6 @@ def __api_call_helper(self, name, http_method, data=None, params=None):
956956 return response .json ()
957957
958958 def __api_call (self , url , http_method , data ):
959- #logger.debug('url:: %s http_method:: %s data:: %s headers:: %s', url, http_method, data, headers)
960959 # Update header with Session ID
961960 headers = { "Content-Type" : "application/json" ,
962961 "Authorization" : f"Bearer { self .__username } { self .__session_id } " }
0 commit comments