0Pricing
Pandas & NumPy Academy · 课时

指数加权移动平均

使用 ewm(span=) 应用指数平滑,使近期值获得更高权重;这是金融分析中的标准技术。

指数加权移动平均 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

The Problem with Simple Moving Averages

A simple moving average treats all n observations in the window equally — a price from 30 days ago contributes the same as yesterday's price. But for predicting near-term values, recent observations are more informative than old ones. An Exponentially Weighted Moving Average (EWMA) solves this by assigning exponentially decreasing weights to older observations: yesterday counts the most, the day before counts slightly less, and so on back to the beginning of the series.

import pandas as pd
import numpy as np

np.random.seed(42)
prices = pd.Series(
    100 + np.cumsum(np.random.normal(0.5, 2, 30)),
    index=pd.date_range('2024-01-01', periods=30)
)

df = pd.DataFrame({'price': prices})
df['SMA_7'] = df['price'].rolling(7).mean()
df['EWM_span7'] = df['price'].ewm(span=7).mean()
print(df.tail(5).round(2))

How EWMA Weighting Works

EWMA computes each value as a weighted combination of the current observation and the previous EWMA: EWM_t = alpha * x_t + (1 - alpha) * EWM_{t-1}. The alpha (smoothing factor) determines how quickly weights decay — a large alpha (close to 1) responds quickly to recent changes (less smoothing), a small alpha (close to 0) is slow to respond (more smoothing). The weights form a geometric series: the most recent value has weight alpha, the previous has alpha*(1-alpha), and so on.

import numpy as np

# Manually show EWMA weights
alpha = 0.3
n_points = 8
weights = [alpha * (1 - alpha)**i for i in range(n_points)]

print(f'Alpha = {alpha}')
print(f'Weights (most recent first):')
for i, w in enumerate(weights):
    print(f'  t-{i}: {w:.4f}')
print(f'Sum of shown weights: {sum(weights):.4f} (converges to 1.0)')

The ewm() Method and span Parameter

Pandas implements EWMA through Series.ewm(). The most intuitive parameter is span, which is analogous to a simple moving average window: alpha = 2 / (span + 1). A span of 10 approximately corresponds to a 10-period SMA but with exponentially decaying weights instead of equal weights. Use ewm(span=n).mean() to get the EWMA, and ewm(span=n).std() for exponentially weighted volatility.

import pandas as pd
import numpy as np

np.random.seed(0)
data = pd.Series(
    np.random.normal(100, 10, 50),
    index=pd.date_range('2024-01-01', periods=50)
)

df = pd.DataFrame({'value': data})
df['EWM_3'] = df['value'].ewm(span=3).mean()
df['EWM_10'] = df['value'].ewm(span=10).mean()
df['EWM_20'] = df['value'].ewm(span=20).mean()

print('Alpha values:')
for span in [3, 10, 20]:
    alpha = 2 / (span + 1)
    print(f'  span={span} → alpha={alpha:.3f}')
print(df.tail(5).round(2))

The halflife Parameter

The halflife parameter defines how many periods it takes for the weight to decrease by half. If halflife=5, a value from 5 periods ago has half the weight of the current value. The relationship is: alpha = 1 - exp(log(0.5) / halflife). Halflife is often more intuitive than span or alpha because it directly expresses the 'memory' of the average in business terms — for example, 'prices from 2 weeks ago should have half the influence of today's price'.

import pandas as pd
import numpy as np

np.random.seed(5)
prices = pd.Series(
    100 + np.cumsum(np.random.normal(0, 2, 30)),
    index=pd.date_range('2024-01-01', periods=30)
)

df = pd.DataFrame({'price': prices})
# halflife=5: weight halves every 5 days
df['EWM_hl5'] = df['price'].ewm(halflife=5).mean()
# halflife=14: weight halves every 14 days (longer memory)
df['EWM_hl14'] = df['price'].ewm(halflife=14).mean()

print(df.tail(5).round(2))

The alpha Parameter Directly

You can also set the smoothing factor directly with ewm(alpha=). Alpha must be between 0 and 1 (exclusive). A high alpha like 0.8 gives very little smoothing — the EWMA tracks the raw signal closely. A low alpha like 0.1 gives heavy smoothing — the EWMA changes slowly and lags behind sudden shifts. Choosing the right alpha depends on the trade-off between responsiveness (detecting real trend changes quickly) and stability (ignoring random noise).

import pandas as pd
import numpy as np

np.random.seed(3)
data = pd.Series(
    [100]*20 + [120]*20,  # sudden level shift at row 20
    dtype=float
) + np.random.normal(0, 2, 40)

df = pd.DataFrame({'signal': data})
df['alpha_0.1'] = df['signal'].ewm(alpha=0.1).mean()  # slow
df['alpha_0.5'] = df['signal'].ewm(alpha=0.5).mean()  # medium
df['alpha_0.9'] = df['signal'].ewm(alpha=0.9).mean()  # fast

print('After the level shift (rows 20-25):')
print(df.iloc[18:26].round(2))

EWMA for Volatility: ewm().std()

ewm(span=).std() computes an exponentially weighted standard deviation. This is the basis of the EWMA volatility model used in financial risk management (also known as the RiskMetrics model with decay factor lambda=0.94). Exponentially weighted volatility responds faster to volatility spikes than a simple rolling std because it assigns more weight to recent high-variance observations.

import pandas as pd
import numpy as np

np.random.seed(42)
# Simulate returns: quiet period then volatile period
returns = pd.Series(
    np.concatenate([
        np.random.normal(0, 0.01, 60),
        np.random.normal(0, 0.04, 60)  # 4x more volatile
    ]),
    index=pd.date_range('2024-01-01', periods=120)
)

df = pd.DataFrame({'returns': returns})
df['rolling_vol'] = df['returns'].rolling(20).std()
df['ewm_vol'] = df['returns'].ewm(span=20).std()

print('Volatility comparison (transition at row 60):')
print(df.iloc[58:65].round(5))

MACD: Combining Two EWMAs

The MACD (Moving Average Convergence Divergence) is a classic financial indicator built from two EWMAs. It is the difference between a fast (short-span) EWMA and a slow (long-span) EWMA of the price. When the MACD crosses zero from below, it signals potential upward momentum (a buy signal); crossing from above signals downward momentum. MACD is a perfect example of applying two EWMAs to derive a derived indicator.

import pandas as pd
import numpy as np

np.random.seed(7)
prices = pd.Series(
    100 + np.cumsum(np.random.normal(0.3, 2, 100)),
    index=pd.date_range('2024-01-01', periods=100)
)

df = pd.DataFrame({'price': prices})
df['EMA_12'] = df['price'].ewm(span=12).mean()  # fast
df['EMA_26'] = df['price'].ewm(span=26).mean()  # slow
df['MACD'] = df['EMA_12'] - df['EMA_26']
df['Signal'] = df['MACD'].ewm(span=9).mean()   # signal line

print('MACD values (last 8 rows):')
print(df[['price', 'MACD', 'Signal']].tail(8).round(2))

EWMA with Time-Based Halflife

Pandas 1.1+ supports specifying halflife, span, and com as time offsets (e.g. halflife='7D') when working with a DatetimeIndex. This enables proper EWMA on non-uniform time series — for example, if you have hourly data with gaps on weekends, a halflife of '7D' means the weight truly halves every 7 calendar days regardless of the number of rows in that period.

import pandas as pd
import numpy as np

np.random.seed(0)
# Business-day data (no weekends)
biz_dates = pd.bdate_range('2024-01-01', periods=20)
prices = pd.Series(100 + np.cumsum(np.random.randn(20)), index=biz_dates)

# halflife as a time offset (7 calendar days)
ewm_7d = prices.ewm(halflife='7D', times=biz_dates).mean()
# halflife as row count (7 rows)
ewm_7rows = prices.ewm(halflife=7).mean()

df = pd.DataFrame({'price': prices, 'hl_7D': ewm_7d, 'hl_7rows': ewm_7rows})
print(df.round(2))

Choosing Between EWMA, Rolling, and Expanding

Summary of when to use each:

  • Rolling(n).mean(): all n recent observations equally weighted — good for simple trailing metrics and when all recent periods matter equally.
  • ewm(span=n).mean(): exponentially weighted — best when you want smoothing that responds more to recent changes, commonly used in finance and signal processing.
  • expanding().mean(): all historical data with equal weight — best for YTD or all-time statistics where forgetting old data is not acceptable.

Visualising EWMA vs SMA

Plotting EWMA and SMA together on a time series chart makes the difference in their responsiveness visible. The EWMA reacts more quickly to sudden level changes because it has not 'forgotten' all previous observations — the short-span EWMA will turn faster than an SMA with the same nominal window. This makes EWMA preferable in forecasting applications where capturing trend direction quickly is more valuable than maximum noise reduction.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)
prices = pd.Series(
    np.concatenate([
        100 + np.cumsum(np.random.normal(0.3, 1.5, 50)),
        130 + np.cumsum(np.random.normal(-0.3, 1.5, 50))
    ]),
    index=pd.date_range('2024-01-01', periods=100)
)

df = pd.DataFrame({'price': prices})
df['SMA_10'] = df['price'].rolling(10).mean()
df['EWMA_10'] = df['price'].ewm(span=10).mean()

df.plot(figsize=(12, 5), title='SMA vs EWMA — EWMA reacts faster to trend reversals')
plt.ylabel('Price')
plt.axvline(x=df.index[50], color='red', linestyle='--', label='Trend change')
plt.legend()
plt.show()

EWMA in Business Applications

Beyond finance, EWMA is widely used in product analytics and operations: customer lifetime value smoothing (recent behaviour predicts future spend better than old behaviour), server latency monitoring (an exponentially smoothed latency metric sounds false alarms faster than a rolling mean), and inventory demand forecasting (recent sales weeks should outweigh data from six months ago). The universal principle is: wherever recency matters, EWMA outperforms simple averaging.

Quick Check

Test your understanding of exponentially weighted moving averages from this lesson.

Lesson Recap

In this lesson you learned: ewm(span=) applies exponentially decaying weights so recent observations matter more, alpha controls responsiveness (high = fast, low = smooth), and the halflife parameter expresses memory in business-meaningful terms. EWMA outperforms SMA when detecting trend reversals quickly is important. Next up we explore rank and percentile calculations within groups using groupby().rank() and pd.qcut.

常见问题解答

「指数加权移动平均」课时是免费的吗?

是的 — 「指数加权移动平均」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。

「指数加权移动平均」这节课中我会学到什么?

使用 ewm(span=) 应用指数平滑,使近期值获得更高权重;这是金融分析中的标准技术。 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Pandas & NumPy Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「指数加权移动平均」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?

能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 滚动窗口
  2. 扩展窗口
  3. 指数加权移动平均
  4. 组内排名与百分位
← 返回 Pandas & NumPy Academy