itofin
Idiomatic Python bindings over the libitofin Rust core, a ground-up port of QuantLib for pricing, risk, and calibration. The heavy numerics run in Rust; Python drives them. The pricing snippets below reproduce the cached Rust test oracles exactly; the abbreviated calibration snippets converge to the same targets within the tests' calibration tolerance (the full matrices live in tests/).
The API mirrors QuantLib's ql/ layout: types live in submodules (itofin.time, itofin.instruments, itofin.models, ...), while Settings and ItofinError stay at the top level.
Source API guides: currency fields, currency presets including TWD, and Black FX delta conventions. These helpers do not add currency rounding, conversion or a new option pricing engine.
Standalone constant-parameter GSR process bindings expose forward-measure transitions, not a fitted GSR model.
Pricing and process binding guides
| Scope | Guide | Example |
|---|---|---|
| Ordinary bond coupon helpers | BPS and ATM rate | Example |
| Price-fed bond ATM | Price-fed ATM | Example |
| European asset-or-nothing FD pricing | Asset digitals | Example |
| European signed gap FD pricing | Gap options | Example |
| European percentage-strike FD pricing | Percentage-strike options | Example |
| Amortizing fixed-rate bonds | Fixed amortization | Example |
| Amortizing plain-Ibor bonds | Ibor amortization | Example |
| European analytic cash digitals | Cash-or-nothing | Example |
| Standalone Gaussian state process | MF state | Example |
| Standalone live-curve forward process | Hull-White forward | Example |
| Constant-notional Ibor cross-currency swaps | Xccy basis | Example |
These helpers do not complete the wider BondFunctions overload set. The
process facades are not fitted models; cash digitals cover European analytic
Black-Scholes pricing, and Xccy spot is an immutable snapshot. Asset/gap FD
pricing retains documented grid limitations. Amortizing constructors cover only
positive, nonincreasing balances.
Install
pip install itofin
Python versions
itofin ships a single abi3 wheel that runs on CPython 3.10, 3.11, 3.12, and 3.13+. Python 3.13 is the primary, tested target; 3.10 through 3.12 are supported through the stable ABI.
Price a European option and its greeks
from itofin import Settings
from itofin.instruments import OptionType, VanillaOption
from itofin.processes import BlackScholesProcess
from itofin.time import Date, DayCounter
s = Settings()
s.set_evaluation_date(Date(15, 6, 2026))
dc = DayCounter.actual360()
process = BlackScholesProcess(60.0, 0.08, 0.0, 0.30, Date(15, 6, 2026), dc)
option = VanillaOption(OptionType.Call, 65.0, Date(15, 6, 2026) + 90, s)
option.set_engine(process)
print(f"NPV {option.npv():.10f}")
print(f"delta {option.delta():.10f}")
print(f"vega {option.vega():.10f}")
print(f"rho {option.rho():.10f}")
NPV 2.1333684449
delta 0.3724827980
vega 11.3515440535
rho 5.0538998582
Price under Heston and calibrate to a flat-vol surface
Semi-analytic Heston pricing:
from itofin import Settings
from itofin.instruments import OptionType, VanillaOption
from itofin.models import HestonModel
from itofin.processes import HestonProcess
from itofin.time import Date, DayCounter
s = Settings()
s.set_evaluation_date(Date(27, 12, 2004))
dc = DayCounter.actual_actual_isda()
# HestonProcess(risk_free, dividend, spot, v0, kappa, theta, sigma, rho, reference_date, day_counter)
process = HestonProcess(
0.0225, 0.02, 1.0, 0.1, 3.16, 0.09, 0.4, -0.2, Date(27, 12, 2004), dc
)
model = HestonModel(process)
option = VanillaOption(OptionType.Call, 1.05, Date(28, 3, 2005), s)
option.set_heston_engine(model, 64)
print(f"Heston NPV {option.npv():.10f}")
Heston NPV 0.0404774515
Calibrating the model to a flat 10% vol surface drives the vol-of-vol sigma toward zero and both v0 and theta toward the flat variance (0.10 squared = 0.01). The snippet below fits a 3-maturity by 3-strike grid; the full 21-helper matrix and its tighter tolerances live in tests/test_heston_calibration.py.
import math
from itofin import Settings
from itofin.models import CalibrationErrorType, HestonModel, HestonModelHelper
from itofin.optimization import EndCriteria, LevenbergMarquardt
from itofin.processes import HestonProcess
from itofin.termstructures import FlatForward
from itofin.time import Calendar, Date, DayCounter, Period
s = Settings()
s.set_evaluation_date(Date(15, 1, 2026))
dc = DayCounter.actual360()
ref = Date(15, 1, 2026)
risk_free = FlatForward(ref, 0.04, dc)
dividend = FlatForward(ref, 0.50, dc)
VOL = 0.10
helpers = []
for n in (1, 2, 3):
tau = float(n)
forward = dividend.discount(tau) / risk_free.discount(tau) # spot = 1.0
for moneyness in (-1.0, 0.0, 1.0):
strike = forward * math.exp(-moneyness * VOL * math.sqrt(tau))
helpers.append(HestonModelHelper(
Period(n, "Years"),
Calendar.null_calendar(),
1.0, # spot
strike,
VOL, # 10% flat vol
0.04, # risk-free
0.50, # dividend yield
CalibrationErrorType.RelativePriceError,
ref,
dc,
s,
))
process = HestonProcess(0.04, 0.50, 1.0, 0.01, 0.2, 0.02, 0.5, -0.75, ref, dc)
model = HestonModel(process)
method = LevenbergMarquardt(1e-8, 1e-8, 1e-8, False)
end_criteria = EndCriteria(400, 40, 1e-8, 1e-8, 1e-8)
model.calibrate(helpers, method, end_criteria, 96)
print(f"sigma {model.sigma():.6f}")
print(f"v0 {model.v0():.6f}")
print(f"theta {model.theta():.6f}")
sigma 0.000216
v0 0.010000
theta 0.010000
Calibrate Hull-White to a swaption matrix
from itofin import Settings
from itofin.indexes import Euribor
from itofin.models import CalibrationErrorType, HullWhite, SwaptionHelper
from itofin.optimization import EndCriteria, LevenbergMarquardt
from itofin.termstructures import FlatForward
from itofin.time import Date, DayCounter, Period
s = Settings()
s.set_evaluation_date(Date(15, 2, 2002))
curve = FlatForward(Date(19, 2, 2002), 0.04875825, DayCounter.actual365_fixed())
model = HullWhite(curve, 0.1, 0.01)
index = Euribor.six_months(curve, s)
# (option tenor, swap tenor, Black vol); the full co-terminal matrix is in the tests.
swaptions = [(1, 5, 0.1148), (2, 4, 0.1108), (3, 3, 0.1070), (4, 2, 0.1021), (5, 1, 0.1000)]
helpers = [
SwaptionHelper(
Period(maturity, "Years"),
Period(length, "Years"),
vol,
index,
Period(1, "Years"),
DayCounter.thirty360_bond_basis(),
DayCounter.actual360(),
curve,
CalibrationErrorType.RelativePriceError,
1.0,
)
for maturity, length, vol in swaptions
]
method = LevenbergMarquardt(1e-8, 1e-8, 1e-8, False)
end_criteria = EndCriteria(10000, 100, 1e-6, 1e-8, 1e-8)
model.calibrate(helpers, method, end_criteria, False) # fix_reversion=False, fit a and sigma
print(f"a {model.a():.7f}")
print(f"sigma {model.sigma():.7f}")
a 0.0464113
sigma 0.0057992
Price a European swaption with the Jamshidian engine
from itofin import Settings
from itofin.indexes import Euribor
from itofin.instruments import (
EuropeanExercise,
SettlementMethod,
SettlementType,
Swaption,
SwapType,
VanillaSwap,
)
from itofin.models import HullWhite
from itofin.termstructures import FlatForward
from itofin.time import BusinessDayConvention, Calendar, Date, DayCounter, Frequency, Schedule
s = Settings()
s.set_evaluation_date(Date(15, 1, 2026))
curve = FlatForward(Date(15, 1, 2026), 0.03, DayCounter.actual365_fixed())
fixed = Schedule(
Date(15, 1, 2028), Date(15, 1, 2033),
Frequency.Annual, Calendar.target(),
BusinessDayConvention.Unadjusted,
)
floating = Schedule(
Date(15, 1, 2028), Date(15, 1, 2033),
Frequency.Semiannual, Calendar.target(),
BusinessDayConvention.Unadjusted,
)
index = Euribor.six_months(curve, s)
swap = VanillaSwap(
SwapType.Payer, 100.0,
fixed, 0.03, DayCounter.thirty360_bond_basis(),
floating, index, 0.0, DayCounter.actual360(), s,
)
swaption = Swaption(
swap, EuropeanExercise(Date(15, 1, 2027)),
SettlementType.Physical, SettlementMethod.PhysicalOTC, s,
)
swaption.set_jamshidian_engine(HullWhite(curve, 0.05, 0.01))
print(f"payer swaption NPV {swaption.npv():.10f}")
payer swaption NPV 1.5666103956
Draw random numbers as NumPy arrays
The generators under itofin.randomnumbers carry QuantLib's Python names. Sequence
generators return NumPy arrays, and next_sequences(count) draws a whole
(count, dimension) matrix in one call, so a path loop never crosses the binding
once per path.
from itofin.randomnumbers import (
GaussianRandomSequenceGenerator,
UniformRandomGenerator,
UniformRandomSequenceGenerator,
)
rng = UniformRandomGenerator(42) # MT19937, seeded
usg = UniformRandomSequenceGenerator(3, rng) # 3 uniforms per sequence
gsg = GaussianRandomSequenceGenerator(usg) # inverse-cumulative normals
print(usg.next_sequence()) # ndarray, shape (3,)
print(gsg.next_sequences(1000).shape) # (1000, 3) standard normals
SobolRsg, HaltonRsg and GaussianLowDiscrepancySequenceGenerator provide the
low-discrepancy counterparts with the same next_sequence / next_sequences calls.
Global bootstrap callbacks
PiecewiseYieldCurve(..., bootstrap="global") accepts keyword-only
additional_penalties(times, data), additional_dates(), and
additional_variables=SimpleQuoteVariables(quotes, guesses, lower_bounds).
Penalties return finite residuals with a constant length during each calculation;
times and data are copied lists including the reference node. Extra helpers can
be repriced through helper.quote_error() inside a penalty. Additional dates and
variables add unknowns and need corresponding residuals. Iterative bootstrap
rejects these options.
For a futures convexity quote being fitted, pass register_conv_adj=False to its
FuturesRateHelper constructor. This prevents optimizer updates from recursively
invalidating the curve. The default remains observable for ordinary market quotes.
See the joint futures-convexity example, checked
against an independently compiled QuantLib oracle.
Callbacks run synchronously on the owning thread. Python exceptions become
ItofinError from fallible pricing/bootstrap queries, including the original
exception type and message. Failed queries can be retried after fixing the callback.
max_date() retains its existing fallback on bootstrap failure; it neither proves
a successful solve nor hides the error from a subsequent pricing query. Do not mutate quotes inside callbacks;
such mutations are rejected. Callback state changes alone do not invalidate a
successfully cached curve; observed market-input changes do.
Native consumers retain callbacks after the Python curve wrapper is dropped.
Avoid callbacks that strongly capture the curve or an engine/model/index that
retains it. Those mixed native/Python cycles need the user to break the capture;
use weakref.ref(curve) for trial-curve queries. Capturing an additional helper
is supported. Full mixed-graph garbage collection is tracked in
#1028.
Typing checks
See typing acceptance for the pinned, bidirectional Pyright gate.
License
BSD-3-Clause, matching the libitofin core. See the repository for the layer status table, the divergences-from-QuantLib catalogue, and the Rust test oracles behind the numbers above.
Standalone plain-vanilla BlackCalculator
Immutable Black 1976 value and scalar Greeks are available through BlackCalculator.
See the bounded scope and edge-case contract.
Par asset swap bindings
Plain fixed/zero bullet-bond AssetSwap supports live discounting, signed leg
values, floating BPS and fair spread/clean price. Market, amortizing, callable
and overnight variants are excluded. See the bounded scope and lifecycle
contract.
Bounded CPI pricing
- CPI coupons, plain pricers and legs
- CPI bonds
- CPI versus Ibor swaps
- Cumulative CPI cap/floor quoted-price snapshots
These are additive bounded routes, not CPI optionlet volatility/calibration parity.
Metadata
Release files for itofin 0.46.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| itofin-0.46.0.tar.gz | 4.8 MB | With provenance | Details for itofin-0.46.0.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| itofin-0.46.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details for itofin-0.46.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| itofin-0.46.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details for itofin-0.46.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| itofin-0.46.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details for itofin-0.46.0-cp310-abi3-macosx_11_0_arm64.whl |
| itofin-0.46.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details for itofin-0.46.0-cp310-abi3-macosx_10_12_x86_64.whl |
Total release size: 26.7 MB
Release files / itofin-0.46.0.tar.gz
| Download URL | itofin-0.46.0.tar.gz |
|---|---|
| Size | 4.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
52e04ec503d26354f33b7f760164fa88664c7e79c4046aaa4c8c44920abb067d
|
|
BLAKE2b-256 checksum How to use checksums |
b4965e39fb24ecf98bcdcb4d502dc1d7df222e8733a842a5268bf7f71054a363
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / itofin-0.46.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | itofin-0.46.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 5.6 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c3ac23b89289fa9dcd50495452523bd676ae55778fbe451895a0c8eb66a3ec87
|
|
BLAKE2b-256 checksum How to use checksums |
f9babb8c3cc3db98fb4c59abb6f36f7f4d39c0ba574350f145f27e94896b2dbf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / itofin-0.46.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | itofin-0.46.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 5.6 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
b5aebdcea6d75c0e74345d5d5a640795c85a43cea74e03b95d62f731d26f684c
|
|
BLAKE2b-256 checksum How to use checksums |
e1722af42f6a19e4f5e0a314cde827e4b61b2547894e6fffde3675c55bb9cb49
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / itofin-0.46.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | itofin-0.46.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 5.3 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
a1f8382b3ac5275ca484adead887dbbe2011007a24133e3d125de8563d712599
|
|
BLAKE2b-256 checksum How to use checksums |
76eeeef40a38741894519ea4e74a09f4e21f38c9fdfe46fbf2dddb20b16e8083
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logRelease files / itofin-0.46.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | itofin-0.46.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 5.3 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
6f09ad06aaf683080cec837440cf43f1273c14f823ad8aaef630342abaa9f25c
|
|
BLAKE2b-256 checksum How to use checksums |
f101806839f47205578082a8b0a46c81b5d161ef578fcec0d8fbff4159041d22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logSecurity for itofin 0.46.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for itofin-0.46.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.