σμ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 学习路线›模块 6 · 回测与交易成本›主题 6.1·~1小时高级

向量化回测器

路线进度模块 6/11 · 主题 1/6
6.1 向量化回测器
—实验得分— / 100总 XP—连续天数—

回放历史

回测要问的是:一条交易规则在过去的数据上会赚多少。向量化版本一次处理整个数组:不对天数循环,只有每天一个信号、每天一个仓位,再与收益率做一次乘法。它简短、快速、容易检查,所以是筛选想法的第一件工具。它的局限(没有订单、没有现金、没有部分成交)正是下一节要补上的。

import numpy as np

returns = np.array([0.01, 0.02, -0.01, 0.03])           # the asset's daily returns
signal = np.array([2.5, 0.4, -1.0, 0.0])                # any score: positive = buy, negative = sell

position = np.sign(signal)                               # +1, +1, -1, 0
print(position)

一天的延迟

用今天收盘价算出的信号,只能在今天收盘之后交易,所以赚到的是明天的收益,而不是今天的。在数组里这就是一次平移:strategy[t] = position[t - 1] * returns[t],第一天之前没有仓位。忘掉这一步,策略就是在交易它已经看到的收益,回测会好看得离谱。这次平移只有一行,却是整个函数里最重要的一行。

# Yesterday's position earns today's return
import numpy as np

returns = np.array([0.01, 0.02, -0.01, 0.03])
position = np.array([1.0, 1.0, -1.0, 0.0])

strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
print(strategy)                                    # [ 0.    0.02 -0.01 -0.03]
print(position * returns)                          # the same-day version: [ 0.01  0.02  0.01  0. ] looks better, and is cheating

净值与统计

每日收益率会复利累乘:每天结束时的价值,是初始资金乘以 1 + 收益率 的累积乘积。从这条净值曲线,三个数就能概括一次回测。总收益是期末净值减 1。夏普比率是日均收益除以其标准差,再乘以 sqrt(periods) 年化。最大回撤是从此前高点出发的最大跌幅 equity / running max - 1,也就是投资者本该经历的损失。

# Equity curve and the three statistics for a short run
import numpy as np

strategy = np.array([0.0, 0.02, -0.01, -0.03, 0.04])
equity = np.cumprod(1 + strategy)
print(np.round(equity, 5))
print(round(float(equity[-1] - 1), 5))                                        # total return
print(round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 3))   # annualised Sharpe
print(round(float((equity / np.maximum.accumulate(equity) - 1).min()), 5))      # maximum drawdown
# A moving-average rule on a simulated price path, with its statistics
import numpy as np

rng = np.random.default_rng(7)
returns = rng.normal(0.0004, 0.01, size=500)
prices = 100 * np.cumprod(1 + returns)

fast = np.convolve(prices, np.ones(10) / 10, mode="valid")[-(len(prices) - 49):]      # 10-day mean, aligned
slow = np.convolve(prices, np.ones(50) / 50, mode="valid")                            # 50-day mean
position = np.zeros(len(prices))
position[49:] = np.sign(fast - slow)

strategy = np.zeros(len(returns))
strategy[1:] = position[:-1] * returns[1:]
equity = np.cumprod(1 + strategy)
drawdown = equity / np.maximum.accumulate(equity) - 1
print(round(float(equity[-1] - 1), 4), round(float(strategy.mean() / strategy.std(ddof=1) * np.sqrt(252)), 4), round(float(drawdown.min()), 4))
向量化回测是筛选,而不是定论它假设你在收盘时按所见价格、零成本地交易全部仓位。真实交易有成本、滑点和限制,后面几节会讲;而在同一段历史上试很多规则,总会碰到一条靠运气看起来不错的。有希望的结果是继续检验的理由,而不是交易的理由。
  • 仓位 = 信号的符号;策略收益为 position[t - 1] * returns[t]:今天的收益属于昨天的决策。
  • 净值是 start * cumprod(1 + r);用总收益、年化夏普和最大回撤来概括。
  • 向量化回测是快速筛选:忽略成本和成交,同一段历史上多次尝试会产生运气。

下一节:事件驱动回测器,逐根K线回放历史,带有现金、订单和成交。

实验:评分内容

  • 用 signal_to_position 把信号变成仓位25 分
  • 用 strategy_returns 计算前一日仓位的收益25 分
  • 用 equity_curve 把各日复利相乘25 分
  • 用 backtest_stats 汇总回测结果25 分
资源管理器 · lab-87-1
▾ lab-87-1
…
bash · 浏览器内
learner@quantcraft:~/lab-87-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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