2 min read

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.