diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md
index 937d799..0de8594 100644
--- a/docs/API_REFERENCE.md
+++ b/docs/API_REFERENCE.md
@@ -6,19 +6,19 @@
| 指标 | 数量 |
|---|---:|
-| SDK 方法总数 | 238 |
+| SDK 方法总数 | 244 |
## 专题分布
| ftshare-doc 专题 | SDK 方法数 | API mixin 模块 | Endpoint 模块 |
|---|---:|---|---|
-| 股票数据 | 127 | `ftshare.apis.stock` | `ftshare.endpoints.stock` |
+| 股票数据 | 131 | `ftshare.apis.stock` | `ftshare.endpoints.stock` |
| 港股数据 | 3 | `ftshare.apis.hk` | `ftshare.endpoints.hk` |
| 美股数据 | 2 | `ftshare.apis.us` | `ftshare.endpoints.us` |
| 指数专题 | 15 | `ftshare.apis.index` | `ftshare.endpoints.index` |
| ETF专题 | 17 | `ftshare.apis.etf` | `ftshare.endpoints.etf` |
| 公募基金 | 18 | `ftshare.apis.fund` | `ftshare.endpoints.fund` |
-| 期货数据 | 18 | `ftshare.apis.futures` | `ftshare.endpoints.futures` |
+| 期货数据 | 20 | `ftshare.apis.futures` | `ftshare.endpoints.futures` |
| 债券专题 | 9 | `ftshare.apis.bond` | `ftshare.endpoints.bond` |
| 宏观经济 | 23 | `ftshare.apis.economic` | `ftshare.endpoints.economic` |
| 大模型语料 | 5 | `ftshare.apis.llm_corpus` | `ftshare.endpoints.llm_corpus` |
@@ -55,7 +55,7 @@
| [`cashflow_stock_code`](#api-cashflow-stock-code) | 现金流支持股票代码 | `GET` | `api/v2/market/data/finance/cashflow-stock-code` | - | `现金流支持股票代码.md` |
| [`company_list`](#api-company-list) | 公司列表 | `GET` | `api/v1/market/data/company-list` | `stock_name`, `stock_code`, `page`, `page_size` | `公司列表.md` |
| [`earnings_reports_paginated`](#api-earnings-reports-paginated) | 业绩快报 | `GET` | `api/v1/market/data/finance/stock-performance-express` | `stock_code`, `year`, `report_type`, `page`, `page_size` | `业绩快报.md` |
-| [`eastmoney_board_constituents`](#api-eastmoney-board-constituents) | 东方财富板块成份股 | `GET` | `api/v1/market/data/eastmoney-board-constituents` | `board_code` | `东方财富板块成份股.md` |
+| [`eastmoney_board_constituents`](#api-eastmoney-board-constituents) | 东方财富板块成分股 | `GET` | `api/v1/market/data/eastmoney-board-constituents` | `board_code`, `date`, `page`, `page_size` | `东方财富板块成分股.md` |
| [`eastmoney_board_daily_kline`](#api-eastmoney-board-daily-kline) | 东方财富板块日线OHLC | `GET` | `api/v1/market/data/eastmoney-board-daily-ohlc` | `board_code`, `start_date`, `end_date`, `page`, `page_size` | `东方财富板块日线OHLC.md` |
| [`eastmoney_concept_boards`](#api-eastmoney-concept-boards) | 东方财富概念板块 | `GET` | `api/v1/market/data/eastmoney-concept-boards` | - | `东方财富概念板块.md` |
| [`eastmoney_dapan_flow`](#api-eastmoney-dapan-flow) | 东方财富大盘资金流 | `GET` | `api/v1/market/data/eastmoney-dapan-flow` | `trade_date`, `start_date`, `end_date`, `page`, `page_size` | `东方财富大盘资金流.md` |
@@ -90,6 +90,7 @@
| [`southbound`](#api-southbound) | 南向资金交易 | `GET` | `api/v1/market/data/southbound` | `date` | `南向资金交易.md` |
| [`stk_alert_broker`](#api-stk-alert-broker) | 交易所重点提示证券 | `GET` | `api/v2/market/data/stk-alert-broker` | `ts_code`, `start_date`, `end_date`, `page`, `page_size` | `交易所重点提示证券.md` |
| [`stk_code_change`](#api-stk-code-change) | A股代码变更 | `GET` | `api/v1/market/data/stk-code-change` | `trade_code`, `start_date`, `end_date` | `A股代码变更.md` |
+| [`stk_code_change_timeline`](#api-stk-code-change-timeline) | A股代码变更时间线 | `GET` | `api/v1/market/data/stk_code_change_timeline` | - | `A股代码变更时间线.md` |
| [`stk_status_change`](#api-stk-status-change) | A股状态变更 | `GET` | `api/v1/market/data/stk-status-change` | `trade_code`, `change_date`, `change_type` | `A股状态变更.md` |
| [`stk_limit`](#api-stk-limit) | 涨跌停价 | `GET` | `api/v1/market/data/stk-limit` | `instrument_type`, `symbol`, `symbol_id`, `market_id`, `trade_date`, `start_date`, `end_date`, `page`, `page_size` | `涨跌停价.md` |
| [`stk_manager_hold`](#api-stk-manager-hold) | 上市公司管理层持股 | `GET` | `api/v1/market/data/stk-manager-hold` | `trade_code`, `end_date` | `上市公司管理层持股.md` |
@@ -100,6 +101,7 @@
| [`stk_surv`](#api-stk-surv) | 个股严重异常波动 | `GET` | `api/v2/market/data/stk-surv` | `ts_code`, `trade_date`, `start_date`, `end_date`, `page`, `page_size` | `个股严重异常波动.md` |
| [`stock_adjust_factor`](#api-stock-adjust-factor) | 股票复权因子 | `GET` | `api/v1/market/data/stock-adjust-factor` | `symbol`, `trade_date`, `start_date`, `end_date`, `page`, `page_size` | `股票复权因子.md` |
| [`stock_candlesticks`](#api-stock-candlesticks) | 股票K线 | `GET` | `api/v1/market/data/stock-candlesticks` | `symbol`, `interval_unit`, `adjust_kind`, `since_ts_millis`, `until_ts_millis`, `limit` | `股票K线.md` |
+| [`stock_candlesticks_daily`](#api-stock-candlesticks-daily) | 股票日K快照 | `GET` | `api/v1/market/data/stock-candlesticks-daily` | `trade_date`, `page`, `page_size` | `股票日K快照.md` |
| [`stock_capital_flows`](#api-stock-capital-flows) | 股票资金流向 | `GET` | `api/v1/market/data/stock-capital-flows` | `date`, `time`, `symbol`, `page`, `page_size` | `股票资金流向.md` |
| [`stock_comment_desire_em`](#api-stock-comment-desire-em) | 千股千评意愿度 | `GET` | `api/v1/market/data/stock-comment/desire` | `symbol` | `千股千评意愿度.md` |
| [`stock_comment_em`](#api-stock-comment-em) | 千股千评 | `GET` | `api/v1/market/data/stock-comment/index` | `page`, `page_size` | `千股千评.md` |
@@ -110,6 +112,7 @@
| [`stock_daec_stocks`](#api-stock-daec-stocks) | A股行情列表 | `GET` | `api/v1/market/data/daec/stocks/{board}` | `board`, `page`, `page_size`, `filter`, `order_by` | `A股行情列表.md` |
| [`stock_description`](#api-stock-description) | 股票基础信息 | `GET` | `api/v1/market/data/stock-description` | `symbol_id`, `page`, `page_size` | `股票基础信息.md` |
| [`stock_dividends`](#api-stock-dividends) | 股票分红记录 | `GET` | `api/v1/market/data/stock-dividends` | `symbol`, `since_date`, `until_date`, `page`, `page_size` | `股票分红记录.md` |
+| [`stock_factor`](#api-stock-factor) | 股票技术面因子 | `GET` | `api/v2/market/data/stock-factor` | `symbol`, `trade_date`, `category` | `股票技术面因子.md` |
| [`stock_filter`](#api-stock-filter) | 股票筛选 | `GET` | `api/v2/market/data/stock-list/filter` | `symbol`, `board`, `listing_date_since`, `page`, `page_size` | `股票筛选.md` |
| [`stock_float_holders`](#api-stock-float-holders) | 十大流通股东 | `GET` | `api/v1/market/data/holder/stock-holder-ften` | `stock_code`, `is_last`, `page`, `page_size` | `十大流通股东.md` |
| [`stock_ggcg_em`](#api-stock-ggcg-em) | 东方财富股东增减持 | `GET` | `api/v1/market/data/holder/stock-ggcg-em` | `symbol`, `page`, `page_size` | `东方财富股东增减持.md` |
@@ -135,7 +138,7 @@
| [`stock_realtime_day_kline`](#api-stock-realtime-day-kline) | 股票实时日K线 | `GET` | `api/v4/market/data/stock-realtime-day-kline` | `symbols` | `股票实时日K线.md` |
| [`stock_realtime_list`](#api-stock-realtime-list) | A股行情列表 | `GET` | `api/v1/market/data/stock-list/{board}` | `board`, `page`, `page_size` | `A股行情列表.md` |
| [`stock_realtime_minute_kline`](#api-stock-realtime-minute-kline) | 股票实时分钟K线 | `GET` | `api/v4/market/data/stock-realtime-minute-kline` | `symbols` | `股票实时分钟K线.md` |
-| [`stock_share`](#api-stock-share) | 股本 | `GET` | `api/v2/market/data/share/get-stock-share` | `stock_code`, `date` | `股本.md` |
+| [`stock_share`](#api-stock-share) | 股本 | `GET` | `api/v2/market/data/share/get-stock-share` | `stock_code`, `date`, `date_type`, `page`, `page_size` | `股本.md` |
| [`stock_share_chg`](#api-stock-share-chg) | 股东增减持 | `GET` | `api/v1/market/data/holder/stock-share-chg` | `stock_code`, `is_last`, `page`, `page_size` | `股东增减持.md` |
| [`stock_st_history`](#api-stock-st-history) | 股票历史ST状态 | `GET` | `api/v1/market/data/stock-st-history` | `symbol`, `st_type` | `股票历史ST状态.md` |
| [`stock_unlock`](#api-stock-unlock) | 限售解禁 | `GET` | `api/v1/market/data/unlock/stock_unlock` | `stock_code`, `start_date`, `end_date`, `page`, `page_size` | `限售解禁.md` |
@@ -150,6 +153,7 @@
| [`tdx_board_index`](#api-tdx-board-index) | 通达信板块指数最新快照 | `GET` | `api/v1/market/data/tdx-board-index` | `ts_code`, `board_name`, `board_type`, `board_type_code`, `market`, `page`, `page_size` | `通达信板块指数最新快照.md` |
| [`tdx_board_members`](#api-tdx-board-members) | 通达信板块成分股最新快照 | `GET` | `api/v1/market/data/tdx-board-members` | `ts_code`, `board_name`, `board_type`, `board_type_code`, `con_code`, `con_name`, `market`, `page`, `page_size` | `通达信板块成分股最新快照.md` |
| [`ths_board_kline`](#api-ths-board-kline) | 同花顺板块K线 | `GET` | `api/v1/market/data/ths-board-kline` | `board_code`, `page`, `page_size` | `同花顺板块K线.md` |
+| [`ths_board_constituents`](#api-ths-board-constituents) | 同花顺板块成分股 | `GET` | `api/v1/market/data/ths-board-constituents` | `board_code`, `board_name`, `board_type`, `date`, `page`, `page_size` | `同花顺板块成分股.md` |
| [`stock_signal_latest_snapshot`](#api-stock-signal-latest-snapshot) | 信号最新快照 | `GET` | `api/v3/market/data/stock-signal-latest-snapshot` | `signal_type`, `page`, `page_size` | `信号最新快照.md` |
| [`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`, `board_name`, `page`, `page_size` | `同花顺概念板块资金流日度.md` |
@@ -260,9 +264,11 @@
| [`fut_settle`](#api-fut-settle) | 期货每日结算参数 | `GET` | `api/v1/market/data/futures/fut-settle` | `ts_code`, `trade_date`, `start_date`, `end_date`, `exchange`, `page`, `page_size` | `期货每日结算参数.md` |
| [`fut_weekly_detail`](#api-fut-weekly-detail) | 期货主要品种交易周报 | `GET` | `api/v1/market/data/futures/fut-weekly-detail` | `week`, `prd`, `start_week`, `end_week`, `exchange`, `page`, `page_size` | `期货主要品种交易周报.md` |
| [`fut_wsr`](#api-fut-wsr) | 期货仓单日报 | `GET` | `api/v1/market/data/futures/fut-wsr` | `trade_date`, `start_date`, `end_date`, `symbol`, `exchange`, `page`, `page_size` | `期货仓单日报.md` |
-| [`futures_contract_kline`](#api-futures-contract-kline) | 期货行情 | `GET` | `api/v1/market/data/futures/kline` | `symbol`, `interval`, `start`, `end`, `limit` | `期货行情.md` |
-| [`futures_minutes`](#api-futures-minutes) | 期货历史分钟行情 | `GET` | `api/v2/market/data/futures_minutes` | `symbol`, `interval`, `start`, `end`, `limit` | `期货历史分钟行情.md` |
+| [`futures_kline`](#api-futures-kline) | 期货合约历史K线 | `GET` | `api/v1/market/data/futures/kline` | `symbol`, `interval`, `start`, `end`, `limit` | `期货合约历史K线.md` |
+| [`futures_kline_batch`](#api-futures-kline-batch) | 批量期货合约历史K线 | `GET` | `api/v2/market/data/futures/kline/batch` | `symbols`, `interval`, `start`, `end`, `limit` | `批量期货合约历史K线.md` |
+| [`futures_minutes`](#api-futures-minutes) | 期货历史分钟K线 | `GET` | `api/v2/market/data/futures_minutes` | `symbol`, `symbols`, `interval`, `start`, `end`, `limit` | `期货历史分钟K线.md` |
| [`futures_minutes_realtime`](#api-futures-minutes-realtime) | 期货实时分钟K线 | `GET` | `api/v4/market/data/futures_minutes/realtime` | `symbols` | `期货实时分钟K线.md` |
+| [`futures_realtime_day_kline`](#api-futures-realtime-day-kline) | 期货实时日K线 | `GET` | `api/v4/market/data/futures-realtime-day-kline` | `symbols` | `期货实时日K线.md` |
| [`futures_nanhua_index_kline`](#api-futures-nanhua-index-kline) | 南华期货指数日K线 | `GET` | `api/v1/market/data/futures/nanhua-index-kline` | `code`, `trade_date`, `start_date`, `end_date`, `page`, `page_size` | `南华期货指数日K线.md` |
| [`major_contract`](#api-major-contract) | 重大合同 | `GET` | `api/v1/market/data/corporate/contract` | `start_date`, `end_date` | `重大合同.md` |
| [`major_contract_by_symbol`](#api-major-contract-by-symbol) | 重大合同按标的 | `GET` | `api/v1/market/data/corporate/contract/by-symbol` | `symbol`, `page`, `page_size` | `重大合同按标的.md` |
@@ -697,22 +703,28 @@ Returns:
eastmoney_board_constituents
-- 接口名称:东方财富板块成份股
+- 接口名称:东方财富板块成分股
- HTTP:`GET`
- Path:`api/v1/market/data/eastmoney-board-constituents`
-- 参数:`board_code`
-- 来源文档:`东方财富板块成份股.md`
+- 参数:`board_code`, `date`, `page`, `page_size`
+- 来源文档:`东方财富板块成分股.md`
- 原始接口:`eastmoney_board_constituents`
```text
-东方财富板块成份股.
+东方财富板块成分股.
Endpoint: ``api/v1/market/data/eastmoney-board-constituents``.
Method: ``GET``.
Documented endpoint: ``eastmoney_board_constituents``.
Args:
- board_code: 板块代码,如 BK1024 (type: string; required: Y).
+ board_code: 板块代码,取东方财富板块代码(BK 开头),如 BK0475、BK0490、BK0153 (type: string; required: Y).
+ date: 查询日期,格式 YYYYMMDD;不传返回当前仍在板块内的成分股 (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.
+ all_pages: Fetch and combine pages until the server reports the last page.
+ max_pages: Optional safety cap for ``all_pages``.
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
@@ -1893,6 +1905,19 @@ Returns:
payloads when multi-page fetching is used with ``raw=True``.
```
+stock_candlesticks_daily
+
+- 接口名称:股票日K快照
+- HTTP:`GET`
+- Path:`api/v1/market/data/stock-candlesticks-daily`
+- 参数:`trade_date`, `page`, `page_size`
+- 来源文档:`股票日K快照.md`
+- 原始接口:`stock_candlesticks_daily`
+
+```text
+股票日K快照.
+```
+
stock_capital_flows
- 接口名称:股票资金流向
@@ -2198,6 +2223,38 @@ Returns:
payloads when multi-page fetching is used with ``raw=True``.
```
+stock_factor
+
+- 接口名称:股票技术面因子
+- HTTP:`GET`
+- Path:`api/v2/market/data/stock-factor`
+- 参数:`symbol`, `trade_date`, `category`
+- 来源文档:`股票技术面因子.md`
+- 原始接口:`market_stock_factor`
+
+```text
+股票技术面因子.
+
+Endpoint: ``api/v2/market/data/stock-factor``.
+Method: ``GET``.
+Documented endpoint: ``market_stock_factor``.
+
+Args:
+ symbol: 沪深北 A 股代码,必须带交易所后缀,如 ``000001.SZ`` (type: string; required: Y).
+ trade_date: 目标日期 YYYYMMDD;不传时以当前日期为目标日期 (type: string; required: N).
+ category: 返回字段分类:``quote``、``valuation``、``capital``、``trend``、``momentum``、``volatility``、``volume`` 或 ``sentiment``;不传时返回全部字段 (type: string; required: N).
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged. Useful when the service adds parameters before the SDK is regenerated.
+
+Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, raw JSON when ``raw=True``。服务端把因子放在
+ ``data.values``,SDK 直接返回该对象(``as_dataframe=True`` 时为单行
+ ``DataFrame``)。
+```
+
stock_filter
- 接口名称:股票筛选
@@ -2924,20 +2981,26 @@ Returns:
- 接口名称:股本
- HTTP:`GET`
- Path:`api/v2/market/data/share/get-stock-share`
-- 参数:`stock_code`, `date`
+- 参数:`stock_code`, `date`, `date_type`, `page`, `page_size`
- 来源文档:`股本.md`
- 原始接口:`get_stock_share`
```text
股本.
-Endpoint: ``api/v1/market/data/share/get-stock-share``.
+Endpoint: ``api/v2/market/data/share/get-stock-share``.
Method: ``GET``.
Documented endpoint: ``get_stock_share``.
Args:
- stock_code: 股票代码 (type: string; required: Y).
+ stock_code: 股票代码;不传时查询指定日期的全市场股本 (type: string; required: N).
date: 日期 YYYYMMDD (type: string; required: Y).
+ date_type: 日期条件类型:``publish_date``(发布日期,默认)或 ``end_date``(生效日期) (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.
+ all_pages: Fetch and combine pages until the server reports the last page.
+ max_pages: Optional safety cap for ``all_pages``.
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
@@ -3235,6 +3298,33 @@ Returns:
payloads when multi-page fetching is used with ``raw=True``.
```
+stk_code_change_timeline
+
+- 接口名称:A股代码变更时间线
+- HTTP:`GET`
+- Path:`api/v1/market/data/stk_code_change_timeline`
+- 参数:-
+- 来源文档:`A股代码变更时间线.md`
+- 原始接口:`stk_code_change_timeline`
+
+```text
+A股代码变更时间线.
+
+Endpoint: ``api/v1/market/data/stk_code_change_timeline``.
+Method: ``GET``.
+Documented endpoint: ``stk_code_change_timeline``.
+
+Args:
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged. Useful when the service adds parameters before the SDK is regenerated.
+
+Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, raw JSON when ``raw=True``.
+```
+
tdx_board_members
- 接口名称:通达信板块成分股最新快照
@@ -3403,6 +3493,19 @@ Returns:
payloads when multi-page fetching is used with ``raw=True``.
```
+ths_board_constituents
+
+- 接口名称:同花顺板块成分股
+- HTTP:`GET`
+- Path:`api/v1/market/data/ths-board-constituents`
+- 参数:`board_code`, `board_name`, `board_type`, `date`, `page`, `page_size`
+- 来源文档:`同花顺板块成分股.md`
+- 原始接口:`ths_board_constituents`
+
+```text
+同花顺板块成分股.
+```
+
xueqiu_rank
- 接口名称:雪球股票排名
@@ -5020,21 +5123,21 @@ Returns:
payloads when multi-page fetching is used with ``raw=True``.
```
-futures_contract_kline
+futures_kline
-- 接口名称:期货行情
+- 接口名称:期货合约历史K线
- HTTP:`GET`
- Path:`api/v1/market/data/futures/kline`
- 参数:`symbol`, `interval`, `start`, `end`, `limit`
-- 来源文档:`期货行情.md`
-- 原始接口:`futures_contract_kline`
+- 来源文档:`期货合约历史K线.md`
+- 原始接口:`futures_kline`
```text
-期货行情.
+期货合约历史K线.
Endpoint: ``api/v1/market/data/futures/kline``.
Method: ``GET``.
-Documented endpoint: ``futures_contract_kline``.
+Documented endpoint: ``futures_kline``.
Args:
symbol: 期货合约代码 (type: string; required: Y).
@@ -5052,27 +5155,60 @@ Returns:
``as_dataframe=False``, or raw JSON when ``raw=True``.
```
+futures_kline_batch
+
+- 接口名称:批量期货合约历史K线
+- HTTP:`GET`
+- Path:`api/v2/market/data/futures/kline/batch`
+- 参数:`symbols`, `interval`, `start`, `end`, `limit`
+- 来源文档:`批量期货合约历史K线.md`
+- 原始接口:`futures_kline_batch`
+
+```text
+批量期货合约历史K线.
+
+Endpoint: ``api/v2/market/data/futures/kline/batch``.
+Method: ``GET``.
+Documented endpoint: ``futures_kline_batch``.
+
+Args:
+ symbols: 1~20 个期货合约代码;支持重复参数、逗号分隔或列表 (type: string[]; required: Y).
+ interval: K 线周期,默认 ``daily`` (type: string; required: N).
+ start: 起始时间戳,单位毫秒 (type: integer; required: N).
+ end: 结束时间戳,单位毫秒,不能单独传 (type: integer; required: N).
+ limit: 每个合约返回条数上限,默认 500 (type: integer; required: N).
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged.
+
+Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, or raw JSON when ``raw=True``.
+```
+
futures_minutes
-- 接口名称:期货历史分钟行情
+- 接口名称:期货历史分钟K线
- HTTP:`GET`
- Path:`api/v2/market/data/futures_minutes`
-- 参数:`symbol`, `interval`, `start`, `end`, `limit`
-- 来源文档:`期货历史分钟行情.md`
+- 参数:`symbol`, `symbols`, `interval`, `start`, `end`, `limit`
+- 来源文档:`期货历史分钟K线.md`
- 原始接口:`futures_minutes`
```text
-期货历史分钟行情.
+期货历史分钟K线.
Endpoint: ``api/v2/market/data/futures_minutes``.
Method: ``GET``.
Documented endpoint: ``futures_minutes``.
Args:
- symbol: 期货合约代码,带交易所短后缀 (type: string; required: Y).
+ symbol: 单个期货合约代码,带交易所短后缀;与 ``symbols`` 二选一 (type: string; required: N).
+ symbols: 1~20 个期货合约代码;支持重复参数、逗号分隔或列表;与 ``symbol`` 二选一 (type: string[]; required: N).
interval: 分钟周期,默认 ``1min`` (type: string; required: N).
- start: 起始时间戳,单位毫秒 (type: integer; required: N).
- end: 结束时间戳,单位毫秒,不能单独传 (type: integer; required: N).
+ start: 起始时间戳,单位毫秒;必填 (type: integer; required: Y).
+ end: 结束时间戳,单位毫秒;必填,不能单独传 (type: integer; required: Y).
limit: 返回条数上限,范围 1~1000 (type: integer; required: N).
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
@@ -5149,6 +5285,34 @@ Returns:
payloads when multi-page fetching is used with ``raw=True``.
```
+futures_realtime_day_kline
+
+- 接口名称:期货实时日K线
+- HTTP:`GET`
+- Path:`api/v4/market/data/futures-realtime-day-kline`
+- 参数:`symbols`
+- 来源文档:`期货实时日K线.md`
+- 原始接口:`futures_realtime_day_kline`
+
+```text
+期货实时日K线.
+
+Endpoint: ``api/v4/market/data/futures-realtime-day-kline``.
+Method: ``GET``.
+Documented endpoint: ``futures_realtime_day_kline``.
+
+Args:
+ symbols: 1~20 个期货合约代码;支持重复参数、逗号分隔或列表 (type: string[]; required: Y).
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged.
+
+Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, or raw JSON when ``raw=True``.
+```
+
major_contract
- 接口名称:重大合同
diff --git a/pyproject.toml b/pyproject.toml
index 257f5c0..5a1892f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ftshare"
-version = "1.0.10"
+version = "1.0.11"
description = "Python SDK for FTShare market data APIs."
readme = "README.md"
requires-python = ">=3.9"
diff --git a/src/ftshare/apis/futures.py b/src/ftshare/apis/futures.py
index dd6b38b..e53a074 100644
--- a/src/ftshare/apis/futures.py
+++ b/src/ftshare/apis/futures.py
@@ -298,6 +298,7 @@ def eastmoney_futures_position(
def futures_minutes(
self,
symbol: Any | None = None,
+ symbols: Any | None = None,
interval: Any | None = None,
start: Any | None = None,
end: Any | None = None,
@@ -308,17 +309,18 @@ def futures_minutes(
as_dataframe: bool = True,
**kwargs: Any,
) -> Any:
- """期货历史分钟行情.
+ """期货历史分钟K线.
Endpoint: ``api/v2/market/data/futures_minutes``.
Method: ``GET``.
Documented endpoint: ``futures_minutes``.
Args:
- symbol: 期货合约代码,带交易所短后缀 (type: string; required: Y).
+ symbol: 单个期货合约代码,带交易所短后缀;与 ``symbols`` 二选一 (type: string; required: N).
+ symbols: 1~20 个期货合约代码;支持重复参数、逗号分隔或列表;与 ``symbol`` 二选一 (type: string[]; required: N).
interval: 分钟周期,默认 ``1min`` (type: string; required: N).
- start: 起始时间戳,单位毫秒 (type: integer; required: N).
- end: 结束时间戳,单位毫秒,不能单独传 (type: integer; required: N).
+ start: 起始时间戳,单位毫秒;必填 (type: integer; required: Y).
+ end: 结束时间戳,单位毫秒;必填,不能单独传 (type: integer; required: Y).
limit: 返回条数上限,范围 1~1000 (type: integer; required: N).
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
@@ -331,6 +333,7 @@ def futures_minutes(
"""
request_params = {
'symbol': symbol,
+ 'symbols': symbols,
'interval': interval,
'start': start,
'end': end,
@@ -345,7 +348,7 @@ def futures_minutes(
**request_params,
)
- def futures_contract_kline(
+ def futures_kline(
self,
symbol: Any | None = None,
interval: Any | None = None,
@@ -358,11 +361,11 @@ def futures_contract_kline(
as_dataframe: bool = True,
**kwargs: Any,
) -> Any:
- """期货行情.
+ """期货合约历史K线.
Endpoint: ``api/v1/market/data/futures/kline``.
Method: ``GET``.
- Documented endpoint: ``futures_contract_kline``.
+ Documented endpoint: ``futures_kline``.
Args:
symbol: 期货合约代码 (type: string; required: Y).
@@ -388,7 +391,57 @@ def futures_contract_kline(
}
request_params.update(kwargs)
return self._call_endpoint(
- 'futures_contract_kline',
+ 'futures_kline',
+ raw=raw,
+ fields=fields,
+ as_dataframe=as_dataframe,
+ **request_params,
+ )
+
+ def futures_kline_batch(
+ self,
+ symbols: Any | None = None,
+ interval: Any | None = None,
+ start: Any | None = None,
+ end: Any | None = None,
+ limit: Any | None = None,
+ *,
+ raw: bool = False,
+ fields: Sequence[str] | str | None = None,
+ as_dataframe: bool = True,
+ **kwargs: Any,
+ ) -> Any:
+ """批量期货合约历史K线.
+
+ Endpoint: ``api/v2/market/data/futures/kline/batch``.
+ Method: ``GET``.
+ Documented endpoint: ``futures_kline_batch``.
+
+ Args:
+ symbols: 1~20 个期货合约代码;支持重复参数、逗号分隔或列表 (type: string[]; required: Y).
+ interval: K 线周期,默认 ``daily`` (type: string; required: N).
+ start: 起始时间戳,单位毫秒 (type: integer; required: N).
+ end: 结束时间戳,单位毫秒,不能单独传 (type: integer; required: N).
+ limit: 每个合约返回条数上限,默认 500 (type: integer; required: N).
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged.
+
+ Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, or raw JSON when ``raw=True``.
+ """
+ request_params = {
+ 'symbols': symbols,
+ 'interval': interval,
+ 'start': start,
+ 'end': end,
+ 'limit': limit,
+ }
+ request_params.update(kwargs)
+ return self._call_endpoint(
+ 'futures_kline_batch',
raw=raw,
fields=fields,
as_dataframe=as_dataframe,
@@ -430,6 +483,43 @@ def futures_minutes_realtime(
as_dataframe=as_dataframe,
**request_params,
)
+
+ def futures_realtime_day_kline(
+ self,
+ symbols: Any | None = None,
+ *,
+ raw: bool = False,
+ fields: Sequence[str] | str | None = None,
+ as_dataframe: bool = True,
+ **kwargs: Any,
+ ) -> Any:
+ """期货实时日K线.
+
+ Endpoint: ``api/v4/market/data/futures-realtime-day-kline``.
+ Method: ``GET``.
+ Documented endpoint: ``futures_realtime_day_kline``.
+
+ Args:
+ symbols: 1~20 个期货合约代码;支持重复参数、逗号分隔或列表 (type: string[]; required: Y).
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged.
+
+ Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, or raw JSON when ``raw=True``.
+ """
+ request_params = {'symbols': symbols}
+ request_params.update(kwargs)
+ return self._call_endpoint(
+ 'futures_realtime_day_kline',
+ raw=raw,
+ fields=fields,
+ as_dataframe=as_dataframe,
+ **request_params,
+ )
+
def fut_wsr(
self,
trade_date: Any | None = None,
diff --git a/src/ftshare/apis/stock.py b/src/ftshare/apis/stock.py
index 619572d..5d1d079 100644
--- a/src/ftshare/apis/stock.py
+++ b/src/ftshare/apis/stock.py
@@ -1104,20 +1104,32 @@ def company_list(
def eastmoney_board_constituents(
self,
board_code: Any | None = None,
+ date: 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:
- """东方财富板块成份股.
+ """东方财富板块成分股.
Endpoint: ``api/v1/market/data/eastmoney-board-constituents``.
Method: ``GET``.
Documented endpoint: ``eastmoney_board_constituents``.
Args:
- board_code: 板块代码,如 BK1024 (type: string; required: Y).
+ board_code: 板块代码,取东方财富板块代码(BK 开头),如 BK0475、BK0490、BK0153 (type: string; required: Y).
+ date: 查询日期,格式 YYYYMMDD;不传返回当前仍在板块内的成分股 (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.
+ all_pages: Fetch and combine pages until the server reports the last page.
+ max_pages: Optional safety cap for ``all_pages``.
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
@@ -1128,10 +1140,17 @@ def eastmoney_board_constituents(
``as_dataframe=False``, raw JSON when ``raw=True``, or raw page
payloads when multi-page fetching is used with ``raw=True``.
"""
- request_params = {'board_code': board_code}
+ request_params = {'board_code': board_code, 'date': date}
request_params.update(kwargs)
- return self._call_endpoint(
- 'eastmoney_board_constituents',
+ path = ENDPOINTS['eastmoney_board_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=ENDPOINTS['eastmoney_board_constituents'].max_page_size,
raw=raw,
fields=fields,
as_dataframe=as_dataframe,
@@ -1695,6 +1714,33 @@ def stk_code_change(
request_params.update(kwargs)
return self._call_endpoint('stk_code_change', raw=raw, fields=fields, as_dataframe=as_dataframe, **request_params)
+ def stk_code_change_timeline(
+ self,
+ *,
+ raw: bool = False,
+ fields: Sequence[str] | str | None = None,
+ as_dataframe: bool = True,
+ **kwargs: Any,
+ ) -> Any:
+ """A股代码变更时间线.
+
+ Endpoint: ``api/v1/market/data/stk_code_change_timeline``.
+ Method: ``GET``.
+ Documented endpoint: ``stk_code_change_timeline``.
+
+ Args:
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged. Useful when the service adds parameters before the SDK is regenerated.
+
+ Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, raw JSON when ``raw=True``.
+ """
+ request_params = dict(kwargs)
+ return self._call_endpoint('stk_code_change_timeline', raw=raw, fields=fields, as_dataframe=as_dataframe, **request_params)
+
def stk_status_change(
self,
trade_code: Any | None = None,
@@ -3881,6 +3927,12 @@ def stock_share(
self,
stock_code: Any | None = None,
date: Any | None = None,
+ date_type: 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,
@@ -3889,13 +3941,19 @@ def stock_share(
) -> Any:
"""股本.
- Endpoint: ``api/v1/market/data/share/get-stock-share``.
+ Endpoint: ``api/v2/market/data/share/get-stock-share``.
Method: ``GET``.
Documented endpoint: ``get_stock_share``.
Args:
- stock_code: 股票代码 (type: string; required: Y).
+ stock_code: 股票代码;不传时查询指定日期的全市场股本 (type: string; required: N).
date: 日期 YYYYMMDD (type: string; required: Y).
+ date_type: 日期条件类型:``publish_date``(发布日期,默认)或 ``end_date``(生效日期) (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.
+ all_pages: Fetch and combine pages until the server reports the last page.
+ max_pages: Optional safety cap for ``all_pages``.
raw: Return the decoded JSON payload without tabular extraction.
fields: Optional field list or comma-separated field string applied after extraction.
as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
@@ -3906,13 +3964,64 @@ def stock_share(
``as_dataframe=False``, raw JSON when ``raw=True``, or raw page
payloads when multi-page fetching is used with ``raw=True``.
"""
- request_params = {'stock_code': stock_code, 'date': date}
+ request_params = {'stock_code': stock_code, 'date': date, 'date_type': date_type}
request_params.update(kwargs)
- return self._call_endpoint(
- 'stock_share',
+ endpoint = ENDPOINTS['stock_share']
+ return self.get_paginated(
+ 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,
+ **request_params,
+ )
+
+ def stock_factor(
+ self,
+ symbol: Any | None = None,
+ trade_date: Any | None = None,
+ category: Any | None = None,
+ *,
+ raw: bool = False,
+ fields: Sequence[str] | str | None = None,
+ as_dataframe: bool = True,
+ **kwargs: Any,
+ ) -> Any:
+ """股票技术面因子.
+
+ Endpoint: ``api/v2/market/data/stock-factor``.
+ Method: ``GET``.
+ Documented endpoint: ``market_stock_factor``.
+
+ Args:
+ symbol: 沪深北 A 股代码,必须带交易所后缀,如 ``000001.SZ`` (type: string; required: Y).
+ trade_date: 目标日期 YYYYMMDD;不传时以当前日期为目标日期 (type: string; required: N).
+ category: 返回字段分类:``quote``、``valuation``、``capital``、``trend``、``momentum``、``volatility``、``volume`` 或 ``sentiment``;不传时返回全部字段 (type: string; required: N).
+ raw: Return the decoded JSON payload without tabular extraction.
+ fields: Optional field list or comma-separated field string applied after extraction.
+ as_dataframe: Return a pandas ``DataFrame`` by default; set to ``False`` for Python rows.
+ **kwargs: Extra request parameters forwarded unchanged. Useful when the service adds parameters before the SDK is regenerated.
+
+ Returns:
+ A pandas ``DataFrame`` by default, Python rows when
+ ``as_dataframe=False``, raw JSON when ``raw=True``。服务端把因子放在
+ ``data.values``,SDK 直接返回该对象(``as_dataframe=True`` 时为单行
+ ``DataFrame``)。
+ """
+ request_params = {'symbol': symbol, 'trade_date': trade_date, 'category': category}
+ request_params.update(kwargs)
+ path = ENDPOINTS['stock_factor'].path
+ return self.get(
+ path,
raw=raw,
fields=fields,
as_dataframe=as_dataframe,
+ unwrap_bare_data=True,
**request_params,
)
@@ -4738,3 +4847,15 @@ def news_reaction_snapshot(self, symbol: Any | None = None, start_date: Any | No
params.update(kwargs)
path = ENDPOINTS['news_reaction_snapshot'].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=200, raw=raw, fields=fields, as_dataframe=as_dataframe, **params)
+
+ def stock_candlesticks_daily(self, trade_date: 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:
+ """股票日K快照."""
+ params = {'trade_date': trade_date}
+ params.update(kwargs)
+ return self.get_paginated(ENDPOINTS['stock_candlesticks_daily'].path, page=page, page_size=page_size, limit=limit, all_pages=all_pages, max_pages=max_pages, max_page_size=500, raw=raw, fields=fields, as_dataframe=as_dataframe, **params)
+
+ def ths_board_constituents(self, board_code: Any | None = None, board_name: Any | None = None, board_type: Any | None = None, date: 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 = {'board_code': board_code, 'board_name': board_name, 'board_type': board_type, 'date': date}
+ params.update(kwargs)
+ return self.get_paginated(ENDPOINTS['ths_board_constituents'].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)
diff --git a/src/ftshare/base.py b/src/ftshare/base.py
index b3dd0ed..1a7b697 100644
--- a/src/ftshare/base.py
+++ b/src/ftshare/base.py
@@ -20,6 +20,29 @@
from .response import extract_tabular, raise_for_api_error, total_pages as extract_total_pages
+def _truncate_tabular_rows(payload: Any, keep: int) -> Any:
+ """Copy a raw payload with its row list truncated to ``keep`` rows.
+
+ Mirrors the container precedence used by :func:`extract_tabular`; unknown
+ shapes are returned unchanged.
+ """
+ if not isinstance(payload, dict) or keep < 0:
+ return payload
+ data = payload.get("data")
+ if isinstance(data, list):
+ return {**payload, "data": data[:keep]}
+ if isinstance(data, dict):
+ for key in ("records", "items", "index_descriptions"):
+ value = data.get(key)
+ if isinstance(value, list):
+ return {**payload, "data": {**data, key: value[:keep]}}
+ items = payload.get("items")
+ if isinstance(items, list):
+ return {**payload, "items": items[:keep]}
+ return payload
+
+
+
class BaseClient:
"""Base synchronous client with shared HTTP behavior.
@@ -256,7 +279,8 @@ def get_paginated(
max_pages: Optional safety cap used with ``all_pages``.
max_page_size: Maximum allowed page size for this endpoint.
raw: Return raw JSON. When multiple pages are fetched, returns a
- list of raw page payloads.
+ list of raw page payloads; with ``limit`` the final payload's
+ rows are trimmed so the total matches ``limit``.
fields: Optional field list or comma-separated field string.
as_dataframe: Return a pandas ``DataFrame`` by default.
unwrap_bare_data: When ``True``, an object-shaped ``data`` field
@@ -294,16 +318,18 @@ def get_paginated(
request_page_size = min(limit, max_page_size)
else:
request_page_size = max_page_size
+ if limit is not None:
+ request_page_size = min(request_page_size, limit)
+ # Keep the requested page size constant across pages: the server derives
+ # each page's offset from page * page_size, so shrinking the last page
+ # would shift its window and duplicate or drop rows. Overshoot is trimmed
+ # by ``limit`` after the loop instead.
while True:
- current_page_size = request_page_size
- if remaining is not None:
- current_page_size = min(current_page_size, remaining)
-
payload = self.get(
path,
page=effective_page,
- page_size=current_page_size,
+ page_size=request_page_size,
raw=True,
**params,
)
@@ -324,15 +350,28 @@ def get_paginated(
break
if page_count is not None and effective_page >= page_count:
break
- if page_count is None and len(page_rows) < current_page_size:
+ if page_count is None and len(page_rows) < request_page_size:
break
if not page_rows:
break
effective_page += 1
if raw:
+ if limit is not None:
+ kept = 0
+ for index, payload in enumerate(payloads):
+ page_rows = extract_tabular(payload)
+ if not isinstance(page_rows, list):
+ break
+ if kept + len(page_rows) <= limit:
+ kept += len(page_rows)
+ continue
+ payloads[index] = _truncate_tabular_rows(payload, limit - kept)
+ break
return payloads[0] if len(payloads) == 1 and not all_pages else payloads
+ if limit is not None:
+ rows = rows[:limit]
result = self._select_fields(rows, fields)
if as_dataframe:
return self._to_dataframe(result)
diff --git a/src/ftshare/endpoints/futures.py b/src/ftshare/endpoints/futures.py
index 39a810e..02d3437 100644
--- a/src/ftshare/endpoints/futures.py
+++ b/src/ftshare/endpoints/futures.py
@@ -29,18 +29,25 @@
},
'futures_minutes': {
'path': 'api/v2/market/data/futures_minutes',
- 'title': '期货历史分钟行情',
- 'doc_file': '期货历史分钟行情.md',
+ 'title': '期货历史分钟K线',
+ 'doc_file': '期货历史分钟K线.md',
'original_api': 'futures_minutes',
- 'params': ('symbol', 'interval', 'start', 'end', 'limit'),
+ 'params': ('symbol', 'symbols', 'interval', 'start', 'end', 'limit'),
},
- 'futures_contract_kline': {
+ 'futures_kline': {
'path': 'api/v1/market/data/futures/kline',
- 'title': '期货行情',
- 'doc_file': '期货行情.md',
- 'original_api': 'futures_contract_kline',
+ 'title': '期货合约历史K线',
+ 'doc_file': '期货合约历史K线.md',
+ 'original_api': 'futures_kline',
'params': ('symbol', 'interval', 'start', 'end', 'limit'),
},
+ 'futures_kline_batch': {
+ 'path': 'api/v2/market/data/futures/kline/batch',
+ 'title': '批量期货合约历史K线',
+ 'doc_file': '批量期货合约历史K线.md',
+ 'original_api': 'futures_kline_batch',
+ 'params': ('symbols', 'interval', 'start', 'end', 'limit'),
+ },
'futures_minutes_realtime': {
'path': 'api/v4/market/data/futures_minutes/realtime',
'title': '期货实时分钟K线',
@@ -48,6 +55,13 @@
'original_api': 'futures_minutes_realtime',
'params': ('symbols',),
},
+ 'futures_realtime_day_kline': {
+ 'path': 'api/v4/market/data/futures-realtime-day-kline',
+ 'title': '期货实时日K线',
+ 'doc_file': '期货实时日K线.md',
+ 'original_api': 'futures_realtime_day_kline',
+ 'params': ('symbols',),
+ },
'major_contract': {
'path': 'api/v1/market/data/corporate/contract',
'title': '重大合同',
diff --git a/src/ftshare/endpoints/stock.py b/src/ftshare/endpoints/stock.py
index 4995c6f..e185bb9 100644
--- a/src/ftshare/endpoints/stock.py
+++ b/src/ftshare/endpoints/stock.py
@@ -81,10 +81,11 @@
},
'eastmoney_board_constituents': {
'path': 'api/v1/market/data/eastmoney-board-constituents',
- 'title': '东方财富板块成份股',
- 'doc_file': '东方财富板块成份股.md',
+ 'title': '东方财富板块成分股',
+ 'doc_file': '东方财富板块成分股.md',
'original_api': 'eastmoney_board_constituents',
- 'params': ('board_code',),
+ 'params': ('board_code', 'date', 'page', 'page_size'),
+ 'max_page_size': 500,
},
'eastmoney_board_daily_kline': {
'path': 'api/v1/market/data/eastmoney-board-daily-ohlc',
@@ -519,7 +520,15 @@
'title': '股本',
'doc_file': '股本.md',
'original_api': 'get_stock_share',
- 'params': ('stock_code', 'date'),
+ 'params': ('stock_code', 'date', 'date_type', 'page', 'page_size'),
+ 'max_page_size': 500,
+ },
+ 'stock_factor': {
+ 'path': 'api/v2/market/data/stock-factor',
+ 'title': '股票技术面因子',
+ 'doc_file': '股票技术面因子.md',
+ 'original_api': 'market_stock_factor',
+ 'params': ('symbol', 'trade_date', 'category'),
},
'stock_ggcg_em': {
'path': 'api/v1/market/data/holder/stock-ggcg-em',
@@ -536,6 +545,13 @@
'original_api': 'get_stk_code_change',
'params': ('trade_code', 'start_date', 'end_date'),
},
+ 'stk_code_change_timeline': {
+ 'path': 'api/v1/market/data/stk_code_change_timeline',
+ 'title': 'A股代码变更时间线',
+ 'doc_file': 'A股代码变更时间线.md',
+ 'original_api': 'stk_code_change_timeline',
+ 'params': (),
+ },
'stk_status_change': {
'path': 'api/v1/market/data/stk-status-change',
'title': 'A股状态变更',
@@ -962,4 +978,22 @@
'max_page_size': 200,
},
+ 'stock_candlesticks_daily': {
+ 'path': 'api/v1/market/data/stock-candlesticks-daily',
+ 'title': '股票日K快照',
+ 'doc_file': '股票日K快照.md',
+ 'original_api': 'stock_candlesticks_daily',
+ 'params': ('trade_date', 'page', 'page_size'),
+ 'max_page_size': 500,
+ },
+
+ 'ths_board_constituents': {
+ 'path': 'api/v1/market/data/ths-board-constituents',
+ 'title': '同花顺板块成分股',
+ 'doc_file': '同花顺板块成分股.md',
+ 'original_api': 'ths_board_constituents',
+ 'params': ('board_code', 'board_name', 'board_type', 'date', 'page', 'page_size'),
+ 'max_page_size': 1000,
+ },
+
})
diff --git a/src/ftshare/response.py b/src/ftshare/response.py
index e316061..fbe499b 100644
--- a/src/ftshare/response.py
+++ b/src/ftshare/response.py
@@ -31,7 +31,8 @@ def extract_tabular(payload: Any, unwrap_bare_data: bool = False) -> Any:
When ``unwrap_bare_data`` is true and ``data`` is an object without
``records``/``items``/``index_descriptions`` rows (single-item query
- shapes), the ``data`` object itself is returned instead of the full
+ shapes), a nested ``data.values`` object is returned when present,
+ otherwise the ``data`` object itself is returned instead of the full
envelope.
Any unsupported shape is returned unchanged so callers do not lose data.
@@ -48,6 +49,9 @@ def extract_tabular(payload: Any, unwrap_bare_data: bool = False) -> Any:
if isinstance(data.get("index_descriptions"), list):
return data["index_descriptions"]
if unwrap_bare_data:
+ values = data.get("values")
+ if isinstance(values, dict):
+ return values
return data
if isinstance(payload.get("items"), list):
return payload["items"]
diff --git a/tests/endpoint_cases.py b/tests/endpoint_cases.py
index f7666d3..cf14a3a 100644
--- a/tests/endpoint_cases.py
+++ b/tests/endpoint_cases.py
@@ -189,9 +189,11 @@
"fut_settle": {"ts_code": "A2609.DCE", "trade_date": "20260717", "start_date": None, "end_date": None, "exchange": "DCE", "page": 1, "page_size": 5},
"fut_weekly_detail": {"week": "202629", "prd": "CU,AL", "start_week": None, "end_week": None, "exchange": "DCE", "page": 1, "page_size": 5},
"fut_wsr": {"trade_date": "20260717", "start_date": None, "end_date": None, "symbol": "CU", "exchange": "DCE", "page": 1, "page_size": 5},
- "futures_contract_kline": {"symbol": "A2609.DCE", "interval": "daily", "start": 1784048400000, "end": 1784050200000, "limit": 5},
- "futures_minutes": {"symbol": "A2609.DCE", "interval": "1min", "start": 1784048400000, "end": 1784050200000, "limit": 5},
+ "futures_kline": {"symbol": "A2609.DCE", "interval": "daily", "start": 1784048400000, "end": 1784050200000, "limit": 5},
+ "futures_kline_batch": {"symbols": "A2609.DCE", "interval": "daily", "start": 1784048400000, "end": 1784050200000, "limit": 5},
+ "futures_minutes": {"symbol": "A2609.DCE", "symbols": None, "interval": "1min", "start": 1784048400000, "end": 1784050200000, "limit": 5},
"futures_minutes_realtime": {"symbols": "A2609.DCE"},
+ "futures_realtime_day_kline": {"symbols": "A2609.DCE"},
"hk_candlesticks": {"trade_code": "00700.HK", "interval_unit": "day", "until_date": "20260717", "since_date": "20260715", "interval_value": 1, "limit": 5, "adjust_kind": "forward"},
"index_candlesticks": {"symbol": "000300.SH", "interval_unit": "day", "adjust_kind": "forward", "since_ts_millis": 1784048400000, "until_ts_millis": 1784050200000, "limit": 5},
"index_candlesticks_batch": {"symbols": "000300.SH", "interval_unit": "day", "adjust_kind": "forward", "since_ts_millis": 1784048400000, "until_ts_millis": 1784050200000, "limit": 5},
@@ -301,9 +303,12 @@ def _millis(day: str, time: str = "00:00:00") -> int:
"fund_share": {"fund_code": "000001", "stati_perd": None, "start_date": None, "end_date": None, "page": 1, "page_size": 5},
"fut_weekly_detail": {"week": "202629", "prd": None, "start_week": None, "end_week": None, "exchange": None, "page": 1, "page_size": 5},
"fut_wsr": {"trade_date": "20260717", "start_date": None, "end_date": None, "symbol": None, "exchange": None, "page": 1, "page_size": 5},
- "futures_contract_kline": {"symbol": "A2609.DCE", "interval": "daily", "start": _millis("2026-05-01"), "end": _millis("2026-07-17", "15:00:00"), "limit": 5},
- "futures_minutes": {"symbol": "A2609.DCE", "interval": "1min", "start": _millis("2026-07-17", "09:30:00"), "end": _millis("2026-07-17", "10:00:00"), "limit": 5},
+ "futures_kline": {"symbol": "A2609.DCE", "interval": "daily", "start": _millis("2026-05-01"), "end": _millis("2026-07-17", "15:00:00"), "limit": 5},
+ "futures_kline_batch": {"symbols": "A2609.DCE", "interval": "daily", "start": _millis("2026-05-01"), "end": _millis("2026-07-17", "15:00:00"), "limit": 5},
+ "futures_minutes": {"symbol": "A2609.DCE", "symbols": None, "interval": "1min", "start": _millis("2026-07-17", "09:30:00"), "end": _millis("2026-07-17", "10:00:00"), "limit": 5},
"futures_minutes_batch": {"symbols": "A2609.DCE", "interval": "1min", "start": _millis("2026-07-17", "09:30:00"), "end": _millis("2026-07-17", "10:00:00"), "limit": 5},
+ "futures_minutes_realtime": {"symbols": "A2609.DCE"},
+ "futures_realtime_day_kline": {"symbols": "A2609.DCE"},
"futures_nanhua_index_kline": {"code": "AG_NH", "trade_date": None, "start_date": None, "end_date": None, "page": 1, "page_size": 5},
"global_index_daily_kline": {"secid": "100.HSI", "start_date": "20260701", "end_date": "20260717", "limit": 5},
"kline_pattern_annotations": {"date": "20260918", "symbol": None, "pattern": None, "page": 1, "page_size": 5},
@@ -317,6 +322,8 @@ def _millis(day: str, time: str = "00:00:00") -> int:
"stk_surv": {"ts_code": None, "trade_date": None, "start_date": None, "end_date": None, "page": 1, "page_size": 5},
"stock_dividends": {"symbol": "600519.SH", "since_date": None, "until_date": None, "page": 1, "page_size": 5},
"stock_dividends_effective": {"symbol": "600519.SH", "since_date": None, "until_date": None, "page": 1, "page_size": 5},
+ "stock_factor": {"symbol": "000001.SZ", "trade_date": "20261012", "category": "trend"},
+ "stock_share": {"stock_code": "920476.BJ", "date": "20261012", "date_type": "end_date", "page": 1, "page_size": 5},
"stock_ggmx": {"stock_code": None, "change_direction": None, "start_date": None, "end_date": None, "page": 1, "page_size": 5},
"stock_minute_seal": {"trade_date": "20260923", "symbol": "000560.SZ"},
"sw_industry_constituent_history": {"industry_code": "801010.SI"},
@@ -350,6 +357,7 @@ def _millis(day: str, time: str = "00:00:00") -> int:
"stk_manager_pay": {"trade_code": "000001.SZ", "end_date": None},
"namechange": {"trade_code": "000001.SZ", "start_date": None, "end_date": None},
"stk_code_change": {"trade_code": "001872.SZ", "start_date": None, "end_date": None},
+ "stk_code_change_timeline": {},
"stock_st_history": {"symbol": "600735.SH", "st_type": None},
"limit_event_timeline_3s": {"symbol": "000019.SZ", "trade_date": "20260909", "page": 1, "page_size": 5},
"etf_pre_single": {"symbol": "510300.SH", "date": "20260717"},
diff --git a/tests/test_client.py b/tests/test_client.py
index b446036..49966d3 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -88,6 +88,35 @@ def paginated_records(records, page=1, pages=1):
}
+class OffsetPagingSession:
+ """Fake session mimicking the server's offset = (page - 1) * page_size window."""
+
+ def __init__(self, rows, pages):
+ self.rows = list(rows)
+ self.pages = pages
+ self.calls = []
+
+ def get(self, url, params=None, timeout=None, headers=None, stream=False):
+ self.calls.append({"url": url, "params": params, "headers": headers})
+ page = params["page"]
+ page_size = params["page_size"]
+ start = (page - 1) * page_size
+ window = self.rows[start : start + page_size]
+ payload = {
+ "code": 0,
+ "message": "success",
+ "data": {
+ "pageNum": page,
+ "pageSize": page_size,
+ "total": len(self.rows),
+ "pages": self.pages,
+ "records": window,
+ },
+ }
+ return FakeResponse(payload=payload)
+
+
+
def test_new_endpoints_forward_documented_parameters():
session = FakeSession([FakeResponse(payload=paginated_records([{"code": "NVDA"}]))] * 2 + [FakeResponse(payload={"code": 200, "message": "success", "data": {"n": 5}})] * 3)
client = FtshareClient(session=session)
@@ -346,6 +375,8 @@ def test_requested_endpoint_api_versions():
"ths_concept_daily_flow": "api/v1/market/data/ths-concept-daily-flow",
"ths_industry_daily_flow": "api/v1/market/data/ths-industry-daily-flow",
"eastmoney_etf_flow": "api/v1/market/data/eastmoney-etf-flow",
+ "stock_candlesticks_daily": "api/v1/market/data/stock-candlesticks-daily",
+ "ths_board_constituents": "api/v1/market/data/ths-board-constituents",
}
assert {name: ENDPOINTS[name].path for name in expected_paths} == expected_paths
@@ -519,7 +550,7 @@ def test_stock_description_rejects_page_size_above_200():
("stock_unlock", {"stock_code": "000001"}),
("yzxdr_detail", {"year": 2026, "quarter": 2}),
("futures_minutes", {"symbol": "A2605.DCE", "interval": "1min", "limit": 5}),
- ("futures_contract_kline", {"symbol": "A2605.DCE", "interval": "daily", "limit": 5}),
+ ("futures_kline", {"symbol": "A2605.DCE", "interval": "daily", "limit": 5}),
("company_list", {}),
("wallstreetcn_financial_calendar", {"start_date": "2026-05-01", "end_date": "2026-05-07"}),
("stk_limit", {}),
@@ -760,6 +791,60 @@ def test_realtime_kline_methods_serialize_symbols_list_to_json():
assert session.calls[0]["params"] == {"symbols": '["600519.SH"]'}
+def test_futures_kline_batch_and_realtime_day_kline_forward_symbols_list():
+ session = FakeSession([FakeResponse(payload=[])])
+ client = FtshareClient(session=session)
+
+ client.futures_kline_batch(
+ symbols=["A2611.DCE", "M2611.DCE"],
+ interval="daily",
+ start=1790784000000,
+ end=1791647999000,
+ limit=5,
+ as_dataframe=False,
+ )
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v2/market/data/futures/kline/batch"
+ assert session.calls[0]["params"] == {
+ "symbols": ["A2611.DCE", "M2611.DCE"],
+ "interval": "daily",
+ "start": 1790784000000,
+ "end": 1791647999000,
+ "limit": 5,
+ }
+
+ session = FakeSession([FakeResponse(payload=[])])
+ client = FtshareClient(session=session)
+
+ client.futures_realtime_day_kline(symbols=["A2611.DCE", "CU2611.SHF"], as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v4/market/data/futures-realtime-day-kline"
+ assert session.calls[0]["params"] == {"symbols": ["A2611.DCE", "CU2611.SHF"]}
+
+
+def test_futures_minutes_forwards_symbols_for_batch_queries():
+ session = FakeSession([FakeResponse(payload=[])])
+ client = FtshareClient(session=session)
+
+ client.futures_minutes(
+ symbols=["A2611.DCE", "CU2611.SHF"],
+ interval="5min",
+ start=1791475200000,
+ end=1791647999000,
+ limit=5,
+ as_dataframe=False,
+ )
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v2/market/data/futures_minutes"
+ assert session.calls[0]["params"] == {
+ "symbols": ["A2611.DCE", "CU2611.SHF"],
+ "interval": "5min",
+ "start": 1791475200000,
+ "end": 1791647999000,
+ "limit": 5,
+ }
+
+
def test_etf_pcf_infos_unwraps_bare_object_data():
session = FakeSession([FakeResponse(payload={"code": 200, "message": "success", "data": {"symbol": "510300.SH", "trade_date": 20260909, "creation_redemption_unit": 900000}})])
client = FtshareClient(session=session)
@@ -984,7 +1069,37 @@ def test_limit_above_single_page_fetches_multiple_pages():
assert session.calls[0]["params"]["page"] == 1
assert session.calls[0]["params"]["page_size"] == 200
assert session.calls[1]["params"]["page"] == 2
- assert session.calls[1]["params"]["page_size"] == 100
+ assert session.calls[1]["params"]["page_size"] == 200
+
+
+def test_limit_does_not_duplicate_rows_across_pages():
+ rows = [{"id": i} for i in range(1, 1001)]
+ session = OffsetPagingSession(rows, pages=5)
+ client = FtshareClient(session=session)
+
+ df = client.baidu_financial_calendar(start_date="2026-05-01", end_date="2026-05-02", limit=250)
+
+ assert list(df["id"]) == list(range(1, 251))
+ assert session.calls[0]["params"]["page_size"] == 200
+ assert session.calls[1]["params"]["page_size"] == 200
+ assert len(session.calls) == 2
+
+
+def test_limit_trims_raw_page_payloads():
+ rows = [{"id": i} for i in range(1, 1001)]
+ session = OffsetPagingSession(rows, pages=5)
+ client = FtshareClient(session=session)
+
+ payloads = client.baidu_financial_calendar(
+ start_date="2026-05-01",
+ end_date="2026-05-02",
+ limit=250,
+ raw=True,
+ )
+
+ assert len(payloads) == 2
+ assert [row["id"] for row in payloads[0]["data"]["records"]] == list(range(1, 201))
+ assert [row["id"] for row in payloads[1]["data"]["records"]] == list(range(201, 251))
@pytest.mark.parametrize(
@@ -1021,7 +1136,7 @@ def test_stk_limit_uses_500_as_default_request_page_size():
assert isinstance(df, pd.DataFrame)
assert len(df) == 501
assert session.calls[0]["params"]["page_size"] == 500
- assert session.calls[1]["params"]["page_size"] == 1
+ assert session.calls[1]["params"]["page_size"] == 500
def test_stk_limit_allows_500_page_size():
@@ -1376,6 +1491,65 @@ def test_stock_share_chg_forwards_is_last_paging():
assert session.calls[0]["params"]["is_last"] == "true"
+def test_stock_share_forwards_date_type_and_full_market_pagination():
+ session = FakeSession([FakeResponse(payload={"items": [], "total_pages": 0, "total_items": 0})])
+ client = FtshareClient(session=session)
+
+ client.stock_share(date="20261012", date_type="end_date", page=1, page_size=5, as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v2/market/data/share/get-stock-share"
+ assert session.calls[0]["params"] == {"date": "20261012", "date_type": "end_date", "page": 1, "page_size": 5}
+
+
+def test_stock_share_rejects_page_size_above_500():
+ client = FtshareClient(session=FakeSession([]))
+
+ with pytest.raises(ValueError, match="page_size must be between 1 and 500"):
+ client.stock_share(date="20261012", page_size=501)
+
+
+def test_stock_factor_unwraps_values_object():
+ session = FakeSession([FakeResponse(payload={"code": 200, "message": "success", "data": {"values": {"ts_code": "000001.SZ", "trade_date": "20261012", "close": 11.73}}})])
+ client = FtshareClient(session=session)
+
+ result = client.stock_factor(symbol="000001.SZ", trade_date="20261012", category="quote", as_dataframe=False)
+
+ assert result == {"ts_code": "000001.SZ", "trade_date": "20261012", "close": 11.73}
+
+
+def test_stock_factor_values_object_dataframe_is_single_row():
+ session = FakeSession([FakeResponse(payload={"code": 200, "message": "success", "data": {"values": {"ts_code": "000001.SZ", "close": 11.73}}})])
+ client = FtshareClient(session=session)
+
+ frame = client.stock_factor(symbol="000001.SZ")
+
+ assert isinstance(frame, pd.DataFrame)
+ assert len(frame) == 1
+ assert frame.loc[0, "ts_code"] == "000001.SZ"
+ assert frame.loc[0, "close"] == 11.73
+
+
+def test_stock_factor_forwards_symbol_date_and_category():
+ session = FakeSession([FakeResponse(payload={"code": 200, "message": "success", "data": {"values": {}}})])
+ client = FtshareClient(session=session)
+
+ client.stock_factor(symbol="000001.SZ", trade_date="20261012", category="trend", as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v2/market/data/stock-factor"
+ assert session.calls[0]["params"] == {"symbol": "000001.SZ", "trade_date": "20261012", "category": "trend"}
+
+
+def test_stk_code_change_timeline_hits_documented_path_and_extracts_data():
+ session = FakeSession([FakeResponse(payload={"code": 200, "message": "success", "data": [{"stock_name": "上海医药", "new_code": "601607", "market": "SH"}]})])
+ client = FtshareClient(session=session)
+
+ rows = client.stk_code_change_timeline(as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/stk_code_change_timeline"
+ assert session.calls[0]["params"] == {}
+ assert rows == [{"stock_name": "上海医药", "new_code": "601607", "market": "SH"}]
+
+
def test_ths_industry_constituents_forwards_filters_and_pagination():
session = FakeSession([FakeResponse(payload={"items": [], "total_pages": 0, "total_items": 0})])
client = FtshareClient(session=session)
@@ -1628,3 +1802,64 @@ def test_a_share_reference_endpoints_map_to_expected_paths(
assert session.calls[0]["url"] == "https://market.ft.tech/gateway/" + expected_path
assert session.calls[0]["params"] == expected_params
+
+
+def test_stock_candlesticks_daily_forwards_documented_parameters():
+ session = FakeSession([FakeResponse(payload=paginated_records([]))])
+ client = FtshareClient(session=session)
+
+ client.stock_candlesticks_daily(trade_date="20261008", page=1, page_size=10, as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/stock-candlesticks-daily"
+ assert session.calls[0]["params"] == {"trade_date": "20261008", "page": 1, "page_size": 10}
+
+
+def test_stock_candlesticks_daily_allows_page_size_up_to_500():
+ session = FakeSession([FakeResponse(payload=paginated_records([]))])
+ client = FtshareClient(session=session)
+
+ client.stock_candlesticks_daily(trade_date="20261008", page=1, page_size=500, as_dataframe=False)
+
+ assert session.calls[0]["params"] == {"trade_date": "20261008", "page": 1, "page_size": 500}
+
+ with pytest.raises(ValueError, match="page_size must be between 1 and 500"):
+ client.stock_candlesticks_daily(trade_date="20261008", page_size=501)
+
+
+def test_eastmoney_board_constituents_forwards_date_and_pagination():
+ session = FakeSession([FakeResponse(payload=paginated_records([]))])
+ client = FtshareClient(session=session)
+
+ client.eastmoney_board_constituents(board_code="BK0475", date="20260930", page=1, page_size=20, as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/eastmoney-board-constituents"
+ assert session.calls[0]["params"] == {"board_code": "BK0475", "date": "20260930", "page": 1, "page_size": 20}
+
+
+def test_ths_board_constituents_forwards_documented_parameters():
+ session = FakeSession([FakeResponse(payload=paginated_records([]))])
+ client = FtshareClient(session=session)
+
+ client.ths_board_constituents(board_code="300082", board_name="军工", board_type="concept", date="20260915", page=1, page_size=100, as_dataframe=False)
+
+ assert session.calls[0]["url"] == "https://market.ft.tech/gateway/api/v1/market/data/ths-board-constituents"
+ assert session.calls[0]["params"] == {
+ "board_code": "300082",
+ "board_name": "军工",
+ "board_type": "concept",
+ "date": "20260915",
+ "page": 1,
+ "page_size": 100,
+ }
+
+
+def test_ths_board_constituents_allows_page_size_up_to_1000():
+ session = FakeSession([FakeResponse(payload=paginated_records([]))])
+ client = FtshareClient(session=session)
+
+ client.ths_board_constituents(board_code="300082", page=1, page_size=1000, as_dataframe=False)
+
+ assert session.calls[0]["params"] == {"board_code": "300082", "page": 1, "page_size": 1000}
+
+ with pytest.raises(ValueError, match="page_size must be between 1 and 1000"):
+ client.ths_board_constituents(board_code="300082", page_size=1001)
diff --git a/tests/test_integration_market.py b/tests/test_integration_market.py
index 986fca0..2945fbd 100644
--- a/tests/test_integration_market.py
+++ b/tests/test_integration_market.py
@@ -16,7 +16,8 @@
# Endpoints that return HTTP 200 with an empty payload on the test server for
# every parameter combination, or fail before reaching the data layer. Verified
-# against http://xz09:10103 on 2026-09-22; re-check these when the server moves.
+# against the integration test server on 2026-09-22; re-check these when the
+# server moves.
KNOWN_SERVER_ISSUES: dict[str, str] = {
"ashare_news_sentiment_factors": "HTTP 502 下游服务请求失败 for every parameter combination",
"futures_minutes_batch": "HTTP 404 请求的资源不存在 — route absent on the test server",