quantplay 2.0.39__tar.gz → 2.0.41__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 (64) hide show
  1. {quantplay-2.0.39 → quantplay-2.0.41}/PKG-INFO +1 -1
  2. {quantplay-2.0.39 → quantplay-2.0.41}/pyproject.toml +1 -1
  3. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/aliceblue.py +7 -3
  4. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/angelone.py +4 -1
  5. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/dhan.py +5 -11
  6. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/five_paisa.py +17 -5
  7. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/generics/broker.py +19 -123
  8. quantplay-2.0.41/quantplay/broker/generics/broker_factory.py +222 -0
  9. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/motilal.py +12 -14
  10. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/noren.py +6 -3
  11. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/upstox.py +18 -14
  12. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/xts.py +16 -8
  13. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/zerodha.py +9 -0
  14. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/model/broker.py +1 -1
  15. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/model/generics.py +18 -12
  16. quantplay-2.0.41/quantplay/utils/caching.py +35 -0
  17. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay.egg-info/PKG-INFO +1 -1
  18. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay.egg-info/SOURCES.txt +2 -0
  19. {quantplay-2.0.39 → quantplay-2.0.41}/setup.py +1 -1
  20. {quantplay-2.0.39 → quantplay-2.0.41}/README.md +0 -0
  21. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/__init__.py +0 -0
  22. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/__init__.py +0 -0
  23. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/auto_login/__init__.py +0 -0
  24. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/auto_login/aliceblue.py +0 -0
  25. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/finvasia_utils/__init__.py +0 -0
  26. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
  27. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/flattrade.py +0 -0
  28. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/ft_utils/__init__.py +0 -0
  29. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/ft_utils/flattrade_utils.py +0 -0
  30. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/ft_utils/ft_noren.py +0 -0
  31. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/generics/__init__.py +0 -0
  32. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/iifl_xts.py +0 -0
  33. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/kite_utils.py +0 -0
  34. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/shoonya.py +0 -0
  35. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/uplink/__init__.py +0 -0
  36. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/uplink/uplink_utils.py +0 -0
  37. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/xts_utils/Connect.py +0 -0
  38. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/xts_utils/Exception.py +0 -0
  39. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
  40. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/broker/xts_utils/__init__.py +0 -0
  41. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/exception/__init__.py +0 -0
  42. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/exception/exceptions.py +0 -0
  43. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/model/__init__.py +0 -0
  44. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/model/instrument_data.py +0 -0
  45. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/model/order_event.py +0 -0
  46. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/py.typed +0 -0
  47. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/utils/__init__.py +0 -0
  48. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/utils/constant.py +0 -0
  49. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/utils/exchange.py +0 -0
  50. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/utils/number_utils.py +0 -0
  51. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/utils/pickle_utils.py +0 -0
  52. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/utils/selenium_utils.py +0 -0
  53. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/wrapper/__init__.py +0 -0
  54. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/wrapper/aws/__init__.py +0 -0
  55. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay/wrapper/aws/s3.py +0 -0
  56. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay.egg-info/dependency_links.txt +0 -0
  57. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay.egg-info/requires.txt +0 -0
  58. {quantplay-2.0.39 → quantplay-2.0.41}/quantplay.egg-info/top_level.txt +0 -0
  59. {quantplay-2.0.39 → quantplay-2.0.41}/setup.cfg +0 -0
  60. {quantplay-2.0.39 → quantplay-2.0.41}/tests/__init__.py +0 -0
  61. {quantplay-2.0.39 → quantplay-2.0.41}/tests/conftest.py +0 -0
  62. {quantplay-2.0.39 → quantplay-2.0.41}/tests/wrapper/__init__.py +0 -0
  63. {quantplay-2.0.39 → quantplay-2.0.41}/tests/wrapper/aws/__init__.py +0 -0
  64. {quantplay-2.0.39 → quantplay-2.0.41}/tests/wrapper/aws/s3_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.39
3
+ Version: 2.0.41
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -5,7 +5,7 @@ line-length = 90
5
5
  line-length = 90
6
6
 
7
7
  [tool.pyright]
8
- typeCheckingMode = "standard"
8
+ typeCheckingMode = "strict"
9
9
  pythonVersion = "3.12"
10
10
  venvPath = "."
11
11
  venv = ".venv"
@@ -314,8 +314,9 @@ class Aliceblue(Broker):
314
314
  data: ModifyOrderRequest = {
315
315
  "order_id": order_id,
316
316
  "price": price,
317
- "order_type": order_type,
318
317
  }
318
+ if trigger_price is not None:
319
+ data["order_type"] = order_type
319
320
 
320
321
  if trigger_price is not None and trigger_price > 0:
321
322
  data["trigger_price"] = trigger_price
@@ -403,7 +404,7 @@ class Aliceblue(Broker):
403
404
  if not isinstance(positions_response, list):
404
405
  return pl.DataFrame(schema=self.positions_schema)
405
406
 
406
- positions_df = pl.from_dicts(positions_response)
407
+ positions_df = pl.from_dicts(positions_response) # type: ignore
407
408
 
408
409
  positions_df = positions_df.with_columns(
409
410
  (
@@ -454,13 +455,16 @@ class Aliceblue(Broker):
454
455
  self.positions_schema
455
456
  )
456
457
 
458
+ def get_exchange(self, exchange: ExchangeType) -> Any:
459
+ return exchange
460
+
457
461
  def orders(self, tag: str | None = None, add_ltp: bool = True) -> pl.DataFrame:
458
462
  orders_response = self.invoke_aliceblue_api(self.alice.order_data)
459
463
 
460
464
  if not isinstance(orders_response, list):
461
465
  return pl.DataFrame(schema=self.orders_schema)
462
466
 
463
- if len(orders_response) == 0:
467
+ if len(orders_response) == 0: # type: ignore
464
468
  return pl.DataFrame(schema=self.orders_schema)
465
469
 
466
470
  orders_df = pl.DataFrame(orders_response)
@@ -106,6 +106,9 @@ class AngelOne(Broker):
106
106
  if load_instrument:
107
107
  self.load_instrument()
108
108
 
109
+ def get_exchange(self, exchange: ExchangeType) -> Any:
110
+ return exchange
111
+
109
112
  def load_instrument(self, file_name: str | None = None) -> None:
110
113
  try:
111
114
  instrument_data_instance = InstrumentData.get_instance()
@@ -282,7 +285,7 @@ class AngelOne(Broker):
282
285
 
283
286
  Constants.logger.info(f"Modifying order [{order_id}] params [{order_params}]")
284
287
  response = self.invoke_angelone_api(
285
- self.wrapper.modifyOrder,
288
+ self.wrapper.modifyOrder, # type: ignore
286
289
  orderparams=order_params,
287
290
  )
288
291
  Constants.logger.info(f"[MODIFY_ORDER_RESPONSE] {response}")
@@ -1,17 +1,14 @@
1
1
  import codecs
2
2
  import pickle
3
3
  import traceback
4
- from datetime import datetime, timedelta
5
- from typing import Any, Callable, Dict, Hashable, List
4
+ from typing import Any, Dict, Hashable, List
6
5
 
7
- import pandas as pd
8
6
  import polars as pl
9
7
  from kiteconnect import KiteConnect, KiteTicker # type: ignore
10
8
  from kiteconnect.exceptions import TokenException # type: ignore
11
9
  from retrying import retry # type: ignore
12
10
 
13
11
  from quantplay.broker.generics.broker import Broker
14
- from quantplay.broker.kite_utils import KiteUtils
15
12
  from quantplay.exception.exceptions import (
16
13
  InvalidArgumentException,
17
14
  QuantplayOrderPlacementException,
@@ -21,19 +18,16 @@ from quantplay.exception.exceptions import (
21
18
  from quantplay.exception.exceptions import TokenException as QuantplayTokenException
22
19
  from quantplay.model.broker import (
23
20
  ExchangeType,
24
- ModifyOrderRequest,
25
- UserBrokerProfileResponse,
26
21
  )
27
22
  from quantplay.model.generics import (
28
23
  DhanTypes,
29
- NorenTypes,
30
24
  OrderTypeType,
31
25
  ProductType,
32
26
  TransactionType,
33
27
  )
34
28
  from quantplay.utils.constant import Constants, OrderType
35
29
  from quantplay.utils.pickle_utils import InstrumentData, PickleUtils
36
- from dhanhq import dhanhq
30
+ from dhanhq import dhanhq # type:ignore
37
31
 
38
32
 
39
33
  class Dhan(Broker):
@@ -244,7 +238,7 @@ class Dhan(Broker):
244
238
  Constants.logger.info(
245
239
  f"[PLACING_ORDER] {tradingsymbol} {exchange} {quantity} {tag}"
246
240
  )
247
- order_id = self.dhan.place_order(
241
+ order_id: str = self.dhan.place_order( # type:ignore
248
242
  security_id="1333", # hdfcbank
249
243
  exchange_segment=self.get_exchange_segment(exchange),
250
244
  transaction_type=transaction_type,
@@ -254,7 +248,7 @@ class Dhan(Broker):
254
248
  price=price,
255
249
  )
256
250
 
257
- return order_id
251
+ return order_id # type:ignore
258
252
  except Exception as e:
259
253
  raise QuantplayOrderPlacementException(str(e))
260
254
 
@@ -300,7 +294,7 @@ class Dhan(Broker):
300
294
  def positions(self, drop_cnc: bool = True) -> pl.DataFrame:
301
295
  positions = self.dhan_w.get_positions()
302
296
 
303
- positions_df = pl.DataFrame(positions.get("data", {}))
297
+ positions_df = pl.DataFrame(positions.get("data", {})) # type:ignore
304
298
 
305
299
  if len(positions_df) == 0:
306
300
  return pl.DataFrame(schema=self.positions_schema)
@@ -1,7 +1,7 @@
1
1
  import codecs
2
2
  import pickle
3
3
  import traceback
4
- from typing import Dict
4
+ from typing import Any, Dict
5
5
 
6
6
  import polars as pl
7
7
  import pyotp
@@ -17,7 +17,7 @@ from quantplay.exception.exceptions import (
17
17
  TokenException,
18
18
  retry_exception,
19
19
  )
20
- from quantplay.model.broker import UserBrokerProfileResponse
20
+ from quantplay.model.broker import ModifyOrderRequest, UserBrokerProfileResponse
21
21
  from quantplay.model.generics import (
22
22
  ExchangeType,
23
23
  OrderTypeType,
@@ -98,6 +98,9 @@ class FivePaisa(Broker):
98
98
  self.load_instrument()
99
99
  super(FivePaisa, self).__init__()
100
100
 
101
+ def get_exchange(self, exchange: ExchangeType) -> Any:
102
+ return exchange
103
+
101
104
  def set_client(self, serialized_client: str):
102
105
  self.client: FivePaisaClient = pickle.loads(
103
106
  codecs.decode(serialized_client.encode(), "base64")
@@ -150,7 +153,7 @@ class FivePaisa(Broker):
150
153
  )
151
154
  def ltp(self, exchange: ExchangeType, tradingsymbol: str) -> float:
152
155
  tradingsymbol = self.get_symbol(tradingsymbol, exchange)
153
- exchange_name = self.get_exchange(exchange)
156
+ exchange_name = self.get_exchange_name(exchange)
154
157
  exchange_type = self.get_exchange_type(exchange)
155
158
  token = self.symbol_attribute(exchange, tradingsymbol, "token")
156
159
  req_list = [
@@ -448,7 +451,10 @@ class FivePaisa(Broker):
448
451
 
449
452
  return False
450
453
 
451
- def get_exchange(self, exchange):
454
+ def get_order_type(self, order_type: OrderTypeType) -> Any:
455
+ return order_type
456
+
457
+ def get_exchange_name(self, exchange: ExchangeType):
452
458
  return exchange[0]
453
459
 
454
460
  @staticmethod
@@ -490,7 +496,7 @@ class FivePaisa(Broker):
490
496
  )
491
497
  response = self.client.place_order(
492
498
  OrderType=transaction_type[0],
493
- Exchange=self.get_exchange(exchange),
499
+ Exchange=self.get_exchange_name(exchange),
494
500
  ExchangeType=FivePaisa.get_exchange_type(exchange),
495
501
  ScripCode=token,
496
502
  Qty=quantity,
@@ -551,6 +557,12 @@ class FivePaisa(Broker):
551
557
  }
552
558
  return response
553
559
 
560
+ def cancel_order(self, order_id: str, variety: str | None = None) -> None:
561
+ raise NotImplementedError("Cancel Order Not Implementd")
562
+
563
+ def modify_order(self, order: ModifyOrderRequest) -> str:
564
+ raise NotImplementedError("Cancel Order Not Implementd")
565
+
554
566
  def get_quantplay_product(self, exchange: ExchangeType, product: ProductType):
555
567
  product_map: Dict[str, ProductType] = {"D": "CNC", "I": "MIS"}
556
568
  if product in product_map:
@@ -16,7 +16,7 @@ from queue import Queue
16
16
  from threading import Lock
17
17
  from typing import Any, Dict, List, Literal, Type
18
18
 
19
- import pandas as pd
19
+ import pandas as pd # type:ignore
20
20
  import polars as pl
21
21
  import requests
22
22
  from cachetools import TTLCache, cached
@@ -24,7 +24,6 @@ from cachetools import TTLCache, cached
24
24
  from quantplay.exception.exceptions import (
25
25
  FeatureNotSupported,
26
26
  InvalidArgumentException,
27
- QuantplayOrderPlacementException,
28
27
  StaleDataFound,
29
28
  )
30
29
  from quantplay.model.broker import (
@@ -32,7 +31,12 @@ from quantplay.model.broker import (
32
31
  ModifyOrderRequest,
33
32
  UserBrokerProfileResponse,
34
33
  )
35
- from quantplay.model.generics import ProductType, TransactionType, OrderTypeType
34
+ from quantplay.model.generics import (
35
+ ProductType,
36
+ QuantplayOrder,
37
+ TransactionType,
38
+ OrderTypeType,
39
+ )
36
40
  from quantplay.model.instrument_data import InstrumentDataType
37
41
  from quantplay.model.order_event import OrderUpdateEvent
38
42
  from quantplay.utils.constant import Constants
@@ -218,104 +222,6 @@ class Broker(ABC):
218
222
  def cached_positions(self) -> pl.DataFrame:
219
223
  return self.positions()
220
224
 
221
- def execute_order(
222
- self,
223
- tradingsymbol: str,
224
- exchange: ExchangeType,
225
- quantity: int,
226
- order_type: OrderTypeType,
227
- transaction_type: TransactionType,
228
- stoploss: float | None,
229
- tag: str,
230
- product: ProductType,
231
- price: float,
232
- ):
233
- upper_circuit = None
234
- trade_price = copy.deepcopy(price)
235
- if price is None:
236
- live_data = self.live_data(exchange=exchange, tradingsymbol=tradingsymbol)
237
- price = live_data["ltp"]
238
- upper_circuit = live_data["upper_circuit"]
239
- trade_price = copy.deepcopy(price)
240
- try:
241
- if stoploss is not None:
242
- if transaction_type == "SELL":
243
- sl_transaction_type = "BUY"
244
- sl_trigger_price = self.round_to_tick(price * (1 + stoploss))
245
-
246
- if exchange == "NFO":
247
- price = sl_trigger_price * 1.05
248
- elif exchange == "NSE":
249
- price = sl_trigger_price * 1.01
250
- else:
251
- raise Exception("{} not supported for trading".format(exchange))
252
-
253
- sl_price = self.round_to_tick(price)
254
- elif transaction_type == "BUY":
255
- sl_transaction_type = "SELL"
256
- sl_trigger_price = self.round_to_tick(price * (1 - stoploss))
257
-
258
- if exchange == self.nfo_exchange:
259
- price = sl_trigger_price * 0.95
260
- elif exchange == "NSE":
261
- price = sl_trigger_price * 0.99
262
- else:
263
- raise Exception("{} not supported for trading".format(exchange))
264
-
265
- sl_price = self.round_to_tick(price)
266
-
267
- else:
268
- raise Exception(
269
- "Invalid transaction_type {}".format(transaction_type)
270
- )
271
-
272
- if upper_circuit is not None and sl_price > upper_circuit:
273
- raise Exception(
274
- f"[PRICE_BREACHED] {sl_price} is above upper circuit price [{upper_circuit}]"
275
- )
276
-
277
- stoploss_order_id = self.place_order(
278
- tradingsymbol=tradingsymbol,
279
- exchange=exchange,
280
- quantity=quantity,
281
- order_type=self.order_type_sl, # type: ignore
282
- transaction_type=sl_transaction_type,
283
- tag=tag,
284
- product=product,
285
- price=sl_price,
286
- trigger_price=sl_trigger_price,
287
- )
288
-
289
- if stoploss_order_id is None:
290
- Constants.logger.error(
291
- "[ORDER_REJECTED] tradingsymbol {}".format(tradingsymbol)
292
- )
293
- raise QuantplayOrderPlacementException(
294
- "Order reject for {}".format(tradingsymbol)
295
- )
296
-
297
- if order_type == "MARKET":
298
- trade_price: float = 0
299
-
300
- response = self.place_order(
301
- tradingsymbol=tradingsymbol,
302
- exchange=exchange,
303
- quantity=quantity,
304
- order_type=order_type,
305
- transaction_type=transaction_type,
306
- tag=tag,
307
- product=product,
308
- price=trade_price,
309
- )
310
- return response
311
- except Exception as e:
312
- raise e
313
-
314
- """
315
- Input : quantplay symbol
316
- Output : broker symbol
317
- """
318
-
319
225
  def get_symbol(self, symbol: str, exchange: ExchangeType | None = None):
320
226
  return symbol
321
227
 
@@ -324,12 +230,6 @@ class Broker(ABC):
324
230
  Output : broker exchange
325
231
  """
326
232
 
327
- def get_order_type(self, order_type):
328
- return order_type
329
-
330
- def get_exchange(self, exchange):
331
- return exchange
332
-
333
233
  def live_data(self, exchange: ExchangeType, tradingsymbol: str):
334
234
  return {
335
235
  "ltp": self.ltp(exchange, tradingsymbol),
@@ -337,7 +237,7 @@ class Broker(ABC):
337
237
  "lower_circuit": None,
338
238
  }
339
239
 
340
- def basket_margin(self, basket_orders) -> Dict[str, Any]:
240
+ def basket_margin(self, basket_orders: List[Any]) -> Dict[str, Any]:
341
241
  raise FeatureNotSupported("Margin calculator not supported by broker")
342
242
 
343
243
  def verify_rms_square_off(
@@ -387,9 +287,6 @@ class Broker(ABC):
387
287
 
388
288
  return quantity_in_lots * lot_size
389
289
 
390
- def get_product(self, product):
391
- return product
392
-
393
290
  def get_lot_size(self, exchange: ExchangeType, tradingsymbol: str):
394
291
  tradingsymbol = self.get_symbol(tradingsymbol, exchange=exchange)
395
292
  broker_exchange = self.get_exchange(exchange)
@@ -855,16 +752,6 @@ class Broker(ABC):
855
752
 
856
753
  return orders_to_close
857
754
 
858
- def add_ltp(self, orders):
859
- orders.loc[:, "exchange_symbol"] = orders.exchange + ":" + orders.tradingsymbol
860
-
861
- all_symbols = list(orders.exchange_symbol.unique())
862
- symbol_ltp = {}
863
- for exchange_symbol in all_symbols:
864
- ltp = self.ltp(exchange_symbol.split(":")[0], exchange_symbol.split(":")[1])
865
- symbol_ltp[exchange_symbol] = ltp
866
- orders.loc[:, "ltp"] = orders["exchange_symbol"].map(symbol_ltp)
867
-
868
755
  def modify_price(
869
756
  self,
870
757
  order_id: str,
@@ -889,7 +776,7 @@ class Broker(ABC):
889
776
  return "BUY"
890
777
  raise Exception(f"unknown transaction type [{transaction_type}]")
891
778
 
892
- def place_large_orders(self, orders: List) -> List[str | None]:
779
+ def place_large_orders(self, orders: List[QuantplayOrder]) -> List[str | None]:
893
780
  order_ids: List[str | None] = []
894
781
  orders = sorted(orders, key=lambda d: d["transaction_type"])
895
782
  for order in orders:
@@ -943,7 +830,7 @@ class Broker(ABC):
943
830
 
944
831
  new_trading_symbol = f"{underlying}{expiry}{new_strike}{instrument_type}"
945
832
 
946
- orders = [
833
+ orders: List[QuantplayOrder] = [
947
834
  {
948
835
  "tradingsymbol": tradingsymbol,
949
836
  "exchange": exchange,
@@ -1157,3 +1044,12 @@ class Broker(ABC):
1157
1044
  def margins(self) -> Dict[str, float]:
1158
1045
  """Returns User Margin Summary"""
1159
1046
  ...
1047
+
1048
+ @abstractmethod
1049
+ def get_order_type(self, order_type: OrderTypeType) -> ...: ...
1050
+
1051
+ @abstractmethod
1052
+ def get_exchange(self, exchange: ExchangeType) -> ...: ...
1053
+
1054
+ @abstractmethod
1055
+ def get_product(self, product: ProductType) -> ...: ...
@@ -0,0 +1,222 @@
1
+ import os
2
+ import traceback
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict
5
+
6
+ from quantplay.broker.aliceblue import Aliceblue
7
+ from quantplay.broker.angelone import AngelOne
8
+
9
+ from quantplay.broker.five_paisa import FivePaisa
10
+ from quantplay.broker.flattrade import FlatTrade
11
+ from quantplay.broker.iifl_xts import IIFL as IIFL_XTS
12
+ from quantplay.broker.motilal import Motilal
13
+ from quantplay.broker.shoonya import FinvAsia
14
+ from quantplay.broker.upstox import Upstox
15
+ from quantplay.broker.zerodha import Zerodha
16
+ from quantplay.exception.exceptions import InvalidArgumentException
17
+
18
+ from quantplay.utils.pickle_utils import PickleUtils
19
+ from quantplay.utils.caching import InstrumentCache
20
+
21
+ BrokerType = (
22
+ Aliceblue
23
+ | AngelOne
24
+ | FlatTrade
25
+ | Motilal
26
+ | FinvAsia
27
+ | Upstox
28
+ | Zerodha
29
+ | IIFL_XTS
30
+ | FivePaisa
31
+ )
32
+
33
+ instrument_cache = InstrumentCache()
34
+
35
+
36
+ @dataclass
37
+ class Broker:
38
+ ZERODHA = "Zerodha"
39
+ UPSTOX = "Upstox"
40
+ ALICEBLUE = "Aliceblue"
41
+ FIVEPAISA_OPENAPI = "5Paisa_OpenAPI"
42
+ FINVASIA = "Finvasia"
43
+ FLATTRADE = "Flattrade"
44
+ IIFL_XTS = "IIFL_XTS"
45
+ MOTILAL = "Motilal"
46
+ ANGELONE = "Angelone"
47
+
48
+
49
+ class BrokerFactory:
50
+ broker_instruments_map = {
51
+ Broker.ZERODHA: "zerodha_instruments",
52
+ Broker.FINVASIA: "shoonya_instruments",
53
+ Broker.FLATTRADE: "shoonya_instruments",
54
+ Broker.IIFL_XTS: "xts_instruments",
55
+ Broker.MOTILAL: "motilal_instruments",
56
+ Broker.ANGELONE: "angelone_instruments",
57
+ Broker.ALICEBLUE: "aliceblue_instruments",
58
+ Broker.UPSTOX: "upstox_instruments",
59
+ Broker.FIVEPAISA_OPENAPI: "5paisa_instruments",
60
+ }
61
+
62
+ def __init__(self):
63
+ self.client_broker_data: Dict[str, BrokerType] = {}
64
+
65
+ def get_broker_key(self, username: str, broker_name: str) -> str:
66
+ return "{}:{}".format(username, broker_name)
67
+
68
+ def set_broker_instruments(self, broker_name: str, broker: BrokerType) -> None:
69
+ symbol_data_key = f"{broker_name}_instruments"
70
+ quantplay_symbol_key = f"{broker_name}_qplay_symbols"
71
+ broker_symbol_key = f"{broker_name}_broker_symbols"
72
+
73
+ symbol_data = instrument_cache.get(symbol_data_key)
74
+ quantplay_symbol_map = instrument_cache.get(quantplay_symbol_key)
75
+ broker_symbol_map = instrument_cache.get(broker_symbol_key)
76
+
77
+ if symbol_data is not None:
78
+ broker.symbol_data = symbol_data
79
+
80
+ if broker_name != "Zerodha":
81
+ if quantplay_symbol_map is not None and broker_symbol_map is not None:
82
+ broker.quantplay_symbol_map = quantplay_symbol_map
83
+ broker.broker_symbol_map = broker_symbol_map
84
+
85
+ else:
86
+ broker.initialize_broker_symbol_map()
87
+ instrument_cache.set(
88
+ quantplay_symbol_key, broker.quantplay_symbol_map
89
+ )
90
+ instrument_cache.set(broker_symbol_key, broker.broker_symbol_map)
91
+
92
+ return
93
+
94
+ try:
95
+ symbol_data = PickleUtils.load_data(
96
+ BrokerFactory.broker_instruments_map[broker_name]
97
+ )
98
+ broker.symbol_data = symbol_data
99
+
100
+ if broker_name != "Zerodha":
101
+ broker.initialize_broker_symbol_map()
102
+ instrument_cache.set(quantplay_symbol_key, broker.quantplay_symbol_map)
103
+
104
+ instrument_cache.set(symbol_data_key, symbol_data)
105
+
106
+ except Exception:
107
+ traceback.print_exc()
108
+
109
+ if broker_name != "Zerodha":
110
+ broker.load_instrument(BrokerFactory.broker_instruments_map[broker_name])
111
+ else:
112
+ broker.initialize_symbol_data()
113
+
114
+ def store_broker_client(
115
+ self, broker_info: Dict[str, Any], load_instrument: bool = True
116
+ ) -> None:
117
+ username = broker_info["username"]
118
+ nickname = broker_info["nickname"]
119
+
120
+ broker_key = self.get_broker_key(username, nickname)
121
+
122
+ broker_data = broker_info["broker_data"]
123
+ broker = broker_info["broker"]
124
+
125
+ broker_client: BrokerType | None = None
126
+
127
+ if broker == "Motilal":
128
+ motial_headers = broker_data["headers"]
129
+ broker_client = Motilal(
130
+ headers=motial_headers, load_instrument=load_instrument
131
+ )
132
+
133
+ elif broker == "Zerodha":
134
+ broker_client = Zerodha(
135
+ wrapper=broker_data["zerodha_wrapper"],
136
+ load_instrument=load_instrument,
137
+ )
138
+
139
+ elif broker == "Angelone":
140
+ broker_client = AngelOne(
141
+ user_id=broker_data["user_id"],
142
+ api_key=broker_data["api_key"],
143
+ access_token=broker_data["access_token"],
144
+ refresh_token=broker_data["refresh_token"],
145
+ feed_token=broker_data["feed_token"],
146
+ load_instrument=load_instrument,
147
+ )
148
+
149
+ elif broker == Broker.ALICEBLUE:
150
+ broker_client = Aliceblue(client=broker_data["client"])
151
+
152
+ elif broker == Broker.UPSTOX:
153
+ broker_client = Upstox(
154
+ access_token=broker_data["access_token"],
155
+ user_id=broker_data["user_id"],
156
+ )
157
+
158
+ elif broker == Broker.FINVASIA:
159
+ finvasia_data = broker_data
160
+ broker_client = FinvAsia(
161
+ order_updates=None,
162
+ user_id=finvasia_data["user_id"],
163
+ password=finvasia_data["password"],
164
+ user_token=finvasia_data["user_token"],
165
+ load_instrument=load_instrument,
166
+ )
167
+
168
+ elif broker == Broker.FLATTRADE:
169
+ flattrade_data = broker_data
170
+ broker_client = FlatTrade(
171
+ order_updates=None,
172
+ user_id=flattrade_data["user_id"],
173
+ password=flattrade_data["password"],
174
+ user_token=flattrade_data["user_token"],
175
+ load_instrument=load_instrument,
176
+ )
177
+
178
+ elif broker == Broker.FIVEPAISA_OPENAPI:
179
+ broker_client = FivePaisa(
180
+ client=broker_data["client"],
181
+ load_instrument=load_instrument,
182
+ )
183
+
184
+ self.client_broker_data[broker_key] = broker_client
185
+
186
+ elif broker == Broker.IIFL_XTS:
187
+ iifl_xts_data = broker_data
188
+ broker_client = IIFL_XTS(
189
+ wrapper=broker_data["wrapper"],
190
+ md_wrapper=broker_data["md_wrapper"],
191
+ client_id=iifl_xts_data["user_id"],
192
+ load_instrument=load_instrument,
193
+ )
194
+
195
+ else:
196
+ raise InvalidArgumentException(f"Broker {broker} not supported")
197
+
198
+ self.client_broker_data[broker_key] = broker_client
199
+ self.set_broker_instruments(broker_name=broker, broker=broker_client)
200
+
201
+ def get_broker_client(self, broker_info: Dict[str, Any]) -> BrokerType:
202
+ username = broker_info["username"]
203
+ nickname = broker_info["nickname"]
204
+
205
+ broker_key = self.get_broker_key(username, nickname)
206
+
207
+ if broker_key in self.client_broker_data:
208
+ return self.client_broker_data[broker_key]
209
+
210
+ self.store_broker_client(broker_info, load_instrument=False)
211
+
212
+ if broker_key in self.client_broker_data:
213
+ return self.client_broker_data[broker_key]
214
+ else:
215
+ raise InvalidArgumentException("Invalid broker API configuration")
216
+
217
+ def clear_instrument_cache(self, broker: str) -> None:
218
+ symbol_data_key = f"{broker}_instruments"
219
+ instrument_cache.delete(symbol_data_key)
220
+
221
+ file_name = self.broker_instruments_map[broker]
222
+ os.system(f"rm /tmp/{file_name}*")
@@ -4,10 +4,9 @@ from queue import Queue
4
4
  from typing import Any, Dict
5
5
  import requests
6
6
  import json
7
- import pandas as pd
8
7
  import polars as pl
9
8
 
10
- from quantplay.model.broker import ModifyOrderRequest, UserBrokerProfileResponse
9
+ from quantplay.model.broker import UserBrokerProfileResponse
11
10
  from quantplay.model.generics import (
12
11
  ExchangeType,
13
12
  OrderTypeType,
@@ -153,13 +152,6 @@ class Motilal(Broker):
153
152
  self.nfo_exchange = "NSEFO"
154
153
  self.exchange_code_map = {"NFO": "NSEFO", "CDS": "NSECD", "BFO": "BSEFO"}
155
154
 
156
- def load_file_by_url(self, exchange):
157
- data_url = "https://openapi.motilaloswal.com/getscripmastercsv?name={}".format(
158
- exchange
159
- )
160
- df = pd.read_csv(data_url) # type: ignore
161
- self.instrument_data_by_exchange[exchange] = df
162
-
163
155
  def load_instrument(self, file_name: str | None = None) -> None:
164
156
  try:
165
157
  self.symbol_data = InstrumentData.get_instance().load_data( # type: ignore
@@ -181,6 +173,12 @@ class Motilal(Broker):
181
173
 
182
174
  return self.quantplay_symbol_map[symbol]
183
175
 
176
+ def get_motilal_symbol(self, symbol: str):
177
+ if symbol not in self.quantplay_symbol_map:
178
+ return symbol
179
+
180
+ return self.quantplay_symbol_map[symbol]
181
+
184
182
  def get_order_type(self, order_type: OrderTypeType):
185
183
  if order_type == OrderType.sl:
186
184
  return "STOPLOSS"
@@ -198,9 +196,9 @@ class Motilal(Broker):
198
196
  return "DELIVERY"
199
197
  elif product == "NRML":
200
198
  return "NORMAL"
201
- elif product == "DELIVERY":
202
- return "DELIVERY"
203
- return "NORMAL"
199
+ elif product == "MIS":
200
+ return "NORMAL"
201
+ return product
204
202
 
205
203
  def place_order_quantity(
206
204
  self, quantity: int, tradingsymbol: str, exchange: ExchangeType
@@ -210,8 +208,8 @@ class Motilal(Broker):
210
208
 
211
209
  return quantity_in_lots
212
210
 
213
- def get_lot_size(self, exchange, tradingsymbol: str):
214
- tradingsymbol = self.get_symbol(tradingsymbol, exchange=exchange)
211
+ def get_lot_size(self, exchange: str, tradingsymbol: str):
212
+ tradingsymbol = self.get_symbol(tradingsymbol)
215
213
  if exchange == "NSEFO":
216
214
  exchange = "NFO"
217
215
  elif exchange == "BSEFO":
@@ -303,6 +303,9 @@ class Noren(Broker):
303
303
 
304
304
  return data
305
305
 
306
+ def get_exchange(self, exchange: ExchangeType) -> Any:
307
+ return exchange
308
+
306
309
  @retry(
307
310
  wait_exponential_multiplier=3000,
308
311
  wait_exponential_max=10000,
@@ -312,8 +315,6 @@ class Noren(Broker):
312
315
  order_id = order["order_id"]
313
316
  existing_details = self.order_history(order_id)
314
317
 
315
- order["order_type"] = self.get_order_type(order["order_type"]) # type: ignore
316
-
317
318
  if "trigger_price" not in order:
318
319
  order["trigger_price"] = None
319
320
 
@@ -323,11 +324,13 @@ class Noren(Broker):
323
324
  if "quantity" not in order:
324
325
  order["quantity"] = int(existing_details["quantity"])
325
326
 
327
+ order["order_type"] = self.get_order_type(order["order_type"]) # type: ignore
328
+
326
329
  try:
327
330
  logger.info(f"[MODIFYING_ORDER] {order}")
328
331
  response = self.api.modify_order( # type:ignore
329
332
  orderno=order_id,
330
- exchange=existing_details["exchange"],
333
+ exchange=existing_details["exchange"], # type: ignore
331
334
  tradingsymbol=existing_details["tradingsymbol"],
332
335
  newprice_type=order["order_type"],
333
336
  newquantity=order["quantity"],
@@ -115,7 +115,7 @@ class Upstox(Broker):
115
115
 
116
116
  return exchange
117
117
 
118
- def get_quantplay_exchange(self, exchange) -> ExchangeType:
118
+ def get_quantplay_exchange(self, exchange: str) -> ExchangeType:
119
119
  exchange_map: Dict[str, ExchangeType] = {
120
120
  "NSE_FO": "NFO",
121
121
  "NSECD": "CDS",
@@ -125,7 +125,7 @@ class Upstox(Broker):
125
125
  if exchange in exchange_map:
126
126
  return exchange_map[exchange]
127
127
 
128
- return exchange
128
+ return exchange # type: ignore
129
129
 
130
130
  def get_quantplay_symbol(self, symbol: str):
131
131
  return symbol
@@ -133,6 +133,9 @@ class Upstox(Broker):
133
133
  def get_lot_size(self, exchange: ExchangeType, tradingsymbol: str):
134
134
  return int(self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["lot_size"])
135
135
 
136
+ def get_order_type(self, order_type: OrderTypeType) -> Any:
137
+ return order_type
138
+
136
139
  @retry(
137
140
  wait_exponential_multiplier=1000,
138
141
  wait_exponential_max=10000,
@@ -158,7 +161,7 @@ class Upstox(Broker):
158
161
  self.handle_exception(e)
159
162
  Constants.logger.error("Exception when calling MarketQuoteApi->ltp: %s\n" % e)
160
163
 
161
- return ltp
164
+ return ltp # type: ignore
162
165
 
163
166
  @retry(
164
167
  wait_exponential_multiplier=1000,
@@ -216,7 +219,7 @@ class Upstox(Broker):
216
219
  product: ProductType,
217
220
  price: float,
218
221
  trigger_price: float | None = None,
219
- ):
222
+ ) -> str:
220
223
  exchange = self.get_quantplay_exchange(exchange)
221
224
  try:
222
225
  Constants.logger.info(
@@ -297,10 +300,10 @@ class Upstox(Broker):
297
300
  profile_data = api_response.data # type:ignore
298
301
 
299
302
  response: UserBrokerProfileResponse = {
300
- "user_id": profile_data.user_id,
301
- "full_name": profile_data.user_name,
302
- "exchanges": profile_data.exchanges,
303
- "email": profile_data.email,
303
+ "user_id": profile_data.user_id, # type: ignore
304
+ "full_name": profile_data.user_name, # type: ignore
305
+ "exchanges": profile_data.exchanges, # type: ignore
306
+ "email": profile_data.email, # type: ignore
304
307
  }
305
308
  self.email = response["email"]
306
309
  self.enabled_exchanges = response["exchanges"]
@@ -370,8 +373,9 @@ class Upstox(Broker):
370
373
  try:
371
374
  # Get Positions
372
375
  api_response = api_instance.get_positions(self.api_version) # type: ignore
373
- positions = [
374
- position.to_dict() for position in api_response.data # type:ignore
376
+ positions = [ # type:ignore
377
+ position.to_dict() # type:ignore
378
+ for position in api_response.data # type:ignore
375
379
  ]
376
380
  positions_df = pl.DataFrame(positions)
377
381
  except ApiException as e:
@@ -634,7 +638,7 @@ class Upstox(Broker):
634
638
  configuration.access_token = self.access_token
635
639
 
636
640
  # Get portfolio stream feed authorize
637
- response = self.get_portfolio_stream_feed_authorize(
641
+ response = self.get_portfolio_stream_feed_authorize( # type: ignore
638
642
  self.api_version, configuration
639
643
  )
640
644
 
@@ -651,13 +655,13 @@ class Upstox(Broker):
651
655
 
652
656
  def get_portfolio_stream_feed_authorize(
653
657
  self, api_version: str, configuration: upstox_client.Configuration
654
- ):
658
+ ) -> Any:
655
659
  api_instance = upstox_client.WebsocketApi(upstox_client.ApiClient(configuration))
656
660
  api_response = api_instance.get_portfolio_stream_feed_authorize(api_version) # type: ignore
657
661
 
658
- return api_response
662
+ return api_response # type: ignore
659
663
 
660
- def get_quantplay_product(self, exchange, product):
664
+ def get_quantplay_product(self, exchange: ExchangeType, product: str):
661
665
  product_map = {"D": "CNC", "I": "MIS"}
662
666
 
663
667
  if product in product_map:
@@ -703,14 +703,22 @@ class XTS(Broker):
703
703
  trigger_price: float | None = None,
704
704
  order_type: OrderTypeType | None = None,
705
705
  ):
706
- self.modify_order(
707
- {
708
- "order_id": str(order_id),
709
- "price": price,
710
- "trigger_price": trigger_price,
711
- "order_type": order_type,
712
- }
713
- )
706
+ data: ModifyOrderRequest = {
707
+ "order_id": str(order_id),
708
+ "price": price,
709
+ "trigger_price": trigger_price,
710
+ }
711
+
712
+ if order_type is not None:
713
+ data["order_type"] = order_type
714
+
715
+ self.modify_order(data)
716
+
717
+ def get_exchange(self, exchange: ExchangeType) -> Any:
718
+ return exchange
719
+
720
+ def get_product(self, product: ProductType) -> Any:
721
+ return product
714
722
 
715
723
  def stream_order_updates(self):
716
724
  if self.wrapper.token is None:
@@ -197,6 +197,15 @@ class Zerodha(Broker):
197
197
  def cancel_order(self, order_id: str, variety: str | None = "regular"):
198
198
  self.wrapper.cancel_order(order_id=order_id, variety=variety) # type: ignore
199
199
 
200
+ def get_exchange(self, exchange: ExchangeType) -> Any:
201
+ return exchange
202
+
203
+ def get_order_type(self, order_type: OrderTypeType) -> Any:
204
+ return order_type
205
+
206
+ def get_product(self, product: ProductType) -> Any:
207
+ return product
208
+
200
209
  @retry(
201
210
  wait_exponential_multiplier=3000,
202
211
  wait_exponential_max=10000,
@@ -15,7 +15,7 @@ class ModifyOrderRequest(TypedDict):
15
15
  quantity: NotRequired[int]
16
16
  exchange: NotRequired[ExchangeType]
17
17
  trigger_price: NotRequired[float | None]
18
- order_type: NotRequired[OrderTypeType | None]
18
+ order_type: NotRequired[OrderTypeType]
19
19
  price: NotRequired[float]
20
20
 
21
21
 
@@ -1,8 +1,6 @@
1
1
  from typing import Literal
2
- from dhanhq import dhanhq
3
- from typing import Literal, TypedDict
2
+ from typing import TypedDict
4
3
 
5
- from pya3.alicebluepy import Aliceblue
6
4
 
7
5
  ExchangeType = Literal["NSE", "BSE", "NFO", "BFO", "CDS", "BCD", "MCX"]
8
6
  ProductType = Literal["NRML", "MIS", "CNC"]
@@ -33,15 +31,15 @@ class ZerodhaTypes:
33
31
 
34
32
 
35
33
  class AliceblueTypes:
36
- ExchangeType = (
37
- Aliceblue.EXCHANGE_BCD
38
- | Aliceblue.EXCHANGE_BFO
39
- | Aliceblue.EXCHANGE_BSE
40
- | Aliceblue.EXCHANGE_CDS
41
- | Aliceblue.EXCHANGE_MCX
42
- | Aliceblue.EXCHANGE_NFO
43
- | Aliceblue.EXCHANGE_NSE
44
- )
34
+ ExchangeType = Literal[
35
+ "NSE",
36
+ "NFO",
37
+ "CDS",
38
+ "BSE",
39
+ "BFO",
40
+ "BCD",
41
+ "MCX",
42
+ ]
45
43
 
46
44
 
47
45
  class NorenTypes:
@@ -76,3 +74,11 @@ class XTSTypes:
76
74
  PositionSqureOffModeType = Literal["DayWise", "NetWise"]
77
75
  PositionSquareOffQuantityTypeType = Literal["Percentage", "ExactQty"]
78
76
  DayOrNetType = Literal["DAY", "NET"]
77
+
78
+
79
+ class QuantplayOrder(TypedDict):
80
+ exchange: ExchangeType
81
+ transaction_type: TransactionType
82
+ tradingsymbol: str
83
+ quantity: int
84
+ product: ProductType
@@ -0,0 +1,35 @@
1
+ from threading import Event, Thread
2
+ from time import sleep
3
+ from typing import Any, Dict, Hashable
4
+
5
+
6
+ class InstrumentCache:
7
+ def __init__(self) -> None:
8
+ self.cache: Dict[Hashable, Any] = {}
9
+ self.ttl = 300
10
+
11
+ self.stop_event = Event()
12
+ self.stop_event.set()
13
+
14
+ clear_thread = Thread(target=self.auto_clear, daemon=True)
15
+ clear_thread.start()
16
+
17
+ def __del__(self) -> None:
18
+ self.stop_event.clear()
19
+
20
+ def set(self, key: str, data: Any) -> None:
21
+ self.cache[key] = data
22
+
23
+ def get(self, key: str) -> Any | None:
24
+ return self.cache.get(key, None)
25
+
26
+ def delete(self, key: str) -> None:
27
+ del self.cache[key]
28
+
29
+ def clear(self) -> None:
30
+ self.cache = {}
31
+
32
+ def auto_clear(self) -> None:
33
+ while self.stop_event.is_set():
34
+ sleep(self.ttl)
35
+ self.clear()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.39
3
+ Version: 2.0.41
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -31,6 +31,7 @@ quantplay/broker/ft_utils/flattrade_utils.py
31
31
  quantplay/broker/ft_utils/ft_noren.py
32
32
  quantplay/broker/generics/__init__.py
33
33
  quantplay/broker/generics/broker.py
34
+ quantplay/broker/generics/broker_factory.py
34
35
  quantplay/broker/uplink/__init__.py
35
36
  quantplay/broker/uplink/uplink_utils.py
36
37
  quantplay/broker/xts_utils/Connect.py
@@ -45,6 +46,7 @@ quantplay/model/generics.py
45
46
  quantplay/model/instrument_data.py
46
47
  quantplay/model/order_event.py
47
48
  quantplay/utils/__init__.py
49
+ quantplay/utils/caching.py
48
50
  quantplay/utils/constant.py
49
51
  quantplay/utils/exchange.py
50
52
  quantplay/utils/number_utils.py
@@ -21,7 +21,7 @@ requirements = [
21
21
  setup(
22
22
  name="quantplay",
23
23
  long_description=Path("README.md").read_text(),
24
- version="2.0.39",
24
+ version="2.0.41",
25
25
  setup_requires=["pytest-runner"],
26
26
  install_requires=requirements,
27
27
  tests_require=[],
File without changes
File without changes
File without changes
File without changes