σμ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.8·~1 時間上級

pybind11でC++を呼び出す

ロードマップ上の位置モジュール 10/11 · トピック 8/13
10.8 pybind11でC++を呼び出す
—ラボスコア— / 100合計XP—連続日数—

Python が C++ を必要とするとき

トレーディング業界の本格的なコードの多くは C++ です。マッチングエンジン、市場データのハンドラ、15年かけて磨かれた評価ライブラリなどです。書き直したいことはまれで、リサーチのために Python を手放したいこともまれです。pybind11 はその橋渡しです。ヘッダーのみの C++ ライブラリで、「この C++ 関数をこの名前で Python に公開する」という数行を書き、共有ライブラリにコンパイルして、Python から普通のモジュールのように import します。引数と結果は自動で変換され、Python の浮動小数のリストは std::vector<double> になり、また戻ります。

# A C++ function and the pybind11 file that exposes it to Python (shown here as text)
cpp = """\
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>

namespace py = pybind11;

double mean(const std::vector<double>& x) {
    double total = 0.0;
    for (double v : x) total += v;
    return x.empty() ? 0.0 : total / x.size();
}

PYBIND11_MODULE(fastmath, m) {
    m.def("mean", &mean, "Mean of a list of floats");
}
"""
lines = cpp.splitlines()
print(lines[-2].strip())                             # m.def("mean", &mean, "Mean of a list of floats");
print(len(lines))                                    # 15

橋を渡る型

Python の各型には C++ の対応物があります。int は int64_t、float は double、str は std::string、浮動小数のリストは std::vector<double> で、それ以外は汎用の py::object として保持できます。<pybind11/stl.h> を include すると、標準コンテナの変換が自動になります。大きな値はconst 参照(const std::vector<double>& x)で渡して C++ 関数にコピーさせず、数値のような小さな値は値渡しにします。Python のリストからベクトルへの変換は呼び出しごとに1回コピーするので、大きな配列には py::array_t<double> を使います。NumPy 配列のメモリをコピーなしで直接参照できます。

# Choosing the C++ type of each argument; big values are passed by const reference
CPP_TYPES = {"int": "int64_t", "float": "double", "str": "std::string", "list[float]": "std::vector<double>"}

def cpp_type(py_type):
    return CPP_TYPES.get(py_type, "py::object")

def cpp_signature(name, params, returns):
    result = "void" if returns is None else cpp_type(returns)
    arguments = []
    for param, py_type in params:
        ctype = cpp_type(py_type)
        arguments.append(f"const {ctype}& {param}" if ctype.startswith("std::") else f"{ctype} {param}")
    return f"{result} {name}({', '.join(arguments)})"

print(cpp_signature("mean", [("x", "list[float]"), ("n", "int")], "float"))      # double mean(const std::vector<double>& x, int64_t n)
print(cpp_signature("label", [("symbol", "str")], "str"))                       # std::string label(const std::string& symbol)

ビルド、GIL、所有権

ビルドは CMake(pybind11_add_module)か、setup.py の pybind11.setup_helpers で行い、結果として import できる .so や .pyd ファイルができます。知っておくべきことが2つあります。1つ目は、C++ のコードが動いている間、py::gil_scoped_release で手放さない限り Python のグローバルインタプリタロック(GIL)が保持されることです。Python オブジェクトに触れない長い C++ 計算では解放して、他の Python スレッドが動けるようにします。2つ目は所有権です。C++ が自分のメモリへの参照やポインタを返す場合、Python がそれより長く生きてはいけません(py::return_value_policy で制御します)。Python のトレースバックなしのクラッシュは、たいていダングリングポインタです。バインドした関数は、これまでのラボと同じく、純粋な Python 版と照合してテストしましょう。

# Binding lines and the module file, generated from a table of functions
BACKSLASH = chr(92)

def binding_line(py_name, cpp_name, doc):
    escaped = doc.replace(BACKSLASH, BACKSLASH + BACKSLASH).replace('"', BACKSLASH + '"')
    return f'm.def("{py_name}", &{cpp_name}, "{escaped}");'

def module_source(module, functions):
    lines = ["#include <pybind11/pybind11.h>", "#include <pybind11/stl.h>", "", "namespace py = pybind11;", ""]
    lines.append(f"PYBIND11_MODULE({module}, m) {{")
    for py_name, cpp_name, doc in functions:
        lines.append("    " + binding_line(py_name, cpp_name, doc))
    lines.append("}")
    return "\n".join(lines) + "\n"

source = module_source("pricing", [("mean", "mean_impl", "Mean of x"), ("price", "bs_price", 'Black-Scholes "call" price')])
print(source.splitlines()[-3])                       #     m.def("mean", &mean_impl, "Mean of x");
print(source.splitlines()[-2])                       #     m.def("price", &bs_price, "Black-Scholes \"call\" price");
# From Python the built module is used like any other; this stand-in has the same interface
import types

def mean(x):
    """Mean of a list of floats"""
    return sum(x) / len(x) if x else 0.0

fastmath = types.SimpleNamespace(mean=mean)          # stands in for `import fastmath` after the build
print(fastmath.mean([100.0, 102.0, 101.0]))          # 101.0
print(fastmath.mean.__doc__)                         # Mean of a list of floats   the doc string given to m.def
print(fastmath.mean([]))                             # 0.0   what the C++ code returns for an empty vector
クラッシュは Python の例外ではないC++ コードのバグは、トレースバックなしのセグメンテーション違反で Python プロセス全体を落とすことがあり、トレーディングシステムではプロセスが消えることを意味します。境界で受け取ったサイズと値を検証して C++ の例外を投げ(pybind11 が Python の例外に変換します)、開発中は警告とサニタイザーを有効にしてコンパイルし、C++ 側の表面積は小さく保ちます。重い処理をする、よくテストされた少数の関数を C++ に置き、オーケストレーションは Python に任せます。
  • pybind11 は PYBIND11_MODULE と m.def(...) の行で C++ 関数を Python に公開する。結果は import できる .so/.pyd モジュール
  • 型の対応は int→int64_t、float→double、str→std::string、リスト→std::vector。大きな値は const 参照、NumPy 配列は py::array_t で渡す
  • 長い純粋な C++ 処理では GIL を解放し、返したメモリの所有者に注意し、境界で入力を検証し、純粋な Python 版と照合してテストする

次はスレッドと GIL です。Python のスレッドが並列にできること、できないことを学びます。

ラボ:採点される内容

  • cpp_type で Python の型を C++ の型に対応づける25点
  • cpp_signature で関数を宣言する25点
  • binding_line で関数を公開する25点
  • module_source でモジュールを組み立てる25点
エクスプローラー · lab-124-1
▾ lab-124-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-124-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

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

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

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

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

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

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