diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md
index 3b5f97b..b1f11e8 100644
--- a/docs/API_REFERENCE.md
+++ b/docs/API_REFERENCE.md
@@ -147,6 +147,7 @@
| [`ths_stock_daily_flow`](#api-ths-stock-daily-flow) | 同花顺个股资金流日度 | `GET` | `api/v1/market/data/ths-stock-daily-flow` | `start_date`, `end_date`, `code`, `name`, `page`, `page_size` | `同花顺个股资金流日度.md` |
| [`ths_concept_daily_flow`](#api-ths-concept-daily-flow) | 同花顺概念板块资金流日度 | `GET` | `api/v1/market/data/ths-concept-daily-flow` | `start_date`, `end_date`, `sector_name`, `page`, `page_size` | `同花顺概念板块资金流日度.md` |
| [`ths_industry_daily_flow`](#api-ths-industry-daily-flow) | 同花顺行业板块资金流日度 | `GET` | `api/v1/market/data/ths-industry-daily-flow` | `start_date`, `end_date`, `sector_name`, `page`, `page_size` | `同花顺行业板块资金流日度.md` |
+| [`ths_industry_constituents`](#api-ths-industry-constituents) | 同花顺行业成分股列表 | `GET` | `api/v1/market/data/ths-industry-constituents` | `industry_code`, `industry_name`, `stock_code`, `stock_name`, `page`, `page_size` | `同花顺行业成分股列表.md` |
| [`ths_hot_list`](#api-ths-hot-list) | 同花顺热榜 | `GET` | `api/v1/market/data/ths-hot-list` | `list_type`, `trade_date`, `page`, `page_size` | `同花顺热榜.md` |
| [`trading_calendar`](#api-trading-calendar) | 交易日历 | `GET` | `api/v1/market/data/time/trading-calendar` | `market`, `start_date`, `end_date` | `交易日历.md` |
| [`xueqiu_rank`](#api-xueqiu-rank) | 雪球股票排名 | `GET` | `api/v1/market/data/xueqiu-rank` | `rank_group`, `period`, `trade_date`, `page`, `page_size` | `雪球股票排名.md` |
@@ -210,8 +211,8 @@
| SDK 方法 | 接口名称 | HTTP | Path | 参数 | 来源文档 |
|---|---|---|---|---|---|
| [`fund_asset_allocation`](#api-fund-asset-allocation) | 基金资产配置 | `GET` | `api/v1/market/data/fund/fund-asset-allocation` | `fund_code`, `report_date`, `publish_date`, `start_date`, `end_date`, `page`, `page_size` | `基金资产配置.md` |
-| [`fund_basicinfo`](#api-fund-basicinfo) | 基金基础信息 | `GET` | `api/v1/market/data/fund/fund-basicinfo` | `institution_code`, `page`, `page_size` | `基金基础信息.md` |
-| [`fund_cal_return`](#api-fund-cal-return) | 基金收益 | `GET` | `api/v1/market/data/fund/fund-cal-return` | `institution_code`, `cal-type` | `基金收益.md` |
+| [`fund_basicinfo`](#api-fund-basicinfo) | 基金基础信息 | `GET` | `api/v1/market/data/fund/fund-basicinfo` | `fund_code`, `page`, `page_size` | `基金基础信息.md` |
+| [`fund_cal_return`](#api-fund-cal-return) | 基金收益 | `GET` | `api/v1/market/data/fund/fund-cal-return` | `fund_code`, `cal-type` | `基金收益.md` |
| [`fund_classification`](#api-fund-classification) | 基金分类 | `GET` | `api/v1/market/data/fund/fund-classification` | `fund_code`, `classify_std` | `基金分类.md` |
| [`fund_company`](#api-fund-company) | 基金公司 | `GET` | `api/v1/market/data/fund/fund-company` | `fund_company`, `page`, `page_size` | `基金公司.md` |
| [`fund_fee`](#api-fund-fee) | 基金费率 | `GET` | `api/v1/market/data/fund/fund-fee` | `fund_code`, `charge_type`, `client_type`, `page`, `page_size` | `基金费率.md` |
@@ -3022,6 +3023,16 @@ Documented endpoint: ``ths_concept_daily_flow``.
Documented endpoint: ``ths_industry_daily_flow``.
+
ths_industry_constituents
+
+- 同花顺行业成分股列表
+- 原始接口:`ths_industry_constituents`
+- Path:`api/v1/market/data/ths-industry-constituents`
+- Method:`GET`
+- 参数:`industry_code`, `industry_name`, `stock_code`, `stock_name`, `page`, `page_size`
+
+Documented endpoint: ``ths_industry_constituents``.
+
ths_board_kline
- 接口名称:同花顺板块K线
@@ -3889,7 +3900,7 @@ Returns:
- 接口名称:基金基础信息
- HTTP:`GET`
- Path:`api/v1/market/data/fund/fund-basicinfo`
-- 参数:`institution_code`, `page`, `page_size`
+- 参数:`fund_code`, `page`, `page_size`
- 来源文档:`基金基础信息.md`
- 原始接口:`get_fund_basicinfo`
@@ -3901,7 +3912,7 @@ Method: ``GET``.
Documented endpoint: ``get_fund_basicinfo``.
Args:
- institution_code: 基金代码 (type: string; required: Y).
+ fund_code: 基金代码;不传时查询全市场数据的默认分页 (type: string; required: N).
page: Page number, starting from 1. If omitted, the server default is used unless ``limit`` or ``all_pages`` is set.
page_size: Rows per page. The SDK validates this against the endpoint-specific maximum.
limit: Maximum number of rows to return. The SDK may fetch multiple pages to satisfy this limit.
@@ -3923,7 +3934,7 @@ Returns:
- 接口名称:基金收益
- HTTP:`GET`
- Path:`api/v1/market/data/fund/fund-cal-return`
-- 参数:`institution_code`, `cal-type`
+- 参数:`fund_code`, `cal-type`
- 来源文档:`基金收益.md`
- 原始接口:`get_fund_cal_return`
@@ -3935,7 +3946,7 @@ Method: ``GET``.
Documented endpoint: ``get_fund_cal_return``.
Args:
- institution_code: 基金代码(6位数字) (type: string; required: Y).
+ fund_code: 基金代码(6位数字) (type: string; required: Y).
cal_type: 查询区间:1M / 3M / 6M / 1Y / 3Y / 5Y / YTD(请求字段名为 `cal-type`) (type: string; required: Y). Request key: ``cal-type``.
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
diff --git a/src/ftshare/apis/fund.py b/src/ftshare/apis/fund.py
index c3c5c86..67517aa 100644
--- a/src/ftshare/apis/fund.py
+++ b/src/ftshare/apis/fund.py
@@ -13,7 +13,7 @@ class FundApiMixin:
def fund_basicinfo(
self,
- institution_code: Any | None = None,
+ fund_code: Any | None = None,
page: int | None = None,
page_size: int | None = None,
limit: int | None = None,
@@ -32,7 +32,7 @@ def fund_basicinfo(
Documented endpoint: ``get_fund_basicinfo``.
Args:
- institution_code: 基金代码 (type: string; required: Y).
+ fund_code: 基金代码;不传时查询全市场数据的默认分页 (type: string; required: N).
page: Page number, starting from 1. If omitted, the server default is used unless ``limit`` or ``all_pages`` is set.
page_size: Rows per page. The SDK validates this against the endpoint-specific maximum.
limit: Maximum number of rows to return. The SDK may fetch multiple pages to satisfy this limit.
@@ -48,16 +48,17 @@ def fund_basicinfo(
``as_dataframe=False``, raw JSON when ``raw=True``, or raw page
payloads when multi-page fetching is used with ``raw=True``.
"""
- request_params = {'institution_code': institution_code}
+ request_params = {'fund_code': fund_code}
request_params.update(kwargs)
- path = ENDPOINTS['fund_basicinfo'].path
+ endpoint = ENDPOINTS['fund_basicinfo']
return self.get_paginated(
- path,
+ endpoint.path,
page=page,
page_size=page_size,
limit=limit,
all_pages=all_pages,
max_pages=max_pages,
+ max_page_size=endpoint.max_page_size,
raw=raw,
fields=fields,
as_dataframe=as_dataframe,
@@ -66,7 +67,7 @@ def fund_basicinfo(
def fund_cal_return(
self,
- institution_code: Any | None = None,
+ fund_code: Any | None = None,
cal_type: Any | None = None,
*,
raw: bool = False,
@@ -81,7 +82,7 @@ def fund_cal_return(
Documented endpoint: ``get_fund_cal_return``.
Args:
- institution_code: 基金代码(6位数字) (type: string; required: Y).
+ fund_code: 基金代码(6位数字) (type: string; required: Y).
cal_type: 查询区间:1M / 3M / 6M / 1Y / 3Y / 5Y / YTD(请求字段名为 `cal-type`) (type: string; required: Y). Request key: ``cal-type``.
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
@@ -93,7 +94,7 @@ def fund_cal_return(
``as_dataframe=False``, raw JSON when ``raw=True``, or raw page
payloads when multi-page fetching is used with ``raw=True``.
"""
- request_params = {'institution_code': institution_code, 'cal-type': cal_type}
+ request_params = {'fund_code': fund_code, 'cal-type': cal_type}
request_params.update(kwargs)
return self._call_endpoint(
'fund_cal_return',
diff --git a/src/ftshare/apis/stock.py b/src/ftshare/apis/stock.py
index c69e7c4..2de859e 100644
--- a/src/ftshare/apis/stock.py
+++ b/src/ftshare/apis/stock.py
@@ -4100,6 +4100,14 @@ def ths_industry_daily_flow(self, start_date: Any | None = None, end_date: Any |
return self.get_paginated(path, page=page, page_size=page_size, limit=limit, all_pages=all_pages, max_pages=max_pages, max_page_size=1000, raw=raw, fields=fields, as_dataframe=as_dataframe, **params)
+ def ths_industry_constituents(self, industry_code: Any | None = None, industry_name: Any | None = None, stock_code: Any | None = None, stock_name: Any | None = None, page: int | None = None, page_size: int | None = None, limit: int | None = None, all_pages: bool = False, max_pages: int | None = None, *, raw: bool = False, fields: Sequence[str] | str | None = None, as_dataframe: bool = True, **kwargs: Any) -> Any:
+ """同花顺行业成分股列表."""
+ params = {'industry_code': industry_code, 'industry_name': industry_name, 'stock_code': stock_code, 'stock_name': stock_name}
+ params.update(kwargs)
+ path = ENDPOINTS['ths_industry_constituents'].path
+ return self.get_paginated(path, page=page, page_size=page_size, limit=limit, all_pages=all_pages, max_pages=max_pages, max_page_size=1000, raw=raw, fields=fields, as_dataframe=as_dataframe, **params)
+
+
def stock_realtime_minute_kline(self, symbols: Any | None = None, *, raw: bool = False, fields: Sequence[str] | str | None = None, as_dataframe: bool = True, **kwargs: Any) -> Any:
"""股票实时分钟K线."""
params = {'symbols': symbols}
diff --git a/src/ftshare/endpoints/fund.py b/src/ftshare/endpoints/fund.py
index d77a3a1..745b267 100644
--- a/src/ftshare/endpoints/fund.py
+++ b/src/ftshare/endpoints/fund.py
@@ -11,14 +11,15 @@
'title': '基金基础信息',
'doc_file': '基金基础信息.md',
'original_api': 'get_fund_basicinfo',
- 'params': ('institution_code', 'page', 'page_size'),
+ 'params': ('fund_code', 'page', 'page_size'),
+ 'max_page_size': 500,
},
'fund_cal_return': {
'path': 'api/v1/market/data/fund/fund-cal-return',
'title': '基金收益',
'doc_file': '基金收益.md',
'original_api': 'get_fund_cal_return',
- 'params': ('institution_code', 'cal-type'),
+ 'params': ('fund_code', 'cal-type'),
},
'fund_overview': {
'path': 'api/v1/market/data/fund/fund-overview',
diff --git a/src/ftshare/endpoints/stock.py b/src/ftshare/endpoints/stock.py
index d58287d..dbd1fa4 100644
--- a/src/ftshare/endpoints/stock.py
+++ b/src/ftshare/endpoints/stock.py
@@ -813,6 +813,15 @@
'max_page_size': 1000,
},
+ 'ths_industry_constituents': {
+ 'path': 'api/v1/market/data/ths-industry-constituents',
+ 'title': '同花顺行业成分股列表',
+ 'doc_file': '同花顺行业成分股列表.md',
+ 'original_api': 'ths_industry_constituents',
+ 'params': ('industry_code', 'industry_name', 'stock_code', 'stock_name', 'page', 'page_size'),
+ 'max_page_size': 1000,
+ },
+
'stock_realtime_minute_kline': {
'path': 'api/v4/market/data/stock-realtime-minute-kline',
'title': '股票实时分钟K线',
diff --git a/src/ftshare/response.py b/src/ftshare/response.py
index dbe4b22..e17cead 100644
--- a/src/ftshare/response.py
+++ b/src/ftshare/response.py
@@ -24,12 +24,16 @@ def extract_tabular(payload: Any) -> Any:
Supported envelopes:
- ``{"data": {"records": [...]}}``
+ - ``{"data": {"items": [...]}}``
+ - ``{"data": [...]}``
- ``{"items": [...]}``
Any unsupported shape is returned unchanged so callers do not lose data.
"""
if isinstance(payload, dict):
data = payload.get("data")
+ if isinstance(data, list):
+ return data
if isinstance(data, dict) and isinstance(data.get("records"), list):
return data["records"]
if isinstance(data, dict) and isinstance(data.get("items"), list):
diff --git a/tests/endpoint_cases.py b/tests/endpoint_cases.py
index e9b9edb..1c71656 100644
--- a/tests/endpoint_cases.py
+++ b/tests/endpoint_cases.py
@@ -56,7 +56,6 @@
"index_slug": "HSI",
"industry_code": "801010",
"inst_type": "all_inst",
- "institution_code": "000001",
"institution_id": "1001",
"industry_name": "银行",
"instrument_id": "A2609",
diff --git a/tests/test_client.py b/tests/test_client.py
index 38663c7..aadc6d3 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -860,6 +860,66 @@ def test_stock_share_chg_forwards_is_last_paging():
assert session.calls[0]["params"]["is_last"] == "true"
+def test_ths_industry_constituents_forwards_filters_and_pagination():
+ session = FakeSession([FakeResponse(payload={"items": [], "total_pages": 0, "total_items": 0})])
+ client = FtshareClient(session=session)
+
+ client.ths_industry_constituents(industry_name="证券", page=1, page_size=1000, as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/ths-industry-constituents"
+ assert session.calls[0]["params"] == {"industry_name": "证券", "page": 1, "page_size": 1000}
+
+
+def test_fund_basicinfo_paginated_with_fund_code():
+ session = FakeSession([FakeResponse(payload={
+ "code": 200,
+ "message": "success",
+ "data": {"items": [{"fund_code": "110011", "fund_name": "易方达优质精选混合(QDII)"}],
+ "page_num": 1, "page_size": 500, "total": 1, "pages": 1},
+ })])
+ client = FtshareClient(session=session)
+
+ rows = client.fund_basicinfo(fund_code="110011", page=1, page_size=500, as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/fund/fund-basicinfo"
+ assert session.calls[0]["params"] == {"fund_code": "110011", "page": 1, "page_size": 500}
+ assert rows == [{"fund_code": "110011", "fund_name": "易方达优质精选混合(QDII)"}]
+
+
+def test_fund_basicinfo_rejects_page_size_over_max():
+ session = FakeSession([])
+ client = FtshareClient(session=session)
+
+ with pytest.raises(ValueError):
+ client.fund_basicinfo(page=1, page_size=501, as_dataframe=False)
+
+ assert session.calls == []
+
+
+def test_fund_cal_return_forwards_fund_code_and_cal_type():
+ session = FakeSession([FakeResponse(payload=[])])
+ client = FtshareClient(session=session)
+
+ client.fund_cal_return(fund_code="110011", cal_type="1M", as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/fund/fund-cal-return"
+ assert session.calls[0]["params"] == {"fund_code": "110011", "cal-type": "1M"}
+
+
+def test_fund_cal_return_extracts_bare_array_data():
+ session = FakeSession([FakeResponse(payload={
+ "code": 200,
+ "message": "success",
+ "data": [{"date": 20260522, "return": 0.0}, {"date": 20260525, "return": 0.0016}],
+ })])
+ client = FtshareClient(session=session)
+
+ df = client.fund_cal_return(fund_code="110011", cal_type="1M")
+
+ assert list(df.columns) == ["date", "return"]
+ assert len(df) == 2
+
+
def test_fund_share_forwards_paginated_params():
session = FakeSession([FakeResponse(payload={"items": [], "total_pages": 0, "total_items": 0})])
client = FtshareClient(session=session)