quantplay 2.0.94__tar.gz → 2.0.96__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {quantplay-2.0.94 → quantplay-2.0.96}/PKG-INFO +26 -1
  2. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/aliceblue.py +1 -0
  3. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/angelone.py +1 -18
  4. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/auto_login/aliceblue.py +4 -0
  5. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/dhan.py +7 -8
  6. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/five_paisa.py +0 -6
  7. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/ft_utils/flattrade_utils.py +3 -0
  8. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/generics/broker.py +2 -2
  9. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/motilal.py +0 -6
  10. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/noren.py +0 -16
  11. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/shoonya.py +2 -0
  12. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/uplink/uplink_utils.py +4 -0
  13. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/xts.py +103 -67
  14. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/xts_utils/Connect.py +266 -408
  15. quantplay-2.0.96/quantplay/model/broker_response.py +17 -0
  16. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/wrapper/aws/s3.py +6 -1
  17. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay.egg-info/PKG-INFO +26 -1
  18. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay.egg-info/SOURCES.txt +1 -0
  19. {quantplay-2.0.94 → quantplay-2.0.96}/setup.py +1 -1
  20. {quantplay-2.0.94 → quantplay-2.0.96}/README.md +0 -0
  21. {quantplay-2.0.94 → quantplay-2.0.96}/pyproject.toml +0 -0
  22. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/__init__.py +0 -0
  23. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/__init__.py +0 -0
  24. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/auto_login/__init__.py +0 -0
  25. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/broker_factory.py +0 -0
  26. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/finvasia_utils/__init__.py +0 -0
  27. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
  28. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/flattrade.py +0 -0
  29. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/ft_utils/__init__.py +0 -0
  30. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/ft_utils/ft_noren.py +0 -0
  31. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/generics/__init__.py +0 -0
  32. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/iifl_xts.py +0 -0
  33. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/kite_utils.py +0 -0
  34. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/kotak.py +0 -0
  35. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/uplink/__init__.py +0 -0
  36. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/upstox.py +0 -0
  37. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/xts_utils/Exception.py +0 -0
  38. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
  39. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/xts_utils/__init__.py +0 -0
  40. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/broker/zerodha.py +1 -1
  41. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/exception/__init__.py +0 -0
  42. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/exception/exceptions.py +0 -0
  43. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/model/__init__.py +0 -0
  44. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/model/broker.py +0 -0
  45. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/model/generics.py +0 -0
  46. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/model/instrument_data.py +0 -0
  47. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/model/order_event.py +0 -0
  48. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/py.typed +0 -0
  49. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/__init__.py +0 -0
  50. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/caching.py +0 -0
  51. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/constant.py +0 -0
  52. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/exchange.py +0 -0
  53. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/number_utils.py +0 -0
  54. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/pickle_utils.py +0 -0
  55. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/utils/selenium_utils.py +0 -0
  56. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/wrapper/__init__.py +0 -0
  57. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay/wrapper/aws/__init__.py +0 -0
  58. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay.egg-info/dependency_links.txt +0 -0
  59. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay.egg-info/requires.txt +0 -0
  60. {quantplay-2.0.94 → quantplay-2.0.96}/quantplay.egg-info/top_level.txt +0 -0
  61. {quantplay-2.0.94 → quantplay-2.0.96}/setup.cfg +0 -0
  62. {quantplay-2.0.94 → quantplay-2.0.96}/tests/__init__.py +0 -0
  63. {quantplay-2.0.94 → quantplay-2.0.96}/tests/conftest.py +0 -0
  64. {quantplay-2.0.94 → quantplay-2.0.96}/tests/wrapper/__init__.py +0 -0
  65. {quantplay-2.0.94 → quantplay-2.0.96}/tests/wrapper/aws/__init__.py +0 -0
  66. {quantplay-2.0.94 → quantplay-2.0.96}/tests/wrapper/aws/s3_test.py +0 -0
@@ -1,11 +1,36 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.94
3
+ Version: 2.0.96
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
7
7
  Author-email:
8
8
  License: MIT
9
+ Requires-Dist: setuptools
10
+ Requires-Dist: path
11
+ Requires-Dist: pyotp
12
+ Requires-Dist: retrying
13
+ Requires-Dist: boto3
14
+ Requires-Dist: numpy
15
+ Requires-Dist: websocket-client
16
+ Requires-Dist: smartapi-python
17
+ Requires-Dist: logzero
18
+ Requires-Dist: selenium
19
+ Requires-Dist: requests
20
+ Requires-Dist: pandas
21
+ Requires-Dist: pyarrow
22
+ Requires-Dist: polars
23
+ Requires-Dist: kiteconnect
24
+ Requires-Dist: pya3
25
+ Requires-Dist: py5paisa
26
+ Requires-Dist: upstox-python-sdk
27
+ Requires-Dist: undetected-chromedriver
28
+ Requires-Dist: cachetools
29
+ Requires-Dist: py_vollib
30
+ Requires-Dist: python-engineio
31
+ Requires-Dist: python-socketio
32
+ Requires-Dist: six
33
+ Requires-Dist: dhanhq
9
34
 
10
35
  # Quantplay Alpha playground
11
36
 
@@ -250,6 +250,7 @@ class Aliceblue(Broker):
250
250
  exchange=exchange,
251
251
  symbol=tradingsymbol,
252
252
  )
253
+
253
254
  except Exception:
254
255
  inst = self.invoke_aliceblue_api(
255
256
  self.alice.get_instrument_by_symbol,
@@ -47,12 +47,6 @@ class AngelOne(Broker):
47
47
  order_sl = "STOPLOSS_LIMIT"
48
48
  order_slm = "STOPLOSS_MARKET"
49
49
 
50
- @retry(
51
- wait_exponential_multiplier=3000,
52
- wait_exponential_max=10000,
53
- stop_max_attempt_number=5,
54
- retry_on_exception=retry_exception,
55
- )
56
50
  def __init__(
57
51
  self,
58
52
  order_updates: Queue[OrderUpdateEvent] | None = None,
@@ -107,7 +101,7 @@ class AngelOne(Broker):
107
101
  raise InvalidArgumentException("Invalid TOTP key provided")
108
102
 
109
103
  except Exception as e:
110
- print(e)
104
+ traceback.print_exc()
111
105
  raise RetryableException(str(e))
112
106
 
113
107
  self.user_id = user_id
@@ -250,11 +244,6 @@ class AngelOne(Broker):
250
244
  return "NORMAL"
251
245
  return variety
252
246
 
253
- @retry(
254
- wait_exponential_multiplier=3000,
255
- wait_exponential_max=10000,
256
- stop_max_attempt_number=3,
257
- )
258
247
  def modify_order(self, order: ModifyOrderRequest) -> str:
259
248
  data = copy.deepcopy(order)
260
249
  order_id = str(data["order_id"])
@@ -337,12 +326,6 @@ class AngelOne(Broker):
337
326
 
338
327
  return holdings
339
328
 
340
- @retry(
341
- wait_exponential_multiplier=3000,
342
- wait_exponential_max=15000,
343
- stop_max_attempt_number=2,
344
- retry_on_exception=retry_exception,
345
- )
346
329
  def positions(self, drop_cnc: bool = True):
347
330
  positions = self.invoke_angelone_api(self.wrapper.position)
348
331
 
@@ -87,15 +87,19 @@ class AliceblueLogin:
87
87
 
88
88
  except binascii.Error:
89
89
  raise InvalidArgumentException("Invalid TOTP key provided")
90
+
90
91
  except InvalidArgumentException:
91
92
  raise
93
+
92
94
  except NoSuchElementException:
93
95
  raise BrokerException(
94
96
  "Login to Aliceblue failed. Please log in manually to generate a new token"
95
97
  )
98
+
96
99
  except WebDriverException:
97
100
  traceback.print_exc()
98
101
  raise RetryableException("Selenium setup need to be fixed")
102
+
99
103
  except Exception as e:
100
104
  traceback.print_exc()
101
105
  raise RetryableException(str(e))
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import traceback
2
3
  from typing import Any, Dict
3
4
 
@@ -68,13 +69,7 @@ class Dhan(Broker):
68
69
  def get_username(self):
69
70
  return self.username
70
71
 
71
- @retry(
72
- wait_exponential_multiplier=3000,
73
- wait_exponential_max=10000,
74
- stop_max_attempt_number=3,
75
- )
76
72
  def modify_order(self, order: Any) -> str:
77
- # TODO
78
73
  order_id = order["order_id"]
79
74
  existing_order: Dict[str, Any] = self.invoke_dhan_api(
80
75
  self.dhan.get_order_by_id, # type: ignore
@@ -183,7 +178,7 @@ class Dhan(Broker):
183
178
  )
184
179
  if trigger_price is None:
185
180
  trigger_price = 0
186
- order_id: str = self.dhan.place_order( # type:ignore
181
+ response: Dict[str, Any] = self.dhan.place_order( # type:ignore
187
182
  security_id=security_id, # hdfcbank
188
183
  exchange_segment=exchange_segment,
189
184
  transaction_type=transaction_type,
@@ -194,7 +189,11 @@ class Dhan(Broker):
194
189
  trigger_price=trigger_price, # type:ignore
195
190
  )
196
191
 
197
- return order_id # type:ignore
192
+ try:
193
+ return response["data"]["orderId"] # type:ignore
194
+ except Exception:
195
+ Constants.logger.error(f"Failed to place order in Dhan {response}")
196
+ raise Exception(json.dumps(response))
198
197
  except Exception as e:
199
198
  raise QuantplayOrderPlacementException(str(e))
200
199
 
@@ -136,12 +136,6 @@ class FivePaisa(Broker):
136
136
  tradingsymbol = self.get_symbol(tradingsymbol, exchange)
137
137
  return int(self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["lot_size"])
138
138
 
139
- @retry(
140
- wait_exponential_multiplier=1000,
141
- wait_exponential_max=10000,
142
- stop_max_attempt_number=3,
143
- retry_on_exception=retry_exception,
144
- )
145
139
  def profile(self):
146
140
  response: UserBrokerProfileResponse = {
147
141
  "user_id": self.client.client_code,
@@ -105,10 +105,13 @@ class FlatTradeUtils:
105
105
 
106
106
  except binascii.Error:
107
107
  raise InvalidArgumentException("Invalid TOTP key provided")
108
+
108
109
  except (InvalidArgumentException, TokenException) as e:
109
110
  raise e
111
+
110
112
  except WebDriverException:
111
113
  raise WrongLibrarySetup("Selenium setup need to be fixed")
114
+
112
115
  except Exception as e:
113
116
  traceback.print_exc()
114
117
  raise RetryableException(str(e))
@@ -300,11 +300,11 @@ class Broker(ABC):
300
300
  "lot_size"
301
301
  ]
302
302
  )
303
- except Exception as e:
303
+ except Exception:
304
304
  logger.error(
305
305
  f"[GET_LOT_SIZE] unable to get lot size for {broker_exchange} {tradingsymbol}"
306
306
  )
307
- raise e
307
+ raise
308
308
 
309
309
  def filter_orders(
310
310
  self, orders: pl.DataFrame, tag: str | None = None, status: str | None = None
@@ -327,7 +327,6 @@ class Motilal(Broker):
327
327
 
328
328
  self.modify_order(order_to_modify) # type: ignore
329
329
 
330
- # TODO
331
330
  def modify_order(self, order: Any) -> str:
332
331
  order = copy.deepcopy(order) # type:ignore
333
332
  order = self.add_existing_order_details(order)
@@ -517,11 +516,6 @@ class Motilal(Broker):
517
516
 
518
517
  return response
519
518
 
520
- @retry(
521
- wait_exponential_multiplier=3000,
522
- wait_exponential_max=10000,
523
- stop_max_attempt_number=3,
524
- )
525
519
  def holdings(self):
526
520
  response = self.__post_request(self.holdings_url, {})
527
521
  if response["status"] == "ERROR":
@@ -250,11 +250,6 @@ class Noren(Broker):
250
250
  f"[PLACE_ORDER_FAILED] {exception_message}"
251
251
  )
252
252
 
253
- @retry(
254
- wait_exponential_multiplier=3000,
255
- wait_exponential_max=10000,
256
- stop_max_attempt_number=3,
257
- )
258
253
  def ltp(self, exchange: ExchangeType, tradingsymbol: str):
259
254
  tradingsymbol = self.get_symbol(tradingsymbol, exchange)
260
255
 
@@ -380,11 +375,6 @@ class Noren(Broker):
380
375
 
381
376
  return response
382
377
 
383
- @retry(
384
- wait_exponential_multiplier=3000,
385
- wait_exponential_max=10000,
386
- stop_max_attempt_number=3,
387
- )
388
378
  def holdings(self):
389
379
  holdings = self.invoke_noren_api(self.api.get_holdings)
390
380
  if holdings is None or len(holdings) == 0:
@@ -695,12 +685,6 @@ class Noren(Broker):
695
685
  traceback.print_exc()
696
686
  raise RetryableException("Failed to Receive Data from broker. Retrying Again")
697
687
 
698
- @retry(
699
- wait_exponential_multiplier=1000,
700
- wait_exponential_max=10000,
701
- stop_max_attempt_number=3,
702
- retry_on_exception=retry_exception,
703
- )
704
688
  def margins(self) -> MarginsResponse:
705
689
  api_margins = self.invoke_noren_api(self.api.get_limits)
706
690
 
@@ -56,8 +56,10 @@ class FinvAsia(Noren):
56
56
 
57
57
  except TokenException:
58
58
  raise
59
+
59
60
  except binascii.Error:
60
61
  raise TokenException("Invalid TOTP key provided")
62
+
61
63
  except Exception as e:
62
64
  raise RetryableException(str(e))
63
65
 
@@ -108,12 +108,16 @@ class UplinkUtils:
108
108
  driver.close()
109
109
 
110
110
  return request_token
111
+
111
112
  except binascii.Error:
112
113
  raise InvalidArgumentException("Invalid TOTP key provided")
114
+
113
115
  except InvalidArgumentException:
114
116
  raise
117
+
115
118
  except WebDriverException:
116
119
  raise RetryableException("Selenium setup need to be fixed")
120
+
117
121
  except Exception as e:
118
122
  traceback.print_exc()
119
123
  raise RetryableException(str(e))
@@ -12,6 +12,12 @@ from retrying import retry # type: ignore
12
12
 
13
13
  from quantplay.broker.generics.broker import Broker
14
14
  from quantplay.broker.xts_utils.Connect import XTSConnect
15
+ from quantplay.broker.xts_utils.Exception import (
16
+ XTSDataException,
17
+ XTSGeneralException,
18
+ XTSNetworkException,
19
+ XTSTokenException,
20
+ )
15
21
  from quantplay.broker.xts_utils.InteractiveSocketClient import OrderSocket_io
16
22
  from quantplay.exception.exceptions import (
17
23
  BrokerException,
@@ -66,7 +72,7 @@ class XTS(Broker):
66
72
  ):
67
73
  self.login(api_key, api_secret, md_api_key, md_api_secret)
68
74
  else:
69
- raise Exception("Missing Arguments")
75
+ raise InvalidArgumentException("Missing Arguments")
70
76
 
71
77
  except Exception as e:
72
78
  traceback.print_exc()
@@ -76,8 +82,11 @@ class XTS(Broker):
76
82
  self.load_instrument()
77
83
 
78
84
  def set_wrapper(self, serialized_wrapper: str, serialized_md_wrapper: str):
79
- self.wrapper = pickle.loads(codecs.decode(serialized_wrapper.encode(), "base64"))
80
- self.md_wrapper = pickle.loads(
85
+ self.wrapper: XTSConnect = pickle.loads(
86
+ codecs.decode(serialized_wrapper.encode(), "base64")
87
+ )
88
+
89
+ self.md_wrapper: XTSConnect = pickle.loads(
81
90
  codecs.decode(serialized_md_wrapper.encode(), "base64")
82
91
  )
83
92
 
@@ -85,6 +94,7 @@ class XTS(Broker):
85
94
  try:
86
95
  self.symbol_data = InstrumentData.get_instance().load_data("xts_instruments") # type: ignore
87
96
  Constants.logger.info("[LOADING_INSTRUMENTS] loading data from cache")
97
+
88
98
  except Exception:
89
99
  instruments = pd.read_csv( # type: ignore
90
100
  "https://quantplay-public-data.s3.ap-south-1.amazonaws.com/symbol_data/instruments.csv"
@@ -101,29 +111,34 @@ class XTS(Broker):
101
111
 
102
112
  ins_type = instrument["instrument_type"]
103
113
  name = instrument["name"]
114
+
104
115
  if ins_type in ["CE", "PE"]:
105
116
  expiry = datetime.strftime(
106
117
  datetime.strptime(str(instrument["expiry"]), "%Y-%m-%d"),
107
118
  "%d%b%Y",
108
119
  ).upper()
109
120
  strike = str(instrument["strike"]).rstrip("0")
121
+
110
122
  if strike[-1] == ".":
111
123
  strike = strike[:-1]
124
+
112
125
  instrument["broker_symbol"] = f"{name} {expiry} {ins_type} {strike}"
126
+
113
127
  elif ins_type == "FUT":
114
128
  expiry = datetime.strftime(
115
129
  datetime.strptime(str(instrument["expiry"]), "%Y-%m-%d"),
116
130
  "%d%b%Y",
117
131
  ).upper()
118
132
  instrument["broker_symbol"] = f"{name} {expiry}"
133
+
119
134
  else:
120
135
  instrument["broker_symbol"] = tradingsymbol
121
136
 
122
- # TODO: Types
123
137
  self.symbol_data[f"{exchange}:{tradingsymbol}"] = instrument # type: ignore
124
138
 
125
139
  PickleUtils.save_data(self.symbol_data, "xts_instruments")
126
140
  Constants.logger.info("[LOADING_INSTRUMENTS] loading data from server")
141
+
127
142
  self.initialize_broker_symbol_map()
128
143
 
129
144
  def login(self, api_key: str, api_secret: str, md_api_key: str, md_api_secret: str):
@@ -133,49 +148,35 @@ class XTS(Broker):
133
148
  secretKey=api_secret,
134
149
  root=self.root_url,
135
150
  )
136
- xt_core_response = self.wrapper.interactive_login()
151
+ xt_core_response = self.invoke_xts_api(self.wrapper.interactive_login)
152
+
137
153
  self.md_wrapper = XTSConnect(
138
154
  apiKey=md_api_key,
139
155
  secretKey=md_api_secret,
140
156
  root=self.root_url,
141
157
  )
142
- md_response = self.md_wrapper.marketdata_login()
158
+ md_response = self.invoke_xts_api(self.md_wrapper.marketdata_login)
159
+
143
160
  if "type" not in xt_core_response or xt_core_response["type"] != "success":
144
161
  print(f"api login response {xt_core_response}")
145
162
  raise TokenException("Api key credentials are incorrect")
163
+
146
164
  if "type" not in md_response or md_response["type"] != "success":
147
165
  print(f"market data login response {md_response}")
148
166
  raise TokenException("Market data api credentials are invalid")
167
+
149
168
  self.ClientID = xt_core_response["result"]["userID"]
169
+
150
170
  except TokenException:
151
171
  raise
172
+
152
173
  except Exception:
153
174
  raise InvalidArgumentException("Invalid api key/secret")
154
175
 
155
- def handle_exception(self, response: Dict[str, Any]):
156
- if "data" in response and "description" in response["data"]:
157
- data = response["data"]
158
- if "max limit" in data["description"].lower():
159
- user_id = self.profile()["user_id"]
160
- print("Rate limit problem")
161
- raise RetryableException(f"{user_id}: Request limit exceeded")
162
- if (
163
- "description" in response
164
- and "Authorization not found" in response["description"]
165
- ):
166
- raise TokenException(response["description"])
167
- if "type" in response and response["type"] == "error":
168
- raise Exception(f"[XTS_Error]: {response['description']}")
169
-
170
- @retry(
171
- wait_exponential_multiplier=3000,
172
- wait_exponential_max=10000,
173
- stop_max_attempt_number=3,
174
- retry_on_exception=retry_exception,
175
- )
176
176
  def margins(self) -> MarginsResponse:
177
- api_response = self.wrapper.get_balance(clientID=self.ClientID)
178
- self.handle_exception(api_response)
177
+ api_response = self.invoke_xts_api(
178
+ self.wrapper.get_balance, clientID=self.ClientID
179
+ )
179
180
 
180
181
  if not api_response:
181
182
  return {
@@ -184,9 +185,11 @@ class XTS(Broker):
184
185
  "total_balance": 0,
185
186
  "cash": 0,
186
187
  }
188
+
187
189
  api_response = api_response["result"]["BalanceList"][0]["limitObject"]
188
190
  margin_used = api_response["RMSSubLimits"]["marginUtilized"]
189
191
  margin_available = api_response["RMSSubLimits"]["netMarginAvailable"]
192
+
190
193
  return {
191
194
  "margin_used": margin_used,
192
195
  "margin_available": margin_available,
@@ -194,15 +197,10 @@ class XTS(Broker):
194
197
  "cash": 0,
195
198
  }
196
199
 
197
- @retry(
198
- wait_exponential_multiplier=3000,
199
- wait_exponential_max=10000,
200
- stop_max_attempt_number=3,
201
- retry_on_exception=retry_exception,
202
- )
203
200
  def profile(self) -> UserBrokerProfileResponse:
204
- api_response = self.wrapper.get_profile(self.ClientID)
205
- self.handle_exception(api_response)
201
+ api_response = self.invoke_xts_api(
202
+ self.wrapper.get_profile, clientID=self.ClientID
203
+ )
206
204
  api_response = api_response["result"]
207
205
 
208
206
  response: UserBrokerProfileResponse = {
@@ -213,15 +211,10 @@ class XTS(Broker):
213
211
 
214
212
  return response
215
213
 
216
- @retry(
217
- wait_exponential_multiplier=3000,
218
- wait_exponential_max=10000,
219
- stop_max_attempt_number=3,
220
- retry_on_exception=retry_exception,
221
- )
222
214
  def orders(self, tag: str | None = None, add_ltp: bool = True) -> pl.DataFrame:
223
- api_response = self.wrapper.get_order_book(self.ClientID)
224
- self.handle_exception(api_response)
215
+ api_response = self.invoke_xts_api(
216
+ self.wrapper.get_order_book, clientID=self.ClientID
217
+ )
225
218
 
226
219
  api_response = api_response["result"]
227
220
  if api_response is None or len(api_response) == 0:
@@ -345,23 +338,13 @@ class XTS(Broker):
345
338
 
346
339
  return orders_df[list(self.orders_schema.keys())].cast(self.orders_schema)
347
340
 
348
- @retry(
349
- wait_exponential_multiplier=3000,
350
- wait_exponential_max=10000,
351
- stop_max_attempt_number=3,
352
- )
353
341
  def holdings(self):
354
342
  return pl.DataFrame(schema=self.holidings_schema)
355
343
 
356
- @retry(
357
- wait_exponential_multiplier=3000,
358
- wait_exponential_max=10000,
359
- stop_max_attempt_number=3,
360
- retry_on_exception=retry_exception,
361
- )
362
344
  def positions(self, drop_cnc: bool = True) -> pl.DataFrame:
363
- api_response = self.wrapper.get_position_daywise(self.ClientID)
364
- self.handle_exception(api_response)
345
+ api_response = self.invoke_xts_api(
346
+ self.wrapper.get_position_daywise, clientID=self.ClientID
347
+ )
365
348
 
366
349
  api_response = api_response["result"]["positionList"]
367
350
  positions_df = pl.DataFrame(api_response)
@@ -478,7 +461,9 @@ class XTS(Broker):
478
461
  }
479
462
  for x in symbols
480
463
  ]
481
- api_response = self.md_wrapper.get_quote(
464
+
465
+ api_response = self.invoke_xts_api(
466
+ self.md_wrapper.get_quote,
482
467
  Instruments=instruments,
483
468
  xtsMessageCode=1512,
484
469
  publishFormat="JSON",
@@ -486,6 +471,7 @@ class XTS(Broker):
486
471
 
487
472
  if "type" in api_response and api_response["type"] == "error":
488
473
  raise TokenException(api_response["description"])
474
+
489
475
  api_response = api_response["result"]
490
476
 
491
477
  ltp_json = api_response["listQuotes"]
@@ -537,11 +523,11 @@ class XTS(Broker):
537
523
  def ltp(self, exchange: ExchangeType, tradingsymbol: str) -> float:
538
524
  exchange_code = self.get_exchange_code(exchange)
539
525
  exchange_token = self.symbol_data[f"{exchange}:{tradingsymbol}"].get(
540
- "exchange_token",
541
- "", # TODO
526
+ "exchange_token", ""
542
527
  )
543
528
 
544
- api_response = self.md_wrapper.get_quote(
529
+ api_response = self.invoke_xts_api(
530
+ self.md_wrapper.get_quote,
545
531
  Instruments=[
546
532
  {
547
533
  "exchangeSegment": exchange_code,
@@ -561,8 +547,9 @@ class XTS(Broker):
561
547
 
562
548
  try:
563
549
  ltp_json = api_response["listQuotes"][0]
564
- except IndexError as e:
565
- print(api_response, e)
550
+
551
+ except IndexError:
552
+ traceback.print_exc()
566
553
  raise BrokerException("Broker Provided Invalid Response")
567
554
 
568
555
  ltp = json.loads(ltp_json)["LastTradedPrice"]
@@ -626,7 +613,8 @@ class XTS(Broker):
626
613
 
627
614
  tag = order_data["tag"]
628
615
 
629
- api_response = self.wrapper.cancel_order(
616
+ api_response = self.invoke_xts_api(
617
+ self.wrapper.cancel_order,
630
618
  appOrderID=int(order_id),
631
619
  clientID=order_data["user_id"],
632
620
  orderUniqueIdentifier=tag,
@@ -678,7 +666,8 @@ class XTS(Broker):
678
666
  time_in_force = "DAY"
679
667
  disclosed_quantity = 0
680
668
 
681
- api_response = self.wrapper.modify_order(
669
+ api_response = self.invoke_xts_api(
670
+ self.wrapper.modify_order,
682
671
  appOrderID=int(order_id),
683
672
  modifiedTimeInForce=time_in_force,
684
673
  modifiedDisclosedQuantity=disclosed_quantity,
@@ -804,5 +793,52 @@ class XTS(Broker):
804
793
  self.order_updates.put(new_ord)
805
794
 
806
795
  except Exception as e:
807
- print(e)
796
+ traceback.print_exc()
808
797
  Constants.logger.error("[ORDER_UPDATE_PROCESSING_FAILED] {}".format(e))
798
+
799
+ @retry(
800
+ wait_exponential_multiplier=3000,
801
+ wait_exponential_max=10000,
802
+ stop_max_attempt_number=3,
803
+ retry_on_exception=retry_exception,
804
+ )
805
+ def invoke_xts_api(self, fn: Any, *args: Any, **kwargs: Any) -> Dict[str, Any]:
806
+ try:
807
+ response = fn(*args, **kwargs)
808
+
809
+ if "data" in response and "description" in response["data"]:
810
+ data = response["data"]
811
+
812
+ if "max limit" in data["description"].lower():
813
+ user_id = self.profile()["user_id"]
814
+ raise RetryableException(f"{user_id}: Request limit exceeded")
815
+
816
+ if (
817
+ "description" in response
818
+ and "Authorization not found" in response["description"]
819
+ ):
820
+ raise TokenException(response["description"])
821
+
822
+ if (
823
+ "type" in response and response["type"] == "error"
824
+ ) or "result" not in response:
825
+ raise BrokerException(f"[XTS_Error]: {response['description']}")
826
+
827
+ return response
828
+
829
+ except XTSTokenException as e:
830
+ raise TokenException(str(e))
831
+
832
+ except (TokenException, RetryableException, BrokerException):
833
+ raise
834
+
835
+ except (
836
+ XTSGeneralException,
837
+ XTSDataException,
838
+ XTSNetworkException,
839
+ ) as e:
840
+ raise BrokerException(str(e))
841
+
842
+ except Exception:
843
+ traceback.print_exc()
844
+ raise