jcdata 0.1.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.
- jcdata-0.1.0/PKG-INFO +63 -0
- jcdata-0.1.0/README.md +50 -0
- jcdata-0.1.0/__init__.py +63 -0
- jcdata-0.1.0/_http.py +187 -0
- jcdata-0.1.0/config.py +28 -0
- jcdata-0.1.0/data.py +352 -0
- jcdata-0.1.0/errors.py +28 -0
- jcdata-0.1.0/jcdata.egg-info/PKG-INFO +63 -0
- jcdata-0.1.0/jcdata.egg-info/SOURCES.txt +17 -0
- jcdata-0.1.0/jcdata.egg-info/dependency_links.txt +1 -0
- jcdata-0.1.0/jcdata.egg-info/requires.txt +5 -0
- jcdata-0.1.0/jcdata.egg-info/top_level.txt +1 -0
- jcdata-0.1.0/pyproject.toml +23 -0
- jcdata-0.1.0/setup.cfg +4 -0
jcdata-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jcdata
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: JCDATA
|
|
5
|
+
Author: JCDATA
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx>=0.24
|
|
10
|
+
Requires-Dist: pandas>=1.3
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# jcdata
|
|
15
|
+
|
|
16
|
+
连接 JCDATA 数据服务,详情可查看官网:[https://jicequant.com/](https://jicequant.com/)
|
|
17
|
+
|
|
18
|
+
用户注册后即可在个人中心使用一定额度的数据服务
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install jcdata
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 配置
|
|
27
|
+
|
|
28
|
+
访问令牌任选一种方式提供:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import jcdata
|
|
32
|
+
|
|
33
|
+
jcdata.login(token="你的访问令牌")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
或设置环境变量后直接 `jcdata.login()`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export JCDATA_TOKEN="你的访问令牌"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
也可安装后编辑包内 **`jcdata/config.py`** 中的 `TOKEN`,再执行 `jcdata.login()`。
|
|
43
|
+
|
|
44
|
+
## 使用
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import jcdata
|
|
48
|
+
|
|
49
|
+
jcdata.login(token="你的访问令牌")
|
|
50
|
+
print(jcdata.whoami())
|
|
51
|
+
|
|
52
|
+
df = jcdata.get_market_data("600000.SH", "2025-01-01", "2025-01-31")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 主要接口
|
|
56
|
+
|
|
57
|
+
- **行情**:`get_market_data`
|
|
58
|
+
- **基础信息**:`get_instrument`、`get_price_limits`、`get_adj_factor`、`get_turn_rate`、`get_trading_status`
|
|
59
|
+
- **期货/转债**:`get_settlement`、`get_conv_bond_terms`、`get_future_contract`
|
|
60
|
+
- **股票因子**:`get_valuation`、`get_market_value`、`get_finance`
|
|
61
|
+
- **其它**:`get_trade_dates`、`get_index_constituents`、`get_option_bars`、`get_option_info`、`get_sectors`(板块快照,`ts` 为库内占位)
|
|
62
|
+
|
|
63
|
+
代码格式为 Wind 风格,如 `600000.SH`。
|
jcdata-0.1.0/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# jcdata
|
|
2
|
+
|
|
3
|
+
连接 JCDATA 数据服务,详情可查看官网:[https://jicequant.com/](https://jicequant.com/)
|
|
4
|
+
|
|
5
|
+
用户注册后即可在个人中心使用一定额度的数据服务
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install jcdata
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 配置
|
|
14
|
+
|
|
15
|
+
访问令牌任选一种方式提供:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import jcdata
|
|
19
|
+
|
|
20
|
+
jcdata.login(token="你的访问令牌")
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
或设置环境变量后直接 `jcdata.login()`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export JCDATA_TOKEN="你的访问令牌"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
也可安装后编辑包内 **`jcdata/config.py`** 中的 `TOKEN`,再执行 `jcdata.login()`。
|
|
30
|
+
|
|
31
|
+
## 使用
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import jcdata
|
|
35
|
+
|
|
36
|
+
jcdata.login(token="你的访问令牌")
|
|
37
|
+
print(jcdata.whoami())
|
|
38
|
+
|
|
39
|
+
df = jcdata.get_market_data("600000.SH", "2025-01-01", "2025-01-31")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 主要接口
|
|
43
|
+
|
|
44
|
+
- **行情**:`get_market_data`
|
|
45
|
+
- **基础信息**:`get_instrument`、`get_price_limits`、`get_adj_factor`、`get_turn_rate`、`get_trading_status`
|
|
46
|
+
- **期货/转债**:`get_settlement`、`get_conv_bond_terms`、`get_future_contract`
|
|
47
|
+
- **股票因子**:`get_valuation`、`get_market_value`、`get_finance`
|
|
48
|
+
- **其它**:`get_trade_dates`、`get_index_constituents`、`get_option_bars`、`get_option_info`、`get_sectors`(板块快照,`ts` 为库内占位)
|
|
49
|
+
|
|
50
|
+
代码格式为 Wind 风格,如 `600000.SH`。
|
jcdata-0.1.0/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
JCDATA 客户端:连接数据服务后取历史行情与因子数据。
|
|
4
|
+
|
|
5
|
+
使用前调用 ``jcdata.login(token=...)``(或设置环境变量 ``JCDATA_TOKEN``)::
|
|
6
|
+
|
|
7
|
+
import jcdata
|
|
8
|
+
|
|
9
|
+
jcdata.login(token="你的访问令牌")
|
|
10
|
+
df = jcdata.get_market_data("600000.SH", "2025-01-01", "2025-01-31")
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from jcdata._http import current_user, login, whoami
|
|
15
|
+
from jcdata.errors import CellQuotaExceededError, ErrorCode, RowQuotaExceededError
|
|
16
|
+
from jcdata.data import (
|
|
17
|
+
get_adj_factor,
|
|
18
|
+
get_conv_bond_terms,
|
|
19
|
+
get_finance,
|
|
20
|
+
get_future_contract,
|
|
21
|
+
get_index_constituents,
|
|
22
|
+
get_instrument,
|
|
23
|
+
get_market_data,
|
|
24
|
+
get_market_value,
|
|
25
|
+
get_option_bars,
|
|
26
|
+
get_option_info,
|
|
27
|
+
get_price_limits,
|
|
28
|
+
get_sectors,
|
|
29
|
+
get_settlement,
|
|
30
|
+
get_trade_dates,
|
|
31
|
+
get_trading_status,
|
|
32
|
+
get_turn_rate,
|
|
33
|
+
get_valuation,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__version__ = "0.1.0"
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"__version__",
|
|
40
|
+
"login",
|
|
41
|
+
"whoami",
|
|
42
|
+
"current_user",
|
|
43
|
+
"ErrorCode",
|
|
44
|
+
"CellQuotaExceededError",
|
|
45
|
+
"RowQuotaExceededError",
|
|
46
|
+
"get_market_data",
|
|
47
|
+
"get_instrument",
|
|
48
|
+
"get_price_limits",
|
|
49
|
+
"get_adj_factor",
|
|
50
|
+
"get_turn_rate",
|
|
51
|
+
"get_trading_status",
|
|
52
|
+
"get_settlement",
|
|
53
|
+
"get_conv_bond_terms",
|
|
54
|
+
"get_future_contract",
|
|
55
|
+
"get_valuation",
|
|
56
|
+
"get_market_value",
|
|
57
|
+
"get_finance",
|
|
58
|
+
"get_trade_dates",
|
|
59
|
+
"get_index_constituents",
|
|
60
|
+
"get_option_bars",
|
|
61
|
+
"get_option_info",
|
|
62
|
+
"get_sectors",
|
|
63
|
+
]
|
jcdata-0.1.0/_http.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""内部 HTTP 传输(不对外导出)。"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from jcdata import config
|
|
12
|
+
from jcdata.errors import CellQuotaExceededError, ErrorCode
|
|
13
|
+
|
|
14
|
+
_client: httpx.Client | None = None
|
|
15
|
+
_user_name: str | None = None
|
|
16
|
+
_authenticated: bool = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _build_base_url(url: str, port: int | None = None) -> str:
|
|
20
|
+
"""构建完整的 base_url,支持端口配置。"""
|
|
21
|
+
url = url.rstrip("/")
|
|
22
|
+
if port is not None:
|
|
23
|
+
if "://" in url:
|
|
24
|
+
protocol, rest = url.split("://", 1)
|
|
25
|
+
host = rest.split(":")[0].split("/")[0]
|
|
26
|
+
return f"{protocol}://{host}:{port}"
|
|
27
|
+
else:
|
|
28
|
+
host = url.split(":")[0].split("/")[0]
|
|
29
|
+
return f"{host}:{port}"
|
|
30
|
+
return url
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _resolve_token(token: str | None) -> str:
|
|
34
|
+
"""解析访问令牌,优先级:参数 > 环境变量 > config.py。"""
|
|
35
|
+
key = token if token is not None else os.environ.get("JCDATA_TOKEN") or config.TOKEN
|
|
36
|
+
key = str(key or "").strip()
|
|
37
|
+
if not key:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"请设置访问令牌:login(token=...)、环境变量 JCDATA_TOKEN,或 jcdata/config.py 中的 TOKEN"
|
|
40
|
+
)
|
|
41
|
+
return key
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _resolve_base_url(base_url: str | None) -> str:
|
|
45
|
+
"""解析服务地址,优先级:参数 > 环境变量 > config.py。"""
|
|
46
|
+
return (base_url or os.environ.get("JCDATA_BASE_URL") or config.BASE_URL).strip()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _resolve_port(port: int | None) -> int | None:
|
|
50
|
+
"""解析端口,优先级:参数 > 环境变量 > config.py。"""
|
|
51
|
+
if port is not None:
|
|
52
|
+
return port
|
|
53
|
+
env_port = os.environ.get("JCDATA_PORT")
|
|
54
|
+
if env_port:
|
|
55
|
+
try:
|
|
56
|
+
return int(env_port)
|
|
57
|
+
except ValueError:
|
|
58
|
+
pass
|
|
59
|
+
return config.PORT
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _resolve_timeout(timeout_sec: float | None) -> float:
|
|
63
|
+
"""解析超时时间,优先级:参数 > config.py。"""
|
|
64
|
+
return timeout_sec if timeout_sec is not None else config.TIMEOUT_SEC
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolve_verify(verify: bool | None) -> bool:
|
|
68
|
+
"""解析是否验证令牌,优先级:参数 > config.py。"""
|
|
69
|
+
return verify if verify is not None else config.VERIFY_ON_LOGIN
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def login(
|
|
73
|
+
base_url: str | None = None,
|
|
74
|
+
*,
|
|
75
|
+
token: str | None = None,
|
|
76
|
+
port: int | None = None,
|
|
77
|
+
timeout_sec: float | None = None,
|
|
78
|
+
verify: bool | None = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""
|
|
81
|
+
连接数据服务。
|
|
82
|
+
|
|
83
|
+
参数:
|
|
84
|
+
base_url: API 服务地址,默认使用 config.BASE_URL
|
|
85
|
+
token: 访问令牌,优先级:参数 > 环境变量 JCDATA_TOKEN > config.TOKEN
|
|
86
|
+
port: API 服务端口,优先级:参数 > 环境变量 JCDATA_PORT > config.PORT
|
|
87
|
+
timeout_sec: 请求超时时间(秒),默认使用 config.TIMEOUT_SEC
|
|
88
|
+
verify: 是否验证令牌有效性,默认使用 config.VERIFY_ON_LOGIN
|
|
89
|
+
|
|
90
|
+
示例:
|
|
91
|
+
>>> jcdata.login(token="your_token")
|
|
92
|
+
>>> jcdata.login(base_url="http://localhost", port=8000, token="your_token")
|
|
93
|
+
"""
|
|
94
|
+
global _client, _user_name, _authenticated
|
|
95
|
+
|
|
96
|
+
url = _resolve_base_url(base_url)
|
|
97
|
+
key = _resolve_token(token)
|
|
98
|
+
resolved_port = _resolve_port(port)
|
|
99
|
+
to = _resolve_timeout(timeout_sec)
|
|
100
|
+
do_verify = _resolve_verify(verify)
|
|
101
|
+
|
|
102
|
+
headers = {
|
|
103
|
+
"Authorization": f"Bearer {key}",
|
|
104
|
+
"X-Api-Key": key,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if _client is not None:
|
|
108
|
+
_client.close()
|
|
109
|
+
|
|
110
|
+
final_url = _build_base_url(url, resolved_port)
|
|
111
|
+
_client = httpx.Client(base_url=final_url, timeout=to, headers=headers)
|
|
112
|
+
|
|
113
|
+
_user_name = None
|
|
114
|
+
_authenticated = False
|
|
115
|
+
if do_verify:
|
|
116
|
+
_user_name, _authenticated = _whoami_request()
|
|
117
|
+
if not _authenticated:
|
|
118
|
+
raise PermissionError("令牌校验未通过,请检查访问令牌")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def whoami() -> str | None:
|
|
122
|
+
"""当前登录用户名;未登录或未鉴权时返回 None。"""
|
|
123
|
+
global _user_name, _authenticated
|
|
124
|
+
if _client is None:
|
|
125
|
+
return None
|
|
126
|
+
_user_name, _authenticated = _whoami_request()
|
|
127
|
+
return _user_name if _authenticated else None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def current_user() -> str | None:
|
|
131
|
+
"""最近一次 login 成功后的用户名。"""
|
|
132
|
+
return _user_name
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _whoami_request() -> tuple[str | None, bool]:
|
|
136
|
+
r = _client.get("/api/v1/auth/whoami") # type: ignore[union-attr]
|
|
137
|
+
if r.status_code == 401:
|
|
138
|
+
raise PermissionError("缺少令牌,请先 jcdata.login(token=...)")
|
|
139
|
+
if r.status_code == 403:
|
|
140
|
+
raise PermissionError("令牌无效或已过期")
|
|
141
|
+
r.raise_for_status()
|
|
142
|
+
body = r.json()
|
|
143
|
+
return body.get("user_name"), bool(body.get("authenticated"))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _client_or_raise() -> httpx.Client:
|
|
147
|
+
if _client is None:
|
|
148
|
+
raise RuntimeError("请先调用 jcdata.login()")
|
|
149
|
+
return _client
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _parse_error_detail(r: httpx.Response) -> dict | str | None:
|
|
153
|
+
try:
|
|
154
|
+
body = r.json()
|
|
155
|
+
except Exception:
|
|
156
|
+
return None
|
|
157
|
+
detail = body.get("detail")
|
|
158
|
+
return detail if isinstance(detail, (dict, str)) else None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _raise_for_detail(status_code: int, detail: dict | str | None) -> None:
|
|
162
|
+
if isinstance(detail, dict) and detail.get("code") == ErrorCode.TOKEN_CELL_QUOTA_EXCEEDED:
|
|
163
|
+
raise CellQuotaExceededError(
|
|
164
|
+
str(detail.get("message") or "本周非空单元格配额已用尽"),
|
|
165
|
+
code=str(detail.get("code") or ErrorCode.TOKEN_CELL_QUOTA_EXCEEDED),
|
|
166
|
+
cell_quota=detail.get("cell_quota"),
|
|
167
|
+
cell_quota_remaining=detail.get("cell_quota_remaining"),
|
|
168
|
+
)
|
|
169
|
+
if status_code == 401:
|
|
170
|
+
raise PermissionError("未授权,请先 jcdata.login()")
|
|
171
|
+
if status_code == 403:
|
|
172
|
+
raise PermissionError("无权访问")
|
|
173
|
+
if status_code == 429 and isinstance(detail, dict):
|
|
174
|
+
raise PermissionError(str(detail.get("message") or "请求过于频繁"))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def fetch(path: str, params: dict[str, Any]) -> pd.DataFrame:
|
|
178
|
+
"""请求取数接口,返回 DataFrame。"""
|
|
179
|
+
r = _client_or_raise().get(
|
|
180
|
+
path,
|
|
181
|
+
params={k: v for k, v in params.items() if v is not None},
|
|
182
|
+
)
|
|
183
|
+
detail = _parse_error_detail(r)
|
|
184
|
+
if r.status_code in (401, 403, 429):
|
|
185
|
+
_raise_for_detail(r.status_code, detail)
|
|
186
|
+
r.raise_for_status()
|
|
187
|
+
return pd.DataFrame(r.json().get("rows") or [])
|
jcdata-0.1.0/config.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
jcdata 客户端配置。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
# ==================== 服务端配置 ====================
|
|
9
|
+
|
|
10
|
+
# 数据服务方发放的访问令牌
|
|
11
|
+
TOKEN = ""
|
|
12
|
+
|
|
13
|
+
# API 服务地址
|
|
14
|
+
BASE_URL = "https://api.jicequant.com"
|
|
15
|
+
|
|
16
|
+
# API 服务端口
|
|
17
|
+
PORT = None
|
|
18
|
+
|
|
19
|
+
# 客户端请求超时时间(秒)
|
|
20
|
+
TIMEOUT_SEC = 60.0
|
|
21
|
+
|
|
22
|
+
# ==================== 客户端行为配置 ====================
|
|
23
|
+
|
|
24
|
+
# 是否在 login 时验证令牌有效性
|
|
25
|
+
VERIFY_ON_LOGIN = True
|
|
26
|
+
|
|
27
|
+
# 默认单次查询行数限制(防止误操作拉取过多数据)
|
|
28
|
+
DEFAULT_ROW_LIMIT = 100000
|
jcdata-0.1.0/data.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""取数接口:login 后调用,返回 pandas.DataFrame。"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from jcdata._http import fetch
|
|
10
|
+
|
|
11
|
+
_DATA = "/api/v1/data"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _symbols(symbol: str | list[str]) -> str:
|
|
15
|
+
return symbol if isinstance(symbol, str) else ",".join(symbol)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _fields(fields: str | list[str] | None) -> str | None:
|
|
19
|
+
if fields is None:
|
|
20
|
+
return None
|
|
21
|
+
return fields if isinstance(fields, str) else ",".join(fields)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _symbol_query(
|
|
25
|
+
path: str,
|
|
26
|
+
symbol: str | list[str],
|
|
27
|
+
start_date: str,
|
|
28
|
+
end_date: str,
|
|
29
|
+
*,
|
|
30
|
+
asset_type: str | None = None,
|
|
31
|
+
fields: str | list[str] | None = None,
|
|
32
|
+
row_limit: int | None = None,
|
|
33
|
+
) -> pd.DataFrame:
|
|
34
|
+
return fetch(
|
|
35
|
+
f"{_DATA}{path}",
|
|
36
|
+
{
|
|
37
|
+
"symbol": _symbols(symbol),
|
|
38
|
+
"start_date": start_date,
|
|
39
|
+
"end_date": end_date,
|
|
40
|
+
"asset_type": asset_type,
|
|
41
|
+
"fields": _fields(fields),
|
|
42
|
+
"row_limit": row_limit,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_market_data(
|
|
48
|
+
symbol: str | list[str],
|
|
49
|
+
start_date: str,
|
|
50
|
+
end_date: str,
|
|
51
|
+
*,
|
|
52
|
+
asset_type: str | None = None,
|
|
53
|
+
fields: str | list[str] | None = None,
|
|
54
|
+
row_limit: int | None = None,
|
|
55
|
+
dividend_type: str | None = None,
|
|
56
|
+
adjust_date: str | None = None,
|
|
57
|
+
) -> pd.DataFrame:
|
|
58
|
+
"""
|
|
59
|
+
日 K 线(开高低收量额)。代码示例:``600000.SH``。
|
|
60
|
+
|
|
61
|
+
``dividend_type``:``none``/不复权、``front``/前复权、``back``/后复权、``point``/定点复权。
|
|
62
|
+
库内为未复权价;股票/ETF/可转债用 ``adj_factor`` 换算 OHLC,期货/期权始终不复权。
|
|
63
|
+
定点复权须传 ``adjust_date``;前复权默认以 ``end_date`` 为基准日。
|
|
64
|
+
"""
|
|
65
|
+
return fetch(
|
|
66
|
+
f"{_DATA}/bars",
|
|
67
|
+
{
|
|
68
|
+
"symbol": _symbols(symbol),
|
|
69
|
+
"start_date": start_date,
|
|
70
|
+
"end_date": end_date,
|
|
71
|
+
"asset_type": asset_type,
|
|
72
|
+
"fields": _fields(fields),
|
|
73
|
+
"row_limit": row_limit,
|
|
74
|
+
"dividend_type": dividend_type,
|
|
75
|
+
"adjust_date": adjust_date,
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_instrument(
|
|
81
|
+
symbol: str | list[str],
|
|
82
|
+
start_date: str,
|
|
83
|
+
end_date: str,
|
|
84
|
+
*,
|
|
85
|
+
asset_type: str | None = None,
|
|
86
|
+
fields: str | list[str] | None = None,
|
|
87
|
+
row_limit: int | None = None,
|
|
88
|
+
) -> pd.DataFrame:
|
|
89
|
+
"""证券档案:名称、板块、上市日等。"""
|
|
90
|
+
return _symbol_query(
|
|
91
|
+
"/instrument", symbol, start_date, end_date,
|
|
92
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_price_limits(
|
|
97
|
+
symbol: str | list[str],
|
|
98
|
+
start_date: str,
|
|
99
|
+
end_date: str,
|
|
100
|
+
*,
|
|
101
|
+
asset_type: str | None = None,
|
|
102
|
+
fields: str | list[str] | None = None,
|
|
103
|
+
row_limit: int | None = None,
|
|
104
|
+
) -> pd.DataFrame:
|
|
105
|
+
"""涨跌停价格。"""
|
|
106
|
+
return _symbol_query(
|
|
107
|
+
"/price_limits", symbol, start_date, end_date,
|
|
108
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_adj_factor(
|
|
113
|
+
symbol: str | list[str],
|
|
114
|
+
start_date: str,
|
|
115
|
+
end_date: str,
|
|
116
|
+
*,
|
|
117
|
+
asset_type: str | None = None,
|
|
118
|
+
fields: str | list[str] | None = None,
|
|
119
|
+
row_limit: int | None = None,
|
|
120
|
+
) -> pd.DataFrame:
|
|
121
|
+
"""复权因子。"""
|
|
122
|
+
return _symbol_query(
|
|
123
|
+
"/adj_factor", symbol, start_date, end_date,
|
|
124
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_turn_rate(
|
|
129
|
+
symbol: str | list[str],
|
|
130
|
+
start_date: str,
|
|
131
|
+
end_date: str,
|
|
132
|
+
*,
|
|
133
|
+
asset_type: str | None = None,
|
|
134
|
+
fields: str | list[str] | None = None,
|
|
135
|
+
row_limit: int | None = None,
|
|
136
|
+
) -> pd.DataFrame:
|
|
137
|
+
"""日换手率。"""
|
|
138
|
+
return _symbol_query(
|
|
139
|
+
"/turn_rate", symbol, start_date, end_date,
|
|
140
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_trading_status(
|
|
145
|
+
symbol: str | list[str],
|
|
146
|
+
start_date: str,
|
|
147
|
+
end_date: str,
|
|
148
|
+
*,
|
|
149
|
+
asset_type: str | None = None,
|
|
150
|
+
fields: str | list[str] | None = None,
|
|
151
|
+
row_limit: int | None = None,
|
|
152
|
+
) -> pd.DataFrame:
|
|
153
|
+
"""停牌、ST 等交易状态。"""
|
|
154
|
+
return _symbol_query(
|
|
155
|
+
"/trading_status", symbol, start_date, end_date,
|
|
156
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_settlement(
|
|
161
|
+
symbol: str | list[str],
|
|
162
|
+
start_date: str,
|
|
163
|
+
end_date: str,
|
|
164
|
+
*,
|
|
165
|
+
asset_type: str | None = None,
|
|
166
|
+
fields: str | list[str] | None = None,
|
|
167
|
+
row_limit: int | None = None,
|
|
168
|
+
) -> pd.DataFrame:
|
|
169
|
+
"""结算价、持仓(仅可转债、期货)。"""
|
|
170
|
+
return _symbol_query(
|
|
171
|
+
"/settlement", symbol, start_date, end_date,
|
|
172
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_conv_bond_terms(
|
|
177
|
+
symbol: str | list[str],
|
|
178
|
+
start_date: str,
|
|
179
|
+
end_date: str,
|
|
180
|
+
*,
|
|
181
|
+
asset_type: str | None = None,
|
|
182
|
+
fields: str | list[str] | None = None,
|
|
183
|
+
row_limit: int | None = None,
|
|
184
|
+
) -> pd.DataFrame:
|
|
185
|
+
"""可转债条款(仅可转债)。"""
|
|
186
|
+
return _symbol_query(
|
|
187
|
+
"/conv_bond_terms", symbol, start_date, end_date,
|
|
188
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_future_contract(
|
|
193
|
+
symbol: str | list[str],
|
|
194
|
+
start_date: str,
|
|
195
|
+
end_date: str,
|
|
196
|
+
*,
|
|
197
|
+
asset_type: str | None = None,
|
|
198
|
+
fields: str | list[str] | None = None,
|
|
199
|
+
row_limit: int | None = None,
|
|
200
|
+
) -> pd.DataFrame:
|
|
201
|
+
"""期货合约参数(仅期货)。"""
|
|
202
|
+
return _symbol_query(
|
|
203
|
+
"/future_contract", symbol, start_date, end_date,
|
|
204
|
+
asset_type=asset_type, fields=fields, row_limit=row_limit,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_valuation(
|
|
209
|
+
symbol: str | list[str],
|
|
210
|
+
start_date: str,
|
|
211
|
+
end_date: str,
|
|
212
|
+
*,
|
|
213
|
+
fields: str | list[str] | None = None,
|
|
214
|
+
row_limit: int | None = None,
|
|
215
|
+
) -> pd.DataFrame:
|
|
216
|
+
"""股票日度估值(市盈率、市净率等)。"""
|
|
217
|
+
return _symbol_query("/valuation", symbol, start_date, end_date, fields=fields, row_limit=row_limit)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def get_market_value(
|
|
221
|
+
symbol: str | list[str],
|
|
222
|
+
start_date: str,
|
|
223
|
+
end_date: str,
|
|
224
|
+
*,
|
|
225
|
+
fields: str | list[str] | None = None,
|
|
226
|
+
row_limit: int | None = None,
|
|
227
|
+
) -> pd.DataFrame:
|
|
228
|
+
"""股票日度市值。"""
|
|
229
|
+
return _symbol_query("/market_value", symbol, start_date, end_date, fields=fields, row_limit=row_limit)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def get_finance(
|
|
233
|
+
symbol: str | list[str],
|
|
234
|
+
start_date: str,
|
|
235
|
+
end_date: str,
|
|
236
|
+
*,
|
|
237
|
+
kind: Literal["prime", "deriv"] = "prime",
|
|
238
|
+
fields: str | list[str] | None = None,
|
|
239
|
+
row_limit: int | None = None,
|
|
240
|
+
) -> pd.DataFrame:
|
|
241
|
+
"""股票财务指标;``kind`` 为 ``prime``(主要)或 ``deriv``(衍生)。"""
|
|
242
|
+
return fetch(
|
|
243
|
+
f"{_DATA}/finance",
|
|
244
|
+
{
|
|
245
|
+
"symbol": _symbols(symbol),
|
|
246
|
+
"start_date": start_date,
|
|
247
|
+
"end_date": end_date,
|
|
248
|
+
"kind": kind,
|
|
249
|
+
"fields": _fields(fields),
|
|
250
|
+
"row_limit": row_limit,
|
|
251
|
+
},
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_trade_dates(
|
|
256
|
+
start_date: str,
|
|
257
|
+
end_date: str,
|
|
258
|
+
*,
|
|
259
|
+
fields: str | list[str] | None = None,
|
|
260
|
+
row_limit: int | None = None,
|
|
261
|
+
) -> pd.DataFrame:
|
|
262
|
+
"""A 股交易日历。"""
|
|
263
|
+
return fetch(
|
|
264
|
+
f"{_DATA}/trade_dates",
|
|
265
|
+
{
|
|
266
|
+
"start_date": start_date,
|
|
267
|
+
"end_date": end_date,
|
|
268
|
+
"fields": _fields(fields),
|
|
269
|
+
"row_limit": row_limit,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def get_index_constituents(
|
|
275
|
+
index_code: str,
|
|
276
|
+
start_date: str = "1990-01-01",
|
|
277
|
+
end_date: str = "2099-12-31",
|
|
278
|
+
*,
|
|
279
|
+
trade_date: str | None = None,
|
|
280
|
+
fields: str | list[str] | None = None,
|
|
281
|
+
row_limit: int | None = None,
|
|
282
|
+
) -> pd.DataFrame:
|
|
283
|
+
"""指数成分股及纳入/剔除日期。"""
|
|
284
|
+
return fetch(
|
|
285
|
+
f"{_DATA}/index_constituents",
|
|
286
|
+
{
|
|
287
|
+
"index_code": index_code,
|
|
288
|
+
"start_date": start_date,
|
|
289
|
+
"end_date": end_date,
|
|
290
|
+
"trade_date": trade_date,
|
|
291
|
+
"fields": _fields(fields),
|
|
292
|
+
"row_limit": row_limit,
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_option_bars(
|
|
298
|
+
symbol: str | list[str],
|
|
299
|
+
start_date: str,
|
|
300
|
+
end_date: str,
|
|
301
|
+
*,
|
|
302
|
+
fields: str | list[str] | None = None,
|
|
303
|
+
row_limit: int | None = None,
|
|
304
|
+
) -> pd.DataFrame:
|
|
305
|
+
"""期权日 K。"""
|
|
306
|
+
return _symbol_query("/option_bars", symbol, start_date, end_date, fields=fields, row_limit=row_limit)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def get_option_info(
|
|
310
|
+
underlying: str | list[str] | None = None,
|
|
311
|
+
symbol: str | list[str] | None = None,
|
|
312
|
+
start_date: str = "1990-01-01",
|
|
313
|
+
end_date: str = "2099-12-31",
|
|
314
|
+
*,
|
|
315
|
+
fields: str | list[str] | None = None,
|
|
316
|
+
row_limit: int | None = None,
|
|
317
|
+
) -> pd.DataFrame:
|
|
318
|
+
"""期权合约信息;``underlying`` 与 ``symbol`` 只能传其一。"""
|
|
319
|
+
params: dict = {
|
|
320
|
+
"start_date": start_date,
|
|
321
|
+
"end_date": end_date,
|
|
322
|
+
"fields": _fields(fields),
|
|
323
|
+
"row_limit": row_limit,
|
|
324
|
+
}
|
|
325
|
+
if underlying is not None:
|
|
326
|
+
params["underlying"] = _symbols(underlying)
|
|
327
|
+
if symbol is not None:
|
|
328
|
+
params["symbol"] = _symbols(symbol)
|
|
329
|
+
return fetch(f"{_DATA}/option_info", params)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def get_sectors(
|
|
333
|
+
start_date: str,
|
|
334
|
+
end_date: str,
|
|
335
|
+
*,
|
|
336
|
+
symbol: str | list[str] | None = None,
|
|
337
|
+
sector_name: str | None = None,
|
|
338
|
+
fields: str | list[str] | None = None,
|
|
339
|
+
row_limit: int | None = None,
|
|
340
|
+
) -> pd.DataFrame:
|
|
341
|
+
"""板块与成分股关系(快照;库内 ``ts`` 为写入占位,服务端按最新采集日返回)。"""
|
|
342
|
+
return fetch(
|
|
343
|
+
f"{_DATA}/sectors",
|
|
344
|
+
{
|
|
345
|
+
"start_date": start_date,
|
|
346
|
+
"end_date": end_date,
|
|
347
|
+
"symbol": _symbols(symbol) if symbol is not None else None,
|
|
348
|
+
"sector_name": sector_name,
|
|
349
|
+
"fields": _fields(fields),
|
|
350
|
+
"row_limit": row_limit,
|
|
351
|
+
},
|
|
352
|
+
)
|
jcdata-0.1.0/errors.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
class ErrorCode:
|
|
4
|
+
TOKEN_INVALID = "E1001"
|
|
5
|
+
TOKEN_CELL_QUOTA_EXCEEDED = "E1002"
|
|
6
|
+
RATE_LIMIT_EXCEEDED = "E1003"
|
|
7
|
+
TOKEN_MISSING = "E1004"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CellQuotaExceededError(PermissionError):
|
|
11
|
+
"""本周非空单元格配额已用尽。"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str = "本周非空单元格配额已用尽",
|
|
16
|
+
*,
|
|
17
|
+
code: str = ErrorCode.TOKEN_CELL_QUOTA_EXCEEDED,
|
|
18
|
+
cell_quota: int | None = None,
|
|
19
|
+
cell_quota_remaining: int | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.code = code
|
|
23
|
+
self.cell_quota = cell_quota
|
|
24
|
+
self.cell_quota_remaining = cell_quota_remaining
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# 兼容旧名
|
|
28
|
+
RowQuotaExceededError = CellQuotaExceededError
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jcdata
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: JCDATA
|
|
5
|
+
Author: JCDATA
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx>=0.24
|
|
10
|
+
Requires-Dist: pandas>=1.3
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# jcdata
|
|
15
|
+
|
|
16
|
+
连接 JCDATA 数据服务,详情可查看官网:[https://jicequant.com/](https://jicequant.com/)
|
|
17
|
+
|
|
18
|
+
用户注册后即可在个人中心使用一定额度的数据服务
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install jcdata
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 配置
|
|
27
|
+
|
|
28
|
+
访问令牌任选一种方式提供:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import jcdata
|
|
32
|
+
|
|
33
|
+
jcdata.login(token="你的访问令牌")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
或设置环境变量后直接 `jcdata.login()`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export JCDATA_TOKEN="你的访问令牌"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
也可安装后编辑包内 **`jcdata/config.py`** 中的 `TOKEN`,再执行 `jcdata.login()`。
|
|
43
|
+
|
|
44
|
+
## 使用
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import jcdata
|
|
48
|
+
|
|
49
|
+
jcdata.login(token="你的访问令牌")
|
|
50
|
+
print(jcdata.whoami())
|
|
51
|
+
|
|
52
|
+
df = jcdata.get_market_data("600000.SH", "2025-01-01", "2025-01-31")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 主要接口
|
|
56
|
+
|
|
57
|
+
- **行情**:`get_market_data`
|
|
58
|
+
- **基础信息**:`get_instrument`、`get_price_limits`、`get_adj_factor`、`get_turn_rate`、`get_trading_status`
|
|
59
|
+
- **期货/转债**:`get_settlement`、`get_conv_bond_terms`、`get_future_contract`
|
|
60
|
+
- **股票因子**:`get_valuation`、`get_market_value`、`get_finance`
|
|
61
|
+
- **其它**:`get_trade_dates`、`get_index_constituents`、`get_option_bars`、`get_option_info`、`get_sectors`(板块快照,`ts` 为库内占位)
|
|
62
|
+
|
|
63
|
+
代码格式为 Wind 风格,如 `600000.SH`。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
__init__.py
|
|
3
|
+
_http.py
|
|
4
|
+
config.py
|
|
5
|
+
data.py
|
|
6
|
+
errors.py
|
|
7
|
+
pyproject.toml
|
|
8
|
+
./__init__.py
|
|
9
|
+
./_http.py
|
|
10
|
+
./config.py
|
|
11
|
+
./data.py
|
|
12
|
+
./errors.py
|
|
13
|
+
jcdata.egg-info/PKG-INFO
|
|
14
|
+
jcdata.egg-info/SOURCES.txt
|
|
15
|
+
jcdata.egg-info/dependency_links.txt
|
|
16
|
+
jcdata.egg-info/requires.txt
|
|
17
|
+
jcdata.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jcdata
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jcdata"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "JCDATA"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "JCDATA" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"httpx>=0.24",
|
|
15
|
+
"pandas>=1.3",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = ["pytest>=7"]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
package-dir = { "jcdata" = "." }
|
|
23
|
+
packages = ["jcdata"]
|
jcdata-0.1.0/setup.cfg
ADDED