σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python 学习路线›模块 2 · 用于市场数据的 NumPy 与 pandas›主题 2.14·~40分钟中级

滚动窗口

路线进度模块 2/11 · 主题 14/20
2.14 滚动窗口
—实验得分— / 100总 XP—连续天数—

滑动的窗口

今天的价格本身意义有限,交易者会把它与最近的过去比较。滚动窗口保留最近 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 分
资源管理器 · lab-47-1
▾ lab-47-1
…
bash · 浏览器内
learner@quantcraft:~/lab-47-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-47-1 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记

登录后即可运行你的代码。

所有讲解均可免费阅读。创建账户或登录后,即可运行代码、运行测试并提交此实验。

  • 浏览器内 IDE 与 bash 终端,无需安装
  • 即时评分、XP 以及你在学习路线中的位置
创建账户登录
或
使用 GitHub 继续