Creating python bindings in c++
Push it to the limit
Finally reaching those speed boundaries in python but still want flexibility of data manip and scripting? Write your core code in c++ then call it in python.
But Camron, why do this c++ is hard? Because it's insanely faster. Want threading? No more GIL, want to maximize all the powers of the ancients in your CPU? Use c++.
Step 1.
Create a Cmake project. This involves creating a CMakeList.txt and a Setup.py that will allow for you to build the project.
set(CMAKE_CUDA_STANDARD 17)
find_package(CUDAToolkit REQUIRED)
include_directories(${CUDAToolkit_INCLUDE_DIRS})
include(FetchContent)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.6.1 # Optionally, specify a version tag
)
FetchContent_MakeAvailable(pybind11)
file(GLOB_RECURSE LIB_SOURCES
"src/*.cpp"
"src/**/*.cpp"
"src/*.cu"
"src/**/*.cu"
)
include_directories(src)
include_directories(src/nodes)
add_library(cuda_options SHARED ${LIB_SOURCES}
src/black_scholes.cpp
src/black_scholes.h)
target_link_libraries(cuda_options PRIVATE ${CUDAToolkit_LIBRARIES})
pybind11_add_module(options_py bindings/bindings.cpp)
target_link_libraries(options_py PRIVATE cuda_options pybind11::module ${CUDAToolkit_LIBRARIES})
set_target_properties(cuda_options PROPERTIES
CUDA_SEPARABLE_COMPILATION ON
CUDA_ARCHITECTURES "80" # Change to your GPU's compute capability
)
Step 2.
In your CMakeLists.txt we need to add a pybind11 import and link it to our binary.
cmake_minimum_required(VERSION 3.22)
project(cuda_options LANGUAGES CXX CUDA)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CUDA_STANDARD 17)
find_package(CUDAToolkit REQUIRED)
include_directories(${CUDAToolkit_INCLUDE_DIRS})
include(FetchContent)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.6.1 # Optionally, specify a version tag
)
FetchContent_MakeAvailable(pybind11)
file(GLOB_RECURSE LIB_SOURCES
"src/*.cpp"
"src/**/*.cpp"
"src/*.cu"
"src/**/*.cu"
)
include_directories(src)
include_directories(src/nodes)
add_library(cuda_options SHARED ${LIB_SOURCES}
src/black_scholes.cpp
src/black_scholes.h)
target_link_libraries(cuda_options PRIVATE ${CUDAToolkit_LIBRARIES})
pybind11_add_module(options_py bindings/bindings.cpp)
target_link_libraries(options_py PRIVATE cuda_options pybind11::module ${CUDAToolkit_LIBRARIES})
set_target_properties(cuda_options PROPERTIES
CUDA_SEPARABLE_COMPILATION ON
CUDA_ARCHITECTURES "80" # Change to your GPU's compute capability
)
Step 3.
Create a bindings file in c++ that will expose any of your c++ code to python.
//
// Created by camrongodbout on 7/1/24.
//
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../src/monte_carlo_options.h"
#include "black_scholes.h"
float calculateImpliedVolatility(float market_price, float s, float k, float t, float r, bool is_call, float tol, int max_iterations, int n_paths, int n_steps) {
MonteCarloOptions mc;
float sigma_low = 0.001f;
float sigma_high = 10.0f;
for (int i = 0; i < max_iterations; ++i) {
float sigma = (sigma_low + sigma_high) / 2.0f;
float price_estimate = mc.calculate_option_price(s, k, t, r, sigma, is_call, n_paths, n_steps);
if (fabs(price_estimate - market_price) < tol) {
return sigma;
} else if (price_estimate < market_price) {
sigma_low = sigma;
} else {
sigma_high = sigma;
}
}
return (sigma_low + sigma_high) / 2.0f;
}
namespace py = pybind11;
PYBIND11_MODULE(options_py, m) {
m.doc() = "Python bindings for custom C++ library";
m.def("implied_vol_monte_carlo", &calculateImpliedVolatility, "Calculate implied volatility using Monte Carlo method",
pybind11::arg("market_price"), pybind11::arg("s"), pybind11::arg("k"), pybind11::arg("t"), pybind11::arg("r"),
pybind11::arg("is_call"), pybind11::arg("tol"), pybind11::arg("max_iterations"), pybind11::arg("n_paths"), pybind11::arg("n_steps"));
m.def("calculateImpliedVolatilityBlackScholes", &find_implied_volatility, "Find implied volatility using Black-Scholes model",
py::arg("option_prices"), py::arg("stock_prices"), py::arg("strikes"), py::arg("times"),
py::arg("risk_free_rate"), py::arg("isCalls"), py::arg("tolerance"), py::arg("max_iterations"));
}
And there you have it. This is a simple example from a project I made using c++ to monte-carlo method for calculating implied volatility on a GPU.