quantplay 2.0.36__tar.gz → 2.0.38__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.
- {quantplay-2.0.36 → quantplay-2.0.38}/PKG-INFO +1 -1
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/aliceblue.py +100 -72
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/angelone.py +54 -53
- quantplay-2.0.38/quantplay/broker/dhan.py +477 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/finvasia_utils/fa_noren.py +104 -69
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/five_paisa.py +53 -33
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/ft_utils/ft_noren.py +104 -69
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/generics/broker.py +146 -318
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/iifl_xts.py +2 -2
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/motilal.py +33 -101
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/noren.py +39 -28
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/upstox.py +80 -58
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/xts.py +61 -46
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/xts_utils/Connect.py +96 -94
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/zerodha.py +86 -179
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/model/broker.py +3 -13
- quantplay-2.0.38/quantplay/model/generics.py +78 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/model/instrument_data.py +2 -2
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay.egg-info/PKG-INFO +1 -1
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay.egg-info/SOURCES.txt +1 -1
- {quantplay-2.0.36 → quantplay-2.0.38}/setup.py +1 -1
- quantplay-2.0.36/quantplay/model/generics.py +0 -8
- quantplay-2.0.36/quantplay/model/xts.py +0 -7
- {quantplay-2.0.36 → quantplay-2.0.38}/README.md +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/pyproject.toml +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/auto_login/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/auto_login/aliceblue.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/finvasia_utils/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/flattrade.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/ft_utils/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/ft_utils/flattrade_utils.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/generics/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/kite_utils.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/shoonya.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/uplink/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/uplink/uplink_utils.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/xts_utils/Exception.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/broker/xts_utils/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/exception/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/exception/exceptions.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/model/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/model/order_event.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/py.typed +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/utils/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/utils/constant.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/utils/exchange.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/utils/number_utils.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/utils/pickle_utils.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/utils/selenium_utils.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/wrapper/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/wrapper/aws/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay/wrapper/aws/s3.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay.egg-info/dependency_links.txt +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay.egg-info/requires.txt +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/quantplay.egg-info/top_level.txt +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/setup.cfg +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/tests/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/tests/conftest.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/tests/wrapper/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/tests/wrapper/aws/__init__.py +0 -0
- {quantplay-2.0.36 → quantplay-2.0.38}/tests/wrapper/aws/s3_test.py +0 -0
|
@@ -3,13 +3,16 @@ import copy
|
|
|
3
3
|
import pickle
|
|
4
4
|
from queue import Queue
|
|
5
5
|
import traceback
|
|
6
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Dict, Literal
|
|
7
7
|
|
|
8
8
|
import polars as pl
|
|
9
|
-
from pya3 import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
from pya3 import (
|
|
10
|
+
Aliceblue as Alice,
|
|
11
|
+
OrderType as AliceOrderType,
|
|
12
|
+
TransactionType as AliceTransactionType,
|
|
13
|
+
ProductType as AliceProductType,
|
|
14
|
+
)
|
|
15
|
+
from retrying import retry # type: ignore
|
|
13
16
|
|
|
14
17
|
from quantplay.broker.generics.broker import Broker
|
|
15
18
|
from quantplay.exception.exceptions import (
|
|
@@ -20,7 +23,12 @@ from quantplay.exception.exceptions import (
|
|
|
20
23
|
retry_exception,
|
|
21
24
|
)
|
|
22
25
|
from quantplay.model.broker import ModifyOrderRequest, UserBrokerProfileResponse
|
|
23
|
-
from quantplay.model.generics import
|
|
26
|
+
from quantplay.model.generics import (
|
|
27
|
+
ExchangeType,
|
|
28
|
+
OrderTypeType,
|
|
29
|
+
ProductType,
|
|
30
|
+
TransactionType,
|
|
31
|
+
)
|
|
24
32
|
from quantplay.model.order_event import OrderUpdateEvent
|
|
25
33
|
from quantplay.utils.constant import Constants, OrderType
|
|
26
34
|
from quantplay.utils.pickle_utils import InstrumentData
|
|
@@ -41,7 +49,7 @@ class Aliceblue(Broker):
|
|
|
41
49
|
user_id: str | None = None,
|
|
42
50
|
api_key: str | None = None,
|
|
43
51
|
order_updates: Queue[OrderUpdateEvent] | None = None,
|
|
44
|
-
client=None,
|
|
52
|
+
client: str | None = None,
|
|
45
53
|
):
|
|
46
54
|
super(Aliceblue, self).__init__()
|
|
47
55
|
self.order_updates = order_updates
|
|
@@ -60,37 +68,36 @@ class Aliceblue(Broker):
|
|
|
60
68
|
)
|
|
61
69
|
response = self.alice.get_session_id()
|
|
62
70
|
|
|
63
|
-
if
|
|
71
|
+
if response.get("sessionID") is None:
|
|
64
72
|
if "emsg" in response:
|
|
65
73
|
if response["emsg"].lower() == "invalid input":
|
|
66
74
|
response["emsg"] = "Invalid broker credentials"
|
|
67
75
|
raise InvalidArgumentException(response["emsg"])
|
|
68
76
|
raise InvalidArgumentException(f"Invalid API Key {api_key}")
|
|
77
|
+
|
|
69
78
|
except (InvalidArgumentException, TokenException):
|
|
70
79
|
raise
|
|
80
|
+
|
|
71
81
|
except Exception as e:
|
|
72
82
|
raise RetryableException(str(e))
|
|
83
|
+
|
|
73
84
|
self.user_id = self.alice.user_id
|
|
74
85
|
self.load_instrument()
|
|
75
86
|
|
|
76
|
-
def set_client(self, serialized_client):
|
|
87
|
+
def set_client(self, serialized_client: str):
|
|
77
88
|
try:
|
|
78
|
-
self.alice = pickle.loads(
|
|
89
|
+
self.alice: Alice = pickle.loads(
|
|
90
|
+
codecs.decode(serialized_client.encode(), "base64")
|
|
91
|
+
)
|
|
79
92
|
except Exception:
|
|
80
93
|
raise TokenException("Session expired")
|
|
81
94
|
|
|
82
|
-
def
|
|
83
|
-
self.email = response["email"]
|
|
84
|
-
self.user_id = response["actid"]
|
|
85
|
-
self.full_name = response["uname"]
|
|
86
|
-
self.user_token = response["susertoken"]
|
|
87
|
-
|
|
88
|
-
def get_quantplay_symbol(self, symbol):
|
|
95
|
+
def get_quantplay_symbol(self, symbol: str):
|
|
89
96
|
if "-EQ" in symbol:
|
|
90
97
|
return symbol.replace("-EQ", "")
|
|
91
98
|
return symbol
|
|
92
99
|
|
|
93
|
-
def get_symbol(self, symbol, exchange=None):
|
|
100
|
+
def get_symbol(self, symbol: str, exchange: ExchangeType | None = None):
|
|
94
101
|
if exchange == "NSE":
|
|
95
102
|
if "-EQ" not in symbol:
|
|
96
103
|
return f"{symbol}-EQ"
|
|
@@ -118,55 +125,63 @@ class Aliceblue(Broker):
|
|
|
118
125
|
|
|
119
126
|
def get_transaction_type(
|
|
120
127
|
self,
|
|
121
|
-
transaction_type: Literal[
|
|
128
|
+
transaction_type: Literal[
|
|
129
|
+
"BUY", "SELL", "B", "S", AliceTransactionType.Buy, AliceTransactionType.Sell
|
|
130
|
+
],
|
|
122
131
|
):
|
|
123
132
|
if (
|
|
124
133
|
transaction_type == "BUY"
|
|
125
|
-
or transaction_type ==
|
|
134
|
+
or transaction_type == AliceTransactionType.Buy
|
|
126
135
|
or transaction_type == "B"
|
|
127
136
|
):
|
|
128
|
-
return
|
|
137
|
+
return AliceTransactionType.Buy
|
|
129
138
|
elif (
|
|
130
139
|
transaction_type == "SELL"
|
|
131
|
-
or transaction_type ==
|
|
140
|
+
or transaction_type == AliceTransactionType.Sell
|
|
132
141
|
or transaction_type == "S"
|
|
133
142
|
):
|
|
134
|
-
return
|
|
143
|
+
return AliceTransactionType.Sell
|
|
135
144
|
|
|
136
145
|
raise InvalidArgumentException(
|
|
137
146
|
f"transaction type {transaction_type} not supported for trading"
|
|
138
147
|
)
|
|
139
148
|
|
|
140
|
-
def get_order_type(
|
|
141
|
-
|
|
149
|
+
def get_order_type(
|
|
150
|
+
self,
|
|
151
|
+
order_type: (
|
|
152
|
+
OrderTypeType
|
|
153
|
+
| Literal[
|
|
154
|
+
AliceOrderType.Market,
|
|
155
|
+
AliceOrderType.StopLossLimit,
|
|
156
|
+
AliceOrderType.StopLossMarket,
|
|
157
|
+
AliceOrderType.Limit,
|
|
158
|
+
]
|
|
159
|
+
),
|
|
160
|
+
):
|
|
161
|
+
if order_type == OrderType.market or order_type == AliceOrderType.Market:
|
|
142
162
|
return AliceOrderType.Market
|
|
143
|
-
elif
|
|
144
|
-
order_type == OrderType.sl or order_type == AliceOrderType.StopLossLimit.value
|
|
145
|
-
):
|
|
163
|
+
elif order_type == OrderType.sl or order_type == AliceOrderType.StopLossLimit:
|
|
146
164
|
return AliceOrderType.StopLossLimit
|
|
147
|
-
elif
|
|
148
|
-
order_type == OrderType.slm
|
|
149
|
-
or order_type == AliceOrderType.StopLossMarket.value
|
|
150
|
-
):
|
|
165
|
+
elif order_type == OrderType.slm or order_type == AliceOrderType.StopLossMarket:
|
|
151
166
|
return AliceOrderType.StopLossMarket
|
|
152
|
-
elif order_type == OrderType.limit or order_type == AliceOrderType.Limit
|
|
167
|
+
elif order_type == OrderType.limit or order_type == AliceOrderType.Limit:
|
|
153
168
|
return AliceOrderType.Limit
|
|
154
169
|
|
|
155
170
|
return order_type
|
|
156
171
|
|
|
157
|
-
def get_product(self, product):
|
|
172
|
+
def get_product(self, product: ProductType):
|
|
158
173
|
if product == "NRML":
|
|
159
|
-
return
|
|
174
|
+
return AliceProductType.Normal
|
|
160
175
|
elif product == "CNC":
|
|
161
|
-
return
|
|
176
|
+
return AliceProductType.Delivery
|
|
162
177
|
elif product == "MIS":
|
|
163
|
-
return
|
|
178
|
+
return AliceProductType.Intraday
|
|
164
179
|
elif product in [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
180
|
+
AliceProductType.BracketOrder,
|
|
181
|
+
AliceProductType.CoverOrder,
|
|
182
|
+
AliceProductType.Delivery,
|
|
183
|
+
AliceProductType.Normal,
|
|
184
|
+
AliceProductType.Intraday,
|
|
170
185
|
]:
|
|
171
186
|
return product
|
|
172
187
|
|
|
@@ -174,15 +189,15 @@ class Aliceblue(Broker):
|
|
|
174
189
|
|
|
175
190
|
def place_order(
|
|
176
191
|
self,
|
|
177
|
-
tradingsymbol
|
|
178
|
-
exchange
|
|
179
|
-
quantity
|
|
180
|
-
order_type
|
|
181
|
-
transaction_type
|
|
182
|
-
tag
|
|
183
|
-
product
|
|
184
|
-
price
|
|
185
|
-
trigger_price=None,
|
|
192
|
+
tradingsymbol: str,
|
|
193
|
+
exchange: ExchangeType,
|
|
194
|
+
quantity: int,
|
|
195
|
+
order_type: OrderTypeType,
|
|
196
|
+
transaction_type: TransactionType,
|
|
197
|
+
tag: str | None,
|
|
198
|
+
product: ProductType,
|
|
199
|
+
price: float,
|
|
200
|
+
trigger_price: float | None = None,
|
|
186
201
|
):
|
|
187
202
|
try:
|
|
188
203
|
if trigger_price == 0:
|
|
@@ -190,8 +205,8 @@ class Aliceblue(Broker):
|
|
|
190
205
|
if trigger_price is not None:
|
|
191
206
|
trigger_price = float(trigger_price)
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
aliceblue_order_type = self.get_order_type(order_type)
|
|
209
|
+
aliceblue_product = self.get_product(product)
|
|
195
210
|
tradingsymbol = self.get_symbol(tradingsymbol)
|
|
196
211
|
|
|
197
212
|
instrument = self.alice.get_instrument_by_symbol(exchange, tradingsymbol)
|
|
@@ -199,9 +214,9 @@ class Aliceblue(Broker):
|
|
|
199
214
|
response = self.invoke_aliceblue_api(
|
|
200
215
|
self.alice.place_order,
|
|
201
216
|
transaction_type=self.get_transaction_type(transaction_type),
|
|
202
|
-
product_type=
|
|
217
|
+
product_type=aliceblue_product,
|
|
203
218
|
instrument=instrument,
|
|
204
|
-
order_type=
|
|
219
|
+
order_type=aliceblue_order_type,
|
|
205
220
|
quantity=quantity,
|
|
206
221
|
price=float(price),
|
|
207
222
|
trigger_price=trigger_price,
|
|
@@ -237,12 +252,13 @@ class Aliceblue(Broker):
|
|
|
237
252
|
wait_exponential_max=10000,
|
|
238
253
|
stop_max_attempt_number=3,
|
|
239
254
|
)
|
|
240
|
-
def modify_order(self,
|
|
241
|
-
data = copy.deepcopy(
|
|
255
|
+
def modify_order(self, order: ModifyOrderRequest) -> str:
|
|
256
|
+
data = copy.deepcopy(order)
|
|
242
257
|
order_id = data["order_id"]
|
|
243
258
|
try:
|
|
244
259
|
order_history = self.invoke_aliceblue_api(
|
|
245
|
-
self.alice.get_order_history,
|
|
260
|
+
self.alice.get_order_history,
|
|
261
|
+
nextorder=order_id,
|
|
246
262
|
)
|
|
247
263
|
|
|
248
264
|
# TODO: None,str Raise
|
|
@@ -254,7 +270,12 @@ class Aliceblue(Broker):
|
|
|
254
270
|
order_type = order_history["Prctype"]
|
|
255
271
|
if "order_type" in data:
|
|
256
272
|
order_type = data["order_type"]
|
|
257
|
-
|
|
273
|
+
|
|
274
|
+
aliceblue_order_type = (
|
|
275
|
+
self.get_order_type(order_type)
|
|
276
|
+
if order_type
|
|
277
|
+
else order_history["Prctype"]
|
|
278
|
+
)
|
|
258
279
|
|
|
259
280
|
transaction_type = order_history["Trantype"]
|
|
260
281
|
if "transaction_type" in data:
|
|
@@ -265,17 +286,17 @@ class Aliceblue(Broker):
|
|
|
265
286
|
if "trigger_price" in data and data["trigger_price"] is not None:
|
|
266
287
|
trigger_price = float(data["trigger_price"])
|
|
267
288
|
|
|
268
|
-
self.alice.modify_order(
|
|
289
|
+
response = self.alice.modify_order(
|
|
269
290
|
instrument=self.alice.get_instrument_by_token(exchange, token),
|
|
270
291
|
transaction_type=transaction_type,
|
|
271
292
|
order_id=order_id,
|
|
272
293
|
product_type=product_type,
|
|
273
|
-
order_type=
|
|
274
|
-
price=float(data
|
|
275
|
-
trigger_price=trigger_price,
|
|
294
|
+
order_type=aliceblue_order_type,
|
|
295
|
+
price=float(data.get("price", 0)),
|
|
296
|
+
trigger_price=trigger_price,
|
|
276
297
|
quantity=quantity,
|
|
277
298
|
)
|
|
278
|
-
logger.info("[MODIFY_ORDER_RESPONSE] [{order_id}] response [{response}]")
|
|
299
|
+
logger.info(f"[MODIFY_ORDER_RESPONSE] [{order_id}] response [{response}]")
|
|
279
300
|
except Exception as e:
|
|
280
301
|
print(traceback.print_exc())
|
|
281
302
|
Constants.logger.error(
|
|
@@ -283,20 +304,27 @@ class Aliceblue(Broker):
|
|
|
283
304
|
)
|
|
284
305
|
return order_id
|
|
285
306
|
|
|
286
|
-
def modify_price(
|
|
287
|
-
|
|
307
|
+
def modify_price(
|
|
308
|
+
self,
|
|
309
|
+
order_id: str,
|
|
310
|
+
price: float,
|
|
311
|
+
trigger_price: float | None = None,
|
|
312
|
+
order_type: OrderTypeType | None = None,
|
|
313
|
+
):
|
|
314
|
+
data: ModifyOrderRequest = {
|
|
315
|
+
"order_id": order_id,
|
|
316
|
+
"price": price,
|
|
317
|
+
"order_type": order_type,
|
|
318
|
+
}
|
|
288
319
|
|
|
289
|
-
data["order_id"] = order_id
|
|
290
|
-
data["price"] = price
|
|
291
|
-
data["order_type"] = order_type
|
|
292
320
|
if trigger_price is not None and trigger_price > 0:
|
|
293
321
|
data["trigger_price"] = trigger_price
|
|
294
322
|
else:
|
|
295
323
|
data["trigger_price"] = None
|
|
296
324
|
|
|
297
|
-
self.modify_order(data)
|
|
325
|
+
self.modify_order(data)
|
|
298
326
|
|
|
299
|
-
def cancel_order(self, order_id: str, variety=None) -> None:
|
|
327
|
+
def cancel_order(self, order_id: str, variety: str | None = None) -> None:
|
|
300
328
|
self.invoke_aliceblue_api(self.alice.cancel_order, nestordernmbr=order_id)
|
|
301
329
|
|
|
302
330
|
def profile(self):
|
|
@@ -573,7 +601,7 @@ class Aliceblue(Broker):
|
|
|
573
601
|
|
|
574
602
|
return margins
|
|
575
603
|
|
|
576
|
-
def invoke_aliceblue_api(self, fn:
|
|
604
|
+
def invoke_aliceblue_api(self, fn: Any, *args: Any, **kwargs: Any) -> Any:
|
|
577
605
|
try:
|
|
578
606
|
response = fn(*args, **kwargs)
|
|
579
607
|
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import binascii
|
|
2
|
-
import codecs
|
|
3
2
|
import copy
|
|
4
3
|
import json
|
|
5
|
-
import pickle
|
|
6
4
|
from queue import Queue
|
|
7
5
|
import traceback
|
|
8
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Dict
|
|
9
7
|
|
|
10
|
-
from SmartApi.smartExceptions import DataException
|
|
8
|
+
from SmartApi.smartExceptions import DataException # type: ignore
|
|
11
9
|
import polars as pl
|
|
12
10
|
import pyotp
|
|
13
11
|
from requests.exceptions import ConnectionError, ConnectTimeout
|
|
14
12
|
from retrying import retry # type: ignore
|
|
15
|
-
from SmartApi import SmartConnect
|
|
13
|
+
from SmartApi import SmartConnect # type: ignore
|
|
16
14
|
|
|
17
15
|
from quantplay.broker.generics.broker import Broker
|
|
18
16
|
from quantplay.exception.exceptions import (
|
|
@@ -25,6 +23,12 @@ from quantplay.exception.exceptions import (
|
|
|
25
23
|
retry_exception,
|
|
26
24
|
)
|
|
27
25
|
from quantplay.model.broker import ModifyOrderRequest, UserBrokerProfileResponse
|
|
26
|
+
from quantplay.model.generics import (
|
|
27
|
+
ExchangeType,
|
|
28
|
+
OrderTypeType,
|
|
29
|
+
ProductType,
|
|
30
|
+
TransactionType,
|
|
31
|
+
)
|
|
28
32
|
from quantplay.model.order_event import OrderUpdateEvent
|
|
29
33
|
from quantplay.utils.constant import Constants, OrderType
|
|
30
34
|
from quantplay.utils.exchange import Market as MarketConstants
|
|
@@ -71,7 +75,7 @@ class AngelOne(Broker):
|
|
|
71
75
|
raise InvalidArgumentException("TOTP Key is Missing")
|
|
72
76
|
self.wrapper = SmartConnect(api_key=api_key)
|
|
73
77
|
response = self.invoke_angelone_api(
|
|
74
|
-
self.wrapper.generateSession,
|
|
78
|
+
self.wrapper.generateSession, # type: ignore
|
|
75
79
|
clientCode=user_id,
|
|
76
80
|
password=mpin,
|
|
77
81
|
totp=pyotp.TOTP(totp).now(),
|
|
@@ -81,32 +85,27 @@ class AngelOne(Broker):
|
|
|
81
85
|
raise InvalidArgumentException(response["message"])
|
|
82
86
|
raise InvalidArgumentException("Invalid API credentials")
|
|
83
87
|
token_data = self.invoke_angelone_api(
|
|
84
|
-
self.wrapper.generateToken,
|
|
88
|
+
self.wrapper.generateToken, # type: ignore
|
|
89
|
+
refresh_token=self.wrapper.refresh_token, # type: ignore
|
|
85
90
|
)
|
|
86
91
|
self.refresh_token = token_data["data"]["refreshToken"]
|
|
92
|
+
|
|
87
93
|
except InvalidArgumentException:
|
|
88
94
|
raise
|
|
95
|
+
|
|
89
96
|
except binascii.Error:
|
|
90
97
|
raise InvalidArgumentException("Invalid TOTP key provided")
|
|
98
|
+
|
|
91
99
|
except Exception as e:
|
|
92
100
|
print(e)
|
|
93
101
|
raise RetryableException(str(e))
|
|
94
102
|
|
|
95
103
|
self.user_id = user_id
|
|
96
|
-
self.api_key = self.wrapper.api_key
|
|
104
|
+
self.api_key = self.wrapper.api_key # type: ignore
|
|
97
105
|
|
|
98
106
|
if load_instrument:
|
|
99
107
|
self.load_instrument()
|
|
100
108
|
|
|
101
|
-
def set_wrapper(self, serialized_wrapper):
|
|
102
|
-
self.wrapper: SmartConnect = pickle.loads(
|
|
103
|
-
codecs.decode(serialized_wrapper.encode(), "base64")
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def handle_exception(self, response):
|
|
107
|
-
if "errorCode" in response and response["errorCode"] == "AG8001":
|
|
108
|
-
raise TokenException(f"{self.user_id}: Invalid Token")
|
|
109
|
-
|
|
110
109
|
def load_instrument(self, file_name: str | None = None) -> None:
|
|
111
110
|
try:
|
|
112
111
|
instrument_data_instance = InstrumentData.get_instance()
|
|
@@ -124,7 +123,7 @@ class AngelOne(Broker):
|
|
|
124
123
|
|
|
125
124
|
self.initialize_broker_symbol_map()
|
|
126
125
|
|
|
127
|
-
def get_symbol(self, symbol: str, exchange=None):
|
|
126
|
+
def get_symbol(self, symbol: str, exchange: ExchangeType | None = None):
|
|
128
127
|
if exchange == "NSE":
|
|
129
128
|
if symbol in ["NIFTY", "BANKNIFTY"]:
|
|
130
129
|
return symbol
|
|
@@ -139,7 +138,7 @@ class AngelOne(Broker):
|
|
|
139
138
|
return symbol
|
|
140
139
|
return self.quantplay_symbol_map[symbol]
|
|
141
140
|
|
|
142
|
-
def get_order_type(self, order_type):
|
|
141
|
+
def get_order_type(self, order_type: OrderTypeType | None):
|
|
143
142
|
if order_type == OrderType.sl:
|
|
144
143
|
return AngelOne.order_sl
|
|
145
144
|
elif order_type == OrderType.slm:
|
|
@@ -147,7 +146,7 @@ class AngelOne(Broker):
|
|
|
147
146
|
|
|
148
147
|
return order_type
|
|
149
148
|
|
|
150
|
-
def get_product(self, product):
|
|
149
|
+
def get_product(self, product: ProductType):
|
|
151
150
|
if product == "NRML":
|
|
152
151
|
return "CARRYFORWARD"
|
|
153
152
|
elif product == "CNC":
|
|
@@ -159,7 +158,7 @@ class AngelOne(Broker):
|
|
|
159
158
|
|
|
160
159
|
raise InvalidArgumentException(f"Product {product} not supported for trading")
|
|
161
160
|
|
|
162
|
-
def ltp(self, exchange, tradingsymbol: str) -> float:
|
|
161
|
+
def ltp(self, exchange: ExchangeType, tradingsymbol: str) -> float:
|
|
163
162
|
if tradingsymbol in MarketConstants.INDEX_SYMBOL_TO_DERIVATIVE_SYMBOL_MAP:
|
|
164
163
|
tradingsymbol = MarketConstants.INDEX_SYMBOL_TO_DERIVATIVE_SYMBOL_MAP[
|
|
165
164
|
tradingsymbol
|
|
@@ -171,7 +170,7 @@ class AngelOne(Broker):
|
|
|
171
170
|
symboltoken = symbol_data["token"]
|
|
172
171
|
|
|
173
172
|
response = self.invoke_angelone_api(
|
|
174
|
-
self.wrapper.ltpData,
|
|
173
|
+
self.wrapper.ltpData, # type: ignore
|
|
175
174
|
exchange=exchange,
|
|
176
175
|
tradingsymbol=tradingsymbol,
|
|
177
176
|
symboltoken=symboltoken,
|
|
@@ -181,23 +180,23 @@ class AngelOne(Broker):
|
|
|
181
180
|
|
|
182
181
|
def place_order(
|
|
183
182
|
self,
|
|
184
|
-
tradingsymbol
|
|
185
|
-
exchange
|
|
186
|
-
quantity
|
|
187
|
-
order_type
|
|
188
|
-
transaction_type
|
|
189
|
-
tag
|
|
190
|
-
product
|
|
191
|
-
price
|
|
192
|
-
trigger_price=None,
|
|
183
|
+
tradingsymbol: str,
|
|
184
|
+
exchange: ExchangeType,
|
|
185
|
+
quantity: int,
|
|
186
|
+
order_type: OrderTypeType,
|
|
187
|
+
transaction_type: TransactionType,
|
|
188
|
+
tag: str | None,
|
|
189
|
+
product: ProductType,
|
|
190
|
+
price: float,
|
|
191
|
+
trigger_price: float | None = None,
|
|
193
192
|
):
|
|
194
193
|
order = {}
|
|
195
194
|
try:
|
|
196
195
|
if trigger_price == 0:
|
|
197
196
|
trigger_price = None
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
angelone_order_type = self.get_order_type(order_type)
|
|
199
|
+
angelone_product = self.get_product(product)
|
|
201
200
|
tradingsymbol = self.get_symbol(tradingsymbol, exchange=exchange)
|
|
202
201
|
|
|
203
202
|
variety = "NORMAL"
|
|
@@ -211,11 +210,11 @@ class AngelOne(Broker):
|
|
|
211
210
|
"transactiontype": transaction_type,
|
|
212
211
|
"variety": variety,
|
|
213
212
|
"tradingsymbol": tradingsymbol,
|
|
214
|
-
"ordertype":
|
|
213
|
+
"ordertype": angelone_order_type,
|
|
215
214
|
"triggerprice": trigger_price,
|
|
216
215
|
"exchange": exchange,
|
|
217
216
|
"symboltoken": symbol_token,
|
|
218
|
-
"producttype":
|
|
217
|
+
"producttype": angelone_product,
|
|
219
218
|
"price": price,
|
|
220
219
|
"quantity": quantity,
|
|
221
220
|
"duration": "DAY",
|
|
@@ -223,7 +222,7 @@ class AngelOne(Broker):
|
|
|
223
222
|
}
|
|
224
223
|
|
|
225
224
|
Constants.logger.info(f"[PLACING_ORDER] {json.dumps(order)}")
|
|
226
|
-
return self.invoke_angelone_api(self.wrapper.placeOrder, orderparams=order)
|
|
225
|
+
return self.invoke_angelone_api(self.wrapper.placeOrder, orderparams=order) # type: ignore
|
|
227
226
|
|
|
228
227
|
except (TimeoutError, ConnectTimeout):
|
|
229
228
|
Constants.logger.info(f"[ANGELONE_REQUEST_TIMEOUT] {order}")
|
|
@@ -233,7 +232,7 @@ class AngelOne(Broker):
|
|
|
233
232
|
Constants.logger.error(f"[PLACE_ORDER_FAILED] {e} {order}")
|
|
234
233
|
raise QuantplayOrderPlacementException(str(e))
|
|
235
234
|
|
|
236
|
-
def get_variety(self, variety):
|
|
235
|
+
def get_variety(self, variety: str):
|
|
237
236
|
if variety == "regular":
|
|
238
237
|
return "NORMAL"
|
|
239
238
|
return variety
|
|
@@ -243,19 +242,19 @@ class AngelOne(Broker):
|
|
|
243
242
|
wait_exponential_max=10000,
|
|
244
243
|
stop_max_attempt_number=3,
|
|
245
244
|
)
|
|
246
|
-
def modify_order(self,
|
|
247
|
-
data = copy.deepcopy(
|
|
245
|
+
def modify_order(self, order: ModifyOrderRequest) -> str:
|
|
246
|
+
data = copy.deepcopy(order)
|
|
248
247
|
order_id = str(data["order_id"])
|
|
249
248
|
try:
|
|
250
249
|
orders = self.orders()
|
|
251
250
|
filtered_order = orders.filter(pl.col("order_id") == str(data["order_id"]))
|
|
252
|
-
|
|
253
|
-
quantity =
|
|
254
|
-
token =
|
|
255
|
-
exchange =
|
|
256
|
-
product = self.get_product(
|
|
257
|
-
variety =
|
|
258
|
-
order_type = self.get_order_type(data
|
|
251
|
+
order_data = filtered_order.to_dicts()[0]
|
|
252
|
+
quantity = order_data["quantity"]
|
|
253
|
+
token = order_data["token"]
|
|
254
|
+
exchange = order_data["exchange"]
|
|
255
|
+
product = self.get_product(order_data["product"])
|
|
256
|
+
variety = order_data["variety"]
|
|
257
|
+
order_type = self.get_order_type(data.get("order_type"))
|
|
259
258
|
|
|
260
259
|
if "trigger_price" not in data:
|
|
261
260
|
data["trigger_price"] = None
|
|
@@ -268,7 +267,7 @@ class AngelOne(Broker):
|
|
|
268
267
|
order_params = {
|
|
269
268
|
"orderid": order_id,
|
|
270
269
|
"variety": variety,
|
|
271
|
-
"price": data
|
|
270
|
+
"price": data.get("price"),
|
|
272
271
|
"triggerprice": data["trigger_price"],
|
|
273
272
|
"producttype": product,
|
|
274
273
|
"duration": "DAY",
|
|
@@ -277,13 +276,14 @@ class AngelOne(Broker):
|
|
|
277
276
|
"ordertype": order_type,
|
|
278
277
|
"exchange": exchange,
|
|
279
278
|
"tradingsymbol": self.get_symbol(
|
|
280
|
-
|
|
279
|
+
order_data["tradingsymbol"], exchange=exchange
|
|
281
280
|
),
|
|
282
281
|
}
|
|
283
282
|
|
|
284
283
|
Constants.logger.info(f"Modifying order [{order_id}] params [{order_params}]")
|
|
285
284
|
response = self.invoke_angelone_api(
|
|
286
|
-
self.wrapper.modifyOrder,
|
|
285
|
+
self.wrapper.modifyOrder,
|
|
286
|
+
orderparams=order_params,
|
|
287
287
|
)
|
|
288
288
|
Constants.logger.info(f"[MODIFY_ORDER_RESPONSE] {response}")
|
|
289
289
|
return order_id
|
|
@@ -294,8 +294,8 @@ class AngelOne(Broker):
|
|
|
294
294
|
)
|
|
295
295
|
raise
|
|
296
296
|
|
|
297
|
-
def cancel_order(self, order_id, variety="NORMAL"):
|
|
298
|
-
self.wrapper.cancelOrder(order_id=order_id, variety=variety)
|
|
297
|
+
def cancel_order(self, order_id: str, variety: str | None = "NORMAL"):
|
|
298
|
+
self.wrapper.cancelOrder(order_id=order_id, variety=variety) # type: ignore
|
|
299
299
|
|
|
300
300
|
def holdings(self):
|
|
301
301
|
holdings = self.invoke_angelone_api(self.wrapper.holding)
|
|
@@ -499,7 +499,8 @@ class AngelOne(Broker):
|
|
|
499
499
|
|
|
500
500
|
def profile(self):
|
|
501
501
|
profile_data = self.invoke_angelone_api(
|
|
502
|
-
self.wrapper.getProfile,
|
|
502
|
+
self.wrapper.getProfile, # type: ignore
|
|
503
|
+
refreshToken=self.refresh_token,
|
|
503
504
|
)
|
|
504
505
|
|
|
505
506
|
profile_data = profile_data["data"]
|
|
@@ -561,7 +562,7 @@ class AngelOne(Broker):
|
|
|
561
562
|
stop_max_attempt_number=3,
|
|
562
563
|
retry_on_exception=retry_exception,
|
|
563
564
|
)
|
|
564
|
-
def invoke_angelone_api(self, fn:
|
|
565
|
+
def invoke_angelone_api(self, fn: Any, *args: Any, **kwargs: Any) -> Any:
|
|
565
566
|
try:
|
|
566
567
|
response = fn(*args, **kwargs)
|
|
567
568
|
if "errorCode" in response and response["errorCode"] == "AG8001":
|