数据契约与索引顺序

finta 的数据格式:列名其实不挑大小写,但行序错了会算出未来数据

关于输入,README 只说了一句「列名需要小写」。本站实测发现这句偏严—— inputvalidator 装饰器会先把列名统一转小写,因此 Close、HIGH 这类写法都能正常计算。

真正致命的是 README 没写的另一件事:FinTA 按行序(row order)计算,完全不读 DatetimeIndex 的先后。把同一份数据按时间倒序排列后,2017-06-10 的 SMA(14) 从 2471.232500 变成 2612.512786,而后者正好等于 「当天到未来第 13 根 K 线」的收盘均值——最新在前的数据会把未来数据算进当天。

列名不挑大小写缺列抛 LookupError行序决定结果不看日期先后
列名:自动转小写Close / HIGH 都能算,缺列抛 LookupError
行序:决定计算结果不读日期先后,只按行的位置滚动
倒序 = 未来函数当天的值会用到 D+1…D+13 的 K 线
依据 finta/finta.py 中的 inputvalidator 装饰器与本机顺序对照实验(_evidence/order_pandas1.txt)整理的数据契约示意;非官方插图。

FinTA · 列名契约

列名到底要不要小写?缺列会报什么错

表 1:列名规则的实测结果
输入情况实测结果说明
标准小写列名 open/high/low/close/volume正常README 推荐的写法
全大写列名 OPEN/HIGH/LOW/CLOSE/VOLUME正常,SMA(14) 结果与标准写法完全一致(6922.3392206307135)装饰器在调用前把每一列名 .lower(),因此大小写不敏感
混合大小写(只改 Close、High)正常,结果同上同上
缺 closeLookupError: Must have a dataframe column named "close"只报第一个缺的列名,不会一次列全;修饰器按 o→h→l→c→v 的顺序检查
缺 high(但 OHLC 类指标只需 close)仍然报错凡是使用 @inputvalidator("ohlc") 的方法都要求四列齐全
缺 volume,调用 OHLC 类方法(如 TA.SMA)正常只有 @inputvalidator("ohlcv") 的方法才要求 volume
缺 volume,调用 OHLCV 类方法(如 TA.OBV、TA.VWAP)LookupError: Must have a dataframe column named "volume"错误信息里只有列名,没有方法名,排查时要结合 traceback
第一个参数不是 DataFrameAttributeError: 'int' object has no attribute 'rename'装饰器用 isinstance(args[0], DataFrame) 猜数据参数的位置,猜错时报错信息与数据无关

所以真正的要求是「列名能被转成小写后匹配」,而不是「必须手写小写」。但本站仍建议统一成小写——因为 TA.SMA(ohlc, column='close') 这类调用会把你传的列名也一起转小写,两种风格混用时容易对不上。

FinTA · 必需列

哪些方法要 volume、哪些只要 OHLC

表 2:按输入需求给方法分组
输入要求代表方法数量级缺列时的表现
只要 open/high/low/closeSMA、EMA、RSI、MACD、BBANDS、ATR、ADX、STOCH、WILLIAMS、CCI、CCI 族、KC、DO、ICHIMOKU…约 70 个缺任何一列都抛 LookupError
需要 volumeOBV、WOBV、ADL、CHAIKIN、MFI、VZO、PZO、EFI、CFI、EBBP、EMV、TMF、VPT、FVE、VFI、VWAP、VAMA、EVWMA、EV_MACD、VW_MACD、VIDYA约 20 个缺 volume 抛 LookupError;其余四列仍要齐
可以用 column= 指定别的价格列TA.SMA(ohlc, 14, column='high') 实测可用(最后一根 7103.111206626428)带 column 参数的方法指定不存在的列名同样抛 LookupError
# 最小可用的数据准备(实测通过)
import pandas as pd
from finta import TA

df = pd.read_csv('ohlc.csv')                 # 至少要有 open/high/low/close
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date').sort_index()       # ← 关键一步:先按时间正序排好
df.columns = [c.lower() for c in df.columns] # 可选:统一小写,便于阅读

print(df.index.is_monotonic_increasing)      # 必须是 True,否则先 sort_index()
print(TA.SMA(df, 14).isna().sum())           # 实测:13 —— 前 13 行是预热

第三行 sort_index() 是整页最重要的一步。FinTA 不会替你排序,也不会在顺序不对时给出任何警告——它只是安静地按行滚动,然后给你一个看起来正常的数字。下一节说明这个数字错在哪里。

FinTA · 行序陷阱

行序反了会怎样?可复现的实测数值

表 3:同一份 874 行数据,只把时间顺序倒过来,同一个日期(2017-06-10)的指标值
指标正序(旧→新)倒序(新→旧)差值
SMA(14)2471.2325002612.512786+141.28
SMMA(14)2287.5264652591.918313+304.39
EMA(20)2399.7250622595.150177+195.43
WMA(14)2610.5524002627.020181+16.47
RSI(14)69.63414157.314342−12.32
ATR(14)249.606173334.969890+85.36
BBANDS 上轨3007.9461802857.227605−150.72
MACD257.60757041.307208−216.30
OBV4620.13−272477.49符号都变了
# 证明「倒序的结果 = 用了未来数据」
asc  = ohlc.sort_index()                  # 正序
desc = ohlc.sort_index(ascending=False)   # 倒序(很多行情接口的默认形态)

print(TA.SMA(asc,  14).loc['2017-06-10'])   # 2471.232500  ← 当天往前 14 根
print(TA.SMA(desc, 14).loc['2017-06-10'])   # 2612.512786  ← 当天往后 14 根(未来!)

# 2612.512786 正好等于用正序数据算「当天到 D+13」这 14 根的收盘均值:
i = asc.index.get_loc('2017-06-10')
print(asc['close'].iloc[i:i+14].mean())     # 2612.512786  ← 完全一致

# 另外两个判据:
x = asc.copy(); x.index = x.index[::-1]     # 只把索引标签反向、行序不动
print(TA.SMA(x, 14).iloc[i])                # 2471.232500  ← 不变,证明看重的是「行序」
z = asc.sample(frac=1.0, random_state=0)    # 打乱行序
print(TA.SMA(z, 14).iloc[i])                # 3769.49...   ← 变成无意义的数,且不会报错

这是本站相对官方文档最大的增量,也是「指标看起来有效」最常见的成因。国内不少行情接口(尤其网页端与部分 SDK)返回的是「最新一行在前」,直接 pd.DataFrame(rows) 就落进这个坑:算出来的 RSI 低点恰好出现在价格低点——因为那一天的 RSI 本来就用了后面 13 天的数据。处置只有一句:传进去之前 df.sort_index(),并用 is_monotonic_increasing 断言。

FinTA · 边界情形

停牌、缺口、重复日期会怎样影响结果?四类异常实测

表 4:四类数据异常在 FinTA 下的实测表现
情形怎么造FinTA 的表现风险
中间插入全 NaN 的 K 线(模拟停牌补齐)把 200–209 行的 OHLC 全设为 NaNSMA(14) 输出里共 36 个 NaN(10 行自身 + 滑过缺口的 24 行窗口);但 RSI(14) 只有 1 个 NaN——RSI 用 ewm 平滑,在无数据的行上仍会输出数值RSI 的曲线在停牌段看起来是连续的,容易被误读
中间直接删行删掉第 100–119 行不报错,继续算,最后一根的结果与完整数据一致缺口不会被检测;如果删掉的是关键行,中间段指标会悄悄偏移
重复时间戳把最后一行再 append 一次(875 行)不报错,按 875 行计算,最后一根 SMA 由 6922.339221 变成 6837.227305重复行会被当成一根新 K 线,直接改变结果
只有 RangeIndex、没有日期df.reset_index(drop=True)正常工作,结果与日期索引版完全一致说明 FinTA 只关心行的顺序,完全不需要时间信息

把四条合起来看:FinTA 不对数据质量做任何检查——不排序、不去重、不检测缺口、不因 NaN 报错。它假设你已经把数据整理好了。所以在接数据这一步,「先自己做一次数据体检」比「调对参数」重要得多。

FinTA · A 股适配

A 股数据传给 FinTA 要注意什么

表 5:A 股数据落地的六个口径问题
问题为什么重要怎么处理
复权口径前复权 / 后复权 / 不复权的收盘价不同,算出的指标自然不同;跨除权日的均线会跳空同一份研究里固定一种口径;跨除权日的区间要在结论里声明口径,不要把三种混用
停牌日怎么表示停牌当天没有成交,有的数据源补一行 0 成交量的记录,有的是直接缺失如果补了行,指标会把它当成真实价格波动;建议保留缺失并在指标段单独标注(注意 RSI 在 NaN 行仍会输出数值)
涨跌停与一字板涨跌停当天的 high=low=open=close,会让 ATR、布林带宽度等波动率类指标被低估在结论里区分「含涨跌停日」与「剔除后的」两组,或用更长周期摊平
T+1 与收盘价成交假设指标用当日收盘价计算,但当天买入不能当天卖出;把收盘价当成交价在回测里会高估收益回测时把成交价挪到下一个可交易日,并显式写出这个假设
成交量 vs 成交额FinTA 的 OBV、VWAP、MFI 等只看 volume 字段的数值,不知道单位统一用「手」或「股」,并在项目里写清楚;混用单位会让 OBV 的量级失去可比性
交易日历与缺口A 股有长假与调休,日线序列里会出现跨周缺口,滚动窗口会跨过缺口明白窗口是「按行数」而不是「按自然日」;要按自然日对齐需自己重采样

需要强调的是:FinTA 本身不提供任何行情数据,也不做复权处理。上面每一条都是「你怎么把数据整理成它要的样子」的问题,不是它的功能。取数与复权要由你自己的数据源负责。

FinTA · 数据源转换

从常见数据源转到 FinTA 要改什么

五步把任意数据源接到 FinTA

  • 先把时间列转成索引并排序:df['date'] = pd.to_datetime(df['date'])、df = df.set_index('date').sort_index(),再断言 is_monotonic_increasing 为 True。这一步能挡掉本页最常见的那个坑。
  • 把列名统一成 open/high/low/close/volume:中文数据源常见「开盘/最高/最低/收盘/成交量」,英文源常见 Open/High/Low/Close/Volume(FinTA 能自动转小写,但统一之后可读性更好)。
  • 处理多级列索引:某些数据源(尤其一次取多只标的时)会返回 MultiIndex 列,需要先 droplevel 或单只取一层,否则列名匹配不上、直接抛 LookupError。
  • 确认数值列是数值型:带千分位或货币符号的字符串列要先 replace + astype(float),否则滚动计算会静默失败或报类型错误。
  • 最后跑一次最小验收:用 TA.SMA(df, 14).isna().sum() 应为 13,再用一个已知的简单均线手算值对照——确认数据真的接对了,再开始做指标。
  • 表 6:常见来源的转换要点
    数据来源典型形态要改什么特别提醒
    CSV 文件(本地或下载)日期是一列字符串,数值列可能带引号或千分位转日期 → 设索引 → 排序 → 统一列名 → astype(float)行序取决于文件写法,必须显式排序
    一次取多只标的的宽表列变成 MultiIndex(字段 × 代码)或代码前缀列名先按标的分出单表,droplevel 后重命名列名匹配不上时报 LookupError,信息里只有列名
    网页/接口返回的 JSON 列表常见的顺序是「最新一行在最前」构造 DataFrame 后必须排序这正是本页第 3 节那个未来函数坑的高发场景
    带复权因子的数据同一标的有前复权 / 后复权两套收盘价选定一套,并把口径写进研究记录换口径后全部指标都会变,跨口径比较没有意义
    加密/外盘 7×24 数据没有停牌,但有交易所维护窗口与跳空按需要重采样成固定周期(FinTA 自带 utils.resample)重采样后行数变化,预热窗口的行数含义也随之变化

    FAQ

    关于 finta 数据契约的常见问题

    FinTA 要求列名必须小写吗?
    实测不要求。源码里的 inputvalidator 装饰器会先把所有列名转成小写再匹配,所以 Close、HIGH、混合大小写都能正常计算,结果与标准小写写法完全一致。但统一小写仍然值得做——因为你传 column= 参数时也会被转小写,两种风格混用容易对不上。
    报 LookupError: Must have a dataframe column named "close" 怎么办?
    先 print(df.columns.tolist()) 看实际列名。常见原因是列名带空格、带单位后缀、或来自多级列索引没有降级。注意这个错误只报第一个缺的列,修完 close 可能还会提示 high——按 o→h→l→c→v 顺序逐个补。
    为什么算出来的指标和行情软件不一样?
    本站实测到三个可量化的原因:① 数据行序——最新在前的数据会让当天指标用上未来 K 线(SMA14 从 2471.23 变 2612.51);② 复权口径——前复权与不复权在不同除权日会有系统性偏离;③ 算法口径——布林带用样本标准差、ATR 用简单均值,与 TA-Lib 类的软件不同。逐条对照见交叉验证页。
    数据里最新的日期在最前面,直接传给 FinTA 会怎样?
    会静默算错:它不排序也不报警。实测把同一份数据倒序后,2017-06-10 的 SMA(14) 变成 2612.512786,这个数正好等于「当天到未来第 13 根」的均值——也就是把未来数据用上了。传进去之前务必 df.sort_index(),并断言 index.is_monotonic_increasing。
    停牌日要怎么处理?
    FinTA 不会替你判断。实测两种情况都提醒一下:补 NaN 行时 SMA 会出现成片 NaN,但 RSI 在 NaN 行上仍会输出数值(因为它用 ewm 平滑);直接删行时不会报错,缺口会被悄悄跳过。建议在你的研究记录里明确写出停牌的处理方式,并把它当成结论的一部分。
    FinTA 能自己取 A 股行情吗?
    不能。它没有任何行情接口,也不做复权。「FinTA 能不能用 A 股」这个问题等价于「你能不能把 A 股数据整理成 open/high/low/close(/volume) 五列并按时间正序排好」。A 股特有的复权、停牌、涨跌停、T+1 口径问题见上方 A 股适配表。

    相关阅读:源码公式与预热、指标不是策略。本页结论绑定 2026-10-12 的实测环境(Python 3.11.9),数值口径以官方源码为准。

    接下来读哪一页?

    数据接对之后,下一个问题是「为什么前几行是 NaN、返回的列叫什么」——源码公式页给出逐个指标的预热长度、返回列名与平滑口径实测表。