bw-essentials-core 0.1.17__py3-none-any.whl → 0.1.33__py3-none-any.whl

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.
@@ -80,7 +80,8 @@ class PortfolioCatalogue(ApiClient):
80
80
  "rebalance": "rebalance",
81
81
  "get_rebalance": "rebalance",
82
82
  "subscriptions_by_ids": "subscription-plan/list",
83
- "otp_subscriptions_by_ids": "one-time-plans/list"
83
+ "otp_subscriptions_by_ids": "one-time-plans/list",
84
+ "option_recommendations": "options/recommendations"
84
85
  }
85
86
 
86
87
  def create_rebalance(self, json: Dict[str, Any]) -> Optional[Dict[str, Any]]:
@@ -210,4 +211,62 @@ class PortfolioCatalogue(ApiClient):
210
211
  }
211
212
  response = self._post(url=url, endpoint=self.urls.get('otp_subscriptions_by_ids'), json=payload)
212
213
  logger.info(f"Successfully fetched otp subscriptions by ids data. {response =}")
213
- return response.get('data', [])
214
+ return response.get('data', [])
215
+
216
+ def get_options_recommendations(self,
217
+ strike_price_gt: float = None,
218
+ segment_in: str = None,
219
+ date_gte: str = None,
220
+ limit: int = None,
221
+ broker: str = None,
222
+ underlying_symbol: str = None,
223
+ status: str = None,
224
+ isin_code: str = None):
225
+ """
226
+ Fetch options trading recommendations based on filtering parameters.
227
+
228
+ This method sends a GET request to the Portfolio-Catalogue service's
229
+ `/options/recommendations` endpoint with optional query parameters
230
+ for filtering by strike price, trading segment, date, broker, and limit.
231
+
232
+ Endpoint:
233
+ GET /options/recommendations
234
+
235
+ Args:
236
+ strike_price_gt (float, optional): Fetch recommendations where strike price > given value.
237
+ segment_in (str, optional): Trading segment to filter by (e.g., 'fno').
238
+ date_gte (str, optional): Minimum date (ISO format, e.g., '2025-10-28').
239
+ limit (int, optional): Limit the number of records returned.
240
+ broker (str, optional): Broker name to filter recommendations (e.g., 'zerodha').
241
+ underlying_symbol (str, optional): Trading symbol to filter recommendations (e.g., 'RELIANCE').
242
+ status (str, optional): Status of the recommendation. (e.g., 'active', 'inactive')
243
+ isin_code (str, optional): ISIN code to filter recommendations).
244
+
245
+ Returns:
246
+ list[dict]: A list of recommendation records if available, otherwise an empty list.
247
+ """
248
+ logger.info(
249
+ "In - get_options_recommendations strike_price_gt=%s, segment_in=%s, date_gte=%s, limit=%s, "
250
+ "broker=%s, underlying_symbol=%s, status=%s, isin_code=%s",
251
+ strike_price_gt, segment_in, date_gte, limit, broker, underlying_symbol, status, isin_code
252
+ )
253
+
254
+ params = {k: v for k, v in {
255
+ "strike_price__gt": strike_price_gt,
256
+ "segment__in": segment_in,
257
+ "date__gte": date_gte,
258
+ "limit": limit,
259
+ "broker": broker,
260
+ "underlying_symbol": underlying_symbol,
261
+ "status": status,
262
+ "isin_code": isin_code
263
+ }.items() if v is not None}
264
+
265
+ response = self._get(
266
+ url=self.base_url,
267
+ endpoint=self.urls.get('option_recommendations'),
268
+ params=params
269
+ )
270
+
271
+ logger.info(f"Successfully fetched options recommendations. {response =}")
272
+ return response.get("data", [])
@@ -90,4 +90,4 @@ class PortfolioContent(ApiClient):
90
90
  json=payload
91
91
  )
92
92
  logger.info(f"Successfully fetched portfolio details by ids. {response=}")
93
- return response.get('data', [])
93
+ return response.get('data', [])
@@ -53,6 +53,7 @@ class TradePlacement(ApiClient):
53
53
 
54
54
  EXECUTABLE = 'executable'
55
55
  READY_ONLY = 'read_only'
56
+ PUBLISHER = 'publisher'
56
57
  BROKER_CONFIG_MAPPER = {
57
58
  DEALER: READY_ONLY,
58
59
  USER: EXECUTABLE
@@ -78,7 +79,8 @@ class TradePlacement(ApiClient):
78
79
  self.name = Services.TRADE_PLACEMENT.value
79
80
  self.urls = {
80
81
  "order": "orders/order",
81
- "update_orders": "orders/order/update"
82
+ "update_orders": "orders/order/update",
83
+ "update_order_status": "orders/order/status/update"
82
84
  }
83
85
  if self.user_info.get("broker_name") in [self.PAPER_TRADE]:
84
86
  self._authenticate()
@@ -146,8 +148,8 @@ class TradePlacement(ApiClient):
146
148
  }
147
149
  }
148
150
 
149
- def _place_order(self, trading_symbol, qty, side, order_tag, meta_data, product, order_type, proxy=None,
150
- asm_consent=None, asm_reason=None):
151
+ def _place_order(self, trading_symbol, qty, side, order_tag, price, meta_data, product, order_type, proxy=None,
152
+ asm_consent=None, asm_reason=None, publisher=False):
151
153
  """
152
154
  Places an order with the provided metadata and order details.
153
155
 
@@ -156,6 +158,7 @@ class TradePlacement(ApiClient):
156
158
  qty (int): Quantity to trade.
157
159
  side (str): Order side ('buy' or 'sell').
158
160
  order_tag (str): Identifier tag for the order.
161
+ price (float): Price of the order.
159
162
  meta_data (dict): Metadata about the order context.
160
163
  product (str): Type of product ('cnc' or 'mtf').
161
164
  order_type (str): Type of order ('market', 'limit').
@@ -171,15 +174,16 @@ class TradePlacement(ApiClient):
171
174
  broker_config = self.EXECUTABLE
172
175
  if proxy == self.READY_ONLY:
173
176
  broker_config = self.READY_ONLY
174
-
175
- payload = json.dumps({
177
+ if publisher:
178
+ broker_config = self.PUBLISHER
179
+ payload = {
176
180
  "user_id": self.user_info.get("user_id"),
177
181
  "entity_id": self.user_info.get("entity_id"),
178
182
  "symbol": trading_symbol,
179
183
  "quantity": abs(qty),
180
184
  "broker": self.user_info.get("broker_name"),
181
185
  "broker_config": broker_config,
182
- "price": 0,
186
+ "price": price,
183
187
  "product_type": product,
184
188
  "order_type": order_type,
185
189
  "side": side,
@@ -187,10 +191,10 @@ class TradePlacement(ApiClient):
187
191
  "metadata": meta_data,
188
192
  "asm_consent": asm_consent,
189
193
  "asm_reason": asm_reason
190
- })
194
+ }
191
195
  placed_orders_data = self._post(url=self.base_url,
192
196
  endpoint=self.urls.get("order"),
193
- data=payload)
197
+ json=payload)
194
198
  return placed_orders_data.get("data")
195
199
 
196
200
  def _market_buy_cnc(self, trading_symbol, qty, generate_order_tag,
@@ -494,6 +498,255 @@ class TradePlacement(ApiClient):
494
498
  response = self._put(
495
499
  url=self.base_url,
496
500
  endpoint=f'{self.urls.get("update_orders")}/{tag}',
497
- data=json.dumps(instruction)
501
+ json=instruction
498
502
  )
499
503
  return response
504
+
505
+ def update_order_status(self, broker_config, product_type, tag):
506
+ """
507
+ Update the status of an existing order.
508
+
509
+ This method sends a PUT request to the Trade Placement Service to update
510
+ the status of a specific order identified by its tag. The update includes
511
+ user, broker, and product configuration details.
512
+
513
+ Endpoint:
514
+ PUT /orders/order/status/update
515
+
516
+ Args:
517
+ broker_config (str): Broker configuration (e.g., 'publisher', 'executable').
518
+ product_type (str): Type of product (e.g., 'fno', 'cnc', 'mtf').
519
+ tag (str): Unique tag identifying the order.
520
+
521
+ Returns:
522
+ dict: API response from the Trade Placement Service.
523
+
524
+ Raises:
525
+ HTTPError: If the PUT request fails.
526
+ """
527
+ logger.info(f"Updating order status for {tag =}, {self.broker =}, {product_type =}")
528
+
529
+ if not self.user_id or not self.entity_id:
530
+ raise ValueError("user_id and entity_id must be set to update order status.")
531
+
532
+ payload = {
533
+ "user_id": self.user_id,
534
+ "entity_id": self.entity_id,
535
+ "broker": self.broker,
536
+ "broker_config": broker_config,
537
+ "product_type": product_type,
538
+ "tag": tag
539
+ }
540
+
541
+ response = self._put(
542
+ url=self.base_url,
543
+ endpoint=self.urls.get("update_order_status"),
544
+ json=payload
545
+ )
546
+
547
+ logger.info(f"Order status update response: {response}")
548
+ return response
549
+
550
+ def _get_fno_metadata(self):
551
+ """
552
+ Build metadata dictionary for FNO order.
553
+ Returns:
554
+ dict: Metadata for FNO order.
555
+ """
556
+ user_info = self.user_info or {}
557
+ return {
558
+ "user_id": user_info.get("user_id"),
559
+ "date": str(datetime.now().date()),
560
+ "user_info": {
561
+ "current_broker": user_info.get("current_broker"),
562
+ "broker_name": user_info.get("broker_name"),
563
+ "entity_id": user_info.get("entity_id")
564
+ }
565
+ }
566
+
567
+ def _market_buy_fno(self, trading_symbol, qty, generate_order_tag, price, publisher, proxy=None):
568
+ """
569
+ Places a market buy order for FNO.
570
+
571
+ Args:
572
+ trading_symbol (str): Symbol to buy.
573
+ qty (int): Quantity to buy.
574
+ generate_order_tag (str): Order tag.
575
+ proxy (str, optional): Proxy config.
576
+
577
+ Returns:
578
+ dict: Order response data.
579
+ """
580
+ logger.info(f"Placing FNO market buy order for {trading_symbol=}, {qty=}")
581
+ meta_data = self._get_fno_metadata()
582
+ return self._place_order(trading_symbol, qty, self.BUY, generate_order_tag, price,
583
+ meta_data, "fno", self.MARKET, proxy=proxy, publisher=publisher)
584
+
585
+ def _market_sell_fno(self, trading_symbol, qty, generate_order_tag, price, publisher, proxy=None):
586
+ """
587
+ Places a market sell order for FNO.
588
+
589
+ Args:
590
+ trading_symbol (str): Symbol to sell.
591
+ qty (int): Quantity to sell.
592
+ generate_order_tag (str): Order tag.
593
+ price (float): Price of the order.
594
+ publisher (bool): Whether to use publisher config. Defaults to False.
595
+ proxy (str, optional): Proxy config.
596
+
597
+ Returns:
598
+ dict: Order response data.
599
+ """
600
+ logger.info(f"Placing FNO market sell order for {trading_symbol=}, {qty=}")
601
+ meta_data = self._get_fno_metadata()
602
+ return self._place_order(trading_symbol, qty, self.SELL, generate_order_tag, price,
603
+ meta_data, "fno", self.MARKET, proxy=proxy, publisher=publisher)
604
+
605
+ def execute_market_buy_orders_fno(self, instructions, publisher):
606
+ """
607
+ Execute FNO Market buy orders in bulk.
608
+
609
+ Args:
610
+ instructions (list): List of buy order dicts (symbol, qty, tag, instruction_id).
611
+ publisher (bool): Whether to use publisher config. Defaults to False.
612
+
613
+ Returns:
614
+ None
615
+ """
616
+ logger.info(f"In execute_market_buy_orders_fno - {instructions =}")
617
+ for order in instructions:
618
+ data = self._market_buy_fno(
619
+ trading_symbol=order.get("symbol"),
620
+ qty=order.get("qty"),
621
+ generate_order_tag=order.get("tag"),
622
+ price=order.get("price"),
623
+ publisher=publisher
624
+ )
625
+ logger.info(f"execute_market_buy_orders_fno {data = }")
626
+
627
+ def execute_market_sell_orders_fno(self, instructions, publisher):
628
+ """
629
+ Execute FNO Market sell orders in bulk.
630
+
631
+ Args:
632
+ instructions (list): List of sell order dicts (symbol, qty, tag, instruction_id).
633
+ publisher (bool): Whether to use publisher config. Defaults to False.
634
+
635
+ Returns:
636
+ None
637
+ """
638
+ logger.info(f"In execute_market_sell_orders_fno - {instructions =}, {publisher =}")
639
+ for order in instructions:
640
+ data = self._market_sell_fno(
641
+ trading_symbol=order.get("symbol"),
642
+ qty=order.get("qty"),
643
+ generate_order_tag=order.get("tag"),
644
+ price=order.get("price"),
645
+ publisher=publisher
646
+ )
647
+ logger.info(f"execute_market_sell_orders_fno {data = }")
648
+
649
+ def _limit_buy_fno(self, trading_symbol, qty, generate_order_tag, price, publisher, proxy=None):
650
+ """
651
+ Places a limit buy order for FNO.
652
+
653
+ Args:
654
+ trading_symbol (str): Symbol to buy.
655
+ qty (int): Quantity to buy.
656
+ generate_order_tag (str): Order tag.
657
+ price (float): Limit price.
658
+ publisher (bool): Whether to use publisher config.
659
+ proxy (str, optional): Proxy config.
660
+
661
+ Returns:
662
+ dict: Order response data.
663
+ """
664
+ logger.info(f"Placing FNO limit buy order for {trading_symbol=}, {qty=}, {price=}")
665
+ meta_data = self._get_fno_metadata()
666
+ return self._place_order(
667
+ trading_symbol,
668
+ qty,
669
+ self.BUY,
670
+ generate_order_tag,
671
+ price,
672
+ meta_data,
673
+ "fno",
674
+ self.LIMIT,
675
+ proxy=proxy,
676
+ publisher=publisher
677
+ )
678
+
679
+ def _limit_sell_fno(self, trading_symbol, qty, generate_order_tag, price, publisher, proxy=None):
680
+ """
681
+ Places a limit sell order for FNO.
682
+
683
+ Args:
684
+ trading_symbol (str): Symbol to sell.
685
+ qty (int): Quantity to sell.
686
+ generate_order_tag (str): Order tag.
687
+ price (float): Limit price.
688
+ publisher (bool): Whether to use publisher config.
689
+ proxy (str, optional): Proxy config.
690
+
691
+ Returns:
692
+ dict: Order response data.
693
+ """
694
+ logger.info(f"Placing FNO limit sell order for {trading_symbol=}, {qty=}, {price=}")
695
+ meta_data = self._get_fno_metadata()
696
+ return self._place_order(
697
+ trading_symbol,
698
+ qty,
699
+ self.SELL,
700
+ generate_order_tag,
701
+ price,
702
+ meta_data,
703
+ "fno",
704
+ self.LIMIT,
705
+ proxy=proxy,
706
+ publisher=publisher
707
+ )
708
+
709
+ def execute_limit_buy_orders_fno(self, instructions, publisher):
710
+ """
711
+ Execute FNO limit buy orders in bulk.
712
+
713
+ Args:
714
+ instructions (list): List of buy order dicts (symbol, qty, tag, price).
715
+ publisher (bool): Whether to use publisher config.
716
+
717
+ Returns:
718
+ None
719
+ """
720
+ logger.info(f"In execute_limit_buy_orders_fno - {instructions =}")
721
+ for order in instructions:
722
+ data = self._limit_buy_fno(
723
+ trading_symbol=order.get("symbol"),
724
+ qty=order.get("qty"),
725
+ generate_order_tag=order.get("tag"),
726
+ price=order.get("price"),
727
+ publisher=publisher
728
+ )
729
+ logger.info(f"execute_limit_buy_orders_fno {data = }")
730
+
731
+ def execute_limit_sell_orders_fno(self, instructions, publisher):
732
+ """
733
+ Execute FNO limit sell orders in bulk.
734
+
735
+ Args:
736
+ instructions (list): List of sell order dicts (symbol, qty, tag, price).
737
+ publisher (bool): Whether to use publisher config.
738
+
739
+ Returns:
740
+ None
741
+ """
742
+ logger.info(f"In execute_limit_sell_orders_fno - {instructions =}, {publisher =}")
743
+ for order in instructions:
744
+ data = self._limit_sell_fno(
745
+ trading_symbol=order.get("symbol"),
746
+ qty=order.get("qty"),
747
+ generate_order_tag=order.get("tag"),
748
+ price=order.get("price"),
749
+ publisher=publisher
750
+ )
751
+ logger.info(f"execute_limit_sell_orders_fno {data = }")
752
+