bw-essentials-core 0.0.1__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.
Potentially problematic release.
This version of bw-essentials-core might be problematic. Click here for more details.
- bw_essentials/__init__.py +0 -0
- bw_essentials/constants/__init__.py +0 -0
- bw_essentials/constants/services.py +19 -0
- bw_essentials/data_loch/__init__.py +0 -0
- bw_essentials/data_loch/data_loch.py +282 -0
- bw_essentials/email_client/__init__.py +0 -0
- bw_essentials/email_client/email_client.py +243 -0
- bw_essentials/notifications/__init__.py +0 -0
- bw_essentials/notifications/teams_notification_schemas.py +173 -0
- bw_essentials/notifications/teams_notifications.py +189 -0
- bw_essentials/s3_utils/__init__.py +0 -0
- bw_essentials/s3_utils/s3_utils.py +361 -0
- bw_essentials/services/__init__.py +0 -0
- bw_essentials/services/api_client.py +229 -0
- bw_essentials/services/broker.py +250 -0
- bw_essentials/services/market_pricer.py +233 -0
- bw_essentials/services/master_data.py +257 -0
- bw_essentials/services/model_portfolio_reporting.py +81 -0
- bw_essentials/services/trade_placement.py +499 -0
- bw_essentials/services/user_portfolio.py +406 -0
- bw_essentials/services/user_portfolio_reporting.py +153 -0
- bw_essentials_core-0.0.1.dist-info/METADATA +213 -0
- bw_essentials_core-0.0.1.dist-info/RECORD +25 -0
- bw_essentials_core-0.0.1.dist-info/WHEEL +5 -0
- bw_essentials_core-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for interacting with the Trade Placement Service.
|
|
3
|
+
|
|
4
|
+
This module defines a `TradePlacement` class responsible for managing and executing
|
|
5
|
+
equity trades such as CNC (Cash and Carry) and MTF (Margin Trading Facility) across
|
|
6
|
+
multiple brokers. It handles order placement, authentication, metadata construction,
|
|
7
|
+
and communication with external broker APIs. It supports placing buy/sell orders,
|
|
8
|
+
fetching order details, and executing bulk instructions.
|
|
9
|
+
|
|
10
|
+
Classes:
|
|
11
|
+
TradePlacement: Extends `ApiClient` to interact with Trade Placement Service.
|
|
12
|
+
|
|
13
|
+
Dependencies:
|
|
14
|
+
- bw_essentials.constants.services.Services
|
|
15
|
+
- bw_essentials.services.api_client.ApiClient
|
|
16
|
+
- bw_essentials.services.broker.Broker
|
|
17
|
+
"""
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
|
|
22
|
+
from bw_essentials.constants.services import Services
|
|
23
|
+
from bw_essentials.services.api_client import ApiClient
|
|
24
|
+
from bw_essentials.services.broker import Broker
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TradePlacement(ApiClient):
|
|
30
|
+
"""
|
|
31
|
+
Client to interact with the Trade Placement Service.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
base_url (str): Base URL of the Trade Placement Service.
|
|
35
|
+
broker_service_base_url (str): Base URL of the Broker Service.
|
|
36
|
+
user_info (dict): User context including broker, user_id, and entity_id.
|
|
37
|
+
name (str): Name of the service.
|
|
38
|
+
"""
|
|
39
|
+
SELL = 'sell'
|
|
40
|
+
BUY = 'buy'
|
|
41
|
+
MARKET = 'market'
|
|
42
|
+
LIMIT = 'limit'
|
|
43
|
+
MTF = 'mtf'
|
|
44
|
+
CNC = 'cnc'
|
|
45
|
+
LKP = "lkp"
|
|
46
|
+
AXIS = "axis"
|
|
47
|
+
ZERODHA = "zerodha"
|
|
48
|
+
PAPER_TRADE = "paper_trade"
|
|
49
|
+
PL = "pl"
|
|
50
|
+
EMKAY = 'emkay'
|
|
51
|
+
DEALER = "dealer"
|
|
52
|
+
USER = "user"
|
|
53
|
+
|
|
54
|
+
EXECUTABLE = 'executable'
|
|
55
|
+
READY_ONLY = 'read_only'
|
|
56
|
+
BROKER_CONFIG_MAPPER = {
|
|
57
|
+
DEALER: READY_ONLY,
|
|
58
|
+
USER: EXECUTABLE
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def __init__(self,
|
|
62
|
+
service_user: str,
|
|
63
|
+
user_info: dict):
|
|
64
|
+
"""
|
|
65
|
+
Initialize TradePlacement client.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
service_user (str): Username for service access.
|
|
69
|
+
user_info (dict): Metadata including broker and user identity.
|
|
70
|
+
"""
|
|
71
|
+
logger.info(f"Initializing TradePlacement client for user: {service_user}")
|
|
72
|
+
super().__init__(user=service_user)
|
|
73
|
+
self.base_url = self.get_base_url(Services.TRADE_PLACEMENT.value)
|
|
74
|
+
self.user_info = user_info
|
|
75
|
+
self.broker = self.user_info.get('current_broker')
|
|
76
|
+
self.user_id = self.user_info.get('user_id')
|
|
77
|
+
self.entity_id = self.user_info.get("entity_id")
|
|
78
|
+
self.name = Services.TRADE_PLACEMENT.value
|
|
79
|
+
self.urls = {
|
|
80
|
+
"order": "orders/order",
|
|
81
|
+
"update_orders": "orders/order/update"
|
|
82
|
+
}
|
|
83
|
+
if self.user_info.get("broker_name") in [self.PAPER_TRADE]:
|
|
84
|
+
self._authenticate()
|
|
85
|
+
|
|
86
|
+
def _authenticate(self):
|
|
87
|
+
"""
|
|
88
|
+
Authenticates with the broker service if using paper trade broker.
|
|
89
|
+
"""
|
|
90
|
+
broker = Broker(service_user=self.user)
|
|
91
|
+
broker.authenticate(broker_name=self.broker,
|
|
92
|
+
user_id=self.user_id,
|
|
93
|
+
entity_id=self.user_id)
|
|
94
|
+
|
|
95
|
+
def _get_cnc_metadata(self, user_portfolio_id, instruction_id, rebalance, user_portfolio_rebalance_id):
|
|
96
|
+
"""
|
|
97
|
+
Build metadata dictionary for CNC (Cash and Carry) order.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
user_portfolio_id (str): Portfolio ID of the user.
|
|
101
|
+
instruction_id (str): Unique ID for the instruction.
|
|
102
|
+
rebalance (bool): Indicates whether rebalance is happening.
|
|
103
|
+
user_portfolio_rebalance_id (str): Unique ID for rebalance.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
dict: Metadata for CNC order.
|
|
107
|
+
"""
|
|
108
|
+
user_info = self.user_info or {}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"user_portfolio_id": user_portfolio_id,
|
|
112
|
+
"instruction_id": instruction_id,
|
|
113
|
+
"user_id": user_info.get("user_id"),
|
|
114
|
+
"date": str(datetime.now().date()),
|
|
115
|
+
"rebalance": rebalance,
|
|
116
|
+
"user_portfolio_rebalance_id": user_portfolio_rebalance_id,
|
|
117
|
+
"user_info": {
|
|
118
|
+
"current_broker": user_info.get("current_broker"),
|
|
119
|
+
"broker_name": user_info.get("broker_name"),
|
|
120
|
+
"entity_id": user_info.get("entity_id")
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
def _get_mtf_metadata(self, basket_id, instruction_id):
|
|
125
|
+
"""
|
|
126
|
+
Build metadata dictionary for MTF (Margin Trading Facility) order.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
basket_id (int): Basket identifier.
|
|
130
|
+
instruction_id (int): Instruction identifier.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
dict: Metadata for MTF order.
|
|
134
|
+
"""
|
|
135
|
+
user_info = self.user_info or {} # Use an empty dictionary as a fallback
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"user_id": user_info.get("user_id"),
|
|
139
|
+
"date": str(datetime.now().date()),
|
|
140
|
+
"basket_id": basket_id,
|
|
141
|
+
"instruction_id": instruction_id,
|
|
142
|
+
"user_info": {
|
|
143
|
+
"current_broker": user_info.get("current_broker"),
|
|
144
|
+
"broker_name": user_info.get("broker_name"),
|
|
145
|
+
"entity_id": user_info.get("entity_id")
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
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
|
+
"""
|
|
152
|
+
Places an order with the provided metadata and order details.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
trading_symbol (str): Symbol to trade.
|
|
156
|
+
qty (int): Quantity to trade.
|
|
157
|
+
side (str): Order side ('buy' or 'sell').
|
|
158
|
+
order_tag (str): Identifier tag for the order.
|
|
159
|
+
meta_data (dict): Metadata about the order context.
|
|
160
|
+
product (str): Type of product ('cnc' or 'mtf').
|
|
161
|
+
order_type (str): Type of order ('market', 'limit').
|
|
162
|
+
proxy (str, optional): Indicates if proxy config is used.
|
|
163
|
+
asm_consent (str, optional): Consent for ASM if applicable.
|
|
164
|
+
asm_reason (str, optional): Reason for ASM if applicable.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
dict: Response containing placed order details.
|
|
168
|
+
"""
|
|
169
|
+
logger.info(f"In - _place_order {trading_symbol =}, {qty =}, {side =}, {order_tag =}"
|
|
170
|
+
f"{proxy =}")
|
|
171
|
+
broker_config = self.EXECUTABLE
|
|
172
|
+
if proxy == self.READY_ONLY:
|
|
173
|
+
broker_config = self.READY_ONLY
|
|
174
|
+
|
|
175
|
+
payload = json.dumps({
|
|
176
|
+
"user_id": self.user_info.get("user_id"),
|
|
177
|
+
"entity_id": self.user_info.get("entity_id"),
|
|
178
|
+
"symbol": trading_symbol,
|
|
179
|
+
"quantity": abs(qty),
|
|
180
|
+
"broker": self.user_info.get("broker_name"),
|
|
181
|
+
"broker_config": broker_config,
|
|
182
|
+
"price": 0,
|
|
183
|
+
"product_type": product,
|
|
184
|
+
"order_type": order_type,
|
|
185
|
+
"side": side,
|
|
186
|
+
"tag": order_tag,
|
|
187
|
+
"metadata": meta_data,
|
|
188
|
+
"asm_consent": asm_consent,
|
|
189
|
+
"asm_reason": asm_reason
|
|
190
|
+
})
|
|
191
|
+
placed_orders_data = self._post(url=self.base_url,
|
|
192
|
+
endpoint=self.urls.get("order"),
|
|
193
|
+
data=payload)
|
|
194
|
+
return placed_orders_data.get("data")
|
|
195
|
+
|
|
196
|
+
def _market_buy_cnc(self, trading_symbol, qty, generate_order_tag,
|
|
197
|
+
user_portfolio_id, instruction_id, rebalance,
|
|
198
|
+
user_portfolio_rebalance_id, proxy=None,
|
|
199
|
+
asm_consent=None, asm_reason=None):
|
|
200
|
+
"""
|
|
201
|
+
Places a market buy order using CNC.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
trading_symbol (str): Symbol to buy.
|
|
205
|
+
qty (int): Quantity to buy.
|
|
206
|
+
generate_order_tag (str): Order tag.
|
|
207
|
+
user_portfolio_id (str): Portfolio ID.
|
|
208
|
+
instruction_id (str): Instruction ID.
|
|
209
|
+
rebalance (bool): Rebalance flag.
|
|
210
|
+
user_portfolio_rebalance_id (str): Rebalance ID.
|
|
211
|
+
proxy (str, optional): Proxy config.
|
|
212
|
+
asm_consent (str, optional): ASM consent.
|
|
213
|
+
asm_reason (str, optional): ASM reason.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
dict: Order response data.
|
|
217
|
+
"""
|
|
218
|
+
logger.info(f"Placing CNC market buy order for {trading_symbol=}, {qty=}")
|
|
219
|
+
meta_data = self._get_cnc_metadata(user_portfolio_id, instruction_id, rebalance, user_portfolio_rebalance_id)
|
|
220
|
+
return self._place_order(trading_symbol, qty, self.BUY, generate_order_tag,
|
|
221
|
+
meta_data, self.CNC, self.MARKET,
|
|
222
|
+
proxy=proxy, asm_consent=asm_consent, asm_reason=asm_reason)
|
|
223
|
+
|
|
224
|
+
def _market_sell_cnc(self, trading_symbol, qty, generate_order_tag,
|
|
225
|
+
user_portfolio_id, instruction_id, rebalance,
|
|
226
|
+
user_portfolio_rebalance_id, proxy=None,
|
|
227
|
+
asm_consent=None, asm_reason=None):
|
|
228
|
+
"""
|
|
229
|
+
Places a market sell order using CNC.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
trading_symbol (str): Symbol to sell.
|
|
233
|
+
qty (int): Quantity to sell.
|
|
234
|
+
generate_order_tag (str): Order tag.
|
|
235
|
+
user_portfolio_id (str): Portfolio ID.
|
|
236
|
+
instruction_id (str): Instruction ID.
|
|
237
|
+
rebalance (bool): Rebalance flag.
|
|
238
|
+
user_portfolio_rebalance_id (str): Rebalance ID.
|
|
239
|
+
proxy (str, optional): Proxy config.
|
|
240
|
+
asm_consent (str, optional): ASM consent.
|
|
241
|
+
asm_reason (str, optional): ASM reason.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
dict: Order response data.
|
|
245
|
+
"""
|
|
246
|
+
logger.info(f"Placing CNC market sell order for {trading_symbol=}, {qty=}")
|
|
247
|
+
meta_data = self._get_cnc_metadata(user_portfolio_id, instruction_id, rebalance, user_portfolio_rebalance_id)
|
|
248
|
+
return self._place_order(trading_symbol, qty, self.SELL, generate_order_tag,
|
|
249
|
+
meta_data, self.CNC, self.MARKET,
|
|
250
|
+
proxy=proxy, asm_consent=asm_consent, asm_reason=asm_reason)
|
|
251
|
+
|
|
252
|
+
def _market_buy_mtf(self, trading_symbol, qty, generate_order_tag, basket_id, instruction_id, proxy=None):
|
|
253
|
+
"""
|
|
254
|
+
Places a market buy order using MTF.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
trading_symbol (str): Symbol to buy.
|
|
258
|
+
qty (int): Quantity to buy.
|
|
259
|
+
generate_order_tag (str): Order tag.
|
|
260
|
+
basket_id (int): Basket ID.
|
|
261
|
+
instruction_id (int): Instruction ID.
|
|
262
|
+
proxy (str, optional): Proxy config.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
dict: Order response data.
|
|
266
|
+
"""
|
|
267
|
+
logger.info(f"Placing MTF market buy order for {trading_symbol=}, {qty=}")
|
|
268
|
+
meta_data = self._get_mtf_metadata(basket_id, instruction_id)
|
|
269
|
+
return self._place_order(trading_symbol, qty, self.BUY, generate_order_tag,
|
|
270
|
+
meta_data, self.MTF, self.MARKET, proxy=proxy)
|
|
271
|
+
|
|
272
|
+
def _market_sell_mtf(self, trading_symbol, qty, generate_order_tag, basket_id, instruction_id, proxy=None):
|
|
273
|
+
"""
|
|
274
|
+
Places a market sell order using MTF.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
trading_symbol (str): Symbol to sell.
|
|
278
|
+
qty (int): Quantity to sell.
|
|
279
|
+
generate_order_tag (str): Order tag.
|
|
280
|
+
basket_id (int): Basket ID.
|
|
281
|
+
instruction_id (int): Instruction ID.
|
|
282
|
+
proxy (str, optional): Proxy config.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
dict: Order response data.
|
|
286
|
+
"""
|
|
287
|
+
logger.info(f"Placing MTF market sell order for {trading_symbol=}, {qty=}")
|
|
288
|
+
meta_data = self._get_mtf_metadata(basket_id, instruction_id)
|
|
289
|
+
return self._place_order(trading_symbol, qty, self.SELL, generate_order_tag,
|
|
290
|
+
meta_data, self.MTF, self.MARKET, proxy=proxy)
|
|
291
|
+
|
|
292
|
+
def get_order_details(self, order_id):
|
|
293
|
+
"""
|
|
294
|
+
Fetch order details by order ID.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
order_id (str): Unique ID of the order.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
dict: Detailed order information.
|
|
301
|
+
"""
|
|
302
|
+
logger.info(f"Fetching order details for {order_id=}")
|
|
303
|
+
order_details = self._get(url=self.base_url,
|
|
304
|
+
endpoint=f"{self.urls.get('order')}/{order_id}")
|
|
305
|
+
return order_details.get("data")
|
|
306
|
+
|
|
307
|
+
def execute_sell_orders_cnc(self, instructions, request_data, portfolio_rebalance_id):
|
|
308
|
+
"""
|
|
309
|
+
Executes a list of CNC sell instructions.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
instructions (list): List of sell order dictionaries.
|
|
313
|
+
request_data (dict): Metadata including portfolio info.
|
|
314
|
+
portfolio_rebalance_id (str): Rebalance ID.
|
|
315
|
+
"""
|
|
316
|
+
logger.info(f"Executing CNC sell orders - {instructions=}")
|
|
317
|
+
for orders in instructions:
|
|
318
|
+
data = self._market_sell_cnc(trading_symbol=orders.get("symbol"),
|
|
319
|
+
qty=orders.get("qty"),
|
|
320
|
+
generate_order_tag=orders.get("tag"),
|
|
321
|
+
user_portfolio_id=request_data.get("user_portfolio_id"),
|
|
322
|
+
instruction_id=orders.get("instruction_id"),
|
|
323
|
+
rebalance=request_data.get("rebalance"),
|
|
324
|
+
user_portfolio_rebalance_id=portfolio_rebalance_id,
|
|
325
|
+
asm_consent=orders.get('asm_consent'),
|
|
326
|
+
asm_reason=orders.get('asm_reason'))
|
|
327
|
+
logger.info(f"CNC sell executed: {data=}")
|
|
328
|
+
|
|
329
|
+
def execute_sell_orders_mtf(self, instructions, basket_id):
|
|
330
|
+
"""
|
|
331
|
+
Executes sell orders for the Multi-Trade Fund (MTF).
|
|
332
|
+
|
|
333
|
+
This method iterates through a list of sell instructions and places market sell
|
|
334
|
+
orders for each security based on the given instructions. It uses the `_market_sell_mtf`
|
|
335
|
+
method to handle the order execution.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
instructions (list): A list of dictionaries containing sell order details.
|
|
339
|
+
Each dictionary includes:
|
|
340
|
+
- "symbol" (str): The trading symbol of the security.
|
|
341
|
+
- "qty" (int): The quantity of the security to sell.
|
|
342
|
+
- "tag" (bool): Indicates whether to generate an order tag for the sell order.
|
|
343
|
+
|
|
344
|
+
basket_id (int): The unique identifier for the basket.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
None
|
|
348
|
+
"""
|
|
349
|
+
logger.info(f"In execute_orders - {instructions =}")
|
|
350
|
+
for orders in instructions:
|
|
351
|
+
data = self._market_sell_mtf(
|
|
352
|
+
trading_symbol=orders.get("symbol"),
|
|
353
|
+
qty=orders.get("qty"),
|
|
354
|
+
generate_order_tag=orders.get("tag"),
|
|
355
|
+
basket_id=basket_id,
|
|
356
|
+
instruction_id=orders.get("instruction_id")
|
|
357
|
+
)
|
|
358
|
+
logger.info(f"execute_orders sell {data = }")
|
|
359
|
+
|
|
360
|
+
def execute_buy_orders_cnc(self, instructions, request_data, portfolio_rebalance_id):
|
|
361
|
+
"""
|
|
362
|
+
Execute a list of orders.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
instructions (dict): Instructions for executing orders.
|
|
366
|
+
"""
|
|
367
|
+
logger.info(f"In execute_buy_orders - {instructions =}, {request_data =}, {portfolio_rebalance_id =}")
|
|
368
|
+
for orders in instructions:
|
|
369
|
+
data = self._market_buy_cnc(trading_symbol=orders.get("symbol"),
|
|
370
|
+
qty=orders.get("qty"),
|
|
371
|
+
generate_order_tag=orders.get("tag"),
|
|
372
|
+
user_portfolio_id=request_data.get("user_portfolio_id"),
|
|
373
|
+
instruction_id=orders.get("instruction_id"),
|
|
374
|
+
rebalance=request_data.get("rebalance"),
|
|
375
|
+
user_portfolio_rebalance_id=portfolio_rebalance_id,
|
|
376
|
+
asm_consent=orders.get('asm_consent'),
|
|
377
|
+
asm_reason=orders.get('asm_reason')
|
|
378
|
+
)
|
|
379
|
+
logger.info(f"execute_buy_orders {data = }")
|
|
380
|
+
|
|
381
|
+
def execute_buy_orders_mtf(self, instructions, basket_id):
|
|
382
|
+
"""
|
|
383
|
+
Executes buy orders for the Multi-Trade Fund (MTF).
|
|
384
|
+
|
|
385
|
+
This method processes a list of buy instructions and places market buy
|
|
386
|
+
orders for each security. It utilizes the `_market_buy_mtf` method for
|
|
387
|
+
executing the orders based on the provided instructions.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
instructions (list): A list of dictionaries containing buy order details.
|
|
391
|
+
Each dictionary includes:
|
|
392
|
+
- "symbol" (str): The trading symbol of the security to buy.
|
|
393
|
+
- "qty" (int): The quantity of the security to buy.
|
|
394
|
+
- "tag" (bool): Indicates whether to generate an order tag for the buy order.
|
|
395
|
+
for order processing.
|
|
396
|
+
basket_id (int): The unique identifier for the basket.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
None
|
|
400
|
+
"""
|
|
401
|
+
logger.info(f"In execute_buy_orders_mtf - {instructions =}")
|
|
402
|
+
for orders in instructions:
|
|
403
|
+
data = self._market_buy_mtf(
|
|
404
|
+
trading_symbol=orders.get("symbol"),
|
|
405
|
+
qty=orders.get("qty"),
|
|
406
|
+
generate_order_tag=orders.get("tag"),
|
|
407
|
+
basket_id=basket_id,
|
|
408
|
+
instruction_id=orders.get("instruction_id")
|
|
409
|
+
)
|
|
410
|
+
logger.info(f"execute_buy_orders_mtf {data = }")
|
|
411
|
+
|
|
412
|
+
def create_draft_orders(self, instructions, request_data, portfolio_rebalance_id):
|
|
413
|
+
"""
|
|
414
|
+
Create draft buy/sell CNC (Cash & Carry) market orders based on provided instructions.
|
|
415
|
+
|
|
416
|
+
This method iterates over a list of order instructions and dispatches them to the appropriate
|
|
417
|
+
order creation method (`_market_buy_cnc` or `_market_sell_cnc`) depending on the order side.
|
|
418
|
+
|
|
419
|
+
Parameters:
|
|
420
|
+
----------
|
|
421
|
+
instructions : list[dict]
|
|
422
|
+
A list of order instruction dictionaries. Each dictionary must include:
|
|
423
|
+
- 'side' : str ('BUY' or 'SELL')
|
|
424
|
+
- 'symbol' : str
|
|
425
|
+
- 'qty' : int
|
|
426
|
+
- 'tag' : str
|
|
427
|
+
- 'instruction_id' : str
|
|
428
|
+
|
|
429
|
+
request_data : dict
|
|
430
|
+
A dictionary containing contextual information about the rebalance operation.
|
|
431
|
+
Must include:
|
|
432
|
+
- 'user_portfolio_id' : str
|
|
433
|
+
- 'rebalance' : bool or dict
|
|
434
|
+
|
|
435
|
+
portfolio_rebalance_id : str or int
|
|
436
|
+
The ID associated with the current user portfolio rebalance session.
|
|
437
|
+
|
|
438
|
+
Raises:
|
|
439
|
+
------
|
|
440
|
+
ValueError:
|
|
441
|
+
If an instruction has an unrecognized `side` value.
|
|
442
|
+
"""
|
|
443
|
+
logger.info(
|
|
444
|
+
f"In draft_orders - {instructions =}, {request_data =}, {portfolio_rebalance_id =} ")
|
|
445
|
+
|
|
446
|
+
for order in instructions:
|
|
447
|
+
side = order.get("side")
|
|
448
|
+
if side == self.BUY:
|
|
449
|
+
method = self._market_buy_cnc
|
|
450
|
+
elif side == self.SELL:
|
|
451
|
+
method = self._market_sell_cnc
|
|
452
|
+
else:
|
|
453
|
+
raise ValueError(f"Invalid order side '{side}' for instruction: {order}")
|
|
454
|
+
|
|
455
|
+
method(
|
|
456
|
+
trading_symbol=order.get("symbol"),
|
|
457
|
+
qty=order.get("qty"),
|
|
458
|
+
generate_order_tag=order.get("tag"),
|
|
459
|
+
user_portfolio_id=request_data.get("user_portfolio_id"),
|
|
460
|
+
instruction_id=order.get("instruction_id"),
|
|
461
|
+
rebalance=request_data.get("rebalance"),
|
|
462
|
+
user_portfolio_rebalance_id=portfolio_rebalance_id,
|
|
463
|
+
proxy=self.READY_ONLY
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
def update_orders(self, instructions):
|
|
467
|
+
"""
|
|
468
|
+
Update multiple orders based on the provided instructions.
|
|
469
|
+
|
|
470
|
+
This method takes a list of order instructions, iterates through each instruction,
|
|
471
|
+
and sends a PUT request to update the order details on the broker's server.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
instructions (list): A list of dictionaries, where each dictionary contains the details
|
|
475
|
+
of an order that needs to be updated. Each dictionary typically
|
|
476
|
+
includes keys such as 'tag', 'symbol', 'quantity', 'side',
|
|
477
|
+
'order_price', etc.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
None
|
|
481
|
+
|
|
482
|
+
Raises:
|
|
483
|
+
HTTPError: If the PUT request to update an order fails or returns an error.
|
|
484
|
+
|
|
485
|
+
Logs:
|
|
486
|
+
- Logs the start of the update process and each order update attempt.
|
|
487
|
+
- Logs detailed information about the instructions being processed.
|
|
488
|
+
"""
|
|
489
|
+
logger.info(f"In update_orders {instructions =}")
|
|
490
|
+
response = {}
|
|
491
|
+
for instruction in instructions:
|
|
492
|
+
tag = instruction.get('tag')
|
|
493
|
+
logger.info(f"Updating order for {tag =}")
|
|
494
|
+
response = self._put(
|
|
495
|
+
url=self.base_url,
|
|
496
|
+
endpoint=f'{self.urls.get("update_orders")}/{tag}',
|
|
497
|
+
data=json.dumps(instruction)
|
|
498
|
+
)
|
|
499
|
+
return response
|