quantplay 2.0.95__tar.gz → 2.0.97__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.95 → quantplay-2.0.97}/PKG-INFO +26 -1
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/aliceblue.py +1 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/angelone.py +1 -18
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/auto_login/aliceblue.py +4 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/dhan.py +0 -6
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/five_paisa.py +0 -6
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/ft_utils/flattrade_utils.py +3 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/generics/broker.py +6 -6
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/motilal.py +0 -6
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/noren.py +0 -16
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/shoonya.py +2 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/uplink/uplink_utils.py +4 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/xts.py +103 -67
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/xts_utils/Connect.py +266 -408
- quantplay-2.0.97/quantplay/model/broker_response.py +17 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/wrapper/aws/s3.py +6 -1
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay.egg-info/PKG-INFO +26 -1
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay.egg-info/SOURCES.txt +1 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/setup.py +1 -1
- {quantplay-2.0.95 → quantplay-2.0.97}/README.md +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/pyproject.toml +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/auto_login/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/broker_factory.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/finvasia_utils/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/finvasia_utils/fa_noren.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/flattrade.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/ft_utils/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/ft_utils/ft_noren.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/generics/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/iifl_xts.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/kite_utils.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/kotak.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/uplink/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/upstox.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/xts_utils/Exception.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/xts_utils/InteractiveSocketClient.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/xts_utils/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/broker/zerodha.py +1 -1
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/exception/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/exception/exceptions.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/model/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/model/broker.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/model/generics.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/model/instrument_data.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/model/order_event.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/py.typed +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/caching.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/constant.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/exchange.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/number_utils.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/pickle_utils.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/utils/selenium_utils.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/wrapper/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay/wrapper/aws/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay.egg-info/dependency_links.txt +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay.egg-info/requires.txt +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/quantplay.egg-info/top_level.txt +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/setup.cfg +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/tests/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/tests/conftest.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/tests/wrapper/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/tests/wrapper/aws/__init__.py +0 -0
- {quantplay-2.0.95 → quantplay-2.0.97}/tests/wrapper/aws/s3_test.py +0 -0
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: quantplay
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.97
|
|
4
4
|
Summary: This python package will be stored in AWS CodeArtifact
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
7
7
|
Author-email:
|
|
8
8
|
License: MIT
|
|
9
|
+
Requires-Dist: setuptools
|
|
10
|
+
Requires-Dist: path
|
|
11
|
+
Requires-Dist: pyotp
|
|
12
|
+
Requires-Dist: retrying
|
|
13
|
+
Requires-Dist: boto3
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: websocket-client
|
|
16
|
+
Requires-Dist: smartapi-python
|
|
17
|
+
Requires-Dist: logzero
|
|
18
|
+
Requires-Dist: selenium
|
|
19
|
+
Requires-Dist: requests
|
|
20
|
+
Requires-Dist: pandas
|
|
21
|
+
Requires-Dist: pyarrow
|
|
22
|
+
Requires-Dist: polars
|
|
23
|
+
Requires-Dist: kiteconnect
|
|
24
|
+
Requires-Dist: pya3
|
|
25
|
+
Requires-Dist: py5paisa
|
|
26
|
+
Requires-Dist: upstox-python-sdk
|
|
27
|
+
Requires-Dist: undetected-chromedriver
|
|
28
|
+
Requires-Dist: cachetools
|
|
29
|
+
Requires-Dist: py_vollib
|
|
30
|
+
Requires-Dist: python-engineio
|
|
31
|
+
Requires-Dist: python-socketio
|
|
32
|
+
Requires-Dist: six
|
|
33
|
+
Requires-Dist: dhanhq
|
|
9
34
|
|
|
10
35
|
# Quantplay Alpha playground
|
|
11
36
|
|
|
@@ -47,12 +47,6 @@ class AngelOne(Broker):
|
|
|
47
47
|
order_sl = "STOPLOSS_LIMIT"
|
|
48
48
|
order_slm = "STOPLOSS_MARKET"
|
|
49
49
|
|
|
50
|
-
@retry(
|
|
51
|
-
wait_exponential_multiplier=3000,
|
|
52
|
-
wait_exponential_max=10000,
|
|
53
|
-
stop_max_attempt_number=5,
|
|
54
|
-
retry_on_exception=retry_exception,
|
|
55
|
-
)
|
|
56
50
|
def __init__(
|
|
57
51
|
self,
|
|
58
52
|
order_updates: Queue[OrderUpdateEvent] | None = None,
|
|
@@ -107,7 +101,7 @@ class AngelOne(Broker):
|
|
|
107
101
|
raise InvalidArgumentException("Invalid TOTP key provided")
|
|
108
102
|
|
|
109
103
|
except Exception as e:
|
|
110
|
-
|
|
104
|
+
traceback.print_exc()
|
|
111
105
|
raise RetryableException(str(e))
|
|
112
106
|
|
|
113
107
|
self.user_id = user_id
|
|
@@ -250,11 +244,6 @@ class AngelOne(Broker):
|
|
|
250
244
|
return "NORMAL"
|
|
251
245
|
return variety
|
|
252
246
|
|
|
253
|
-
@retry(
|
|
254
|
-
wait_exponential_multiplier=3000,
|
|
255
|
-
wait_exponential_max=10000,
|
|
256
|
-
stop_max_attempt_number=3,
|
|
257
|
-
)
|
|
258
247
|
def modify_order(self, order: ModifyOrderRequest) -> str:
|
|
259
248
|
data = copy.deepcopy(order)
|
|
260
249
|
order_id = str(data["order_id"])
|
|
@@ -337,12 +326,6 @@ class AngelOne(Broker):
|
|
|
337
326
|
|
|
338
327
|
return holdings
|
|
339
328
|
|
|
340
|
-
@retry(
|
|
341
|
-
wait_exponential_multiplier=3000,
|
|
342
|
-
wait_exponential_max=15000,
|
|
343
|
-
stop_max_attempt_number=2,
|
|
344
|
-
retry_on_exception=retry_exception,
|
|
345
|
-
)
|
|
346
329
|
def positions(self, drop_cnc: bool = True):
|
|
347
330
|
positions = self.invoke_angelone_api(self.wrapper.position)
|
|
348
331
|
|
|
@@ -87,15 +87,19 @@ class AliceblueLogin:
|
|
|
87
87
|
|
|
88
88
|
except binascii.Error:
|
|
89
89
|
raise InvalidArgumentException("Invalid TOTP key provided")
|
|
90
|
+
|
|
90
91
|
except InvalidArgumentException:
|
|
91
92
|
raise
|
|
93
|
+
|
|
92
94
|
except NoSuchElementException:
|
|
93
95
|
raise BrokerException(
|
|
94
96
|
"Login to Aliceblue failed. Please log in manually to generate a new token"
|
|
95
97
|
)
|
|
98
|
+
|
|
96
99
|
except WebDriverException:
|
|
97
100
|
traceback.print_exc()
|
|
98
101
|
raise RetryableException("Selenium setup need to be fixed")
|
|
102
|
+
|
|
99
103
|
except Exception as e:
|
|
100
104
|
traceback.print_exc()
|
|
101
105
|
raise RetryableException(str(e))
|
|
@@ -69,13 +69,7 @@ class Dhan(Broker):
|
|
|
69
69
|
def get_username(self):
|
|
70
70
|
return self.username
|
|
71
71
|
|
|
72
|
-
@retry(
|
|
73
|
-
wait_exponential_multiplier=3000,
|
|
74
|
-
wait_exponential_max=10000,
|
|
75
|
-
stop_max_attempt_number=3,
|
|
76
|
-
)
|
|
77
72
|
def modify_order(self, order: Any) -> str:
|
|
78
|
-
# TODO
|
|
79
73
|
order_id = order["order_id"]
|
|
80
74
|
existing_order: Dict[str, Any] = self.invoke_dhan_api(
|
|
81
75
|
self.dhan.get_order_by_id, # type: ignore
|
|
@@ -136,12 +136,6 @@ class FivePaisa(Broker):
|
|
|
136
136
|
tradingsymbol = self.get_symbol(tradingsymbol, exchange)
|
|
137
137
|
return int(self.symbol_data["{}:{}".format(exchange, tradingsymbol)]["lot_size"])
|
|
138
138
|
|
|
139
|
-
@retry(
|
|
140
|
-
wait_exponential_multiplier=1000,
|
|
141
|
-
wait_exponential_max=10000,
|
|
142
|
-
stop_max_attempt_number=3,
|
|
143
|
-
retry_on_exception=retry_exception,
|
|
144
|
-
)
|
|
145
139
|
def profile(self):
|
|
146
140
|
response: UserBrokerProfileResponse = {
|
|
147
141
|
"user_id": self.client.client_code,
|
|
@@ -105,10 +105,13 @@ class FlatTradeUtils:
|
|
|
105
105
|
|
|
106
106
|
except binascii.Error:
|
|
107
107
|
raise InvalidArgumentException("Invalid TOTP key provided")
|
|
108
|
+
|
|
108
109
|
except (InvalidArgumentException, TokenException) as e:
|
|
109
110
|
raise e
|
|
111
|
+
|
|
110
112
|
except WebDriverException:
|
|
111
113
|
raise WrongLibrarySetup("Selenium setup need to be fixed")
|
|
114
|
+
|
|
112
115
|
except Exception as e:
|
|
113
116
|
traceback.print_exc()
|
|
114
117
|
raise RetryableException(str(e))
|
|
@@ -217,12 +217,12 @@ class Broker(ABC):
|
|
|
217
217
|
return pd.read_csv(f"/tmp/{file_name}.csv") # type: ignore
|
|
218
218
|
|
|
219
219
|
@cached(cache=TTLCache(maxsize=1, ttl=2)) # type: ignore
|
|
220
|
-
def cached_orders(self) -> pl.DataFrame:
|
|
221
|
-
return self.orders()
|
|
220
|
+
def cached_orders(self, tag: str | None = None, add_ltp: bool = True) -> pl.DataFrame:
|
|
221
|
+
return self.orders(tag, add_ltp)
|
|
222
222
|
|
|
223
223
|
@cached(cache=TTLCache(maxsize=1, ttl=2)) # type: ignore
|
|
224
|
-
def cached_positions(self) -> pl.DataFrame:
|
|
225
|
-
return self.positions()
|
|
224
|
+
def cached_positions(self, drop_cnc: bool = True) -> pl.DataFrame:
|
|
225
|
+
return self.positions(drop_cnc)
|
|
226
226
|
|
|
227
227
|
def get_symbol(self, symbol: str, exchange: ExchangeType | None = None):
|
|
228
228
|
return symbol
|
|
@@ -300,11 +300,11 @@ class Broker(ABC):
|
|
|
300
300
|
"lot_size"
|
|
301
301
|
]
|
|
302
302
|
)
|
|
303
|
-
except Exception
|
|
303
|
+
except Exception:
|
|
304
304
|
logger.error(
|
|
305
305
|
f"[GET_LOT_SIZE] unable to get lot size for {broker_exchange} {tradingsymbol}"
|
|
306
306
|
)
|
|
307
|
-
raise
|
|
307
|
+
raise
|
|
308
308
|
|
|
309
309
|
def filter_orders(
|
|
310
310
|
self, orders: pl.DataFrame, tag: str | None = None, status: str | None = None
|
|
@@ -327,7 +327,6 @@ class Motilal(Broker):
|
|
|
327
327
|
|
|
328
328
|
self.modify_order(order_to_modify) # type: ignore
|
|
329
329
|
|
|
330
|
-
# TODO
|
|
331
330
|
def modify_order(self, order: Any) -> str:
|
|
332
331
|
order = copy.deepcopy(order) # type:ignore
|
|
333
332
|
order = self.add_existing_order_details(order)
|
|
@@ -517,11 +516,6 @@ class Motilal(Broker):
|
|
|
517
516
|
|
|
518
517
|
return response
|
|
519
518
|
|
|
520
|
-
@retry(
|
|
521
|
-
wait_exponential_multiplier=3000,
|
|
522
|
-
wait_exponential_max=10000,
|
|
523
|
-
stop_max_attempt_number=3,
|
|
524
|
-
)
|
|
525
519
|
def holdings(self):
|
|
526
520
|
response = self.__post_request(self.holdings_url, {})
|
|
527
521
|
if response["status"] == "ERROR":
|
|
@@ -250,11 +250,6 @@ class Noren(Broker):
|
|
|
250
250
|
f"[PLACE_ORDER_FAILED] {exception_message}"
|
|
251
251
|
)
|
|
252
252
|
|
|
253
|
-
@retry(
|
|
254
|
-
wait_exponential_multiplier=3000,
|
|
255
|
-
wait_exponential_max=10000,
|
|
256
|
-
stop_max_attempt_number=3,
|
|
257
|
-
)
|
|
258
253
|
def ltp(self, exchange: ExchangeType, tradingsymbol: str):
|
|
259
254
|
tradingsymbol = self.get_symbol(tradingsymbol, exchange)
|
|
260
255
|
|
|
@@ -380,11 +375,6 @@ class Noren(Broker):
|
|
|
380
375
|
|
|
381
376
|
return response
|
|
382
377
|
|
|
383
|
-
@retry(
|
|
384
|
-
wait_exponential_multiplier=3000,
|
|
385
|
-
wait_exponential_max=10000,
|
|
386
|
-
stop_max_attempt_number=3,
|
|
387
|
-
)
|
|
388
378
|
def holdings(self):
|
|
389
379
|
holdings = self.invoke_noren_api(self.api.get_holdings)
|
|
390
380
|
if holdings is None or len(holdings) == 0:
|
|
@@ -695,12 +685,6 @@ class Noren(Broker):
|
|
|
695
685
|
traceback.print_exc()
|
|
696
686
|
raise RetryableException("Failed to Receive Data from broker. Retrying Again")
|
|
697
687
|
|
|
698
|
-
@retry(
|
|
699
|
-
wait_exponential_multiplier=1000,
|
|
700
|
-
wait_exponential_max=10000,
|
|
701
|
-
stop_max_attempt_number=3,
|
|
702
|
-
retry_on_exception=retry_exception,
|
|
703
|
-
)
|
|
704
688
|
def margins(self) -> MarginsResponse:
|
|
705
689
|
api_margins = self.invoke_noren_api(self.api.get_limits)
|
|
706
690
|
|
|
@@ -108,12 +108,16 @@ class UplinkUtils:
|
|
|
108
108
|
driver.close()
|
|
109
109
|
|
|
110
110
|
return request_token
|
|
111
|
+
|
|
111
112
|
except binascii.Error:
|
|
112
113
|
raise InvalidArgumentException("Invalid TOTP key provided")
|
|
114
|
+
|
|
113
115
|
except InvalidArgumentException:
|
|
114
116
|
raise
|
|
117
|
+
|
|
115
118
|
except WebDriverException:
|
|
116
119
|
raise RetryableException("Selenium setup need to be fixed")
|
|
120
|
+
|
|
117
121
|
except Exception as e:
|
|
118
122
|
traceback.print_exc()
|
|
119
123
|
raise RetryableException(str(e))
|
|
@@ -12,6 +12,12 @@ from retrying import retry # type: ignore
|
|
|
12
12
|
|
|
13
13
|
from quantplay.broker.generics.broker import Broker
|
|
14
14
|
from quantplay.broker.xts_utils.Connect import XTSConnect
|
|
15
|
+
from quantplay.broker.xts_utils.Exception import (
|
|
16
|
+
XTSDataException,
|
|
17
|
+
XTSGeneralException,
|
|
18
|
+
XTSNetworkException,
|
|
19
|
+
XTSTokenException,
|
|
20
|
+
)
|
|
15
21
|
from quantplay.broker.xts_utils.InteractiveSocketClient import OrderSocket_io
|
|
16
22
|
from quantplay.exception.exceptions import (
|
|
17
23
|
BrokerException,
|
|
@@ -66,7 +72,7 @@ class XTS(Broker):
|
|
|
66
72
|
):
|
|
67
73
|
self.login(api_key, api_secret, md_api_key, md_api_secret)
|
|
68
74
|
else:
|
|
69
|
-
raise
|
|
75
|
+
raise InvalidArgumentException("Missing Arguments")
|
|
70
76
|
|
|
71
77
|
except Exception as e:
|
|
72
78
|
traceback.print_exc()
|
|
@@ -76,8 +82,11 @@ class XTS(Broker):
|
|
|
76
82
|
self.load_instrument()
|
|
77
83
|
|
|
78
84
|
def set_wrapper(self, serialized_wrapper: str, serialized_md_wrapper: str):
|
|
79
|
-
self.wrapper = pickle.loads(
|
|
80
|
-
|
|
85
|
+
self.wrapper: XTSConnect = pickle.loads(
|
|
86
|
+
codecs.decode(serialized_wrapper.encode(), "base64")
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self.md_wrapper: XTSConnect = pickle.loads(
|
|
81
90
|
codecs.decode(serialized_md_wrapper.encode(), "base64")
|
|
82
91
|
)
|
|
83
92
|
|
|
@@ -85,6 +94,7 @@ class XTS(Broker):
|
|
|
85
94
|
try:
|
|
86
95
|
self.symbol_data = InstrumentData.get_instance().load_data("xts_instruments") # type: ignore
|
|
87
96
|
Constants.logger.info("[LOADING_INSTRUMENTS] loading data from cache")
|
|
97
|
+
|
|
88
98
|
except Exception:
|
|
89
99
|
instruments = pd.read_csv( # type: ignore
|
|
90
100
|
"https://quantplay-public-data.s3.ap-south-1.amazonaws.com/symbol_data/instruments.csv"
|
|
@@ -101,29 +111,34 @@ class XTS(Broker):
|
|
|
101
111
|
|
|
102
112
|
ins_type = instrument["instrument_type"]
|
|
103
113
|
name = instrument["name"]
|
|
114
|
+
|
|
104
115
|
if ins_type in ["CE", "PE"]:
|
|
105
116
|
expiry = datetime.strftime(
|
|
106
117
|
datetime.strptime(str(instrument["expiry"]), "%Y-%m-%d"),
|
|
107
118
|
"%d%b%Y",
|
|
108
119
|
).upper()
|
|
109
120
|
strike = str(instrument["strike"]).rstrip("0")
|
|
121
|
+
|
|
110
122
|
if strike[-1] == ".":
|
|
111
123
|
strike = strike[:-1]
|
|
124
|
+
|
|
112
125
|
instrument["broker_symbol"] = f"{name} {expiry} {ins_type} {strike}"
|
|
126
|
+
|
|
113
127
|
elif ins_type == "FUT":
|
|
114
128
|
expiry = datetime.strftime(
|
|
115
129
|
datetime.strptime(str(instrument["expiry"]), "%Y-%m-%d"),
|
|
116
130
|
"%d%b%Y",
|
|
117
131
|
).upper()
|
|
118
132
|
instrument["broker_symbol"] = f"{name} {expiry}"
|
|
133
|
+
|
|
119
134
|
else:
|
|
120
135
|
instrument["broker_symbol"] = tradingsymbol
|
|
121
136
|
|
|
122
|
-
# TODO: Types
|
|
123
137
|
self.symbol_data[f"{exchange}:{tradingsymbol}"] = instrument # type: ignore
|
|
124
138
|
|
|
125
139
|
PickleUtils.save_data(self.symbol_data, "xts_instruments")
|
|
126
140
|
Constants.logger.info("[LOADING_INSTRUMENTS] loading data from server")
|
|
141
|
+
|
|
127
142
|
self.initialize_broker_symbol_map()
|
|
128
143
|
|
|
129
144
|
def login(self, api_key: str, api_secret: str, md_api_key: str, md_api_secret: str):
|
|
@@ -133,49 +148,35 @@ class XTS(Broker):
|
|
|
133
148
|
secretKey=api_secret,
|
|
134
149
|
root=self.root_url,
|
|
135
150
|
)
|
|
136
|
-
xt_core_response = self.wrapper.interactive_login
|
|
151
|
+
xt_core_response = self.invoke_xts_api(self.wrapper.interactive_login)
|
|
152
|
+
|
|
137
153
|
self.md_wrapper = XTSConnect(
|
|
138
154
|
apiKey=md_api_key,
|
|
139
155
|
secretKey=md_api_secret,
|
|
140
156
|
root=self.root_url,
|
|
141
157
|
)
|
|
142
|
-
md_response = self.md_wrapper.marketdata_login
|
|
158
|
+
md_response = self.invoke_xts_api(self.md_wrapper.marketdata_login)
|
|
159
|
+
|
|
143
160
|
if "type" not in xt_core_response or xt_core_response["type"] != "success":
|
|
144
161
|
print(f"api login response {xt_core_response}")
|
|
145
162
|
raise TokenException("Api key credentials are incorrect")
|
|
163
|
+
|
|
146
164
|
if "type" not in md_response or md_response["type"] != "success":
|
|
147
165
|
print(f"market data login response {md_response}")
|
|
148
166
|
raise TokenException("Market data api credentials are invalid")
|
|
167
|
+
|
|
149
168
|
self.ClientID = xt_core_response["result"]["userID"]
|
|
169
|
+
|
|
150
170
|
except TokenException:
|
|
151
171
|
raise
|
|
172
|
+
|
|
152
173
|
except Exception:
|
|
153
174
|
raise InvalidArgumentException("Invalid api key/secret")
|
|
154
175
|
|
|
155
|
-
def handle_exception(self, response: Dict[str, Any]):
|
|
156
|
-
if "data" in response and "description" in response["data"]:
|
|
157
|
-
data = response["data"]
|
|
158
|
-
if "max limit" in data["description"].lower():
|
|
159
|
-
user_id = self.profile()["user_id"]
|
|
160
|
-
print("Rate limit problem")
|
|
161
|
-
raise RetryableException(f"{user_id}: Request limit exceeded")
|
|
162
|
-
if (
|
|
163
|
-
"description" in response
|
|
164
|
-
and "Authorization not found" in response["description"]
|
|
165
|
-
):
|
|
166
|
-
raise TokenException(response["description"])
|
|
167
|
-
if "type" in response and response["type"] == "error":
|
|
168
|
-
raise Exception(f"[XTS_Error]: {response['description']}")
|
|
169
|
-
|
|
170
|
-
@retry(
|
|
171
|
-
wait_exponential_multiplier=3000,
|
|
172
|
-
wait_exponential_max=10000,
|
|
173
|
-
stop_max_attempt_number=3,
|
|
174
|
-
retry_on_exception=retry_exception,
|
|
175
|
-
)
|
|
176
176
|
def margins(self) -> MarginsResponse:
|
|
177
|
-
api_response = self.
|
|
178
|
-
|
|
177
|
+
api_response = self.invoke_xts_api(
|
|
178
|
+
self.wrapper.get_balance, clientID=self.ClientID
|
|
179
|
+
)
|
|
179
180
|
|
|
180
181
|
if not api_response:
|
|
181
182
|
return {
|
|
@@ -184,9 +185,11 @@ class XTS(Broker):
|
|
|
184
185
|
"total_balance": 0,
|
|
185
186
|
"cash": 0,
|
|
186
187
|
}
|
|
188
|
+
|
|
187
189
|
api_response = api_response["result"]["BalanceList"][0]["limitObject"]
|
|
188
190
|
margin_used = api_response["RMSSubLimits"]["marginUtilized"]
|
|
189
191
|
margin_available = api_response["RMSSubLimits"]["netMarginAvailable"]
|
|
192
|
+
|
|
190
193
|
return {
|
|
191
194
|
"margin_used": margin_used,
|
|
192
195
|
"margin_available": margin_available,
|
|
@@ -194,15 +197,10 @@ class XTS(Broker):
|
|
|
194
197
|
"cash": 0,
|
|
195
198
|
}
|
|
196
199
|
|
|
197
|
-
@retry(
|
|
198
|
-
wait_exponential_multiplier=3000,
|
|
199
|
-
wait_exponential_max=10000,
|
|
200
|
-
stop_max_attempt_number=3,
|
|
201
|
-
retry_on_exception=retry_exception,
|
|
202
|
-
)
|
|
203
200
|
def profile(self) -> UserBrokerProfileResponse:
|
|
204
|
-
api_response = self.
|
|
205
|
-
|
|
201
|
+
api_response = self.invoke_xts_api(
|
|
202
|
+
self.wrapper.get_profile, clientID=self.ClientID
|
|
203
|
+
)
|
|
206
204
|
api_response = api_response["result"]
|
|
207
205
|
|
|
208
206
|
response: UserBrokerProfileResponse = {
|
|
@@ -213,15 +211,10 @@ class XTS(Broker):
|
|
|
213
211
|
|
|
214
212
|
return response
|
|
215
213
|
|
|
216
|
-
@retry(
|
|
217
|
-
wait_exponential_multiplier=3000,
|
|
218
|
-
wait_exponential_max=10000,
|
|
219
|
-
stop_max_attempt_number=3,
|
|
220
|
-
retry_on_exception=retry_exception,
|
|
221
|
-
)
|
|
222
214
|
def orders(self, tag: str | None = None, add_ltp: bool = True) -> pl.DataFrame:
|
|
223
|
-
api_response = self.
|
|
224
|
-
|
|
215
|
+
api_response = self.invoke_xts_api(
|
|
216
|
+
self.wrapper.get_order_book, clientID=self.ClientID
|
|
217
|
+
)
|
|
225
218
|
|
|
226
219
|
api_response = api_response["result"]
|
|
227
220
|
if api_response is None or len(api_response) == 0:
|
|
@@ -345,23 +338,13 @@ class XTS(Broker):
|
|
|
345
338
|
|
|
346
339
|
return orders_df[list(self.orders_schema.keys())].cast(self.orders_schema)
|
|
347
340
|
|
|
348
|
-
@retry(
|
|
349
|
-
wait_exponential_multiplier=3000,
|
|
350
|
-
wait_exponential_max=10000,
|
|
351
|
-
stop_max_attempt_number=3,
|
|
352
|
-
)
|
|
353
341
|
def holdings(self):
|
|
354
342
|
return pl.DataFrame(schema=self.holidings_schema)
|
|
355
343
|
|
|
356
|
-
@retry(
|
|
357
|
-
wait_exponential_multiplier=3000,
|
|
358
|
-
wait_exponential_max=10000,
|
|
359
|
-
stop_max_attempt_number=3,
|
|
360
|
-
retry_on_exception=retry_exception,
|
|
361
|
-
)
|
|
362
344
|
def positions(self, drop_cnc: bool = True) -> pl.DataFrame:
|
|
363
|
-
api_response = self.
|
|
364
|
-
|
|
345
|
+
api_response = self.invoke_xts_api(
|
|
346
|
+
self.wrapper.get_position_daywise, clientID=self.ClientID
|
|
347
|
+
)
|
|
365
348
|
|
|
366
349
|
api_response = api_response["result"]["positionList"]
|
|
367
350
|
positions_df = pl.DataFrame(api_response)
|
|
@@ -478,7 +461,9 @@ class XTS(Broker):
|
|
|
478
461
|
}
|
|
479
462
|
for x in symbols
|
|
480
463
|
]
|
|
481
|
-
|
|
464
|
+
|
|
465
|
+
api_response = self.invoke_xts_api(
|
|
466
|
+
self.md_wrapper.get_quote,
|
|
482
467
|
Instruments=instruments,
|
|
483
468
|
xtsMessageCode=1512,
|
|
484
469
|
publishFormat="JSON",
|
|
@@ -486,6 +471,7 @@ class XTS(Broker):
|
|
|
486
471
|
|
|
487
472
|
if "type" in api_response and api_response["type"] == "error":
|
|
488
473
|
raise TokenException(api_response["description"])
|
|
474
|
+
|
|
489
475
|
api_response = api_response["result"]
|
|
490
476
|
|
|
491
477
|
ltp_json = api_response["listQuotes"]
|
|
@@ -537,11 +523,11 @@ class XTS(Broker):
|
|
|
537
523
|
def ltp(self, exchange: ExchangeType, tradingsymbol: str) -> float:
|
|
538
524
|
exchange_code = self.get_exchange_code(exchange)
|
|
539
525
|
exchange_token = self.symbol_data[f"{exchange}:{tradingsymbol}"].get(
|
|
540
|
-
"exchange_token",
|
|
541
|
-
"", # TODO
|
|
526
|
+
"exchange_token", ""
|
|
542
527
|
)
|
|
543
528
|
|
|
544
|
-
api_response = self.
|
|
529
|
+
api_response = self.invoke_xts_api(
|
|
530
|
+
self.md_wrapper.get_quote,
|
|
545
531
|
Instruments=[
|
|
546
532
|
{
|
|
547
533
|
"exchangeSegment": exchange_code,
|
|
@@ -561,8 +547,9 @@ class XTS(Broker):
|
|
|
561
547
|
|
|
562
548
|
try:
|
|
563
549
|
ltp_json = api_response["listQuotes"][0]
|
|
564
|
-
|
|
565
|
-
|
|
550
|
+
|
|
551
|
+
except IndexError:
|
|
552
|
+
traceback.print_exc()
|
|
566
553
|
raise BrokerException("Broker Provided Invalid Response")
|
|
567
554
|
|
|
568
555
|
ltp = json.loads(ltp_json)["LastTradedPrice"]
|
|
@@ -626,7 +613,8 @@ class XTS(Broker):
|
|
|
626
613
|
|
|
627
614
|
tag = order_data["tag"]
|
|
628
615
|
|
|
629
|
-
api_response = self.
|
|
616
|
+
api_response = self.invoke_xts_api(
|
|
617
|
+
self.wrapper.cancel_order,
|
|
630
618
|
appOrderID=int(order_id),
|
|
631
619
|
clientID=order_data["user_id"],
|
|
632
620
|
orderUniqueIdentifier=tag,
|
|
@@ -678,7 +666,8 @@ class XTS(Broker):
|
|
|
678
666
|
time_in_force = "DAY"
|
|
679
667
|
disclosed_quantity = 0
|
|
680
668
|
|
|
681
|
-
api_response = self.
|
|
669
|
+
api_response = self.invoke_xts_api(
|
|
670
|
+
self.wrapper.modify_order,
|
|
682
671
|
appOrderID=int(order_id),
|
|
683
672
|
modifiedTimeInForce=time_in_force,
|
|
684
673
|
modifiedDisclosedQuantity=disclosed_quantity,
|
|
@@ -804,5 +793,52 @@ class XTS(Broker):
|
|
|
804
793
|
self.order_updates.put(new_ord)
|
|
805
794
|
|
|
806
795
|
except Exception as e:
|
|
807
|
-
|
|
796
|
+
traceback.print_exc()
|
|
808
797
|
Constants.logger.error("[ORDER_UPDATE_PROCESSING_FAILED] {}".format(e))
|
|
798
|
+
|
|
799
|
+
@retry(
|
|
800
|
+
wait_exponential_multiplier=3000,
|
|
801
|
+
wait_exponential_max=10000,
|
|
802
|
+
stop_max_attempt_number=3,
|
|
803
|
+
retry_on_exception=retry_exception,
|
|
804
|
+
)
|
|
805
|
+
def invoke_xts_api(self, fn: Any, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
|
806
|
+
try:
|
|
807
|
+
response = fn(*args, **kwargs)
|
|
808
|
+
|
|
809
|
+
if "data" in response and "description" in response["data"]:
|
|
810
|
+
data = response["data"]
|
|
811
|
+
|
|
812
|
+
if "max limit" in data["description"].lower():
|
|
813
|
+
user_id = self.profile()["user_id"]
|
|
814
|
+
raise RetryableException(f"{user_id}: Request limit exceeded")
|
|
815
|
+
|
|
816
|
+
if (
|
|
817
|
+
"description" in response
|
|
818
|
+
and "Authorization not found" in response["description"]
|
|
819
|
+
):
|
|
820
|
+
raise TokenException(response["description"])
|
|
821
|
+
|
|
822
|
+
if (
|
|
823
|
+
"type" in response and response["type"] == "error"
|
|
824
|
+
) or "result" not in response:
|
|
825
|
+
raise BrokerException(f"[XTS_Error]: {response['description']}")
|
|
826
|
+
|
|
827
|
+
return response
|
|
828
|
+
|
|
829
|
+
except XTSTokenException as e:
|
|
830
|
+
raise TokenException(str(e))
|
|
831
|
+
|
|
832
|
+
except (TokenException, RetryableException, BrokerException):
|
|
833
|
+
raise
|
|
834
|
+
|
|
835
|
+
except (
|
|
836
|
+
XTSGeneralException,
|
|
837
|
+
XTSDataException,
|
|
838
|
+
XTSNetworkException,
|
|
839
|
+
) as e:
|
|
840
|
+
raise BrokerException(str(e))
|
|
841
|
+
|
|
842
|
+
except Exception:
|
|
843
|
+
traceback.print_exc()
|
|
844
|
+
raise
|