p123api 2.3.0__tar.gz → 2.4.0__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.
- {p123api-2.3.0 → p123api-2.4.0}/PKG-INFO +1 -1
- p123api-2.4.0/p123api/__init__.py +1 -0
- {p123api-2.3.0 → p123api-2.4.0}/p123api/client.py +273 -327
- p123api-2.4.0/p123api/types.py +53 -0
- {p123api-2.3.0 → p123api-2.4.0}/p123api.egg-info/PKG-INFO +1 -1
- {p123api-2.3.0 → p123api-2.4.0}/p123api.egg-info/SOURCES.txt +2 -0
- p123api-2.4.0/pyproject.toml +12 -0
- {p123api-2.3.0 → p123api-2.4.0}/setup.py +2 -6
- p123api-2.3.0/p123api/__init__.py +0 -1
- {p123api-2.3.0 → p123api-2.4.0}/LICENSE +0 -0
- {p123api-2.3.0 → p123api-2.4.0}/README.md +0 -0
- {p123api-2.3.0 → p123api-2.4.0}/p123api.egg-info/dependency_links.txt +0 -0
- {p123api-2.3.0 → p123api-2.4.0}/p123api.egg-info/requires.txt +0 -0
- {p123api-2.3.0 → p123api-2.4.0}/p123api.egg-info/top_level.txt +0 -0
- {p123api-2.3.0 → p123api-2.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .client import Client, ClientException, ClientItemNotFoundException
|
|
@@ -3,8 +3,19 @@ import requests
|
|
|
3
3
|
import time
|
|
4
4
|
import pandas
|
|
5
5
|
from string import Template
|
|
6
|
-
from typing import IO,
|
|
7
|
-
|
|
6
|
+
from typing import IO, Callable, List, Literal, Optional, Union, overload
|
|
7
|
+
from typing_extensions import deprecated
|
|
8
|
+
|
|
9
|
+
from .types import (
|
|
10
|
+
DataSeriesInfoResult,
|
|
11
|
+
DataSeriesResult,
|
|
12
|
+
IdResult,
|
|
13
|
+
RankInfoResult,
|
|
14
|
+
RankingMethod,
|
|
15
|
+
StockFactorInfoResult,
|
|
16
|
+
StockFactorResult,
|
|
17
|
+
StrategyInfoResult,
|
|
18
|
+
)
|
|
8
19
|
|
|
9
20
|
ENDPOINT = "https://api.portfolio123.com"
|
|
10
21
|
AUTH_PATH = "/auth"
|
|
@@ -13,10 +24,11 @@ SCREEN_BACKTEST_PATH = "/screen/backtest"
|
|
|
13
24
|
SCREEN_RUN_PATH = "/screen/run"
|
|
14
25
|
UNIVERSE_PATH = "/universe"
|
|
15
26
|
RANK_PATH = "/rank"
|
|
16
|
-
DATA_PATH = "/data"
|
|
17
27
|
RANK_RANKS_PATH = "/rank/ranks"
|
|
18
28
|
RANK_PERF_PATH = "/rank/performance"
|
|
19
29
|
RANK_TOUCH_PATH = Template("/rank/$id/touch")
|
|
30
|
+
RANK_CREATE = "/rank/create"
|
|
31
|
+
DATA_PATH = "/data"
|
|
20
32
|
DATA_UNIVERSE_PATH = "/data/universe"
|
|
21
33
|
DATA_PRICES_PATH = Template("/data/prices/$identifier")
|
|
22
34
|
STRATEGY_DETAILS_PATH = Template("/strategy/$id")
|
|
@@ -25,41 +37,54 @@ STRATEGY_TRADING_SYSTEM_PATH = Template("/strategy/$id/trading-system")
|
|
|
25
37
|
BOOK_TRADING_SYSTEM_PATH = Template("/strategy/$id/book-trading-system")
|
|
26
38
|
SIM_RERUN_PATH = Template("/strategy/$id/rerun")
|
|
27
39
|
BOOK_SIM_RERUN_PATH = Template("/strategy/$id/book-rerun")
|
|
40
|
+
STRATEGY_INFO_PATH = "/strategy"
|
|
28
41
|
STRATEGY_REBALANCE_PATH = Template("/strategy/$id/rebalance")
|
|
29
42
|
STRATEGY_REBALANCE_COMMIT_PATH = Template("/strategy/$id/rebalance/commit")
|
|
30
43
|
STRATEGY_TRANS_PATH = Template("/strategy/$id/transactions")
|
|
44
|
+
STRATEGY_COPY_PATH = Template("/strategy/$id/copy")
|
|
45
|
+
BOOK_COPY_PATH = Template("/strategy/$id/copy-book")
|
|
31
46
|
STOCK_FACTOR_UPLOAD_PATH = Template("/stockFactor/upload/$id")
|
|
32
47
|
STOCK_FACTOR_CREATE_UPDATE_PATH = "/stockFactor"
|
|
33
48
|
STOCK_FACTOR_DOWNLOAD_PATH = Template("/stockFactor/$id")
|
|
34
|
-
STOCK_FACTOR_DELETE_PATH =
|
|
35
|
-
STOCK_FACTOR_INFO_PATH =
|
|
49
|
+
STOCK_FACTOR_DELETE_PATH = Template("/stockFactor/$id")
|
|
50
|
+
STOCK_FACTOR_INFO_PATH = "/stockFactor"
|
|
36
51
|
DATA_SERIES_UPLOAD_PATH = Template("/dataSeries/upload/$id")
|
|
37
52
|
DATA_SERIES_CREATE_UPDATE_PATH = "/dataSeries"
|
|
53
|
+
DATA_SERIES_INFO_PATH = "/dataSeries"
|
|
38
54
|
DATA_SERIES_DELETE_PATH = Template("/dataSeries/$id")
|
|
39
55
|
AIFACTOR_PREDICT_PATH = Template("/aiFactor/predict/$id")
|
|
40
56
|
|
|
41
57
|
|
|
42
58
|
class ClientException(Exception):
|
|
43
|
-
def __init__(self, message, *, resp=None, exception=None):
|
|
59
|
+
def __init__(self, message, *, resp: Union[requests.Response, None] = None, exception: Union[Exception, None] = None):
|
|
44
60
|
super().__init__(message)
|
|
45
61
|
self._resp = resp
|
|
46
62
|
self._exception = exception
|
|
47
63
|
|
|
48
|
-
def get_resp(self)
|
|
64
|
+
def get_resp(self):
|
|
49
65
|
return self._resp
|
|
50
66
|
|
|
51
|
-
def get_cause(self)
|
|
67
|
+
def get_cause(self):
|
|
52
68
|
return self._exception
|
|
53
69
|
|
|
70
|
+
@staticmethod
|
|
71
|
+
def build(message, resp: requests.Response):
|
|
72
|
+
if resp.status_code == 404:
|
|
73
|
+
return ClientItemNotFoundException(resp=resp)
|
|
74
|
+
return ClientException(message=message, resp=resp)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ClientItemNotFoundException(ClientException):
|
|
78
|
+
def __init__(self, resp: requests.Response):
|
|
79
|
+
super().__init__("Item not found", resp=resp)
|
|
80
|
+
|
|
54
81
|
|
|
55
82
|
class Client:
|
|
56
83
|
"""
|
|
57
84
|
class for interfacing with P123 API
|
|
58
85
|
"""
|
|
59
86
|
|
|
60
|
-
def __init__(
|
|
61
|
-
self, *, api_id, api_key, auth_extra={}, endpoint=ENDPOINT, verify_requests=True
|
|
62
|
-
):
|
|
87
|
+
def __init__(self, *, api_id, api_key, auth_extra={}, endpoint=ENDPOINT, verify_requests=True):
|
|
63
88
|
self._endpoint = endpoint
|
|
64
89
|
self._verify_requests = verify_requests
|
|
65
90
|
self._max_req_retries = 5
|
|
@@ -73,6 +98,7 @@ class Client:
|
|
|
73
98
|
|
|
74
99
|
self._auth_params = {"apiId": api_id, "apiKey": api_key, **auth_extra}
|
|
75
100
|
self._session = requests.Session()
|
|
101
|
+
self._method_map = {"GET": self._session.get, "POST": self._session.post, "DELETE": self._session.delete}
|
|
76
102
|
|
|
77
103
|
def __enter__(self):
|
|
78
104
|
return self
|
|
@@ -104,18 +130,19 @@ class Client:
|
|
|
104
130
|
:return: bool
|
|
105
131
|
"""
|
|
106
132
|
self._session.headers.clear()
|
|
107
|
-
|
|
133
|
+
with req_with_retry(
|
|
108
134
|
self._session.post,
|
|
109
135
|
self._max_req_retries,
|
|
110
136
|
url=self._endpoint + AUTH_PATH,
|
|
111
137
|
json=self._auth_params,
|
|
112
138
|
verify=self._verify_requests,
|
|
113
139
|
timeout=30,
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
) as resp:
|
|
141
|
+
if resp.status_code == 200:
|
|
142
|
+
self._token = resp.text
|
|
143
|
+
self._session.headers.update({"Authorization": f"Bearer {resp.text}"})
|
|
144
|
+
return
|
|
145
|
+
|
|
119
146
|
if resp.status_code == 406:
|
|
120
147
|
message = "user account inactive"
|
|
121
148
|
elif resp.status_code == 402:
|
|
@@ -131,79 +158,49 @@ class Client:
|
|
|
131
158
|
raise ClientException(f"API authentication failed{message}", resp=resp)
|
|
132
159
|
|
|
133
160
|
def _req_with_auth_fallback(
|
|
134
|
-
self,
|
|
135
|
-
|
|
136
|
-
name: str,
|
|
137
|
-
method: str = "POST",
|
|
138
|
-
url: str,
|
|
139
|
-
json=None,
|
|
140
|
-
params=None,
|
|
141
|
-
data=None,
|
|
142
|
-
headers=None,
|
|
143
|
-
stop: bool = False,
|
|
144
|
-
) -> Optional[requests.Response]:
|
|
161
|
+
self, *, method: Literal["GET", "POST", "DELETE"] = "POST", url: str, json=None, params=None, data=None, headers=None
|
|
162
|
+
):
|
|
145
163
|
"""
|
|
146
164
|
Request with authentication fallback, used by all requests (except authentication)
|
|
147
|
-
:param name: request action
|
|
148
165
|
:param method: request method
|
|
149
166
|
:param url: request url
|
|
150
167
|
:param json: request json
|
|
151
168
|
:param params: request params
|
|
152
169
|
:param data: request data
|
|
153
170
|
:param headers: request headers
|
|
154
|
-
:param stop: flag to stop infinite authentication recursion
|
|
155
171
|
:return: request response object
|
|
156
172
|
"""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if
|
|
160
|
-
resp = req_with_retry(
|
|
161
|
-
self._session.post,
|
|
162
|
-
self._max_req_retries,
|
|
163
|
-
url=url,
|
|
164
|
-
json=json,
|
|
165
|
-
params=params,
|
|
166
|
-
verify=self._verify_requests,
|
|
167
|
-
timeout=self._timeout,
|
|
168
|
-
data=data,
|
|
169
|
-
headers=headers,
|
|
170
|
-
)
|
|
171
|
-
else:
|
|
172
|
-
req_type = (
|
|
173
|
-
self._session.delete if method == "DELETE" else self._session.get
|
|
174
|
-
)
|
|
175
|
-
resp = req_with_retry(
|
|
176
|
-
req_type,
|
|
177
|
-
self._max_req_retries,
|
|
178
|
-
url=url,
|
|
179
|
-
json=json,
|
|
180
|
-
params=params,
|
|
181
|
-
verify=self._verify_requests,
|
|
182
|
-
timeout=self._timeout,
|
|
183
|
-
headers=headers,
|
|
184
|
-
)
|
|
185
|
-
if resp is None or resp.status_code == 401 or resp.status_code == 403:
|
|
186
|
-
if not stop:
|
|
173
|
+
reauth = False
|
|
174
|
+
while True:
|
|
175
|
+
if self._session.headers.get("Authorization") is None:
|
|
187
176
|
self.auth()
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
177
|
+
with req_with_retry(
|
|
178
|
+
self._method_map[method],
|
|
179
|
+
self._max_req_retries,
|
|
180
|
+
url=url,
|
|
181
|
+
json=json,
|
|
182
|
+
params=params,
|
|
183
|
+
verify=self._verify_requests,
|
|
184
|
+
timeout=self._timeout,
|
|
185
|
+
data=data,
|
|
186
|
+
headers=headers,
|
|
187
|
+
) as resp:
|
|
188
|
+
|
|
189
|
+
if resp.status_code == 200:
|
|
190
|
+
return resp.json()
|
|
191
|
+
|
|
192
|
+
if resp.status_code == 401 or resp.status_code == 403:
|
|
193
|
+
del self._session.headers["Authorization"]
|
|
194
|
+
if not reauth:
|
|
195
|
+
reauth = True
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
message = resp.text
|
|
199
|
+
if not message and resp.status_code == 402:
|
|
200
|
+
message = "request quota exhausted"
|
|
201
|
+
if message:
|
|
202
|
+
message = ": " + message
|
|
203
|
+
raise ClientException.build(f"API request failed{message}", resp=resp)
|
|
207
204
|
|
|
208
205
|
def screen_rolling_backtest(self, params: dict, to_pandas=False):
|
|
209
206
|
"""
|
|
@@ -212,11 +209,7 @@ class Client:
|
|
|
212
209
|
:param to_pandas:
|
|
213
210
|
:return:
|
|
214
211
|
"""
|
|
215
|
-
ret = self._req_with_auth_fallback(
|
|
216
|
-
name="screen rolling backtest",
|
|
217
|
-
url=self._endpoint + SCREEN_ROLLING_BACKTEST_PATH,
|
|
218
|
-
json=params,
|
|
219
|
-
).json()
|
|
212
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + SCREEN_ROLLING_BACKTEST_PATH, json=params)
|
|
220
213
|
|
|
221
214
|
if to_pandas:
|
|
222
215
|
rows = ret["rows"]
|
|
@@ -237,11 +230,7 @@ class Client:
|
|
|
237
230
|
:param to_pandas:
|
|
238
231
|
:return:
|
|
239
232
|
"""
|
|
240
|
-
ret = self._req_with_auth_fallback(
|
|
241
|
-
name="screen backtest",
|
|
242
|
-
url=self._endpoint + SCREEN_BACKTEST_PATH,
|
|
243
|
-
json=params,
|
|
244
|
-
).json()
|
|
233
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + SCREEN_BACKTEST_PATH, json=params)
|
|
245
234
|
|
|
246
235
|
if to_pandas:
|
|
247
236
|
columns = [
|
|
@@ -297,28 +286,14 @@ class Client:
|
|
|
297
286
|
rows.append(ret["results"]["upMarkets"])
|
|
298
287
|
ret["results"]["downMarkets"][0] = "Down Markets"
|
|
299
288
|
rows.append(ret["results"]["downMarkets"])
|
|
300
|
-
panda_results = pandas.DataFrame(
|
|
301
|
-
data=rows, columns=ret["results"]["columns"]
|
|
302
|
-
)
|
|
289
|
+
panda_results = pandas.DataFrame(data=rows, columns=ret["results"]["columns"])
|
|
303
290
|
|
|
304
|
-
columns = [
|
|
305
|
-
"Date",
|
|
306
|
-
"Screen Return",
|
|
307
|
-
"Bench Return",
|
|
308
|
-
"Turnover %",
|
|
309
|
-
"Position Count",
|
|
310
|
-
]
|
|
291
|
+
columns = ["Date", "Screen Return", "Bench Return", "Turnover %", "Position Count"]
|
|
311
292
|
chart = ret["chart"]
|
|
312
293
|
rows = []
|
|
313
294
|
for idx, date in enumerate(chart["dates"]):
|
|
314
295
|
rows.append(
|
|
315
|
-
[
|
|
316
|
-
date,
|
|
317
|
-
chart["screenReturns"][idx],
|
|
318
|
-
chart["benchReturns"][idx],
|
|
319
|
-
chart["turnoverPct"][idx],
|
|
320
|
-
chart["positionCnt"][idx],
|
|
321
|
-
]
|
|
296
|
+
[date, chart["screenReturns"][idx], chart["benchReturns"][idx], chart["turnoverPct"][idx], chart["positionCnt"][idx]]
|
|
322
297
|
)
|
|
323
298
|
panda_chart = pandas.DataFrame(data=rows, columns=columns)
|
|
324
299
|
|
|
@@ -333,9 +308,7 @@ class Client:
|
|
|
333
308
|
:param to_pandas:
|
|
334
309
|
:return:
|
|
335
310
|
"""
|
|
336
|
-
ret = self._req_with_auth_fallback(
|
|
337
|
-
name="screen backtest", url=self._endpoint + SCREEN_RUN_PATH, json=params
|
|
338
|
-
).json()
|
|
311
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + SCREEN_RUN_PATH, json=params)
|
|
339
312
|
|
|
340
313
|
if to_pandas:
|
|
341
314
|
ret = pandas.DataFrame(data=ret["rows"], columns=ret["columns"])
|
|
@@ -348,9 +321,7 @@ class Client:
|
|
|
348
321
|
:param params:
|
|
349
322
|
:return:
|
|
350
323
|
"""
|
|
351
|
-
return self._req_with_auth_fallback(
|
|
352
|
-
name="universe update", url=self._endpoint + UNIVERSE_PATH, json=params
|
|
353
|
-
).json()
|
|
324
|
+
return self._req_with_auth_fallback(url=self._endpoint + UNIVERSE_PATH, json=params)
|
|
354
325
|
|
|
355
326
|
def rank_update(self, params: dict):
|
|
356
327
|
"""
|
|
@@ -358,9 +329,7 @@ class Client:
|
|
|
358
329
|
:param params:
|
|
359
330
|
:return:
|
|
360
331
|
"""
|
|
361
|
-
return self._req_with_auth_fallback(
|
|
362
|
-
name="ranking system update", url=self._endpoint + RANK_PATH, json=params
|
|
363
|
-
).json()
|
|
332
|
+
return self._req_with_auth_fallback(url=self._endpoint + RANK_PATH, json=params)
|
|
364
333
|
|
|
365
334
|
def data(self, params: dict, to_pandas=False):
|
|
366
335
|
"""
|
|
@@ -369,9 +338,7 @@ class Client:
|
|
|
369
338
|
:param to_pandas:
|
|
370
339
|
:return:
|
|
371
340
|
"""
|
|
372
|
-
ret = self._req_with_auth_fallback(
|
|
373
|
-
name="data", url=self._endpoint + DATA_PATH, json=params
|
|
374
|
-
).json()
|
|
341
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + DATA_PATH, json=params)
|
|
375
342
|
|
|
376
343
|
if to_pandas:
|
|
377
344
|
raw_obj = dict(ret)
|
|
@@ -407,9 +374,7 @@ class Client:
|
|
|
407
374
|
:param to_pandas:
|
|
408
375
|
:return:
|
|
409
376
|
"""
|
|
410
|
-
ret = self._req_with_auth_fallback(
|
|
411
|
-
name="data universe", url=self._endpoint + DATA_UNIVERSE_PATH, json=params
|
|
412
|
-
).json()
|
|
377
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + DATA_UNIVERSE_PATH, json=params)
|
|
413
378
|
|
|
414
379
|
if to_pandas:
|
|
415
380
|
raw_obj = ret
|
|
@@ -417,11 +382,7 @@ class Client:
|
|
|
417
382
|
f_indices = range(len(params["formulas"]))
|
|
418
383
|
if params.get("asOfDt"):
|
|
419
384
|
for formula_idx in f_indices:
|
|
420
|
-
name =
|
|
421
|
-
names[formula_idx]
|
|
422
|
-
if names is not None
|
|
423
|
-
else f"formula{formula_idx + 1}"
|
|
424
|
-
)
|
|
385
|
+
name = names[formula_idx] if names is not None else f"formula{formula_idx + 1}"
|
|
425
386
|
ret[name] = ret["data"][formula_idx]
|
|
426
387
|
del ret["dt"], ret["cost"], ret["quotaRemaining"], ret["data"]
|
|
427
388
|
ret = pandas.DataFrame(ret)
|
|
@@ -437,9 +398,7 @@ class Client:
|
|
|
437
398
|
includeFigi = True
|
|
438
399
|
formulas = defaultdict(list)
|
|
439
400
|
for dtObj in ret["dates"]:
|
|
440
|
-
data["dates"].extend(
|
|
441
|
-
dtObj["dt"] for _ in range(len(dtObj["p123Uids"]))
|
|
442
|
-
)
|
|
401
|
+
data["dates"].extend(dtObj["dt"] for _ in range(len(dtObj["p123Uids"])))
|
|
443
402
|
data["p123Uids"].extend(dtObj["p123Uids"])
|
|
444
403
|
data["tickers"].extend(dtObj["tickers"])
|
|
445
404
|
if includeNames:
|
|
@@ -449,11 +408,7 @@ class Client:
|
|
|
449
408
|
for formula_idx in f_indices:
|
|
450
409
|
formulas[formula_idx].extend(dtObj["data"][formula_idx])
|
|
451
410
|
for formula_idx in f_indices:
|
|
452
|
-
name =
|
|
453
|
-
names[formula_idx]
|
|
454
|
-
if names is not None
|
|
455
|
-
else f"formula{formula_idx + 1}"
|
|
456
|
-
)
|
|
411
|
+
name = names[formula_idx] if names is not None else f"formula{formula_idx + 1}"
|
|
457
412
|
data[name] = formulas[formula_idx]
|
|
458
413
|
ret = pandas.DataFrame(data)
|
|
459
414
|
ret.attrs["raw_obj"] = raw_obj
|
|
@@ -467,11 +422,7 @@ class Client:
|
|
|
467
422
|
:param to_pandas:
|
|
468
423
|
:return:
|
|
469
424
|
"""
|
|
470
|
-
ret = self._req_with_auth_fallback(
|
|
471
|
-
name="ranking system ranks",
|
|
472
|
-
url=self._endpoint + RANK_RANKS_PATH,
|
|
473
|
-
json=params,
|
|
474
|
-
).json()
|
|
425
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + RANK_RANKS_PATH, json=params)
|
|
475
426
|
|
|
476
427
|
if to_pandas:
|
|
477
428
|
names = dict()
|
|
@@ -511,23 +462,63 @@ class Client:
|
|
|
511
462
|
:param params:
|
|
512
463
|
:return:
|
|
513
464
|
"""
|
|
514
|
-
return self._req_with_auth_fallback(
|
|
515
|
-
name="ranking system performance",
|
|
516
|
-
url=self._endpoint + RANK_PERF_PATH,
|
|
517
|
-
json=params,
|
|
518
|
-
).json()
|
|
465
|
+
return self._req_with_auth_fallback(url=self._endpoint + RANK_PERF_PATH, json=params)
|
|
519
466
|
|
|
520
467
|
def rank_touch(self, rank_id: int):
|
|
521
468
|
"""
|
|
522
469
|
Rank touch
|
|
523
470
|
:param rank_id:
|
|
524
471
|
"""
|
|
525
|
-
self._req_with_auth_fallback(
|
|
526
|
-
|
|
472
|
+
self._req_with_auth_fallback(method="POST", url=self._endpoint + RANK_TOUCH_PATH.substitute(id=rank_id))
|
|
473
|
+
|
|
474
|
+
def rank_create(
|
|
475
|
+
self,
|
|
476
|
+
name: str,
|
|
477
|
+
nodes: str,
|
|
478
|
+
*,
|
|
479
|
+
rankingMethod=RankingMethod.PERCENTILE_NA_NEGATIVE,
|
|
480
|
+
type: Literal["Stock", "ETF"] = "Stock",
|
|
481
|
+
currency="USD",
|
|
482
|
+
) -> IdResult:
|
|
483
|
+
"""
|
|
484
|
+
Creates Ranking System
|
|
485
|
+
|
|
486
|
+
:param name: Rank name
|
|
487
|
+
:param nodes: Rank nodes XML
|
|
488
|
+
:param rankingMethod: Ranking method
|
|
489
|
+
:param type: Ranking method type ["Stock", "ETF"]
|
|
490
|
+
:param currency: Ranking method currency. Example: USD
|
|
491
|
+
:return: rank_id:
|
|
492
|
+
"""
|
|
493
|
+
return self._req_with_auth_fallback(
|
|
527
494
|
method="POST",
|
|
528
|
-
url=self._endpoint +
|
|
495
|
+
url=self._endpoint + RANK_CREATE,
|
|
496
|
+
json={"name": name, "nodes": nodes, "currency": currency, "type": type, "rankingMethod": rankingMethod},
|
|
529
497
|
)
|
|
530
498
|
|
|
499
|
+
@overload
|
|
500
|
+
def rank_get(self, *, id: int) -> RankInfoResult: ...
|
|
501
|
+
@overload
|
|
502
|
+
def rank_get(self, *, name: str) -> RankInfoResult: ...
|
|
503
|
+
def rank_get(self, *, id: Optional[int] = None, name: Optional[str] = None) -> RankInfoResult:
|
|
504
|
+
"""
|
|
505
|
+
Gets Rank info
|
|
506
|
+
|
|
507
|
+
:param id: Rank Id
|
|
508
|
+
:param name: Rank name
|
|
509
|
+
:return: RankInfoResult object containing:
|
|
510
|
+
- name (str)
|
|
511
|
+
- id (int)
|
|
512
|
+
- xml (str)
|
|
513
|
+
- currency (str)
|
|
514
|
+
- description (str)
|
|
515
|
+
- rankingMethod (int)
|
|
516
|
+
- type (Literal["Stock", "ETF"])
|
|
517
|
+
- groupUid (int)
|
|
518
|
+
- resolveGroupUid (int)
|
|
519
|
+
"""
|
|
520
|
+
return self._req_with_auth_fallback(method="GET", url=self._endpoint + RANK_PATH, params={"id": id, "name": name})
|
|
521
|
+
|
|
531
522
|
def strategy(self, strategy_id: int):
|
|
532
523
|
"""
|
|
533
524
|
Strategy details
|
|
@@ -535,15 +526,35 @@ class Client:
|
|
|
535
526
|
:return:
|
|
536
527
|
"""
|
|
537
528
|
|
|
529
|
+
return self._req_with_auth_fallback(method="GET", url=self._endpoint + STRATEGY_DETAILS_PATH.substitute(id=strategy_id))
|
|
530
|
+
|
|
531
|
+
def strategy_copy(self, id: int, name: str, type: Optional[Literal["PTF", "SIM"]] = None) -> IdResult:
|
|
532
|
+
"""
|
|
533
|
+
Strategy copy
|
|
534
|
+
|
|
535
|
+
:param id: Strategy Id
|
|
536
|
+
:param name: name of the strategy copy
|
|
537
|
+
:param type: type of the strategy copy ("PTF"|"SIM")
|
|
538
|
+
:return: id
|
|
539
|
+
"""
|
|
540
|
+
return self._req_with_auth_fallback(
|
|
541
|
+
method="POST", url=self._endpoint + STRATEGY_COPY_PATH.substitute(id=id), json={"name": name, "type": type}
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
def book_copy(self, id: int, name: str, type: Optional[Literal["BOOK", "BOOKSIM"]] = None) -> IdResult:
|
|
545
|
+
"""
|
|
546
|
+
Book copy
|
|
547
|
+
|
|
548
|
+
:param book_id:
|
|
549
|
+
:param name: name of the book copy
|
|
550
|
+
:param type: type of the book copy ("BOOK"|"BOOKSIM")
|
|
551
|
+
:return: id
|
|
552
|
+
"""
|
|
538
553
|
return self._req_with_auth_fallback(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
url=self._endpoint + STRATEGY_DETAILS_PATH.substitute(id=strategy_id),
|
|
542
|
-
).json()
|
|
554
|
+
method="POST", url=self._endpoint + BOOK_COPY_PATH.substitute(id=id), json={"name": name, "type": type}
|
|
555
|
+
)
|
|
543
556
|
|
|
544
|
-
def strategy_transactions(
|
|
545
|
-
self, strategy_id: int, start: str, end: str, to_pandas=False
|
|
546
|
-
):
|
|
557
|
+
def strategy_transactions(self, strategy_id: int, start: str, end: str, to_pandas=False):
|
|
547
558
|
"""
|
|
548
559
|
Strategy transactions
|
|
549
560
|
:param strategy_id:
|
|
@@ -553,11 +564,8 @@ class Client:
|
|
|
553
564
|
"""
|
|
554
565
|
|
|
555
566
|
ret = self._req_with_auth_fallback(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
url=self._endpoint + STRATEGY_TRANS_PATH.substitute(id=strategy_id),
|
|
559
|
-
params=[("start", start), ("end", end)],
|
|
560
|
-
).json()
|
|
567
|
+
method="GET", url=self._endpoint + STRATEGY_TRANS_PATH.substitute(id=strategy_id), params=[("start", start), ("end", end)]
|
|
568
|
+
)
|
|
561
569
|
return pandas.DataFrame(ret["trans"]) if to_pandas else ret
|
|
562
570
|
|
|
563
571
|
def strategy_transaction_import(
|
|
@@ -585,18 +593,13 @@ class Client:
|
|
|
585
593
|
get_params.append(("makeRebalDtCurr", "1"))
|
|
586
594
|
|
|
587
595
|
return self._req_with_auth_fallback(
|
|
588
|
-
name="strategy transaction import",
|
|
589
596
|
url=self._endpoint + STRATEGY_TRANS_PATH.substitute(id=strategy_id),
|
|
590
597
|
params=get_params,
|
|
591
598
|
data=data,
|
|
592
599
|
headers={"Content-Type": content_type},
|
|
593
|
-
)
|
|
600
|
+
)
|
|
594
601
|
|
|
595
|
-
def strategy_transaction_delete(
|
|
596
|
-
self,
|
|
597
|
-
strategy_id: int,
|
|
598
|
-
params: List[int],
|
|
599
|
-
):
|
|
602
|
+
def strategy_transaction_delete(self, strategy_id: int, params: List[int]):
|
|
600
603
|
"""
|
|
601
604
|
Strategy transaction delete
|
|
602
605
|
:param strategy_id:
|
|
@@ -604,15 +607,10 @@ class Client:
|
|
|
604
607
|
:return:
|
|
605
608
|
"""
|
|
606
609
|
return self._req_with_auth_fallback(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
url=self._endpoint + STRATEGY_TRANS_PATH.substitute(id=strategy_id),
|
|
610
|
-
json=params,
|
|
611
|
-
).json()
|
|
610
|
+
method="DELETE", url=self._endpoint + STRATEGY_TRANS_PATH.substitute(id=strategy_id), json=params
|
|
611
|
+
)
|
|
612
612
|
|
|
613
|
-
def strategy_holdings(
|
|
614
|
-
self, strategy_id: int, date: Optional[str] = None, to_pandas=False
|
|
615
|
-
):
|
|
613
|
+
def strategy_holdings(self, strategy_id: int, date: Optional[str] = None, to_pandas=False):
|
|
616
614
|
"""
|
|
617
615
|
Strategy holdings
|
|
618
616
|
:param strategy_id:
|
|
@@ -623,29 +621,20 @@ class Client:
|
|
|
623
621
|
get_params = [("date", date)] if date is not None else []
|
|
624
622
|
|
|
625
623
|
ret = self._req_with_auth_fallback(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
url=self._endpoint + STRATEGY_HOLDINGS_PATH.substitute(id=strategy_id),
|
|
629
|
-
params=get_params,
|
|
630
|
-
).json()
|
|
624
|
+
method="GET", url=self._endpoint + STRATEGY_HOLDINGS_PATH.substitute(id=strategy_id), params=get_params
|
|
625
|
+
)
|
|
631
626
|
|
|
632
627
|
return pandas.DataFrame(ret["holdings"]) if to_pandas else ret
|
|
633
|
-
|
|
634
|
-
def strategy_trading_system(
|
|
635
|
-
self, strategy_id: int
|
|
636
|
-
):
|
|
628
|
+
|
|
629
|
+
def strategy_trading_system(self, strategy_id: int):
|
|
637
630
|
"""
|
|
638
631
|
Strategy trading system
|
|
639
632
|
:param strategy_id:
|
|
640
633
|
:return:
|
|
641
634
|
"""
|
|
642
635
|
|
|
643
|
-
return self._req_with_auth_fallback(
|
|
644
|
-
|
|
645
|
-
method="GET",
|
|
646
|
-
url=self._endpoint + STRATEGY_TRADING_SYSTEM_PATH.substitute(id=strategy_id)
|
|
647
|
-
).json()
|
|
648
|
-
|
|
636
|
+
return self._req_with_auth_fallback(method="GET", url=self._endpoint + STRATEGY_TRADING_SYSTEM_PATH.substitute(id=strategy_id))
|
|
637
|
+
|
|
649
638
|
def strategy_trading_system_update(self, strategy_id: int, params: dict):
|
|
650
639
|
"""
|
|
651
640
|
Live strategy trading system update
|
|
@@ -654,12 +643,8 @@ class Client:
|
|
|
654
643
|
:return:
|
|
655
644
|
"""
|
|
656
645
|
|
|
657
|
-
return self._req_with_auth_fallback(
|
|
658
|
-
|
|
659
|
-
url=self._endpoint + STRATEGY_TRADING_SYSTEM_PATH.substitute(id=strategy_id),
|
|
660
|
-
json=params,
|
|
661
|
-
).json()
|
|
662
|
-
|
|
646
|
+
return self._req_with_auth_fallback(url=self._endpoint + STRATEGY_TRADING_SYSTEM_PATH.substitute(id=strategy_id), json=params)
|
|
647
|
+
|
|
663
648
|
def book_trading_system_update(self, strategy_id: int, params: dict):
|
|
664
649
|
"""
|
|
665
650
|
Live book trading system update
|
|
@@ -668,12 +653,8 @@ class Client:
|
|
|
668
653
|
:return:
|
|
669
654
|
"""
|
|
670
655
|
|
|
671
|
-
return self._req_with_auth_fallback(
|
|
672
|
-
|
|
673
|
-
url=self._endpoint + BOOK_TRADING_SYSTEM_PATH.substitute(id=strategy_id),
|
|
674
|
-
json=params,
|
|
675
|
-
).json()
|
|
676
|
-
|
|
656
|
+
return self._req_with_auth_fallback(url=self._endpoint + BOOK_TRADING_SYSTEM_PATH.substitute(id=strategy_id), json=params)
|
|
657
|
+
|
|
677
658
|
def strategy_rerun(self, strategy_id: int, params: dict):
|
|
678
659
|
"""
|
|
679
660
|
Simulated strategy rerun
|
|
@@ -682,12 +663,8 @@ class Client:
|
|
|
682
663
|
:return:
|
|
683
664
|
"""
|
|
684
665
|
|
|
685
|
-
return self._req_with_auth_fallback(
|
|
686
|
-
|
|
687
|
-
url=self._endpoint + SIM_RERUN_PATH.substitute(id=strategy_id),
|
|
688
|
-
json=params,
|
|
689
|
-
).json()
|
|
690
|
-
|
|
666
|
+
return self._req_with_auth_fallback(url=self._endpoint + SIM_RERUN_PATH.substitute(id=strategy_id), json=params)
|
|
667
|
+
|
|
691
668
|
def book_rerun(self, strategy_id: int, params: dict):
|
|
692
669
|
"""
|
|
693
670
|
Simulated book rerun
|
|
@@ -696,11 +673,7 @@ class Client:
|
|
|
696
673
|
:return:
|
|
697
674
|
"""
|
|
698
675
|
|
|
699
|
-
return self._req_with_auth_fallback(
|
|
700
|
-
name="simulated book rerun",
|
|
701
|
-
url=self._endpoint + BOOK_SIM_RERUN_PATH.substitute(id=strategy_id),
|
|
702
|
-
json=params,
|
|
703
|
-
).json()
|
|
676
|
+
return self._req_with_auth_fallback(url=self._endpoint + BOOK_SIM_RERUN_PATH.substitute(id=strategy_id), json=params)
|
|
704
677
|
|
|
705
678
|
def strategy_rebalance(self, strategy_id: int, params: dict):
|
|
706
679
|
"""
|
|
@@ -710,11 +683,7 @@ class Client:
|
|
|
710
683
|
:return:
|
|
711
684
|
"""
|
|
712
685
|
|
|
713
|
-
ret = self._req_with_auth_fallback(
|
|
714
|
-
name="strategy rebalance",
|
|
715
|
-
url=self._endpoint + STRATEGY_REBALANCE_PATH.substitute(id=strategy_id),
|
|
716
|
-
json=params,
|
|
717
|
-
).json()
|
|
686
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + STRATEGY_REBALANCE_PATH.substitute(id=strategy_id), json=params)
|
|
718
687
|
|
|
719
688
|
return ret
|
|
720
689
|
|
|
@@ -726,12 +695,7 @@ class Client:
|
|
|
726
695
|
:return:
|
|
727
696
|
"""
|
|
728
697
|
|
|
729
|
-
ret = self._req_with_auth_fallback(
|
|
730
|
-
name="strategy rebalance commit",
|
|
731
|
-
url=self._endpoint
|
|
732
|
-
+ STRATEGY_REBALANCE_COMMIT_PATH.substitute(id=strategy_id),
|
|
733
|
-
json=params,
|
|
734
|
-
).json()
|
|
698
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + STRATEGY_REBALANCE_COMMIT_PATH.substitute(id=strategy_id), json=params)
|
|
735
699
|
|
|
736
700
|
return ret
|
|
737
701
|
|
|
@@ -739,12 +703,12 @@ class Client:
|
|
|
739
703
|
self,
|
|
740
704
|
factor_id: int,
|
|
741
705
|
data: Union[str, IO[str]],
|
|
742
|
-
column_separator: str = None,
|
|
743
|
-
existing_data: str = None,
|
|
744
|
-
date_format: str = None,
|
|
745
|
-
decimal_separator: str = None,
|
|
746
|
-
ignore_errors: bool = None,
|
|
747
|
-
ignore_duplicates: bool = None,
|
|
706
|
+
column_separator: Union[str, None] = None,
|
|
707
|
+
existing_data: Union[str, None] = None,
|
|
708
|
+
date_format: Union[str, None] = None,
|
|
709
|
+
decimal_separator: Union[str, None] = None,
|
|
710
|
+
ignore_errors: Union[bool, None] = None,
|
|
711
|
+
ignore_duplicates: Union[bool, None] = None,
|
|
748
712
|
):
|
|
749
713
|
"""
|
|
750
714
|
Stock factor data upload
|
|
@@ -770,27 +734,18 @@ class Client:
|
|
|
770
734
|
if ignore_errors is not None:
|
|
771
735
|
get_params.append(("onError", "continue" if ignore_errors else "stop"))
|
|
772
736
|
if ignore_duplicates is not None:
|
|
773
|
-
get_params.append(
|
|
774
|
-
("onDuplicates", "continue" if ignore_duplicates else "stop")
|
|
775
|
-
)
|
|
737
|
+
get_params.append(("onDuplicates", "continue" if ignore_duplicates else "stop"))
|
|
776
738
|
return self._req_with_auth_fallback(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
params=get_params,
|
|
780
|
-
data=data,
|
|
781
|
-
).json()
|
|
739
|
+
url=self._endpoint + STOCK_FACTOR_UPLOAD_PATH.substitute(id=factor_id), params=get_params, data=data
|
|
740
|
+
)
|
|
782
741
|
|
|
783
|
-
def stock_factor_create_update(self, params: dict):
|
|
742
|
+
def stock_factor_create_update(self, params: dict) -> StockFactorResult:
|
|
784
743
|
"""
|
|
785
744
|
Stock factor create/update
|
|
786
745
|
:param params:
|
|
787
746
|
:return:
|
|
788
747
|
"""
|
|
789
|
-
return self._req_with_auth_fallback(
|
|
790
|
-
name="stock factor create/update",
|
|
791
|
-
url=self._endpoint + STOCK_FACTOR_CREATE_UPDATE_PATH,
|
|
792
|
-
json=params,
|
|
793
|
-
).json()
|
|
748
|
+
return self._req_with_auth_fallback(url=self._endpoint + STOCK_FACTOR_CREATE_UPDATE_PATH, json=params)
|
|
794
749
|
|
|
795
750
|
def stock_factor_delete(self, factor_id: int):
|
|
796
751
|
"""
|
|
@@ -798,27 +753,23 @@ class Client:
|
|
|
798
753
|
:param factor_id: id of the data stock factor to delete
|
|
799
754
|
:return:
|
|
800
755
|
"""
|
|
801
|
-
return self._req_with_auth_fallback(
|
|
802
|
-
name="stock factor delete",
|
|
803
|
-
method="DELETE",
|
|
804
|
-
url=self._endpoint + STOCK_FACTOR_DELETE_PATH.substitute(id=factor_id),
|
|
805
|
-
).json()
|
|
756
|
+
return self._req_with_auth_fallback(url=self._endpoint + STOCK_FACTOR_DELETE_PATH.substitute(id=factor_id), method="DELETE")
|
|
806
757
|
|
|
807
758
|
def data_series_upload(
|
|
808
759
|
self,
|
|
809
760
|
series_id: int,
|
|
810
761
|
data: Union[str, IO[str]],
|
|
811
|
-
existing_data: str = None,
|
|
812
|
-
date_format: str = None,
|
|
813
|
-
decimal_separator: str = None,
|
|
814
|
-
ignore_errors: bool = None,
|
|
815
|
-
ignore_duplicates: bool = None,
|
|
816
|
-
contains_header_row: bool = None,
|
|
762
|
+
existing_data: Union[str, None] = None,
|
|
763
|
+
date_format: Union[str, None] = None,
|
|
764
|
+
decimal_separator: Union[str, None] = None,
|
|
765
|
+
ignore_errors: Union[bool, None] = None,
|
|
766
|
+
ignore_duplicates: Union[bool, None] = None,
|
|
767
|
+
contains_header_row: Union[bool, None] = None,
|
|
817
768
|
):
|
|
818
769
|
"""
|
|
819
770
|
Data series upload
|
|
820
771
|
:param series_id:
|
|
821
|
-
:param
|
|
772
|
+
:param data:
|
|
822
773
|
:param existing_data: overwrite, skip or delete
|
|
823
774
|
:param date_format: dd for day, mm for month and yyyy for year, any separator allowed (defaults to yyyy-mm-dd)
|
|
824
775
|
:param decimal_separator: . or ,
|
|
@@ -837,29 +788,20 @@ class Client:
|
|
|
837
788
|
if ignore_errors is not None:
|
|
838
789
|
get_params.append(("onError", "continue" if ignore_errors else "stop"))
|
|
839
790
|
if ignore_duplicates is not None:
|
|
840
|
-
get_params.append(
|
|
841
|
-
("onDuplicates", "continue" if ignore_duplicates else "stop")
|
|
842
|
-
)
|
|
791
|
+
get_params.append(("onDuplicates", "continue" if ignore_duplicates else "stop"))
|
|
843
792
|
if contains_header_row is not None:
|
|
844
793
|
get_params.append(("headerRow", contains_header_row))
|
|
845
794
|
return self._req_with_auth_fallback(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
params=get_params,
|
|
849
|
-
data=data,
|
|
850
|
-
).json()
|
|
795
|
+
url=self._endpoint + DATA_SERIES_UPLOAD_PATH.substitute(id=series_id), params=get_params, data=data
|
|
796
|
+
)
|
|
851
797
|
|
|
852
|
-
def data_series_create_update(self, params: dict):
|
|
798
|
+
def data_series_create_update(self, params: dict) -> DataSeriesResult:
|
|
853
799
|
"""
|
|
854
800
|
Data series create/update
|
|
855
801
|
:param params:
|
|
856
802
|
:return:
|
|
857
803
|
"""
|
|
858
|
-
return self._req_with_auth_fallback(
|
|
859
|
-
name="data series create/update",
|
|
860
|
-
url=self._endpoint + DATA_SERIES_CREATE_UPDATE_PATH,
|
|
861
|
-
json=params,
|
|
862
|
-
).json()
|
|
804
|
+
return self._req_with_auth_fallback(url=self._endpoint + DATA_SERIES_CREATE_UPDATE_PATH, json=params)
|
|
863
805
|
|
|
864
806
|
def data_series_delete(self, series_id: int):
|
|
865
807
|
"""
|
|
@@ -867,11 +809,7 @@ class Client:
|
|
|
867
809
|
:param series_id: id of the data series to delete
|
|
868
810
|
:return:
|
|
869
811
|
"""
|
|
870
|
-
return self._req_with_auth_fallback(
|
|
871
|
-
name="data series delete",
|
|
872
|
-
method="DELETE",
|
|
873
|
-
url=self._endpoint + DATA_SERIES_DELETE_PATH.substitute(id=series_id),
|
|
874
|
-
).json()
|
|
812
|
+
return self._req_with_auth_fallback(method="DELETE", url=self._endpoint + DATA_SERIES_DELETE_PATH.substitute(id=series_id))
|
|
875
813
|
|
|
876
814
|
def get_api_id(self):
|
|
877
815
|
return self._auth_params["apiId"]
|
|
@@ -883,11 +821,7 @@ class Client:
|
|
|
883
821
|
:param params:
|
|
884
822
|
:return:
|
|
885
823
|
"""
|
|
886
|
-
ret = self._req_with_auth_fallback(
|
|
887
|
-
name="AI Factor predict",
|
|
888
|
-
url=self._endpoint + AIFACTOR_PREDICT_PATH.substitute(id=predictor_id),
|
|
889
|
-
json=params,
|
|
890
|
-
).json()
|
|
824
|
+
ret = self._req_with_auth_fallback(url=self._endpoint + AIFACTOR_PREDICT_PATH.substitute(id=predictor_id), json=params)
|
|
891
825
|
|
|
892
826
|
if to_pandas:
|
|
893
827
|
data = {"p123Uid": ret["p123Uids"], "ticker": ret["tickers"]}
|
|
@@ -902,16 +836,7 @@ class Client:
|
|
|
902
836
|
[
|
|
903
837
|
df,
|
|
904
838
|
pandas.DataFrame(ret["data"], columns=ret["features"]),
|
|
905
|
-
*(
|
|
906
|
-
(
|
|
907
|
-
pandas.DataFrame(
|
|
908
|
-
ret["rawData"],
|
|
909
|
-
columns=["raw " + x for x in ret["features"]],
|
|
910
|
-
),
|
|
911
|
-
)
|
|
912
|
-
if "rawData" in ret
|
|
913
|
-
else ()
|
|
914
|
-
),
|
|
839
|
+
*((pandas.DataFrame(ret["rawData"], columns=["raw " + x for x in ret["features"]]),) if "rawData" in ret else ()),
|
|
915
840
|
],
|
|
916
841
|
axis="columns",
|
|
917
842
|
)
|
|
@@ -921,63 +846,84 @@ class Client:
|
|
|
921
846
|
|
|
922
847
|
def stock_factor_download(self, factor_id: int):
|
|
923
848
|
"""
|
|
924
|
-
|
|
925
|
-
:param
|
|
849
|
+
Stock factor download
|
|
850
|
+
:param factor_id:
|
|
926
851
|
:return:
|
|
927
852
|
"""
|
|
928
|
-
return self._req_with_auth_fallback(
|
|
929
|
-
|
|
930
|
-
method="GET",
|
|
931
|
-
url=self._endpoint + STOCK_FACTOR_DOWNLOAD_PATH.substitute(id=factor_id),
|
|
932
|
-
).json()
|
|
933
|
-
|
|
853
|
+
return self._req_with_auth_fallback(method="GET", url=self._endpoint + STOCK_FACTOR_DOWNLOAD_PATH.substitute(id=factor_id))
|
|
854
|
+
|
|
934
855
|
def data_prices(self, identifier: Union[int, str], start: str, end: Optional[str], to_pandas=False):
|
|
935
|
-
"""
|
|
936
|
-
"""
|
|
856
|
+
""" """
|
|
937
857
|
get_params = [("start", start)]
|
|
938
858
|
if end is not None:
|
|
939
859
|
get_params.append(("end", end))
|
|
940
860
|
ret = self._req_with_auth_fallback(
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
url=self._endpoint + DATA_PRICES_PATH.substitute(identifier=identifier),
|
|
944
|
-
params=get_params
|
|
945
|
-
).json()
|
|
861
|
+
method="GET", url=self._endpoint + DATA_PRICES_PATH.substitute(identifier=identifier), params=get_params
|
|
862
|
+
)
|
|
946
863
|
return pandas.DataFrame(ret["prices"]) if to_pandas else ret
|
|
947
|
-
|
|
948
864
|
|
|
949
|
-
@overload
|
|
950
|
-
def stock_factor_info(self, *,
|
|
951
|
-
|
|
952
|
-
@overload
|
|
953
|
-
def stock_factor_info(self, *,
|
|
954
|
-
|
|
955
|
-
def stock_factor_info(self, *,
|
|
865
|
+
@overload
|
|
866
|
+
def stock_factor_info(self, *, id: int) -> StockFactorInfoResult: ...
|
|
867
|
+
@overload
|
|
868
|
+
@deprecated("use overload accepting `id` parameter instead")
|
|
869
|
+
def stock_factor_info(self, *, factor_id: int) -> StockFactorInfoResult: ...
|
|
870
|
+
@overload
|
|
871
|
+
def stock_factor_info(self, *, name: str) -> StockFactorInfoResult: ...
|
|
872
|
+
def stock_factor_info(
|
|
873
|
+
self, *, id: Optional[int] = None, factor_id: Optional[int] = None, name: Optional[str] = None
|
|
874
|
+
) -> StockFactorInfoResult:
|
|
956
875
|
"""
|
|
957
876
|
Stock factor info, only specify factor_id or name
|
|
958
877
|
"""
|
|
878
|
+
if id is not None:
|
|
879
|
+
params = {"id": id}
|
|
880
|
+
elif factor_id is not None:
|
|
881
|
+
params = {"id": factor_id}
|
|
882
|
+
else:
|
|
883
|
+
params = {"name": name}
|
|
884
|
+
return self._req_with_auth_fallback(method="GET", url=self._endpoint + STOCK_FACTOR_INFO_PATH, params=params)
|
|
885
|
+
|
|
886
|
+
@overload
|
|
887
|
+
def data_series_info(self, *, id: int) -> DataSeriesInfoResult: ...
|
|
888
|
+
@overload
|
|
889
|
+
def data_series_info(self, *, name: str) -> DataSeriesInfoResult: ...
|
|
890
|
+
def data_series_info(self, *, id: Optional[int] = None, name: Optional[str] = None) -> DataSeriesInfoResult:
|
|
891
|
+
"""
|
|
892
|
+
Data series info, only specify factor_id or name
|
|
893
|
+
"""
|
|
894
|
+
return self._req_with_auth_fallback(
|
|
895
|
+
method="GET", url=self._endpoint + DATA_SERIES_INFO_PATH, params={"name": name} if id is None else {"id": id}
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
@overload
|
|
899
|
+
def strategy_info(self, *, id: int) -> StrategyInfoResult: ...
|
|
900
|
+
@overload
|
|
901
|
+
def strategy_info(self, *, name: str) -> StrategyInfoResult: ...
|
|
902
|
+
def strategy_info(self, *, id: Optional[int] = None, name: Optional[str] = None) -> StrategyInfoResult:
|
|
903
|
+
"""
|
|
904
|
+
Strategy info, only specify factor_id or name
|
|
905
|
+
"""
|
|
959
906
|
return self._req_with_auth_fallback(
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
url=self._endpoint + STOCK_FACTOR_INFO_PATH,
|
|
963
|
-
params={"name": name} if factor_id is None else {"id": factor_id}
|
|
964
|
-
).json()
|
|
907
|
+
method="GET", url=self._endpoint + STRATEGY_INFO_PATH, params={"name": name} if id is None else {"id": id}
|
|
908
|
+
)
|
|
965
909
|
|
|
966
910
|
|
|
967
|
-
def req_with_retry(req: Callable[..., requests.Response], max_tries=
|
|
911
|
+
def req_with_retry(req: Callable[..., requests.Response], max_tries=5, **kwargs):
|
|
968
912
|
tries = 0
|
|
969
|
-
|
|
970
|
-
max_tries = 5
|
|
971
|
-
resp = None
|
|
972
|
-
while tries < max_tries:
|
|
913
|
+
while True:
|
|
973
914
|
if tries > 0:
|
|
974
915
|
time.sleep(2 * tries)
|
|
975
916
|
try:
|
|
976
917
|
resp = req(**kwargs)
|
|
977
|
-
|
|
978
|
-
break
|
|
918
|
+
exception = None
|
|
979
919
|
except requests.ConnectionError as e:
|
|
980
|
-
|
|
981
|
-
|
|
920
|
+
resp = None
|
|
921
|
+
exception = e
|
|
922
|
+
if resp is not None:
|
|
923
|
+
if resp.status_code < 500:
|
|
924
|
+
return resp
|
|
925
|
+
resp.close()
|
|
982
926
|
tries += 1
|
|
983
|
-
|
|
927
|
+
if tries >= max_tries:
|
|
928
|
+
break
|
|
929
|
+
raise ClientException("Cannot connect to API", exception=exception)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from typing import Literal, Optional, TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SharedResult(TypedDict):
|
|
6
|
+
cost: int
|
|
7
|
+
quotaRemaining: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IdResult(SharedResult):
|
|
11
|
+
id: int
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataSeriesResult(SharedResult):
|
|
15
|
+
dataSeriesId: int
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DataSeriesInfoResult(DataSeriesResult):
|
|
19
|
+
name: str
|
|
20
|
+
description: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StockFactorResult(SharedResult):
|
|
24
|
+
factorId: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StockFactorInfoResult(StockFactorResult):
|
|
28
|
+
name: str
|
|
29
|
+
description: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RankInfoResult(SharedResult):
|
|
33
|
+
name: str
|
|
34
|
+
id: int
|
|
35
|
+
xml: str
|
|
36
|
+
currency: str
|
|
37
|
+
rankingMethod: int
|
|
38
|
+
type: Literal["Stock", "ETF"]
|
|
39
|
+
description: Optional[str]
|
|
40
|
+
groupUid: int
|
|
41
|
+
resolveGroupUid: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RankingMethod(IntEnum):
|
|
45
|
+
PERCENTILE_NA_NEGATIVE = 2
|
|
46
|
+
PERCENTILE_NA_NEUTRAL = 4
|
|
47
|
+
NORMAL_DISTRIBUTION = 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StrategyInfoResult(TypedDict):
|
|
51
|
+
strategyId: int
|
|
52
|
+
name: str
|
|
53
|
+
description: str
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[tool.black]
|
|
2
|
+
line-length = 140
|
|
3
|
+
skip-magic-trailing-comma = true
|
|
4
|
+
|
|
5
|
+
[tool.pyright]
|
|
6
|
+
pythonVersion = "3.6"
|
|
7
|
+
typeCheckingMode = "basic"
|
|
8
|
+
deprecateTypingAliases = true
|
|
9
|
+
disableBytesTypePromotions = true
|
|
10
|
+
strictDictionaryInference = true
|
|
11
|
+
strictListInference = true
|
|
12
|
+
strictSetInference = true
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="p123api",
|
|
8
|
-
version="2.
|
|
8
|
+
version="2.4.0",
|
|
9
9
|
author="Portfolio123",
|
|
10
10
|
author_email="info@portfolio123.com",
|
|
11
11
|
description="Portfolio123 API wrapper",
|
|
@@ -13,11 +13,7 @@ setuptools.setup(
|
|
|
13
13
|
long_description_content_type="text/markdown",
|
|
14
14
|
url="https://github.com/portfolio-123/p123api-py",
|
|
15
15
|
packages=setuptools.find_packages(),
|
|
16
|
-
classifiers=[
|
|
17
|
-
"Programming Language :: Python :: 3",
|
|
18
|
-
"License :: OSI Approved :: MIT License",
|
|
19
|
-
"Operating System :: OS Independent",
|
|
20
|
-
],
|
|
16
|
+
classifiers=["Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent"],
|
|
21
17
|
python_requires=">=3.6",
|
|
22
18
|
install_requires=["pandas", "requests"],
|
|
23
19
|
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .client import Client, ClientException
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|