Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down Expand Up @@ -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` |
Expand Down Expand Up @@ -3022,6 +3023,16 @@ Documented endpoint: ``ths_concept_daily_flow``.

Documented endpoint: ``ths_industry_daily_flow``.

<h4 id="api-ths-industry-constituents"><code>ths_industry_constituents</code></h4>

- 同花顺行业成分股列表
- 原始接口:`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``.

<h4 id="api-ths-board-kline"><code>ths_board_kline</code></h4>

- 接口名称:同花顺板块K线
Expand Down Expand Up @@ -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`

Expand All @@ -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.
Expand All @@ -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`

Expand All @@ -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.
Expand Down
17 changes: 9 additions & 8 deletions src/ftshare/apis/fund.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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.
Expand All @@ -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',
Expand Down
8 changes: 8 additions & 0 deletions src/ftshare/apis/stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
5 changes: 3 additions & 2 deletions src/ftshare/endpoints/fund.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
9 changes: 9 additions & 0 deletions src/ftshare/endpoints/stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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线',
Expand Down
4 changes: 4 additions & 0 deletions src/ftshare/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 0 additions & 1 deletion tests/endpoint_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
60 changes: 60 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading