与 TA-Lib、ta 的交叉验证
finta 和 TA-Lib 的结果为什么不一样:逐指标对照与差异归因
本站把 finta 1.3、ta 0.11.0 与 TA-Lib 0.8.1 装在同一个环境里,
用同一份 874 行 OHLCV、同样的周期参数逐个指标对比。
结果是:SMA、Stochastic、Williams %R、CCI(20)、MFI 五组完全一致(最大差 0.000000);
而布林带、ATR、ADX 有系统性差异,且每一个都能定位到源码里的具体口径——"
"布林带是样本标准差与总体标准差之差(比值恒为 √(20/19)),ATR 是简单均值与 Wilder 平滑之差,"
"ADX 的分歧最大(与 ta 的相关系数只有 0.6361)。
_evidence/cross_library.txt)整理的对照示意;不是官方兼容性声明。FinTA · 对照表
三库逐指标对照:哪些一致、差多少
| 指标(周期) | finta vs ta 最大差 | finta vs TA-Lib 最大差 | 相关系数 | 结论 |
|---|---|---|---|---|
| SMA(14) | 0.000000 | 0.000000 | 1.000000 | 三库完全一致 |
| Stochastic %K(14) | —(与 TA-Lib 比) | 0.000000 | 1.000000 | 与 TA-Lib、与手写公式都完全一致 |
| Williams %R(14) | — | 0.000000 | 1.000000 | 与 TA-Lib、与手写公式都完全一致 |
| CCI(20) | — | 0.000000(同为 20 期) | 1.000000 | 期数一致时完全一致;TA-Lib 常用 14 期,那时最大差 223.23 |
| MFI(14) | — | 0.000000 | 1.000000 | 数值完全一致,只差 1 行预热长度(13 vs 14) |
| OBV | 4.854918 | 0.000304 | 1.000000 | 与 TA-Lib 基本一致;ta 的平盘处理不同 |
| EMA(20) | 2.107417 | 2.245449 | 1.000000 | 只在前 1/3 有差,中后段为 0 |
| RSI(14) | 0.000000 | 4.740213 | 0.999759 | 与 ta 完全一致;与 TA-Lib 只差预热段 |
| MACD(12,26,9) | 1.366540 | 4.045252 | 0.999999 | 收敛后一致;但 FinTA 不返回柱状线 |
| 布林带 UPPER(20,2) | 160.060692 | 160.060692 | 0.999998 | 样本标准差 vs 总体标准差 |
| ATR(14) | 561.693027 | 561.693027 | 0.988654 | 简单均值 vs Wilder 平滑 |
| ADX(14) | 100.000000(ta 有极值行) | 14.507540 | 0.932113(vs TA-Lib) | 分歧最大的一类 |
读法:先看「最大差」,但不要只看它。差 160 的布林带与差 561 的 ATR 都是系统性口径差(能解释、能复现、比值固定),而 RSI 的 4.74 只出现在预热段、之后为 0(属收敛过程)。真正需要警惕的是那种「差得没规律、比值不固定」的情况——本站没有测到这种。
FinTA · 完全一致
哪些指标可以直接互换使用
| 指标 | 为什么能完全一致 | 能放心互换吗 | 注意点 |
|---|---|---|---|
| SMA | 算法没有自由度:rolling(14).mean() 只有一种实现 | 可以 | 分期数与数据行序必须一致 |
| Stochastic %K(快线) | 就是 14 期最高最低区间上的归一化,公式固定 | 可以 | 慢线/平滑版本才有自由度,对比时要说明是哪种 %K |
| Williams %R | 同一区间的补数(−100~0),也是固定公式 | 可以 | 注意正负号方向:FinTA 与 TA-Lib 输出的都是负值 |
| CCI(期数相同时) | 均差绝对值 × 常数 0.015,公式只有一种写法 | 可以 | 期数必须显式对齐——FinTA 默认 20,TA-Lib 示例常用 14 |
| MFI(14) | 典型价格 × 成交量的正负资金流比值,公式只有一种写法 | 可以 | 只差 1 行预热(FinTA 13 行、TA-Lib 14 行) |
这张表的价值在于:它把「该不该怀疑」这件事变成可操作的。如果你的 SMA 与另一个库不一致,那几乎不可能是算法问题,应该回去查数据(行序、列、行数);而如果你的布林带与另一个库不一致,那多半不是谁错了,只是口径不同。
FinTA · 布林带归因
布林带差多少?为什么比值是固定的
# 线性验证:FinTA 的半宽到底等于 2 倍的哪一种标准差 half = TA.BBANDS(ohlc, 20)['BB_UPPER'] - TA.BBANDS(ohlc, 20)['BB_MIDDLE'] print((half - 2 * ohlc['close'].rolling(20).std(ddof=0)).abs().max()) # 160.060692 ← 与总体标准差(ddof=0)差得很明显 print((half - 2 * ohlc['close'].rolling(20).std(ddof=1)).abs().max()) # 0.000000 ← 与样本标准差(ddof=1)完全一致 # 比值检验(最后一根): # FinTA 半宽 1102.2471 ; TA-Lib 半宽 1074.3376 # 1102.2471 / 1074.3376 = 1.02598 # sqrt(20/(20-1)) = 1.02598 ← 完全一致
| 项目 | FinTA | TA-Lib / ta | 说明 |
|---|---|---|---|
| 中轨(20 期均线) | 完全一致 | 完全一致 | 中轨没有分歧,说明差异只来自标准差 |
| 半宽(最后一根) | 1102.2471 | 1074.3376 | 比值 1.02598 |
| 宽度比值 | — | — | √(20/19) = 1.02598,与实测完全吻合——理论值与实测双向印证 |
| 上轨(最后一根) | 8212.7979 | 8184.8884 | 差 27.91 |
| 最大绝对差(全序列) | — | — | 160.06(出现在波动较大的区间) |
这个例子最值得学的地方是验证方法:不靠「看起来差不多」,而是先猜口径(ddof),再用理论比值(√(N/(N−1)))去核对实测比值。两者吻合到小数点后五位,结论就从「可能是」变成「就是」。本站的立场:这不是 bug,是两种都合理的统计口径——但你必须在研究记录里写明用了哪种。
FinTA · ATR 归因
FinTA 的 ATR 和 TA-Lib 有什么区别?差在简单均值还是 Wilder
| 对照对象 | 最大绝对差 | 结论 |
|---|---|---|
| FinTA vs 手算「True Range 的 14 期简单均值」 | 0.000000 | FinTA 的 ATR 就是这个定义——完全一致 |
| FinTA vs 手算「TR 的 Wilder 递推(α=1/14)」 | 561.693027 | 换一种平滑方式就差出量级:最后一根 328.57 vs 336.40 |
| FinTA vs TA-Lib / ta 的 ATR | 561.693027 | TA-Lib 与 ta 用的是 Wilder 口径,所以两者彼此一致、与 FinTA 不一致 |
# 两种 ATR 口径的差,从代码上只差一个平滑函数
tr = pd.concat([(ohlc.high - ohlc.low),
(ohlc.high - ohlc.close.shift()).abs(),
(ohlc.low - ohlc.close.shift()).abs()], axis=1).max(axis=1)
finta_like = tr.rolling(14).mean() # ← FinTA 的 ATR
wilder = tr.ewm(alpha=1/14, adjust=False).mean() # ← TA-Lib / ta 的 ATR
print(float(TA.ATR(ohlc, 14).iloc[-1])) # 328.568904 ← 与 finta_like 完全一致
print(float(finta_like.iloc[-1])) # 328.568904
print(float(wilder.iloc[-1])) # 336.4... ← 与 TA-Lib 一致
# 两者的差距在波动放大的区间最大(最大差 561.69),在平稳区间很小
这个差异有实际后果:ATR 常被用来设止损距离(例如 2 × ATR)。同样叫「2 倍 ATR」,用 FinTA 和用 TA-Lib 会在波动大的行情里给出不同的止损位。所以换库时不能只改函数名,必须重新校准参数——这一点在选型与迁移页有专门一节。
FinTA · ADX 归因
分歧最大的 ADX:为什么 adjust 也对不齐
| 对照 | 最大绝对差 | 相关系数 | 最后一根 | 说明 |
|---|---|---|---|---|
| finta vs TA-Lib | 14.507540 | 0.932113 | 46.4395 vs 44.7657 | 方向一致、数值系统性偏高 |
| finta vs ta | 100.000000 | 0.636135 | 46.4395 vs 44.7657 | ta 的极值行把最大差放大到 100,相关系数掉到 0.64 |
finta(adjust=False) vs TA-Lib | 30.541466 | 0.906972 | 46.4395 vs 44.7657 | 改开关后更差——说明分歧不只来自 ewm 的归一化方式 |
| 实现 | 首个有效行 | NaN 数 | 差异来源 |
|---|---|---|---|
| finta | 13 | 13 | DMI 差值经 ewm,起步很早 |
| ta | 0 | 0 | 完全不给 NaN——早期值是「半成品」,与另两库不可比 |
| TA-Lib | 27 | 27 | Wilder 递推要求两倍周期还多,是最保守的一个 |
ADX 是本次对照里既差在预热、又差在算法、还无法用开关对齐的那一个。实践建议只有一句:ADX 相关的阈值(例如「ADX > 25 视为趋势」)不能跨库搬运,换库后必须用自己的数据重新看分布,而不是沿用旧参数。
FinTA · 自己做一遍
怎么在自己的数据上做交叉验证
五步做交叉验证
pip install "finta==1.3" ta TA-Lib。本站实测在 Python 3.11.9 + numpy 1.26.4 + pandas 1.5.3 上一次装成(FinTA 需要旧 pandas 才能发挥全部方法,见安装页)。index.is_monotonic_increasing——否则你会同时测出「库差异」和「行序错误」两个变量。# 最小交叉验证脚本(结构与本站实测一致)
import numpy as np, pandas as pd, talib, ta
from finta import TA
ohlc = pd.read_csv('ohlc.csv', index_col='date', parse_dates=True).sort_index()
assert ohlc.index.is_monotonic_increasing # 先排除行序问题
c, h, l, v = ohlc['close'], ohlc['high'], ohlc['low'], ohlc['volume']
def compare(a, b, label):
a = pd.Series(np.asarray(a, float), index=ohlc.index)
b = pd.Series(np.asarray(b, float), index=ohlc.index)
m = a.notna() & b.notna()
d = (a[m] - b[m]).abs()
print('%-24s n=%3d max=%10.6f mean=%9.6f corr=%.6f'
% (label, m.sum(), d.max(), d.mean(), a[m].corr(b[m])))
compare(TA.SMA(ohlc, 14), talib.SMA(c.values, timeperiod=14), 'SMA14 finta vs talib')
compare(TA.RSI(ohlc), talib.RSI(c.values, timeperiod=14), 'RSI14 finta vs talib')
compare(TA.ATR(ohlc), talib.ATR(h.values, l.values, c.values, 14),'ATR14 finta vs talib')
compare(TA.BBANDS(ohlc)['BB_UPPER'],
talib.BBANDS(c.values, timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)[0],
'BB_UP finta vs talib')
# 本站实测输出:
# SMA14 finta vs talib max= 0.000000 corr=1.000000
# RSI14 finta vs talib max= 4.740213 corr=0.999759
# ATR14 finta vs talib max=561.693027 corr=0.988654
# BB_UP finta vs talib max=160.060692 corr=0.999998
顺带说明一个容易忽略的底气:上游自己也做过这件事——仓库的 requirements-test.txt 里列着 TA-Lib,.travis.yml 里有一段从源码编译 ta-lib 0.4.0 的脚本。只是仓库已归档、CI 停跑,这套对照现在只能自己重做。
FAQ
关于 finta 数值差异的常见问题
finta 和 TA-Lib 算出来不一样,是哪个错了?
ta 的相关系数只有 0.6361。布林带的差值为什么是固定的倍数?
std(ddof=1) = std(ddof=0) × √(N/(N−1))。本站用半宽做了线性验证:FinTA 的半宽与 2 × rolling.std(ddof=1) 完全一致,与 TA-Lib 的比值实测 1.02598,理论值 √(20/19) = 1.02598——两者吻合到小数点后五位。所以这不是随机噪声,是可以精确换算的口径差。ATR 差得那么多,用哪个做止损?
ta 用 Wilder 递推;本站实测最后一根分别是 328.57 与 336.40,波动大的区间最大差到 561.69。换库时必须重新校准倍数——同样写「2 倍 ATR」,在不同库里对应的是不同的止损距离。本站不给交易建议。RSI 的差异为什么只出现在前面?
33.386806。差异来自起步阶段的播种与归一化方式,属于预热差异而不是算法分歧。adjust=False 能让结果对齐吗?
EMA 改成 False 后与 ta 完全一致;但 RSI 改成 False 后与 TA-Lib 的最大差从 4.74 涨到 23.35;ADX 从 14.51 涨到 30.54。建议先保留默认值,做过对照再针对单个指标调整。这套对照数据是哪来的?可以复现吗?
tests/data/bittrex_btc-usdt.csv(875 行,2015-12-12 至 2018-06-18),三库分别是 finta 1.3、ta 0.11.0、TA-Lib 0.8.1,运行在 Python 3.11.9 + numpy 1.26.4 + pandas 1.5.3。本页给出了完整脚本,可原样复跑;原始读数保存在站点的证据目录里。换数据或换周期时具体差值会变,但口径差异的方向不变。相关阅读:替代库选型、源码公式与预热。本页结论绑定 2026-10-12 的实测环境(Python 3.11.9),数值口径以官方源码为准。