QMT xtquant原生接口实战:获取股票列表、申万行业与概念板块数据
玩量化的人应该都遇到过这种尴尬QMT客户端里明明放着股票列表、申万行业、概念板块这些现成的数据但真要把它们导出到本地做分析或者整合进自己的策略框架时反而要用akshare、tushare这些第三方数据源去重新拉一遍。费时费力不说数据一致性还经常对不上。其实QMT自带一套原生Python接口就是xtquant包专门用来干这个事。今天我就把用这套接口获取股票列表、申万行业和概念板块数据的完整流程写出来代码可以直接复制跑省得大家再去翻那本写得不太全的官方文档。下面这段内容不是什么理论课就是一个实际操作记录的整理。我会把接口怎么连、板块命名规则长什么样、代码要怎么写、常见报错怎么排查全部按我自己的实操顺序讲一遍。适合那些已经在用QMT下单、但对xtdata数据接口还不熟的量化爱好者也适合准备做行业轮动、概念热度监控、个股题材归因的选手。先别急着复制代码把原理看明白遇到问题才知道怎么改。1. 整体设计与方案选型1.1 为什么我最终选了QMT原生接口在此之前我做过不止一次关于选股数据的工具链对比。最早期用的是akshare免费的花样也多但问题在于它是爬虫方案上游页面结构一旦调整接口就挂后来用tushare数据质量好但积分和调频限制比较多做盘中实时策略的时候总觉得隔了一层。用QMT的xtdata接口则完全是另一种体验它是直接连QMT客户端的行情服务端客户端本地有什么数据接口就能取什么数据不需要爬虫也不需要额外的数据库速度、稳定性、字段完整度都靠谱。这里要澄清一个概念这里说的“QMT原生接口”是指迅投提供的xtquant包你直接以Python第三方库的方式调用而不是用模拟键鼠点击界面、或者通过图形界面导出的那种操作。这两者天差地别原生接口能拿到结构化的行情和板块数据能嵌入程序化策略而且不需要长时间盯着客户端界面操作而UI自动化本质上是在模拟人工效率低还容易受界面改动影响。对真正做量化的人来说原生接口是必然选择。另外还涉及一个很现实的问题数据源的一致性。如果你用akshare拉行业板块用QMT下单那么你的“板块成分股”很可能和券商终端显示的不一致因为不同的平台对板块成分股的更新规则不同。而直接用QMT原生接口取数据拿到的就是你客户端里看到的那份整个策略从数据到执行都来自同一套体系对账和复现都方便很多。1.2 xtquant整体架构先弄懂数据的组织方式xtquant包的核心模块是xtdata它负责连接本地行情服务端、下载和读取行情数据、获取板块分类数据。简单理解QMT客户端会启动一个本地行情服务xtdata通过本地端口和它通信所以使用前必须保证QMT客户端已启动并登录。这也决定了它的一个特点数据不会凭空出现客户端没开接口就没法用。从数据组织来看股票列表、申万行业、概念板块三者是层层关联的。股票列表最简单就是沪深京三个市场里所有A股代码行业和概念则是“板块”这个概念下的两种分类体系。QMT把所有的板块统一放在一个板块列表里通过板块名称的前缀来区分类型比如申万行业以“SW1-”、“SW2-”开头概念板块以“概念-”开头。拿到板块列表再取每个板块里的成分股就能反推出某只股票属于哪些行业和概念。这个设计初看有点绕但习惯之后会发现好处很明显你不需要逐只遍历股票去查属性而是先把板块列表拉一遍再批量获取成分股数据组织是“从上到下”的效率很高。理解了这一点后面代码的整个逻辑链条就通了。2. 核心细节解析与实操要点2.1 股票列表获取get_stock_list_in_exchange的参数玄机获取股票列表用的是xtdata.get_stock_list_in_exchange(exchange)这个接口支持传入SH、SZ、BJ以及全部。我实测下来传入全部时会返回沪深京三个市场的全部A股省去了手动拼接的麻烦。需要注意一个大坑接口返回的股票代码是带交易所后缀的比如000001.SZ、600000.SH、830799.BJ。很多人拿到这个列表后直接用tp.StkCode去和别的数据源匹配结果对不上因为不少第三方数据源用的是纯6位数字代码。解决办法是在代码里做一次映射转换或者干脆统一使用带后缀的格式后续所有数据都按这个格式处理就不容易乱。还有一个容易被忽略的点这个接口默认返回的是当前市场上全部股票包括ST、次新股、停牌股等。如果你做的是可交易股票筛选还需要额外过滤ST、退市风险股这些不过那是后话这里先把列表拿到位。我是建议先拿到全量列表再根据策略需要自行过滤不要在源头就丢数据。2.2 申万行业与概念板块的命名规则先看清list再动手很多人第一次执行xtdata.get_sector_list()的时候会被返回的板块列表吓一跳——里面什么都有行业、概念、指数、可转债、基金全都堆在一起密密麻麻上百个。这时候就要靠命名规则来过滤。在我测试的版本里申万行业的板块名称格式是这样的申万一级行业SW1-银行、SW1-电力设备申万二级行业SW2-白酒、SW2-电池申万三级行业SW3-...概念板块的格式则是概念-人工智能、概念-储能这样的前缀。知道了这个规律用一次简单的startswith过滤就能把不同分类拆开。不过我要提醒一句不同券商的QMT版本板块命名规则可能略有差异。有的人拿到的可能不是SW1-而是申万行业-...或者别的什么前缀。所以我强烈建议第一次用的时候先别写死过滤条件先打印一下板块列表看看自己的版本里到底什么格式再写过滤逻辑。我这里给出的代码基于常见版本但自检这一步不能省。2.3 板块数据不是实时自动同步download_sector_data的作用QMT里的板块分类数据本质上是一个会自动更新的本地数据集。第一次使用板块接口时如果本地没有缓存get_sector_list()返回的就是空值或者不完整很多人第一次跑就栽在这里。解决办法很简单调用xtdata.download_sector_data()主动下载一次板块分类数据。这个操作可以理解成把客户端的板块配置文件同步到本地接口可读的位置。我一般在每次程序启动时都调用一次反正带宽占用不大但能保证板块是最新的。另外要区分两个接口download_sector_data()同步的是板块分类信息download_history_data()下载的是K线历史行情。如果你后面想拿板块成分股的行情数据来做分析还需要额外调用后者。这两个别搞混了否则你会发现板块列表有了但股票的行情数据还是空的。3. 实操过程与核心环节实现3.1 环境准备添加xtquant路径是第一步xtquant包不像普通第三方库那样直接用pip安装到site-packages里它跟随QMT客户端一起发布一般位于QMT安装目录下的bin/xtquant里。所以标准做法是先用sys.path把它加进去。具体路径根据你的QMT安装位置来定常见的是C:\迅投QMT\bin\xtquant。如果你不确定自己的安装路径可以右键点击QMT桌面图标在文件位置里找到主程序目录然后往下找bin文件夹。这一步做不对后面import xtquant就全会失败。同时建议准备好pandas方便后续整理数据如果要导出Excel还需要装openpyxl引擎。至少pandas是标配用纯Python列表去拼接数据会很痛苦。3.2 完整代码一版能直接跑通的获取脚本下面这版代码是我整理过的把核心流程都串起来了连接客户端、下载板块数据、拉股票列表、过滤申万行业和概念板块、输出为CSV文件。注释写得很详细直接复制就能用。# -*- coding: utf-8 -*- QMT 原生接口实操获取股票列表、申万行业、概念数据 使用前提启动并登录 QMT 客户端极简模式即可 import sys import pandas as pd # 1. 添加 xtquant 模块路径根据本机 QMT 安装目录调整 qmt_xtquant_path rC:\迅投QMT\bin\xtquant if qmt_xtquant_path not in sys.path: sys.path.append(qmt_xtquant_path) from xtquant import xtdata # 2. 连接本地行情服务端 connect_result xtdata.connect() # 正常情况下返回 0 或者 None如果报错说明客户端没起来 print(连接 QMT 结果:, connect_result) # 3. 下载板块分类数据保证第一次能拿到完整板块列表 try: xtdata.download_sector_data() print(板块分类数据下载完成) except Exception as e: print(下载板块数据异常:, e) # 4. 获取全部A股列表 all_stocks xtdata.get_stock_list_in_exchange(全部) print(A股数量:, len(all_stocks)) # 5. 获取全部板块列表并根据命名规则过滤 sector_list xtdata.get_sector_list() # 申万层级行业注意不同QMT版本前缀可能不同先打印确认 sw1 [s for s in sector_list if s.startswith(SW1-)] sw2 [s for s in sector_list if s.startswith(SW2-)] sw3 [s for s in sector_list if s.startswith(SW3-)] # 概念板块 concept_sectors [s for s in sector_list if s.startswith(概念-)] print(f申万一级行业数: {len(sw1)}) print(f申万二级行业数: {len(sw2)}) print(f申万三级行业数: {len(sw3)}) print(f概念板块数: {len(concept_sectors)}) # 6. 获取某个板块的成分股 def get_stocks_of_sector(sector_name): try: return xtdata.get_stock_list_in_sector(sector_name) except Exception as e: print(f获取板块 {sector_name} 成分股失败: {e}) return [] # 7. 整理申万一级行业数据 sw1_data [] for sector in sw1: stocks get_stocks_of_sector(sector) sw1_data.append({ 行业板块: sector, 成分股数量: len(stocks), 成分股: ,.join(stocks) }) df_sw1 pd.DataFrame(sw1_data) print(申万一级行业示例:) print(df_sw1.head()) # 8. 整理概念板块数据 concept_data [] for sector in concept_sectors: stocks get_stocks_of_sector(sector) concept_data.append({ 概念板块: sector, 成分股数量: len(stocks), 成分股: ,.join(stocks) }) df_concept pd.DataFrame(concept_data) print(概念板块示例:) print(df_concept.head()) # 9. 输出到本地 CSVutf-8-sig 编码保证 Excel 打开不乱码 df_sw1.to_csv(shenwan_industry.csv, indexFalse, encodingutf-8-sig) df_concept.to_csv(concept_sector.csv, indexFalse, encodingutf-8-sig) print(数据已导出请检查当前目录下的 CSV 文件)这版代码跑完你会在当前目录得到两个文件shenwan_industry.csv和concept_sector.csv。前者是每个申万一级行业对应的成分股后者是每个概念板块对应的成分股。股票列表我没有单独存文件因为行业和概念的成分股已经覆盖了全市场股票不需要重复保存。3.3 从板块到个股一张反查映射表更实用上面的数据属于“板块视角”也就是一行一个板块。但实际做策略的时候经常需要“个股视角”也就是一只股票属于哪些行业、哪些概念。这个需求可以从上面的数据推导出来只需要把方向反过来。下面这段扩展代码就是把申万一级行业的板块映射成股票维度的行业归属表# 构建 股票 - 所属申万一级行业 的映射 stock_industry_map {} for sector in sw1: stocks get_stocks_of_sector(sector) for stock in stocks: if stock not in stock_industry_map: stock_industry_map[stock] [] stock_industry_map[stock].append(sector) df_mapping pd.DataFrame([ { 股票代码: stock, 所属申万一级行业: /.join(sectors) } for stock, sectors in stock_industry_map.items() ]) df_mapping.to_csv(stock_industry_mapping.csv, indexFalse, encodingutf-8-sig) print(个股行业映射表已导出共, len(df_mapping), 条记录)同理概念板块也可以做同样的映射。有些股票可能同时属于多个概念板块这种处理方式会把它们用“/”拼接起来读起来比较直观。拿到这张表之后你就能做很多有意思的事情了比如统计每个行业覆盖多少股票、每个概念板块的平均涨幅、以及根据个股所属题材做因子归类。3.4 执行效率与输出格式的优化建议如果你第一次跑完会觉得循环取板块成分股的时候有点慢尤其是概念板块有一两百个的时候需要逐块调用接口。实测下来每个板块耗时大约几十毫秒全量跑完大概需要几秒到十几秒完全在可接受范围内。核心优化点在于别在循环里做大量数据处理。上面代码已经先把结果存成列表最后统一转DataFrame这样的批量操作比一行行append到DataFrame里要快很多。另外CSV编码一定要用utf-8-sig这样用Excel打开不会乱码如果你要保留更多细节可以换成to_excel但需要额外安装openpyxl。我个人的习惯是把板块数据落地成CSV之后再通过pandas读入而不是每次跑策略都去实时拉一遍板块数据。因为板块成分股在一天内基本不会变化没必要每次都请求接口行业分类的更新频率就更低了一般每周同步一次已经足够。实时拉取的唯一好处是盘中有新概念上线能及时捕捉但对大多数策略来说并没那么敏感。4. 常见问题与排查技巧实录4.1 踩坑速查表我这几个月遇到的高频问题这个环节我直接整理成表格方便你遇到问题的时候快速定位。都是我自己真实跑过的坑不是从文档里抄的。现象原因解决方法connect()报错或返回异常QMT客户端未启动或没有登录启动QMT客户端并登录极简模式即可get_sector_list()返回空列表本地板块分类数据未同步先调用xtdata.download_sector_data()get_stock_list_in_sector()返回空板块名称拼写错误或前缘不对先打印板块列表确认实际名称格式导入xtquant失败没有把bin/xtquant加入sys.path检查QMT安装目录修改路径参数拿到代码无法与第三方数据匹配代码带交易所后缀.SZ/.SH/.BJ统一格式或用6位数字做映射转换导出CSV用Excel打开乱码编码用了utf-8改成utf-8-sig盘中首次调用板块数据延迟高正在下载板块分类数据预热机制启动程序时就先下载一次4.2 连接QMT时最常见的三个注意点第一个注意点是极简模式也可以跑接口。很多人以为QMT的Python接口一定要打开完整的客户端实际上经我测试独立行情端或者极简模式启动后也能连接xtdata。只要行情服务在线connect()就能通过不需要打开完整交易终端。第二个注意点是同一个账户不要开多个QMT实例。如果你同时开了多个客户端行情服务端口可能会冲突导致connect()不稳定。我踩过这个坑后来统一只开一个极简模式连接稳定很多。第三个注意点是板块分类数据需要手动同步但频率不用太高。download_sector_data()每次调用其实会做一次增量更新如果网络不好也可能超时。保守的做法是程序启动时调用一次失败也不影响主体流程因为本地已经有缓存的板块数据。4.3 板块列表版本差异先自查格式再写过滤必须再次强调过滤条件这块一定要根据自己机器的实际情况来。不同券商版本的QMT对于申万行业和概念板块的命名规则不完全相同。有些新版里面概念板块可能不叫“概念-”了有些行业分类前缀也不是“SW1-”。怎么自查很简单在脚本里加一行for s in sector_list: print(s)把板块列表全量打印出来心里就有数了。我看到过有人的板块列表里是用“BK-”开头来表示概念的也见过直接就叫“人工智能”不带前缀的。处理这种情况代码里的过滤条件改成人工智能 in s或者BK- in s就行不需要改动其他逻辑。这种灵活性正是先打印自检的价值所在。4.4 关于数据更新的两条实用经验第一个经验是股票列表和板块成分股的更新节奏不同。股票列表相对稳定新股上市或退市时才变化通常开盘前同步一次即可板块成分股虽然也相对稳定但遇到指数调仓、概念板块新增成分股时会变化建议每周同步一次。你要是每天跑策略其实每次启动时调一次download_sector_data()也无压力。第二个经验是如果同时需要板块成分股和行情数据一定要把两步分开处理。先同步板块分类再下载历史行情。因为下载历史行情是按股票代码逐个下载的通常耗时较久而板块分类同步快得多。把板块同步放在前面能提前发现板块为空的问题避免等到下载行情的时候才发现数据源不对白白浪费时间。5. 数据拿到手之后怎么用5.1 基于行业数据的选股与轮动思路拿到申万一级行业成分股后一个自然的用途是做行业轮动统计。你可以每天获取每个申万一级行业的成分股涨幅均值按涨幅排名观察哪些行业在走强。这种做法比单纯看指数更精细因为等权平均能避免权重股对指数的影响更真实地反映行业内部中小票的表现。另外行业成分股也可以用来做“行业中性化”处理。做股票多因子的时候如果不想让行业因子干扰收益归因就需要先知道每只股票所属行业然后对因子值做行业内部标准化处理。有了这张行业映射表整个行业中性化流程就顺了。5.2 概念板块数据的应用场景概念板块相比行业变化更快、更依赖市场热点但也很适合做情绪指标。比如统计某个概念板块的当日涨停数量、跌停数量、封板资金等就能判断这个概念是发酵期、高潮期还是退潮期。配合涨跌幅数据还能计算概念热度的持续性。一个实际操作经验是概念板块的成分股是动态调整的一个板块可能突然新增几只股票或者剔除几只。如果你在做概念轮动策略一定要定期同步板块数据并且保留历史快照否则复盘的时候会发现“这个股票当时到底属不属于这个概念”这种问题是说不清的。我的做法是每天收盘后固定跑一次全量同步把板块快照存成带日期字段的表方便后续回溯。5.3 如何继续扩展接入实时行情和K线如果你已经拿到了板块和行业映射下一步自然是想把行情数据也接进来。xtdata支持get_full_tick()获取实时快照也支持download_history_data()后通过get_market_data_ex()读取K线。把行情数据和板块映射结合起来就能做行业涨幅监控、概念资金流向分析这些进阶功能。这里我给你的建议是先把板块映射表做好然后单独一个模块处理行情数据两个模块通过股票代码关联。这样耦合度低出问题也好排查。别把所有的代码写在一个长脚本里后续维护会很痛苦。最后再分享两个小技巧一个是在写这套代码之前其实一直觉得QMT的板块数据很难拿绕了很多路后来发现真的就是一层窗户纸。你只要掌握两条核心线索连接客户端全局打开板块列表先打印再过滤大部分问题都能解决。这大概就是“原生接口”和“自己猜接口”之间的差距。另一个是如果你开发环境里跑代码时老是与QMT客户端断开检查一下是不是客户端开了自动打新或者休眠功能。量化交易员最怕的策略跑到一半数据源掉了却不知道。我自己的做法是给客户端设置一个电源管理策略禁止系统休眠另外把数据同步任务调到开盘前固定时间跑完开盘后尽量只读缓存这样既稳定又不占用盘中带宽。最后建议你把这套流程跑通之后顺手做一个“每日板块快照”的定时任务。别小看这一步时间久了之后你会发现这些历史板块数据本身就能形成一个新的因子库。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →