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.
- tradx/__init__.py +2 -0
- tradx/algoContainer.py +90 -0
- tradx/baseClass/baseAlgo.py +338 -0
- tradx/baseClass/candleData.py +89 -0
- tradx/baseClass/cmInstrument.py +84 -0
- tradx/baseClass/futureInstrument.py +74 -0
- tradx/baseClass/index.py +24 -0
- tradx/baseClass/instrumentPropertyChangeData.py +14 -0
- tradx/baseClass/ltpData.py +89 -0
- tradx/baseClass/ltpPartialData.py +108 -0
- tradx/baseClass/marketDepthData.py +126 -0
- tradx/baseClass/marketStatusData.py +110 -0
- tradx/baseClass/openInterestData.py +84 -0
- tradx/baseClass/openInterestPartialData.py +47 -0
- tradx/baseClass/optionsInstrument.py +279 -0
- tradx/baseClass/order.py +27 -0
- tradx/baseClass/orderEvent.py +90 -0
- tradx/baseClass/position.py +46 -0
- tradx/baseClass/positionEvent.py +84 -0
- tradx/baseClass/touchLineData.py +201 -0
- tradx/baseClass/touchLinePartialData.py +109 -0
- tradx/baseClass/tradeConversionEvent.py +80 -0
- tradx/baseClass/tradeEvent.py +153 -0
- tradx/constants/holidays.py +32 -0
- tradx/dualHashMap.py +57 -0
- tradx/interactiveEngine.py +764 -0
- tradx/logger/logger.py +83 -0
- tradx/logger/logger2.py +73 -0
- tradx/marketDataEngine.py +781 -0
- tradx/py.typed +0 -0
- tradx-0.1.0.dist-info/METADATA +69 -0
- tradx-0.1.0.dist-info/RECORD +33 -0
- tradx-0.1.0.dist-info/WHEEL +4 -0
@@ -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))
|