Usage

quantplus is designed around a straightforward numerical-finance workflow: price an option using a CRR binomial lattice, compare the result against the Black-Scholes closed form, and inspect tree values when needed for debugging or visualization.

The package is intentionally small and exposes a few top-level functions:

Importing the package

import quantplus as qp

This is the standard import pattern used in the examples below.

How the package is organized

The source tree is organized as follows:

quantplus/
├── __init__.py
├── _crr_pricer.pyx
├── plotting.py
├── csrc/
│   ├── crr_core.cpp
│   └── crr_core.h
└── tests/
    └── test_quantplus.py

A few important points:

  • quantplus/__init__.py re-exports the public Python functions.

  • quantplus/_crr_pricer.pyx is the Cython bridge between Python and native C++ code.

  • quantplus/plotting.py provides the optional interactive Matplotlib view.

  • quantplus/csrc/crr_core.cpp contains the numerical implementation for the CRR and Black-Scholes pricing logic.

  • quantplus/tests/test_quantplus.py contains regression and validation tests.

This split is useful because Python is best for ergonomics and API design, while C++ is excellent for performance-sensitive root-finding, loops, and lattice operations.

CRR option pricing

The core function is quantplus.crr_price(). It values a European or American option by building a binomial tree and recursively evaluating the value at each node.

price = qp.crr_price(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    N=200,
    q=0.01,
    is_call=True,
    american=False,
)

print(price)

Parameter meaning

  • S0: current underlying price

  • K: strike price

  • r: continuously compounded risk-free rate

  • sigma: volatility

  • T: time to maturity in years

  • N: number of time steps in the CRR tree

  • q: dividend yield or cost-of-carry adjustment

  • is_call: True for a call, False for a put

  • american: True for American exercise, False for European exercise

The implementation validates common edge cases such as non-positive time to maturity, non-positive step counts, and negative volatility.

Threshold-pruned pricing

The CRR pricer can restrict the tree to an inclusive stock-price band. Bounds apply to expiry nodes by default. With every_step=True, intermediate nodes outside the band are also pruned. Earlier nodes are considered alive only when they can reach an allowed terminal node (and, in every-step mode, are themselves inside the band).

bounded_call = qp.crr_price(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    N=200,
    q=0.01,
    is_call=True,
    american=True,
    S_lower=80.0,
    S_upper=120.0,
    every_step=False,
)

every_step_call = qp.crr_price(
    S0=100.0, K=100.0, r=0.05, sigma=0.20, T=1.0, N=200,
    q=0.01, is_call=True, american=True,
    S_lower=80.0, S_upper=120.0, every_step=True,
)

When both child nodes survive, the usual risk-neutral probabilities are used. When only one child survives, it receives the remaining probability. Bounds that are reversed or leave no terminal node reachable from the root raise ValueError.

European vs American options

The difference between European and American exercise is controlled by the american flag.

european_call = qp.crr_price(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    N=200,
    q=0.01,
    is_call=True,
    american=False,
)

american_call = qp.crr_price(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    N=200,
    q=0.01,
    is_call=True,
    american=True,
)

print(european_call)
print(american_call)

American options are at least as valuable as their European counterparts because exercising early is a right, not an obligation.

Black-Scholes benchmark

For closed-form comparison, use quantplus.black_scholes_price(). This implements the standard Black-Scholes-Merton formula and is often used to check whether the CRR price converges as the lattice becomes finer.

bs_price = qp.black_scholes_price(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    q=0.01,
    is_call=True,
)

print(bs_price)

Interesting fact: as N becomes large, the CRR binomial price approaches the Black-Scholes value. This makes the CRR model intuitive and also gives a useful sanity check during testing.

Inspecting the tree

The quantplus.crr_tree() helper returns the root price, the terminal stock values, and the terminal option values for the lattice. This is useful for debugging, plotting, or understanding how the payoff evolves across the tree.

root, stocks, values = qp.crr_tree(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    N=5,
    q=0.01,
    is_call=True,
)

print(root)
print(stocks)
print(values)

The call returns three items:

  • root: the option value at the tree root

  • stocks: the terminal stock prices at maturity

  • values: the terminal option payoffs at maturity

Interactive tree plot

Use quantplus.plot_crr_tree() to visualize the lattice and adjust the thresholds interactively. The red and green bars set the upper and lower bounds; the checkbox switches between expiry-only and every-step filtering.

fig, ax = qp.plot_crr_tree(
    S0=100.0,
    K=100.0,
    r=0.05,
    sigma=0.20,
    T=1.0,
    N=30,
    q=0.01,
    is_call=True,
    american=True,
    S_lower=80.0,
    S_upper=120.0,
    show=True,
)

The function returns the Matplotlib (figure, axes) pair. Set show=False to construct the figure without starting the GUI event loop. Matplotlib is needed only when this plotting function is called.

Example: put option pricing

put_price = qp.crr_price(
    S0=90.0,
    K=100.0,
    r=0.03,
    sigma=0.25,
    T=2.0,
    N=500,
    q=0.00,
    is_call=False,
    american=False,
)

print(f"European put price: {put_price:.4f}")

C++ and Python working together

The package is a good example of a hybrid numerical library:

  1. Python defines the public API and validation logic.

  2. The Cython bridge exposes a low-level extension module.

  3. The native C++ implementation performs the numerical calculations.

  4. The compiled extension is loaded by Python at import time.

The C++ layer is compiled as part of the extension module using setup.py and Cython. This allows the runtime to call the C++ pricing functions directly while keeping the end-user experience simple and Pythonic.

The performance benefit is especially noticeable in repeated lattice calculations, where the hot loop is executed in compiled C++ rather than pure Python.