滑动的窗口
今天的价格本身意义有限,交易者会把它与最近的过去比较。滚动窗口保留最近 n 个观测并每次前进一步,因此每个日期都得到由自己近期历史算出的统计量:移动平均、近期波动率、近期高点等。在 pandas 中,series.rolling(n) 描述窗口,.mean() 之类的统计量会在每个位置同时计算。
import pandas as pd
prices = pd.Series(
[100.0, 102, 101, 105, 107, 106, 110],
index=pd.bdate_range("2026-03-02", periods=7),
name="close",
)
moving_average = prices.rolling(3).mean()
print(moving_average.round(2).tolist())
# [nan, nan, 101.0, 102.67, 104.33, 106.0, 107.67]
print(prices.rolling(3).max().tolist()) # [nan, nan, 102.0, 105.0, 107.0, 107.0, 110.0] the 3-day high序列的开头
前 n - 1 个位置还没有完整窗口,所以结果是 NaN。这是诚实的:两个价格的平均不是 3 日平均。如果确实想更早得到值,min_periods=1 用现有数据计算,expanding() 则使用从开头起的全部数据而非固定窗口。需要多个点的统计量(如标准差)在观测足够之前也是 NaN。已经以 NaN 开头的序列(如简单收益率)会让第一个有效窗口再后移一步。
# min_periods=1: use whatever is available at the start
print(prices.rolling(3, min_periods=1).mean().round(2).tolist()) # [100.0, 101.0, 101.0, 102.67, 104.33, 106.0, 107.67]
# expanding(): everything since the first day, not a fixed window
print(prices.expanding().mean().round(2).tolist()) # [100.0, 101.0, 101.0, 102.0, 103.0, 103.5, 104.43]
# Rolling volatility of the returns: the first return is NaN, so three returns exist only from the 4th day
returns = prices.pct_change()
print(returns.rolling(3).std().round(4).tolist()) # [nan, nan, nan, 0.0249, 0.0248, 0.0246, 0.0237]从窗口到信号
滚动统计量经过比较就成为交易信号。Z 分数表示今天的价格偏离其近期均值多少个近期标准差:(price - rolling mean) / rolling std。滚动对象只需构建一次(r = prices.rolling(3))并使用两次。均线交叉比较快线和慢线:快线在上方时,近期趋势向上。比较得到布尔值,astype("int64") 把它们变成 0/1 信号。与 NaN 的比较为 False,所以在两条均线都存在之前信号为 0。
# z-score of each price against its own 3-day window
rolling = prices.rolling(3)
zscore = (prices - rolling.mean()) / rolling.std()
print(zscore.round(2).tolist()) # [nan, nan, 0.0, 1.12, 0.87, 0.0, 1.12]# Moving-average crossover: 1 while the 2-day average is above the 4-day average
fast = prices.rolling(2).mean()
slow = prices.rolling(4).mean()
signal = (fast > slow).astype("int64")
print(signal.tolist()) # [0, 0, 0, 1, 1, 1, 1]
# Trade on the next day: the signal of today's close cannot be traded before tomorrow
print(signal.shift(1).fillna(0).astype("int64").tolist()) # [0, 0, 0, 0, 1, 1, 1]不要偷看未来滚动窗口以当前行结束,因此只用到过去。
center=True 把当前行放在窗口中间,用到了未来价格:图上很漂亮,但无法交易。用今天收盘价算出的信号应在明天才使用,因此要 shift(1)。series.rolling(n).mean()(或std()、max()等)在每个位置计算最近n个观测的统计量。- 前
n - 1个元素是NaN;min_periods和expanding()可以有意改变这一点。 - Z 分数和均线交叉把窗口变成信号;交易前先平移信号,并避免
center=True。
下一节:处理缺失数据,如何处理真实价格表中总会出现的 NaN。
实验:评分内容
- 用
moving_average平滑价格25 分 - 用
rolling_volatility衡量近期风险25 分 - 用
rolling_zscore将每个价格相对其窗口标准化25 分 - 用
ma_crossover把两条均线变成信号25 分