σμ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 学习路线›模块 1 · 面向量化的 Python 基础›主题 1.25·~40分钟入门

可复现的研究工作流

路线进度模块 1/11 · 主题 25/33
1.25 可复现的研究工作流
—实验得分— / 100总 XP—连续天数—

相同的输入,相同的结果

当你、同事或评审者以后能运行同一个项目并得到相同的数字时,结果就是可复现的。无法重建的回测既不能审计、调试,也不值得信任。结果漂移通常只有几个原因:随机数、库版本不同、数据被改动或被手工编辑、设置散落在代码各处,以及隐藏状态(例如乱序运行的笔记本)。可复现的工作流会消除每一种漂移来源,并记录所用的内容。

import random

# Same seed, same "random" numbers
a = random.Random(42)
b = random.Random(42)
print([round(a.random(), 3) for _ in range(3)])   # [0.639, 0.025, 0.275]
print([round(b.random(), 3) for _ in range(3)])   # [0.639, 0.025, 0.275]

# A different seed gives a different but equally repeatable sequence
print(round(random.Random(7).random(), 3))        # 0.324

随机种子,以及它属于哪个生成器

模拟和自助法会用到随机数,而随机数其实是从种子(seed)出发的确定性序列。固定种子,运行就会完全重复。random.seed(42) 设置的是整个程序共享的唯一全局生成器,所以别处的代码再取随机数,就会让你的序列错位。更稳妥的做法是创建你自己的生成器 rng = random.Random(42)(或 numpy.random.default_rng(42)),把它传递下去并从中取数。这样结果只取决于你的种子,你设置种子也不会干扰别人的随机性。

import random

# The global generator is shared: seeding it changes everyone's numbers
random.seed(1)
first = random.random()
random.seed(1)
print(random.random() == first)        # True: global seeding repeats...

# ...but a private generator leaves the global one untouched
random.seed(1)
before = random.getstate()
mine = random.Random(99)
mine.random()
print(random.getstate() == before)     # True: your draws did not disturb it

数据与设置的指纹

你怎么知道数据文件就是上个月用的那个?计算校验和:SHA-256 之类的哈希会把任何文件变成一个定长的短字符串,只要有一个字节变化它就会变。hashlib.sha256(data).hexdigest() 可得到它;大文件要分块读取。设置也值得同样处理:用排序后的键序列化配置(json.dumps(config, sort_keys=True)),再对文本做哈希,这样两份含义相同的配置无论键的顺序如何,指纹都相同,而一旦某个值改变,指纹就不同。

import hashlib
import json

# A checksum of bytes
print(hashlib.sha256(b"AAPL,184.25").hexdigest()[:12])   # 06ea4aa73ec1 (12 characters of 64)

# A fingerprint of settings, independent of key order
def fingerprint(config):
    text = json.dumps(config, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(text.encode("utf-8")).hexdigest()[:12]

one = {"fee": 0.001, "window": 20}
two = {"window": 20, "fee": 0.001}
print(fingerprint(one) == fingerprint(two))              # True
print(fingerprint(one) == fingerprint({"fee": 0.002, "window": 20}))  # False

记录你运行了什么

每次运行都应在结果旁留下一份小小的清单(manifest):种子、配置指纹、Python 与库的版本、每个输入文件的校验和,以及代码的 Git 提交。有了这份记录,你就能准确说出某个数字是由什么产生的,并在以后重建它。再结合前面的习惯:在 requirements.txt 中固定环境、用 Git 管理代码、把设置集中在一个文件里、用一条命令从原始数据一路运行到最终表格,中间不对数据做任何手工修改。

import json
import platform

# A manifest records what a run used
manifest = {
    "seed": 42,
    "config_fingerprint": "a1b2c3d4e5f6",
    "python": platform.python_version().split(".")[0] + ".x",
    "inputs": {"prices.csv": "9f86d081884c"},
}
print(json.dumps(manifest, indent=2, sort_keys=True))
# {
#   "config_fingerprint": "a1b2c3d4e5f6",
#   "inputs": {
#     "prices.csv": "9f86d081884c"
#   },
#   "python": "3.x",
#   "seed": 42
# }
可复现不等于正确错误同样会被完美地重现。可复现性让别人能检查你的工作、找到错误并信任修复,但它不能代替测试、对数据的合理性检查和对结果的批判性审视。种子也会掩盖运气:当结果依赖种子时,请用多个种子运行并报告离散程度。
  • 使用私有的 random.Random(seed),让结果只取决于你的种子。
  • 为数据(文件校验和)和设置(排序后的 JSON 再做 SHA-256)生成指纹。
  • 每次运行保存清单;固定环境 + Git 提交 + 一条重建命令。

下一节:类与对象,把数据和处理它的函数整合成一个整洁的单元。

实验:评分内容

  • 用 seeded_returns 生成可重复的随机收益率25 分
  • 用 file_checksum 为数据文件生成指纹25 分
  • 用 config_fingerprint 为配置生成指纹25 分
  • 用 run_manifest 记录一次运行所用的内容25 分
资源管理器 · lab-25-1
▾ lab-25-1
…
bash · 浏览器内
learner@quantcraft:~/lab-25-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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