bw-essentials-core 0.1.5__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.
- bw_essentials/constants/services.py +12 -0
- bw_essentials/email_client/email_client.py +31 -18
- bw_essentials/services/api_client.py +49 -0
- bw_essentials/services/broker.py +30 -4
- bw_essentials/services/compliance.py +78 -0
- bw_essentials/services/job_scheduler.py +62 -0
- bw_essentials/services/market_pricer.py +71 -17
- bw_essentials/services/master_data.py +49 -0
- bw_essentials/services/model_portfolio_reporting.py +30 -1
- bw_essentials/services/notification.py +19 -15
- bw_essentials/services/payment.py +84 -0
- bw_essentials/services/portfolio_catalogue.py +169 -1
- bw_essentials/services/portfolio_content.py +24 -0
- bw_essentials/services/trade_placement.py +262 -9
- bw_essentials/services/user_app.py +23 -4
- bw_essentials/services/user_portfolio.py +562 -1
- bw_essentials/services/user_portfolio_reporting.py +37 -1
- {bw_essentials_core-0.1.5.dist-info → bw_essentials_core-0.1.33.dist-info}/METADATA +1 -1
- bw_essentials_core-0.1.33.dist-info/RECORD +32 -0
- bw_essentials_core-0.1.5.dist-info/RECORD +0 -29
- {bw_essentials_core-0.1.5.dist-info → bw_essentials_core-0.1.33.dist-info}/WHEEL +0 -0
- {bw_essentials_core-0.1.5.dist-info → bw_essentials_core-0.1.33.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
|
@@ -34,18 +34,22 @@ class PrometheusUserApp(ApiClient):
|
|
|
34
34
|
urls (Dict[str, str]): A dictionary of endpoint path templates
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
def __init__(self, service_user: str):
|
|
37
|
+
def __init__(self, service_user: str, tenant_id: str = None):
|
|
38
38
|
"""
|
|
39
|
-
Initialize the PrometheusUserApp client with the given service user.
|
|
39
|
+
Initialize the PrometheusUserApp client with the given service user and tenant id.
|
|
40
40
|
|
|
41
41
|
Args:
|
|
42
42
|
service_user (str): Username or service identifier for authentication.
|
|
43
|
+
tenant_id (str, optional): Tenant identifier for multi-tenant usage.
|
|
44
|
+
Defaults to None if not applicable.
|
|
43
45
|
"""
|
|
44
46
|
super().__init__(user=service_user)
|
|
45
47
|
self.base_url = self.get_base_url(Services.PROMETHEUS_USER_APP.value)
|
|
48
|
+
self.api_key = self.get_api_key(Services.PROMETHEUS_USER_APP.value)
|
|
46
49
|
self.name = Services.PROMETHEUS_USER_APP.value
|
|
50
|
+
self.tenant_id = tenant_id
|
|
47
51
|
self.urls = {
|
|
48
|
-
"user_details": "user/details",
|
|
52
|
+
"user_details": "user/details/",
|
|
49
53
|
"profile": "user/profile",
|
|
50
54
|
"get_profile": "user/profile/",
|
|
51
55
|
"login": "user/login",
|
|
@@ -60,6 +64,20 @@ class PrometheusUserApp(ApiClient):
|
|
|
60
64
|
"sso": "user/{}/sso"
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
|
|
68
|
+
def _headers(self):
|
|
69
|
+
"""
|
|
70
|
+
Prepares headers for API calls.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
dict: Headers
|
|
74
|
+
"""
|
|
75
|
+
return {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
**({'X-Tenant-ID': self.tenant_id} if self.tenant_id else {}),
|
|
78
|
+
'x-api-key': self.api_key
|
|
79
|
+
}
|
|
80
|
+
|
|
63
81
|
def get_user_details(self, user_id: str) -> Optional[Dict[str, Any]]:
|
|
64
82
|
"""
|
|
65
83
|
Fetch detailed user information by user ID.
|
|
@@ -71,10 +89,11 @@ class PrometheusUserApp(ApiClient):
|
|
|
71
89
|
Optional[Dict[str, Any]]: Parsed user data if found, else None.
|
|
72
90
|
"""
|
|
73
91
|
logger.info("Fetching user details for user_id=%s", user_id)
|
|
92
|
+
self.headers = self._headers()
|
|
74
93
|
response = self._get(
|
|
75
94
|
endpoint=self.urls["user_details"],
|
|
76
95
|
url=self.base_url,
|
|
77
|
-
params={'user_id': user_id}
|
|
96
|
+
params={'user_id': user_id},
|
|
78
97
|
)
|
|
79
98
|
return response.get('data')
|
|
80
99
|
|