tradx 0.6__tar.gz → 0.7.1__tar.gz
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-0.6 → tradx-0.7.1}/PKG-INFO +1 -1
- {tradx-0.6 → tradx-0.7.1}/pyproject.toml +1 -1
- tradx-0.7.1/src/tradx/baseClass/baseAlgo.py +495 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/marketDataEngine.py +2 -1
- {tradx-0.6 → tradx-0.7.1}/.gitignore +0 -0
- {tradx-0.6 → tradx-0.7.1}/.vscode/settings.json +0 -0
- {tradx-0.6 → tradx-0.7.1}/README.md +0 -0
- {tradx-0.6 → tradx-0.7.1}/examples/example1.log +0 -0
- {tradx-0.6 → tradx-0.7.1}/examples/example1.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/examples/example2.log +0 -0
- {tradx-0.6 → tradx-0.7.1}/examples/example2.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/__init__.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/algoContainer.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/candleData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/cmInstrument.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/futureInstrument.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/index.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/instrumentPropertyChangeData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/ltpData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/ltpPartialData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/marketDepthData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/marketStatusData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/openInterestData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/openInterestPartialData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/optionsInstrument.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/order.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/orderEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/position.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/positionEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/touchLineData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/touchLinePartialData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/tradeConversionEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/baseClass/tradeEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/constants/holidays.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/dualHashMap.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/interactiveEngine.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/logger/logger.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/logger/logger2.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/src/tradx/py.typed +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_candleData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_interactiveEngine.log +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_interactiveEngine.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_logger.log +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_logger.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_ltpData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_ltpPartailData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_marketDataEngine.log +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_marketDataEngine.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_marketDepthData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_marketStatusData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_openInterestData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_openInterestPartialData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_option.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_orderEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_positionEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_touchLineData.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_tradeConversionEvent.py +0 -0
- {tradx-0.6 → tradx-0.7.1}/test/test_tradeEvent.py +0 -0
@@ -0,0 +1,495 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
import shortuuid
|
3
|
+
from typing import Any, TYPE_CHECKING, List, Dict
|
4
|
+
from decimal import Decimal
|
5
|
+
from tradx.baseClass.order import Order
|
6
|
+
from tradx.baseClass.position import Position
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from tradx.marketDataEngine import marketDataEngine
|
13
|
+
from tradx.interactiveEngine import interactiveEngine
|
14
|
+
from tradx.baseClass.touchLineData import TouchLineData
|
15
|
+
from tradx.baseClass.orderEvent import OrderEvent
|
16
|
+
from tradx.baseClass.tradeEvent import TradeEvent
|
17
|
+
from tradx.baseClass.candleData import CandleData
|
18
|
+
|
19
|
+
|
20
|
+
class BaseAlgo(ABC):
|
21
|
+
"""
|
22
|
+
BaseAlgo is an abstract base class for algorithmic trading strategies. It provides a framework for handling market data, orders, and trades, and requires subclasses to implement specific methods for processing data and events.
|
23
|
+
Attributes:
|
24
|
+
cache (dict): A cache for storing temporary data.
|
25
|
+
order_nos (ShortUUID): Instance of ShortUUID for generating unique order identifiers.
|
26
|
+
position_diary (List[Position]): A list to store position data.
|
27
|
+
order_diary (List[Order]): A list to store order data.
|
28
|
+
name (str): A unique identifier for the algorithm.
|
29
|
+
Methods:
|
30
|
+
__init__(marketDataEngine, interactiveEngine):
|
31
|
+
Initializes the BaseAlgo class with marketDataEngine and interactiveEngine instances.
|
32
|
+
on_barData(data):
|
33
|
+
Abstract method to process candle data. Must be implemented by subclasses.
|
34
|
+
subscribe():
|
35
|
+
Abstract method to handle subscription logic. Must be implemented by subclasses.
|
36
|
+
order_no():
|
37
|
+
Generates a unique order identifier.
|
38
|
+
order_(order):
|
39
|
+
Handles the order event by updating the status of an existing order or inserting a new order.
|
40
|
+
on_orderEvent(message):
|
41
|
+
Abstract method to handle order events. Must be implemented by subclasses.
|
42
|
+
trade_(trade):
|
43
|
+
Handles a trade event by updating the position diary and triggering the on_tradeEvent callback.
|
44
|
+
on_tradeEvent(message):
|
45
|
+
Abstract method to handle trade events. Must be implemented by subclasses.
|
46
|
+
unsubscribe():
|
47
|
+
Abstract method to handle unsubscription logic. Must be implemented by subclasses.
|
48
|
+
initialize():
|
49
|
+
Abstract method to initialize the strategy. Must be implemented by subclasses.
|
50
|
+
deinitialize():
|
51
|
+
Abstract method to deinitialize the strategy. Must be implemented by subclasses.
|
52
|
+
liquidateIntraday():
|
53
|
+
Class method to liquidate all intraday positions and cancel pending orders.
|
54
|
+
isInvested():
|
55
|
+
Class method to check if there are any investments by summing the quantities of all positions in the position diary.
|
56
|
+
"""
|
57
|
+
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
marketDataEngine: "marketDataEngine",
|
61
|
+
interactiveEngine: "interactiveEngine",
|
62
|
+
):
|
63
|
+
"""
|
64
|
+
Initialize the BaseAlgo class.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
marketDataEngine (marketDataEngine): Instance of marketDataEngine.
|
68
|
+
interactiveEngine (interactiveEngine): Instance of interactiveEngine.
|
69
|
+
"""
|
70
|
+
# Initialize instance variables
|
71
|
+
self.cache = {}
|
72
|
+
self.marketDataEngine = marketDataEngine
|
73
|
+
self.interactiveEngine = interactiveEngine
|
74
|
+
self.order_nos = shortuuid
|
75
|
+
self.position_diary: List[Position] = []
|
76
|
+
self.order_diary: List[Order] = []
|
77
|
+
# Registering inside interactive engine
|
78
|
+
self.name = interactiveEngine.shortuuid.ShortUUID().random(length=4)
|
79
|
+
self.interactiveEngine.strategy_to_id[self.name] = self
|
80
|
+
|
81
|
+
# Logging the initialization
|
82
|
+
if self.marketDataEngine.user_logger:
|
83
|
+
self.marketDataEngine.user_logger.info(
|
84
|
+
f"Algorithm initialized with ID: {self.name}",
|
85
|
+
caller=f"{self.__class__.__name__}.__init__",
|
86
|
+
)
|
87
|
+
|
88
|
+
@abstractmethod
|
89
|
+
async def on_barData(self, data: "CandleData") -> None:
|
90
|
+
"""
|
91
|
+
Abstract method (virtual function) to process candle data.
|
92
|
+
Must be implemented by subclasses.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
data (any): The input data to process.
|
96
|
+
"""
|
97
|
+
...
|
98
|
+
|
99
|
+
@abstractmethod
|
100
|
+
async def on_touchLineData(self, data: "TouchLineData") -> None:
|
101
|
+
"""
|
102
|
+
Asynchronous method to handle touch line data.
|
103
|
+
Args:
|
104
|
+
data (TouchLineData): The touch line data to be processed.
|
105
|
+
Returns:
|
106
|
+
None
|
107
|
+
"""
|
108
|
+
|
109
|
+
...
|
110
|
+
|
111
|
+
@abstractmethod
|
112
|
+
async def subscribe(self) -> None:
|
113
|
+
"""
|
114
|
+
Abstract method (virtual function) to process subscribe.
|
115
|
+
Must be implemented by subclasses.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
data (any): The input data to process.
|
119
|
+
"""
|
120
|
+
...
|
121
|
+
|
122
|
+
def order_no(self) -> str:
|
123
|
+
"""
|
124
|
+
Generate a unique order identifier.
|
125
|
+
The identifier is an 8-digit string composed of a 4-digit algorithm ID (derived from `self.name`)
|
126
|
+
and a 4-digit random sequence generated using the `ShortUUID` library.
|
127
|
+
Returns:
|
128
|
+
str: An 8-digit unique order identifier.
|
129
|
+
"""
|
130
|
+
return f"{self.name}{self.order_nos.ShortUUID().random(length=4)}"
|
131
|
+
|
132
|
+
async def order_(self, order: "OrderEvent") -> None:
|
133
|
+
"""
|
134
|
+
Handles the order event by either updating the status of an existing order
|
135
|
+
or inserting a new order into the order diary.
|
136
|
+
Args:
|
137
|
+
order (OrderEvent): The order event containing order details.
|
138
|
+
Returns:
|
139
|
+
None
|
140
|
+
"""
|
141
|
+
|
142
|
+
existing_order = next(
|
143
|
+
(
|
144
|
+
O
|
145
|
+
for O in self.order_diary
|
146
|
+
if O.OrderUniqueIdentifier == order.OrderUniqueIdentifier
|
147
|
+
),
|
148
|
+
None,
|
149
|
+
)
|
150
|
+
|
151
|
+
if existing_order:
|
152
|
+
# Update the status of the existing order
|
153
|
+
existing_order.OrderStatus = order.OrderStatus
|
154
|
+
if self.interactiveEngine.user_logger:
|
155
|
+
self.interactiveEngine.user_logger.info(
|
156
|
+
f"Updated order {order.OrderUniqueIdentifier} status to {order.OrderStatus}",
|
157
|
+
caller=f"{self.__class__.__name__}.order_",
|
158
|
+
)
|
159
|
+
else:
|
160
|
+
# Insert the new order by creating an object
|
161
|
+
new_order = Order(
|
162
|
+
order.OrderUniqueIdentifier,
|
163
|
+
order.AppOrderID,
|
164
|
+
order.ProductType,
|
165
|
+
order.OrderType,
|
166
|
+
order.OrderQuantity,
|
167
|
+
order.OrderDisclosedQuantity,
|
168
|
+
order.OrderPrice,
|
169
|
+
order.OrderStopPrice,
|
170
|
+
order.OrderSide,
|
171
|
+
order.TimeInForce,
|
172
|
+
order.OrderStatus,
|
173
|
+
)
|
174
|
+
self.order_diary.append(new_order)
|
175
|
+
if self.interactiveEngine.user_logger:
|
176
|
+
self.interactiveEngine.user_logger.info(
|
177
|
+
f"Inserted new order {order.OrderUniqueIdentifier} with status {order.OrderStatus}",
|
178
|
+
caller=f"{self.__class__.__name__}.order_",
|
179
|
+
)
|
180
|
+
asyncio.ensure_future(self.on_orderEvent(order))
|
181
|
+
|
182
|
+
@abstractmethod
|
183
|
+
async def on_orderEvent(self, message: "OrderEvent") -> None:
|
184
|
+
"""
|
185
|
+
On Order Event for strategy.
|
186
|
+
"""
|
187
|
+
...
|
188
|
+
|
189
|
+
async def trade_(self, trade: "TradeEvent") -> None:
|
190
|
+
"""
|
191
|
+
Handles a trade event by updating the position diary and triggering the on_tradeEvent callback.
|
192
|
+
Args:
|
193
|
+
trade (TradeEvent): The trade event to be processed.
|
194
|
+
Returns:
|
195
|
+
None
|
196
|
+
The method performs the following steps:
|
197
|
+
1. Checks if the trade exists in the position diary.
|
198
|
+
2. Updates the position if it exists, otherwise inserts a new position.
|
199
|
+
3. Logs the update or insertion of the position.
|
200
|
+
4. Ensures the on_tradeEvent callback is called asynchronously.
|
201
|
+
"""
|
202
|
+
if trade.OrderStatus != "Filled":
|
203
|
+
return
|
204
|
+
# Check if the trade exists in the position diary
|
205
|
+
existing_position = next(
|
206
|
+
(
|
207
|
+
_
|
208
|
+
for _ in self.position_diary
|
209
|
+
if _.ExchangeSegment == trade.ExchangeSegment
|
210
|
+
and _.ExchangeInstrumentID == trade.ExchangeInstrumentID
|
211
|
+
and _.ProductType == trade.ProductType
|
212
|
+
),
|
213
|
+
None,
|
214
|
+
)
|
215
|
+
# Update the position
|
216
|
+
_quantity = (1 if trade.OrderSide == "Buy" else -1) * trade.OrderQuantity
|
217
|
+
if existing_position:
|
218
|
+
existing_position.Quantity += _quantity
|
219
|
+
if self.interactiveEngine.user_logger:
|
220
|
+
self.interactiveEngine.user_logger.info(
|
221
|
+
f"Updated position for trade {trade.ExchangeInstrumentID}: {existing_position.Quantity}",
|
222
|
+
caller=f"{self.__class__.__name__}.trade_",
|
223
|
+
)
|
224
|
+
else:
|
225
|
+
new_position = Position(
|
226
|
+
trade.ExchangeSegment,
|
227
|
+
trade.ExchangeInstrumentID,
|
228
|
+
trade.ProductType,
|
229
|
+
_quantity,
|
230
|
+
)
|
231
|
+
self.position_diary.append(new_position)
|
232
|
+
if self.interactiveEngine.user_logger:
|
233
|
+
self.interactiveEngine.user_logger.info(
|
234
|
+
f"Inserted new position {trade.ExchangeInstrumentID} with quantity {new_position.Quantity}",
|
235
|
+
caller=f"{self.__class__.__name__}.trade_",
|
236
|
+
)
|
237
|
+
asyncio.ensure_future(self.on_tradeEvent(trade))
|
238
|
+
|
239
|
+
@abstractmethod
|
240
|
+
def on_tradeEvent(self, message: "TradeEvent") -> None:
|
241
|
+
"""
|
242
|
+
On trade Event for strategy.
|
243
|
+
"""
|
244
|
+
...
|
245
|
+
|
246
|
+
@abstractmethod
|
247
|
+
async def unsubscribe(self) -> None:
|
248
|
+
"""
|
249
|
+
Abstract method (virtual function) to handle unsubscription logic.
|
250
|
+
Must be implemented by subclasses.
|
251
|
+
"""
|
252
|
+
...
|
253
|
+
|
254
|
+
@abstractmethod
|
255
|
+
async def initialize(self) -> None:
|
256
|
+
"""
|
257
|
+
Initialize the strategy.
|
258
|
+
This function is intended to be a one-time asynchronous setup function for the strategy.
|
259
|
+
It can be used to perform any necessary initialization tasks, such as scheduling jobs with APScheduler.
|
260
|
+
"""
|
261
|
+
# Perform initialization tasks here
|
262
|
+
...
|
263
|
+
|
264
|
+
@abstractmethod
|
265
|
+
async def deinitialize(self) -> None:
|
266
|
+
"""
|
267
|
+
Deinitialize the strategy.
|
268
|
+
This function is intended to be a one-time asynchronous teardown function for the strategy.
|
269
|
+
It can be used to perform any necessary cleanup tasks, such as unscheduling jobs with APScheduler.
|
270
|
+
"""
|
271
|
+
# Perform deinitialization tasks here
|
272
|
+
...
|
273
|
+
|
274
|
+
async def liquidateIntraday(self) -> None:
|
275
|
+
"""
|
276
|
+
Asynchronously liquidates all intraday positions and cancels pending orders.
|
277
|
+
This method iterates through the order diary and cancels any orders that are
|
278
|
+
in the "PendingNew" or "New" status. It also iterates through the position
|
279
|
+
diary and places market orders to liquidate any positions with a non-zero
|
280
|
+
quantity.
|
281
|
+
Returns:
|
282
|
+
None
|
283
|
+
"""
|
284
|
+
if self.interactiveEngine.user_logger:
|
285
|
+
self.interactiveEngine.user_logger.info(
|
286
|
+
f"Cancel open order and square off position for strategy {self.name}",
|
287
|
+
caller=f"{self.__class__.__name__}.liquidateIntraday",
|
288
|
+
)
|
289
|
+
|
290
|
+
for order in self.order_diary:
|
291
|
+
if order.OrderStatus in ["PendingNew", "New"]:
|
292
|
+
asyncio.ensure_future(
|
293
|
+
self.interactiveEngine.cancel_order(
|
294
|
+
order.AppOrderID, order.OrderUniqueIdentifier
|
295
|
+
)
|
296
|
+
)
|
297
|
+
for position in self.position_diary:
|
298
|
+
if position.Quantity != 0:
|
299
|
+
_order_no = self.order_no()
|
300
|
+
asyncio.ensure_future(
|
301
|
+
self.interactiveEngine.market_order(
|
302
|
+
position.ExchangeSegment,
|
303
|
+
position.ExchangeInstrumentID,
|
304
|
+
position.ProductType,
|
305
|
+
-1 * position.Quantity,
|
306
|
+
_order_no,
|
307
|
+
)
|
308
|
+
)
|
309
|
+
|
310
|
+
def isInvested(self) -> bool:
|
311
|
+
"""
|
312
|
+
Asynchronously checks if there are any investments by summing the quantities
|
313
|
+
of all positions in the position diary.
|
314
|
+
Returns:
|
315
|
+
bool: True if the total quantity is not zero, indicating that there are
|
316
|
+
investments. False otherwise.
|
317
|
+
"""
|
318
|
+
|
319
|
+
qty = 0
|
320
|
+
for position in self.position_diary:
|
321
|
+
qty += abs(position.Quantity)
|
322
|
+
return qty != 0
|
323
|
+
|
324
|
+
async def liquidateIntradayDummy(self) -> None:
|
325
|
+
|
326
|
+
if self.interactiveEngine.user_logger:
|
327
|
+
self.interactiveEngine.user_logger.info(
|
328
|
+
f"Cancel open order and square off position for strategy {self.name}",
|
329
|
+
caller=f"{self.__class__.__name__}.liquidateIntraday",
|
330
|
+
)
|
331
|
+
|
332
|
+
# for order in self.order_diary:
|
333
|
+
# if order.OrderStatus in ["PendingNew", "New"]:
|
334
|
+
# asyncio.ensure_future(
|
335
|
+
# self.interactiveEngine.cancel_order(
|
336
|
+
# order.AppOrderID, order.OrderUniqueIdentifier
|
337
|
+
# )
|
338
|
+
# )
|
339
|
+
for position in self.position_diary:
|
340
|
+
if position.Quantity != 0:
|
341
|
+
_order_no = self.order_no()
|
342
|
+
asyncio.ensure_future(
|
343
|
+
self.marketDataEngine.dummy_order(
|
344
|
+
position.ExchangeSegment,
|
345
|
+
position.ExchangeInstrumentID,
|
346
|
+
position.ProductType,
|
347
|
+
-1 * position.Quantity,
|
348
|
+
_order_no,
|
349
|
+
self,
|
350
|
+
)
|
351
|
+
)
|
352
|
+
|
353
|
+
async def safe_market_order(
|
354
|
+
self, ExchangeSegment, ExchangeInstrumentID, ProductType, Quantity, InitialBid, InitialAsk
|
355
|
+
):
|
356
|
+
"""
|
357
|
+
Places a safe market order by continuously modifying the order until it is filled.
|
358
|
+
Args:
|
359
|
+
ExchangeSegment (str): The segment of the exchange.
|
360
|
+
ExchangeInstrumentID (int): The instrument ID of the exchange.
|
361
|
+
ProductType (str): The type of the product.
|
362
|
+
Quantity (int): The quantity to be ordered. Positive for buy, negative for sell.
|
363
|
+
Returns:
|
364
|
+
None
|
365
|
+
Raises:
|
366
|
+
Exception: If there is an issue with fetching market data or placing/modifying the order.
|
367
|
+
This method performs the following steps:
|
368
|
+
1. Generates a unique identifier for the order.
|
369
|
+
2. Fetches the latest market data for the given instrument.
|
370
|
+
3. Places a limit order with a slight price adjustment.
|
371
|
+
4. Continuously checks the order status until it is filled.
|
372
|
+
5. If the order is not filled, modifies the order with updated market data.
|
373
|
+
"""
|
374
|
+
OrderUniqueIdentifier = self.order_no()
|
375
|
+
_adjust = Decimal("0.05")
|
376
|
+
Data: TouchLineData = (
|
377
|
+
await self.marketDataEngine.fetch_ltp(
|
378
|
+
[
|
379
|
+
{
|
380
|
+
"exchangeSegment": ExchangeSegment,
|
381
|
+
"exchangeInstrumentID": ExchangeInstrumentID,
|
382
|
+
}
|
383
|
+
]
|
384
|
+
)
|
385
|
+
)[0]
|
386
|
+
price = (Data.AskInfo.Price + _adjust).to_eng_string() if Quantity > 0 else (Data.BidInfo.Price - _adjust).to_eng_string()
|
387
|
+
await self.interactiveEngine.limit_order(
|
388
|
+
ExchangeSegment,
|
389
|
+
ExchangeInstrumentID,
|
390
|
+
ProductType,
|
391
|
+
Quantity,
|
392
|
+
price,
|
393
|
+
OrderUniqueIdentifier,
|
394
|
+
)
|
395
|
+
if self.interactiveEngine.user_logger:
|
396
|
+
self.interactiveEngine.user_logger.info(
|
397
|
+
f"Placing Limit Order for {ExchangeInstrumentID} with Quantity {Quantity} with Price {price} and Initial Bid {InitialBid} and Initial Ask {InitialAsk}",
|
398
|
+
caller=f"{self.__class__.__name__}.safe_market_order",
|
399
|
+
)
|
400
|
+
await asyncio.sleep(0.1)
|
401
|
+
order = next(
|
402
|
+
(
|
403
|
+
O
|
404
|
+
for O in self.order_diary
|
405
|
+
if O.OrderUniqueIdentifier == OrderUniqueIdentifier
|
406
|
+
),
|
407
|
+
None,
|
408
|
+
)
|
409
|
+
while order is None or (order is not None and order.OrderStatus != "Filled"):
|
410
|
+
await asyncio.sleep(0.1)
|
411
|
+
order = next(
|
412
|
+
(
|
413
|
+
O
|
414
|
+
for O in self.order_diary
|
415
|
+
if O.OrderUniqueIdentifier == OrderUniqueIdentifier
|
416
|
+
),
|
417
|
+
None,
|
418
|
+
)
|
419
|
+
if order is not None and order.OrderStatus == "Rejected":
|
420
|
+
if self.interactiveEngine.user_logger:
|
421
|
+
self.interactiveEngine.user_logger.info(
|
422
|
+
f"Order Rejected for {ExchangeInstrumentID} with Quantity {Quantity}",
|
423
|
+
caller=f"{self.__class__.__name__}.safe_market_order",
|
424
|
+
)
|
425
|
+
break
|
426
|
+
if order is not None and order.OrderStatus != "Filled":
|
427
|
+
Data: TouchLineData = (
|
428
|
+
await self.marketDataEngine.fetch_ltp(
|
429
|
+
[
|
430
|
+
{
|
431
|
+
"exchangeSegment": ExchangeSegment,
|
432
|
+
"exchangeInstrumentID": ExchangeInstrumentID,
|
433
|
+
}
|
434
|
+
]
|
435
|
+
)
|
436
|
+
)[0]
|
437
|
+
|
438
|
+
await self.interactiveEngine.xt.modify_order(
|
439
|
+
order.AppOrderID,
|
440
|
+
order.ProductType,
|
441
|
+
order.OrderType,
|
442
|
+
order.OrderQuantity,
|
443
|
+
order.OrderDisclosedQuantity,
|
444
|
+
(
|
445
|
+
(Data.AskInfo.Price + _adjust).to_eng_string()
|
446
|
+
if Quantity > 0
|
447
|
+
else (Data.BidInfo.Price - _adjust).to_eng_string()
|
448
|
+
),
|
449
|
+
order.OrderStopPrice.to_eng_string(),
|
450
|
+
order.TimeInForce,
|
451
|
+
order.OrderUniqueIdentifier,
|
452
|
+
"*****",
|
453
|
+
)
|
454
|
+
if self.interactiveEngine.user_logger:
|
455
|
+
self.interactiveEngine.user_logger.info(
|
456
|
+
f"Modifying Order for {ExchangeInstrumentID} with Quantity {Quantity} and Price "
|
457
|
+
f"{(Data.AskInfo.Price + _adjust).to_eng_string() if Quantity > 0 else (Data.BidInfo.Price - _adjust).to_eng_string()}",
|
458
|
+
caller=f"{self.__class__.__name__}.safe_market_order",
|
459
|
+
)
|
460
|
+
await asyncio.sleep(0.7)
|
461
|
+
|
462
|
+
async def safeLiquidateIntraday(self) -> None:
|
463
|
+
"""
|
464
|
+
Asynchronously liquidates all intraday positions and cancels all open orders.
|
465
|
+
This method performs the following actions:
|
466
|
+
1. Logs the action of canceling open orders and squaring off positions if a user logger is available.
|
467
|
+
2. Iterates through the order diary and cancels any orders with a status of "PendingNew" or "New".
|
468
|
+
3. Iterates through the position diary and places market orders to square off any positions with a non-zero quantity.
|
469
|
+
Returns:
|
470
|
+
None
|
471
|
+
"""
|
472
|
+
|
473
|
+
if self.interactiveEngine.user_logger:
|
474
|
+
self.interactiveEngine.user_logger.info(
|
475
|
+
f"Cancel open order and square off position for strategy {self.name}",
|
476
|
+
caller=f"{self.__class__.__name__}.safeLiquidateIntraday",
|
477
|
+
)
|
478
|
+
|
479
|
+
for order in self.order_diary:
|
480
|
+
if order.OrderStatus in ["PendingNew", "New"]:
|
481
|
+
asyncio.ensure_future(
|
482
|
+
self.interactiveEngine.cancel_order(
|
483
|
+
order.AppOrderID, order.OrderUniqueIdentifier
|
484
|
+
)
|
485
|
+
)
|
486
|
+
for position in self.position_diary:
|
487
|
+
if position.Quantity != 0:
|
488
|
+
asyncio.ensure_future(
|
489
|
+
self.safe_market_order(
|
490
|
+
position.ExchangeSegment,
|
491
|
+
position.ExchangeInstrumentID,
|
492
|
+
position.ProductType,
|
493
|
+
-1 * position.Quantity,
|
494
|
+
)
|
495
|
+
)
|
@@ -122,7 +122,8 @@ class marketDataEngine(MarketDataSocketClient):
|
|
122
122
|
|
123
123
|
async def on_event_market_status_full(self, data):
|
124
124
|
"""On receiving message code 1507:Market Status full"""
|
125
|
-
__ = MarketStatusData(data)
|
125
|
+
# __ = MarketStatusData(data)
|
126
|
+
_ = data
|
126
127
|
if self.user_logger:
|
127
128
|
self.user_logger.info(
|
128
129
|
f"1507:Market Status full;{__}",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|