σμ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.16·~40分钟中级

检测异常值

路线进度模块 2/11 · 主题 16/20
2.16 检测异常值
—实验得分— / 100总 XP—连续天数—

不可能正确的数值

大盘股单日涨 30%,要么是被收购,要么是数据错误,比如漏了拆股或小数点错位。异常值与其他值相距足够远,需要做出决定:保留、修正或剔除。千万不要仅仅因为碍事就删掉它,因为真实的冲击恰恰是风险模型必须看到的。第一步始终是用明确且可重复的规则让它们显现出来。

Z 分数规则及其可能失效的原因

经典规则会标出偏离均值超过 2 或 3 个标准差的值:z = (x - mean) / std。它简单,在干净数据上有效。但异常值自己会抬高均值,尤其是标准差,于是从规则里躲了起来。下面的例子中,涨 30% 那天的 Z 分数只有 2.85,阈值取 3 就不会被标出。错误越极端,保护自己的能力越强。

import pandas as pd

returns = pd.Series([0.004, -0.002, 0.001, 0.003, -0.004, 0.002, 0.30, -0.001, 0.0, 0.002])

z = (returns - returns.mean()) / returns.std()
print(z.round(2).tolist())
# [-0.28, -0.34, -0.31, -0.29, -0.36, -0.3, 2.85, -0.33, -0.32, -0.3]

print(returns.mean().round(4), returns.std().round(4))   # 0.0305 0.0947   one day moved the mean tenfold
print(returns.drop(6).std().round(4))                    # 0.0026          the std without that day

稳健规则:IQR 与 MAD

稳健统计量几乎不受它们要找的异常值影响。四分位距 IQR = Q3 - Q1 衡量数据中间一半的离散程度,超出 Q1 - 1.5 * IQR 或 Q3 + 1.5 * IQR 的值就落在箱线图的边界之外。MAD(绝对中位差)用中位数取代标准差,修正 Z 分数 0.6745 * (x - median) / MAD 的解读方式与普通 Z 分数相同(3.5 是常用的阈值)。两者都能轻松标出涨 30% 的那天。

# The fences of a box plot
q1, q3 = returns.quantile(0.25), returns.quantile(0.75)
spread = q3 - q1
lower, upper = q1 - 1.5 * spread, q3 + 1.5 * spread
print(round(lower, 4), round(upper, 4))                    # -0.006 0.008
print(returns[(returns < lower) | (returns > upper)].to_dict())   # {6: 0.3}

# The modified z-score, built on the median
median = returns.median()
mad = (returns - median).abs().median()
modified_z = 0.6745 * (returns - median) / mad
print(modified_z.round(1).tolist())   # [0.8, -1.2, -0.2, 0.5, -1.9, 0.2, 100.7, -0.8, -0.5, 0.2]

缩尾处理:截断而不是删除

删除一行也会删掉其他列的信息,并打乱日期间隔。缩尾处理保留所有行,把极端值拉回到选定的分位数,例如 1% 和 99%:低于 1% 分位数的变成该分位数,高于 99% 分位数的变成该分位数。在 pandas 中就是用 quantile 得到上下限再 clip(lower, upper)。序列的形态得以保留,极端值对均值和回归的拉扯也随之减弱。

low, high = returns.quantile(0.1), returns.quantile(0.9)
print(round(low, 4), round(high, 4))             # -0.0022 0.0336

clipped = returns.clip(lower=low, upper=high)
print(clipped.round(4).tolist())
# [0.004, -0.002, 0.001, 0.003, -0.0022, 0.002, 0.0336, -0.001, 0.0, 0.002]
#                                 ^ raised                  ^ the 30% day pulled back to the 90% quantile
# Flag with a rule, then look at what is flagged before deciding anything
mask = z.abs() > 2
print(returns[mask].to_dict())                 # {6: 0.3}        the +30% day
print(returns[~mask].std().round(4))           # 0.0026          the volatility of the remaining days
用规则做决定,并写下来异常值规则是建模选择,而不是事实。在改动任何数据之前,先查清被标出的日子是什么(新闻、拆股还是错误报价),保留原始列,并记录所用阈值。阈值不同,回测结果也不同。
  • Z 分数规则 abs((x - mean) / std) > k 很简单,但异常值会抬高均值与标准差而自我隐藏。
  • 稳健规则使用分位数和中位数:IQR 边界(Q1 - 1.5 IQR、Q3 + 1.5 IQR)与基于 MAD 的修正 Z 分数。
  • 用分位数上下限做 clip(lower, upper)(缩尾)会保留行;没弄清冲击是什么之前绝不删除它。

下一节:公司行为,让旧价格与今天的价格无法直接比较的拆股和分红。

实验:评分内容

  • 用 zscore_outliers 标出偏离很远的收益率25 分
  • 用 iqr_bounds 计算箱线图的边界25 分
  • 用 mad_outliers 以稳健得分标出异常值25 分
  • 用 winsorize 压制极端值25 分
资源管理器 · lab-49-1
▾ lab-49-1
…
bash · 浏览器内
learner@quantcraft:~/lab-49-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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