tushare前复权数据与Python技术指标计算全指南
2026/9/11 18:20:18 网站建设 项目流程

简介:一个面向股票量化与数据分析场景的Python资源包,基于Tushare接口获取前复权日线数据,并实现均线、KDJ、MACD、RSI等常用技术指标计算,适合需要批量获取行情数据并进行指标建模的开发者使用。压缩包共32个文件,以18个Python源码为核心,配有9个可直接依赖的pyc编译文件、2个Markdown说明文档及配置文件等,整体仅38KB,轻量且目录清晰。已有7379人学习下载。解压后运行demo示例脚本即可获取指定股票的日线数据,而另一个批量脚本通过for循环抓取沪深全市场数据,前复权处理与常用股票软件显示一致。indexes文件夹内封装了MA、KDJ、MACD、RSI等指标函数,tests目录下还附带对应测试代码,方便验证与二次扩展。 最近做个人行情分析工具的时候,我又一次被“复权”问题坑了一遍。当时图省事直接从数据源拉了一个不复权的日线,结果某只股票刚分红完,MACD直接给我画出一个夸张的金叉。说实话,这一幕在量化新手身上太常见了,搞技术指标还没搞清楚前复权,后面算的均线、KDJ、MACD全是错的。这篇文章就把我用tushare获取前复权数据,并在Python里完整实现均线、KDJ、MACD计算的过程写清楚,包括那些搜索引擎里不会告诉你的坑。

先说结论:tushare pro的日线接口拿前复权数据非常方便,pandas里计算技术指标也就十几二十行代码,但真正让结果变得可靠的关键在于三点——数据是否按时间升序、指标是否用了正确的递推方式、以及你是否理解前复权数据会随着后续除权除息发生历史改变。下面展开讲。

1. 为什么前复权是技术分析的第一步(而非可选项)

1.1 除权除息之后,不复权行情本身就是“断裂”的

A股股票经常发生分红、送转、配股,这些操作会在股权登记日之后让股价出现一个名义上的跳空。比如一只股票10派10元,除息日开盘价会直接低开超过1元。这个跳空不是因为市场突然看空,而是因为公司把一部分价值以现金形式分给了股东。

如果你拿着不复权的K线去算技术指标,这个事件会被当成一次明显的价格下跌,于是均线可能“假跌破”,MACD可能产生一个不存在的死叉,KDJ也会因为价格大幅跳空而进入超卖区。技术分析的前提是价格连续,而复权就是为了抹平这种非交易因素造成的缺口,让历史价格和当前价格处于同一个坐标系里。

1.2 为什么技术分析界面默认用前复权而不是后复权

前复权以最新价格为基准,把历史价格向下调整,后复权以最早价格为基准,把后续价格向上调整。当你打开任何行情软件看K线时,看到的是前复权视角——最近价格是真实的,历史价格是修正过的。

技术指标本身就是基于当前价格形态做判断,所以前复权最贴近看盘时的真实感受。后复权更适合算长期收益率,但它会导致当前价格被缩放得很离谱,均线和MACD的绝对值会和你在行情软件里看到的差很多,不利于技术信号对照。

1.3 tushare里的“adj=qfq”到底是什么

tushare pro的日线接口里,adj参数有三个取值:不复权、qfq(前复权)、hfq(后复权)。如果你调用pro.dailyts.pro_bar时不传这个参数,拿到的就是不复发数据。很多新手在这里踩坑,以为tushare返回的close就是正常收盘价,实际上只有传了adj='qfq'后,才和你在同花顺、通达信里看到的复权K线一致。

2. tushare pro取数链路:从token到可计算的DataFrame

2.1 注册、token和安装依赖

先去tushare官网注册账号,登录后在个人主页里找到token。这个token就是调用接口的钥匙,建议存到环境变量里,不要硬编码到公开项目里。

安装依赖很简单:

pip install tushare pandas numpy

初始化:

import tushare as ts import pandas as pd import numpy as np ts.set_token('你的token') pro = ts.pro_api()

2.2 优先用pro_bar而不是直接pro.daily

tushare里获取日线数据有两种常见写法:

# 方式一:pro.daily,返回数据默认按trade_date降序 df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20241231', adj='qfq') # 方式二:ts.pro_bar,返回数据自动处理复权,且按trade_date升序 df = ts.pro_bar(ts_code='000001.SZ', adj='qfq', start_date='20230101', end_date='20241231')

我更推荐用ts.pro_bar。它内部对日线接口做了封装,adj='qfq'时返回的字段包括trade_date、open、high、low、close、pre_close、change、pct_chg、vol、amount,最关键的是它自动按trade_date升序排列。pro.daily默认是降序的,很多人忘记重新排序,计算rolling(5)的时候等于在倒着算均线,出来的信号全乱了。

2.3 拿到数据后的两件必修课

第一件事是把trade_date转成datetime并设为索引,方便后面的时间对齐和画图:

df['trade_date'] = pd.to_datetime(df['trade_date']) df.set_index('trade_date', inplace=True) df.sort_index(inplace=True)

第二件是注意tushare返回的vol单位是手,不是股。计算换手率或者需要精确成交量的时候,记得先换算。

2.4 建议加一层本地缓存

tushare pro的免费接口有积分权限和调用频率限制,如果你在循环里一次性拉几十只股票,很容易触发限流。我的做法是搞一个简单的本地CSV或parquet缓存,命中缓存就直接读取,不命中再去请求接口:

import os def load_daily(ts_code, start_date, end_date, adj='qfq'): cache_file = f'{ts_code}_{start_date}_{end_date}_{adj}.csv' if os.path.exists(cache_file): df = pd.read_csv(cache_file, parse_dates=['trade_date']) else: df = ts.pro_bar(ts_code=ts_code, adj=adj, start_date=start_date, end_date=end_date) if df is None or df.empty: return pd.DataFrame() df.to_csv(cache_file, index=False) df['trade_date'] = pd.to_datetime(df['trade_date']) df.set_index('trade_date', inplace=True) df.sort_index(inplace=True) return df

缓存能让策略重跑的时候快很多,tushare的日均请求限制也没那么容易触达。

3. 均线计算:从普通MA到EMA再到ZLSMA

3.1 MA是最基础的移动平均

均线说白了就是把过去N根K线的收盘价取平均,用来观察趋势方向。pandas里一行代码:

df['MA5'] = df['close'].rolling(5).mean() df['MA10'] = df['close'].rolling(10).mean() df['MA20'] = df['close'].rolling(20).mean() df['MA60'] = df['close'].rolling(60).mean()

这个没什么好讲的,唯一要注意的是前4行会得到NaN,因为凑不齐5根K线。很多人后续做信号判断时不处理NaN,最后结果里就会混进一些NaN空值。

3.2 EMA的权重逻辑比MA更聪明

MA给每根K线同样的权重,而EMA是指数移动平均,越近的价格权重越大,所以EMA对价格变化更敏感。pandas里的实现:

df['EMA12'] = df['close'].ewm(span=12, adjust=False).mean() df['EMA26'] = df['close'].ewm(span=26, adjust=False).mean()

这里一定要加adjust=False,它表示从第一个有效值开始就直接进入递推,这也是中国行情软件里EMA的标准算法。如果不加,pandas会先对均值做修正,结果和通达信、文华财经里的EMA对不上。

3.3 顺带说说最近常被问到的ZLSMA

ZLSMA(零滞后均线)是最近讨论比较多的自定义均线,核心思路是降低EMA的滞后性。网上流传的ZLSMA公式并不完全统一,有的版本是2*EMA - EMA(EMA)的二次平滑结构,也有版本用了偏移补偿。我在自己工具里用的是这种简化实现:

def zlsma(close, n): ema1 = close.ewm(span=n, adjust=False).mean() ema2 = ema1.ewm(span=n, adjust=False).mean() return 2 * ema1 - ema2

它的想法很简单:普通EMA会有滞后,二次EMA的滞后更大,2*EMA1 - EMA2就是用一个外推的方式把滞后的部分补回来。这个指标不是官方标准化公式,如果你要和行情软件里的ZLSMA严格对照,最好打开软件里的公式编辑器核对一下具体写法,别拿一个简化版就当真。

3.4 多周期均线合并到同一个DataFrame

实际使用中通常会同时算多根均线:

for w in [5, 10, 20, 60]: df[f'MA{w}'] = df['close'].rolling(w).mean() for w in [12, 26]: df[f'EMA{w}'] = df['close'].ewm(span=w, adjust=False).mean()

这样出来的DataFrame每一行就是一天的所有指标快照,做条件筛选非常方便。

4. KDJ:递归公式在pandas里怎么写才不翻车

4.1 KDJ标准公式回顾

KDJ随机指标由RSV、K值、D值、J值四部分组成:

  • RSV = (收盘价 - N日内最低价) / (N日内最高价 - N日内最低价) * 100
  • K = SMA(RSV, M1, 1)
  • D = SMA(K, M2, 1)
  • J = 3 * K - 2 * D

默认参数一般是N=9,M1=3,M2=3。这里最麻烦的是SMA的递推方式,它不是pandas里普通的ewm,而是中国软件里约定的“通达信SMA”,即Y = (M * X + (N - M) * Y') / N

4.2 用ewm实现KDJ的向量化计算

直接看代码:

N, M1, M2 = 9, 3, 3 low_min = df['low'].rolling(N).min() high_max = df['high'].rolling(N).max() df['RSV'] = (df['close'] - low_min) / (high_max - low_min) * 100 # 这里ewm(alpha=1/3) 等价于 SMA(RSV, 3, 1) df['K'] = df['RSV'].ewm(alpha=1/M1, adjust=False).mean() df['D'] = df['K'].ewm(alpha=1/M2, adjust=False).mean() df['J'] = 3 * df['K'] - 2 * df['D']

为什么ewm(alpha=1/3)能等价于通达信的SMA(X, 3, 1)?因为通达信公式Y = (X + 2 * Y') / 3展开后正好是Y = Y' + (X - Y') / 3,也就是alpha=1/3的指数移动平均。理解了这一层,你就不用再用for循环逐行算了。网上很多老代码用循环算KDJ,遇上几千只股票、几万根K线时就非常慢,你换成ewm后速度会快几个量级。

4.3 KDJ计算中容易被忽略的细节

前8行因为rolling(9)还没凑满,RSV是NaN。ewm会从第一个非NaN值开始递推,所以K和D会在第9行附近才有值。你判断KDJ金叉时,一定要等数据积累够了再看,前几周的信号没有参考价值。

还有一点,前复权数据里highlowclose都是调整后的,所以直接用rolling(N)算最高最低没有问题。如果你不小心拿了不复权数据算KDJ,那么除权当天RSV可能就是极端值,K线整个被拉失真。

5. MACD:把DIF、DEA和柱子的关系写明白

5.1 标准MACD公式

MACD指标由三部分组成:

  • DIF = EMA(收盘价, 12) - EMA(收盘价, 26)
  • DEA = EMA(DIF, 9)
  • MACD柱 = 2 * (DIF - DEA)

这里要特别强调,国内行情软件(通达信、文华财经、同花顺)在显示MACD柱子时,普遍会乘以2。你去文华财经里看MACD指标源码,里面写的就是MACD:(DIF-DEA)*2。所以如果你的策略里引用柱体数值,要对应地乘以2,不然和软件上的数值会差一倍。

5.2 pandas里的四行代码

ema12 = df['close'].ewm(span=12, adjust=False).mean() ema26 = df['close'].ewm(span=26, adjust=False).mean() df['DIF'] = ema12 - ema26 df['DEA'] = df['DIF'].ewm(span=9, adjust=False).mean() df['MACD_BAR'] = 2 * (df['DIF'] - df['DEA'])

这段代码看起来简单,但它就是MACD的一切。所谓“邪修版本MACD指标源码”,什么双底、什么变色、什么面积,底层都逃不过这三次EMA计算,剩下的只是对DIF和DEA形态做额外加工。

5.3 金叉死叉与双底识别

判断金叉死叉不能只看当前DIF和DEA的大小关系,要判断“上穿”或“下穿”,需要对比上一根K线:

df['golden_cross'] = (df['DIF'] > df['DEA']) & (df['DIF'].shift(1) <= df['DEA'].shift(1)) df['dead_cross'] = (df['DIF'] < df['DEA']) & (df['DIF'].shift(1) >= df['DEA'].shift(1))

最近很火的“MACD双底信号”,本质是先找DIF在低位连续出现两个低点,第二个低点高于第一个低点,然后等一个金叉确认。你可以这样简单实现:

# 找出DIF局部低点(前后都比它高) local_low = (df['DIF'] < df['DIF'].shift(1)) & (df['DIF'] < df['DIF'].shift(-1)) low_values = df.loc[local_low, 'DIF'] # 两个最近低点,如果第二个更高,就是背离/双底雏形 if len(low_values) >= 2: first_low = low_values.iloc[-2] second_low = low_values.iloc[-1] if second_low > first_low: # 再等一个金叉确认 if df['golden_cross'].iloc[-1]: print('出现双底金叉的潜在信号')

注意,这里我只是在做形态识别演示,不代表选股建议。双底信号在震荡行情里假信号很多,一定要配合其他过滤器,比如均线方向、成交量变化,不能单独拿MACD双底就说要抄底。

6. 组装成一个可复用的技术指标模块,附排坑清单

6.1 一个完整的函数封装

把取数和指标计算合成一个函数,以后每次研究新的股票就不用重复粘贴代码:

def build_indicators(ts_code, start_date, end_date, adj='qfq'): df = load_daily(ts_code, start_date, end_date, adj=adj) if df.empty: return df # 均线 for w in [5, 10, 20, 60]: df[f'MA{w}'] = df['close'].rolling(w).mean() df['EMA12'] = df['close'].ewm(span=12, adjust=False).mean() df['EMA26'] = df['close'].ewm(span=26, adjust=False).mean() # MACD df['DIF'] = df['EMA12'] - df['EMA26'] df['DEA'] = df['DIF'].ewm(span=9, adjust=False).mean() df['MACD_BAR'] = 2 * (df['DIF'] - df['DEA']) # KDJ n, m1, m2 = 9, 3, 3 df['RSV'] = (df['close'] - df['low'].rolling(n).min()) / (df['high'].rolling(n).max() - df['low'].rolling(n).min()) * 100 df['K'] = df['RSV'].ewm(alpha=1/m1, adjust=False).mean() df['D'] = df['K'].ewm(alpha=1/m2, adjust=False).mean() df['J'] = 3 * df['K'] - 2 * df['D'] return df

调用方式:

df = build_indicators('000001.SZ', '20240101', '20241231') print(df[['close', 'MA5', 'MA20', 'K', 'D', 'J', 'DIF', 'DEA', 'MACD_BAR']].tail(10))

6.2 排坑清单:这些坑我一个个踩过

坑一:数据没按时间升序

如果你用pro.daily而非pro_bar,默认返回结果是按trade_date降序的。直接对这样的DataFrame用rolling(5),相当于从最近一天往前数5天,等于把均线方向完全算反。我踩过这个坑后,所有取数函数里一律强制sort_values('trade_date')

坑二:前N行NaN没清理

MA20的前19行、KDJ的前8行都是NaN。如果后面做df['close'] > df['MA20']这样的筛选,NaN比较会返回False,导致这些天不会产生任何信号。如果要做回测,最好显式地df.dropna(subset=['MA20', 'K', 'D', 'DIF']),把指标未稳定阶段剔掉。

坑三:别在前复权数据上做历史回测

前复权价格会随着未来新的除权除息而整体平移。你今天拉到的2020年历史数据和去年拉到的2020年历史数据,价格可能完全不一样。所以如果要做严肃的历史回测,最好用后复权,或者把每次取数的复权基准日固定下来。但如果你只是做当前行情的技术信号判断,前复权完全够用,而且和你的行情软件最接近。

坑四:小心未来函数

所谓未来函数,就是某一根K线的计算结果包含了这根K线之后的信息。比如你在判断5日均线时用rolling(5).mean().shift(-1),那就用到了后面一天的数据,这是典型的未来函数。再比如,有人看到MACD金叉后想确认“第二天没有反转”,于是把第二天的数据也放进信号里,这在回测里会严重高估策略胜率。我的原则很简单:一切指标计算禁止shift(-1),禁止fillna(method='bfill'),信号只能在当前K线收盘后确定。

6.3 一些个人使用心得

我实际跑下来,tushare pro的日线接口稳定性不错,但免费额度不是无限量,尤其盘中高频取数会被限流。建议在本地维护一个按交易日更新的数据仓库,每天收盘后批量更新一次,别在盘中反复拉取同一只股票。

指标计算用pandas的向量化操作后,一个几百只股票的小组合全量扫描也就几秒钟。真正耗时的反而是数据IO和格式化,所以缓存策略很重要。

最后说一句我自己这几年总结下来的体会:技术指标公式本身从来不是秘密,MA、KDJ、MACD任何一个都开源到烂大街了,真正拉开差距的是你对数据的理解——复权怎么处理、数据怎么对齐、未来函数怎么避免。把这三点吃透,你写出来的指标分析代码才敢真正拿去做决策参考。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询