ardata-sdk 1.0.0__py3-none-any.whl
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.
- ardata_sdk/__init__.py +28 -0
- ardata_sdk/api/__init__.py +15 -0
- ardata_sdk/api/billboard.py +16 -0
- ardata_sdk/api/client.py +195 -0
- ardata_sdk/api/extras.py +31 -0
- ardata_sdk/api/factors.py +45 -0
- ardata_sdk/api/fundamentals.py +156 -0
- ardata_sdk/api/history.py +79 -0
- ardata_sdk/api/index_concept.py +34 -0
- ardata_sdk/api/industry.py +22 -0
- ardata_sdk/api/margin.py +11 -0
- ardata_sdk/api/money_flow.py +19 -0
- ardata_sdk/api/mtss.py +17 -0
- ardata_sdk/api/price.py +74 -0
- ardata_sdk/api/securities.py +24 -0
- ardata_sdk/api/ticks.py +50 -0
- ardata_sdk/api/trade_days.py +11 -0
- ardata_sdk/config.py +30 -0
- ardata_sdk/finance/__init__.py +55 -0
- ardata_sdk/finance/columns.py +58 -0
- ardata_sdk/finance/query.py +45 -0
- ardata_sdk/finance/run_query.py +10 -0
- ardata_sdk/finance/tables.py +262 -0
- ardata_sdk-1.0.0.dist-info/METADATA +74 -0
- ardata_sdk-1.0.0.dist-info/RECORD +27 -0
- ardata_sdk-1.0.0.dist-info/WHEEL +5 -0
- ardata_sdk-1.0.0.dist-info/top_level.txt +1 -0
ardata_sdk/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .config import init, get_client
|
|
2
|
+
from .api.price import get_price, get_bars
|
|
3
|
+
from .api.securities import get_security_info, get_all_securities
|
|
4
|
+
from .api.index_concept import get_index_stocks, get_industry_stocks, get_concept_stocks, get_index_weights
|
|
5
|
+
from .api.industry import get_industry, get_industries, get_concepts
|
|
6
|
+
from .api.fundamentals import get_valuation, get_fundamentals, get_fundamentals_continuously, get_history_fundamentals
|
|
7
|
+
from .api.money_flow import get_money_flow
|
|
8
|
+
from .api.mtss import get_mtss
|
|
9
|
+
from .api.billboard import get_billboard_list
|
|
10
|
+
from .api.margin import get_margincash_stocks, get_marginsec_stocks
|
|
11
|
+
from .api.extras import get_extras
|
|
12
|
+
from .api.history import history, attribute_history
|
|
13
|
+
from .api.trade_days import get_trade_days
|
|
14
|
+
from .api.factors import get_all_factors, get_factor_values
|
|
15
|
+
from .api.ticks import get_ticks, get_call_auction
|
|
16
|
+
from . import finance
|
|
17
|
+
from .finance import query
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'init', 'get_price', 'get_bars', 'get_security_info', 'get_all_securities',
|
|
21
|
+
'get_index_stocks', 'get_industry_stocks', 'get_concept_stocks',
|
|
22
|
+
'get_industry', 'get_industries', 'get_concepts', 'get_index_weights', 'get_valuation', 'get_fundamentals', 'get_fundamentals_continuously', 'get_history_fundamentals',
|
|
23
|
+
'get_money_flow', 'get_mtss', 'get_billboard_list',
|
|
24
|
+
'get_margincash_stocks', 'get_marginsec_stocks', 'get_extras',
|
|
25
|
+
'history', 'attribute_history',
|
|
26
|
+
'get_trade_days', 'get_all_factors', 'get_factor_values', 'get_ticks', 'get_call_auction',
|
|
27
|
+
'finance', 'query',
|
|
28
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .price import get_price, get_bars
|
|
2
|
+
from .securities import get_security_info, get_all_securities
|
|
3
|
+
from .index_concept import get_index_stocks, get_industry_stocks, get_concept_stocks, get_index_weights
|
|
4
|
+
from .industry import get_industry, get_industries, get_concepts
|
|
5
|
+
from .fundamentals import get_valuation, get_fundamentals, get_fundamentals_continuously, get_history_fundamentals
|
|
6
|
+
from .money_flow import get_money_flow
|
|
7
|
+
from .mtss import get_mtss
|
|
8
|
+
from .billboard import get_billboard_list
|
|
9
|
+
from .margin import get_margincash_stocks, get_marginsec_stocks
|
|
10
|
+
from .extras import get_extras
|
|
11
|
+
from .trade_days import get_trade_days
|
|
12
|
+
from .factors import get_all_factors, get_factor_values
|
|
13
|
+
from .ticks import get_ticks, get_call_auction
|
|
14
|
+
from .history import history, attribute_history
|
|
15
|
+
from ..finance.run_query import run_query
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_billboard_list(stock_list=None, start_date=None, end_date=None, count=None):
|
|
5
|
+
client = get_client()
|
|
6
|
+
params = {}
|
|
7
|
+
if stock_list:
|
|
8
|
+
params['stock_list'] = ','.join(stock_list) if isinstance(stock_list, list) else stock_list
|
|
9
|
+
if start_date:
|
|
10
|
+
params['start_date'] = start_date
|
|
11
|
+
if end_date:
|
|
12
|
+
params['end_date'] = end_date
|
|
13
|
+
if count:
|
|
14
|
+
params['count'] = count
|
|
15
|
+
data = client.get('/get_billboard_list', params=params)
|
|
16
|
+
return client.to_dataframe(data)
|
ardata_sdk/api/client.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""ARData Proxy HTTP Client"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import requests
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from datetime import datetime, date
|
|
7
|
+
from typing import Optional, List, Union
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class APIClient:
|
|
11
|
+
"""ARData Proxy HTTP Client (计划中名为 ARDataClient)"""
|
|
12
|
+
def __init__(self, host: str = "http://localhost:8000", token: str = ""):
|
|
13
|
+
self.host = host.rstrip("/")
|
|
14
|
+
self.token = token
|
|
15
|
+
self.session = requests.Session()
|
|
16
|
+
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
|
17
|
+
|
|
18
|
+
def _prefix(self, path: str) -> str:
|
|
19
|
+
"""server 路由都挂在 /api/v1 下, client 自动补前缀 (已有则不重复)."""
|
|
20
|
+
if not path.startswith("/api/v1"):
|
|
21
|
+
return "/api/v1" + path
|
|
22
|
+
return path
|
|
23
|
+
|
|
24
|
+
def _get(self, path: str, params: dict = None) -> dict:
|
|
25
|
+
resp = self.session.get(f"{self.host}{self._prefix(path)}", params=params, timeout=120)
|
|
26
|
+
resp.raise_for_status()
|
|
27
|
+
return resp.json()
|
|
28
|
+
|
|
29
|
+
def get(self, path: str, params: dict = None) -> dict:
|
|
30
|
+
return self._get(path, params)
|
|
31
|
+
|
|
32
|
+
def to_dataframe(self, data):
|
|
33
|
+
import pandas as pd
|
|
34
|
+
if not data:
|
|
35
|
+
return pd.DataFrame()
|
|
36
|
+
return pd.DataFrame(data)
|
|
37
|
+
|
|
38
|
+
def _post(self, path: str, json: dict) -> dict:
|
|
39
|
+
resp = self.session.post(f"{self.host}{self._prefix(path)}", json=json, timeout=30)
|
|
40
|
+
resp.raise_for_status()
|
|
41
|
+
return resp.json()
|
|
42
|
+
|
|
43
|
+
def get_token(self, api_key: str, expire_days: int = 30) -> dict:
|
|
44
|
+
"""用 api_key 登录拿 JWT (server /auth/login 端点)."""
|
|
45
|
+
return self._post("/api/v1/auth/login", {
|
|
46
|
+
"api_key": api_key, "expire_days": expire_days,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
def get_price(self, security, start_date=None, end_date=None, frequency="daily",
|
|
50
|
+
fields=None, skip_paused=False, fq="pre", count=None, panel=True):
|
|
51
|
+
codes = ",".join(security) if isinstance(security, list) else security
|
|
52
|
+
params = {"security": codes, "frequency": frequency, "fq": fq if fq is not None else '', "skip_paused": str(skip_paused).lower()}
|
|
53
|
+
if start_date:
|
|
54
|
+
params["start_date"] = str(start_date)
|
|
55
|
+
if end_date:
|
|
56
|
+
params["end_date"] = str(end_date)
|
|
57
|
+
if fields:
|
|
58
|
+
params["fields"] = ",".join(fields) if isinstance(fields, list) else fields
|
|
59
|
+
|
|
60
|
+
data = self._get("/get_price", params)
|
|
61
|
+
if not data:
|
|
62
|
+
return pd.DataFrame()
|
|
63
|
+
|
|
64
|
+
df = pd.DataFrame(data)
|
|
65
|
+
if "code" in df.columns:
|
|
66
|
+
df.set_index(["code", "time" if frequency == "1m" else "date"], inplace=True)
|
|
67
|
+
|
|
68
|
+
if fields:
|
|
69
|
+
df = df[[c for c in fields if c in df.columns]]
|
|
70
|
+
return df
|
|
71
|
+
|
|
72
|
+
def get_trade_days(self, start_date=None, end_date=None):
|
|
73
|
+
params = {}
|
|
74
|
+
if start_date:
|
|
75
|
+
params["start_date"] = str(start_date)
|
|
76
|
+
if end_date:
|
|
77
|
+
params["end_date"] = str(end_date)
|
|
78
|
+
return self._get("/get_trade_days", params)
|
|
79
|
+
|
|
80
|
+
def get_all_securities(self, types=None, date=None):
|
|
81
|
+
params = {}
|
|
82
|
+
if types:
|
|
83
|
+
params["types"] = ",".join(types) if isinstance(types, list) else types
|
|
84
|
+
if date:
|
|
85
|
+
params["date"] = str(date)
|
|
86
|
+
data = self._get("/get_all_securities", params)
|
|
87
|
+
df = pd.DataFrame(data)
|
|
88
|
+
if "code" in df.columns:
|
|
89
|
+
df.set_index("code", inplace=True)
|
|
90
|
+
return df
|
|
91
|
+
|
|
92
|
+
def get_security_info(self, code, date=None):
|
|
93
|
+
data = self._get("/get_security_info", {"code": code})
|
|
94
|
+
return data
|
|
95
|
+
|
|
96
|
+
def get_industry(self, security, date=None):
|
|
97
|
+
codes = ",".join(security) if isinstance(security, list) else security
|
|
98
|
+
params = {"security": codes}
|
|
99
|
+
if date:
|
|
100
|
+
params["date"] = str(date)
|
|
101
|
+
return self._get("/get_industry", params)
|
|
102
|
+
|
|
103
|
+
def get_concept(self):
|
|
104
|
+
return self._get("/get_concepts")
|
|
105
|
+
|
|
106
|
+
def get_index_constituents(self, index_code, date=None):
|
|
107
|
+
params = {"index_code": index_code}
|
|
108
|
+
if date:
|
|
109
|
+
params["date"] = str(date)
|
|
110
|
+
return self._get("/get_index_stocks", params)
|
|
111
|
+
|
|
112
|
+
def get_industry_constituents(self, industry_code, date=None):
|
|
113
|
+
params = {"industry_code": industry_code}
|
|
114
|
+
if date:
|
|
115
|
+
params["date"] = str(date)
|
|
116
|
+
return self._get("/get_industry_stocks", params)
|
|
117
|
+
|
|
118
|
+
def get_index_weights(self, index_code, date=None):
|
|
119
|
+
params = {"index_code": index_code}
|
|
120
|
+
if date:
|
|
121
|
+
params["date"] = str(date)
|
|
122
|
+
return self._get("/get_index_weights", params)
|
|
123
|
+
|
|
124
|
+
def get_margin_stocks(self, date=None):
|
|
125
|
+
params = {"date": str(date)} if date else {}
|
|
126
|
+
return self._get("/get_margincash_stocks", params)
|
|
127
|
+
|
|
128
|
+
def get_ST_stocks(self):
|
|
129
|
+
return self._get("/get_extras", {"info": "is_st", "security_list": "*", "df": "false"})
|
|
130
|
+
|
|
131
|
+
def get_billboard_list(self, stock_list=None, start_date=None, end_date=None, count=None):
|
|
132
|
+
params = {}
|
|
133
|
+
if stock_list:
|
|
134
|
+
params["stock_list"] = ",".join(stock_list) if isinstance(stock_list, list) else stock_list
|
|
135
|
+
if start_date:
|
|
136
|
+
params["start_date"] = str(start_date)
|
|
137
|
+
if end_date:
|
|
138
|
+
params["end_date"] = str(end_date)
|
|
139
|
+
if count is not None:
|
|
140
|
+
params["count"] = count
|
|
141
|
+
return self._get("/get_billboard_list", params)
|
|
142
|
+
|
|
143
|
+
def get_fundamentals(self, query_object, date=None, statDate=None):
|
|
144
|
+
from ardata_sdk.finance.query import Query
|
|
145
|
+
if not isinstance(query_object, Query):
|
|
146
|
+
q = Query(query_object)
|
|
147
|
+
else:
|
|
148
|
+
q = query_object
|
|
149
|
+
if date is not None:
|
|
150
|
+
q._filters.append({'col': 'day' if q._table == 'valuation' else 'pubDate', 'op': 'lte', 'val': str(date)})
|
|
151
|
+
if statDate is not None:
|
|
152
|
+
s = str(statDate)
|
|
153
|
+
if 'q' in s:
|
|
154
|
+
year, qtr = s.split('q', 1)
|
|
155
|
+
quarter_end = {'1': '03-31', '2': '06-30', '3': '09-30', '4': '12-31'}.get(qtr)
|
|
156
|
+
if quarter_end:
|
|
157
|
+
q._filters.append({'col': 'statDate', 'op': 'eq', 'val': f'{year}-{quarter_end}'})
|
|
158
|
+
else:
|
|
159
|
+
q._filters.append({'col': 'statDate', 'op': 'lte', 'val': f'{s}-12-31'})
|
|
160
|
+
return self._query_finance(q._table, self._q_to_params(q))
|
|
161
|
+
|
|
162
|
+
def _q_to_params(self, q):
|
|
163
|
+
params = {}
|
|
164
|
+
for f in q._filters:
|
|
165
|
+
key = f['col'] if f['op'] == 'eq' else f"{f['col']}__{f['op']}"
|
|
166
|
+
params[key] = f['val']
|
|
167
|
+
if q._limit_val:
|
|
168
|
+
params['_limit'] = q._limit_val
|
|
169
|
+
if q._offset_val:
|
|
170
|
+
params['_offset'] = q._offset_val
|
|
171
|
+
if q._order_by_cols:
|
|
172
|
+
params['_order_by'] = ','.join(q._order_by_cols)
|
|
173
|
+
return params
|
|
174
|
+
|
|
175
|
+
def _query_finance(self, table_name: str, params: dict = None) -> pd.DataFrame:
|
|
176
|
+
data = self._get(f"/table/{table_name}", params)
|
|
177
|
+
if not data:
|
|
178
|
+
return pd.DataFrame()
|
|
179
|
+
df = pd.DataFrame(data)
|
|
180
|
+
if "id" in df.columns:
|
|
181
|
+
df.drop(columns=["id"], inplace=True)
|
|
182
|
+
return df
|
|
183
|
+
|
|
184
|
+
def get_factor_data(self, codes, start_date, end_date, fields=None):
|
|
185
|
+
params = {"securities": ",".join(codes) if isinstance(codes, list) else codes}
|
|
186
|
+
if start_date:
|
|
187
|
+
params["start_date"] = str(start_date)
|
|
188
|
+
if end_date:
|
|
189
|
+
params["end_date"] = str(end_date)
|
|
190
|
+
if fields:
|
|
191
|
+
params["factors"] = ",".join(fields)
|
|
192
|
+
return self._get("/get_factor_values", params)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
ARDataClient = APIClient
|
ardata_sdk/api/extras.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_extras(info='is_st', security_list=None, start_date=None, end_date=None, df=True):
|
|
5
|
+
"""spec: get_extras(info, security_list, start_date, end_date, df=True).
|
|
6
|
+
df=True -> pandas.DataFrame, 行=日期(datetime), 列=股票代码.
|
|
7
|
+
df=False -> dict {code: numpy.ndarray(bool)}.
|
|
8
|
+
"""
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import numpy as np
|
|
11
|
+
if security_list is None:
|
|
12
|
+
return pd.DataFrame() if df else {}
|
|
13
|
+
client = get_client()
|
|
14
|
+
list_str = ','.join(security_list) if isinstance(security_list, list) else security_list
|
|
15
|
+
params = {'info': info, 'security_list': list_str, 'df': str(df).lower()}
|
|
16
|
+
if start_date:
|
|
17
|
+
params['start_date'] = start_date
|
|
18
|
+
if end_date:
|
|
19
|
+
params['end_date'] = end_date
|
|
20
|
+
raw = client.get('/get_extras', params=params)
|
|
21
|
+
if not raw:
|
|
22
|
+
return pd.DataFrame() if df else {}
|
|
23
|
+
if df:
|
|
24
|
+
# server 返回 {index, columns, data}; data 是 {code: {day: value}}
|
|
25
|
+
from datetime import date as _date
|
|
26
|
+
idx = [_date.fromisoformat(d) for d in raw['index']]
|
|
27
|
+
cols = raw['columns']
|
|
28
|
+
data = {c: [raw['data'].get(c, {}).get(d) for d in raw['index']] for c in cols}
|
|
29
|
+
return pd.DataFrame(data, index=pd.DatetimeIndex(idx), columns=cols)
|
|
30
|
+
else:
|
|
31
|
+
return {c: np.array(list(raw.get(c, [])), dtype=bool) for c in raw}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_all_factors():
|
|
5
|
+
"""获取所有可用因子名. 返回 list[str] (spec)."""
|
|
6
|
+
client = get_client()
|
|
7
|
+
data = client.get('/get_all_factors')
|
|
8
|
+
if isinstance(data, list):
|
|
9
|
+
# server 返回 [{factor: name}, ...] 或 [name, ...], 统一为 list[str]
|
|
10
|
+
if data and isinstance(data[0], dict):
|
|
11
|
+
return [d.get('factor') for d in data if d.get('factor')]
|
|
12
|
+
return list(data)
|
|
13
|
+
return []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_factor_values(securities, factors, *, start_date=None, end_date=None, count=1):
|
|
17
|
+
"""spec: get_factor_values(securities, factors, start_date, end_date, count)
|
|
18
|
+
后两个参数 keyword-only 避免与历史 positional 调用混淆.
|
|
19
|
+
返回: dict[factor_name -> DataFrame(date x code)]"""
|
|
20
|
+
client = get_client()
|
|
21
|
+
params = {
|
|
22
|
+
'securities': ','.join(securities) if isinstance(securities, list) else securities,
|
|
23
|
+
'factors': ','.join(factors) if isinstance(factors, list) else factors,
|
|
24
|
+
'count': count,
|
|
25
|
+
}
|
|
26
|
+
if start_date:
|
|
27
|
+
params['start_date'] = start_date
|
|
28
|
+
if end_date:
|
|
29
|
+
params['end_date'] = end_date
|
|
30
|
+
data = client.get('/get_factor_values', params=params)
|
|
31
|
+
|
|
32
|
+
import pandas as pd
|
|
33
|
+
result = {}
|
|
34
|
+
for date_str, stocks in data.items():
|
|
35
|
+
for code, values in stocks.items():
|
|
36
|
+
for fname, fval in values.items():
|
|
37
|
+
if fname not in result:
|
|
38
|
+
result[fname] = {}
|
|
39
|
+
if date_str not in result[fname]:
|
|
40
|
+
result[fname][date_str] = {}
|
|
41
|
+
result[fname][date_str][code] = fval
|
|
42
|
+
|
|
43
|
+
for fname in result:
|
|
44
|
+
result[fname] = pd.DataFrame(result[fname]).T
|
|
45
|
+
return result
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# spec 字段名 -> 本地 valuation 表列名 (因 ARData 用了 'total_market_cap' 而本地是 'market_cap')
|
|
5
|
+
_VALUATION_FIELD_ALIASES = {
|
|
6
|
+
"total_market_cap": "market_cap",
|
|
7
|
+
"total_cap": "market_cap",
|
|
8
|
+
"mkt_cap": "market_cap",
|
|
9
|
+
# 其它字段 1-to-1 (pe_ratio, pb_ratio, ps_ratio, pcf_ratio, turnover_ratio 等)
|
|
10
|
+
"pe_ratio_ttm": "pe_ratio_lyr", # 简化映射
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _translate_valuation_fields(fields):
|
|
15
|
+
"""spec -> 本地 列名映射; 不识别的字段保持原样 (服务端会 400)."""
|
|
16
|
+
if fields is None:
|
|
17
|
+
return None
|
|
18
|
+
items = fields.split(",") if isinstance(fields, str) else list(fields)
|
|
19
|
+
out = [_VALUATION_FIELD_ALIASES.get(f.strip(), f.strip()) for f in items]
|
|
20
|
+
return ",".join(out) if isinstance(fields, str) else out
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_valuation(security, start_date=None, end_date=None, fields=None, count=None):
|
|
24
|
+
"""spec: get_valuation(security, start_date, end_date, fields, count) -> DataFrame (index=day).
|
|
25
|
+
count: take the most recent N rows (ORDER BY day DESC LIMIT N).
|
|
26
|
+
spec 字段名 (如 total_market_cap) 自动翻译成本地表列名 (market_cap)."""
|
|
27
|
+
client = get_client()
|
|
28
|
+
params = {"security": security}
|
|
29
|
+
if start_date:
|
|
30
|
+
params["start_date"] = start_date
|
|
31
|
+
if end_date:
|
|
32
|
+
params["end_date"] = end_date
|
|
33
|
+
if fields is not None:
|
|
34
|
+
params["fields"] = _translate_valuation_fields(fields)
|
|
35
|
+
if count is not None:
|
|
36
|
+
params["count"] = count
|
|
37
|
+
data = client.get("/get_valuation", params=params)
|
|
38
|
+
df = client.to_dataframe(data)
|
|
39
|
+
if not df.empty and "day" in df.columns:
|
|
40
|
+
df = df.set_index("day")
|
|
41
|
+
return df
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_fundamentals(query_object, date=None, statDate=None):
|
|
45
|
+
"""spec: get_fundamentals(query_object, date, statDate) -> DataFrame.
|
|
46
|
+
query_object accepts a Query instance (recommended) or a table name string (back-compat).
|
|
47
|
+
Routes through /table/{table} via finance.run_query, applying date/statDate as
|
|
48
|
+
additional filter params on the table.
|
|
49
|
+
|
|
50
|
+
date: 查询日期, 传 '2015-10-15' 格式, 过滤 day <= date 或 pubDate <= date
|
|
51
|
+
statDate: 财报统计季度/年份, 传 '2014q1' 或 '2014', 过滤 statDate = 对应季末
|
|
52
|
+
"""
|
|
53
|
+
from ..finance import run_query
|
|
54
|
+
from ..finance.query import Query
|
|
55
|
+
if isinstance(query_object, Query):
|
|
56
|
+
q = query_object
|
|
57
|
+
else:
|
|
58
|
+
q = Query(query_object)
|
|
59
|
+
|
|
60
|
+
if date is not None:
|
|
61
|
+
from datetime import date as _date
|
|
62
|
+
from ..finance.columns import Compare
|
|
63
|
+
try:
|
|
64
|
+
parsed = str(_date.fromisoformat(str(date).replace('/', '-')))
|
|
65
|
+
except ValueError:
|
|
66
|
+
parsed = str(date)
|
|
67
|
+
if q._table == 'valuation':
|
|
68
|
+
q._filters.append({'col': 'day', 'op': 'lte', 'val': parsed})
|
|
69
|
+
else:
|
|
70
|
+
q._filters.append({'col': 'pubDate', 'op': 'lte', 'val': parsed})
|
|
71
|
+
|
|
72
|
+
if statDate is not None:
|
|
73
|
+
s = str(statDate)
|
|
74
|
+
if 'q' in s:
|
|
75
|
+
year, qtr = s.split('q', 1)
|
|
76
|
+
quarter_end = {'1': '03-31', '2': '06-30', '3': '09-30', '4': '12-31'}.get(qtr)
|
|
77
|
+
if quarter_end:
|
|
78
|
+
from ..finance.columns import Compare
|
|
79
|
+
q._filters.append({'col': 'statDate', 'op': 'eq', 'val': f'{year}-{quarter_end}'})
|
|
80
|
+
else:
|
|
81
|
+
from ..finance.columns import Compare
|
|
82
|
+
q._filters.append({'col': 'statDate', 'op': 'lte', 'val': f'{s}-12-31'})
|
|
83
|
+
|
|
84
|
+
return run_query(q)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_fundamentals_continuously(query_object, end_date=None, count=None, panel=True):
|
|
88
|
+
"""spec: get_fundamentals_continuously(query_object, end_date, count, panel).
|
|
89
|
+
对估值/财务时间序列表取 end_date 之前 count 条.
|
|
90
|
+
panel=True (默认) -> dict[code -> DataFrame] (pandas.Panel 已弃用, 用 dict 替代).
|
|
91
|
+
panel=False -> 原始 DataFrame."""
|
|
92
|
+
import pandas as pd
|
|
93
|
+
from ..finance.query import Query
|
|
94
|
+
|
|
95
|
+
def _table_name(obj):
|
|
96
|
+
if isinstance(obj, Query):
|
|
97
|
+
return obj._table
|
|
98
|
+
return obj
|
|
99
|
+
|
|
100
|
+
table = _table_name(query_object)
|
|
101
|
+
code = None
|
|
102
|
+
if isinstance(query_object, Query):
|
|
103
|
+
for f in query_object._filters:
|
|
104
|
+
if f.get('col') == 'code' and f.get('op') == 'eq':
|
|
105
|
+
code = f.get('val')
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
client = get_client()
|
|
109
|
+
params = {'table': table}
|
|
110
|
+
if code:
|
|
111
|
+
params['code'] = code
|
|
112
|
+
if end_date:
|
|
113
|
+
params['end_date'] = end_date
|
|
114
|
+
if count is not None:
|
|
115
|
+
params['count'] = count
|
|
116
|
+
params['panel'] = str(panel).lower()
|
|
117
|
+
|
|
118
|
+
rows = client.get('/get_fundamentals_continuously', params=params)
|
|
119
|
+
df = pd.DataFrame(rows)
|
|
120
|
+
if not panel or df.empty or 'code' not in df.columns:
|
|
121
|
+
return df
|
|
122
|
+
codes = df['code'].unique().tolist()
|
|
123
|
+
if len(codes) == 1:
|
|
124
|
+
return df.drop(columns=['code'])
|
|
125
|
+
return {c: df[df['code'] == c].drop(columns=['code']) for c in codes}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_history_fundamentals(security, fields, watch_date=None, stat_date=None,
|
|
129
|
+
count=1, interval="1q", stat_by_year=False):
|
|
130
|
+
"""spec: get_history_fundamentals(security, fields, watch_date, stat_date, count, interval, stat_by_year).
|
|
131
|
+
返回 list[dict]: 历史多个报告期数据, 按 statDate 升序.
|
|
132
|
+
|
|
133
|
+
- watch_date: 仅返回 pubDate <= watch_date 的报告
|
|
134
|
+
- stat_date: 'YYYY' (年) 或 'YYYYqN' (季); 只取 <= stat_date 的报告期
|
|
135
|
+
- interval: '1q' / '1y' (本参数只用于对齐 spec, 本地实现统一按 statDate 排序)
|
|
136
|
+
- stat_by_year: True 时强制使用 indicator 表
|
|
137
|
+
"""
|
|
138
|
+
import pandas as pd
|
|
139
|
+
client = get_client()
|
|
140
|
+
table = "indicator" if stat_by_year else "valuation"
|
|
141
|
+
params = {
|
|
142
|
+
"table": table,
|
|
143
|
+
"count": count,
|
|
144
|
+
"interval": interval,
|
|
145
|
+
"stat_by_year": str(stat_by_year).lower(),
|
|
146
|
+
}
|
|
147
|
+
if security:
|
|
148
|
+
params["security"] = security
|
|
149
|
+
if fields:
|
|
150
|
+
params["fields"] = ",".join(fields) if isinstance(fields, list) else fields
|
|
151
|
+
if watch_date:
|
|
152
|
+
params["watch_date"] = watch_date
|
|
153
|
+
if stat_date:
|
|
154
|
+
params["stat_date"] = stat_date
|
|
155
|
+
rows = client.get("/get_history_fundamentals", params=params)
|
|
156
|
+
return pd.DataFrame(rows)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def attribute_history(security, count, unit='1d', fields=None,
|
|
5
|
+
skip_paused=True, df=True, fq='pre'):
|
|
6
|
+
"""spec: attribute_history(security, count, unit, fields, skip_paused, df, fq).
|
|
7
|
+
获取单个标的多个字段的历史数据。
|
|
8
|
+
df=True (default) -> DataFrame; df=False -> numpy array.
|
|
9
|
+
"""
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import numpy as np
|
|
12
|
+
end_date = _today_str()
|
|
13
|
+
start_date = _compute_start(count, unit)
|
|
14
|
+
from .price import get_price
|
|
15
|
+
df_price = get_price(security, start_date=start_date, end_date=end_date,
|
|
16
|
+
frequency=_unit_to_freq(unit), fields=fields,
|
|
17
|
+
skip_paused=skip_paused, fq=fq)
|
|
18
|
+
if df_price is None or df_price.empty:
|
|
19
|
+
return pd.DataFrame() if df else np.array([])
|
|
20
|
+
df_price = df_price.tail(count)
|
|
21
|
+
if df:
|
|
22
|
+
return df_price
|
|
23
|
+
return df_price.to_records(index=False)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def history(count, unit='1d', field=None, security_list=None,
|
|
27
|
+
df=True, skip_paused=False, fq='pre'):
|
|
28
|
+
"""spec: history(count, unit, field, security_list, df, skip_paused, fq).
|
|
29
|
+
获取多个标的单个字段的历史数据。
|
|
30
|
+
df=True -> DataFrame (列=股票代码, 行=日期); df=False -> dict of numpy arrays.
|
|
31
|
+
"""
|
|
32
|
+
import pandas as pd
|
|
33
|
+
if security_list is None:
|
|
34
|
+
return pd.DataFrame() if df else {}
|
|
35
|
+
end_date = _today_str()
|
|
36
|
+
start_date = _compute_start(count, unit)
|
|
37
|
+
from .price import get_price
|
|
38
|
+
fields = [field] if field else None
|
|
39
|
+
df_price = get_price(security_list, start_date=start_date, end_date=end_date,
|
|
40
|
+
frequency=_unit_to_freq(unit), fields=fields,
|
|
41
|
+
skip_paused=skip_paused, fq=fq)
|
|
42
|
+
if df_price is None or df_price.empty:
|
|
43
|
+
return pd.DataFrame() if df else {}
|
|
44
|
+
|
|
45
|
+
result = {}
|
|
46
|
+
if 'code' in df_price.columns:
|
|
47
|
+
for code in df_price['code'].unique():
|
|
48
|
+
subset = df_price[df_price['code'] == code].tail(count)
|
|
49
|
+
result[code] = subset[field].values if field else subset
|
|
50
|
+
else:
|
|
51
|
+
codes = security_list if isinstance(security_list, list) else [security_list]
|
|
52
|
+
for code in codes:
|
|
53
|
+
subset = df_price.tail(count)
|
|
54
|
+
result[code] = subset[field].values if field else subset
|
|
55
|
+
|
|
56
|
+
if df:
|
|
57
|
+
return pd.DataFrame(result)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _unit_to_freq(unit):
|
|
62
|
+
_MAP = {
|
|
63
|
+
'1d': 'daily', '1m': '1m', '5m': '1m', '15m': '1m',
|
|
64
|
+
'30m': '1m', '60m': '1m', '120m': '1m',
|
|
65
|
+
'1w': 'daily', '1M': 'daily',
|
|
66
|
+
}
|
|
67
|
+
return _MAP.get(unit, 'daily')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _today_str():
|
|
71
|
+
from datetime import date
|
|
72
|
+
return date.today().isoformat()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _compute_start(count, unit):
|
|
76
|
+
from datetime import date, timedelta
|
|
77
|
+
buf = {'1d': 3, '1m': 0, '5m': 0, '15m': 0, '30m': 0, '60m': 0, '120m': 0, '1w': 10, '1M': 40}
|
|
78
|
+
days_back = buf.get(unit, 3) * count + 30
|
|
79
|
+
return (date.today() - timedelta(days=days_back)).isoformat()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_index_stocks(index_symbol, date=None):
|
|
5
|
+
client = get_client()
|
|
6
|
+
params = {'index_code': index_symbol}
|
|
7
|
+
if date:
|
|
8
|
+
params['date'] = date
|
|
9
|
+
return client.get('/get_index_stocks', params=params)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_industry_stocks(industry_code, date=None):
|
|
13
|
+
client = get_client()
|
|
14
|
+
params = {'industry_code': industry_code}
|
|
15
|
+
if date:
|
|
16
|
+
params['date'] = date
|
|
17
|
+
return client.get('/get_industry_stocks', params=params)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_concept_stocks(concept_code, date=None):
|
|
21
|
+
client = get_client()
|
|
22
|
+
params = {'concept_code': concept_code}
|
|
23
|
+
if date:
|
|
24
|
+
params['date'] = date
|
|
25
|
+
return client.get('/get_concept_stocks', params=params)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_index_weights(index_id, date=None):
|
|
29
|
+
"""获取指数成分股权重. 返回: list[dict{index_code, stock_code, weight, date, display_name}]"""
|
|
30
|
+
client = get_client()
|
|
31
|
+
params = {'index_code': index_id}
|
|
32
|
+
if date:
|
|
33
|
+
params['date'] = date
|
|
34
|
+
return client.get('/get_index_weights', params=params)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_industry(security, date=None):
|
|
5
|
+
client = get_client()
|
|
6
|
+
params = {'security': security}
|
|
7
|
+
if date:
|
|
8
|
+
params['date'] = date
|
|
9
|
+
return client.get('/get_industry', params=params)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_industries(industry_type='sw_l1'):
|
|
13
|
+
"""获取行业列表. industry_type in {jq_l1, jq_l2, sw_l1, sw_l2, zjw}.
|
|
14
|
+
返回: list[dict{industry_code, industry_name}]"""
|
|
15
|
+
client = get_client()
|
|
16
|
+
return client.get('/get_industries', params={'type': industry_type})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_concepts():
|
|
20
|
+
"""获取所有概念列表. 返回: list[dict{code}]"""
|
|
21
|
+
client = get_client()
|
|
22
|
+
return client.get('/get_concepts')
|
ardata_sdk/api/margin.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_margincash_stocks(date=None):
|
|
5
|
+
client = get_client()
|
|
6
|
+
return client.get('/get_margincash_stocks', params={'date': date} if date else {})
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_marginsec_stocks(date=None):
|
|
10
|
+
client = get_client()
|
|
11
|
+
return client.get('/get_marginsec_stocks', params={'date': date} if date else {})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..config import get_client
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_money_flow(security_list=None, start_date=None, end_date=None, fields=None, count=None, *, security=None):
|
|
5
|
+
if security_list is None and security is not None:
|
|
6
|
+
security_list = security
|
|
7
|
+
client = get_client()
|
|
8
|
+
list_str = ','.join(security_list) if isinstance(security_list, list) else security_list
|
|
9
|
+
params = {'security': list_str}
|
|
10
|
+
if start_date:
|
|
11
|
+
params['start_date'] = start_date
|
|
12
|
+
if end_date:
|
|
13
|
+
params['end_date'] = end_date
|
|
14
|
+
if fields:
|
|
15
|
+
params['fields'] = ','.join(fields) if isinstance(fields, list) else fields
|
|
16
|
+
if count:
|
|
17
|
+
params['count'] = count
|
|
18
|
+
data = client.get('/get_money_flow', params=params)
|
|
19
|
+
return client.to_dataframe(data)
|