σμ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.3·~45 分初級

ベクトル化

ロードマップ上の位置モジュール 2/11 · トピック 3/20
2.3 ベクトル化
—ラボスコア— / 100合計XP—連続日数—

要素ではなく配列全体で考える

ベクトル化とは、計算を配列全体に対する演算として書き、ループを高速なコンパイル済みコードで NumPy に実行させることです。a + b は2つの配列を要素ごとに足し、a * b は掛け、a ** 2 は各要素を2乗し、np.sqrt(a) はすべての平方根を取ります。これらはユニバーサル関数(ufunc)です。短く書けて読みやすく、大きなデータでは同等の Python の for ループより50倍ほど速いこともよくあります。for ループは要素ごとにインタープリターのオーバーヘッドを払うからです。

import numpy as np

a = np.array([1.0, 2.0, 3.0])
b = np.array([10.0, 20.0, 30.0])

print(a + b)             # [11. 22. 33.]
print(a * b)             # [10. 40. 90.]
print(a ** 2)            # [1. 4. 9.]
print(np.sqrt(a))        # [1.         1.41421356 1.73205081]

# The loop version of a * b, which vectorisation replaces
result = np.empty(3)
for i in range(3):
    result[i] = a[i] * b[i]
print(result)            # [10. 40. 90.]

ループなしのリターン:スライスでずらす

各要素を隣と比べる計算には、1つずらした配列のコピーがもう1つ必要で、スライスならそれがただで得られます。p[1:] は今日の価格、p[:-1] は昨日の価格で、どちらも長さ n - 1 なので、単純リターンは p[1:] / p[:-1] - 1 です。同じ組から差分も得られます(p[1:] - p[:-1] または np.diff(p))。累積和と累積積は np.cumsum と np.cumprod で、成長経路は np.cumprod(1 + returns) です。配列全体の比較(r > 0)と np.where(条件, 真のとき, 偽のとき) が、ループ内の if に取って代わります。

import numpy as np

p = np.array([100.0, 102.0, 101.0, 103.0])

returns = p[1:] / p[:-1] - 1                 # today / yesterday - 1, no loop
print(np.round(returns, 4))                  # [ 0.02   -0.0098  0.0198]
print(np.diff(p))                            # [ 2. -1.  2.]
print(np.cumprod(1 + np.array([0.01, 0.02])))   # [1.01   1.0302]

# np.where replaces if/else inside a loop
r = np.array([0.01, -0.02, 0.03, -0.01])
print(np.where(r > 0, "up", "down"))         # ['up' 'down' 'up' 'down']
print(np.where(r > 0, r, 0.0))               # [0.01 0.   0.03 0.  ]

軸方向の集計と行列の積

sum、mean、std、min、max などの関数は配列を集約します。リターンの2次元配列(行が日、列が資産)では、axis=0 は行方向に集約して列ごとに1つの値(各資産の時間平均)を、axis=1 は行ごとに1つの値(各日の資産平均)を返します。(日数, 資産数) の配列とウェイトのベクトルの行列積 R @ w は、毎日のポートフォリオリターンを1回の呼び出しで返します。ベクトルのトピックの内積を、全行に対して一度に行ったものです。

import numpy as np

# 2 days x 3 assets
R = np.array([[0.02, -0.01, 0.03],
              [0.01,  0.00, 0.02]])
w = np.array([0.5, 0.3, 0.2])

print(R.shape)            # (2, 3)
print(R.mean(axis=0))     # [ 0.015 -0.005  0.025]   one value per asset
print(R.sum(axis=1))      # [0.04 0.03]              one value per day
print(R @ w)              # [0.013 0.009]            portfolio return each day

# Standardise: how many standard deviations from the mean?
z = np.array([1.0, 2.0, 3.0, 4.0])
print((z - z.mean()) / z.std())   # [-1.34164079 -0.4472136   0.4472136   1.34164079]

cumsum による移動平均と、ベクトル化の限界

ウィンドウ w の移動平均はループが必要に見えますが、累積和を使えば不要です。np.cumsum(x) の先頭に 0 を置くと、連続する w 個の値の和は2つの累積和の差 cs[w:] - cs[:-w] になるので、w で割ります。結果は len(x) - w + 1 個です。ベクトル化するのは算術であり、直前の結果に依存するロジック(たとえば前日の約定に依存するポジション)ではありません。そうしたものはループに残すか、後で Numba のようなコンパイル型のツールに移します。中間配列はそれぞれ完全なコピーなので、メモリにも注意しましょう。

import numpy as np

x = np.array([1.0, 2.0, 4.0, 8.0, 16.0])
w = 3

cs = np.concatenate(([0.0], np.cumsum(x)))   # [0, 1, 3, 7, 15, 31]
print((cs[w:] - cs[:-w]) / w)                # [2.33333333 4.66666667 9.33333333]

# Check against the plain definition
print([x[i:i + w].mean() for i in range(len(x) - w + 1)])   # three values, the same
ベクトル化したら、遅い版と照合する速くても間違っている計算は、遅くても正しい計算より悪いものです。素直なループ版を残し、ベクトル化版を書いて、頼る前にランダムなデータで np.allclose(速い版, 遅い版) と照合しましょう。端のケースにも気を配ります。空配列、要素が1つ、定数の系列(標準偏差が0なので z スコアは0で割ることになる)、nan の値には、クラッシュや黙って誤った数値ではなく、方針の決定が必要です。
  • 配列全体の演算(a + b・a ** 2・np.sqrt(a))がループの代わりになる。条件は np.where
  • ずらしたスライス:p[1:] / p[:-1] - 1。累積和・累積積は cumsum・cumprod
  • axis=0 は行を集約(列ごと)、axis=1 は列を集約(行ごと)。R @ w は行ごとの内積

次はブロードキャストです。形の異なる配列を一緒に扱える規則です。

ラボ:採点される内容

  • simple_returns ですべてのリターンを一度に計算する25点
  • portfolio_returns で毎日のポートフォリオリターンを求める25点
  • moving_average でループなしに系列を平滑化する25点
  • zscore で値を標準化する25点
エクスプローラー · lab-36-1
▾ lab-36-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-36-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-36-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける