Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
angel-one
GitHub Repository: angel-one/smartapi-python
Path: blob/main/SmartApi/smartConnect.py
404 views
1
from six.moves.urllib.parse import urljoin
2
import json
3
import logging
4
import SmartApi.smartExceptions as ex
5
import requests
6
from requests import get
7
import re, uuid
8
import socket
9
import os
10
import logzero
11
from logzero import logger
12
import time
13
import ssl
14
from SmartApi.version import __version__, __title__
15
16
log = logging.getLogger(__name__)
17
18
class SmartConnect(object):
19
#_rootUrl = "https://openapisuat.angelbroking.com"
20
_rootUrl="https://apiconnect.angelone.in" #prod endpoint
21
#_login_url ="https://smartapi.angelbroking.com/login"
22
_login_url="https://smartapi.angelone.in/publisher-login" #prod endpoint
23
_default_timeout = 7 # In seconds
24
25
_routes = {
26
"api.login":"/rest/auth/angelbroking/user/v1/loginByPassword",
27
"api.logout":"/rest/secure/angelbroking/user/v1/logout",
28
"api.token": "/rest/auth/angelbroking/jwt/v1/generateTokens",
29
"api.refresh": "/rest/auth/angelbroking/jwt/v1/generateTokens",
30
"api.user.profile": "/rest/secure/angelbroking/user/v1/getProfile",
31
32
"api.order.place": "/rest/secure/angelbroking/order/v1/placeOrder",
33
"api.order.placefullresponse": "/rest/secure/angelbroking/order/v1/placeOrder",
34
"api.order.modify": "/rest/secure/angelbroking/order/v1/modifyOrder",
35
"api.order.cancel": "/rest/secure/angelbroking/order/v1/cancelOrder",
36
"api.order.book":"/rest/secure/angelbroking/order/v1/getOrderBook",
37
38
"api.ltp.data": "/rest/secure/angelbroking/order/v1/getLtpData",
39
"api.trade.book": "/rest/secure/angelbroking/order/v1/getTradeBook",
40
"api.rms.limit": "/rest/secure/angelbroking/user/v1/getRMS",
41
"api.holding": "/rest/secure/angelbroking/portfolio/v1/getHolding",
42
"api.position": "/rest/secure/angelbroking/order/v1/getPosition",
43
"api.convert.position": "/rest/secure/angelbroking/order/v1/convertPosition",
44
45
"api.gtt.create":"/gtt-service/rest/secure/angelbroking/gtt/v1/createRule",
46
"api.gtt.modify":"/gtt-service/rest/secure/angelbroking/gtt/v1/modifyRule",
47
"api.gtt.cancel":"/gtt-service/rest/secure/angelbroking/gtt/v1/cancelRule",
48
"api.gtt.details":"/rest/secure/angelbroking/gtt/v1/ruleDetails",
49
"api.gtt.list":"/rest/secure/angelbroking/gtt/v1/ruleList",
50
51
"api.candle.data":"/rest/secure/angelbroking/historical/v1/getCandleData",
52
"api.oi.data":"/rest/secure/angelbroking/historical/v1/getOIData",
53
"api.market.data":"/rest/secure/angelbroking/market/v1/quote",
54
"api.search.scrip": "/rest/secure/angelbroking/order/v1/searchScrip",
55
"api.allholding": "/rest/secure/angelbroking/portfolio/v1/getAllHolding",
56
57
"api.individual.order.details": "/rest/secure/angelbroking/order/v1/details/",
58
"api.margin.api" : 'rest/secure/angelbroking/margin/v1/batch',
59
"api.estimateCharges" : 'rest/secure/angelbroking/brokerage/v1/estimateCharges',
60
"api.verifyDis" : 'rest/secure/angelbroking/edis/v1/verifyDis',
61
"api.generateTPIN" : 'rest/secure/angelbroking/edis/v1/generateTPIN',
62
"api.getTranStatus" : 'rest/secure/angelbroking/edis/v1/getTranStatus',
63
"api.optionGreek" : 'rest/secure/angelbroking/marketData/v1/optionGreek',
64
"api.gainersLosers" : 'rest/secure/angelbroking/marketData/v1/gainersLosers',
65
"api.putCallRatio" : 'rest/secure/angelbroking/marketData/v1/putCallRatio',
66
"api.oIBuildup" : 'rest/secure/angelbroking/marketData/v1/OIBuildup',
67
"api.nseIntraday" : 'rest/secure/angelbroking/marketData/v1/nseIntraday',
68
"api.bseIntraday" : 'rest/secure/angelbroking/marketData/v1/bseIntraday',
69
}
70
71
try:
72
clientPublicIp= " " + get('https://api.ipify.org').text
73
if " " in clientPublicIp:
74
clientPublicIp=clientPublicIp.replace(" ","")
75
hostname = socket.gethostname()
76
clientLocalIp=socket.gethostbyname(hostname)
77
except Exception as e:
78
logger.error(f"Exception while retriving IP Address,using local host IP address: {e}")
79
finally:
80
clientPublicIp="106.193.147.98"
81
clientLocalIp="127.0.0.1"
82
clientMacAddress=':'.join(re.findall('..', '%012x' % uuid.getnode()))
83
accept = "application/json"
84
userType = "USER"
85
sourceID = "WEB"
86
87
def __init__(self, api_key=None, access_token=None, refresh_token=None,feed_token=None, userId=None, root=None, debug=False, timeout=None, proxies=None, pool=None, disable_ssl=False,accept=None,userType=None,sourceID=None,Authorization=None,clientPublicIP=None,clientMacAddress=None,clientLocalIP=None,privateKey=None):
88
self.debug = debug
89
self.api_key = api_key
90
self.session_expiry_hook = None
91
self.disable_ssl = disable_ssl
92
self.access_token = access_token
93
self.refresh_token = refresh_token
94
self.feed_token = feed_token
95
self.userId = userId
96
self.proxies = proxies if proxies else {}
97
self.root = root or self._rootUrl
98
self.timeout = timeout or self._default_timeout
99
self.Authorization= None
100
self.clientLocalIP=self.clientLocalIp
101
self.clientPublicIP=self.clientPublicIp
102
self.clientMacAddress=self.clientMacAddress
103
self.privateKey=api_key
104
self.accept=self.accept
105
self.userType=self.userType
106
self.sourceID=self.sourceID
107
108
# Create SSL context
109
self.ssl_context = ssl.create_default_context()
110
self.ssl_context.options |= ssl.OP_NO_TLSv1 # Disable TLS 1.0
111
self.ssl_context.options |= ssl.OP_NO_TLSv1_1 # Disable TLS 1.1
112
113
# Configure minimum TLS version to TLS 1.2
114
self.ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
115
116
if not disable_ssl:
117
self.reqsession = requests.Session()
118
if pool is not None:
119
reqadapter = requests.adapters.HTTPAdapter(**pool)
120
self.reqsession.mount("https://", reqadapter)
121
else:
122
reqadapter = requests.adapters.HTTPAdapter()
123
self.reqsession.mount("https://", reqadapter)
124
logger.info(f"in pool")
125
else:
126
# If SSL is disabled, use the default SSL context
127
self.reqsession = requests
128
129
# Create a log folder based on the current date
130
log_folder = time.strftime("%Y-%m-%d", time.localtime())
131
log_folder_path = os.path.join("logs", log_folder) # Construct the full path to the log folder
132
os.makedirs(log_folder_path, exist_ok=True) # Create the log folder if it doesn't exist
133
log_path = os.path.join(log_folder_path, "app.log") # Construct the full path to the log file
134
logzero.logfile(log_path, loglevel=logging.ERROR) # Output logs to a date-wise log file
135
136
if pool:
137
self.reqsession = requests.Session()
138
reqadapter = requests.adapters.HTTPAdapter(**pool)
139
self.reqsession.mount("https://", reqadapter)
140
logger.info(f"in pool")
141
else:
142
self.reqsession = requests
143
144
# disable requests SSL warning
145
requests.packages.urllib3.disable_warnings()
146
def requestHeaders(self):
147
return{
148
"Content-type":self.accept,
149
"X-ClientLocalIP": self.clientLocalIp,
150
"X-ClientPublicIP": self.clientPublicIp,
151
"X-MACAddress": self.clientMacAddress,
152
"Accept": self.accept,
153
"X-PrivateKey": self.privateKey,
154
"X-UserType": self.userType,
155
"X-SourceID": self.sourceID
156
}
157
158
def setSessionExpiryHook(self, method):
159
if not callable(method):
160
raise TypeError("Invalid input type. Only functions are accepted.")
161
self.session_expiry_hook = method
162
163
def getUserId():
164
return userId
165
166
def setUserId(self,id):
167
self.userId=id
168
169
def setAccessToken(self, access_token):
170
171
self.access_token = access_token
172
173
def setRefreshToken(self, refresh_token):
174
175
self.refresh_token = refresh_token
176
177
def setFeedToken(self,feedToken):
178
179
self.feed_token=feedToken
180
181
def getfeedToken(self):
182
return self.feed_token
183
184
185
def login_url(self):
186
"""Get the remote login url to which a user should be redirected to initiate the login flow."""
187
return "%s?api_key=%s" % (self._login_url, self.api_key)
188
189
def _request(self, route, method, parameters=None):
190
"""Make an HTTP request."""
191
params = parameters.copy() if parameters else {}
192
193
uri =self._routes[route].format(**params)
194
url = urljoin(self.root, uri)
195
196
197
# Custom headers
198
headers = self.requestHeaders()
199
200
if self.access_token:
201
# set authorization header
202
203
auth_header = self.access_token
204
headers["Authorization"] = "Bearer {}".format(auth_header)
205
206
if self.debug:
207
log.debug("Request: {method} {url} {params} {headers}".format(method=method, url=url, params=params, headers=headers))
208
209
try:
210
r = requests.request(method,
211
url,
212
data=json.dumps(params) if method in ["POST", "PUT"] else None,
213
params=json.dumps(params) if method in ["GET", "DELETE"] else None,
214
headers=headers,
215
verify=not self.disable_ssl,
216
allow_redirects=True,
217
timeout=self.timeout,
218
proxies=self.proxies)
219
220
except Exception as e:
221
logger.error(f"Error occurred while making a {method} request to {url}. Headers: {headers}, Request: {params}, Response: {e}")
222
raise e
223
224
if self.debug:
225
log.debug("Response: {code} {content}".format(code=r.status_code, content=r.content))
226
227
# Validate the content type.
228
if "json" in headers["Content-type"]:
229
try:
230
data = json.loads(r.content.decode("utf8"))
231
232
except ValueError:
233
raise ex.DataException("Couldn't parse the JSON response received from the server: {content}".format(
234
content=r.content))
235
236
# api error
237
if data.get("error_type"):
238
# Call session hook if its registered and TokenException is raised
239
if self.session_expiry_hook and r.status_code == 403 and data["error_type"] == "TokenException":
240
self.session_expiry_hook()
241
242
# native errors
243
exp = getattr(ex, data["error_type"], ex.GeneralException)
244
raise exp(data["message"], code=r.status_code)
245
if data.get("status",False) is False :
246
logger.error(f"Error occurred while making a {method} request to {url}. Error: {data['message']}. URL: {url}, Headers: {self.requestHeaders()}, Request: {params}, Response: {data}")
247
return data
248
elif "csv" in headers["Content-type"]:
249
return r.content
250
else:
251
raise ex.DataException("Unknown Content-type ({content_type}) with response: ({content})".format(
252
content_type=headers["Content-type"],
253
content=r.content))
254
255
def _deleteRequest(self, route, params=None):
256
"""Alias for sending a DELETE request."""
257
return self._request(route, "DELETE", params)
258
def _putRequest(self, route, params=None):
259
"""Alias for sending a PUT request."""
260
return self._request(route, "PUT", params)
261
def _postRequest(self, route, params=None):
262
"""Alias for sending a POST request."""
263
return self._request(route, "POST", params)
264
def _getRequest(self, route, params=None):
265
"""Alias for sending a GET request."""
266
return self._request(route, "GET", params)
267
268
def generateSession(self,clientCode,password,totp):
269
270
params={"clientcode":clientCode,"password":password,"totp":totp}
271
loginResultObject=self._postRequest("api.login",params)
272
273
if loginResultObject['status']==True:
274
jwtToken=loginResultObject['data']['jwtToken']
275
self.setAccessToken(jwtToken)
276
refreshToken = loginResultObject['data']['refreshToken']
277
feedToken = loginResultObject['data']['feedToken']
278
self.setRefreshToken(refreshToken)
279
self.setFeedToken(feedToken)
280
user = self.getProfile(refreshToken)
281
282
id = user['data']['clientcode']
283
# id='D88311'
284
self.setUserId(id)
285
user['data']['jwtToken'] = "Bearer " + jwtToken
286
user['data']['refreshToken'] = refreshToken
287
user['data']['feedToken'] = feedToken
288
289
return user
290
else:
291
return loginResultObject
292
293
def terminateSession(self,clientCode):
294
logoutResponseObject=self._postRequest("api.logout",{"clientcode":clientCode})
295
return logoutResponseObject
296
297
def generateToken(self,refresh_token):
298
response=self._postRequest('api.token',{"refreshToken":refresh_token})
299
jwtToken=response['data']['jwtToken']
300
feedToken=response['data']['feedToken']
301
self.setFeedToken(feedToken)
302
self.setAccessToken(jwtToken)
303
304
return response
305
306
def renewAccessToken(self):
307
response =self._postRequest('api.refresh', {
308
"jwtToken": self.access_token,
309
"refreshToken": self.refresh_token,
310
311
})
312
313
tokenSet={}
314
315
if "jwtToken" in response:
316
tokenSet['jwtToken']=response['data']['jwtToken']
317
tokenSet['clientcode']=self. userId
318
tokenSet['refreshToken']=response['data']["refreshToken"]
319
320
return tokenSet
321
322
def getProfile(self,refreshToken):
323
user=self._getRequest("api.user.profile",{"refreshToken":refreshToken})
324
return user
325
326
def placeOrder(self,orderparams):
327
params=orderparams
328
for k in list(params.keys()):
329
if params[k] is None :
330
del(params[k])
331
response= self._postRequest("api.order.place", params)
332
if response is not None and response.get('status', False):
333
if 'data' in response and response['data'] is not None and 'orderid' in response['data']:
334
orderResponse = response['data']['orderid']
335
return orderResponse
336
else:
337
logger.error(f"Invalid response format: {response}")
338
else:
339
logger.error(f"API request failed: {response}")
340
return None
341
342
def placeOrderFullResponse(self,orderparams):
343
params=orderparams
344
for k in list(params.keys()):
345
if params[k] is None :
346
del(params[k])
347
response= self._postRequest("api.order.placefullresponse", params)
348
if response is not None and response.get('status', False):
349
if 'data' in response and response['data'] is not None and 'orderid' in response['data']:
350
orderResponse = response
351
return orderResponse
352
else:
353
logger.error(f"Invalid response format: {response}")
354
else:
355
logger.error(f"API request failed: {response}")
356
return response
357
358
def modifyOrder(self,orderparams):
359
params = orderparams
360
361
for k in list(params.keys()):
362
if params[k] is None:
363
del(params[k])
364
365
orderResponse= self._postRequest("api.order.modify", params)
366
return orderResponse
367
368
def cancelOrder(self, order_id,variety):
369
orderResponse= self._postRequest("api.order.cancel", {"variety": variety,"orderid": order_id})
370
return orderResponse
371
372
def ltpData(self,exchange,tradingsymbol,symboltoken):
373
params={
374
"exchange":exchange,
375
"tradingsymbol":tradingsymbol,
376
"symboltoken":symboltoken
377
}
378
ltpDataResponse= self._postRequest("api.ltp.data",params)
379
return ltpDataResponse
380
381
def orderBook(self):
382
orderBookResponse=self._getRequest("api.order.book")
383
return orderBookResponse
384
385
386
def tradeBook(self):
387
tradeBookResponse=self._getRequest("api.trade.book")
388
return tradeBookResponse
389
390
def rmsLimit(self):
391
rmsLimitResponse= self._getRequest("api.rms.limit")
392
return rmsLimitResponse
393
394
def position(self):
395
positionResponse= self._getRequest("api.position")
396
return positionResponse
397
398
def holding(self):
399
holdingResponse= self._getRequest("api.holding")
400
return holdingResponse
401
402
def allholding(self):
403
allholdingResponse= self._getRequest("api.allholding")
404
return allholdingResponse
405
406
def convertPosition(self,positionParams):
407
params=positionParams
408
for k in list(params.keys()):
409
if params[k] is None:
410
del(params[k])
411
convertPositionResponse= self._postRequest("api.convert.position",params)
412
413
return convertPositionResponse
414
415
def gttCreateRule(self,createRuleParams):
416
params=createRuleParams
417
for k in list(params.keys()):
418
if params[k] is None:
419
del(params[k])
420
421
createGttRuleResponse=self._postRequest("api.gtt.create",params)
422
return createGttRuleResponse['data']['id']
423
424
def gttModifyRule(self,modifyRuleParams):
425
params=modifyRuleParams
426
for k in list(params.keys()):
427
if params[k] is None:
428
del(params[k])
429
modifyGttRuleResponse=self._postRequest("api.gtt.modify",params)
430
return modifyGttRuleResponse['data']['id']
431
432
def gttCancelRule(self,gttCancelParams):
433
params=gttCancelParams
434
for k in list(params.keys()):
435
if params[k] is None:
436
del(params[k])
437
cancelGttRuleResponse=self._postRequest("api.gtt.cancel",params)
438
return cancelGttRuleResponse
439
440
def gttDetails(self,id):
441
params={
442
"id":id
443
}
444
gttDetailsResponse=self._postRequest("api.gtt.details",params)
445
return gttDetailsResponse
446
447
def gttLists(self,status,page,count):
448
if type(status)== list:
449
params={
450
"status":status,
451
"page":page,
452
"count":count
453
}
454
gttListResponse=self._postRequest("api.gtt.list",params)
455
return gttListResponse
456
else:
457
message="The status param is entered as" +str(type(status))+". Please enter status param as a list i.e., status=['CANCELLED']"
458
return message
459
460
def getCandleData(self,historicDataParams):
461
params=historicDataParams
462
for k in list(params.keys()):
463
if params[k] is None:
464
del(params[k])
465
getCandleDataResponse=self._postRequest("api.candle.data",historicDataParams)
466
return getCandleDataResponse
467
468
def getOIData(self,historicOIDataParams):
469
params=historicOIDataParams
470
for k in list(params.keys()):
471
if params[k] is None:
472
del(params[k])
473
getOIDataResponse=self._postRequest("api.oi.data",historicOIDataParams)
474
return getOIDataResponse
475
476
def getMarketData(self,mode,exchangeTokens):
477
params={
478
"mode":mode,
479
"exchangeTokens":exchangeTokens
480
}
481
marketDataResult=self._postRequest("api.market.data",params)
482
return marketDataResult
483
484
def searchScrip(self, exchange, searchscrip):
485
params = {
486
"exchange": exchange,
487
"searchscrip": searchscrip
488
}
489
searchScripResult = self._postRequest("api.search.scrip", params)
490
if searchScripResult["status"] is True and searchScripResult["data"]:
491
message = f"Search successful. Found {len(searchScripResult['data'])} trading symbols for the given query:"
492
symbols = ""
493
for index, item in enumerate(searchScripResult["data"], start=1):
494
symbol_info = f"{index}. exchange: {item['exchange']}, tradingsymbol: {item['tradingsymbol']}, symboltoken: {item['symboltoken']}"
495
symbols += "\n" + symbol_info
496
logger.info(message + symbols)
497
return searchScripResult
498
elif searchScripResult["status"] is True and not searchScripResult["data"]:
499
logger.info("Search successful. No matching trading symbols found for the given query.")
500
return searchScripResult
501
else:
502
return searchScripResult
503
504
def make_authenticated_get_request(self, url, access_token):
505
headers = self.requestHeaders()
506
if access_token:
507
headers["Authorization"] = "Bearer " + access_token
508
response = requests.get(url, headers=headers)
509
if response.status_code == 200:
510
data = json.loads(response.text)
511
return data
512
else:
513
logger.error(f"Error in make_authenticated_get_request: {response.status_code}")
514
return None
515
516
def individual_order_details(self, qParam):
517
url = self._rootUrl + self._routes["api.individual.order.details"] + qParam
518
try:
519
response_data = self.make_authenticated_get_request(url, self.access_token)
520
return response_data
521
except Exception as e:
522
logger.error(f"Error occurred in ind_order_details: {e}")
523
return None
524
525
def getMarginApi(self,params):
526
marginApiResult=self._postRequest("api.margin.api",params)
527
return marginApiResult
528
529
def estimateCharges(self,params):
530
estimateChargesResponse=self._postRequest("api.estimateCharges",params)
531
return estimateChargesResponse
532
533
def verifyDis(self,params):
534
verifyDisResponse=self._postRequest("api.verifyDis",params)
535
return verifyDisResponse
536
537
def generateTPIN(self,params):
538
generateTPINResponse=self._postRequest("api.generateTPIN",params)
539
return generateTPINResponse
540
541
def getTranStatus(self,params):
542
getTranStatusResponse=self._postRequest("api.getTranStatus",params)
543
return getTranStatusResponse
544
545
def optionGreek(self,params):
546
optionGreekResponse=self._postRequest("api.optionGreek",params)
547
return optionGreekResponse
548
549
def gainersLosers(self,params):
550
gainersLosersResponse=self._postRequest("api.gainersLosers",params)
551
return gainersLosersResponse
552
553
def putCallRatio(self):
554
putCallRatioResponse=self._getRequest("api.putCallRatio")
555
return putCallRatioResponse
556
557
def nseIntraday(self):
558
nseIntraday=self._getRequest("api.nseIntraday")
559
return nseIntraday
560
561
def bseIntraday(self):
562
bseIntraday=self._getRequest("api.bseIntraday")
563
return bseIntraday
564
565
def oIBuildup(self,params):
566
oIBuildupResponse=self._postRequest("api.oIBuildup",params)
567
return oIBuildupResponse
568
569
570
def _user_agent(self):
571
return (__title__ + "-python/").capitalize() + __version__
572
573
574