From 36b586b0159919eab2cb1eb663b74033a5eebb1e Mon Sep 17 00:00:00 2001 From: awayings Date: Tue, 8 Sep 2026 23:21:35 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20README=20Python=20API=20=E7=AB=A0?= =?UTF-8?q?=E6=8B=86=E8=87=B3=20python-api.md=EF=BC=88=E6=95=99=E7=A8=8B?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E6=9E=9A=E4=B8=BE/API=20=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=BD=92=E5=85=A5=E5=8F=82=E8=80=83=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - python-api.md:连接/MAC/统一客户端/标准协议/离线/缠论/公告/财报/实时轮询教程 - MAC 协议枚举(Period/Adjust/Category/BoardType/SortType/ExMarket)并入 field_mapping.md - MacClient/MacExClient 完整方法表并入 api_reference.md(TdxClient 表与正文重复,删除) Co-Authored-By: Claude --- docs/python-api.md | 421 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 docs/python-api.md diff --git a/docs/python-api.md b/docs/python-api.md new file mode 100644 index 0000000..205ce45 --- /dev/null +++ b/docs/python-api.md @@ -0,0 +1,421 @@ +# Python API 使用指南 + +## 连接管理 + +所有客户端支持 `from_best_host()` 自动选最低延迟服务器: + +```python +from easy_tdx import MacClient + +with MacClient.from_best_host() as c: + df = c.get_stock_kline(...) +``` + +| 客户端 | 端口 | 覆盖范围 | +|--------|------|----------| +| `MacClient` / `AsyncMacClient` | 7709 | A 股行情(MAC 协议,推荐) | +| `MacExClient` / `AsyncMacExClient` | 7727 | 港股/美股/期货(MAC 协议) | +| `UnifiedTdxClient` / `AsyncUnifiedTdxClient` | 自动 | A 股 + 扩展市场统一入口 | +| `TdxClient` / `AsyncTdxClient` | 7709 | A 股行情(标准协议) | + +## MAC 协议(推荐) + +### 报价 + +```python +from easy_tdx import MacClient, Market, Category, SortType, SortOrder + +with MacClient.from_best_host() as c: + # 批量报价(最多 80 只/次) + df = c.get_stock_quotes([(Market.SH, "600519"), (Market.SZ, "000858")]) + + # 市场分类排序报价 + df = c.get_stock_quotes_list( + Category.A, count=20, + sort_type=SortType.CHANGE_PCT, + sort_order=SortOrder.DESC, + ) +``` + +返回列:`market, code, name` + 动态字段(`pre_close, open, high, low, close, vol, amount, turnover, vol_ratio` 等)。 + +### K 线(支持复权) + +```python +from easy_tdx import MacClient, Market, Period, Adjust + +with MacClient.from_best_host() as c: + # 日K前复权 + df = c.get_stock_kline(Market.SH, "600519", Period.DAILY, count=10, adjust=Adjust.QFQ) + # 5分钟线 + df = c.get_stock_kline(Market.SZ, "000001", Period.MIN_5, count=100) +``` + +返回列:`datetime, open, close, high, low, vol, amount`。 + +### 技术指标 + +自动获取 200+ 条历史数据预热 EMA,返回最后 `count` 条带指标的结果: + +```python +from easy_tdx import MacClient, Market, Period, Adjust +from easy_tdx.indicator import compute_indicators, list_indicators + +with MacClient.from_best_host() as c: + # 便捷方法:获取 K 线 + 计算指标一步完成(默认前复权) + df = c.get_stock_kline_with_indicators( + Market.SH, "600519", + indicators=["MACD", "KDJ", "RSI", "BOLL"], + count=30, + ) + # df 包含: datetime, open, close, high, low, vol, amount + # + MACD_DIF, MACD_DEA, MACD_HIST, KDJ_K, KDJ_D, KDJ_J, RSI, + # BOLL_UPPER, BOLL_MID, BOLL_LOWER + + # 自定义指标参数 + df = c.get_stock_kline_with_indicators( + Market.SH, "600519", + indicators=["MACD"], + params={"MACD": {"SHORT": 10, "LONG": 22}}, + ) + + # 独立使用:对已有 DataFrame 计算指标 + raw = c.get_stock_kline(Market.SH, "600519", Period.DAILY, count=200, adjust=Adjust.QFQ) + result = compute_indicators(raw, ["ATR", "CCI", "WR"], tail=30) + + # 查看所有可用指标 + for info in list_indicators(): + print(info["name"], info["description"], info["outputs"]) +``` + +支持 34 个技术指标: + +| 指标 | 输入 | 输出列 | +|------|------|--------| +| MACD | close | MACD_DIF, MACD_DEA, MACD_HIST | +| KDJ | close, high, low | KDJ_K, KDJ_D, KDJ_J | +| RSI | close | RSI | +| BOLL | close | BOLL_UPPER, BOLL_MID, BOLL_LOWER | +| DMI | close, high, low | DMI_PDI, DMI_MDI, DMI_ADX, DMI_ADXR | +| ATR | close, high, low | ATR | +| WR | close, high, low | WR1, WR2 | +| CCI | close, high, low | CCI | +| BIAS | close | BIAS1, BIAS2, BIAS3 | +| OBV | close, vol | OBV | +| VR | close, vol | VR | +| EMV | high, low, vol | EMV, EMV_MA | +| MFI | close, high, low, vol | MFI | +| BRAR | open, close, high, low | AR, BR | +| ASI | open, close, high, low | ASI, ASI_MA | +| TRIX | close | TRIX, TRIX_MA | +| DPO | close | DPO, DPO_MA | +| MTM | close | MTM, MTM_MA | +| ROC | close | ROC, ROC_MA | +| EXPMA | close | EXPMA_12, EXPMA_50 | +| BBI | close | BBI | +| PSY | close | PSY, PSY_MA | +| DFMA | close | DFMA_DIF, DFMA_DMA | +| CR | close, high, low | CR | +| KTN | close, high, low | KTN_UPPER, KTN_MID, KTN_LOWER | +| XSII | close, high, low | XSII_TD1, XSII_TD2, XSII_TD3, XSII_TD4 | +| MASS | high, low | MASS, MASS_MA | +| TAQ | high, low | TAQ_UP, TAQ_MID, TAQ_DOWN | +| ZHUOYAO | close | ZY_LONG, ZY_MID, ZY_SHORT, ZY_TREND | +| BIAS_SIGNAL | close | BS_X, BS_SMA, BS_LMA | +| SAR | high, low | SAR(抛物线转向/动态止损位) | +| VWAP | close, high, low, vol | VWAP(N日滚动成交量加权均价) | +| AROON | high, low | AROON_UP, AROON_DOWN, AROON_OSC | +| FK | close | FK(EMA(2) 突破斜率外推 EMA(42)) | + +### 分时 + +```python +with MacClient.from_best_host() as c: + df = c.get_tick_chart(Market.SH, "600519") # 单日分时 + df = c.get_tick_charts(Market.SH, "600519", days=3) # 多日分时(最多5天) + df = c.get_chart_sampling(Market.SH, "600519") # 240点缩略采样 +``` + +### 逐笔成交 + +```python +with MacClient.from_best_host() as c: + df = c.get_transactions(Market.SH, "600519", count=100) + df = c.get_transactions(Market.SH, "600519", count=100, date=20250115) +``` + +### 板块 + +```python +from easy_tdx import BoardSortColumn, BoardType + +with MacClient.from_best_host() as c: + df = c.get_board_list(BoardType.GN) # 概念板块 + df = c.get_board_list(sort_column=BoardSortColumn.SPEED) # 按涨速排序取涨速% + df = c.get_board_members("881001", sort_type=SortType.CHANGE_PCT) + df = c.get_belong_board(Market.SZ, "000001") # 个股所属板块 + + # 板块汇总:成交额、主力净流入、涨跌家数 + summary = c.get_board_summary("881001") + # summary = { + # "member_count": 82, + # "amount": 5823456000.0, # 板块总成交额(元) + # "vol": 412356789, # 板块总成交量(股) + # "main_net_amount": -123456.0, # 当日主力净流入 + # "main_net_3d": -567890.0, # 近3日主力净流入 + # "main_net_5d": -234567.0, # 近5日主力净流入 + # "up_count": 45, + # "down_count": 37, + # "members": DataFrame(...), # 成分股明细 + # } + + # 板块涨跌幅排行榜 + df = c.get_board_ranking(BoardType.HY, top_n=10, sort_by="change_pct") + df = c.get_board_ranking(BoardType.GN, top_n=20, sort_by="main_net_amount") + # 返回列:code, name, change_pct, amount, vol, main_net_amount, up_count, down_count, member_count + + # 板块 N 日涨跌幅排行(支持指定截止日期,默认全部) + df = c.get_board_change_ranking(BoardType.HY, days=20) + df = c.get_board_change_ranking(BoardType.GN, target_date=20250530, days=10, top_n=15) + # 返回列:code, name, close_end, close_start, change_pct +``` + +### 资金流向 + +```python +with MacClient.from_best_host() as c: + df = c.get_capital_flow(Market.SH, "600519") +``` + +返回列:`date, main_in, main_out, main_net, small_in/out/net, mid_in/out/net, large_in/out/net`。 + +### 监控 + +```python +with MacClient.from_best_host() as c: + df = c.get_auction(Market.SH, "600519") # 集合竞价 + df = c.get_unusual(Market.SH) # 市场异动 + df = c.get_symbol_info(Market.SZ, "000001") # 个股特征快照 + df = c.get_server_info() # 服务器交易时段 +``` + +`get_unusual` 返回列含 `unusual_type`(类型码)与 `desc`(中文描述),共 19 种类型 +(主力买卖/加速拉升/急速拉升/盘中强弱/竞价异动/涨跌停/大单盘口等)。类型码→名称 +可用顶层常量映射: + +```python +from easy_tdx import UNUSUAL_TYPE_NAMES + +df["type_name"] = df["unusual_type"].map(UNUSUAL_TYPE_NAMES) +``` + +## 扩展市场 + +```python +from datetime import date + +from easy_tdx import MacExClient, ExMarket, Period + +with MacExClient.from_best_host() as c: + count = c.goods_count(ExMarket.HK_MAIN_BOARD) + df = c.goods_list(ExMarket.HK_MAIN_BOARD, start=0, count=50) + df = c.goods_kline(ExMarket.US_STOCK, "AAPL", Period.DAILY, count=10) + df = c.goods_quotes([(ExMarket.HK_MAIN_BOARD, "00700")]) + df = c.goods_tick_chart(ExMarket.HK_MAIN_BOARD, "00700") + df = c.goods_transaction(ExMarket.HK_MAIN_BOARD, "00700", count=100) + df = c.goods_transaction_all(ExMarket.HK_MAIN_BOARD, "00700", date(2026, 7, 3)) # 港股当日全部逐笔 +``` + +> **逐笔成交排序**:通达信协议为**倒序**——`start=0` 指向最新一笔(收盘方向),`count=2000` 默认只取最近 2000 笔。港股单日成交常达数万笔(如 02715 约 1.3 万笔/日),若需当日全部成交,用 `goods_transaction_all`(仅港股股票类市场,自动按 1800/页翻页取全天,安全上限 9 万条;返回协议原生倒序,需正序展示自行 `df.iloc[::-1]`)。 + +## 统一客户端 + +```python +from easy_tdx import UnifiedTdxClient, ExMarket, Market, Period + +with UnifiedTdxClient() as client: + # A 股 -- 自动路由到 MacClient + df = client.get_stock_kline(Market.SH, "600519", Period.DAILY, count=5) + df = client.get_stock_quotes([(Market.SH, "600519")]) + df = client.get_board_list() + + # 扩展市场 -- 自动路由到 MacExClient + df = client.goods_kline(ExMarket.HK_MAIN_BOARD, "00700", Period.DAILY, count=5) +``` + +## 标准协议 + +```python +from easy_tdx import TdxClient, Market, KlineCategory + +with TdxClient.from_best_host() as c: + count = c.get_security_count(Market.SH) + stocks = c.get_security_list(Market.SH, start=0) + quotes = c.get_security_quotes([(Market.SH, "600000"), (Market.SZ, "000001")]) + bars = c.get_security_bars(Market.SZ, "002176", KlineCategory.DAY, 0, 100) + minute = c.get_minute_time_data(Market.SH, "600000") + trades = c.get_transaction_data(Market.SH, "600000", 0, 20) + flow = c.get_fund_flow(Market.SH, "600519") + blocks = c.get_block_info("block_gn.dat") + xdxr = c.get_xdxr_info(Market.SH, "600519") + stat = c.get_market_stat() +``` + +> **资金流口径注意**(Issue #55):`get_fund_flow` / `get_history_fund_flow` 按 0x0fb5 逐笔接口的"单笔成交额"分档,而该接口返回的记录是交易所真实逐笔**聚合**后的(实测 000001.SZ 单日约 17:1),分档看的也不是挂单额。结果:高价股小单档可不足成交额 1%、主力档常占 95%+,`main_net_inflow` 实质更接近"当日主动买卖总失衡"。东财/同花顺的"主力净流入"基于 L2 逐笔委托、按挂单额分档——两套口径**不可比**(实证同规则选股信号重合度仅约 14%),勿混用于同一张表或同一个因子。 + +`AsyncTdxClient` 提供对应的 `async def` 方法,接口一一对应。 + +## SecurityQuote 字段说明 + +`get_security_quotes()` 返回的 DataFrame 包含以下特殊字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `trading_status` | int | 交易状态标志。`0x8020`(32800) = 停牌,其余值表示正常交易或集合竞价 | +| `open_amount` | float | 集合竞价成交金额(元)。仅个股有效,指数该字段无意义 | +| `server_time` | str | 服务器时间,格式 `HH:MM:SS.mmm` | +| `unknown_2` | int | 指数: 集合竞价成交金额/100;个股: 舍入残差≈0 | +| `unknown_3` | int | 个股: 集合竞价成交金额/100;指数: 负值/无意义 | +| `unknown_5-8` | int | 保留字段,恒为 0 | + +检测停牌: + +```python +df = c.get_security_quotes([(Market.SH, "600000")]) +is_suspended = df.iloc[0]["trading_status"] == 0x8020 +``` + + +## 公告检索(巨潮资讯网) + +独立数据源(巨潮资讯网 cninfo),无需连接 TDX 行情服务器即可检索公司公告。 +标准库 urllib 实现,零额外依赖。 + +```python +from easy_tdx.cninfo import CninfoClient + +client = CninfoClient() + +# 检索公告(默认 30 条,最新在前) +df = client.get_announcements("688017") +# → DataFrame[title, type, date, url, code, org_id, announcement_id, announcement_time, pdf_url] + +# 翻页 + 自定义数量 +df = client.get_announcements("601088", count=10, page=2) + +# 返回示例(url 含 4 参数可直点打开,pdf_url 为 PDF 直链): +# title type date url pdf_url +# 0 关于召开2025年年度股东大会... 股东大会 2025-06-14 .../detail?stockCode=688017&announcementId=... http://static.cninfo.com.cn/.../xxx.PDF +# 1 2024年年度报告 PDF 2025-03-28 .../detail?stockCode=688017&announcementId=... http://static.cninfo.com.cn/.../yyy.PDF +``` + +> - ``type`` 优先取 cninfo 的 ``announcementTypeName``;该字段对很多公告为 null +> (数据源限制),此时回退到 ``adjunctType``(如 "PDF"),再为空给空字符串。 +> - ``url`` 必须含 4 参数(``stockCode``/``announcementId``/``orgId``/``announcementTime``) +> 才能打开,少参数会 404。 +> - orgId 解析沿用 #19 修复:动态拉取官方映射表,查不到回退硬编码规则, +> 保证 601xxx 等非标 orgId 段也能正常查询。 + +### 下载公告 PDF + +```python +# 下载最新一条公告的 PDF 到当前目录 +df = client.get_announcements("601088", count=5) +path = client.download_pdf(df.iloc[0]) # 接受 Announcement 或 DataFrame 的一行 +print(path) # /abs/path/20260605_1225351400.PDF + +# 批量下载 +for _, row in df.iterrows(): + try: + path = client.download_pdf(row, dest_dir="./pdfs") + except Exception as e: + print(f"跳过(无附件或失败): {e}") +``` + +## 财报三表(新浪财经) + +独立数据源(新浪财经),无需连接 TDX 行情服务器即可获取利润表/资产负债表/现金流量表。 +标准库 urllib 实现,零额外依赖。 + +```python +from easy_tdx.sina import SinaClient + +client = SinaClient() + +# 利润表(默认 8 期,最新在前) +df = client.get_financial_report("600519", report_type="lrb") +# → DataFrame,每行一期,列 = [报告期, 营业总收入, 营业总收入_同比, ...] + +# 资产负债表 / 现金流量表(report_type 也接受中文别名:利润表/资产负债表/现金流量表) +df = client.get_financial_report("600519", report_type="fzb", num=4) +df = client.get_financial_report("600519", report_type="llb", num=4) + +# 返回示例(item_value 已转 float,可直接数值计算): +# 报告期 营业总收入 营业总收入_同比 营业收入 营业收入_同比 +# 0 2026-03-31 54702912385.23 0.06336 53909252220.51 0.06538 +# 1 2025-12-31 174000000000.00 0.10000 NaN NaN +``` + +> - ``item_value`` 是字符串(新浪原始格式),本实现转 float;空/非数值转 None +> - 有同比的科目附加 ``{科目}_同比`` 列(float 比例,如 0.06336 = +6.3%) +> - 大类标题行(如 ``流动资产``,原 ``item_value=""``)保留为 None,反映报表结构 + +## 实时行情轮询(RealtimeDataFeed) + +> ⚠️ 通达信协议**没有服务端推送**,只有请求/响应。本模块的「实时」是 **轮询五档快照近似** +> (默认约 3 秒延迟),适合盘中信号提醒、轻量监控;**不适合高频 / 逐笔撮合**。 + +`EventBus` 自身是纯发布/订阅管道,不会产生数据。要让 `RealtimeStrategy` 跑起来, +需要配合 `RealtimeDataFeed`:它自动完成 `get_stock_quotes → MarketEvent → bus.publish`。 + +```python +import asyncio +from easy_tdx.mac.client import AsyncMacClient +from easy_tdx.realtime import ( + EventBus, + RealtimeStrategy, + MarketEvent, + RealtimeDataFeed, +) + + +class MyStrategy(RealtimeStrategy): + def on_tick(self, event: MarketEvent) -> None: + print(f"{event.market}{event.code} price={event.price} vol={event.volume}") + + +async def main(): + bus = EventBus() + strategy = MyStrategy() + bus.subscribe("SZ000001", strategy.on_tick) # 注意:key 必须带市场前缀 + + feed = RealtimeDataFeed( + bus=bus, + symbols=[(0, "000001"), (1, "600519")], # [(Market.SZ, code), ...],单批 ≤80 只 + interval=3.0, # 轮询间隔(秒),下限 0.1 + dedup=True, # (price, volume) 未变的标的跳过发布 + # sessions=(), # 传空 tuple 表示全天轮询;默认仅 9:15-11:30 / 13:00-15:00 + ) + async with AsyncMacClient.from_best_host() as client: + await feed.run_async(client) # Ctrl+C 或 feed.stop() 退出 + + +asyncio.run(main()) +``` + +同步客户端(`MacClient`)用 `run_sync`,feed 会把阻塞调用丢到线程池,不卡事件循环: + +```python +from easy_tdx.mac.client import MacClient + +with MacClient.from_best_host() as client: + feed.run_sync(client) +``` + +> **关键坑(issue #34)**: +> - 订阅 key 必须与 `publish` 内部拼的 `f"{market}{code}"` 一致,即 `"SZ000001"` +> 而不是 `"000001"`;否则事件分发匹配不到。 +> - 传给 `subscribe` 的必须是**实例的绑定方法** `strategy.on_tick`, +> 不是未绑定的类方法 `MyStrategy.on_tick`。 +> - 整个流程跑在 `asyncio.run()` 里,否则协程不会被调度。 +