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