σμ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—連続日数—

過去を再生する

バックテストは、売買ルールが過去のデータでいくら稼いだかを調べます。ベクトル化版は配列全体を一度に扱い、日ごとのループはなく、日ごとのシグナルとポジション、そしてリターンとの掛け算が1回あるだけです。短く、速く、検算しやすいので、アイデアを選別する最初の道具になります。限界(注文も現金も部分約定もない)は、次のトピックが加えるものそのものです。

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)

1日のずれ

今日の終値から計算したシグナルは今日の終値の後でしか取引できないため、稼ぐのは今日ではなく明日のリターンです。配列ではこれはシフトで、strategy[t] = position[t - 1] * returns[t] となり、初日より前のポジションはありません。これを忘れると、戦略はすでに知っているリターンで取引することになり、バックテストは素晴らしく見えます。シフトは1行ですが、この関数全体で最も重要な1行です。

# 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 + リターン の累積積を掛けたものです。この資産曲線から、3つの数が結果を要約します。総リターンは最終値から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)。総リターン、年率シャープ、最大ドローダウンで要約する
  • ベクトル化バックテストは速い選別。コストや約定を無視し、同じ過去データでの多数の試行は運を生む

次はイベント駆動型バックテスターです。現金・注文・約定を伴い、バーを1本ずつ再生します。

ラボ:採点される内容

  • 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で続ける