quantplay 2.0.8__tar.gz → 2.0.10__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 (70) hide show
  1. {quantplay-2.0.8 → quantplay-2.0.10}/PKG-INFO +1 -1
  2. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/noren.py +99 -68
  3. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay.egg-info/PKG-INFO +1 -1
  4. {quantplay-2.0.8 → quantplay-2.0.10}/setup.py +1 -1
  5. {quantplay-2.0.8 → quantplay-2.0.10}/README.md +0 -0
  6. {quantplay-2.0.8 → quantplay-2.0.10}/pyproject.toml +0 -0
  7. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/__init__.py +0 -0
  8. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/__init__.py +0 -0
  9. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/aliceblue.py +0 -0
  10. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/angelone.py +0 -0
  11. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/auto_login/__init__.py +0 -0
  12. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/auto_login/aliceblue.py +0 -0
  13. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/finvasia_utils/__init__.py +0 -0
  14. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
  15. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/five_paisa.py +0 -0
  16. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/flattrade.py +0 -0
  17. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/ft_utils/__init__.py +0 -0
  18. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/ft_utils/flattrade_utils.py +0 -0
  19. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/ft_utils/ft_noren.py +0 -0
  20. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/generics/__init__.py +0 -0
  21. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/generics/broker.py +0 -0
  22. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/iifl_xts.py +0 -0
  23. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/kite_utils.py +0 -0
  24. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/motilal.py +0 -0
  25. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/shoonya.py +0 -0
  26. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/uplink/__init__.py +0 -0
  27. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/uplink/uplink_utils.py +0 -0
  28. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/upstox.py +0 -0
  29. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/xts.py +0 -0
  30. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/xts_utils/Connect.py +0 -0
  31. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/xts_utils/Exception.py +0 -0
  32. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
  33. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/xts_utils/__init__.py +0 -0
  34. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/broker/zerodha.py +0 -0
  35. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/exception/__init__.py +0 -0
  36. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/exception/exceptions.py +0 -0
  37. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/model/__init__.py +0 -0
  38. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/model/broker/__init__.py +0 -0
  39. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/model/broker/generics.py +0 -0
  40. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/py.typed +0 -0
  41. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/__init__.py +0 -0
  42. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/equities/__init__.py +0 -0
  43. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/equities/intraday/__init__.py +0 -0
  44. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/equities/overnight/__init__.py +0 -0
  45. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/futures/__init__.py +0 -0
  46. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/futures/overnight/__init__.py +0 -0
  47. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/options/__init__.py +0 -0
  48. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/options/intraday/__init__.py +0 -0
  49. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/options/intraday/ladder.py +0 -0
  50. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/options/intraday/musk.py +0 -0
  51. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/strategies/options/intraday/short_straddle.py +0 -0
  52. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/utils/__init__.py +0 -0
  53. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/utils/constant.py +0 -0
  54. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/utils/exchange.py +0 -0
  55. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/utils/number_utils.py +0 -0
  56. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/utils/pickle_utils.py +0 -0
  57. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/utils/selenium_utils.py +0 -0
  58. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/wrapper/__init__.py +0 -0
  59. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/wrapper/aws/__init__.py +0 -0
  60. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay/wrapper/aws/s3.py +0 -0
  61. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay.egg-info/SOURCES.txt +0 -0
  62. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay.egg-info/dependency_links.txt +0 -0
  63. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay.egg-info/requires.txt +0 -0
  64. {quantplay-2.0.8 → quantplay-2.0.10}/quantplay.egg-info/top_level.txt +0 -0
  65. {quantplay-2.0.8 → quantplay-2.0.10}/setup.cfg +0 -0
  66. {quantplay-2.0.8 → quantplay-2.0.10}/tests/__init__.py +0 -0
  67. {quantplay-2.0.8 → quantplay-2.0.10}/tests/conftest.py +0 -0
  68. {quantplay-2.0.8 → quantplay-2.0.10}/tests/wrapper/__init__.py +0 -0
  69. {quantplay-2.0.8 → quantplay-2.0.10}/tests/wrapper/aws/__init__.py +0 -0
  70. {quantplay-2.0.8 → quantplay-2.0.10}/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.8
3
+ Version: 2.0.10
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -1,12 +1,14 @@
1
1
  import json
2
2
  from queue import Queue
3
3
  import traceback
4
- from typing import Dict
4
+ from typing import Callable, Dict, Literal
5
5
 
6
6
  import polars as pl
7
7
  from retrying import retry
8
8
  from json.decoder import JSONDecodeError
9
9
 
10
+ from quantplay.broker.finvasia_utils.fa_noren import FA_NorenApi
11
+ from quantplay.broker.ft_utils.ft_noren import FT_NorenApi
10
12
  from quantplay.broker.generics.broker import Broker
11
13
  from quantplay.exception.exceptions import (
12
14
  InvalidArgumentException,
@@ -47,7 +49,9 @@ class Noren(Broker):
47
49
  self.order_type_sl = "SL-LMT"
48
50
  self.trigger_pending_status = "TRIGGER_PENDING"
49
51
 
50
- def set_attributes(self, response):
52
+ self.api: FA_NorenApi | FT_NorenApi
53
+
54
+ def set_attributes(self, response: Dict[str, str | None]) -> None:
51
55
  self.email = response["email"]
52
56
  self.user_id = response["actid"]
53
57
  self.full_name = response["uname"]
@@ -72,9 +76,10 @@ class Noren(Broker):
72
76
  def get_symbol(self, symbol, exchange=None):
73
77
  if symbol not in self.quantplay_symbol_map:
74
78
  return symbol
79
+
75
80
  return self.quantplay_symbol_map[symbol]
76
81
 
77
- def get_transaction_type(self, transaction_type):
82
+ def get_transaction_type(self, transaction_type: Literal["BUY", "SELL"]):
78
83
  if transaction_type == "BUY":
79
84
  return "B"
80
85
  elif transaction_type == "SELL":
@@ -87,16 +92,19 @@ class Noren(Broker):
87
92
  def get_order_type(self, order_type):
88
93
  if order_type == OrderType.market:
89
94
  return "MKT"
95
+
90
96
  elif order_type == OrderType.sl:
91
97
  return "SL-LMT"
98
+
92
99
  elif order_type == OrderType.slm:
93
100
  return "SL-MKT"
101
+
94
102
  elif order_type == OrderType.limit:
95
103
  return "LMT"
96
104
 
97
105
  return order_type
98
106
 
99
- def get_product(self, product):
107
+ def get_product(self, product: str):
100
108
  if product == "NRML":
101
109
  return "M"
102
110
  elif product == "CNC":
@@ -106,9 +114,7 @@ class Noren(Broker):
106
114
  elif product in ["M", "C", "I"]:
107
115
  return product
108
116
 
109
- raise InvalidArgumentException(
110
- "Product {} not supported for trading".format(product)
111
- )
117
+ raise InvalidArgumentException(f"Product {product} not supported for trading")
112
118
 
113
119
  def event_handler_order_update(self, order):
114
120
  try:
@@ -117,14 +123,11 @@ class Noren(Broker):
117
123
  order["order_id"] = order["norenordno"]
118
124
  order["exchange_order_id"] = order["exchordid"]
119
125
  order["exchange"] = order["exch"]
120
-
121
- # TODO translate symbol
122
- # -EQ should be removed
123
- # F&O symbol translation
124
126
  order["tradingsymbol"] = order["tsym"]
125
127
 
126
128
  if order["exchange"] == "NSE":
127
129
  order["tradingsymbol"] = order["tradingsymbol"].replace("-EQ", "")
130
+
128
131
  elif order["exchange"] in ["NFO", "MCX"]:
129
132
  order["tradingsymbol"] = self.broker_symbol_map[order["tradingsymbol"]]
130
133
 
@@ -210,7 +213,7 @@ class Noren(Broker):
210
213
  "remarks": tag,
211
214
  }
212
215
  Constants.logger.info("[PLACING_ORDER] {}".format(json.dumps(data)))
213
- response = self.api.place_order( # type:ignore
216
+ response = self.api.place_order(
214
217
  buy_or_sell=noren_transaction_type,
215
218
  product_type=noren_product,
216
219
  exchange=exchange,
@@ -227,10 +230,12 @@ class Noren(Broker):
227
230
  Constants.logger.info(
228
231
  "[PLACE_ORDER_RESPONSE] {} input {}".format(response, json.dumps(data))
229
232
  )
230
- if "norenordno" in response:
233
+
234
+ if response is not None and "norenordno" in response:
231
235
  return response["norenordno"]
232
236
  else:
233
237
  raise Exception(response)
238
+
234
239
  except Exception as e:
235
240
  traceback.print_exc()
236
241
  exception_message = "Order placement failed with error [{}]".format(str(e))
@@ -248,12 +253,21 @@ class Noren(Broker):
248
253
  tradingsymbol = self.get_symbol(tradingsymbol)
249
254
 
250
255
  token = self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["token"]
251
- return float(self.api.get_quotes(exchange, str(token))["lp"]) # type:ignore
256
+ quote = self.api.get_quotes(exchange, str(token))
257
+
258
+ if quote is None:
259
+ raise BrokerException("Response from Broker for LTP is Invalid")
260
+
261
+ return float(quote["lp"])
252
262
 
253
263
  def live_data(self, exchange, tradingsymbol):
254
264
  tradingsymbol = self.get_symbol(tradingsymbol)
255
265
  token = self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["token"]
256
- data = self.api.get_quotes(exchange, str(token)) # type:ignore
266
+ data = self.api.get_quotes(exchange, str(token))
267
+
268
+ if data is None:
269
+ raise BrokerException("Response from Broker for Market Data is Invalid")
270
+
257
271
  return {
258
272
  "ltp": float(data["lp"]),
259
273
  "upper_circuit": float(data["uc"]),
@@ -261,15 +275,20 @@ class Noren(Broker):
261
275
  }
262
276
 
263
277
  def order_history(self, order_id):
264
- order_history = self.api.single_order_history(order_id) # type:ignore
278
+ order_history = self.api.single_order_history(order_id)
279
+
280
+ if order_history is None:
281
+ raise BrokerException("Response from Broker for Order History is Invalid")
282
+
265
283
  order_details = order_history[0]
266
284
 
267
- data = {}
268
- data["order_id"] = order_id
269
- data["order_type"] = order_details["prctyp"]
270
- data["exchange"] = order_details["exch"]
271
- data["quantity"] = order_details["qty"]
272
- data["tradingsymbol"] = order_details["tsym"]
285
+ data = {
286
+ "order_id": order_id,
287
+ "order_type": order_details["prctyp"],
288
+ "exchange": order_details["exch"],
289
+ "quantity": order_details["qty"],
290
+ "tradingsymbol": order_details["tsym"],
291
+ }
273
292
 
274
293
  return data
275
294
 
@@ -289,6 +308,7 @@ class Noren(Broker):
289
308
 
290
309
  if "order_type" not in order_to_modify:
291
310
  order_to_modify["order_type"] = existing_details["order_type"]
311
+
292
312
  if "quantity" not in order_to_modify:
293
313
  order_to_modify["quantity"] = existing_details["quantity"]
294
314
 
@@ -308,33 +328,39 @@ class Noren(Broker):
308
328
  order_to_modify["order_id"], response
309
329
  )
310
330
  )
331
+
332
+ if response is None:
333
+ raise Exception("Response is None")
334
+
311
335
  return response
336
+
312
337
  except Exception as e:
313
- exception_message = (
314
- "OrderModificationFailed for {} failed with exception {}".format(
315
- order_to_modify["order_id"], e
316
- )
317
- )
318
- Constants.logger.error("{}".format(exception_message))
338
+ exception_message = f"OrderModificationFailed for {order_to_modify['order_id']} failed with exception {e}"
339
+ Constants.logger.error(f"{exception_message}")
319
340
  raise e
320
341
 
321
342
  @timeit(MetricName="Finvasia:cancel_order")
322
343
  def cancel_order(self, order_id, variety=None):
323
- self.api.cancel_order(order_id) # type:ignore
344
+ self.api.cancel_order(order_id)
324
345
 
325
346
  def stream_order_data(self):
326
- self.api.start_websocket( # type:ignore
327
- order_update_callback=self.event_handler_order_update
328
- )
347
+ self.api.start_websocket(order_update_callback=self.event_handler_order_update)
329
348
 
330
349
  @timeit(MetricName="Finvasia:profile")
331
350
  def profile(self) -> UserBrokerProfileResponse:
351
+ if self.user_id is None:
352
+ raise Exception("User ID is unknown")
353
+
332
354
  response: UserBrokerProfileResponse = {
333
- "user_id": self.user_id, # type:ignore
334
- "full_name": self.full_name,
335
- "email": self.email,
355
+ "user_id": self.user_id,
336
356
  }
337
357
 
358
+ if self.full_name is not None:
359
+ response["full_name"] = self.full_name
360
+
361
+ if self.email is not None:
362
+ response["email"] = self.email
363
+
338
364
  return response
339
365
 
340
366
  @timeit(MetricName="Noren:holdings")
@@ -344,7 +370,7 @@ class Noren(Broker):
344
370
  stop_max_attempt_number=3,
345
371
  )
346
372
  def holdings(self):
347
- holdings = self.api.get_holdings() # type:ignore
373
+ holdings = self.api.get_holdings()
348
374
  if holdings is None or len(holdings) == 0:
349
375
  return pl.DataFrame(schema=self.holidings_schema)
350
376
 
@@ -387,27 +413,13 @@ class Noren(Broker):
387
413
  return holdings_df[list(self.holidings_schema.keys())].cast(self.holidings_schema)
388
414
 
389
415
  @timeit(MetricName="Finvasia:positions")
390
- @retry(
391
- wait_exponential_multiplier=3000,
392
- wait_exponential_max=10000,
393
- stop_max_attempt_number=3,
394
- retry_on_exception=retry_exception,
395
- )
396
416
  def positions(self, drop_cnc: bool = True) -> pl.DataFrame:
397
- positions_df = pl.DataFrame()
398
- try:
399
- positions = self.api.get_positions() # type:ignore
400
- if positions is None or len(positions) == 0:
401
- return pl.DataFrame(schema=self.positions_schema)
402
- except BrokerException:
403
- raise
404
- except JSONDecodeError:
405
- raise BrokerException("Broker failed to send positions")
406
- except Exception:
407
- raise
417
+ positions = self.invoke_noren_api(self.api.get_positions)
408
418
 
409
- positions_df = pl.DataFrame(positions)
419
+ if positions is None or len(positions) == 0:
420
+ return pl.DataFrame(schema=self.positions_schema)
410
421
 
422
+ positions_df = pl.DataFrame(positions)
411
423
  positions_df = positions_df.rename(
412
424
  {
413
425
  "tsym": "tradingsymbol",
@@ -488,16 +500,9 @@ class Noren(Broker):
488
500
 
489
501
  @timeit(MetricName="Finvasia:orders")
490
502
  def orders(self, tag: str | None = None, add_ltp: bool = True) -> pl.DataFrame:
491
- try:
492
- orders = self.api.get_order_book() # type:ignore
493
- if orders is None or len(orders) == 0:
494
- return pl.DataFrame(schema=self.orders_schema)
495
- except BrokerException:
496
- raise
497
- except JSONDecodeError:
498
- raise BrokerException("Broker failed to send orders")
499
- except Exception:
500
- raise
503
+ orders = self.invoke_noren_api(self.api.get_order_book)
504
+ if orders is None or len(orders) == 0:
505
+ return pl.DataFrame(schema=self.orders_schema)
501
506
 
502
507
  orders_df = pl.DataFrame(orders)
503
508
 
@@ -632,6 +637,27 @@ class Noren(Broker):
632
637
 
633
638
  return orders_df[list(self.orders_schema.keys())].cast(self.orders_schema)
634
639
 
640
+ @retry(
641
+ wait_exponential_multiplier=3000,
642
+ wait_exponential_max=10000,
643
+ stop_max_attempt_number=3,
644
+ retry_on_exception=retry_exception,
645
+ )
646
+ def invoke_noren_api(self, fn: Callable, *args, **kwargs) -> Dict:
647
+ try:
648
+ response = fn(*args, **kwargs)
649
+ if "stat" in response and "not_ok" == response["stat"].lower():
650
+ raise TokenException(response["emsg"])
651
+ return response
652
+
653
+ except JSONDecodeError:
654
+ raise BrokerException("Failed to data from broker")
655
+
656
+ except Exception:
657
+ traceback.print_exc()
658
+ # raise RetryableException("Failed to data from broker")
659
+ raise Exception("Failed to data from broker")
660
+
635
661
  @timeit(MetricName="Finvasia:positions")
636
662
  @retry(
637
663
  wait_exponential_multiplier=1000,
@@ -640,9 +666,7 @@ class Noren(Broker):
640
666
  retry_on_exception=retry_exception,
641
667
  )
642
668
  def margins(self) -> Dict:
643
- api_margins = self.api.get_limits() # type:ignore
644
- if "stat" in api_margins and "not_ok" == api_margins["stat"].lower():
645
- raise TokenException(api_margins["emsg"])
669
+ api_margins = self.invoke_noren_api(self.api.get_limits)
646
670
 
647
671
  try:
648
672
  collateral = 0
@@ -653,8 +677,10 @@ class Noren(Broker):
653
677
  api_margins["margin_used"] = 0
654
678
  else:
655
679
  api_margins["margin_used"] = api_margins["marginused"]
680
+
656
681
  if "payin" not in api_margins:
657
682
  api_margins["payin"] = 0
683
+
658
684
  margin_available = (
659
685
  float(api_margins["cash"])
660
686
  + float(collateral)
@@ -665,23 +691,28 @@ class Noren(Broker):
665
691
  margins = {}
666
692
  margins["margin_used"] = api_margins["margin_used"]
667
693
  margins["margin_available"] = margin_available
694
+
668
695
  try:
669
696
  margins["cash"] = float(api_margins["cash"])
670
697
  except Exception:
671
698
  margins["cash"] = 0
699
+
672
700
  margins["total_balance"] = float(api_margins["cash"]) + float(collateral)
673
701
 
674
702
  return margins
703
+
675
704
  except Exception as e:
676
705
  logger.error(f"[NOREN_MARGIN_ERROR] {e}")
677
706
  RetryableException("[NOREN] Failed to fetch account margin")
707
+
678
708
  return {}
679
709
 
680
710
  @timeit(MetricName="Finvasia:account_summary")
681
711
  def account_summary(self):
682
- margins: Dict = self.margins()
683
712
  pnl = 0
713
+ margins: Dict = self.margins()
684
714
  positions = self.positions()
715
+
685
716
  if len(positions) > 0:
686
717
  pnl = positions["pnl"].sum()
687
718
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantplay
3
- Version: 2.0.8
3
+ Version: 2.0.10
4
4
  Summary: This python package will be stored in AWS CodeArtifact
5
5
  Home-page:
6
6
  Author:
@@ -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.8",
24
+ version="2.0.10",
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
File without changes
File without changes