σμ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 ロードマップ›モジュール 10 · 高性能Pythonエンジニアリング›トピック 10.6·~1 時間上級

Numba

ロードマップ上の位置モジュール 10/11 · トピック 6/13
10.6 Numba
—ラボスコア— / 100合計XP—連続日数—

書いたループをコンパイルする

計算の中には、指数加重平均、ドローダウン、経路依存オプションのペイオフのように、各ステップが前のステップに依存する、本質的にループのものがあります。NumPy ではベクトル化できず、100万要素の Python ループは、各ステップがインタプリタを通るため遅くなります。Numba はジャストインタイムコンパイラです。関数の上に @njit を付けると、最初の呼び出し時に、渡された型に合わせて Python のループが機械語に翻訳されます。以降の呼び出しは C に近い速度で動き、コードを変えずに、素の Python の同じループより50〜100倍速くなることもよくあります。

# An exponentially weighted average is a loop: each value depends on the one before
import numpy as np

try:
    from numba import njit                           # one decorator compiles the function when it is installed
except ImportError:
    def njit(func):                                  # without Numba the same code simply runs as Python
        return func

@njit
def ewma(values, alpha):
    out = np.empty(len(values))
    out[0] = values[0]
    for i in range(1, len(values)):
        out[i] = alpha * values[i] + (1.0 - alpha) * out[i - 1]
    return out

print(ewma(np.array([1.0, 2.0, 3.0]), 0.5))          # [1.   1.5  2.25]

Numba が好むスタイル

@njit が選ぶ nopython モードでは、Numba は Python のサブセットだけをコンパイルし、それ以外は黙って遅いコードに戻るのではなく拒否します。数値、NumPy 配列、タプル、単純なループ、if、NumPy の関数の多くを理解します。pandas、任意の Python オブジェクト、型が混在する辞書は理解しません。そのためホットな関数は素朴なスタイルで書きます。配列とスカラーを受け取り、出力を np.empty や np.zeros で確保し、for i in range(n) でループし、スカラーの累積変数(total、peak)を持ちます。毎回同じ型の引数で呼び出してください。型の組み合わせが変わると新たなコンパイルが起きます。

# A drawdown is a running peak and a running worst case: two scalars and one loop
import numpy as np

try:
    from numba import njit
except ImportError:
    def njit(func):
        return func

@njit
def max_drawdown(equity):
    peak = 0.0
    worst = 0.0
    for i in range(len(equity)):
        value = equity[i]
        if value > peak:
            peak = value
        drawdown = (peak - value) / peak
        if drawdown > worst:
            worst = drawdown
    return worst

curve = np.array([100.0, 120.0, 90.0, 130.0, 117.0])
print(max_drawdown(curve))                           # 0.25   from 120 down to 90
print(max_drawdown(np.array([1.0, 2.0, 3.0])))       # 0.0    a curve that only rises

コンパイル時間、キャッシュ、Numba が役立たない場面

最初の呼び出しでコンパイル代を払います(1秒以上かかることもあります)。そのため Numba 関数は、小さなデータで一度呼び出してから計測し、@njit(cache=True) で実行間にコンパイル済みコードを保持します。高速化には十分な仕事量が必要です。3つの数値に対して100万回呼ばれる関数は、ループではなく呼び出しのオーバーヘッドに時間を使います。Numba が助けるのは配列に対するループです。すでに NumPy の1回の呼び出しであるコード、データベースの待ち時間、悪いアルゴリズムには何もしません。@njit(parallel=True) と prange で独立した反復をコアに分散でき、fastmath=True は浮動小数の厳密さを少し犠牲にして速度を得ます。結果が許容誤差内に収まると確認してから有効にしましょう。

# Counting crossings: Numba compiles any loop like this, so keep the logic in scalars
import numpy as np

try:
    from numba import njit
except ImportError:
    def njit(func):
        return func

@njit(cache=False)
def count_crossovers(fast, slow):
    count = 0
    previous = 0.0
    for i in range(len(fast)):
        gap = fast[i] - slow[i]
        if i > 0 and previous * gap < 0:
            count += 1
        previous = gap
    return count

fast = np.array([1.0, 3.0, 2.0, 1.0, 4.0])
slow = np.full(5, 2.0)
print(count_crossovers(fast, slow))                  # 2     below, above, touch, below, above: two strict sign changes
# Options change how a function is compiled; here they are ignored if Numba is not installed
import numpy as np

try:
    from numba import njit, prange
except ImportError:
    prange = range

    def njit(*args, **kwargs):
        if args and callable(args[0]):
            return args[0]
        return lambda func: func

@njit(cache=True)                                    # keep the compiled code on disk between runs
def total_notional(prices, quantities):
    total = 0.0
    for i in prange(len(prices)):                    # prange runs in parallel only with parallel=True
        total += prices[i] * quantities[i]
    return total

prices = np.array([184.25, 410.5, 99.0])
quantities = np.array([100.0, 50.0, 200.0])
print(total_notional(prices, quantities))            # 58750.0
print(total_notional(prices, quantities) == float(prices @ quantities))     # True  same answer as the vectorised dot product
速度だけでなく答えも確認するコンパイルされたループには新しい形の誤りがあります。Python の無制限の整数ではなく整数オーバーフローが起きる、float64 のつもりが float32 配列が黙って使われる、範囲外のインデックスがエラーではなくゴミを読む、などです。コンパイル結果を、ランダムな入力で素の Python 版と比べましょう(Numba が保持する fn.py_func を使います)。これらのラボのテストがしているとおりです。開発中は境界チェックを有効にします。
  • @njit は最初の呼び出しで Python 関数を機械語にコンパイルする。前のステップに依存するループもベクトル化なしで速くなる
  • nopython モード向けに書く。配列とスカラー、事前に確保した出力、for i in range(n)、スカラーの累積変数。pandas や型混在のコンテナは使わない
  • ウォームアップ後に計測し、cache=True を使う。すでに NumPy 1回の呼び出しのコードには効果を期待せず、結果は素の Python 版と照合する

次は Cython です。型を追加し、拡張モジュールをビルドして Python のループをコンパイルするもう1つの方法です。

ラボ:採点される内容

  • rolling_mean で累計を持ち回る25点
  • max_drawdown でピークを追跡する25点
  • count_crossovers で交差を数える25点
  • ewma で出力配列を埋める25点
エクスプローラー · lab-122-1
▾ lab-122-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-122-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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

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

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

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