做量化交易或者日常盯盘的人,多多少少都动过自己拉数据的念头。市面上付费数据源不便宜,免费的数据又经常缺字段、缺历史、更新延迟。我自己的做法是直接从东方财富的公开接口抓,尤其是分时数据,接口稳定、字段全、延迟低,关键是代码写起来不费劲。
这篇文章就围绕东方财富分时数据获取这件事展开,从接口选型、参数含义、完整代码,到基于分时数据计算多空趋势指标的思路和源码,最后是实操中容易踩的坑。不管你只是想做个盯盘工具,还是准备给自己的量化策略补充日内数据,这篇都值得花五分钟看完。
1. 先搞清楚:东方财富的分时数据到底从哪里来
1.1 我为什么选东方财富作为数据源
做股票数据抓取,可选的数据源很多:新浪、腾讯、网易、东方财富,还有券商自带的接口。我自己最终长期用的是东方财富,原因有三个。
第一,接口返回的数据非常规整,分时数据直接是逗号分隔的纯文本,用简单的split就能解析成结构化数据,不需要处理JSON嵌套,也没有乱七八糟的加密参数。第二,字段覆盖面足够广,价格、均价、成交量、成交额、涨跌幅这些日内指标都有,做技术分析绰绰有余。第三,也是最关键的,接口相对稳定,不太容易因为请求频率低就封IP,我自己实测,每秒一次以内的请求频率,持续跑一天都没问题。
新浪的接口虽然也能用,但经常出现数据延迟,而且返回的是js变量包装的文本,格式一致性差一些。腾讯的接口延迟最低,但历史分钟数据补起来麻烦。综合下来,东方财富在“免费、稳定、好解析”这三者之间的平衡是最好的。
1.2 两个常用接口:趋势分时与分钟K线
东方财富和分时数据相关的接口主要有两个,我这里把它们都列出来,方便你理解后续代码里用的是哪一个。
第一个是趋势分时接口,对应的是行情软件里按回车键看到的那条白色分时价格线和黄色均价线。它的特点是:一天只有240个采样点(A股每天4小时交易,每分钟1个点),返回的是从9:30到15:00每个自然分钟的价格快照。这个接口是最贴近“分时图”这个概念的。
第二个是分钟K线接口,返回的是1分钟、5分钟、15分钟、30分钟、60分钟等任意周期的K线数据。它可以拉历史,比如你要回测过去一年的5分钟K线,用这个接口最合适。它和趋势分时接口的区别在于:K线是聚合后的数据(每个周期的开高低收、成交量),趋势分时是逐分钟的快照数据。
一句话总结:如果只是想画分时图、判断当日多空强弱,用趋势分时接口;如果要做分钟级别的策略回测,用分钟K线接口。两个接口都属于东方财富的数据接口体系,请求方式是类似的。
2. 动手之前:接口参数与字段含义全拆解
2.1 secid:藏在数据里最大的坑
无论用哪个接口,都绕不开secid这个参数。它的格式是“市场代码.股票代码”,市场代码只有两个值:1代表上海证券交易所,0代表深圳证券交易所。
举个例子:平安银行的股票代码是000001,它属于深交所,所以secid=0.000001。浦发银行的代码是600000,属于上交所,secid=1.600000。江南嘉捷这类北交所股票,我在实践中发现用0开头通常是有效的,不过北交所的字段返回情况和沪深略有差异,需要单独处理。
这个参数看起来简单,但实际容易搞错。很多人在网上找到别人的代码,复制过来,结果自己换成另一只股票就报错了,问题就出在这里。你不能只改股票代码,必须连市场前缀一起改。建议的做法是用一个函数根据代码前缀自动判断市场:6开头的是上交所,0开头和3开头的是深交所。
2.2 trends接口的核心参数与返回结构
趋势分时接口的完整URL大概长这样:
https://push2his.eastmoney.com/api/qt/stock/trends/get?secid=1.600000&fields1=f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13&fields2=f51,f52,f53,f54,f55,f56,f57,f58&ndays=1&iscr=0&iscca=0&ut=fa5fd1943c7b386f172d6893dbfba10b参数说明如下:
- ndays:拉取多少个交易日的数据,填1就是当天。
- fields1:股票基础信息字段,比如股票代码、名称、市场状态等,一般固定用上面那一串就行。
- fields2:分时数据的字段列表,这才是重点。f51是日期时间,f52是当前时刻价格,f53是均价,f54是成交量,f55是成交额,f56是最新价相对昨日收盘的涨跌幅(单位是百分比吗?不是,单位是涨跌幅数值,比如1.23表示涨1.23%),f57是涨跌额,f58是换手率。
返回的JSON结构里,最关键的是data.trends这个数组。每个元素是一个字符串,像这样:
2025-01-10 09:30,10.15,10.10,11200,1136800,0.50,0.045,0.42用split(',')切成列表后,从左到右依次对应fields2里定义的字段。建议你把这份对应关系保持在一张表里,后面写代码解析时反复对照。
2.3 字段对应关系:哪些列是价格,哪些是量
我把自己常用的两套字段对应关系整理成了一张表。分时数据(trends)和分钟K线(kline)的字段顺序不同,容易混。
分时数据字段对应表
| 字段名 | 含义 | 单位 | 备注 |
|---|---|---|---|
| f51 | 日期时间 | 字符串 | 格式:yyyy-MM-dd HH:mm |
| f52 | 最新价 | 元 | 该分钟收盘价 |
| f53 | 均价 | 元 | 当日累计成交额/累计成交量 |
| f54 | 成交量 | 手 | 注意是手,1手=100股 |
| f55 | 成交额 | 元 | 该分钟成交金额 |
| f56 | 涨跌幅 | % | 相对昨日收盘价 |
| f57 | 涨跌额 | 元 | 相对昨日收盘价 |
| f58 | 换手率 | % | 当日累计换手 |
分钟K线字段对应表
分钟K线接口fields2通常设置成f51到f61,顺序是:日期、开、收、高、低、成交量、成交额、振幅、涨跌幅、涨跌额、换手率。
这里有个特别容易搞混的地方:K线字段的顺序是开、收、高、低,不是开高低收。我第一次写解析代码时顺手按“开高低收”的顺序解析,结果算出来的指标全错了。后来只能对着接口返回数据仔细核对才反应过来。
3. Python快速抓分时数据:完整可跑代码
3.1 环境准备与依赖
写代码之前先准备好环境。我用的是Python 3.9以上版本,核心依赖只有两个:requests用来发HTTP请求,pandas用来做数据处理。另外建议装一个pytz,后面处理时区要用。
pip install requests pandas pytz如果你只是想快速测试,不装pandas也行,用纯Python的列表就能接住数据。但后续要做指标计算,pandas的效率会高很多,建议装上。
3.2 抓取当日分时数据的核心代码
下面这段代码是我实际在用的版本,可以去掉了多余的东西,保留了最核心的逻辑。它的流程是:先根据股票代码判断市场,然后请求趋势分时接口,把返回的trends数组解析成结构化的列表。
import requests import pandas as pd def get_secid(stock_code: str) -> str: """根据股票代码自动判断市场前缀。 6开头是沪市,0和3开头是深市,北交所暂时也按深市处理。 """ if stock_code.startswith("6"): return f"1.{stock_code}" elif stock_code.startswith(("0", "3")): return f"0.{stock_code}" else: raise ValueError(f"无法识别股票代码: {stock_code}") def fetch_trends(stock_code: str, ndays: int = 1) -> pd.DataFrame: """获取东方财富分时数据,返回DataFrame。 """ secid = get_secid(stock_code) url = "https://push2his.eastmoney.com/api/qt/stock/trends/get" params = { "secid": secid, "fields1": "f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13", "fields2": "f51,f52,f53,f54,f55,f56,f57,f58", "ndays": ndays, "iscr": 0, "iscca": 0, "ut": "fa5fd1943c7b386f172d6893dbfba10b", } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://quote.eastmoney.com/", } resp = requests.get(url, params=params, headers=headers, timeout=10) resp.raise_for_status() data = resp.json().get("data") if not data or "trends" not in data: raise RuntimeError(f"接口返回异常: {data}") rows = [] for item in data["trends"]: parts = item.split(",") rows.append({ "time": parts[0], "price": float(parts[1]), "avg_price": float(parts[2]), "volume": float(parts[3]), "amount": float(parts[4]), "pct_change": float(parts[5]), "change": float(parts[6]), "turnover": float(parts[7]), }) df = pd.DataFrame(rows) df["time"] = pd.to_datetime(df["time"]) return df if __name__ == "__main__": df = fetch_trends("600000") print(df.head()) print(df.tail())这段代码跑通之后,你手里就有了一个标准的DataFrame,包含当天从9:30到15:00每一分钟的价格、均价、成交量等信息。这是后续所有衍生指标计算的基础。
3.3 解析成DataFrame并追加涨跌幅等衍生字段
拿到了原始的分时数据之后,直接用还不够,通常还需要追加一些衍生字段。比如:
- 分钟成交量占比:每分钟成交量占全天成交量的比例,用来判断哪个时段交易最活跃。
- 价格位置:最新价相对于当日最高价和最低价的位置,公式是(最新价 - 最低价)/(最高价 - 最低价),这个指标用来看当前价格处于当日区间的什么水平。
- 均线偏离度:(最新价 - 均价)/ 均价,衡量价格偏离当日持仓成本的程度。
这些衍生字段的处理我直接写在同一个解析流程里,代码不复杂,但是很实用。
def enrich_trends(df: pd.DataFrame) -> pd.DataFrame: """在原始分时数据基础上追加衍生字段。 """ df = df.copy() day_high = df["price"].max() day_low = df["price"].min() df["price_position"] = (df["price"] - day_low) / (day_high - day_low) df["avg_deviation"] = (df["price"] - df["avg_price"]) / df["avg_price"] * 100 df["volume_pct"] = df["volume"] / df["volume"].sum() * 100 df["cum_amount"] = df["amount"].cumsum() return df做完这一步,你的分时数据就已经具备了做简单量化分析的基础。接下来我们聊一个很多人关心的东西,多空趋势指标。
4. 从分时数据到多空趋势指标:源码与思路
4.1 多空趋势指标怎么设计才合理
搜“东方财富多空趋势指标源码”的人很多,但真正理解指标原理的没几个。我在这里不直接给你一个可以抄的指标源码,因为不同人的交易风格不一样,参数也不一样。我先讲清楚设计思路,再给出一套可落地的基础版本。
多空趋势指标的核心,本质上是解决一个问题:当前价格相对于市场平均持仓成本的位置。价格在成本线之上,多方占优;价格在成本线之下,空方占优。分时数据里的均价线(avg_price)就是全市场当日持仓成本的平均值。所以最简单的多空趋势指标,就是价格偏离均价的幅度。
但这个指标太粗糙了,单看价格偏离度忽略了成交量的权重。一个高开低走的股票,均价线在开盘时被拉到很高,但随后价格快速跌破均价,这个过程中的成交量如果放大,说明抛压很重。所以合理的设计思路应该是:价格偏离度结合成交量确认。
4.2 分时均线源码示例
我们先写一个最基础的分时多空趋势指标。它的逻辑很简单:当价格在均线上方且成交量放大时,标记为多方强;当价格在均线下方且成交量放大时,标记为空方强。成交量是否放大,用当前分钟量和过去N分钟平均量的比值来判断,我个人习惯用过去20分钟的均值作为基准。
def multi_short_trend(df: pd.DataFrame, volume_window: int = 20) -> pd.DataFrame: """基于分时数据计算多空趋势指标。 返回的DataFrame中,trend列取值: 1代表多方强势,-1代表空方强势,0代表震荡。 """ df = df.copy() df["vol_ma"] = df["volume"].rolling(window=volume_window, min_periods=1).mean() df["vol_ratio"] = df["volume"] / df["vol_ma"] trend = [] for _, row in df.iterrows(): if row["price"] > row["avg_price"] and row["vol_ratio"] > 1.2: trend.append(1) elif row["price"] < row["avg_price"] and row["vol_ratio"] > 1.2: trend.append(-1) else: trend.append(0) df["trend"] = trend return df这段代码的实际效果是:在分时图上标注出那些价格偏离均价且放量的关键节点。你用来复盘昨日走势的时候,会发现自己能很清楚地看到每波拉升和杀跌对应的情绪爆发点。
4.3 量价配合与背离的简单实现
上面的版本还有一个缺陷:它只看瞬间的状态,没有考虑趋势的延续和背离。实盘中经常出现一种情况:价格创了日内新高,但成交量明显萎缩,这就是量价背离,大概率是诱多信号。
量价背离的判断逻辑可以这样实现:连续两个时间点,价格高点在抬升,但对应的成交量峰值在下降,就标记背离。更严谨一点,可以用价格涨跌幅和量能变化率的相关系数来量化。
def detect_divergence(df: pd.DataFrame, lookback: int = 30) -> pd.DataFrame: """检测分时数据中的量价背离。 返回DataFrame中新增divergence列,1为顶背离,-1为底背离,0为无背离。 """ df = df.copy() df["price_high"] = df["price"].rolling(window=lookback, min_periods=1).max() df["vol_high"] = df["volume"].rolling(window=lookback, min_periods=1).max() divergence = [] for i in range(len(df)): if i < lookback: divergence.append(0) continue price_new_high = df["price"].iloc[i] >= df["price_high"].iloc[i - 1] vol_not_new_high = df["volume"].iloc[i] < df["vol_high"].iloc[i - 1] * 0.8 if price_new_high and vol_not_new_high: divergence.append(1) else: divergence.append(0) df["divergence"] = divergence return df这套逻辑不是万能的,但它能帮你把分时图里肉眼容易忽略的细节用代码固化下来。比如你复盘一只股票全天走势,软件上只能看到价格和均线的位置关系,但代码可以精确告诉你,14:23这次创出日内新高的时候,量能只有前高的60%,这在操作上就是一个明确的减仓信号。
5. 实操中必踩的坑与排查技巧
5.1 接口返回空、403异常码怎么处理
东方财富的接口虽然稳定,但不是绝对没有反爬机制。我自己遇到的比较多的两类异常:一是请求频率过高触发限制,二是某些网络环境下需要额外的请求头。
应对方案主要是两个方向:
第一,降低请求频率。做数据抓取不是做实时行情推送,一天240个数据点,你不需要每秒请求一次。做盘中监控的,建议至少间隔3秒以上再请求一次;做盘后复盘的,一天跑一次就够。实测下来,超过每秒2次的持续请求,半小时内大概率会被临时限制访问。
第二,补全请求头。Referer和User-Agent这两个字段最好带上,Referer固定为https://quote.eastmoney.com/,User-Agent用浏览器默认的即可。有部分网络环境下,不带Referer会直接返回403。
另外,接口返回里有一种情况是返回了正常JSON,但data字段是null。这种情况通常有两种可能:股票代码输错了、股票停牌了。代码里对data为空的处理逻辑一定要写,不要假设接口永远返回正常数据。
5.2 时间戳与时区,一个隐蔽的大坑
东方财富返回的时间是北京时间,格式是yyyy-MM-dd HH:mm。这在本地跑没有问题,但如果你用的是云服务器,特别是海外节点,Python的datetime和pandas默认会使用UTC时区,你如果直接做时间计算,结果会偏移8小时。
处理方法是读取时间字符串后,手动指定时区为Asia/Shanghai。用pandas的话,加一行就行。
df["time"] = pd.to_datetime(df["time"]).dt.tz_localize("Asia/Shanghai")注意这里用的是tz_localize,不是tz_convert。因为原始时间没有时区信息,是本地时间,你要做的是告诉pandas这个时间属于哪个时区。如果你先convert,会因为没有时区信息而报错。
如果你想把时间转换为UTC做存储,再在后面加一步:
df["time_utc"] = df["time"].dt.tz_convert("UTC")5.3 停牌、新股、北交所的边界情况
实战中还会遇到几类特殊股票,处理不好代码会直接崩。
第一类是停牌股。当天没有交易,trends接口返回的数据是空数组。解析的时候data["trends"]是None或空列表,需要提前判断并跳过。我的习惯是定义一个常量表示“今日无数据”,避免后续的指标计算传入空DataFrame。
第二类是刚上市的新股。上市前5个交易日没有涨跌幅限制,涨跌幅字段会超过10%,但数据结构不变,代码不用改。需要注意是,次新股的均线数据参考意义不大,因为上市首日没有历史筹码沉淀,分时均价线只能反映当天的成本,做多空趋势指标时要谨慎。
第三类是北交所股票。我实测时发现北交所分时数据和沪深最大的不同是交易时间多出15分钟,也就是从9:30到11:30、13:00到15:15,每天有255个采样点。如果你用固定的240点做循环处理,北交所数据会报索引越界。代码里不要写死长度,用len(df)动态判断。
5.4 关于接口和数据的一点点个人经验
最后分享两个我自己一直在用的小习惯。
第一个习惯是每次请求前先判断当前时间是否处于交易时段。A股交易时间是周一到周五的9:30到11:30、13:00到15:00,周末和法定节假日不开盘。请求之前先用datetime判断一下,如果非交易时段,分时数据不会变化,直接读取本地缓存就行。这个逻辑可以减少大量的无效请求,也降低被限制的风险。
第二个习惯是原始数据落地保存。我每次拉取数据都会在本地存一份CSV,文件名带上日期和股票代码。后面即使策略代码改了、字段算错了,还能拿原始数据重新算。自己写的数据获取代码,一定要把原始数据留档,因为解析代码可以重构,但历史行情数据过了那天就没有了。
数据获取只是第一步,真正的价值在于你拿这些数据做了什么。把分时抓下来、指标算出来,这只是个开始。你完全可以从这里继续延伸:比如用分钟级数据做盘中预警,监测价格快速突破均价线且放量的异动;比如把多空趋势指标做成后台定时任务,收盘后自动生成数据报告;再比如把抓到的分时数据入库,长期积累成自己的日内T+0策略回测数据池。这些方向都值得慢慢折腾,也欢迎在这条路上握个手,互相交流。