ardata-sdk 1.0.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.
Files changed (33) hide show
  1. ardata_sdk-1.0.0/PKG-INFO +74 -0
  2. ardata_sdk-1.0.0/README.md +53 -0
  3. ardata_sdk-1.0.0/ardata_sdk/__init__.py +28 -0
  4. ardata_sdk-1.0.0/ardata_sdk/api/__init__.py +15 -0
  5. ardata_sdk-1.0.0/ardata_sdk/api/billboard.py +16 -0
  6. ardata_sdk-1.0.0/ardata_sdk/api/client.py +195 -0
  7. ardata_sdk-1.0.0/ardata_sdk/api/extras.py +31 -0
  8. ardata_sdk-1.0.0/ardata_sdk/api/factors.py +45 -0
  9. ardata_sdk-1.0.0/ardata_sdk/api/fundamentals.py +156 -0
  10. ardata_sdk-1.0.0/ardata_sdk/api/history.py +79 -0
  11. ardata_sdk-1.0.0/ardata_sdk/api/index_concept.py +34 -0
  12. ardata_sdk-1.0.0/ardata_sdk/api/industry.py +22 -0
  13. ardata_sdk-1.0.0/ardata_sdk/api/margin.py +11 -0
  14. ardata_sdk-1.0.0/ardata_sdk/api/money_flow.py +19 -0
  15. ardata_sdk-1.0.0/ardata_sdk/api/mtss.py +17 -0
  16. ardata_sdk-1.0.0/ardata_sdk/api/price.py +74 -0
  17. ardata_sdk-1.0.0/ardata_sdk/api/securities.py +24 -0
  18. ardata_sdk-1.0.0/ardata_sdk/api/ticks.py +50 -0
  19. ardata_sdk-1.0.0/ardata_sdk/api/trade_days.py +11 -0
  20. ardata_sdk-1.0.0/ardata_sdk/config.py +30 -0
  21. ardata_sdk-1.0.0/ardata_sdk/finance/__init__.py +55 -0
  22. ardata_sdk-1.0.0/ardata_sdk/finance/columns.py +58 -0
  23. ardata_sdk-1.0.0/ardata_sdk/finance/query.py +45 -0
  24. ardata_sdk-1.0.0/ardata_sdk/finance/run_query.py +10 -0
  25. ardata_sdk-1.0.0/ardata_sdk/finance/tables.py +262 -0
  26. ardata_sdk-1.0.0/ardata_sdk.egg-info/PKG-INFO +74 -0
  27. ardata_sdk-1.0.0/ardata_sdk.egg-info/SOURCES.txt +31 -0
  28. ardata_sdk-1.0.0/ardata_sdk.egg-info/dependency_links.txt +1 -0
  29. ardata_sdk-1.0.0/ardata_sdk.egg-info/requires.txt +2 -0
  30. ardata_sdk-1.0.0/ardata_sdk.egg-info/top_level.txt +1 -0
  31. ardata_sdk-1.0.0/pyproject.toml +33 -0
  32. ardata_sdk-1.0.0/setup.cfg +4 -0
  33. ardata_sdk-1.0.0/setup.py +15 -0
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: ardata-sdk
3
+ Version: 1.0.0
4
+ Summary: ARData Python SDK - A-share market data client
5
+ Author-email: ARQuant <otreewen2020@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Keywords: a-share,stock,quant,china-market
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: requests>=2.28
19
+ Requires-Dist: pandas>=1.5
20
+ Dynamic: requires-python
21
+
22
+ # ARData SDK
23
+
24
+ Python SDK for ARQuant market data platform.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install ardata-sdk
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```python
35
+ import ardata_sdk as ardata
36
+
37
+ # Initialize with your API key and server URL
38
+ ardata.init(token="your-api-key", base_url="https://ardata.example.com")
39
+
40
+ # Get daily price data
41
+ df = ardata.get_price(["000001.XSHE", "600519.XSHG"],
42
+ start_date="2024-01-01",
43
+ end_date="2024-12-31",
44
+ frequency="daily",
45
+ fields=["open", "close", "high", "low", "volume"],
46
+ fq="pre")
47
+
48
+ # Get all A-share securities
49
+ securities = ardata.get_all_securities()
50
+
51
+ # Get trade days
52
+ trade_days = ardata.get_trade_days(start_date="2024-01-01", end_date="2024-12-31")
53
+
54
+ # Get industry info
55
+ industries = ardata.get_industry(["000001.XSHE"])
56
+ ```
57
+
58
+ ## API Reference
59
+
60
+ | Function | Description |
61
+ |----------|-------------|
62
+ | `get_price()` | Historical OHLCV data |
63
+ | `get_all_securities()` | List all securities |
64
+ | `get_security_info()` | Security details |
65
+ | `get_trade_days()` | Trading calendar |
66
+ | `get_industry()` | Industry classification |
67
+ | `get_index_stocks()` | Index constituents |
68
+ | `get_fundamentals()` | Financial data (valuation, balance sheet, etc.) |
69
+ | `get_money_flow()` | Capital flow data |
70
+ | `get_billboard_list()` | Dragon-tiger board data |
71
+ | `get_factor_values()` | Alpha factors |
72
+ | `history()` / `attribute_history()` | JQData-compatible history APIs |
73
+ | `get_ticks()` | Tick-level data |
74
+ | `finance.run_query()` | Financial table query DSL |
@@ -0,0 +1,53 @@
1
+ # ARData SDK
2
+
3
+ Python SDK for ARQuant market data platform.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install ardata-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import ardata_sdk as ardata
15
+
16
+ # Initialize with your API key and server URL
17
+ ardata.init(token="your-api-key", base_url="https://ardata.example.com")
18
+
19
+ # Get daily price data
20
+ df = ardata.get_price(["000001.XSHE", "600519.XSHG"],
21
+ start_date="2024-01-01",
22
+ end_date="2024-12-31",
23
+ frequency="daily",
24
+ fields=["open", "close", "high", "low", "volume"],
25
+ fq="pre")
26
+
27
+ # Get all A-share securities
28
+ securities = ardata.get_all_securities()
29
+
30
+ # Get trade days
31
+ trade_days = ardata.get_trade_days(start_date="2024-01-01", end_date="2024-12-31")
32
+
33
+ # Get industry info
34
+ industries = ardata.get_industry(["000001.XSHE"])
35
+ ```
36
+
37
+ ## API Reference
38
+
39
+ | Function | Description |
40
+ |----------|-------------|
41
+ | `get_price()` | Historical OHLCV data |
42
+ | `get_all_securities()` | List all securities |
43
+ | `get_security_info()` | Security details |
44
+ | `get_trade_days()` | Trading calendar |
45
+ | `get_industry()` | Industry classification |
46
+ | `get_index_stocks()` | Index constituents |
47
+ | `get_fundamentals()` | Financial data (valuation, balance sheet, etc.) |
48
+ | `get_money_flow()` | Capital flow data |
49
+ | `get_billboard_list()` | Dragon-tiger board data |
50
+ | `get_factor_values()` | Alpha factors |
51
+ | `history()` / `attribute_history()` | JQData-compatible history APIs |
52
+ | `get_ticks()` | Tick-level data |
53
+ | `finance.run_query()` | Financial table query DSL |
@@ -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)
@@ -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
@@ -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)