σμ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 AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 10 · Ingénierie Python haute performance›Sujet 10.8·~1 hAvancé

Appeler du C++ avec pybind11

Position dans le parcoursModule 10 sur 11 · Sujet 8 sur 13
10.8 Appeler du C++ avec pybind11
—Score du lab— / 100XP total—Série—

Quand Python a besoin de C++

Une bonne part du code sérieux du monde du trading est du C++ : moteurs d'appariement, gestionnaires de données de marché, bibliothèques de valorisation affinées pendant quinze ans. On veut rarement le réécrire, et rarement renoncer à Python pour la recherche. pybind11 est le pont : une bibliothèque C++ à en-têtes seuls dans laquelle on écrit quelques lignes disant « expose cette fonction C++ à Python sous ce nom », on les compile en bibliothèque partagée, et on l'importe depuis Python comme n'importe quel module. Les arguments et résultats sont convertis pour vous : une liste Python de flottants devient un std::vector<double> et inversement.

# 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

Les types de part et d'autre du pont

Chaque type Python a un équivalent C++ : int est int64_t, float est double, str est std::string, une liste de flottants est std::vector<double> ; tout le reste peut être tenu comme un py::object générique. Inclure <pybind11/stl.h> rend automatiques les conversions des conteneurs standard. Passez les grosses valeurs par référence constante (const std::vector<double>& x) pour que la fonction C++ ne les copie pas, et les petites valeurs comme les nombres par valeur. La conversion d'une liste Python en vecteur copie quand même une fois par appel : pour de grands tableaux, utilisez py::array_t<double>, qui voit directement la mémoire d'un tableau NumPy sans copie.

# 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)

Compilation, GIL et propriété

On compile avec CMake (pybind11_add_module) ou avec pybind11.setup_helpers dans un setup.py, et le résultat est un fichier .so ou .pyd que l'on importe. Deux choses à savoir. D'abord, pendant que le code C++ s'exécute, le verrou global de l'interpréteur (GIL) reste tenu sauf si on le relâche avec py::gil_scoped_release ; un long calcul C++ qui ne touche pas aux objets Python devrait le relâcher pour que d'autres threads Python tournent. Ensuite, la propriété : si le C++ renvoie une référence ou un pointeur vers sa propre mémoire, Python ne doit pas lui survivre (py::return_value_policy contrôle cela), et un plantage sans trace Python signale en général un pointeur pendant. Testez les fonctions liées depuis Python contre une version Python pure, exactement comme dans les labos précédents.

# 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
Un plantage n'est pas une exception PythonUn bug dans du code C++ peut faire tomber tout le processus Python par une erreur de segmentation, sans trace, et dans un système de trading cela signifie que le processus a disparu. Validez à la frontière les tailles et valeurs reçues et lancez une exception C++ (pybind11 la transforme en exception Python), compilez avec les avertissements et les sanitizers pendant le développement, et gardez la surface C++ réduite : quelques fonctions bien testées qui font le gros travail, Python faisant l'orchestration.
  • pybind11 expose des fonctions C++ à Python avec PYBIND11_MODULE et des lignes m.def(...) ; le résultat est un module .so/.pyd importable.
  • Les types correspondent : int→int64_t, float→double, str→std::string, listes→std::vector ; passez les grosses valeurs par référence constante et les tableaux NumPy en py::array_t.
  • Relâchez le GIL pour les longs travaux en C++ pur, surveillez qui possède la mémoire renvoyée, validez les entrées à la frontière et testez contre une version Python pure.

À suivre : les threads et le GIL, ce que les threads Python peuvent et ne peuvent pas faire en parallèle.

Lab : ce qui est noté

  • Associer types Python et types C++ avec cpp_type25 pts
  • Déclarer une fonction avec cpp_signature25 pts
  • Exposer une fonction avec binding_line25 pts
  • Assembler le module avec module_source25 pts
Explorateur · lab-124-1
▾ lab-124-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-124-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-124-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub