⚠ 交易存在重大亏损风险。本网站的所有内容仅供教育用途,不构成财务建议。
第 6 课,共 13 课 · 免费

用代码实现指标:向量化的 SMA、EMA、RSI、ATR 和布林带,并对照图表平台进行验证

为五个最常用的指标编写向量化的 pandas 函数,了解为什么它们的数值可能与图表平台不同,以及如何使两者一致。

作者: Moustafa Hijazi内容审核日期: 2026-10-30阅读约 8 分钟自学课程

你将学到什么

  • rolling 和 ewm 如何仅用几行代码分别实现 SMA、EMA、Wilder 平滑、ATR 和布林带。
  • 哪些参数(adjust、alpha、ddof)会在不知不觉中改变数值。
  • 为什么你的 RSI 或 EMA 在最初几根 K 线上与图表平台不同,以及如何正确地比较两者。
  • 如何把指标组织为 src/indicators.py 中的纯函数,以便复用和测试。

讲解

课程《深入技术分析》解释了这些指标的含义;这里的目标是写出正确、快速、可测试的代码。每个函数接收一个 Series,并返回一个具有相同索引的 Series。

SMA(simple moving average,简单移动平均线):def sma(s, n): return s.rolling(n).mean()。

EMA(exponential moving average,指数移动平均线):def ema(s, n): return s.ewm(span=n, adjust=False).mean()。使用 span=n 时,平滑常数为 α = 2 ÷ (n + 1);当 n = 20 时,α = 0.0952。adjust=False 给出各平台使用的递推公式 EMAt = α × Pt + (1 − α) × EMAt−1;默认的 adjust=True 在最初几十根 K 线中采用不同的加权方式。pandas 以第一个价格作为初始值;大多数平台以前 n 根 K 线的 SMA 作为初始值。两种初始值的影响都会逐渐消退;大约 3n 根 K 线之后,两个序列就会非常接近。

RSI(relative strength index,相对强弱指数),Wilder 版本:

delta = s.diff()

gain = delta.clip(lower=0); loss = -delta.clip(upper=0)

avg_gain = gain.ewm(alpha=1/n, adjust=False).mean()

avg_loss = loss.ewm(alpha=1/n, adjust=False).mean()

rsi = 100 - 100 / (1 + avg_gain / avg_loss)

Wilder 平滑是一种 α = 1 ÷ n 的 EMA,而不是 2 ÷ (n + 1);在这里使用 span=14 是最常见的 RSI 错误。当 avg_loss 为零时,RSI 会正确地变为 100。

ATR(average true range,平均真实波幅):真实波幅是 最高价 − 最低价、|最高价 − 前收盘价| 和 |最低价 − 前收盘价| 三者中的最大值:

pc = close.shift(1)

tr = pd.concat([high - low, (high - pc).abs(), (low - pc).abs()], axis=1).max(axis=1)

atr = tr.ewm(alpha=1/n, adjust=False).mean()

有些平台显示的是基于 SMA 的 ATR;比较之前请先确认。第 8 课将使用 ATR 来设置止损和确定仓位规模。

布林带:中轨 = SMA(n),上轨和下轨 = 中轨 ± k × 同样 n 根 K 线的标准差,通常 n = 20、k = 2。陷阱在于 ddof:pandas 的 rolling(n).std() 除以 n − 1(样本标准差),而平台通常除以 n(总体标准差)。写成 std(ddof=0) 即可与之一致。样本标准差的值更大,足以把一次“触及”布林带变成“未触及”。

对照平台进行验证。使用与平台相同的复权序列计算你的指标,然后比较最近 100 个值,因为初始值造成的差异会逐渐衰减。使用带有小容差的 np.allclose。持续存在的差异通常来自五个常见原因:复权与未复权数据、初始值设定、span 与 alpha、ddof,或使用了不同的输入价格。修正或记录这种不一致;永远不要为了让数字吻合而去“调”参数。

实例演算

假设示例:十个以欧元计的日收盘价:20.00、20.50、21.00、20.80、21.40、21.00、21.60、22.00、21.80、22.40。

最后一根 K 线上的 SMA(5):(21.00 + 21.60 + 22.00 + 21.80 + 22.40) ÷ 5 = 108.80 ÷ 5 = €21.76。

最后一根 K 线上的 布林带(5, 2)。相对于 21.76 的偏差:−0.76、−0.16、0.24、0.04、0.64;平方和为 1.0720。总体方差 = 1.0720 ÷ 5 = 0.2144,标准差 0.4630,上轨 = 21.76 + 2 × 0.4630 = €22.69,下轨 €20.83。样本公式:1.0720 ÷ 4 = 0.2680,标准差 0.5177,上轨 €22.80,下轨 €20.72。

EMA(5)(α = 2 ÷ 6 = 0.3333,adjust=False):EMA1 = 20.00(初始值),EMA2 = 0.3333 × 20.50 + 0.6667 × 20.00 = 20.1667,EMA3 = 0.3333 × 21.00 + 0.6667 × 20.1667 = 20.4444,依此类推,最后一根 K 线上为 21.8310。

一根最高价 €21.20、最低价 €20.60、前收盘价 €20.80 的 K 线的真实波幅:max(0.60, |21.20 − 20.80| = 0.40, |20.60 − 20.80| = 0.20) = €0.60。

常见错误

在 ewm 中混淆 span 和 alphaewm(span=14) 使用 α = 2 ÷ 15 = 0.133,而 Wilder 的 RSI 和 ATR 需要 ewm(alpha=1/14),即 α = 0.0714。span 版本的反应速度几乎快一倍,不会与任何标准平台吻合。请明确写出你想要的 α,并把它记录在函数的文档字符串中。

练习活动

在纸上完成,仅为演示使用 3 周期的 Wilder RSI(实际使用为 14):收盘价 50.00、50.50、51.00、50.80。(a) 列出每根 K 线的上涨幅度和下跌幅度。(b) pandas 的 ewm(alpha=1/3, adjust=False) 以第一个观测值作为初始值;计算最后一根 K 线上的 avg_gain、avg_loss 以及 RSI。(c) 用一句话解释:为什么一个以前三次变动的简单平均作为初始值的平台在这里会显示不同的值,以及在断定你的代码有误之前你会怎么做。

显示答案

(a) 变动:+0.50、+0.50、−0.20。上涨:0.50、0.50、0.00。下跌:0.00、0.00、0.20。

(b) α = 1/3。avg_gain:0.50(初始值)→ (1/3)(0.50) + (2/3)(0.50) = 0.50 → (1/3)(0) + (2/3)(0.50) = 0.3333。avg_loss:0 → 0 → (1/3)(0.20) + (2/3)(0) = 0.0667。RS = 0.3333 ÷ 0.0667 = 5.0;RSI = 100 − 100 ÷ (1 + 5) = 83.33。

(c) 在本例中,简单平均初始值(上涨 0.3333,下跌 0.0667)恰好也得出相同的 83.33,但一般来说,两种初始值在最初几十根 K 线中会有差异,因此在改动代码之前,请先比较一个长序列中最近的 100 个值。

局限性:3 周期 RSI 只是教学手段;四根 K 线无法展示在实践中真正重要的收敛行为。

要点回顾

  • SMA 是 rolling(n).mean();EMA 是 ewm(span=n, adjust=False);用于 RSI 和 ATR 的 Wilder 平滑是 ewm(alpha=1/n, adjust=False)。
  • 大多数平台上的布林带使用总体标准差:请传入 ddof=0。
  • 初始值差异会逐渐消退;用一个容差比较最近 100 根 K 线,并在修改代码之前先排查五个常见的不一致原因。
  • 把指标保持为有文档说明的纯函数,以便进行单元测试并在每次回测中复用。

下一步

第 7 课把这些部件组装成一个向量化回测:信号、仓位、防止前视偏差的位移操作、交易成本以及资金曲线。

自测

回答问题后核对答案。你可以随意重试多次。

1. 哪一个 pandas 调用实现了 14 周期 RSI 或 ATR 的 Wilder 平滑?

2. 五个收盘价的均值为 €40.00,偏差平方和为 2.00。使用总体公式和样本公式时,上布林带(k = 2)分别在哪里?

3. 你的 EMA(20) 在最近 200 根 K 线上与平台的差异在 0.01 以内,但在最初 30 根 K 线上相差最多 €0.40。正确的结论是什么?

供您自行复习的自动自测。您的答案在浏览器中核对,我们不会查看或评估。这不是考试,也不是资格证书。

您的进度

“已完成”仅表示您将本课标记为已学完,并不是对您理解程度的测试。 学习进度和笔记仅保存在此设备的此浏览器中,而非账户中。