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

公司行动:拆股与分红

路线进度模块 2/11 · 主题 17/20
2.17 公司行动:拆股与分红
—实验得分— / 100总 XP—连续天数—

会说谎的价格历史

公司按 4 比 1 拆股时,每位股东每持有 1 股会得到 4 股,股价跌到四分之一。没有人亏钱,但原始历史里却出现了一个 -75% 的交易日。现金分红在较小的规模上也是如此:除息日股价大约下跌一个股息的幅度。读取原始价格的策略会看到从未伤害任何人的暴跌。解决办法是复权:对事件之前的所有日子重新缩放,使序列连续,并与今天的价格可比。

import pandas as pd

index = pd.bdate_range("2026-03-02", periods=6)
raw = pd.Series([480.0, 484.0, 488.0, 123.0, 124.5, 126.0], index=index, name="close")   # 4-for-1 split on 5 March

print(raw.pct_change().round(4).tolist())
# [nan, 0.0083, 0.0083, -0.748, 0.0122, 0.012]    the -74.8% day never happened to anyone

拆股复权

只有拆股之前的日子会变化:把它们的价格除以比率(4 比 1 拆股就是 4)。当前价格保持报价不变,因此复权后的序列以真实的当前价格结尾。用 index >= split_date 建立真/假掩码(字符串可以与 DatetimeIndex 比较),再用 where:掩码为真处保留原值,其余位置取替换值。成交量方向相反:拆股后成交的股数是之前的四倍,所以更早的成交量要乘以比率。合股使用小于 1 的比率,公式相同。

split_date = "2026-03-05"
adjusted = raw.where(raw.index >= split_date, raw / 4)    # the days before the split, divided by 4

print(adjusted.tolist())                      # [120.0, 121.0, 122.0, 123.0, 124.5, 126.0]
print(adjusted.pct_change().round(4).tolist())   # [nan, 0.0083, 0.0083, 0.0082, 0.0122, 0.012]   no more crash
# Volume goes the other way: older days are multiplied
volume = pd.Series([1000, 1100, 900, 4200, 4000, 4300], index=index)
print(volume.where(volume.index >= split_date, volume * 4).tolist())   # [4000, 4400, 3600, 4200, 4000, 4300]

分红复权

分红比拆股小,因此复权是一个接近 1 的因子:1 - 股息 / 前一收盘价,其中前一收盘价是除息日之前的最后一个收盘价。把除息日之前的每个价格乘以这个因子。于是除息日前一天的收盘价恰好下降一个股息的数额,除息日的涨跌只反映市场真实的行为,而用复权序列算出的收益率包含了股息。收益率计算应该使用这个复权收盘价。把因子放进一个独立的小函数,方便测试和复用。

def dividend_factor(prev_close, dividend):
    return 1 - dividend / prev_close


prices = pd.Series([100.0, 101.0, 102.0, 100.8, 101.5, 102.5], index=index)   # a 1.50 dividend, ex-date 5 March
ex_date, dividend = "2026-03-05", 1.5

prev_close = prices[prices.index < ex_date].iloc[-1]       # 102.0, the last close before the ex-date
factor = dividend_factor(prev_close, dividend)
print(round(factor, 6))                                    # 0.985294

adjusted = prices.where(prices.index >= ex_date, prices * factor)
print(adjusted.round(4).tolist())                          # [98.5294, 99.5147, 100.5, 100.8, 101.5, 102.5]
print(prices.pct_change().round(4).tolist()[3], adjusted.pct_change().round(4).tolist()[3])   # -0.0118 0.003
算收益用复权价,交易用原始价复权价格用于衡量收益和跨时间比较,并不是你实际支付的价格:订单按当天报价的原始价格成交,而较早的复权价格可能低于当时股票的真实成交价。请同时保留 close 和 adj_close 两列,并说明每个计算用的是哪一列。
  • 拆股把旧价格除以比率、把旧成交量乘以比率;只有拆股之前的日子会变。
  • 分红把除息日之前的价格乘以 1 - 股息 / 前一收盘价。
  • 用掩码做 where(index >= date, 复权值),不要用循环;用复权价算收益,用原始价交易。

下一节:用 matplotlib 画折线图,把现在已经干净的价格序列画出来。

实验:评分内容

  • 用 adjust_for_split 让拆股前后的价格可比25 分
  • 用 adjust_volume_for_split 换算旧成交量25 分
  • 用 dividend_factor 计算分红的调整因子25 分
  • 用 adjust_for_dividend 从历史中消除除息造成的下跌25 分
资源管理器 · lab-50-1
▾ lab-50-1
…
bash · 浏览器内
learner@quantcraft:~/lab-50-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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