tradx 0.1.0__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.
@@ -0,0 +1,781 @@
1
+ from xts_api_client.market_data_socket_client import MarketDataSocketClient
2
+ from xts_api_client.market_data_socket import MDSocket_io
3
+ from xts_api_client.xts_connect_async import XTSConnect
4
+ import xts_api_client.helper.helper as helper
5
+ from typing import Any, List, Dict
6
+ from tradx.logger.logger import *
7
+ from tradx.baseClass.baseAlgo import BaseAlgo
8
+ from tradx.baseClass.candleData import CandleData
9
+ from tradx.baseClass.marketDepthData import MarketDepthData
10
+ from tradx.baseClass.marketStatusData import MarketStatusData
11
+ from tradx.baseClass.openInterestData import OpenInterestData
12
+ from tradx.baseClass.ltpData import LtpData
13
+ from tradx.baseClass.touchLineData import TouchLineData
14
+ from tradx.baseClass.index import Index
15
+ from tradx.baseClass.optionsInstrument import OptionManager, OptionsInstrument
16
+ from tradx.baseClass.tradeEvent import TradeEvent
17
+ from tradx.dualHashMap import DualHashMap
18
+ from tradx.algoContainer import AlgoContainer
19
+ from datetime import datetime
20
+ import json
21
+ import pandas
22
+ import asyncio
23
+
24
+
25
+ class marketDataEngine(MarketDataSocketClient):
26
+ """Class for market DATA API obj"""
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: str,
31
+ api_password: str,
32
+ source: str,
33
+ root: str,
34
+ user_logger: logging.Logger = None,
35
+ ) -> None:
36
+ """
37
+ Initializes the MarketDataEngine object.
38
+ Args:
39
+ api_key (str): The API key for authentication.
40
+ api_password (str): The API password for authentication.
41
+ source (str): The data source.
42
+ root (str): The root directory.
43
+ user_logger (logging.Logger, optional): Logger for user-defined logging. Defaults to None.
44
+ Raises:
45
+ AssertionError: If any of the required arguments (api_key, api_password, source, root) are not provided.
46
+ Attributes:
47
+ _api_key (str): The API key for authentication.
48
+ _api_password (str): The API password for authentication.
49
+ _source (str): The data source.
50
+ _root (str): The root directory.
51
+ exchange_to_exchangeSegments (dict): Mapping of exchanges to exchange segments.
52
+ index_to_exchangeSegmentId (DualHashMap): Mapping of index to exchange segment IDs.
53
+ F_MASTER_DF (pandas.DataFrame): DataFrame for F_MASTER data.
54
+ O_MASTER_DF (pandas.DataFrame): DataFrame for O_MASTER data.
55
+ CM_MASTER_DF (pandas.DataFrame): DataFrame for CM_MASTER data.
56
+ subscribe_manager (AlgoContainer): Manager for subscription algorithms.
57
+ set_marketDataToken (str): Token for market data.
58
+ set_userID (str): User ID.
59
+ user_logger (logging.Logger): Logger for user-defined logging.
60
+ """
61
+ assert api_key, "API key is required"
62
+ assert api_password, "API password is required"
63
+ assert source, "Source is required"
64
+ assert root, "Root is required"
65
+ self._api_key: str = api_key
66
+ self._api_password: str = api_password
67
+ self._source: str = source
68
+ self._root: str = root
69
+ self.exchange_to_exchangeSegments: dict = None
70
+ self.index_list: List[Index] = []
71
+ self.F_MASTER_DF: pandas.DataFrame = None
72
+ self.__option_manager: OptionManager = None
73
+ self.CM_MASTER_DF: pandas.DataFrame = None
74
+ self.subscribe_manager = AlgoContainer()
75
+ self.set_marketDataToken: str = None
76
+ self.set_userID: str = None
77
+ self.user_logger = user_logger
78
+ if self.user_logger:
79
+ self.user_logger.info(
80
+ "Market Data Engine Object initialized.",
81
+ caller="marketDataEngine.__init__",
82
+ )
83
+
84
+ async def initialize(self) -> None:
85
+ """
86
+ Asynchronously initializes the market data engine.
87
+ This method performs the necessary setup by logging in and
88
+ establishing a connection to the socket.
89
+ Returns:
90
+ None
91
+ """
92
+ await self.login()
93
+ await self.socket.connect()
94
+
95
+ async def on_event_candle_data_full(self, message: str) -> None:
96
+ """
97
+ Handles the full candle data event.
98
+ This asynchronous method is triggered when a full candle data event is received.
99
+ It processes the incoming message, converts it into a CandleData object, and
100
+ broadcasts it to the subscribers.
101
+ Args:
102
+ message (str): The incoming message containing candle data in string format.
103
+ Returns:
104
+ None
105
+ """
106
+ __ = CandleData(message)
107
+ if self.user_logger:
108
+ self.user_logger.info(
109
+ f"1505:Candle Data full;{__}",
110
+ caller="marketDataEngine.on_event_candle_data_full",
111
+ )
112
+ asyncio.ensure_future(self.subscribe_manager.broadcast(__))
113
+
114
+ async def on_event_market_data_full(self, data):
115
+ """On receiving message code 1502:Market Data full"""
116
+ __ = MarketDepthData(data)
117
+ if self.user_logger:
118
+ self.user_logger.info(
119
+ f"1502:Market Data full;{__}",
120
+ caller="marketDataEngine.on_event_market_data_full",
121
+ )
122
+
123
+ async def on_event_market_status_full(self, data):
124
+ """On receiving message code 1507:Market Status full"""
125
+ __ = MarketStatusData(data)
126
+ if self.user_logger:
127
+ self.user_logger.info(
128
+ f"1507:Market Status full;{__}",
129
+ caller="marketDataEngine.on_event_market_status_full",
130
+ )
131
+
132
+ async def on_event_last_traded_price_full(self, data):
133
+ """On receiving message code 1512:LTP full"""
134
+ __ = LtpData(data)
135
+ if self.user_logger:
136
+ self.user_logger.info(
137
+ f"1512:LTP full;{__}",
138
+ caller="marketDataEngine.on_event_last_traded_price_full",
139
+ )
140
+
141
+ async def on_event_openinterest_full(self, data):
142
+ """On receiving message code 1510:OpenInterest full"""
143
+ __ = OpenInterestData(data)
144
+ if self.user_logger:
145
+ self.user_logger.info(
146
+ f"1510:OpenInterest full;{__}",
147
+ caller="marketDataEngine.on_event_openinterest_full",
148
+ )
149
+
150
+ async def on_event_touchline_full(self, data):
151
+ """On receiving message code 1501:Touchline full"""
152
+ __ = TouchLineData(data)
153
+ if self.user_logger:
154
+ self.user_logger.info(
155
+ f"1501:Touchline full;{__}",
156
+ caller="marketDataEngine.on_event_touchline_full",
157
+ )
158
+ asyncio.ensure_future(self.subscribe_manager.broadcast(__))
159
+
160
+ async def on_connect(self) -> None:
161
+ """
162
+ Asynchronous method that handles actions to be performed upon successful connection to the market data socket.
163
+ This method logs a message indicating that the market data socket has connected successfully.
164
+ Returns:
165
+ None
166
+ """
167
+
168
+ if self.user_logger:
169
+ self.user_logger.info(
170
+ "Market Data Socket connected successfully!",
171
+ caller="marketDataEngine.on_connect",
172
+ )
173
+
174
+ async def on_disconnect(self) -> None:
175
+ """
176
+ Handles the event when the market data socket gets disconnected.
177
+ This method logs an informational message indicating that the market data
178
+ socket has been disconnected. The log entry includes the caller information
179
+ for easier traceability.
180
+ Returns:
181
+ None
182
+ """
183
+
184
+ if self.user_logger:
185
+ self.user_logger.info(
186
+ "Market Data Socket disconnected!",
187
+ caller="marketDataEngine.on_disconnect",
188
+ )
189
+
190
+ async def on_message(self, xts_message: Any) -> None:
191
+ """
192
+ Asynchronously handles incoming messages.
193
+ This method is triggered when a new message is received. It parses the
194
+ message from JSON format and logs the message if a user logger is available.
195
+ Args:
196
+ xts_message (Any): The incoming message in JSON format.
197
+ Returns:
198
+ None
199
+ """
200
+ if self.user_logger:
201
+ self.user_logger.info(
202
+ f"Received a message: {xts_message}",
203
+ caller="marketDataEngine.on_message",
204
+ )
205
+
206
+ async def on_error(self, xts_message: Any) -> None:
207
+ """
208
+ Handles error messages received from the XTS system.
209
+ Args:
210
+ xts_message (Any): The error message received from the XTS system.
211
+ Returns:
212
+ None
213
+ """
214
+ if self.user_logger:
215
+ self.user_logger.error(
216
+ f"Received a error: {xts_message}", caller="marketDataEngine.on_error"
217
+ )
218
+
219
+ async def shutdown(self) -> None:
220
+ """
221
+ Asynchronously shuts down the market data engine.
222
+ This method performs the following steps:
223
+ 1. Logs the entry into shutdown mode if a user logger is available.
224
+ 2. Disconnects the socket connection.
225
+ 3. Logs out from the market data service.
226
+ 4. Logs the successful logout and end of trading if a user logger is available.
227
+ If an exception occurs during the shutdown process, it logs the error and re-raises the exception.
228
+ Raises:
229
+ Exception: If an error occurs during the shutdown process.
230
+ """
231
+
232
+ try:
233
+ if self.user_logger:
234
+ self.user_logger.info(
235
+ "Entering shut down mode.", caller="marketDataEngine.shutdown"
236
+ )
237
+ await self.socket.disconnect()
238
+ await self.xt.marketdata_logout()
239
+ if self.user_logger:
240
+ self.user_logger.info(
241
+ f"Logged Out.",
242
+ caller="marketDataEngine.shutdown",
243
+ )
244
+
245
+ except Exception as e:
246
+ if self.user_logger:
247
+ self.user_logger.error(e, caller="marketDataEngine.shutdown")
248
+ raise (e)
249
+
250
+ async def login(self) -> None:
251
+ """
252
+ Asynchronously logs in to the market data engine and initializes necessary connections.
253
+ This method performs the following steps:
254
+ 1. Initializes the XTSConnect object with the provided API credentials.
255
+ 2. Performs an interactive login to obtain the market data token and user ID.
256
+ 3. Initializes and connects the MDSocket_io object using the obtained token and user ID.
257
+ 4. Logs the successful login if a user logger is available.
258
+ 5. Retrieves and maps all exchange codes to their respective exchange segments.
259
+ 6. Retrieves and maps all index codes to their respective exchange instrument IDs.
260
+ 7. Logs the completion of the exchange and index mappings if a user logger is available.
261
+ Raises:
262
+ Exception: If any error occurs during the login process, it is logged and re-raised.
263
+ Returns:
264
+ None
265
+ """
266
+
267
+ try:
268
+ # Initialize XTSConnect object
269
+ self.xt = XTSConnect(
270
+ self._api_key, self._api_password, self._source, self._root
271
+ )
272
+
273
+ # Perform interactive login
274
+ response = await self.xt.marketdata_login()
275
+ self.set_marketDataToken = response["result"]["token"]
276
+ self.set_userID = response["result"]["userID"]
277
+
278
+ # Initialize and connect OrderSocket_io object
279
+ self.socket = MDSocket_io(
280
+ self.set_marketDataToken, self.set_userID, self._root, self
281
+ )
282
+ # Log successful login
283
+ if self.user_logger:
284
+ self.user_logger.info(
285
+ f"Login successful.", caller="marketDataEngine.login"
286
+ )
287
+
288
+ """Retrieve all exchange codes"""
289
+ response = await self.xt.get_config()
290
+ self.exchange_to_exchangeSegments = response["result"]["exchangeSegments"]
291
+ if self.user_logger:
292
+ self.user_logger.info(
293
+ f"Exchange to exchange segments mapping completed.",
294
+ caller="marketDataEngine.login",
295
+ )
296
+
297
+ """Retrieve all index codes"""
298
+ for exchange in self.exchange_to_exchangeSegments:
299
+ response = (
300
+ await self.xt.get_index_list(
301
+ exchangeSegment=self.exchange_to_exchangeSegments[exchange]
302
+ )
303
+ )["result"]
304
+ if "indexList" not in response:
305
+ continue
306
+ index_list = response["indexList"]
307
+ for index in index_list:
308
+ idx_name, idx_code = index.split("_")
309
+ self.index_list.append(
310
+ Index(
311
+ idx_name,
312
+ self.exchange_to_exchangeSegments[exchange],
313
+ idx_code,
314
+ )
315
+ )
316
+
317
+ if self.user_logger:
318
+ self.user_logger.info(
319
+ f"Index to exchange instrument id mapping completed.",
320
+ caller="marketDataEngine.login",
321
+ )
322
+ except Exception as e:
323
+ if self.user_logger:
324
+ self.user_logger.error(e, caller="marketDataEngine.login")
325
+ raise (e)
326
+
327
+ async def subscribe(
328
+ self, Instruments: List[Dict], xtsMessageCode: int, algo: BaseAlgo
329
+ ) -> None:
330
+ """
331
+ Subscribes to market data for the given instruments.
332
+ Args:
333
+ Instruments (List[Dict]): A list of dictionaries, each containing 'exchangeSegment' and 'exchangeInstrumentID'.
334
+ xtsMessageCode (int): The message code for the subscription. Must be one of [1501, 1502, 1505, 1507, 1512, 1105].
335
+ algo (BaseAlgo): An instance of a class derived from BaseAlgo, representing the algorithm to be used for processing the market data.
336
+ Raises:
337
+ AssertionError: If any of the input arguments do not meet the required conditions.
338
+ Exception: If an error occurs during the subscription process.
339
+ Returns:
340
+ None
341
+ """
342
+ assert Instruments, "Instruments list is required"
343
+ assert isinstance(Instruments, list), "Instruments must be a list"
344
+ assert isinstance(xtsMessageCode, int), "xtsMessageCode must be an integer"
345
+ assert isinstance(
346
+ algo, BaseAlgo
347
+ ), "algo must be a tradx.baseClass.BaseAlgo object"
348
+ for instrument in Instruments:
349
+ assert isinstance(instrument, dict), "Each instrument must be a dictionary"
350
+ assert (
351
+ "exchangeSegment" in instrument
352
+ ), "Each instrument must have an 'exchangeSegment'"
353
+ assert (
354
+ "exchangeInstrumentID" in instrument
355
+ ), "Each instrument must have an 'exchangeInstrumentID'"
356
+ assert xtsMessageCode in [
357
+ 1501,
358
+ 1502,
359
+ 1505,
360
+ 1507,
361
+ 1512,
362
+ 1105,
363
+ ], "Invalid message code"
364
+
365
+ try:
366
+ for item in range(len(Instruments)):
367
+ self.subscribe_manager.subscribe(
368
+ Instruments[item]["exchangeInstrumentID"], algo
369
+ )
370
+
371
+ response = await self.xt.send_subscription(
372
+ Instruments=Instruments, xtsMessageCode=xtsMessageCode
373
+ )
374
+ if response["type"] != "success":
375
+ self.user_logger.error(
376
+ f"Error in Subscribing Quantities: {Instruments} on request from {algo.name} as {response}",
377
+ caller="marketDataEngine.subscribe",
378
+ )
379
+ raise (response)
380
+ if self.user_logger:
381
+ self.user_logger.info(
382
+ f"Subscribed Quantities: {Instruments} on request from {algo.name}",
383
+ caller="marketDataEngine.subscribe",
384
+ )
385
+ except Exception as e:
386
+ if self.user_logger:
387
+ self.user_logger.error(e, caller="marketDataEngine.subscribe")
388
+ raise (e)
389
+
390
+ async def loadMaster(self) -> None:
391
+ """
392
+ Asynchronously loads master data for different market segments and processes it.
393
+ This method fetches master instruments data for NSE FO, BSE FO, NSE CM, and BSE CM market segments,
394
+ converts the data into DataFrames, saves them as CSV files, and logs the fetched data.
395
+ Raises:
396
+ Exception: If there is an error during the fetching or processing of the master data.
397
+ """
398
+
399
+ try:
400
+ """Get Master Instruments Request for NSE FO market segment"""
401
+ exchangesegments = [self.xt.EXCHANGE_NSEFO, self.xt.EXCHANGE_BSEFO]
402
+ response = await self.xt.get_master(exchangeSegmentList=exchangesegments)
403
+ self.F_MASTER_DF, O_MASTER_DF, f_spread_df = helper.fo_master_string_to_df(
404
+ response["result"]
405
+ )
406
+ O_MASTER_DF["UnderlyingIndexName"] = O_MASTER_DF[
407
+ "UnderlyingIndexName"
408
+ ].str.upper()
409
+ O_MASTER_DF.to_csv(f"MASTER_O.csv", index=False)
410
+ self.__option_manager = OptionManager(O_MASTER_DF)
411
+ self.F_MASTER_DF.to_csv(f"MASTER_F.csv", index=False)
412
+
413
+ if self.user_logger:
414
+ self.user_logger.info(
415
+ f"Options Contract Fetched: Sample - {O_MASTER_DF.head(1)}",
416
+ caller="marketDataEngine.loadMaster",
417
+ )
418
+ if self.user_logger:
419
+ self.user_logger.info(
420
+ f"Futures Contract Fetched: Sample - {self.F_MASTER_DF.head(1)}",
421
+ caller="marketDataEngine.loadMaster",
422
+ )
423
+
424
+ """Get Master Instruments Request for NSE cash market segment"""
425
+ exchangesegments = [self.xt.EXCHANGE_NSECM, self.xt.EXCHANGE_BSECM]
426
+ response = await self.xt.get_master(exchangeSegmentList=exchangesegments)
427
+ self.CM_MASTER_DF = helper.cm_master_string_to_df(response["result"])
428
+
429
+ self.CM_MASTER_DF.to_csv(f"MASTER_CM.csv", index=False)
430
+
431
+ if self.user_logger:
432
+ self.user_logger.info(
433
+ f"Cash Market Contract Fetched: Sample - {self.CM_MASTER_DF.head(1)}",
434
+ caller="marketDataEngine.loadMaster",
435
+ )
436
+ except Exception as e:
437
+ self.user_logger.error(e, caller="marketDataEngine.loadMaster")
438
+ raise (e)
439
+
440
+ async def fetch_ltp(self, Instruments: List[Dict]) -> List[TouchLineData]:
441
+ """
442
+ Fetches the Last Traded Price (LTP) data for a list of instruments.
443
+ Args:
444
+ Instruments (List[Dict]): A list of dictionaries, each containing:
445
+ - 'exchangeSegment' (str): The exchange segment of the instrument.
446
+ - 'exchangeInstrumentID' (str): The exchange instrument ID.
447
+ Returns:
448
+ List[LtpData]: A list of LtpData objects containing the LTP data for each instrument.
449
+ Raises:
450
+ AssertionError: If the Instruments list is empty, not a list, or if any instrument
451
+ dictionary does not contain the required keys.
452
+ """
453
+
454
+ assert Instruments, "Instruments list is required"
455
+ assert isinstance(Instruments, list), "Instruments must be a list"
456
+ for instrument in Instruments:
457
+ assert isinstance(instrument, dict), "Each instrument must be a dictionary"
458
+ assert (
459
+ "exchangeSegment" in instrument
460
+ ), "Each instrument must have an 'exchangeSegment'"
461
+ assert (
462
+ "exchangeInstrumentID" in instrument
463
+ ), "Each instrument must have an 'exchangeInstrumentID'"
464
+
465
+ for instrument in Instruments:
466
+ if isinstance(instrument["exchangeSegment"], str):
467
+ instrument["exchangeSegment"] = self.exchange_to_exchangeSegments[
468
+ instrument["exchangeSegment"]
469
+ ]
470
+ response = await self.xt.get_quote(
471
+ Instruments=Instruments,
472
+ xtsMessageCode=1501,
473
+ publishFormat="JSON",
474
+ )
475
+ _list: List[TouchLineData] = []
476
+ for item in response["result"]["listQuotes"]:
477
+ _list.append(TouchLineData(item))
478
+ return _list
479
+
480
+ async def fetch_option_quotes(self, instrument):
481
+ response = await self.xt.get_quote(
482
+ Instruments=instrument,
483
+ xtsMessageCode=1502,
484
+ publishFormat="JSON",
485
+ )
486
+ return json.loads(response["result"]["listQuotes"][0])
487
+
488
+ async def unsubscribe(
489
+ self, Instruments: List[Dict], xtsMessageCode: int, algo: BaseAlgo
490
+ ) -> None:
491
+ """
492
+ Unsubscribes from market data for the given instruments.
493
+ Args:
494
+ Instruments (List[Dict]): A list of dictionaries, each containing 'exchangeSegment' and 'exchangeInstrumentID'.
495
+ xtsMessageCode (int): The message code for the unsubscription. Must be one of [1501, 1502, 1505, 1507, 1512, 1105].
496
+ algo (BaseAlgo): An instance of a class derived from BaseAlgo, representing the algorithm to be used for processing the market data.
497
+ Raises:
498
+ AssertionError: If any of the input arguments do not meet the required conditions.
499
+ Exception: If an error occurs during the unsubscription process.
500
+ Returns:
501
+ None
502
+ """
503
+ assert Instruments, "Instruments list is required"
504
+ assert isinstance(Instruments, list), "Instruments must be a list"
505
+ assert isinstance(xtsMessageCode, int), "xtsMessageCode must be an integer"
506
+ assert isinstance(
507
+ algo, BaseAlgo
508
+ ), "algo must be a tradx.baseClass.BaseAlgo object"
509
+ for instrument in Instruments:
510
+ assert isinstance(instrument, dict), "Each instrument must be a dictionary"
511
+ assert (
512
+ "exchangeSegment" in instrument
513
+ ), "Each instrument must have an 'exchangeSegment'"
514
+ assert (
515
+ "exchangeInstrumentID" in instrument
516
+ ), "Each instrument must have an 'exchangeInstrumentID'"
517
+ assert xtsMessageCode in [
518
+ 1501,
519
+ 1502,
520
+ 1505,
521
+ 1507,
522
+ 1512,
523
+ 1105,
524
+ ], "Invalid message code"
525
+
526
+ try:
527
+ for item in range(len(Instruments)):
528
+ self.subscribe_manager.unsubscribe(
529
+ Instruments[item]["exchangeInstrumentID"], algo=algo
530
+ )
531
+
532
+ response = await self.xt.send_unsubscription(
533
+ Instruments=Instruments, xtsMessageCode=xtsMessageCode
534
+ )
535
+ if response["type"] != "success":
536
+ self.user_logger.error(
537
+ f"Error in unsubscribing Quantities: {Instruments} on request from {algo.name} as {response}",
538
+ caller="marketDataEngine.unsubscribe",
539
+ )
540
+ raise (response)
541
+ if self.user_logger:
542
+ self.user_logger.info(
543
+ f"Unsubscribed Quantities: {Instruments} on request from {algo.name}",
544
+ caller="marketDataEngine.unsubscribe",
545
+ )
546
+
547
+ except Exception as e:
548
+ if self.user_logger:
549
+ self.user_logger.error(e, caller="marketDataEngine.unsubscribe")
550
+ raise (e)
551
+
552
+ async def option_search_expiry_by_underline(self, underline: str) -> List[datetime]:
553
+ """
554
+ Searches for all contract expirations for a given underlying index name.
555
+ Args:
556
+ underline (str): The underlying index name to search for.
557
+ Returns:
558
+ List[datetime.datetime]: A sorted list of contract expirations.
559
+ """
560
+ return self.__option_manager.search_expiry_by_underline(underline)
561
+
562
+ async def option_search_all_underline(self) -> List[str]:
563
+ """
564
+ Retrieves all unique underlying index names.
565
+ Returns:
566
+ List[str]: A list of all unique underlying index names.
567
+ """
568
+ return self.__option_manager.search_all_underline()
569
+
570
+ async def option_search_by_underline(
571
+ self, underline: str
572
+ ) -> List[OptionsInstrument]:
573
+ """
574
+ Searches for all options instruments for a given underlying index name.
575
+ Args:
576
+ underline (str): The underlying index name to search for.
577
+ Returns:
578
+ List[OptionsInstrument]: A list of options instruments.
579
+ """
580
+ return self.__option_manager.search_option_by_underline(underline)
581
+
582
+ async def option_search_by_expiry_and_underline(
583
+ self, underline: str, expiry: datetime
584
+ ) -> List[OptionsInstrument]:
585
+ """
586
+ Searches for all options instruments for a given underlying index name and contract expiration.
587
+ Args:
588
+ underline (str): The underlying index name to search for.
589
+ expiry (OptionsInstrument.ContractExpiration): The contract expiration to search for.
590
+ Returns:
591
+ List[OptionsInstrument]: A list of options instruments.
592
+ """
593
+ return self.__option_manager.search_option_by_expiry_underline(
594
+ underline, expiry
595
+ )
596
+
597
+ async def option_search(
598
+ self,
599
+ ExchangeSegment: str = None,
600
+ ExchangeInstrumentID: int = None,
601
+ InstrumentType: int = None,
602
+ Name: str = None,
603
+ Series: str = None,
604
+ UnderlyingIndexName: str = None,
605
+ ContractExpiration: datetime = None,
606
+ StrikePrice: int = None,
607
+ OptionType: int = None,
608
+ minimumExpiry: bool = False,
609
+ ) -> List[OptionsInstrument]:
610
+ """
611
+ Searches for options based on various criteria.
612
+ Args:
613
+ ExchangeSegment (str): Exchange segment to search for.
614
+ ExchangeInstrumentID (int): Exchange instrument ID to search for.
615
+ InstrumentType (int): Instrument type to search for.
616
+ Name (str): Name to search for.
617
+ Series (str): Series to search for.
618
+ UnderlyingIndexName (str): Underlying index name to search for.
619
+ ContractExpiration (datetime): Contract expiration to search for.
620
+ StrikePrice (int): Strike price to search for.
621
+ OptionType (int): Option type to search for.
622
+ minimumExpiry (bool): If True, only return options with the minimum expiration date.
623
+ Returns:
624
+ pandas.DataFrame: DataFrame containing the search results.
625
+ """
626
+ return self.__option_manager.search_option(
627
+ ExchangeSegment=ExchangeSegment,
628
+ ExchangeInstrumentID=ExchangeInstrumentID,
629
+ InstrumentType=InstrumentType,
630
+ Name=Name,
631
+ Series=Series,
632
+ UnderlyingIndexName=UnderlyingIndexName,
633
+ ContractExpiration=ContractExpiration,
634
+ StrikePrice=StrikePrice,
635
+ OptionType=OptionType,
636
+ minimumExpiry=minimumExpiry,
637
+ )
638
+
639
+ async def dummy_market_order(
640
+ self,
641
+ exchangeSegment: str,
642
+ exchangeInstrumentID: int,
643
+ productType: str,
644
+ orderQuantity: int,
645
+ orderUniqueIdentifier: str,
646
+ baseAlgo: BaseAlgo,
647
+ ):
648
+ """
649
+ Simulates a market order and generates a trade event.
650
+ Args:
651
+ exchangeSegment (str): The segment of the exchange where the order is placed.
652
+ exchangeInstrumentID (int): The ID of the instrument being traded.
653
+ productType (str): The type of product being traded.
654
+ orderQuantity (int): The quantity of the order. Positive for buy, negative for sell.
655
+ orderUniqueIdentifier (str): A unique identifier for the order.
656
+ baseAlgo (BaseAlgo): An instance of the BaseAlgo class to handle the trade event.
657
+ Returns:
658
+ None
659
+ This function fetches the last traded price (LTP) for the given instrument and creates a TradeEvent
660
+ based on whether the order is a buy or sell. The TradeEvent is then passed to the baseAlgo's trade_
661
+ method for further processing.
662
+ """
663
+ _list = await self.fetch_ltp(
664
+ [
665
+ {
666
+ "exchangeSegment": self.exchange_to_exchangeSegments[
667
+ exchangeSegment
668
+ ],
669
+ "exchangeInstrumentID": exchangeInstrumentID,
670
+ }
671
+ ]
672
+ )
673
+ _data = next(
674
+ item for item in _list if item.ExchangeInstrumentID == exchangeInstrumentID
675
+ )
676
+ tradeEvent: TradeEvent = None
677
+ if orderQuantity < 0:
678
+ tradeEvent = TradeEvent(
679
+ {
680
+ "LoginID": "ANSYM1",
681
+ "ClientID": "PR03",
682
+ "AppOrderID": 1110039096,
683
+ "OrderReferenceID": "",
684
+ "GeneratedBy": "TWSAPI",
685
+ "ExchangeOrderID": "1200000014332079",
686
+ "OrderCategoryType": "NORMAL",
687
+ "ExchangeSegment": exchangeSegment,
688
+ "ExchangeInstrumentID": exchangeInstrumentID,
689
+ "OrderSide": "Sell",
690
+ "OrderType": "Market",
691
+ "ProductType": productType,
692
+ "TimeInForce": "DAY",
693
+ "OrderPrice": 0,
694
+ "OrderQuantity": abs(orderQuantity),
695
+ "OrderStopPrice": 0,
696
+ "OrderStatus": "Filled",
697
+ "OrderAverageTradedPrice": _data.LastTradedPrice,
698
+ "LeavesQuantity": 0,
699
+ "CumulativeQuantity": abs(orderQuantity),
700
+ "OrderDisclosedQuantity": 0,
701
+ "OrderGeneratedDateTime": datetime.now(),
702
+ "ExchangeTransactTime": datetime.now(),
703
+ "LastUpdateDateTime": datetime.now(),
704
+ "CancelRejectReason": "",
705
+ "OrderUniqueIdentifier": orderUniqueIdentifier,
706
+ "OrderLegStatus": "SingleOrderLeg",
707
+ "LastTradedPrice": _data.LastTradedPrice,
708
+ "LastTradedQuantity": 0,
709
+ "LastExecutionTransactTime": "2025-01-06T10:14:40",
710
+ "ExecutionID": "402597456",
711
+ "ExecutionReportIndex": 4,
712
+ "IsSpread": False,
713
+ "OrderAverageTradedPriceAPI": _data.LastTradedPrice,
714
+ "OrderSideAPI": "SELL",
715
+ "OrderGeneratedDateTimeAPI": datetime.now(),
716
+ "ExchangeTransactTimeAPI": datetime.now(),
717
+ "LastUpdateDateTimeAPI": datetime.now(),
718
+ "OrderExpiryDateAPI": "01-01-1980 00:00:00",
719
+ "LastExecutionTransactTimeAPI": "06-01-2025 10:14:43",
720
+ "MessageSynchronizeUniqueKey": "PR03",
721
+ "MessageCode": 9005,
722
+ "MessageVersion": 4,
723
+ "TokenID": 0,
724
+ "ApplicationType": 146,
725
+ "SequenceNumber": 1583119016879540,
726
+ "TradingSymbol": "OBEROIRLTY",
727
+ }
728
+ )
729
+ else:
730
+ tradeEvent = TradeEvent(
731
+ {
732
+ "LoginID": "ANSYM1",
733
+ "ClientID": "PR03",
734
+ "AppOrderID": 1110033460,
735
+ "OrderReferenceID": "",
736
+ "GeneratedBy": "TWSAPI",
737
+ "ExchangeOrderID": "1200000074343640",
738
+ "OrderCategoryType": "NORMAL",
739
+ "ExchangeSegment": exchangeSegment,
740
+ "ExchangeInstrumentID": exchangeInstrumentID,
741
+ "OrderSide": "Buy",
742
+ "OrderType": "Market",
743
+ "ProductType": productType,
744
+ "TimeInForce": "DAY",
745
+ "OrderPrice": 0,
746
+ "OrderQuantity": orderQuantity,
747
+ "OrderStopPrice": 0,
748
+ "OrderStatus": "Filled",
749
+ "OrderAverageTradedPrice": _data.LastTradedPrice,
750
+ "LeavesQuantity": 0,
751
+ "CumulativeQuantity": orderQuantity,
752
+ "OrderDisclosedQuantity": 0,
753
+ "OrderGeneratedDateTime": datetime.now(),
754
+ "ExchangeTransactTime": datetime.now(),
755
+ "LastUpdateDateTime": datetime.now(),
756
+ "CancelRejectReason": "",
757
+ "OrderUniqueIdentifier": orderUniqueIdentifier,
758
+ "OrderLegStatus": "SingleOrderLeg",
759
+ "LastTradedPrice": _data.LastTradedPrice,
760
+ "LastTradedQuantity": 0,
761
+ "LastExecutionTransactTime": "2025-01-15T15:09:56",
762
+ "ExecutionID": "409661490",
763
+ "ExecutionReportIndex": 3,
764
+ "IsSpread": False,
765
+ "OrderAverageTradedPriceAPI": _data.LastTradedPrice,
766
+ "OrderSideAPI": "BUY",
767
+ "OrderGeneratedDateTimeAPI": "15-01-2025 15:10:00",
768
+ "ExchangeTransactTimeAPI": "15-01-2025 15:09:56",
769
+ "LastUpdateDateTimeAPI": "15-01-2025 15:10:00",
770
+ "OrderExpiryDateAPI": "01-01-1980 00:00:00",
771
+ "LastExecutionTransactTimeAPI": "15-01-2025 15:10:00",
772
+ "MessageSynchronizeUniqueKey": "PR03",
773
+ "MessageCode": 9005,
774
+ "MessageVersion": 4,
775
+ "TokenID": 0,
776
+ "ApplicationType": 146,
777
+ "SequenceNumber": 1590913686126258,
778
+ "TradingSymbol": "MARUTI",
779
+ }
780
+ )
781
+ asyncio.ensure_future(baseAlgo.trade_(tradeEvent))