与 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)。

874 行同数据5 项差 03 类系统性差异核验 2026-10-12
完全一致:5 组SMA / Stochastic / Williams %R / CCI(20) / MFI
差在口径:布林带、ATR样本标准差 vs 总体标准差;简单均值 vs Wilder
分歧最大:ADX与 ta 相关系数 0.6361,adjust 开关无法对齐
依据同一份 CSV、同一周期、三库并跑(_evidence/cross_library.txt)整理的对照示意;不是官方兼容性声明。

FinTA · 对照表

三库逐指标对照:哪些一致、差多少

表 1:同数据同周期实测(finta 1.3 / ta 0.11.0 / TA-Lib 0.8.1,n=874)
指标(周期)finta vs ta 最大差finta vs TA-Lib 最大差相关系数结论
SMA(14)0.0000000.0000001.000000三库完全一致
Stochastic %K(14)—(与 TA-Lib 比)0.0000001.000000与 TA-Lib、与手写公式都完全一致
Williams %R(14)—0.0000001.000000与 TA-Lib、与手写公式都完全一致
CCI(20)—0.000000(同为 20 期)1.000000期数一致时完全一致;TA-Lib 常用 14 期,那时最大差 223.23
MFI(14)—0.0000001.000000数值完全一致,只差 1 行预热长度(13 vs 14)
OBV4.8549180.0003041.000000与 TA-Lib 基本一致;ta 的平盘处理不同
EMA(20)2.1074172.2454491.000000只在前 1/3 有差,中后段为 0
RSI(14)0.0000004.7402130.999759与 ta 完全一致;与 TA-Lib 只差预热段
MACD(12,26,9)1.3665404.0452520.999999收敛后一致;但 FinTA 不返回柱状线
布林带 UPPER(20,2)160.060692160.0606920.999998样本标准差 vs 总体标准差
ATR(14)561.693027561.6930270.988654简单均值 vs Wilder 平滑
ADX(14)100.000000(ta 有极值行)14.5075400.932113(vs TA-Lib)分歧最大的一类

读法:先看「最大差」,但不要只看它。差 160 的布林带与差 561 的 ATR 都是系统性口径差(能解释、能复现、比值固定),而 RSI 的 4.74 只出现在预热段、之后为 0(属收敛过程)。真正需要警惕的是那种「差得没规律、比值不固定」的情况——本站没有测到这种。

FinTA · 完全一致

哪些指标可以直接互换使用

表 2:三库完全一致的指标,以及背后的原因
指标为什么能完全一致能放心互换吗注意点
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   ← 完全一致
表 3:布林带差异的量化
项目FinTATA-Lib / ta说明
中轨(20 期均线)完全一致完全一致中轨没有分歧,说明差异只来自标准差
半宽(最后一根)1102.24711074.3376比值 1.02598
宽度比值——√(20/19) = 1.02598,与实测完全吻合——理论值与实测双向印证
上轨(最后一根)8212.79798184.8884差 27.91
最大绝对差(全序列)——160.06(出现在波动较大的区间)

这个例子最值得学的地方是验证方法:不靠「看起来差不多」,而是先猜口径(ddof),再用理论比值(√(N/(N−1)))去核对实测比值。两者吻合到小数点后五位,结论就从「可能是」变成「就是」。本站的立场:这不是 bug,是两种都合理的统计口径——但你必须在研究记录里写明用了哪种。

FinTA · ATR 归因

FinTA 的 ATR 和 TA-Lib 有什么区别?差在简单均值还是 Wilder

表 4:ATR(14) 的差异分解
对照对象最大绝对差结论
FinTA vs 手算「True Range 的 14 期简单均值」0.000000FinTA 的 ATR 就是这个定义——完全一致
FinTA vs 手算「TR 的 Wilder 递推(α=1/14)」561.693027换一种平滑方式就差出量级:最后一根 328.57 vs 336.40
FinTA vs TA-Lib / ta 的 ATR561.693027TA-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 也对不齐

表 5:ADX(14) 的三种对照
对照最大绝对差相关系数最后一根说明
finta vs TA-Lib14.5075400.93211346.4395 vs 44.7657方向一致、数值系统性偏高
finta vs ta100.0000000.63613546.4395 vs 44.7657ta 的极值行把最大差放大到 100,相关系数掉到 0.64
finta(adjust=False) vs TA-Lib30.5414660.90697246.4395 vs 44.7657改开关后更差——说明分歧不只来自 ewm 的归一化方式
表 6:预热长度的三方对照(同一个 ADX(14))
实现首个有效行NaN 数差异来源
finta1313DMI 差值经 ewm,起步很早
ta00完全不给 NaN——早期值是「半成品」,与另两库不可比
TA-Lib2727Wilder 递推要求两倍周期还多,是最保守的一个

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 才能发挥全部方法,见安装页)。
  • 准备一份已排序的 OHLCV:至少 100 行,索引为 DatetimeIndex,并断言 index.is_monotonic_increasing——否则你会同时测出「库差异」和「行序错误」两个变量。
  • 把每个指标的值按列对齐:注意不同库返回 Series 还是 DataFrame、列名不同、预热长度不同。建议统一转成一张对照表再比,避免拿错列。
  • 比较三个量而不是一个:最大绝对差(看极端偏离)、平均绝对差(看整体)、相关系数(看形状是否一致)。只看其中一个容易被极值误导。
  • 定位到源码口径再下结论:如果差是固定倍数,去查标准差/归一化口径;如果差集中在序列前段,是预热差异;如果差没规律,才需要怀疑实现有 bug。
  • # 最小交叉验证脚本(结构与本站实测一致)
    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 算出来不一样,是哪个错了?
    多数情况都不是错,而是口径不同。本站逐指标实测:SMA、Stochastic、Williams %R、CCI(期数一致时)、MFI 五组完全一致(最大差 0.000000);布林带差在标准差口径(FinTA 用样本 ddof=1,TA-Lib 用总体 ddof=0,比值恒为 √(20/19));ATR 差在平滑方式(FinTA 用 True Range 的简单均值,TA-Lib 用 Wilder 递推);ADX 的分歧最大,与 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 差得那么多,用哪个做止损?
    取决于你要和谁对齐。FinTA 的 ATR 等于「True Range 的 14 期简单均值」(与手算完全一致),TA-Lib 与 ta 用 Wilder 递推;本站实测最后一根分别是 328.57 与 336.40,波动大的区间最大差到 561.69。换库时必须重新校准倍数——同样写「2 倍 ATR」,在不同库里对应的是不同的止损距离。本站不给交易建议。
    RSI 的差异为什么只出现在前面?
    因为指数平滑会收敛。本站实测 FinTA 与 TA-Lib 的 RSI(14):前 1/3 最大差 4.740213,中段与末段的差都是 0.000000,最后一根三库都是 33.386806。差异来自起步阶段的播种与归一化方式,属于预热差异而不是算法分歧。
    adjust=False 能让结果对齐吗?
    看指标,不能统一改。本站实测:EMA 改成 False 后与 ta 完全一致;但 RSI 改成 False 后与 TA-Lib 的最大差从 4.74 涨到 23.35;ADX 从 14.51 涨到 30.54。建议先保留默认值,做过对照再针对单个指标调整。
    这套对照数据是哪来的?可以复现吗?
    数据用的是 FinTA 仓库自带的 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),数值口径以官方源码为准。

    接下来读哪一页?

    数值对上了,接下来要回答的是「这些指标能不能直接当买卖信号」——指标不是策略页给出从指标到策略评估缺的每一步,以及未来函数、成本、T+1 的具体检查清单。