Suppose you have an option pricer written in C++, but the people testing it work in Python notebooks. Rewriting the formula in Python would leave you with two implementations to maintain. Instead, we can keep the calculation in C++ and expose a small interface to Python. SWIG generates the glue for that interface.
I’ll use the Derivatives example in my quantitative finance repository. Its src folder contains Options.h, Options.cpp, Options.i, and setup.py; the Python notebook calls the resulting module. The goal is to understand both the financial calculation and what happens between a C++ class and import Options.
The option we are pricing
The C++ class quantero::European takes the spot price $S$, strike $K$, continuously compounded risk-free rate $r$, volatility $\sigma$, time to expiry $T$ in years, and a sign $\psi$. Set $\psi=1$ for a call and $\psi=-1$ for a put. This example assumes a European option with no dividends or foreign interest rate.
The valuation() method implements the call and put in one expression:
Here $N(x)$ is the standard normal cumulative distribution function (CDF). The implementation in Options.cpp uses the error function rather than Boost:
This distinction matters when building the example: the current option pricler does not require Boost for the nor CDF. Older installation notes in the repository describe a Boost-based version of the CDF; installing Boost is unnecessary for the code linked above.
From source code to a Python module
A C++ build usually involves preprocessing headers, compiling source into object files, assembling machine code, and linking objects into a program or library. See this compilation overview for a longer explanation. From the repository’s Derivatives/src directory, you can inspect an intermediate object without creating the Python extension:
g++ -c Options.cpp -o Options.o
nm -C Options.o
objdump -d Options.o
nm -C displays demangled C++ symbols, including methods of quantero::European; objdump -d shows machine instructions. An object file is not a Python module. Nor can we link this particular source directly into an executable with g++ Options.cpp -o options: there is no main() function. For Python, we need a wrapper and a shared extension library instead.
Tell SWIG what to expose
The repository already provides Options.i:
%module Options
%{
#include "Options.h"
%}
%include "Options.h"
The %{ ... %} block inserts the header into the generated C++ wrapper so it can compile. %include "Options.h" tells SWIG which declarations to expose to Python. The module name determines the Python import name. SWIG presents this C++ namespace’s European class as Options.European, as you can see in the notebook.
Hint: expose only what Python needs. Put
%ignoredirectives before%include "Options.h"to exclude individual functions or whole classes. For example, adding%ignore quantero::N;there keeps the CDF helper in C++ while exposingEuropeanand its publicvaluation()method. In a larger header,%ignore quantero::SomeInternalClass;would similarly hide that class. This changes the Python API, not the C++ implementation; rebuild the wrapper after editing the interface.
On Linux, install a C++ compiler, Python development headers, SWIG, and Python build tooling for your chosen interpreter. For example, on Debian or Ubuntu:
sudo apt-get update
sudo apt-get install build-essential python3-dev python3-venv swig
git clone https://github.com/craquinterogo/quantitative_finance.git
cd quantitative_finance/Derivatives/src
python3 -m venv .venv
source .venv/bin/activate
python -m pip install setuptools
swig -c++ -python Options.i
The last command generates Options_wrap.cxx, which bridges Python calls to C++, and Options.py, the Python-facing proxy. It does not compile the extension yet.
Compile the extension
The repository’s setup.py specifies the two C++ sources, Options_wrap.cxx and Options.cpp, and names the native module _Options. Its older distutils.core import predates the removal of distutils from the Python standard library. If you are updating that file for a current Python environment, use setuptools with the same module and sources:
from setuptools import Extension, setup
setup(
name="Options",
version="0.1",
ext_modules=[
Extension("_Options", sources=["Options_wrap.cxx", "Options.cpp"])
],
py_modules=["Options"],
)
Run this from Derivatives/src with the virtual environment active:
python setup.py build_ext --inplace
python -c "import Options; print(Options.European(100, 100, 0.05, 0.20, 1, 1).valuation())"
The printed call premium should be about 10.4506. build_ext --inplace puts the compiled _Options extension next to Options.py, so Python can import both from the current directory. On Linux the extension has a platform-tagged .so suffix; on Windows it is normally .pyd, not a generic .dll. A prebuilt extension is tied to its operating system, architecture, and Python ABI, so distribute or rebuild it for the environment where it will run.
Check the result from Python
In a notebook whose working directory can import Options.py and its matching _Options extension, try both signs:
import Options
call = Options.European(100, 100, 0.05, 0.20, 1, 1)
put = Options.European(100, 100, 0.05, 0.20, 1, -1)
print(f"Call: {call.valuation():.4f}") # 10.4506
print(f"Put: {put.valuation():.4f}") # 5.5735
These inputs also give a useful financial check: call-put parity implies $C-P=S-Ke^{-rT}$, about $4.8771$ here. This helps catch a pricing mistake independently of whether SWIG built successfully.
For this small project, setup.py handles the extension build; CMake is optional, not another required step. The repository also has a CMakeLists.txt that declares an executable from Options.cpp, but that target needs a main() before it can link. If you later add a C++ command-line driver, CMake becomes useful for managing that separate executable. The important boundary today is simpler: C++ owns the formula, SWIG exposes its declarations, and the compiled extension lets Python call the same implementation.