Asset models for lifelib, built with modelx.
bond_portfolio/BondPortfolio is a modelx model of a bond portfolio. It does three things:
- Generates the cash flows of fixed-rate and zero-coupon bonds: schedules, coupons and redemptions.
- Values the bonds on a discount curve plus spread: NPV, settlement value, dirty and clean price, accrued interest, z-spread, yield, duration, convexity and BPV.
- Does (2) at future valuation dates under scenarios, given the curves, spreads and conventions at each date as inputs.
The financial logic is a statement-for-statement transcription of
quantlib-migration's
pureql, a pure-Python port of the fixed-rate bond subset of QuantLib v1.43.
QuantLib's observer, lazy-evaluation and global-settings machinery is
replaced by modelx's dependency graph and explicit arguments.
- No QuantLib dependency. The model does not import QuantLib or
pureql. - Bit-exact with pureql. It reproduces
pureql's numbers exactly (==). This holds in plain modelx, in the pure-Python package frommodelx.export_model, and in the package compiled by modelx-cython.
This project includes code derived from QuantLib. See NOTICE.md. The derived files are licensed BSD-3-Clause, as NOTICE.md lists them; the project's own files are under the MIT License (LICENSE.txt). It is not part of QuantLib and is not endorsed by the QuantLib Group.
v1 is implemented: phases 0-5 of docs/plan.md.
Supported conventions.
- FixedRateBond and ZeroCouponBond.
- DateGeneration Backward.
- Calendars: NullCalendar, TARGET and UnitedStates (Settlement, GovernmentBond).
- Day counters: Actual360, Actual365Fixed and the simple Thirty360 conventions.
- Curves: ZeroCurve and FlatForward, plus z-spreads.
Later conventions are listed in the plan, phase 6 onwards.
How it is checked. Every output is checked against pureql with ==, at t = 0 and at future valuation dates. Values are also checked against QuantLib 1.41 at the T2 tolerance. The compiled package equals the export, which equals modelx, bit for bit.
Compiled speed depends on the modelx-cython version:
- Released modelx-cython. It builds and passes every check.
- Faster: unreleased branch. The branch
bondportfolio/mx2cy-improvementstypes the uncached helper Cells and runs about 30-40% faster. PointMODELX_CYTHON_SRCat a checkout of it when runningtools/build_cy.py. See docs/mx2cy-issues.md and docs/performance.md.
See docs/model-contract.md for the model's interface (section 6 records what was built), DECISIONS.md for the modelling policies, and bond_portfolio/model.md for the model itself.
bond_portfolio/run.py runs the model on a directory of input CSV files and
writes the cash flows, the cash flows per projection period, one valuation
file per scenario, the totals per (scenario, date) and a run summary (times,
peak memory):
python bond_portfolio/run.py --inputs bond_portfolio --out out # the demo inputs, modelx mode
python bond_portfolio/generate_inputs.py --n-bonds 10000 --n-scen 2 --n-times 121 --seed 1 --out big
python tools/build_cy.py # export + compile
python bond_portfolio/run.py --inputs big --out out_big --mode compiled [--scen 1] [--times 0-12,24]
python bond_portfolio/run.py --inputs bond_portfolio --out out2 --output-level 3 --solver-errors nan # + analytics
python tools/calibrate_spreads.py --inputs bond_portfolio --out calibrated # z-spreads at t = 0, fed forward
- Modes.
modelxreads the model with modelx (development, small portfolios);exportruns the pure-Python package frommodelx.export_model;compiledruns that package compiled by modelx-cython. All three give byte-identical outputs. - Building.
tools/build_cy.pyexports the model, compiles it and writes the build sizes (sizes.json). On Windows run it from PowerShell: from Git Bash the MSVCcl.exestep fails. The build goes to~/.cache/lifelib-assets/buildwhen the project is in a OneDrive folder, else tobuild/(BONDPORTFOLIO_BUILDoverrides). Rebuild after every change to the model. - Checks. The driver validates the inputs, refuses any beyond the model limits or the build sizes, and pre-flights every curve before valuing anything; it then values one (scenario, date) item at a time.
- Analytics.
--output-level 1(the default) writes the prices. Level 2 adds the yield to maturity, the durations, convexity, BPV and YVBP. Level 3 also adds the z-spread, for bonds with a quoted price inprices.csv.- A failed solve, or the Macaulay duration of a yield that is not Compounded, raises by default.
--solver-errors nanwrites NaN instead. Inputs with such yields need it at level 2 or 3; the demo inputs and generated portfolios have them.
- Spread calibration.
tools/calibrate_spreads.pysolves each priced bond's z-spread at t = 0 with the model. It writes a copy of the input set that carries those spreads at every (scenario, date).
bond_portfolio/model.md describes the Spaces, inputs, settings, outputs and limits; bond_portfolio/technical-notes.md what a valuation at a future date includes; docs/performance.md the measured times and memory (10,000 bonds x 121 dates x 2 scenarios: about 65 s compiled, 184 s export, at output level 1; the level-2 analytics take about 8 min compiled).
The tests compare the model with pureql. They expect a quantlib-migration
checkout next to this project, or one named by the PUREQL_SRC environment variable.
python -m pytest # all but the slow, perf and compiled tests
python -m pytest -m oracle # the QuantLib 1.41 comparisons (T2 tolerance)
python -m pytest -m slow # full sweeps (fuzz, flag matrices, all mode cases)
python tools/build_cy.py # from PowerShell on Windows; then:
python -m pytest -m compiled # compiled == export == modelx
python -m pytest -m perf # memory streaming on a generated portfolio