Getting started¶
The same pricing task in Python and in Rust. The Python API is the thin binding; the Rust
core is where the numerics live. Both snippets below are the real, runnable example files
from the repository (example/python and crates/libitofin/examples), included verbatim, so what you
read here is exactly what runs.
Price a European option¶
Build a Black-Scholes market, wrap it in a vanilla option, attach the analytic engine and read the value plus the greeks.
example/python/european_option.py
"""Price a European option and read its greeks.
This is the "hello world" of itofin: build a Black-Scholes market, wrap it in a
`VanillaOption`, attach the analytic engine and read the value plus every greek.
The numbers below reproduce the pricing gate in
`crates/itofin-py/tests/test_european_option.py` (row 1), so the printed NPV of
2.1333684449161985 is a self-check: if it drifts, the market was wired wrong.
Run it with:
python example/python/european_option.py
"""
# plugins
# itofin library
from itofin import Settings
from itofin.instruments import OptionType, VanillaOption
from itofin.processes import BlackScholesProcess
from itofin.time import Date, DayCounter
def main() -> None:
# D5: one Settings object holds the evaluation date and is threaded into
# every instrument. There is no global "today"; an unset date is an error,
# not a silent fall back to the system clock.
settings = Settings()
today = Date(15, 6, 2026)
settings.set_evaluation_date(today)
# A generalized Black-Scholes process from scalar market data:
# spot 60, risk-free 8%, dividend yield 0%, volatility 30%,
# all quoted on an Actual/360 day count as of `today`.
day_counter = DayCounter.actual360()
process = BlackScholesProcess(
60.0, # spot
0.08, # risk-free rate
0.0, # dividend yield
0.30, # volatility
today, # reference date
day_counter,
)
# A European call struck at 65, expiring 90 calendar days out.
# `Date + int` advances by days (not by a Period).
option = VanillaOption(OptionType.Call, 65.0, today + 90, settings)
# Attaching the process installs the analytic European engine.
option.set_engine(process)
# NPV plus the full greek set. Each read reprices lazily off the process.
print("European call, K=65, 90d, spot=60, vol=30%, r=8%")
print(f" NPV = {option.npv():.10f}")
print(f" delta = {option.delta():.10f}")
print(f" gamma = {option.gamma():.10f}")
print(f" theta = {option.theta():.10f}")
print(f" vega = {option.vega():.10f}")
print(f" rho = {option.rho():.10f}")
print(f" dividend_rho = {option.dividend_rho():.10f}")
if __name__ == "__main__":
main()
crates/libitofin/examples/european_option.rs
//! Price a European option with the AnalyticEuropeanEngine and print
//! NPV + greeks (delta, gamma, vega, theta, rho).
//!
//! Mirrors the working `mixed_day_counters` M1-slice test in
//! `crates/libitofin/src/pricingengines/vanilla/mod.rs`. Every construction
//! step is the same one the crate's own tests exercise.
use libitofin::exercise::EuropeanExercise;
use libitofin::handle::Handle;
use libitofin::instrument::Instrument; // brings `npv`, `base_mut` into scope
use libitofin::instruments::{EuropeanOption, PlainVanillaPayoff};
use libitofin::interestrate::Compounding;
use libitofin::option::OptionType;
use libitofin::pricingengine::PricingEngine;
use libitofin::pricingengines::AnalyticEuropeanEngine;
use libitofin::processes::BlackScholesMertonProcess;
use libitofin::quotes::{Quote, SimpleQuote};
use libitofin::settings::Settings;
use libitofin::shared::{Shared, SharedMut, shared, shared_mut};
use libitofin::termstructures::volatility::{BlackConstantVol, BlackVolTermStructure};
use libitofin::termstructures::yields::FlatForward;
use libitofin::termstructures::yieldtermstructure::YieldTermStructure;
use libitofin::time::date::{Date, Month};
use libitofin::time::daycounters::actual360::Actual360;
use libitofin::time::frequency::Frequency;
use libitofin::types::Real;
fn main() {
// --- Market date. D5: the evaluation date is set explicitly on an owned
// Settings, not read from a global clock. ---
let today = Date::new(15, Month::June, 2026);
let expiry = today + 146; // Date + i64 shifts by calendar days
let spot: Real = 100.0;
let strike: Real = 105.0;
let q_rate: Real = 0.04; // continuous dividend yield
let r_rate: Real = 0.06; // continuous risk-free rate
let vol: Real = 0.20; // flat Black vol
// Settings is shared (Rc) so the option can register against its
// evaluation-date observable and recompute if the date changes.
let settings = shared(Settings::new());
settings.set_evaluation_date(today);
let dc = Actual360::new(); // DayCounter used for all three curves
// --- Black-Scholes-Merton process: spot quote + dividend curve +
// risk-free curve + Black vol surface, each wrapped in a Handle so it
// can be relinked live. The engine discounts on the risk-free curve
// embedded here. ---
let process = shared(BlackScholesMertonProcess::new(
Handle::new(shared(SimpleQuote::new(spot)) as Shared<dyn Quote>),
Handle::new(shared(FlatForward::with_rate(
today,
q_rate,
dc.clone(),
Compounding::Continuous,
Frequency::Annual,
)) as Shared<dyn YieldTermStructure>),
Handle::new(shared(FlatForward::with_rate(
today,
r_rate,
dc.clone(),
Compounding::Continuous,
Frequency::Annual,
)) as Shared<dyn YieldTermStructure>),
Handle::new(shared(BlackConstantVol::new(today, None, vol, dc.clone()))
as Shared<dyn BlackVolTermStructure>),
));
// --- Instrument: a plain-vanilla call struck at 105, European exercise. ---
let payoff = shared(PlainVanillaPayoff::new(OptionType::Call, strike));
let exercise = shared(EuropeanExercise::new(expiry));
let mut option = EuropeanOption::new(payoff, exercise, Shared::clone(&settings));
// --- Attach the analytic engine. It is a SharedMut (Rc<RefCell<..>>)
// because pricing mutates the engine's cached results. `base_mut()` and
// `set_pricing_engine` come from the Instrument trait / InstrumentBase. ---
let engine = shared_mut(AnalyticEuropeanEngine::new(Shared::clone(&process)));
option
.base_mut()
.set_pricing_engine(engine as SharedMut<dyn PricingEngine>);
// --- Results. Each accessor triggers `calculate()` lazily and returns a
// Result (D4: explicit errors, e.g. if no engine/exercise were set). ---
println!("NPV = {:.6}", option.npv().unwrap());
println!("delta = {:.6}", option.delta().unwrap());
println!("gamma = {:.6}", option.gamma().unwrap());
println!("vega = {:.6}", option.vega().unwrap());
println!("theta = {:.6}", option.theta().unwrap());
println!("rho = {:.6}", option.rho().unwrap());
}
Run them with:
More worked examples¶
Every example ships in both languages with a matching filename. Browse the full set:
| Topic | Python | Rust |
|---|---|---|
| European option | european_option.py |
european_option.rs |
| Monte Carlo | monte_carlo.py |
monte_carlo.rs |
| Vanilla swap | vanilla_swap.py |
vanilla_swap.rs |
| Yield curve | yield_curve.py |
yield_curve.rs |
| Credit CDS | credit_cds.py |
credit_cds.rs |
| ISDA CDS | isda_cds.py |
isda_cds.rs |
| Inflation swap | inflation_swap.py |
inflation_swap.rs |
| YoY inflation cap/floor | yoy_inflation_capfloor.py |
yoy_inflation_capfloor.rs |