第 9 章 · 02 IndicatorBu 弃用 talib 自实现指标


文档摘要

第 9 章 · 02 IndicatorBu 弃用 talib 自实现指标 本节摘要:本节解剖 ABU 的技术指标库—— (约 970 行,5 个指标模块)。ABU 在这里做了一个果断的架构决策:彻底弃用 talib( 里 写死),全部用 pandas 自实现 ATR/MA/EMA/布林/MACD/RSI。原因有三:talib 跨平台安装困难(尤其 Windows,需要编译 C 库)、自实现便于教学(每行公式都看得见)、双引擎架构保留了切换能力(每个指标的 函数仍在,改一行 gcalctype 就能切回 talib)。

第 9 章 · 02 IndicatorBu 弃用 talib 自实现指标

本节摘要:本节解剖 ABU 的技术指标库——IndicatorBu(约 970 行,5 个指标模块)。ABU 在这里做了一个果断的架构决策:彻底弃用 talib(ABuNDBase.pyg_calc_type = ECalcType.E_FROM_PD 写死),全部用 pandas 自实现 ATR/MA/EMA/布林/MACD/RSI。原因有三:talib 跨平台安装困难(尤其 Windows,需要编译 C 库)、自实现便于教学(每行公式都看得见)、双引擎架构保留了切换能力(每个指标的 _calc_xxx_from_ta 函数仍在,改一行 g_calc_type 就能切回 talib)。本节先拆 ABuNDBase 的统一架构(ECalcType 枚举 + plot_from_order 通用绘制流程),再逐一精读 5 个指标的"_from_ta + _from_pd + calc_xxx + plot 四件套"统一模式。重点看 ATR——源码注释坦承"没有完全按照标准公式使用简单移动平均,使用了 pd_ewm_mean 即加权移动平均",这是 ABU 对经典指标的有意改写。读完本节,你将理解 ABU"不依赖 talib 也能算全部常用指标"的工程取舍。

内容来源:原项目源码 abupy/IndicatorBu/ABuNDBase.py + ABuNDAtr.py + ABuNDMa.py + ABuNDBoll.py + ABuNDMacd.py + ABuNDRsi.py(共约 970 行)。

⚠️ 注意:本节重点理解"双引擎架构"。ABUNDBase 用一个模块级变量 g_calc_type 决定全局用 talib 还是 pandas,每个指标模块的 calc_xxx = _calc_xxx_from_pd if g_calc_type == E_FROM_PD else _calc_xxx_from_ta 是统一的分发模式。虽然实际写死了 E_FROM_PD,但 _from_ta 函数都还在(完整可运行),这是"以备不时之需"的工程冗余。另外 ATR 的 ewm 改写是已知偏差——如果你要把 ABU 的 ATR 与 talib 的 ATR 对比,会有数值差异。

学习目标

阅读完本节,你应当能够:

  1. 解释 ECalcType 枚举(E_FROM_TA 用 talib / E_FROM_PD 用 pandas),以及 g_calc_type = E_FROM_PD 写死的三条原因(跨平台安装难/便于教学/双引擎保留切换)。
  2. 说清 plot_from_order 的通用绘制流程:order → 确定 symbol/buy_date/sell_date → date_ext 推算 start/end → make_kl_df 取 KL → plot_nd_func 绘制。
  3. 解释 5 个指标统一的"四件套"模式:_calc_xxx_from_ta + _calc_xxx_from_pd + calc_xxx(分发)+ plot_xxx(绘制)。
  4. 逐行读懂 _calc_atr_from_pd:shift(1) 得昨收 → TR=max(∣H-L∣,∣H-昨收∣,∣昨收-L∣) → pd_ewm_mean 加权(非标准简单移动平均),以及为什么 ABU 故意用 ewm。
  5. 理解 _calc_ma_from_pdcalc_ma_from_prices 的区别(后者多了 min_periods 参数,被 AbuDoubleMaBuy 调用)。
  6. 说清布林带三轨公式(中轨 MA ± nb_dev × std)与 MACD 三件(dif=EMA_fast-EMA_slow, dea=EMA_dif, bar=dif-dea)。
  7. 解释 _calc_rsi_from_pd 的 5 步:diff → gain/loss 分离 → N 日均值 → rs=gain/loss → rsi=100-100/(1+rs)。

一、ABuNDBase:双引擎架构与统一分发

ABuNDBase.py(106 行)是指标库的基础设施。核心是 ECalcType 枚举和全局分发变量:

21 class ECalcType(Enum): 25 """使用talib透传技术指标计算""" 26 E_FROM_TA = 0 28 """使用pandas等库实现技术指标计算""" 29 E_FROM_PD = 1 39 """彻底不用talib,完全都使用自己计算的指标结果""" 40 g_calc_type = ECalcType.E_FROM_PD

E_FROM_TA 表示用 talib(透传 talib.ATR/talib.MA 等),E_FROM_PD 表示用 pandas 自实现。源码注释"彻底不用 talib,完全都使用自己计算的指标结果"——这是 ABU 的明确决策。

1.1 为什么 ABU 弃用 talib

源码上方注释(L31-38)保留了一段被注释掉的"自动探测"逻辑:

31 # try: 32 # # 不强制要求talib,全部局部引用 33 # # noinspection PyUnresolvedReferences 34 # import talib 35 # g_calc_type = ECalcType.E_FROM_TA 36 # except ImportError: 37 # # 没有安装talib,使用E_FROM_PD 38 # g_calc_type = ECalcType.E_FROM_PD

原本设计是"有 talib 就用 talib,没有就退回 pandas"。但最终写死了 E_FROM_PD,原因:

  1. 跨平台安装困难:talib 是 C 库的 Python 封装,Windows 上要先用 TA-Lib C 库编译,经常卡在 MSVC/MinGW 环境问题。这对教学项目是致命的——读者装不上就没法跑。ABU 选择"零原生依赖",pandas/numpy 都是纯 Python wheel,任意平台 pip install 即可。
  2. 自实现便于教学:每个指标的 _calc_xxx_from_pd 都是几十行 pandas 代码,公式一目了然。talib 是黑盒,读者看不到 TR 怎么算、EMA 怎么加权。ABU 是教程项目,透明度优先。
  3. 双引擎保留切换能力:_calc_xxx_from_ta 函数全部保留且可运行(都 import talib),只要把 g_calc_type 改成 E_FROM_TA 就切回 talib。这是"以备不时之需"的工程冗余——如果未来某指标 pandas 实现性能不够,可以单独切。

1.2 plot_from_order:通用绘制流程

plot_from_order(ABuNDBase.py:43-105)是所有指标绘制的基础设施——给定一个 order(交易记录),自动取 KL 数据并绘制指标 + 标注买卖点:

68 def plot_from_series(p_order): 74 target_symbol = p_order['symbol'] 77 buy_index = pd.to_datetime(str(p_order['buy_date'])) 80 start = ABuDateUtil.begin_date(date_ext, date_str=start, fix=False) # 向前推 date_ext 天 83 if p_order['sell_type'] != 'keep': 84 sell_index = pd.to_datetime(str(p_order['sell_date'])) 88 end = ABuDateUtil.begin_date(-date_ext, date_str=end, fix=False) # 向后推 93 kl_pd = ABuSymbolPd.make_kl_df(target_symbol, start=start, end=end) 98 return plot_nd_func(kl_pd, with_points=buy_index, with_points_ext=sell_index, **kwargs)

date_ext 默认 120——围绕交易日期前后各取 120 天,保证图表能看到完整的交易上下文。plot_nd_func 是具体的指标绘制函数(plot_atr/plot_ma/...),由调用方传入。这就是为什么每个指标模块都有 plot_xxx_from_order:它就是 plot_from_order(plot_xxx_from_klpd, ...) 的一行封装。

单子(order 只有 1 行)与多子(DataFrame)的处理在 L59-66:单行 DataFrame 自动转 Series,统一走 plot_from_series;多子用 apply(plot_from_series, axis=1) 逐行绘制。

二、ATR:故意改用加权移动平均

ABuNDAtr.py_calc_atr_from_pd(ABuNDAtr.py:51-85)是本节最该精读的函数:

51 def _calc_atr_from_pd(high, low, close, time_period=14): 60 if isinstance(close, pd.Series): 62 pre_close = close.shift(1).values # shift(1) 得昨收 64 pre_close = shift(close, 1) 67 pre_close[0] = pre_close[1] # 首元素无昨收,用第二天填 74 tr_hl = np.abs(high - low) # ∣最高 - 最低∣ 77 tr_hc = np.abs(high - pre_close) # ∣最高 - 昨收∣ 79 tr_cl = np.abs(pre_close - low) # ∣昨收 - 最低∣ 81 tr = np.maximum(np.maximum(tr_hl, tr_hc), tr_cl) # TR = 三者最大 83 # (ATR)= MA(TR, N), 这里没有完全按照标准公式使用简单移动平均,使用了pd_ewm_mean,即加权移动平均 84 atr = pd_ewm_mean(pd.Series(tr), span=time_period, min_periods=1) 85 return atr.values

五步:

  1. 昨收:close.shift(1)。首元素(第一天)没有昨收,用第二天的值回填(L67),避免 NaN。
  2. TR 三分量:∣H-L∣(日内振幅)、∣H-昨收∣(跳空高开到最高)、∣昨收-L∣(跳空低开到最低)。
  3. TR:三者取最大。这就是"真实波幅"——考虑了跳空缺口,比单纯日内振幅更准。
  4. ATR:对 TR 做 N 日平均。关键偏差:标准 ATR 用简单移动平均(SMA),ABU 用 pd_ewm_mean(指数加权移动平均)。ewm 给近期数据更大权重,对波动变化反应更快。
  5. L88 分发:calc_atr = _calc_atr_from_pd if g_calc_type == E_FROM_PD else _calc_atr_from_ta

L82-83 的注释是 ABU 唯一公开承认的"对经典指标的改写"——作者特意标注"没有完全按照标准公式使用简单移动平均"。这种诚实态度在量化源码里少见。ewm vs SMA 的差异:ewm 反应快但噪声大,SMA 平滑但滞后。ABU 选 ewm,可能是因为它用在卖出因子的止损止盈上,需要快速感知波动变化。

atr14/atr21(L91-112)是固定周期的便捷封装(N=14 和 N=21,14 是 Wilder 原版,21 是月线)。atr14_min/atr14_max 等是确定常数阈值时用的(取序列最小/最大,fillna 后算)。

三、MA/EMA:min_periods 的妙用

ABuNDMa.py_calc_ma_from_pd(L56-71)与 calc_ma_from_prices(L74-91):

56 def _calc_ma_from_pd(prices, time_period=10, from_calc=EMACalcType.E_MA_MA): 64 if isinstance(prices, pd.Series): 65 prices = prices.values 67 if from_calc == EMACalcType.E_MA_MA: 68 ma = pd_rolling_mean(prices, window=time_period, min_periods=time_period) # min_periods=window 70 ma = pd_ewm_mean(prices, span=time_period, min_periods=time_period) 74 def calc_ma_from_prices(prices, time_period=10, min_periods=None, from_calc=EMACalcType.E_MA_MA): 86 min_periods = time_period if min_periods is None else min_periods 87 if from_calc == EMACalcType.E_MA_MA: 88 ma = pd_rolling_mean(prices, window=time_period, min_periods=min_periods) # min_periods 可调 90 ma = pd_ewm_mean(prices, span=time_period, min_periods=min_periods)

两个函数的区别:calc_ma_from_prices 多了 min_periods 参数

  • _calc_ma_from_pd:min_periods=time_period,意思是"窗口不满 N 天就返回 NaN"。比如算 MA60,前 59 天都是 NaN,第 60 天才有值。这是严格定义。
  • calc_ma_from_prices:min_periods 可外部设置。如果设 min_periods=1,窗口不满也能算(用部分数据)。比如 MA60 在第 1 天就用当天价格当 MA60,第 2 天用 2 天均价,...,第 60 天才是真正的 60 日均价。

calc_ma_from_pricesAbuDoubleMaBuy(双均线买入因子)和第 8 章 BtcBigWaveClf(比特币大波动)调用——回测早期数据不足时,strict MA 全是 NaN 会让因子发不出信号,min_periods=1 保证早期也能算。这是 ABU 处理"冷启动"的标准手法。

EMACalcType 枚举(L28-32):E_MA_MA(简单移动平均)/ E_MA_EMA(指数加权)。两个都在 ewm_mean 和 rolling_mean 之间切换。

四、布林带:中轨 ± nb_dev × std

ABuNDBoll.py_calc_boll_from_pd(L52-70):

61 middle = pd_rolling_mean(prices, window=time_period, min_periods=time_period) # 中轨 = N 日 MA 63 n_std = pd_rolling_std(prices, window=20, center=False) # N 日移动标准差 65 upper = middle + nb_dev * n_std # 上轨 = 中轨 + nb_dev × std 67 lower = middle - nb_dev * n_std # 下轨 = 中轨 - nb_dev × std 70 return upper.values, middle.values, lower.values

三轨公式(注释 L9-12):

  • 中轨 = N 日移动平均(默认 N=20)。
  • 上轨 = 中轨 + nb_dev × N 日移动标准差(默认 nb_dev=2)。
  • 下轨 = 中轨 - nb_dev × N 日移动标准差。

注意 L63 的 window=20硬编码——即使 time_period 不是 20,std 的窗口仍是 20。这可能是源码笔误(应该用 time_period),但因为是教学示例,影响不大。布林带的核心思想:价格在上下轨之间波动,带宽随波动率变化(波动大涨得猛,带宽变宽;盘整时带宽变窄)。突破上轨是强势,跌破下轨是弱势。

五、MACD:快慢均线差 + 信号线

ABuNDMacd.py_calc_macd_from_pd(L50-71):

63 ewma_fast = pd_ewm_mean(price, span=fast_period) # EMA12 快线 65 ewma_slow = pd_ewm_mean(price, span=slow_period) # EMA26 慢线 67 dif = ewma_fast - ewma_slow # DIF = 快 - 慢 69 dea = pd_ewm_mean(dif, span=signal_period) # DEA = DIF 的 9 日 EMA(信号线) 70 bar = (dif - dea) # MACD 柱 = DIF - DEA 71 return dif, dea, bar

MACD 三件套:

  • DIF(快慢线差):EMA12 - EMA26。快线上穿慢线(DIF 由负转正)是金叉(买信号),下穿是死叉(卖信号)。
  • DEA(信号线):DIF 的 9 日 EMA。DIF 上穿 DEA 也是买信号。
  • MACD 柱(bar):DIF - DEA。柱状图由负转正,多头力量增强。

注释(L7-9)说"由快的加权移动均线(EMA12)减去慢的加权移动均线(EMA26)得到 DIF,再用 DIF - (快线-慢线的 9 日加权移动均线 DEA)得到 MACD 柱"——这就是经典 MACD 定义,ABU 实现忠实于公式。

六、RSI:5 步算法

ABuNDRsi.py_calc_rsi_from_pd(L51-79):

62 if g_rsi_gain: 64 diff_price = prices.diff() # 用 diff(价格变动绝对值) 66 diff_price = prices.pct_change() # 或用 pct_change(变动比例) 68 diff_price[0] = 0 # 首元素归零 71 gain = np.where(diff_price > 0, diff_price, 0) # 涨日变动,跌日归零 72 loss = np.where(diff_price < 0, abs(diff_price), 0) # 跌日变动(取绝对值),涨日归零 74 gain_mean = pd_rolling_mean(gain, window=time_period) # N 日平均涨幅 75 loss_mean = pd_rolling_mean(loss, window=time_period) # N 日平均跌幅 77 rs = gain_mean / loss_mean # RS = 平均涨幅 / 平均跌幅 78 rsi = 100 - 100 / (1 + rs) # RSI = 100 - 100/(1+RS)

五步:

  1. 价格变动:diff()(默认)或 pct_change()(由 g_rsi_gain 控制,默认 True 用 diff)。
  2. 涨跌分离:gain 只保留涨日变动(跌日归零),loss 只保留跌日变动绝对值(涨日归零)。
  3. N 日均值:分别对 gain/loss 做 N 日移动平均(默认 14)。
  4. RS:gain_mean / loss_mean
  5. RSI:100 - 100 / (1 + rs)

RSI 在 0-100 之间。RSI>70 通常认为超买(该跌),RSI<30 超卖(该涨)。注释(L7-14)完整列出了这 5 步,是 ABU 教学风格的体现——公式写在 docstring 里,代码忠实实现。

七、四件套统一模式回顾

5 个指标模块(ABuNDAtr/ABuNDMa/ABuNDBoll/ABuNDMacd/ABuNDRsi)都遵循同一个"四件套"模式:

命名 作用
1 _calc_xxx_from_ta talib 实现(import talib,透传 talib.ATR 等)
2 _calc_xxx_from_pd pandas 实现(纯 numpy/pandas,公式透明)
3 calc_xxx 分发变量(= _from_pd if g_calc_type == E_FROM_PD else _from_ta)
4 plot_xxx / plot_xxx_from_klpd / plot_xxx_from_order 三层绘制封装(原始/from kl_pd/from order)

绘制层的三级嵌套:

  • plot_xxx:最底层,接收原始数组(high/low/close/index),画图。
  • plot_xxx_from_klpd:接收 kl_pd DataFrame,提取列后调 plot_xxx。
  • plot_xxx_from_order:接收 order,通过 plot_from_order 自动取 KL 后调 plot_xxx_from_klpd。

这种嵌套让用户可以从任何层级调用——有原始数据用 plot_xxx,有 kl_pd 用 from_klpd,有交易记录用 from_order(自动标注买卖点)。这是 ABU API 设计的统一美感。

本节要点回顾

  1. ECalcType 双引擎:E_FROM_TA(talib)/ E_FROM_PD(pandas)。g_calc_type = E_FROM_PD 写死,彻底不用 talib。原因:跨平台安装难(Windows 编译 C 库)/ 便于教学(公式透明)/ 双引擎保留切换(_from_ta 函数仍在)。
  2. plot_from_order 通用流程:order → 取 symbol/buy_date/sell_date → date_ext(默认 120)推算 start/end → make_kl_df 取 KL → plot_nd_func 绘制 + 标注买卖点。单子自动转 Series,多子 apply 逐行绘制。
  3. 四件套模式:每个指标都有 _calc_xxx_from_ta + _calc_xxx_from_pd + calc_xxx(分发)+ plot_xxx/plot_xxx_from_klpd/plot_xxx_from_order(三级绘制)。
  4. ATR 故意改用 ewm:TR=max(∣H-L∣,∣H-昨收∣,∣昨收-L∣) 三者最大。标准 ATR 用 SMA,ABU 用 pd_ewm_mean(加权,反应更快)。源码注释公开承认这个改写。atr14/atr21 是固定周期封装。
  5. MA min_periods:_calc_ma_from_pd 用 min_periods=window(严格,早期 NaN),calc_ma_from_prices 可外部设 min_periods=1(冷启动可用部分数据)。后者被 AbuDoubleMaBuy 和 BtcBigWaveClf 调用。
  6. 布林带:中轨=N 日 MA,上轨=中轨+nb_dev×std,下轨=中轨-nb_dev×std(默认 N=20, nb_dev=2)。注意 n_std 窗口硬编码 20(可能是笔误)。
  7. MACD:DIF=EMA12-EMA26(快慢差),DEA=DIF 的 9 日 EMA(信号线),bar=DIF-DEA(柱)。金叉/死叉由 DIF 穿越零线或 DEA 判断。
  8. RSI 5 步:diff → gain/loss 分离(涨日/跌日)→ N 日均值 → rs=gain/loss → rsi=100-100/(1+rs)。RSI>70 超买,<30 超卖。

下一章(第 10 章)将进入并行调度——ABU 怎么用 Master-Worker 三层架构把全市场几百只股票的回测并行跑起来。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U