ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用MOOTDX抓取A股分时数据,并基于matplotlib绘制专业分时图

用MOOTDX抓取A股分时数据,并基于matplotlib绘制专业分时图 很多人第一次接触行情数据可视化最先想画的就是分时图——那种券商软件里红红绿绿的日内价格走势。以前我做这个需求时换来换去试了一堆数据源要么注册门槛高要么历史分时取不到后来锁定MOOTDX这个Python库配合matplotlib画出来的效果可以做到和商业软件非常接近。这篇文章我会把完整的抓取逻辑、绘图细节和中间踩过的坑都写出来配置好环境之后复制代码就能直接跑不管是做量化研究还是日常复盘盯盘都能用得上。做技术选型时我也纠结过一阵这个库到底值不值得上手下面先把它和常见方案放在一起对比着说清楚。1. MOOTDX是什么为什么我最终选了它1.1 一次真实的选型过程先说结论MOOTDX是国内开源社区基于通达信行情协议封装的一个Python库核心定位就是“免费、免注册、实时性尚可”。它的接口风格比较简洁Quotes.factory()建立连接之后调用client.minute()就能拿分时数据调用client.quotes()能拿实时五档报价不需要申请token也不存在积分限制。我当时候选的有三套方案各有各的毛病Tushare数据质量确实稳但分时这种明细数据对积分要求不低注册、积分、限流一套组合拳下来个人开发者直接劝退。AkShare接口很丰富聚合了很多数据源但历史分时数据经常受上游源限制盘中请求偶尔会被限流接口报错信息也不够友好。pytdx底层协议是好的可是直接用pytdx写代码连接、打包、解包、字段映射全都得自己处理开发成本高不适合只想快速拿到数据的人。MOOTDX相当于把pytdx那层繁琐的东西重新封装了一遍底层走的是通达信行情服务器数据实时性比大多数免费网页接口要强尤其适合做盘中监控和当日分时复盘。我实测下来正常交易日盘中请求分时数据的延迟在毫秒级基本满足个人研究需要。它还有一个好处分时数据返回的就是干净的Pandas DataFrame不需要自己拼结构拿到手直接就能开始分析绘图。补充一句任何免费行情源都有服务器不稳定的可能MOOTDX也一样。网上偶尔能看到“mootdx不能用了吗”这样的讨论多半是通达信的行情节点变动导致的这个在第4章我会重点讲怎么解决。1.2 安装与基础配置别踩这些坑安装本身不复杂用pip一行就搞定pip install mootdx但我建议在动手之前先做一个隔离环境尤其是你机器上同时装了很多Python项目依赖的时候。我遇到过因为numpy版本冲突导致matplotlib绘图报错的情况排查起来纯属浪费时间。用venv或者conda建一个专门的行情分析环境后面会省心很多python -m venv stock_env source stock_env/bin/activate # Windows下执行 stock_env\Scripts\activate pip install mootdx pandas matplotlib安装完成之后先别急着写业务代码跑一下下面这个验证脚本确保库本身能正常工作python -c from mootdx.quotes import Quotes; print(OK)如果输出OK说明安装没有问题。如果在这一步就报错大概率是网络环境访问不了通达信的行情服务器或者库版本过旧。这时可以先升级库pip install -U mootdx再强调一个容易被忽略的点分时数据抓取和绘图不是MOOTDX一个库能搞定的你的环境里至少还要有pandas和matplotlib。有些教程默认你已经装好了但新手往往在这一步卡很久。pandas负责数据处理matplotlib负责最后的图表输出三者的关系就是一个流水线缺一环都会导致最终代码跑不通。2. 分时数据抓取的原理与核心代码2.1 分时数据长什么样看懂字段再动手分时数据和传统的K线数据思路很不一样。K线数据描述的是一个周期内的四个价格开高低收而分时数据是交易时间内每一分钟产生的“切片”。A股一天交易4小时上午9:30到11:30共120分钟下午13:00到15:00共120分钟理论上每个交易日有240根分钟记录。拿到的分时数据通常包含以下核心字段字段含义说明time时间字符串格式如09:31price当前价格该分钟最后一笔成交价avg_price当日分时均价从开盘到该分钟的累计成交金额 ÷ 累计成交量volume成交量该分钟内的成交量单位通常是手last_close昨收价用于计算涨跌幅基准不是所有版本都有这里最关键的是avg_price字段它是画分时均价线就是券商软件里那条黄线的数据基础。分时均价的本质是“当日持仓者的平均成本”它的实时走势能很直观地反映资金是愿意在盘中高位承接还是低位吸筹。不同版本的MOOTDX返回字段可能有细微差异建议拿到数据后先执行一次print(df.head())看看实际列名再往下处理不要硬编码字段。2.2 MOOTDX抓取分时数据的完整实现下面这段代码演示了最基础的抓取流程我加了重试机制和空数据保护这两种情况在实际使用中实在太常见了from mootdx.quotes import Quotes def get_stock_minute(symbol, date, max_retry3): 获取个股分时数据 :param symbol: 股票代码如 600036 :param date: 交易日期字符串格式 YYYYMMDD :param max_retry: 最大重试次数 :return: (DataFrame, Quotes客户端) client Quotes.factory(marketstd) for attempt in range(1, max_retry 1): try: df client.minute(symbolsymbol, datedate) if df is not None and not df.empty: return df, client print(f返回数据为空第 {attempt} 次重试...) except Exception as e: print(f第 {attempt} 次请求失败: {e}) time.sleep(0.5) raise RuntimeError(f连续 {max_retry} 次无法获取 {symbol} 的分时数据)调用方法很简单df, client get_stock_minute(600036, 20240115) print(df.head())有几个细节需要特别提醒date参数格式必须是YYYYMMDD。我一开始传成2024-01-15这种带横杠的格式结果返回空数据查了半天才发现是格式问题。如果传了非交易日或者当天还没开盘返回的DataFrame基本上是空的。最好先确认目标日期确实是交易日否则后续绘图会拿到一个没有任何数据可画的空表。网络请求偶尔会超时所以重试机制不是可有可无的装饰而是实际使用中真的能救命。抓回分时数据之后我顺手还会用client.quotes()拿到当天的昨收价后面画基准线要用quotes_df client.quotes(symbol[600036]) prev_close float(quotes_df.iloc[0][last_close]) print(f昨收价: {prev_close})注意quotes()接口入参是列表返回的是DataFrame。不同库版本里昨收字段名可能叫last_close也可能叫pre_close建议打印列名确认一下。3. 用matplotlib绘制一张专业的分时图3.1 分时图的信息架构设计拿到分时数据之后最核心的问题是一张专业的分时图应该包含哪些信息我复盘了市面上主流券商软件的分时图总结出四个必备元素价格线每根分钟柱的收盘价连成的曲线通常用红色表示上涨、绿色表示下跌和A股的红涨绿跌惯例保持一致。分时均价线当日持仓均价用黄色或白色绘制反映市场整体的成本重心。昨收基准线一条水平虚线用来快速判断当前价格处于昨日收盘价上方还是下方。成交量柱在副图区域展示颜色同样遵循红涨绿跌直观表现每一分钟的多空交战力度。布局上主流做法是上下结构主图放价格线和均价线副图放成交量两个子图共享X轴高度比例大约3:1。有人会问为什么不用X轴直接放时间字符串因为A股交易时间有午间休市上午11:30到下午13:00之间没有交易数据。如果直接用真实时间轴中间会空出一大段空白不仅难看还会让价格走势在视觉上被拉伸变形。所以专业做法是用数据索引作为X轴然后在固定位置上标注对应的时间点这样上午和下午的走势在视觉宽度上是对称的。3.2 完整绘图代码与逐段解析下面这段是我在实际项目中打磨过的完整绘图代码参数都按“券商软件接近的显示效果”调过import matplotlib.pyplot as plt import matplotlib.ticker as mticker import pandas as pd import numpy as np from mootdx.quotes import Quotes # matplotlib 默认字体不支持中文必须先设置中文字体 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False def plot_minute_chart(df, symbol, date, prev_closeNone): 绘制专业分时图 :param df: 分时数据 DataFrame必须包含 time/price/avg_price/volume 列 :param symbol: 股票代码用于标题显示 :param date: 交易日期用于标题显示 :param prev_close: 昨收价如果为空则取数据第一行价格近似代替 df df.copy() df[time_str] df[time].astype(str).str[:5] n len(df) # 如果没有昨收价用第一分钟价格近似替代仅作兜底 if prev_close is None: prev_close float(df[price].iloc[0]) # 动态寻找上午/下午的分界点 pm_mask df[time_str] 13:00 split_idx int(pm_mask.idxmax()) if pm_mask.any() else n # 构造X轴刻度位置上午取5个点下午取5个点 def tick_points(start, end): if end - start 0: return [start] return [int(start (end - start) * i / 4) for i in range(5)] positions tick_points(0, split_idx) tick_points(split_idx, n - 1) labels [df[time_str].iloc[i] for i in positions] # 创建画布主图成交量副图 fig, (ax1, ax2) plt.subplots( 2, 1, figsize(14, 9), sharexTrue, gridspec_kw{height_ratios: [3, 1]} ) fig.subplots_adjust(hspace0.08) x np.arange(n) # 主图价格线 均价线 昨收线 price_color #d33e43 # 红色用于上涨主基调 ax1.plot(x, df[price], colorprice_color, linewidth1.6, label价格) ax1.plot(x, df[avg_price], color#f0a42e, linewidth1.2, label分时均价) ax1.axhline(yprev_close, color#555555, linestyle--, linewidth0.9, label昨收) # 涨跌停参考线按主板10%计算 limit_up round(prev_close * 1.1, 2) limit_down round(prev_close * 0.9, 2) ax1.axhline(ylimit_up, color#d33e43, linestyle:, linewidth0.7, alpha0.5) ax1.axhline(ylimit_down, color#2f9e6e, linestyle:, linewidth0.7, alpha0.5) # 价格与昨收之间的填充区域直观呈现盘中浮盈/浮亏区间 ax1.fill_between( x, df[price], prev_close, where(df[price] prev_close), color#d33e43, alpha0.12 ) ax1.fill_between( x, df[price], prev_close, where(df[price] prev_close), color#2f9e6e, alpha0.12 ) ax1.set_ylabel(价格 (元)) ax1.grid(True, alpha0.35, linestyle--) ax1.legend(locupper left, fontsize9) ax1.set_title(f{symbol} 分时图 {date}, fontsize14, fontweightbold) # 副图成交量柱状图 bar_colors [#d33e43 if p prev_close else #2f9e6e for p in df[price]] ax2.bar(x, df[volume], colorbar_colors, width0.7, alpha0.8) ax2.set_ylabel(成交量 (手)) ax2.grid(True, alpha0.25, linestyle--) # X轴时间刻度 ax2.set_xticks(positions) ax2.set_xticklabels(labels, rotation0) return fig这段代码里有两个设计细节我特意做了处理第一时间轴刻度没有写死成“9:30、10:00、10:30……”而是根据实际数据条数动态计算。这样可以兼容盘中数据不完整、个股停牌时段缺失等情况不会因为索引越界导致绘图失败。第二涨跌停线的计算先按主板10%的规则处理。如果你用的是创业板或科创板标的需要把比例改成20%计算方式是一样的。图中用虚线做了边界提示视觉上能快速看出股价是贴着涨停板运行还是在跌停板附近挣扎。画完之后的保存操作不要用plt.show()直接弹窗就完事我建议输出成高分辨率图片fig plot_minute_chart(df, 600036, 20240115, prev_close) fig.savefig(600036_20240115分时图.png, dpi150, bbox_inchestight) plt.close(fig)dpi调到150以上图片放到复盘笔记或者发到手机上都能看清细节bbox_inchestight可以自动裁掉边缘空白不让图表四周留太多没用区域。3.3 输出与保存海报级效果的小细节在画图的实际操作中有几个小细节很影响最终效果值得单独拿出来说一下。中文字体是第一优先级。matplotlib的默认字体里没有中文字形如果你不设置rcParams[font.sans-serif]标题和图例里的中文就会显示成一个个方框。在Windows上我一般用SimHei黑体在macOS上可以用PingFang SCLinux上如果没有中文字体需要先安装fonts-wqy-microhei之类的字体包。颜色规范千万别搞反。A股的红涨绿跌与国际市场的绿涨红跌是相反的。如果习惯写美股技术指标的颜色逻辑很容易把上涨画成绿色、下跌画成红色这在看盘语境下会引发严重误读。我在代码里用#d33e43作为上涨色、#2f9e6e作为下跌色你可以直接复用。成交量柱的宽度不要太宽。默认宽度为1时柱子会黏在一起视觉上很拥挤我调到0.7之后清爽很多。这个值也可以根据数据条数微调数据量少于200根时适当放宽到0.8。画完记得关掉图形对象。在批量导出多张分时图时不执行plt.close(fig)会累积内存轻则拖慢脚本重则导致内存溢出这是一个在批量任务里特别常见的隐性坑。4. 高频问题与避坑指南4.1 服务器地址不可用怎么办这是MOOTDX使用者遇到率最高的问题也是网上“mootdx不能用了吗”这类讨论的根源。通达信行情协议依赖公网服务器节点而节点并不是永久稳定的——机房维护、策略调整、流量过载都可能导致某个IP地址失联遇到这种情况才会出现connect timeout或者address unavailable之类的报错。我实测有效的排查顺序如下第一先执行自动优选的bestip()方法让库自动测速并选择可用的服务器节点client Quotes.factory(marketstd) client.bestip()这个方法会遍历内置的服务器列表逐个测试连接延迟然后把客户端切换到一个最快最稳的节点上。绝大多数“昨天还能用今天不行了”的情况通过这一步就能解决。第二如果bestip()也救不回来试着升级库版本pip install -U mootdx服务器地址列表会随版本更新新版本通常会修正失效的节点。第三还是不行时检查本机到行情服务器的网络连通性。拿Windows系统举例在命令行里执行ping 你的行情服务器IP不同库版本内置的服务器地址不一样可以通过client.config或client.addr之类的属性查看当前使用的地址。如果ping不通说明是网络层面的问题这时候可以考虑切换网络环境或者稍微等一段时间再试服务器恢复后自然就好了。第四如果急需用数据又暂时没有可用的通达信节点可以临时换数据源过渡AkShare里的stock_zh_a_minute接口或者新浪、腾讯的公开行情接口虽然实时性和稳定性各有差异但作为备选方案没有问题。等MOOTDX节点恢复后再切回来。重点免费库的稳定性不能和商业数据源比。做个人研究可以容忍偶发故障但如果你的程序要7x24小时跑监控告警我建议在架构上做好多数据源自动切换不要在一棵树上吊死。4.2 数据异常排查非交易日、停牌、字段缺失除了服务器问题之外我整理了一个高频异常速查表基本都是实战里验证过的问题现象根本原因解决办法返回空DataFrame传入了非交易日或节假日先通过交易日历接口确认日期是否为交易日返回条数明显少于240个股停牌/盘中数据未完整按实际数据条数动态计算刻度位置KeyError: last_close库版本字段名不一致打印df.columns确认字段名改用pre_close中文标题显示方框matplotlib缺少中文字体设置rcParams[font.sans-serif]绘图时索引越界时间轴刻度写死导致改用数据条数动态生成刻度位置这里我特别想提一下“字段名不一致”这个坑。不同版本的MOOTDX在返回字段命名上确实存在过差异早期版本里last_close可能叫pre_closeavg_price可能叫average。所以最保险的做法是在拿到数据后先看列名再决定后续字段怎么取值我上面写的代码也特意保留了对last_close和pre_close的双重兜底逻辑。历史分时数据还有一个隐性限制通达信节点一般只保留最近几个交易日有时只有最近5个交易日的分钟级数据更早的数据很难通过MOOTDX拿到。如果要做多日分时对比或者历史因子回测最好每天收盘后定时抓取并落地存储把数据积累从“临时调用”变成“持续沉淀”。4.3 进阶玩法交互式图表与定时任务加持matplotlib画出来的分时图是静态图片适合嵌入研究报告或复盘文档。但如果你想在浏览器里查看鼠标悬停的价格明细或者想在手机上随时打开看当日走势我建议试试pyecharts。pyecharts是一个生成JavaScript图表的Python库输出的是HTML文件天然适配浏览器和手机端。画分时图的核心思路是把同样的价格序列和成交量序列传入Line和Bar组件最后拼接成一个Grid布局from pyecharts.charts import Line, Bar, Grid from pyecharts import options as opts # 将df中的价格、均价、时间转为列表 times df[time].astype(str).str[:5].tolist() prices df[price].round(2).tolist() avg_prices df[avg_price].round(2).tolist() line Line() line.add_xaxis(times) line.add_yaxis(价格, prices, is_smoothTrue) line.add_yaxis(均价, avg_prices, is_smoothTrue) bar Bar() bar.add_xaxis(times) bar.add_yaxis(成交量, df[volume].tolist()) grid Grid() grid.add(line, grid_optsopts.GridOpts(pos_bottom25%)) grid.add(bar, grid_optsopts.GridOpts(pos_bottom5%)) grid.render(minute_chart.html)这样生成的HTML文件可以直接用浏览器打开也可以部署到内网服务器上随时随地查看。手机端浏览器访问时交互式图表的缩放和滑动体验比静态图片好很多。如果你的数据抓取任务是固定每天收盘后执行建议再用系统定时任务把它自动化Windows下用“任务计划程序”指定每天15:05运行Python脚本。Linux/macOS下用crontab比如5 15 * * 1-5表示周一到周五下午15:05执行。自动化的另一个好处是能把数据保存成CSV或Parquet格式长期积累下来就是一份自己的分时数据库。有了历史数据之后你可以在上面做日内波动统计、分时均价偏离度分析、尾盘异动识别等更深度的研究这就远远不止“画一张图”那么浅了。我在实际使用中还有一个体会无论是做盘中实时的价格监控还是做盘后的复盘分析工具始终是辅助关键还是看你怎么解读这些数据。分时图和K线最大的不同在于它保留了日内交易节奏的细节价格什么时段走强、什么时段走弱成交量在哪些关键点位放大这些信息配合大盘指数和板块走势一起看往往能帮你发现很多闭着眼睛看日线图发现不了的机会。这套抓取和绘图方案我是建议你直接拿来改一改跑一跑的反正代码都在这跑通了再按自己的标的和风格调整细节就行。
返回列表