
本教程详细介绍了如何在 polars 中高效计算指数移动平均线 (ema)。文章将深入探讨 ema 的基本原理,并重点解决在 polars 中处理初始空值(`nan`/`none`)时常见的陷阱。通过具体的代码示例,您将学习如何正确地构造数据序列,利用 `ewm_mean` 函数,并理解 `np.nan` 与 polars 内部 `none` 之间的差异,从而确保 ema 计算的准确性和稳定性。
Polars 中指数移动平均线 (EMA) 的实现
指数移动平均线 (EMA) 是一种常用的技术分析指标,它对近期数据赋予更高的权重,使其比简单移动平均线 (SMA) 对价格变化反应更灵敏。在数据分析领域,Polars 凭借其卓越的性能,成为处理大规模数据集的理想选择。本教程将指导您如何在 Polars 中实现 EMA 计算,并着重解决在数据初始化过程中可能遇到的空值处理问题。
EMA 计算原理与初始化方法
EMA 的核心计算公式涉及一个平滑因子,该因子决定了当前价格对 EMA 的影响程度。由于 EMA 是一个递归计算过程,它需要一个初始值来启动。常见的初始化方法之一是:
- 计算前 length 个数据点的简单移动平均 (SMA)。
- 将这个 SMA 值作为 EMA 序列的第 length-1 个位置的值(从 0 开始计数)。
- 将 EMA 序列的前 length-1 个位置填充为空值(或 NaN),以表示这些位置没有足够的历史数据进行完整的 EMA 计算。
这种初始化策略确保了 EMA 从一个有意义的基准开始,并且在有足够数据后才开始平滑。
Polars ewm_mean 函数与空值处理
Polars 提供了 Series.ewm_mean() 方法来高效计算指数加权移动平均。然而,在使用此方法时,正确处理空值至关重要。一个常见的陷阱是尝试使用 NumPy 的 np.NaN 来填充 Polars Series 中的空值。尽管 np.NaN 在 Python 和 Pandas 中广泛用于表示“非数字”,但 Polars 在其内部对空值有自己的优化表示,通常是 None。当 ewm_mean 遇到 np.NaN 时,尤其是在 ignore_nulls=False 的情况下,可能会导致整个 EMA 序列返回 NaN,因为 Polars 可能无法正确处理 np.NaN 作为其内部空值表示的一部分,从而阻碍了后续的计算。
正确的做法是使用 Python 内置的 None 或 Polars 明确的空值类型来表示缺失数据。
示例代码:在 Polars 中计算 EMA
以下是一个完整的 Polars EMA 实现,它包含了上述的初始化策略和正确的空值处理方法。
import polars as pl
import numpy as np # 尽管不用于空值,但可能在其他场景有用
def polars_ema(close: pl.Series, length: int = 10, adjust: bool = False, sma_initial: bool = True) -> pl.Series:
"""
计算 Polars Series 的指数移动平均线 (EMA)。
参数:
close (pl.Series): 输入的收盘价或其他数值序列。
length (int): EMA 的周期长度。
adjust (bool): 是否使用调整因子。当 adjust=True 时,权重会考虑初始值的存在。
Polars 的 ewm_mean 默认 adjust=True,与 pandas 行为一致。
sma_initial (bool): 是否在 EMA 计算前,用前 length 个值的简单移动平均 (SMA)
填充第 length-1 个位置,并用 None 填充前 length-1 个位置。
这是一种常见的 EMA 初始化方法。
返回:
pl.Series: 计算出的 EMA 序列。
"""
if not isinstance(close, pl.Series):
raise TypeError("close 必须是一个 Polars Series。")
if close.is_empty():
return pl.Series(dtype=pl.Float64) # 如果输入为空,返回一个空的 Float64 Series
length = int(length) if length and length > 0 else 10
if length <= 0:
raise ValueError("EMA 周期长度 (length) 必须大于 0。")
# 复制 Series 以避免修改原始数据
processed_close = close.clone()
if sma_initial:
if len(processed_close) < length:
# 如果数据长度不足 EMA 周期,则无法计算初始 SMA
# 返回一个包含 None 的 Series,长度与输入相同
return pl.Series([None] * len(processed_close), dtype=pl.Float64)
# 计算前 length 个值的简单移动平均
initial_sma = processed_close.slice(0, length).mean()
# 创建一个包含 (length - 1) 个 None 值的 Series
# 明确指定 dtype 为 pl.Float64 以确保类型兼容性
nones_series = pl.Series([None] * (length - 1), dtype=pl.Float64)
# 创建一个包含初始 SMA 值的 Series
sma_value_series = pl.Series([initial_sma], dtype=pl.Float64) # 确保类型一致
# 拼接 Series: (length-1)个 None + 1个 SMA值 + 剩余数据
# 确保所有 Series 的数据类型兼容
processed_close = nones_series.append(sma_value_series).append(processed_close.slice(length))
# 使用 Polars 的 ewm_mean 函数计算 EMA
# ignore_nulls=False: 关键!确保 None 被视为占位符而非跳过。
# 这意味着 Polars 会在计算权重时考虑这些 None 的位置,
# 但它们本身不贡献数值,从而使得第一个有效 EMA 值
# (即 initial_sma)能够正确地启动指数加权。
# min_periods=0: 允许在数据点不足 span 长度时就开始计算,只要有数据就计算。
# 这与 sma_initial 逻辑结合,确保第一个有效 EMA 值出现在正确位置。
ema_series = processed_close.ewm_mean(
span=length,
adjust=adjust,
ignore_nulls=False,
min_periods=0
)
return ema_series
# 示例数据
sample_data = pl.Series(
"close",
[
1.08086, 1.08069, 1.08077, 1.08077, 1.08052,
1.08055, 1.08068, 1.08073, 1.08077, 1.08073,
1.08068, 1.08062, 1.08052, 1.0806, 1.08063,
1.08064, 1.08063, 1.08053, 1.08067, 1.08058
],
dtype=pl.Float64 # 明确指定数据类型
)
# 调用函数并打印结果
ema_result = polars_ema(sample_data, length=10)
print("计算出的 EMA 结果:")
print(ema_result)关键注意事项
- 空值表示: 在 Polars Series 中,使用 None 来表示空值,而不是 np.NaN。pl.Series([None] * N, dtype=pl.Float64) 是创建包含空值的 Series 的推荐方式,并务必指定数据类型以确保兼容性。
-
ewm_mean 参数:
- span: EMA 的周期长度,与 length 参数对应。
- adjust: 布尔值,决定是否根据权重调整计算。Polars 默认 adjust=True,与 Pandas 行为一致。
- ignore_nulls: 非常重要。当设置为 False 时,ewm_mean 会将 None 值视为序列中的一个位置,并将其权重视为零,从而保持序列的结构。如果设置为 True,ewm_mean 会跳过 None 值,仅对非空值进行计算,这可能会改变 EMA 结果的对齐方式,不适用于本教程的初始化策略。
- min_periods: 指示在计算 EMA 之前所需的最少非空观测值数量。设置为 0 允许在有任何数据可用时就进行计算,这对于结合 sma_initial 策略非常有用,因为它允许在 length-1 个 None 之后立即从 initial_sma 值开始计算 EMA。
- 数据类型: 在拼接 Series 时,确保所有部分的 dtype 兼容,例如,当创建 nones_series 和 sma_value_series 时,明确指定 dtype=pl.Float64 可以避免潜在的类型不匹配问题。
- 原始数据保护: 在函数内部对 close Series 进行 clone() 操作是一个好习惯,可以防止函数意外修改调用者传入的原始数据。
总结
在 Polars 中计算 EMA 是一项高效且直接的任务,但需要特别注意空值的处理方式。通过使用 None 代替 np.NaN 来表示缺失数据,并正确配置 ewm_mean 的 ignore_nulls 和 min_periods 参数,您可以确保 EMA 计算的准确性和与传统实现的一致性。掌握这些细节将帮助您充分利用 Polars 的强大功能进行时间序列分析。










