├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── cbindgen.toml ├── examples └── montecarlo.rs ├── quantmath.h └── src ├── core ├── dedup.rs ├── factories.rs ├── mod.rs └── qm.rs ├── cpp-test ├── first_test.txt ├── inputs │ ├── az.json │ ├── bt.json │ ├── delta_gamma.json │ ├── european.json │ ├── fixings.json │ ├── gbp.json │ ├── market.json │ ├── pricer.json │ └── usd.json ├── quantmath_runner └── quantmath_runner.cpp ├── data ├── bump.rs ├── bumpdivs.rs ├── bumpspot.rs ├── bumpspotdate.rs ├── bumptime.rs ├── bumpvol.rs ├── bumpyield.rs ├── curves.rs ├── divstream.rs ├── fixings.rs ├── forward.rs ├── interfaces.rs ├── mod.rs ├── voldecorators.rs ├── volsmile.rs └── volsurface.rs ├── dates ├── calendar.rs ├── datetime.rs ├── mod.rs └── rules.rs ├── facade ├── c_interface.rs ├── handle.rs └── mod.rs ├── instruments ├── assets.rs ├── basket.rs ├── bonds.rs ├── mod.rs └── options.rs ├── lib.rs ├── math ├── brent.rs ├── interpolation.rs ├── mod.rs ├── numerics.rs └── optionpricing.rs ├── models ├── blackdiffusion.rs └── mod.rs ├── pricers ├── mod.rs ├── montecarlo.rs └── selfpricer.rs ├── risk ├── bumptime.rs ├── cache.rs ├── deltagamma.rs ├── dependencies.rs ├── marketdata.rs ├── mod.rs ├── timebumped.rs └── vegavolga.rs └── solvers ├── impliedvol.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - stable 5 | jobs: 6 | include: 7 | - stage: test 8 | script: cargo test --verbose --all 9 | - stage: build 10 | script: cargo build --verbose --all -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quantmath" 3 | version = "0.1.0" 4 | authors = ["Marcus Rainbow"] 5 | license = "MIT" 6 | description = "A library of quantitative maths and a framework for quantitative valuation and risk" 7 | 8 | [dependencies] 9 | statrs = "0.9.0" 10 | ndarray = "0.11.0" 11 | nalgebra = "0.18" 12 | rand = "0.4.0" 13 | serde = "1.0" 14 | serde_derive = "1.0" 15 | serde_json = "1.0" 16 | erased-serde = "0.3" 17 | serde_tagged = "0.2.0" 18 | lazy_static = "1.0" 19 | void = "1" 20 | libc = "0.2" 21 | 22 | [lib] 23 | name = "quantmath" 24 | path = "src/lib.rs" 25 | crate-type = ["lib", "cdylib"] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MarcusRainbow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuantMath 2 | Financial maths library for risk-neutral pricing and risk 3 | 4 | [Api Documentation](https://docs.rs/quantmath/0.1.0/quantmath/) 5 | 6 | ## Goals 7 | Some quant math libraries are really just a collection of pricing formulae. This hopes to be that (see the math module) but also much more. This library is intended to be plugged into the risk and pricing infrastructure in an investment bank or hedge fund. This does not preclude the use of the library for academic work, but will introduce a level of real-world messiness that is often missing from academia. 8 | 9 | ### Lifecycle and Flows 10 | QuantMath is responsible for managing the lifecycle of financial instruments (see the instrument module). As products make payments or as dividends go ex, this results in the instrument splitting into multiple flows. Nothing ever disappears. The SecDB library at Goldman Sachs is famous for taking this philosophy to extremes, but QuantMath is at least capable of the same level of discipline. It is vital to correctly model when flows go ex -- when they cease to be part of the value of this instrument, and are owned by the counterparty (even if not yet settled). 11 | 12 | ### Settlement 13 | Most investment banks skirt around the issues of settlement. What is the value of an equity? Is it the spot price, or is it the spot price discounted from the date when the payment for the equity would actually be received (e.g. T+2). QuantMath intends to allow rigour in settlement, in the premium payment, the payoff, and in the underlying hedges. 14 | 15 | ### Risk and Scenarios 16 | QuantMath is designed to make it easy to reuse calculations that have not changed as a result of a risk bump. For example, if you have an exotic product with multiple equity underlyings, bumping one of those underlyings only results in the affected Monte-Carlo paths being reevaluated. In my experience this is a vital optimisation, and QuantMath makes it possible from the lowest levels such as bootstrapping dividend curves, to the highest, such as reuse of Longstaff-Schwarz optimisation. 17 | 18 | ### Recursive Instruments 19 | It is common to build instruments recursively. A basket contains composite or quanto underliers, then a dynamic index maintains the basket -- finally an exotic product is written with the dynamic index as an underlying. The library must therefore manage this sort of recursive product, whether valuing in Monte-Carlo, analytically or via a finite difference engine. 20 | 21 | ### Simplicity, Orthogonality and Encapsulation 22 | The library must be easy for quants to work in and for IT systems to work with. Adding a new risk, instrument or model should normally mean changes to only one file (and maybe a list of files in mod.rs). The interface to IT should be data-driven, so IT do not need to rebuild every time an instrument or model is added. Models, instruments and risks should be orthogonal, so any can be used with any (subject to sensible mathematical restrictions). If things go wrong, it should be easy to debug just QuantMath, without having to debug the containing IT system. This means that QuantMath should be runnable purely from serialised state, such as JSON files. 23 | 24 | ## The Architecture 25 | The library has a strict hierarchy of modules. Ideally there should be no backward dependencies, such that the library could be split into a separate crate for each module. If you are looking at the library for the first time, it may be best to start from the top level (Facade). Starting at the top level, the modules are: 26 | 27 | ### Facade 28 | This is the interface that IT systems talk to. It is data-driven, so adding a new product or model should not affect the IT systems at all. 29 | 30 | ### Pricers 31 | A pricer evaluates an instrument given market data and a choice of model. We currently have two pricers: Monte-Carlo, which evaluates instruments by averaging across many random paths; self-pricer, which relies on instruments knowing how to price themselves. I hope to add at least one finite difference backward-induction engine. 32 | 33 | ### Models 34 | Models of how we expect prices to change in the future. All are stochastic, but some have stochastic volatility or rates. Examples of models are BGM (Brace Gatarek Musiela), Black, Heston. 35 | 36 | ### Risk 37 | Defines how market data can be bumped, and manages the dependencies when this happens. This contain definitions of risk reports, such as Delta, Gamma, Vega and Volga for all underliers matching some criteria. 38 | 39 | ### Instruments 40 | Defines financial products, indices, assets and currencies. Anything that has a price. Some instruments know how to price themselves (basically, any instrument where the price is well-defined and not model-dependent -- remember this module is lower than models). Some instruments know how to price themselves in a Monte-Carlo framework, given paths of their underliers. 41 | 42 | ### Data 43 | The input market data; vol surfaces, dividends, spot prices, yield curves etc. Also defines bumps to these data items. Most risks are calculated by bumping these inputs. 44 | 45 | ### Math 46 | Low level mathematical formulae, from the Black-Scholes formula to interpolation and quadrature. Where possible, we use functionality from well-established crates in Rust, such as ndarray and statrs, so this is mainly quant-specific maths. 47 | 48 | ### Dates 49 | Dates are very important for financial maths software. We use explicit dates everywhere rather than year-fractions, which is essential for handling settlement correctly. This module also handles date arithmetic, such as date rules and day counts. 50 | 51 | ### Core 52 | Very low-level functionality, such as the definition of the Error struct, and required extensions to serde, such as dedup (deduplication of nodes in directed acyclic graph) and factories (using tagged_serde to handle polymorphic nodes in serialization and deserialization). 53 | -------------------------------------------------------------------------------- /cbindgen.toml: -------------------------------------------------------------------------------- 1 | # An optional string of text to output at the beginning of the generated file 2 | #header = "/* Text to put at the beginning of the generated file. Probably a license. */" 3 | # An optional string of text to output at the end of the generated file 4 | #trailer = "/* Text to put at the end of the generated file */" 5 | # An optional name to use as an include guard 6 | #include_guard = "mozilla_wr_bindings_h" 7 | # An optional string of text to output between major sections of the generated 8 | # file as a warning against manual editing 9 | autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 10 | # Whether to include a comment with the version of cbindgen used to generate the 11 | # file 12 | include_version = true 13 | # An optional namespace to output around the generated bindings 14 | #namespace = "ffi" 15 | # An optional list of namespaces to output around the generated bindings 16 | #namespaces = ["mozilla", "wr"] 17 | # The style to use for curly braces 18 | #braces = "SameLine" 19 | # The desired length of a line to use when formatting lines 20 | line_length = 80 21 | # The amount of spaces in a tab 22 | tab_width = 4 23 | # The language to output bindings in 24 | language = "C" 25 | # A rule to use to select style of declaration in C, tagname vs typedef 26 | style = "Tag" 27 | 28 | [defines] 29 | # A rule for generating `#ifdef`s for matching `#[cfg]`ed items, 30 | # e.g. `#[cfg(foo = "bar")] ...` -> `#if defined(FOO_IS_BAR) ... #endif` 31 | #"foo = bar" = "FOO_IS_BAR" 32 | 33 | [parse] 34 | # Whether to parse dependent crates and include their types in the generated 35 | # bindings 36 | parse_deps = false 37 | # A white list of crate names that are allowed to be parsed 38 | #include = ["webrender", "webrender_traits"] 39 | # A black list of crate names that are not allowed to be parsed 40 | exclude = ["libc"] 41 | # Whether to use a new temporary target directory when running `rustc --pretty=expanded`. 42 | # This may be required for some build processes. 43 | clean = false 44 | 45 | [parse.expand] 46 | # A list of crate names that should be run through `cargo expand` before 47 | # parsing to expand any macros 48 | #crates = ["euclid"] 49 | # If enabled, use the `--all-features` option when expanding. Ignored when 50 | # `features` is set. Disabled by default, except when using the 51 | # `expand = ["euclid"]` shorthand for backwards-compatibility. 52 | all_features = false 53 | # When `all_features` is disabled and this is also disabled, use the 54 | # `--no-default-features` option when expanding. Enabled by default. 55 | default_features = true 56 | # A list of feature names that should be used when running `cargo expand`. This 57 | # combines with `default_features` like in `Cargo.toml`. Note that the features 58 | # listed here are features for the current crate being built, *not* the crates 59 | # being expanded. The crate's `Cargo.toml` must take care of enabling the 60 | # appropriate features in its dependencies 61 | features = ["cbindgen"] 62 | 63 | [export] 64 | # A list of additional items not used by exported functions to include in 65 | # the generated bindings 66 | #include = ["Foo", "Bar"] 67 | # A list of items to not include in the generated bindings 68 | #exclude = ["Bad"] 69 | # A prefix to add before the name of every item 70 | #prefix = "CAPI_" 71 | # Types of items that we'll generate. 72 | item_types = ["constants", "globals", "enums", "structs", "unions", "typedefs", "opaque", "functions"] 73 | 74 | # Table of name conversions to apply to item names 75 | [export.rename] 76 | #"Struct" = "CAPI_Struct" 77 | 78 | [fn] 79 | # An optional prefix to put before every function declaration 80 | #prefix = "string" 81 | # An optional postfix to put after any function declaration 82 | #postfix = "string" 83 | # How to format function arguments 84 | args = "Auto" 85 | # A rule to use to rename function argument names 86 | #rename_args = "[None|GeckoCase|LowerCase|UpperCase|PascalCase|CamelCase|SnakeCase|ScreamingSnakeCase|QualifiedScreamingSnakeCase]" 87 | 88 | [struct] 89 | # A rule to use to rename field names 90 | #rename_fields = "[None|GeckoCase|LowerCase|UpperCase|PascalCase|CamelCase|SnakeCase|ScreamingSnakeCase|QualifiedScreamingSnakeCase]" 91 | # Whether to derive an operator== for all structs 92 | derive_eq = false 93 | # Whether to derive an operator!= for all structs 94 | derive_neq = false 95 | # Whether to derive an operator< for all structs 96 | derive_lt = false 97 | # Whether to derive an operator<= for all structs 98 | derive_lte = false 99 | # Whether to derive an operator> for all structs 100 | derive_gt = false 101 | # Whether to derive an operator>= for all structs 102 | derive_gte = false 103 | 104 | [enum] 105 | # A rule to use to rename enum variants 106 | #rename_variants = "[None|GeckoCase|LowerCase|UpperCase|PascalCase|CamelCase|SnakeCase|ScreamingSnakeCase|QualifiedScreamingSnakeCase]" 107 | 108 | -------------------------------------------------------------------------------- /examples/montecarlo.rs: -------------------------------------------------------------------------------- 1 | extern crate quantmath as qm; 2 | 3 | use std::collections::HashMap; 4 | use std::sync::Arc; 5 | use qm::core::{ 6 | qm::Error, 7 | factories::{Qrc}, 8 | }; 9 | use qm::dates::{ 10 | Date, 11 | calendar::{WeekdayCalendar, RcCalendar}, 12 | datetime::{DateDayFraction, TimeOfDay, DateTime}, 13 | rules::{DateRule, RcDateRule, BusinessDays}, 14 | }; 15 | use qm::data::{ 16 | divstream::{Dividend, DividendStream, RcDividendStream}, 17 | curves::{RateCurveAct365, RcRateCurve}, 18 | volsurface::{FlatVolSurface, RcVolSurface, VolSurface}, 19 | fixings::{FixingTable, RcFixingTable}, 20 | bump::Bump, 21 | bumpspot::BumpSpot, 22 | bumpvol::BumpVol, 23 | bumpdivs::BumpDivs, 24 | bumpyield::BumpYield, 25 | }; 26 | use qm::math::{ 27 | interpolation::Extrap, 28 | numerics::approx_eq, 29 | }; 30 | use qm::risk::marketdata::{MarketData, RcMarketData}; 31 | use qm::instruments::{ 32 | Priceable, 33 | RcInstrument, 34 | assets::{Currency, RcCurrency, Equity}, 35 | options::{ 36 | SpotStartingEuropean, 37 | ForwardStartingEuropean, 38 | PutOrCall, 39 | OptionSettlement, 40 | }, 41 | }; 42 | use qm::models::{ 43 | RcMonteCarloModelFactory, 44 | blackdiffusion::BlackDiffusionFactory, 45 | }; 46 | use qm::pricers::{ 47 | PricerFactory, 48 | montecarlo::{MonteCarloPricer, MonteCarloPricerFactory}, //, RcMonteCarloModelFactory}, 49 | }; 50 | 51 | fn create_sample_divstream() -> RcDividendStream { 52 | 53 | // Early divs are purely cash. Later ones are mixed cash/relative 54 | let d = Date::from_ymd(2017, 01, 02); 55 | let divs = [ 56 | Dividend::new(1.2, 0.0, d + 28, d + 30), 57 | Dividend::new(0.8, 0.002, d + 210, d + 212), 58 | Dividend::new(0.2, 0.008, d + 392, d + 394), 59 | Dividend::new(0.0, 0.01, d + 574, d + 576)]; 60 | 61 | // dividend yield for later-dated divs. Note that the base date 62 | // for the curve is after the last of the explicit dividends. 63 | let points = [(d + 365 * 2, 0.002), (d + 365 * 3, 0.004), 64 | (d + 365 * 5, 0.01), (d + 365 * 10, 0.015)]; 65 | let curve = RateCurveAct365::new(d + 365 * 2, &points, 66 | Extrap::Zero, Extrap::Flat).unwrap(); 67 | let div_yield = RcRateCurve::new(Arc::new(curve)); 68 | 69 | RcDividendStream::new(Arc::new(DividendStream::new(&divs, div_yield))) 70 | } 71 | 72 | fn create_sample_rate() -> RcRateCurve { 73 | let d = Date::from_ymd(2016, 12, 30); 74 | let rate_points = [(d, 0.05), (d + 14, 0.08), (d + 182, 0.09), 75 | (d + 364, 0.085), (d + 728, 0.082)]; 76 | RcRateCurve::new(Arc::new(RateCurveAct365::new(d, &rate_points, 77 | Extrap::Flat, Extrap::Flat).unwrap())) 78 | } 79 | 80 | fn create_sample_borrow() -> RcRateCurve { 81 | let d = Date::from_ymd(2016, 12, 30); 82 | let borrow_points = [(d, 0.01), (d + 196, 0.012), 83 | (d + 364, 0.0125), (d + 728, 0.012)]; 84 | RcRateCurve::new(Arc::new(RateCurveAct365::new(d, &borrow_points, 85 | Extrap::Flat, Extrap::Flat).unwrap())) 86 | } 87 | 88 | fn create_sample_flat_vol() -> RcVolSurface { 89 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar())); 90 | let base_date = Date::from_ymd(2016, 12, 30); 91 | let base = DateDayFraction::new(base_date, 0.2); 92 | RcVolSurface::new(Arc::new(FlatVolSurface::new(0.3, calendar, base))) 93 | } 94 | 95 | fn sample_currency(step: u32) -> Currency { 96 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar::new())); 97 | let settlement = RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, step))); 98 | Currency::new("GBP", settlement) 99 | } 100 | 101 | fn sample_settlement(step: u32) -> RcDateRule { 102 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar::new())); 103 | RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, step))) 104 | } 105 | 106 | fn sample_equity(currency: RcCurrency, step: u32) -> Equity { 107 | let settlement = sample_settlement(step); 108 | Equity::new("BP.L", "LSE", currency, settlement) 109 | } 110 | 111 | fn sample_european() -> Arc { 112 | 113 | let strike = 100.0; 114 | let put_or_call = PutOrCall::Call; 115 | let expiry = DateTime::new( 116 | Date::from_ymd(2018, 06, 01), TimeOfDay::Close); 117 | let currency = RcCurrency::new(Arc::new(sample_currency(2))); 118 | let settlement = sample_settlement(2); 119 | let equity = RcInstrument::new(Qrc::new(Arc::new(sample_equity(currency, 2)))); 120 | let european = SpotStartingEuropean::new("SampleSpotEuropean", "OPT", 121 | equity.clone(), settlement, expiry, 122 | strike, put_or_call, OptionSettlement::Cash).unwrap(); 123 | Arc::new(european) 124 | } 125 | 126 | fn sample_fixings() -> FixingTable { 127 | let today = Date::from_ymd(2017, 01, 02); 128 | FixingTable::from_fixings(today, &[ 129 | ("BP.L", &[ 130 | (DateTime::new(today - 7, TimeOfDay::Close), 102.0)])]).unwrap() 131 | } 132 | 133 | fn assert_approx(value: f64, expected: f64, tolerance: f64) { 134 | assert!(approx_eq(value, expected, tolerance), 135 | "value={} expected={}", value, expected); 136 | } 137 | 138 | fn main() { 139 | 140 | // prepare dummy market data 141 | 142 | let market_data: RcMarketData = RcMarketData::new(Arc::new({ 143 | let spot_date = Date::from_ymd(2017, 01, 02); 144 | 145 | let mut spots = HashMap::new(); 146 | spots.insert("BP.L".to_string(), 100.0); 147 | spots.insert("GSK.L".to_string(), 200.0); 148 | 149 | let mut dividends = HashMap::new(); 150 | dividends.insert("BP.L".to_string(), create_sample_divstream()); 151 | dividends.insert("GSK.L".to_string(), create_sample_divstream()); 152 | 153 | let mut yield_curves = HashMap::new(); 154 | yield_curves.insert("OPT".to_string(), create_sample_rate()); 155 | yield_curves.insert("LSE".to_string(), create_sample_rate()); 156 | 157 | let mut borrow_curves = HashMap::new(); 158 | borrow_curves.insert("BP.L".to_string(), create_sample_borrow()); 159 | borrow_curves.insert("GSK.L".to_string(), create_sample_borrow()); 160 | 161 | let mut vol_surfaces = HashMap::new(); 162 | vol_surfaces.insert("BP.L".to_string(), create_sample_flat_vol()); 163 | vol_surfaces.insert("GSK.L".to_string(), create_sample_flat_vol()); 164 | 165 | MarketData::new(spot_date, spots, yield_curves, 166 | borrow_curves, dividends, vol_surfaces) 167 | })); 168 | 169 | let instrument = RcInstrument::new(Qrc::new(sample_european())); 170 | let fixings = RcFixingTable::new(Arc::new(sample_fixings())); 171 | 172 | let n_paths = 100000; 173 | let correlation_substep = 20; 174 | let path_substep = 0.01; 175 | let model_factory = RcMonteCarloModelFactory::new(Arc::new(BlackDiffusionFactory::new( 176 | correlation_substep, path_substep, n_paths))); 177 | let factory = MonteCarloPricerFactory::new(model_factory); 178 | let mut pricer = factory.new(instrument, fixings, market_data).unwrap(); 179 | let mut save = pricer.as_bumpable().new_saveable(); 180 | 181 | let unbumped_price = pricer.price().unwrap(); 182 | assert_approx(unbumped_price, 16.710717400832973, 0.3); 183 | 184 | // now bump the spot and price. Note that this equates to roughly 185 | // delta of 0.5, which is what we expect for an atm option 186 | let bump = Bump::new_spot("BP.L", BumpSpot::new_relative(0.01)); 187 | let bumped = pricer.as_mut_bumpable().bump(&bump, Some(&mut *save)).unwrap(); 188 | assert!(bumped); 189 | let bumped_price = pricer.price().unwrap(); 190 | assert_approx(bumped_price - unbumped_price, 0.633187905501792, 0.02); 191 | 192 | // when we restore, it should take the price back 193 | pricer.as_mut_bumpable().restore(&*save).unwrap(); 194 | save.clear(); 195 | let price = pricer.price().unwrap(); 196 | assert_approx(price, unbumped_price, 1e-12); 197 | 198 | // now bump the vol and price. The new price is a bit larger, as 199 | // expected. (An atm option has roughly max vega.) 200 | let bump = Bump::new_vol("BP.L", BumpVol::new_flat_additive(0.01)); 201 | let bumped = pricer.as_mut_bumpable().bump(&bump, Some(&mut *save)).unwrap(); 202 | assert!(bumped); 203 | let bumped_price = pricer.price().unwrap(); 204 | assert_approx(bumped_price - unbumped_price, 0.429105019892687, 0.02); 205 | 206 | // when we restore, it should take the price back 207 | pricer.as_mut_bumpable().restore(&*save).unwrap(); 208 | save.clear(); 209 | let price = pricer.price().unwrap(); 210 | assert_approx(price, unbumped_price, 1e-12); 211 | 212 | // now bump the divs and price. As expected, this makes the 213 | // price decrease by a small amount. 214 | let bump = Bump::new_divs("BP.L", BumpDivs::new_all_relative(0.01)); 215 | let bumped = pricer.as_mut_bumpable().bump(&bump, Some(&mut *save)).unwrap(); 216 | assert!(bumped); 217 | let bumped_price = pricer.price().unwrap(); 218 | assert_approx(bumped_price - unbumped_price, -0.01968507722361, 0.001); 219 | 220 | // when we restore, it should take the price back 221 | pricer.as_mut_bumpable().restore(&*save).unwrap(); 222 | save.clear(); 223 | let price = pricer.price().unwrap(); 224 | assert_approx(price, unbumped_price, 1e-12); 225 | 226 | // now bump the yield underlying the equity and price. This 227 | // increases the forward, so we expect the call price to increase. 228 | let bump = Bump::new_yield("LSE", BumpYield::new_flat_annualised(0.01)); 229 | let bumped = pricer.as_mut_bumpable().bump(&bump, Some(&mut *save)).unwrap(); 230 | assert!(bumped); 231 | let bumped_price = pricer.price().unwrap(); 232 | assert_approx(bumped_price - unbumped_price, 0.814646953109683, 0.01); 233 | 234 | // when we restore, it should take the price back 235 | pricer.as_mut_bumpable().restore(&*save).unwrap(); 236 | save.clear(); 237 | let price = pricer.price().unwrap(); 238 | assert_approx(price, unbumped_price, 1e-12); 239 | 240 | // now bump the yield underlying the option and price 241 | let bump = Bump::new_yield("OPT", BumpYield::new_flat_annualised(0.01)); 242 | let bumped = pricer.as_mut_bumpable().bump(&bump, Some(&mut *save)).unwrap(); 243 | assert!(bumped); 244 | let bumped_price = pricer.price().unwrap(); 245 | assert_approx(bumped_price - unbumped_price, -0.215250594911648, 0.01); 246 | 247 | // when we restore, it should take the price back 248 | pricer.as_mut_bumpable().restore(&*save).unwrap(); 249 | save.clear(); 250 | let price = pricer.price().unwrap(); 251 | assert_approx(price, unbumped_price, 1e-12); 252 | } 253 | -------------------------------------------------------------------------------- /quantmath.h: -------------------------------------------------------------------------------- 1 | /* Generated with cbindgen:0.6.2 */ 2 | 3 | /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /* 10 | * Controls what to do if we are reading or writing a recursive structure 11 | * and we hit a node that is the same as the type we are controlling. 12 | * ErrorIfMissing requires all subnodes to be supplied externally. Inline 13 | * requires all subnodes to be supplied inline. WriteOnce allows nodes to be 14 | * supplied either inline or externally. When writing, WriteOnce writes each 15 | * unique node once, the first time it is encounted, and thereafter does not 16 | * write them. 17 | */ 18 | enum QmDedupControl { 19 | QmErrorIfMissing, 20 | QmInline, 21 | QmWriteOnce, 22 | }; 23 | 24 | /* 25 | * Compares two reports for equality. If they are equal, it returns a handle of type Empty, which 26 | * is not an error. If they are not equal, it returns a handle of type Error, where the error 27 | * message details the differences between the reports. This is normally used for testing. 28 | * 29 | * Both of the report handles passed into the function are freed by the function, so you must not 30 | * invoke free_handle on them. The tolerances are absolute, and specify the allowed differences: 31 | * tol_price is the allowed difference in the price -- for Monte-Carlo this must be larger than the 32 | * expected Monte-Carlo noise; tol_ccy_risk is the allowed difference in risks that are measured in 33 | * units of currency such as Vega or PV01 -- this is likely to be smaller than tol_price, as the same 34 | * Monte-Carlo paths are used for different bumped paths in risk calculations; tol_unit_risk is the 35 | * allowed difference to risks that are measured in non-dimensional units such as delta -- this does 36 | * not scale with the notional. 37 | * 38 | * The resulting handle must be freed with free_handle. In the event of error, the 39 | * resulting handle is of type error, and returns true for qm_is_error. You can 40 | * test for errors immediately after invoking this method, or you can simply use 41 | * the handle and propagate the error. 42 | */ 43 | uint64_t qm_assert_approx_eq_reports(uint64_t reports_freed, 44 | uint64_t expected_freed, 45 | double tol_price, 46 | double tol_ccy_risk, 47 | double tol_unit_risk); 48 | 49 | /* 50 | * Calculates a set of reports on the given data. The pricer_factory specifies how 51 | * pricing is to be done; the instrument specifies what is to be priced; the fixing_table 52 | * and market_data specify the context for pricing, and the report generators specify what 53 | * is to be calculated. 54 | * 55 | * The results are returned as a handle containing reports. These can be viewed as JSON by 56 | * invoking qm_reports_as_json_string, or can be compared with other reports by invoking 57 | * qm_assert_approx_eq_reports, which will free the reports handle for you. 58 | * 59 | * Reports are not cloneable, and are generally consumed by functions that use them, except for 60 | * simple ones such as is_error or qm_reports_as_json_string. If you do not invoke a method such as 61 | * qm_assert_approx_eq_reports, you must manually free the reports using free_handle. If the reports 62 | * are badly formed, the method may return a handle of type error. You can 63 | * test for errors immediately after invoking this method, by calling qm_is_error, or you can simply use 64 | * the handle and propagate the error. 65 | */ 66 | uint64_t qm_calculate(uint64_t pricer_factory, 67 | uint64_t instrument, 68 | uint64_t fixing_table, 69 | uint64_t market_data, 70 | uint32_t number_of_report_generators, 71 | const uint64_t *report_generators); 72 | 73 | /* 74 | * Returns a clone of the given handle. The clone must also be eventually freed using qm_free_handle. 75 | * Any handle can be cloned except for one containing a set of reports. Cloning that would end up 76 | * with a handle representing an error. Error handles can also be cloned. 77 | */ 78 | uint64_t qm_clone(uint64_t handle); 79 | 80 | /* 81 | * Creates a handle (an opaque 64bit unsigned int) that represents a currency. For example, it can 82 | * be passed into the instrument creation functions. The source is a UTF8-encoded filename referring 83 | * to a text file containing UTF8-encoded JSON. (UTF8 is the same as ASCII for the standard characters 84 | * 0..127.). 85 | * 86 | * The resulting handle must be freed with free_handle. In the event of error, the 87 | * resulting handle is of type error, and returns true for qm_is_error. You can 88 | * test for errors immediately after invoking this method, or you can simply use 89 | * the handle and propagate the error. 90 | */ 91 | uint64_t qm_currency_from_json_file(const char *source); 92 | 93 | /* 94 | * Same as qm_currency_from_json_file, but taking inline text. 95 | */ 96 | uint64_t qm_currency_from_json_string(const char *source); 97 | 98 | /* 99 | * Returns the error string associated with this handle. If the handle does not represent 100 | * an error, the call will give you an error message anyway, for using a handle of the 101 | * wrong type. 102 | * 103 | * The resulting string must be freed using qm_free_string 104 | */ 105 | char *qm_error_string(uint64_t handle); 106 | 107 | /* 108 | * Loads a fixing table from a JSON UTF8 file. 109 | * 110 | * The resulting handle must be freed with free_handle. In the event of error, the 111 | * resulting handle is of type error, and returns true for qm_is_error. You can 112 | * test for errors immediately after invoking this method, or you can simply use 113 | * the handle and propagate the error. 114 | */ 115 | uint64_t qm_fixing_table_from_json_file(const char *source); 116 | 117 | /* 118 | * Same as qm_fixing_table_from_json_file, but taking inline text. 119 | */ 120 | uint64_t qm_fixing_table_from_json_string(const char *source); 121 | 122 | /* 123 | * Frees a handle that was created by most of the other methods in this interface. Handles ought to be 124 | * freed, or there is a memory leak. Handles must not be freed more than once -- trying to do so is 125 | * likely to result in a core dump. 126 | */ 127 | void qm_free_handle(uint64_t handle); 128 | 129 | /* 130 | * Frees a string that was allocated by a method such as qm_error_string. Strings must 131 | * only be freed once. 132 | */ 133 | void qm_free_string(char *string); 134 | 135 | /* 136 | * Loads an instrument from a JSON UTF8 file. Currencies and subinstruments can 137 | * be supplied inline or provided in arrays, according to the dedup parameters. 138 | * 139 | * The resulting handle must be freed with free_handle. In the event of error, the 140 | * resulting handle is of type error, and returns true for qm_is_error. You can 141 | * test for errors immediately after invoking this method, or you can simply use 142 | * the handle and propagate the error. 143 | */ 144 | uint64_t qm_instrument_from_json_file(const char *source, 145 | enum QmDedupControl dedup_ccy, 146 | uint32_t number_of_currencies, 147 | const uint64_t *currencies, 148 | enum QmDedupControl dedup_instr, 149 | uint32_t number_of_instruments, 150 | const uint64_t *instruments); 151 | 152 | /* 153 | * Same as qm_instrument_from_json_file, but taking inline text. 154 | */ 155 | uint64_t qm_instrument_from_json_string(const char *source, 156 | enum QmDedupControl dedup_ccy, 157 | uint32_t number_of_currencies, 158 | const uint64_t *currencies, 159 | enum QmDedupControl dedup_instr, 160 | uint32_t number_of_instruments, 161 | const uint64_t *instruments); 162 | 163 | /* 164 | * Tests whether a handle represents an error. If it does, you should normally invoke 165 | * qm_error_string to find out what the error is. 166 | */ 167 | bool qm_is_error(uint64_t handle); 168 | 169 | /* 170 | * Loads a market data collection from a JSON UTF8 file. 171 | * 172 | * The resulting handle must be freed with free_handle. In the event of error, the 173 | * resulting handle is of type error, and returns true for qm_is_error. You can 174 | * test for errors immediately after invoking this method, or you can simply use 175 | * the handle and propagate the error. 176 | */ 177 | uint64_t qm_market_data_from_json_file(const char *source); 178 | 179 | /* 180 | * Same as qm_market_data_from_json_file, but taking inline text. 181 | */ 182 | uint64_t qm_market_data_from_json_string(const char *source); 183 | 184 | /* 185 | * Loads a pricer factory from a JSON UTF8 file. Pricer factories specify how pricing is to be done. For 186 | * example, a Monte-Carlo pricer factory specifies the number of paths, and the stochastic model to be 187 | * used. 188 | * 189 | * The resulting handle must be freed with free_handle. In the event of error, the 190 | * resulting handle is of type error, and returns true for qm_is_error. You can 191 | * test for errors immediately after invoking this method, or you can simply use 192 | * the handle and propagate the error. 193 | */ 194 | uint64_t qm_pricer_factory_from_json_file(const char *source); 195 | 196 | /* 197 | * Same as qm_pricer_factory_from_json_file, but taking inline text. 198 | */ 199 | uint64_t qm_pricer_factory_from_json_string(const char *source); 200 | 201 | /* 202 | * Loads a report generator from a JSON UTF8 file. Report generators are supplied to the 203 | * qm_calculate function, and specify which reports are to be calculated, such as DeltaGamma for 204 | * all assets, etc. 205 | * 206 | * The resulting handle must be freed with free_handle. In the event of error, the 207 | * resulting handle is of type error, and returns true for qm_is_error. You can 208 | * test for errors immediately after invoking this method, or you can simply use 209 | * the handle and propagate the error. 210 | */ 211 | uint64_t qm_report_generator_from_json_file(const char *source); 212 | 213 | /* 214 | * Same as qm_report_generator_from_json_file, but taking inline text. 215 | */ 216 | uint64_t qm_report_generator_from_json_string(const char *source); 217 | 218 | /* 219 | * Converts a handle representing a set of reports into a string. The string must 220 | * be freed using qm_free_string. The handle is left unchanged and unfreed by this 221 | * call. 222 | * 223 | * If the handle is an error or does not contain a set of reports, an error message 224 | * is returned instead. The string must be freed using qm_free_string whether it is 225 | * an error or not. 226 | */ 227 | char *qm_reports_as_json_string(uint64_t handle); 228 | 229 | /* 230 | * Loads a set of reports from a JSON UTF8 file. Reports are normally generated by a qm_calculate, 231 | * so the only reason you would load them from a file is for testing or for distribution. 232 | * 233 | * Reports are not cloneable, and are generally consumed by functions that use them, except for 234 | * simple ones such as is_error or qm_reports_as_json_string. If you do not invoke a method such as 235 | * qm_assert_approx_eq_reports, you must manually free the reports using free_handle. If the reports 236 | * are badly formed, the method may return a handle of type error. You can 237 | * test for errors immediately after invoking this method, by calling qm_is_error, or you can simply use 238 | * the handle and propagate the error. 239 | */ 240 | uint64_t qm_reports_from_json_file(const char *source); 241 | 242 | /* 243 | * Same as qm_reports_from_json_file, but taking inline text. 244 | */ 245 | uint64_t qm_reports_from_json_string(const char *source); 246 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod qm; 2 | pub mod factories; 3 | pub mod dedup; -------------------------------------------------------------------------------- /src/core/qm.rs: -------------------------------------------------------------------------------- 1 | //! Core definitions for quantmath 2 | 3 | use std::error; 4 | use std::fmt; 5 | use std::io; 6 | use std::num; 7 | use std::str; 8 | use ndarray; 9 | use serde_json; 10 | 11 | /// Error returned by any rfin method 12 | #[derive(Debug, Clone)] 13 | pub struct Error { 14 | message: String 15 | } 16 | 17 | impl Error { 18 | /// Creates a new error 19 | pub fn new(message: &str) -> Error { 20 | Error { message: message.to_string() } 21 | } 22 | } 23 | 24 | impl error::Error for Error { 25 | fn description(&self) -> &str { 26 | &*self.message 27 | } 28 | } 29 | 30 | impl fmt::Display for Error { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | write!(f, "rfin error: {}", self.message) 33 | } 34 | } 35 | 36 | impl From for Error { 37 | fn from(error: io::Error) -> Self { 38 | Error::new(&format!("io error: {}", error)) 39 | } 40 | } 41 | 42 | impl From for Error { 43 | fn from(error: num::ParseIntError) -> Self { 44 | Error::new(&format!( 45 | "Error {} when parsing integer. Badly-formed date?", error)) 46 | } 47 | } 48 | 49 | impl From for Error { 50 | fn from(error: ndarray::ShapeError) -> Self { 51 | Error::new(&format!("Error {} when converting array", error)) 52 | } 53 | } 54 | 55 | impl From for Error { 56 | fn from(error: serde_json::Error) -> Self { 57 | Error::new(&format!("Error {} when serializing/deserializing", error)) 58 | } 59 | } 60 | 61 | impl From for Error { 62 | fn from(error: fmt::Error) -> Self { 63 | Error::new(&format!("Error {} when formatting", error)) 64 | } 65 | } 66 | 67 | impl From for Error { 68 | fn from(error: str::Utf8Error) -> Self { 69 | Error::new(&format!("Error {} when converting", error)) 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | #[should_panic] 79 | fn expect_fail() { 80 | fail_or_succeed(false).unwrap(); 81 | } 82 | 83 | #[test] 84 | fn expect_succeed() { 85 | fail_or_succeed(true).unwrap(); 86 | } 87 | 88 | fn fail_or_succeed(ok: bool) -> Result<(), Error> { 89 | if ok { Ok(()) } else { Err(Error::new("failure message")) } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/cpp-test/first_test.txt: -------------------------------------------------------------------------------- 1 | # lines beginning with # are ignored, as are blank lines 2 | 3 | # each section starts with a title, ending with a colon 4 | Currencies: 5 | inputs/usd.json 6 | inputs/gbp.json 7 | 8 | Instruments: 9 | inputs/bt.json 10 | inputs/az.json 11 | inputs/european.json 12 | 13 | Market Data: 14 | inputs/market.json 15 | 16 | Fixings: 17 | inputs/fixings.json 18 | 19 | Pricer: 20 | inputs/pricer.json 21 | 22 | Reports: 23 | inputs/delta_gamma.json 24 | -------------------------------------------------------------------------------- /src/cpp-test/inputs/az.json: -------------------------------------------------------------------------------- 1 | { 2 | "Equity": { 3 | "id": "AZ.L", 4 | "credit_id": "LSE", 5 | "currency": "GBP", 6 | "settlement": { 7 | "BusinessDays": { 8 | "calendar": { 9 | "WeekdayCalendar": [] 10 | }, 11 | "step": 2, 12 | "slip_forward": true 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/bt.json: -------------------------------------------------------------------------------- 1 | { 2 | "Equity": { 3 | "id": "BT.L", 4 | "credit_id": "LSE", 5 | "currency": "GBP", 6 | "settlement": { 7 | "BusinessDays": { 8 | "calendar": { 9 | "WeekdayCalendar": [] 10 | }, 11 | "step": 2, 12 | "slip_forward": true 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/delta_gamma.json: -------------------------------------------------------------------------------- 1 | { 2 | "DeltaGammaReportGenerator": { 3 | "bumpsize": 0.01 4 | } 5 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/european.json: -------------------------------------------------------------------------------- 1 | { 2 | "ForwardStartingEuropean": { 3 | "id": "SampleEuropean", 4 | "credit_id": "OPT", 5 | "underlying": "BT.L", 6 | "settlement": { 7 | "BusinessDays": { 8 | "calendar": { 9 | "WeekdayCalendar": [] 10 | }, 11 | "step": 2, 12 | "slip_forward": true 13 | } 14 | }, 15 | "expiry": { 16 | "date": "2018-12-01", 17 | "time_of_day": "Close" 18 | }, 19 | "put_or_call": "Call", 20 | "cash_or_physical": "Cash", 21 | "expiry_time": { 22 | "date": "2018-12-01", 23 | "day_fraction": 0.8 24 | }, 25 | "pay_date": "2018-12-05", 26 | "strike_fraction": 1.15170375, 27 | "strike_date": { 28 | "date": "2018-06-08", 29 | "time_of_day": "Close" 30 | }, 31 | "strike_time": { 32 | "date": "2018-06-08", 33 | "day_fraction": 0.8 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/fixings.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixings_known_until": "2018-01-01", 3 | "fixings_by_id": { 4 | "BT.L": { 5 | "fixing_by_date": [ 6 | [ 7 | { 8 | "date": "2017-12-25", 9 | "time_of_day": "Close" 10 | }, 11 | 123.3 12 | ], 13 | [ 14 | { 15 | "date": "2017-12-23", 16 | "time_of_day": "Open" 17 | }, 18 | 123.1 19 | ], 20 | [ 21 | { 22 | "date": "2018-01-01", 23 | "time_of_day": "Open" 24 | }, 25 | 123.4 26 | ], 27 | [ 28 | { 29 | "date": "2017-12-25", 30 | "time_of_day": "Open" 31 | }, 32 | 123.2 33 | ] 34 | ] 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/gbp.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GBP", 3 | "settlement": { 4 | "BusinessDays": { 5 | "calendar": { 6 | "WeekdayCalendar": [] 7 | }, 8 | "step": 2, 9 | "slip_forward": true 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/market.json: -------------------------------------------------------------------------------- 1 | { 2 | "spot_date": "2017-01-02", 3 | "spots": { 4 | "BT.L": 100.0 5 | }, 6 | "yield_curves": { 7 | "OPT": { 8 | "RateCurveAct365": { 9 | "base": "2016-12-30", 10 | "interp": { 11 | "left": "Flat", 12 | "right": "Flat", 13 | "points": [ 14 | [ 15 | "2016-12-30", 16 | 0.05 17 | ], 18 | [ 19 | "2017-01-13", 20 | 0.08 21 | ], 22 | [ 23 | "2017-06-30", 24 | 0.09 25 | ], 26 | [ 27 | "2017-12-29", 28 | 0.085 29 | ], 30 | [ 31 | "2018-12-28", 32 | 0.082 33 | ] 34 | ] 35 | } 36 | } 37 | }, 38 | "LSE": { 39 | "RateCurveAct365": { 40 | "base": "2016-12-30", 41 | "interp": { 42 | "left": "Flat", 43 | "right": "Flat", 44 | "points": [ 45 | [ 46 | "2016-12-30", 47 | 0.05 48 | ], 49 | [ 50 | "2017-01-13", 51 | 0.08 52 | ], 53 | [ 54 | "2017-06-30", 55 | 0.09 56 | ], 57 | [ 58 | "2017-12-29", 59 | 0.085 60 | ], 61 | [ 62 | "2018-12-28", 63 | 0.082 64 | ] 65 | ] 66 | } 67 | } 68 | } 69 | }, 70 | "borrow_curves": { 71 | "BT.L": { 72 | "RateCurveAct365": { 73 | "base": "2016-12-30", 74 | "interp": { 75 | "left": "Flat", 76 | "right": "Flat", 77 | "points": [ 78 | [ 79 | "2016-12-30", 80 | 0.01 81 | ], 82 | [ 83 | "2017-07-14", 84 | 0.012 85 | ], 86 | [ 87 | "2017-12-29", 88 | 0.0125 89 | ], 90 | [ 91 | "2018-12-28", 92 | 0.012 93 | ] 94 | ] 95 | } 96 | } 97 | } 98 | }, 99 | "dividends": { 100 | "BT.L": { 101 | "dividends": [ 102 | { 103 | "cash": 1.2, 104 | "relative": 0.0, 105 | "ex_date": "2017-01-30", 106 | "pay_date": "2017-02-01" 107 | }, 108 | { 109 | "cash": 0.8, 110 | "relative": 0.002, 111 | "ex_date": "2017-07-31", 112 | "pay_date": "2017-08-02" 113 | }, 114 | { 115 | "cash": 0.2, 116 | "relative": 0.008, 117 | "ex_date": "2018-01-29", 118 | "pay_date": "2018-01-31" 119 | }, 120 | { 121 | "cash": 0.0, 122 | "relative": 0.01, 123 | "ex_date": "2018-07-30", 124 | "pay_date": "2018-08-01" 125 | } 126 | ], 127 | "div_yield": { 128 | "RateCurveAct365": { 129 | "base": "2019-01-02", 130 | "interp": { 131 | "left": "Zero", 132 | "right": "Flat", 133 | "points": [ 134 | [ 135 | "2019-01-02", 136 | 0.002 137 | ], 138 | [ 139 | "2020-01-02", 140 | 0.004 141 | ], 142 | [ 143 | "2022-01-01", 144 | 0.01 145 | ], 146 | [ 147 | "2026-12-31", 148 | 0.015 149 | ] 150 | ] 151 | } 152 | } 153 | }, 154 | "last_cash_ex_date": "2018-01-29" 155 | } 156 | }, 157 | "vol_surfaces": { 158 | "BT.L": { 159 | "FlatVolSurface": { 160 | "vol": 0.3, 161 | "calendar": { 162 | "WeekdayCalendar": [] 163 | }, 164 | "base_date": { 165 | "date": "2016-12-30", 166 | "day_fraction": 0.2 167 | } 168 | } 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/pricer.json: -------------------------------------------------------------------------------- 1 | { 2 | "SelfPricerFactory": {} 3 | } -------------------------------------------------------------------------------- /src/cpp-test/inputs/usd.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "USD", 3 | "settlement": { 4 | "BusinessDays": { 5 | "calendar": { 6 | "WeekdayCalendar": [] 7 | }, 8 | "step": 2, 9 | "slip_forward": true 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/cpp-test/quantmath_runner: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusRainbow/QuantMath/b51ffacc2cfe5b0c35578537341b207f3eb798ed/src/cpp-test/quantmath_runner -------------------------------------------------------------------------------- /src/cpp-test/quantmath_runner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Command line to compile and link this. Run from the directory containing this file 3 | * g++ -std=c++11 -o quantmath_runner quantmath_runner.cpp ../../target/debug/libquantmath.dylib 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | extern "C" { 11 | #include "quantmath.h" 12 | } 13 | 14 | // RAII class to encapsulate a handle from QuantMath, and prevent leaks. 15 | class Handle { 16 | uint64_t myObj; 17 | public: 18 | Handle(uint64_t obj = 0) : myObj(obj) {} 19 | 20 | Handle(Handle&& that) : myObj(that.myObj) { 21 | that.myObj = 0; 22 | } 23 | 24 | Handle(const Handle& that) : myObj(qm_clone(that.myObj)) {} 25 | 26 | const Handle& operator=(const Handle& that) { 27 | if (this != &that) { 28 | if (myObj) 29 | qm_free_handle(myObj); 30 | if (that.myObj) 31 | myObj = qm_clone(that.myObj); 32 | else 33 | myObj = 0; 34 | } 35 | return *this; 36 | } 37 | 38 | ~Handle() { 39 | if (myObj) 40 | qm_free_handle(myObj); 41 | } 42 | 43 | uint64_t release() { 44 | uint64_t tmp = myObj; 45 | myObj = 0; 46 | return tmp; 47 | } 48 | 49 | uint64_t get() const { 50 | return myObj; 51 | } 52 | }; 53 | 54 | // RAII class to encapsulate a string from QuantMath, and prevent leaks. 55 | class Str { 56 | char* myStr; 57 | public: 58 | Str(char* str = nullptr) : myStr(str) {} 59 | 60 | ~Str() { 61 | if (myStr) 62 | qm_free_string(myStr); 63 | } 64 | 65 | char* release() { 66 | char* tmp = myStr; 67 | myStr = nullptr; 68 | return tmp; 69 | } 70 | 71 | const char* get() const { 72 | return myStr; 73 | } 74 | 75 | private: 76 | Str(const Str& that); 77 | const Str& operator=(const Str& that); 78 | }; 79 | 80 | int abort_showing_usage(const char* argv0) { 81 | std::cerr << "usage: " << argv0 << " [files-to-execute]*" << std::endl; 82 | std::cerr << "sections: Currencies, Instruments, Market Data, Fixings, Pricer, Reports" << std::endl; 83 | return -1; 84 | } 85 | 86 | bool handle_error(uint64_t handle) { 87 | Str error_string(qm_error_string(handle)); 88 | std::cerr << error_string.get() << std::endl; 89 | return false; 90 | } 91 | 92 | bool process_file(const char* filename) { 93 | std::ifstream file (filename); 94 | if (!file.is_open()) { 95 | std::cerr << "unable to open file " << filename << std::endl; 96 | return false; 97 | } 98 | 99 | std::vector currencies; 100 | std::vector instruments; 101 | std::vector market_data; 102 | std::vector fixings; 103 | std::vector pricer_factories; 104 | std::vector report_generators; 105 | 106 | // parse the config file 107 | std::vector* files = nullptr; 108 | std::string line; 109 | while (std::getline(file, line)) { 110 | if (line.size() == 0 || line.front() == '#') 111 | continue; 112 | 113 | if (line.back() == ':') { 114 | if (line == "Currencies:") 115 | files = ¤cies; 116 | else if (line == "Instruments:") 117 | files = &instruments; 118 | else if (line == "Market Data:") 119 | files = &market_data; 120 | else if (line == "Fixings:") 121 | files = &fixings; 122 | else if (line == "Pricer:") 123 | files = &pricer_factories; 124 | else if (line == "Reports:") 125 | files = &report_generators; 126 | else { 127 | std::cerr << "Unknown section: \"" << line << "\"" << std::endl; 128 | return false; 129 | } 130 | } else { 131 | files->push_back(line); 132 | } 133 | } 134 | file.close(); 135 | 136 | // read in the currencies 137 | std::cerr << "read currencies:" << std::endl; 138 | std::list currency_objects; 139 | std::vector currency_handles; 140 | for (const auto& currency_file : currencies) { 141 | std::cerr << currency_file.c_str() << std::endl; 142 | currency_objects.emplace_back(qm_currency_from_json_file(currency_file.c_str())); 143 | const uint64_t handle = currency_objects.back().get(); 144 | if (qm_is_error(handle)) 145 | return handle_error(handle); 146 | currency_handles.push_back(handle); 147 | } 148 | 149 | // read in the instruments 150 | std::cerr << "read instruments:" << std::endl; 151 | std::list instrument_objects; 152 | std::vector instrument_handles; 153 | for (const auto& instrument_file : instruments) { 154 | std::cerr << instrument_file.c_str() << std::endl; 155 | instrument_objects.emplace_back(qm_instrument_from_json_file(instrument_file.c_str(), 156 | QmWriteOnce, currency_handles.size(), currency_handles.data(), 157 | QmWriteOnce, instrument_handles.size(), instrument_handles.data())); 158 | const uint64_t handle = instrument_objects.back().get(); 159 | if (qm_is_error(handle)) 160 | return handle_error(handle); 161 | instrument_handles.push_back(handle); 162 | } 163 | 164 | // read in the market data 165 | std::cerr << "read market data" << std::endl; 166 | std::list market_data_objects; 167 | for (const auto& market_data_file : market_data) { 168 | market_data_objects.emplace_back(qm_market_data_from_json_file(market_data_file.c_str())); 169 | const uint64_t handle = market_data_objects.back().get(); 170 | if (qm_is_error(handle)) 171 | return handle_error(handle); 172 | } 173 | 174 | // read in the fixings 175 | std::cerr << "read fixings" << std::endl; 176 | std::list fixing_objects; 177 | for (const auto& fixings_file : fixings) { 178 | fixing_objects.emplace_back(qm_fixing_table_from_json_file(fixings_file.c_str())); 179 | const uint64_t handle = fixing_objects.back().get(); 180 | if (qm_is_error(handle)) 181 | return handle_error(handle); 182 | } 183 | 184 | // read in the pricer factories 185 | std::cerr << "read pricers" << std::endl; 186 | std::list pricer_factory_objects; 187 | for (const auto& pricer_factory_file : pricer_factories) { 188 | pricer_factory_objects.emplace_back(qm_pricer_factory_from_json_file(pricer_factory_file.c_str())); 189 | const uint64_t handle = pricer_factory_objects.back().get(); 190 | if (qm_is_error(handle)) 191 | return handle_error(handle); 192 | } 193 | 194 | // read in the report generators 195 | std::cerr << "read report generators" << std::endl; 196 | std::list report_generator_objects; 197 | std::vector report_generator_handles; 198 | for (const auto& report_generator_file : report_generators) { 199 | report_generator_objects.emplace_back(qm_report_generator_from_json_file(report_generator_file.c_str())); 200 | const uint64_t handle = report_generator_objects.back().get(); 201 | if (qm_is_error(handle)) 202 | return handle_error(handle); 203 | report_generator_handles.push_back(handle); 204 | } 205 | 206 | // for now, we require exactly one each of the pricer factories, market data, and fixings 207 | if (fixing_objects.size() != 1 || market_data_objects.size() != 1 || pricer_factory_objects.size() != 1) { 208 | std::cerr << "Require exactly one each of the pricer factories, market data, and fixings" << std::endl; 209 | return false; 210 | } 211 | 212 | // do the calculation 213 | std::cerr << "calculate" << std::endl; 214 | uint64_t reports = qm_calculate( 215 | pricer_factory_objects.back().get(), 216 | instrument_objects.back().get(), 217 | fixing_objects.back().get(), 218 | market_data_objects.back().get(), 219 | report_generator_handles.size(), report_generator_handles.data()); 220 | 221 | std::cerr << "write reports" << std::endl; 222 | Str report_str(qm_reports_as_json_string(reports)); 223 | std::cout << report_str.get() << std::endl; 224 | 225 | return true; 226 | } 227 | 228 | int main(int argc, const char** argv) { 229 | for (int i = 1; i < argc; ++i) { 230 | if (!process_file(argv[i])) 231 | return abort_showing_usage(argv[0]); 232 | } 233 | } -------------------------------------------------------------------------------- /src/data/bump.rs: -------------------------------------------------------------------------------- 1 | use data::bumpspot::BumpSpot; 2 | use data::bumpdivs::BumpDivs; 3 | use data::bumpvol::BumpVol; 4 | use data::bumpyield::BumpYield; 5 | use data::bumpspotdate::BumpSpotDate; 6 | 7 | /// Enumeration spanning all bumps of market data 8 | pub enum Bump { 9 | Spot ( String, BumpSpot ), 10 | Divs ( String, BumpDivs ), 11 | Borrow ( String, BumpYield ), 12 | Vol ( String, BumpVol ), 13 | Yield ( String, BumpYield ), 14 | SpotDate ( BumpSpotDate ) 15 | } 16 | 17 | impl Bump { 18 | pub fn new_spot(id: &str, bump: BumpSpot) -> Bump { 19 | Bump::Spot ( id.to_string(), bump ) 20 | } 21 | 22 | pub fn new_divs(id: &str, bump: BumpDivs) -> Bump { 23 | Bump::Divs ( id.to_string(), bump ) 24 | } 25 | 26 | pub fn new_borrow(id: &str, bump: BumpYield) -> Bump { 27 | Bump::Borrow ( id.to_string(), bump ) 28 | } 29 | 30 | pub fn new_vol(id: &str, bump: BumpVol) -> Bump { 31 | Bump::Vol ( id.to_string(), bump ) 32 | } 33 | 34 | pub fn new_yield(credit_id: &str, bump: BumpYield) -> Bump { 35 | Bump::Yield ( credit_id.to_string(), bump ) 36 | } 37 | 38 | pub fn new_spot_date(bump: BumpSpotDate) -> Bump { 39 | Bump::SpotDate ( bump ) 40 | } 41 | } 42 | 43 | /// An interface for applying bumps 44 | pub trait Bumper { 45 | /// Applies the bump to the old value, returning the new value 46 | fn apply(&self, old_value: T) -> T; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/data/bumpdivs.rs: -------------------------------------------------------------------------------- 1 | use data::divstream::RcDividendStream; 2 | use data::divstream::DividendStream; 3 | use data::bump::Bumper; 4 | use std::sync::Arc; 5 | 6 | /// Bump that defines all the supported bumps and risk transformations of a 7 | /// vol surface. 8 | pub enum BumpDivs { 9 | BumpAllRelative { size: f64 }, 10 | } 11 | 12 | impl BumpDivs { 13 | pub fn new_all_relative(size: f64) -> BumpDivs { 14 | BumpDivs::BumpAllRelative { size: size } 15 | } 16 | } 17 | 18 | impl Bumper for BumpDivs { 19 | 20 | fn apply(&self, divs: RcDividendStream) -> RcDividendStream { 21 | match self { 22 | &BumpDivs::BumpAllRelative { size } 23 | => RcDividendStream::new(Arc::new(DividendStream::new_bump_all(&*divs, size))) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/data/bumpspot.rs: -------------------------------------------------------------------------------- 1 | use data::bump::Bumper; 2 | 3 | /// Bump that defines all the supported bumps to a spot value 4 | #[derive(Clone)] 5 | pub enum BumpSpot { 6 | Relative { bump: f64 }, 7 | Replace { spot: f64 } 8 | } 9 | 10 | impl BumpSpot { 11 | pub fn new_relative(bump: f64) -> BumpSpot { 12 | BumpSpot::Relative { bump: bump } 13 | } 14 | 15 | pub fn new_replace(spot: f64) -> BumpSpot { 16 | BumpSpot::Replace { spot: spot } 17 | } 18 | } 19 | 20 | impl Bumper for BumpSpot { 21 | 22 | fn apply(&self, old_spot: f64) -> f64 { 23 | match self { 24 | &BumpSpot::Relative { bump } => old_spot * (1.0 + bump), 25 | &BumpSpot::Replace { spot } => spot 26 | } 27 | } 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/data/bumpspotdate.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | 3 | /// Bump that defines all the supported bumps to the spot date. Do not use this 4 | /// bump directly unless you know what you are doing. It modifies the market 5 | /// data, but does not modify any instruments. As a result, you may end up with 6 | /// code that works most of the time, but fails when the change of spot date 7 | /// straddles a lifecycle event. 8 | #[derive(Serialize, Deserialize, Clone, Debug)] 9 | pub struct BumpSpotDate { 10 | spot_date: Date, 11 | spot_dynamics: SpotDynamics 12 | } 13 | 14 | impl BumpSpotDate { 15 | pub fn new(spot_date: Date, spot_dynamics: SpotDynamics) -> BumpSpotDate { 16 | BumpSpotDate { spot_date: spot_date, spot_dynamics: spot_dynamics } 17 | } 18 | 19 | pub fn spot_date(&self) -> Date { self.spot_date } 20 | pub fn spot_dynamics(&self) -> SpotDynamics { self.spot_dynamics } 21 | } 22 | 23 | /// Enum that defines how spot moves when time is bumped. 24 | #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] 25 | pub enum SpotDynamics { 26 | /// Spot stays the same, except that any dividends going ex are subtracted 27 | StickySpot, 28 | /// Forwards after the spot date stay the same. In other words, spot moves 29 | /// up the forward. 30 | StickyForward 31 | } 32 | -------------------------------------------------------------------------------- /src/data/bumptime.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | 3 | /// Bump that defines all the supported bumps to the spot date 4 | pub struct BumpTime { 5 | spot_date: Date, 6 | spot_dynamics: SpotDynamics 7 | } 8 | 9 | impl BumpTime { 10 | pub fn new(spot_date: Date, spot_dynamics: SpotDynamics) -> BumpTime { 11 | BumpTime { spot_date: spot_date, spot_dynamics: spot_dynamics } 12 | } 13 | 14 | pub fn spot_date(&self) -> Date { self.spot_date } 15 | pub fn spot_dynamics(&self) -> SpotDynamics { self.spot_dynamics } 16 | } 17 | 18 | /// Enum that defines how spot moves when time is bumped. 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)] 20 | pub enum SpotDynamics { 21 | /// Spot stays the same, except that any dividends going ex are subtracted 22 | StickySpot, 23 | /// Forwards after the spot date stay the same. In other words, spot moves 24 | /// up the forward. 25 | StickyForward 26 | } 27 | -------------------------------------------------------------------------------- /src/data/bumpvol.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::f64::NAN; 3 | use data::volsurface::RcVolSurface; 4 | use data::volsurface::FlatVolSurface; 5 | use data::voldecorators::TimeScaledBumpVol; 6 | use data::voldecorators::ParallelBumpVol; 7 | use data::bump::Bumper; 8 | 9 | /// Bump that defines all the supported bumps and risk transformations of a 10 | /// vol surface. 11 | #[derive(Serialize, Deserialize, Clone, Debug)] 12 | pub enum BumpVol { 13 | FlatAdditive { size: f64 }, 14 | TimeScaled { size: f64, floor: f64 }, 15 | Replace { vol: f64 } 16 | } 17 | 18 | impl BumpVol { 19 | pub fn new_flat_additive(size: f64) -> BumpVol { 20 | BumpVol::FlatAdditive { size: size } 21 | } 22 | 23 | pub fn new_time_scaled(size: f64, floor: f64) -> BumpVol { 24 | BumpVol::TimeScaled { size: size, floor: floor } 25 | } 26 | 27 | pub fn new_replace(vol: f64) -> BumpVol { 28 | BumpVol::Replace { vol } 29 | } 30 | 31 | pub fn bumpsize(&self) -> f64 { 32 | match self { 33 | &BumpVol::FlatAdditive { size } => size, 34 | &BumpVol::TimeScaled { size, floor: _ } => size, 35 | &BumpVol::Replace { vol: _ } => NAN 36 | } 37 | } 38 | 39 | /// This is used for symmetric vega and volga calculation. For example, 40 | /// after an up bump, we want a down bump that both cancels out the 41 | /// up bump and applies an equal and opposite down bump. 42 | pub fn opposite(&self) -> BumpVol { 43 | // We first bump up by 1 + bumpsize, then down by (1 - bumpsize) / (1 + bumpsize) 44 | let bumpsize = self.bumpsize(); 45 | let down_bump = (1.0 - bumpsize) / (1.0 + bumpsize) - 1.0; 46 | match self { 47 | &BumpVol::FlatAdditive { size: _ } 48 | => BumpVol::FlatAdditive { size : down_bump }, 49 | &BumpVol::TimeScaled { size: _, floor } 50 | => BumpVol::TimeScaled { size : down_bump, floor: floor }, 51 | &BumpVol::Replace { vol: _ } 52 | => BumpVol::Replace { vol: NAN } 53 | } 54 | } 55 | } 56 | 57 | impl Bumper for BumpVol { 58 | 59 | fn apply(&self, surface: RcVolSurface) -> RcVolSurface { 60 | match self { 61 | &BumpVol::FlatAdditive { size } 62 | => RcVolSurface::new(Arc::new(ParallelBumpVol::new(surface.clone(), size))), 63 | 64 | &BumpVol::TimeScaled { size, floor } 65 | => RcVolSurface::new(Arc::new(TimeScaledBumpVol::new(surface.clone(), size, floor))), 66 | 67 | &BumpVol::Replace { vol } 68 | => RcVolSurface::new(Arc::new(FlatVolSurface::new(vol, 69 | surface.calendar().clone(), surface.base_date()))) 70 | } 71 | } 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/data/bumpyield.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use data::curves::AnnualisedFlatBump; 3 | use data::curves::ContinuouslyCompoundedFlatBump; 4 | use data::bump::Bumper; 5 | use data::curves::RcRateCurve; 6 | 7 | /// Bump that defines all the supported bumps and risk transformations of a 8 | /// rate curve such as a borrow curve or a yield curve. 9 | pub enum BumpYield { 10 | FlatAnnualised { size: f64 }, 11 | FlatContinuouslyCompounded { size: f64 } 12 | } 13 | 14 | impl BumpYield { 15 | pub fn new_flat_annualised(size: f64) -> BumpYield { 16 | BumpYield::FlatAnnualised { size: size } 17 | } 18 | 19 | pub fn new_flat_continuously_compounded(size: f64) -> BumpYield { 20 | BumpYield::FlatContinuouslyCompounded { size: size } 21 | } 22 | } 23 | 24 | impl Bumper for BumpYield { 25 | 26 | fn apply(&self, surface: RcRateCurve) -> RcRateCurve { 27 | match self { 28 | &BumpYield::FlatAnnualised { size } 29 | => RcRateCurve::new(Arc::new(AnnualisedFlatBump::new( 30 | surface.clone(), size))), 31 | 32 | // Note that an alternative methodology here would be to 33 | // bump the pillars. Consider this if profiling shows this 34 | // to be a bottleneck. 35 | &BumpYield::FlatContinuouslyCompounded { size } 36 | => RcRateCurve::new(Arc::new(ContinuouslyCompoundedFlatBump::new( 37 | surface.clone(), size))) 38 | } 39 | } 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/data/fixings.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | use dates::datetime::DateTime; 3 | use core::qm; 4 | use std::collections::HashMap; 5 | use std::sync::Arc; 6 | use std::ops::Deref; 7 | use serde as sd; 8 | 9 | /// A fixing table is a collection of fixing curves, keyed by instrument id. 10 | #[derive(Debug, Clone, Deserialize, Serialize)] 11 | pub struct FixingTable { 12 | fixings_known_until: Date, 13 | fixings_by_id: HashMap 14 | } 15 | 16 | impl FixingTable { 17 | 18 | /// Creates a fixing table, given a date to which fixings are known and an 19 | /// array of fixing curve data keyed by instrument id. 20 | pub fn from_fixings(fixings_known_until: Date, 21 | fixings: &[(&str, &[(DateTime, f64)])]) 22 | -> Result { 23 | 24 | // TODO we ought to be able to implement this in terms of from_iter_known_until 25 | let mut fixing_table = FixingTable::new(fixings_known_until); 26 | for fixing in fixings.iter() { 27 | let fixing_curve = Fixings::new(fixing.0, fixing.1)?; 28 | fixing_table.insert(fixing.0, fixing_curve)?; 29 | } 30 | Ok(fixing_table) 31 | } 32 | 33 | /// Creates a fixing table from a source such as a HashMap iterator, or an iterator from a slice 34 | /// of pairs of ids and slices of Date 35 | pub fn from_iter_known_until(fixings_known_until: Date, iter: T) -> Result 36 | where 37 | T: IntoIterator, 38 | U: AsRef, 39 | V: AsRef<[(DateTime, f64)]> { 40 | 41 | let mut fixing_table = FixingTable::new(fixings_known_until); 42 | for fixing in iter { 43 | let fixing_curve = Fixings::new(fixing.0.as_ref(), fixing.1.as_ref())?; 44 | fixing_table.insert(fixing.0.as_ref(), fixing_curve)?; 45 | } 46 | Ok(fixing_table) 47 | } 48 | 49 | /// Creates an empty fixing table, given a date to which fixings are known. 50 | pub fn new(fixings_known_until: Date) -> FixingTable { 51 | FixingTable { fixings_known_until: fixings_known_until, 52 | fixings_by_id: HashMap::new() } 53 | } 54 | 55 | /// Adds a fixings curve 56 | pub fn insert(&mut self, id: &str, fixings: Fixings) 57 | -> Result<(), qm::Error> { 58 | if let Some(_) = self.fixings_by_id.insert(id.to_string(), fixings) { 59 | return Err(duplicate_fixing_curve(id)) 60 | } 61 | Ok(()) 62 | } 63 | 64 | /// Tries to get a fixing for the given instrument and date. Returns None 65 | /// if it was absent today, or an error if it was absent in the past. 66 | pub fn get(&self, id: &str, date_time: DateTime) 67 | -> Result, qm::Error> { 68 | 69 | match self.get_optional(id, date_time) { 70 | Some(fixing) => Ok(Some(fixing)), 71 | 72 | None => { if date_time.date() < self.fixings_known_until { 73 | Err(missing_fixing(id, date_time)) } else { Ok(None) } 74 | } 75 | } 76 | } 77 | 78 | /// Tries to get a fixing for the given instrument and date. Returns None 79 | /// if the fixing is not found. 80 | pub fn get_optional(&self, id: &str, date_time: DateTime) -> Option { 81 | match self.get_fixings(id) { 82 | Some(fixings) => fixings.get_optional(date_time), 83 | None => None 84 | } 85 | } 86 | 87 | /// Tries to get an entire fixing curve by id. Return None if there is 88 | /// none. 89 | pub fn get_fixings(&self, id: &str) -> Option<&Fixings> { 90 | self.fixings_by_id.get(&id.to_string()) 91 | } 92 | 93 | /// Gets the date to which fixings are known. Fixings on this date may or 94 | /// may not be known. 95 | pub fn fixings_known_until(&self) -> Date { 96 | self.fixings_known_until 97 | } 98 | } 99 | 100 | /// Creates a missing fixing error. This is normally done internally in the 101 | /// get method, but if there are complicated rules for fixings, this allows 102 | /// an external user to generate the message. 103 | pub fn missing_fixing(id: &str, date_time: DateTime) -> qm::Error { 104 | qm::Error::new(&format!("Missing fixing for \"{}\" at {}", id, date_time)) 105 | } 106 | 107 | fn duplicate_fixing_curve(id: &str) -> qm::Error { 108 | qm::Error::new(&format!("Duplicate fixing curve supplied for {}", id)) 109 | } 110 | 111 | /// Create a new type for a Rc so we can implement serialize 112 | /// and deserialize functions for it. 113 | #[derive(Clone, Debug)] 114 | pub struct RcFixingTable(Arc); 115 | 116 | impl RcFixingTable { 117 | pub fn new(table: Arc) -> RcFixingTable { 118 | RcFixingTable(table) 119 | } 120 | } 121 | 122 | impl Deref for RcFixingTable { 123 | type Target = FixingTable; 124 | 125 | fn deref(&self) -> &Self::Target { 126 | &self.0 127 | } 128 | } 129 | 130 | impl sd::Serialize for RcFixingTable { 131 | fn serialize(&self, serializer: S) -> Result 132 | where S: sd::Serializer 133 | { 134 | self.0.serialize(serializer) 135 | } 136 | } 137 | 138 | impl<'de> sd::Deserialize<'de> for RcFixingTable { 139 | fn deserialize(deserializer: D) -> Result 140 | where D: sd::Deserializer<'de> { 141 | let stream = FixingTable::deserialize(deserializer)?; 142 | Ok(RcFixingTable::new(Arc::new(stream))) 143 | } 144 | } 145 | 146 | /// A fixings curve is a set of historical fixings for a known instrument. 147 | /// Fixings are identified by date and time of day. In cases where multiple 148 | /// different fixings are available for the same instrument at the same date 149 | /// and time of day, for example different real-time suppliers, this should 150 | /// be represented by different instruments. 151 | /// 152 | /// Fixings in the past should always be supplied. Fixings in the future 153 | /// should never be supplied (though there may be exceptions). Fixings today, 154 | /// on the fixings_known_until date, may be optionally supplied. 155 | #[derive(Serialize, Deserialize, Default, Debug, Clone)] 156 | pub struct Fixings { 157 | #[serde(with = "map_as_pairs")] 158 | fixing_by_date: HashMap, 159 | } 160 | 161 | // By default, serde only supports HashMap with string as a key. To use DateTime 162 | // as a key, we need to write the serialize methods ourselves. This code is 163 | // modified from an example written by dtolnay. Consider changing the serialized 164 | // format to be less verbose. 165 | mod map_as_pairs { 166 | use std::fmt; 167 | use serde::ser::Serializer; 168 | use serde::de::{Deserializer, Visitor, SeqAccess}; 169 | use std::collections::HashMap; 170 | use dates::datetime::DateTime; 171 | 172 | pub fn serialize(map: &HashMap, serializer: S) -> Result 173 | where 174 | S: Serializer, 175 | { 176 | serializer.collect_seq(map) 177 | } 178 | 179 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 180 | where 181 | D: Deserializer<'de>, 182 | { 183 | struct MapVisitor {} 184 | 185 | impl<'de> Visitor<'de> for MapVisitor { 186 | type Value = HashMap; 187 | 188 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 189 | formatter.write_str("a sequence of key-value pairs") 190 | } 191 | 192 | fn visit_seq(self, mut seq: A) -> Result 193 | where 194 | A: SeqAccess<'de>, 195 | { 196 | let mut map = HashMap::new(); 197 | loop { 198 | let next : Option<(DateTime, f64)> = seq.next_element()?; 199 | if let Some((k, v)) = next { 200 | map.insert(k, v); 201 | } else { 202 | break; 203 | } 204 | } 205 | Ok(map) 206 | } 207 | } 208 | 209 | deserializer.deserialize_seq(MapVisitor {}) 210 | } 211 | } 212 | 213 | impl Fixings { 214 | /// Creates a fixing curve containing the given date-times and fixing 215 | /// values. The id string is used only for error messages, for example 216 | /// to identify missing fixings. 217 | pub fn new(id: &str, fixings: &[(DateTime, f64)]) 218 | -> Result { 219 | 220 | let mut fixing_by_date = HashMap::new(); 221 | for fixing in fixings.iter() { 222 | if let Some(value) = fixing_by_date.insert(fixing.0, fixing.1) { 223 | return Err(duplicate_fixing(id, value, fixing.1, fixing.0)) 224 | } 225 | } 226 | Ok(Fixings { fixing_by_date: fixing_by_date} ) 227 | } 228 | 229 | /// Tries to gets a fixing on the given date, or returns None if it is 230 | /// absent. 231 | pub fn get_optional(&self, date_time: DateTime) -> Option { 232 | if let Some(fixing) = self.fixing_by_date.get(&date_time) { 233 | Some(*fixing) 234 | } else { 235 | None 236 | } 237 | } 238 | } 239 | 240 | fn duplicate_fixing(id: &str, v1: f64, v2: f64, date_time: DateTime) 241 | -> qm::Error { 242 | qm::Error::new(&format!("Duplicate fixing for \"{}\": \ 243 | {} and {} both present at {}", id, v1, v2, date_time)) 244 | } 245 | 246 | #[cfg(test)] 247 | mod tests { 248 | use super::*; 249 | use dates::datetime::TimeOfDay; 250 | use serde_json; 251 | 252 | fn sample_fixings() -> FixingTable { 253 | 254 | let today = Date::from_ymd(2018, 01, 01); 255 | FixingTable::from_fixings(today, &[ 256 | ("BT.L", &[ 257 | (DateTime::new(today, TimeOfDay::Open), 123.4), 258 | (DateTime::new(today - 7, TimeOfDay::Close), 123.3), 259 | (DateTime::new(today - 7, TimeOfDay::Open), 123.2), 260 | (DateTime::new(today - 9, TimeOfDay::Open), 123.1)]), 261 | (&"GSK.L", &[ 262 | (DateTime::new(today, TimeOfDay::Open), 223.4), 263 | (DateTime::new(today - 7, TimeOfDay::Close), 223.3), 264 | (DateTime::new(today - 7, TimeOfDay::Open), 223.2)])]).unwrap() 265 | } 266 | 267 | #[test] 268 | fn find_fixing_today() { 269 | let fixings = sample_fixings(); 270 | let today = fixings.fixings_known_until(); 271 | let fixing = fixings.get("BT.L", 272 | DateTime::new(today, TimeOfDay::Open)).unwrap(); 273 | if let Some(f) = fixing { 274 | assert_eq!(f, 123.4); 275 | } else { 276 | assert!(false, "missing fixing"); 277 | } 278 | } 279 | 280 | #[test] 281 | fn missing_fixing_today() { 282 | let fixings = sample_fixings(); 283 | let today = fixings.fixings_known_until(); 284 | let fixing = fixings.get("BT.L", 285 | DateTime::new(today, TimeOfDay::Close)).unwrap(); 286 | if let Some(_) = fixing { 287 | assert!(false, "fixing present"); 288 | } 289 | } 290 | 291 | #[test] 292 | #[should_panic] 293 | fn missing_fixing_past() { 294 | let fixings = sample_fixings(); 295 | let today = fixings.fixings_known_until(); 296 | let _ = fixings.get("BT.L", 297 | DateTime::new(today - 9, TimeOfDay::Close)).unwrap(); 298 | } 299 | 300 | #[test] 301 | fn find_fixing_past() { 302 | let fixings = sample_fixings(); 303 | let today = fixings.fixings_known_until(); 304 | let fixing = fixings.get("BT.L", 305 | DateTime::new(today - 7, TimeOfDay::Open)).unwrap(); 306 | if let Some(f) = fixing { 307 | assert_eq!(f, 123.2); 308 | } else { 309 | assert!(false, "missing fixing"); 310 | } 311 | } 312 | 313 | #[test] 314 | fn missing_optional_fixing_past() { 315 | let fixings = sample_fixings(); 316 | let today = fixings.fixings_known_until(); 317 | let fixing = fixings.get_optional("BT.L", 318 | DateTime::new(today - 9, TimeOfDay::Close)); 319 | if let Some(_) = fixing { 320 | assert!(false, "fixing present"); 321 | } 322 | } 323 | 324 | #[test] 325 | fn find_optional_fixing_past() { 326 | let fixings = sample_fixings(); 327 | let today = fixings.fixings_known_until(); 328 | let fixing = fixings.get_optional("BT.L", 329 | DateTime::new(today - 9, TimeOfDay::Open)); 330 | if let Some(f) = fixing { 331 | assert_eq!(f, 123.1); 332 | } else { 333 | assert!(false, "missing optional fixing"); 334 | } 335 | } 336 | 337 | #[test] 338 | fn serde_fixing_table_roundtrip() { 339 | 340 | // create some sample data 341 | let fixings = sample_fixings(); 342 | 343 | // round trip it via JSON 344 | let serialized = serde_json::to_string_pretty(&fixings).unwrap(); 345 | print!("serialized: {}\n", serialized); 346 | let deserialized: FixingTable = serde_json::from_str(&serialized).unwrap(); 347 | 348 | // check some fixings 349 | let today = deserialized.fixings_known_until(); 350 | let fixing = deserialized.get("BT.L", DateTime::new(today, TimeOfDay::Open)).unwrap(); 351 | if let Some(f) = fixing { 352 | assert_eq!(f, 123.4); 353 | } else { 354 | assert!(false, "missing fixing"); 355 | } 356 | 357 | let fixing = deserialized.get("BT.L", DateTime::new(today - 7, TimeOfDay::Open)).unwrap(); 358 | if let Some(f) = fixing { 359 | assert_eq!(f, 123.2); 360 | } else { 361 | assert!(false, "missing fixing"); 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /src/data/forward.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | use dates::rules::RcDateRule; 3 | use math::interpolation::Interpolate; 4 | use data::divstream::DividendBootstrap; 5 | use data::divstream::DividendStream; 6 | use data::curves::RcRateCurve; 7 | use data::curves::RateCurve; 8 | use core::qm; 9 | 10 | /// Forward curve. This represents the expectation value of some asset over 11 | /// time. It is implemented in different ways for futures (generally driftless) 12 | /// equities and other assets. 13 | pub trait Forward : Interpolate + Sync + Send { 14 | 15 | /// Allows this forward to be treated as an interpolator 16 | fn as_interp(&self) -> &Interpolate; 17 | 18 | /// Returns the forward on the given date. For example, this may be 19 | /// the equity forward. In almost all cases, forwards can be considered 20 | /// piecewise constant over a day. The exception is where there is a 21 | /// quanto correction. In that case, we use an alternative interface. 22 | fn forward(&self, date: Date) -> Result; 23 | 24 | /// Returns the NPV of any cash dividends after the given date. Defaults to 25 | /// returning zero, because many sort of forwards have no dividends. 26 | fn fixed_divs_after(&self, _date: Date) -> Result { 27 | Ok(0.0) 28 | } 29 | } 30 | 31 | /// Allow any forward to be treated as an interpolator by date 32 | impl Interpolate for T where T: Forward { 33 | fn interpolate(&self, date: Date) -> Result { 34 | self.forward(date) 35 | } 36 | } 37 | 38 | /// Driftless forward, for example for a future, where the expectation on any 39 | /// date is the value today. 40 | pub struct DriftlessForward { 41 | value: f64 42 | } 43 | 44 | impl Forward for DriftlessForward { 45 | fn as_interp(&self) -> &Interpolate { self } 46 | 47 | fn forward(&self, _date: Date) -> Result { 48 | Ok(self.value) 49 | } 50 | } 51 | 52 | impl DriftlessForward { 53 | pub fn new(value: f64) -> DriftlessForward { 54 | DriftlessForward { value: value } 55 | } 56 | } 57 | 58 | /// Forward as an interpolator. For example, this may be used for any asset 59 | /// including equities where we do not care about the dynamics. (Normally 60 | /// we represent an equity forward as a spot plus a dividend stream etc, so 61 | /// that we get the dynamics right as spot is bumped.) 62 | pub struct InterpolatedForward { 63 | interp: Box> 64 | } 65 | 66 | impl Forward for InterpolatedForward { 67 | fn as_interp(&self) -> &Interpolate { self } 68 | 69 | fn forward(&self, date: Date) -> Result { 70 | self.interp.interpolate(date) 71 | } 72 | } 73 | 74 | impl InterpolatedForward { 75 | pub fn new(interp: Box>) -> InterpolatedForward { 76 | InterpolatedForward { interp: interp } 77 | } 78 | } 79 | 80 | /// An equity forward has a spot, a discount rate which, together with a 81 | /// borrow, defines the rate of growth, plus a dividend stream. 82 | pub struct EquityForward { 83 | settlement: RcDateRule, 84 | rate: RcRateCurve, 85 | borrow: RcRateCurve, 86 | div_yield: RcRateCurve, 87 | bootstrap: DividendBootstrap, 88 | reference_spot: f64, 89 | base_log_discount: f64 90 | } 91 | 92 | impl Forward for EquityForward { 93 | fn as_interp(&self) -> &Interpolate { self } 94 | 95 | fn forward(&self, date: Date) -> Result { 96 | 97 | // add up any dividends before and including the given date 98 | let divs = self.bootstrap.discounted_sum_from_base(date)?; 99 | 100 | // calculate the settlement period 101 | let pay_date = self.settlement.apply(date); 102 | 103 | // calculate the growth up to the pay date 104 | let log_df = log_discount_with_borrow(&*self.rate, &*self.borrow, 105 | pay_date)?; 106 | let log_div_yield = self.div_yield.rt(pay_date)?; 107 | let growth = (self.base_log_discount + log_div_yield - log_df).exp(); 108 | 109 | // return the forward 110 | Ok((self.reference_spot - divs) * growth) 111 | } 112 | 113 | fn fixed_divs_after(&self, date: Date) -> Result { 114 | self.bootstrap.discounted_cash_divs_after(date) 115 | } 116 | } 117 | 118 | impl EquityForward { 119 | pub fn new( 120 | base_date: Date, 121 | spot: f64, 122 | settlement: RcDateRule, 123 | rate: RcRateCurve, 124 | borrow: RcRateCurve, 125 | divs: &DividendStream, 126 | high_water_mark: Date) -> Result { 127 | 128 | // If the base dates of the rate and borrow curves do not match, 129 | // we may need to add a correction 130 | let base_log_discount = log_discount_with_borrow( 131 | &*rate, &*borrow, base_date)?; 132 | 133 | // The spot date is the date when the spot actually pays. We discount 134 | // the screen price spot from this date to the base_date (today) to 135 | // find the reference_spot, which is used for all internal 136 | // calculations. 137 | let spot_date = settlement.apply(base_date); 138 | let spot_df = discount_with_borrow( 139 | &*rate, &*borrow, base_log_discount, spot_date)?; 140 | let reference_spot = spot * spot_df; 141 | 142 | // Bootstrap the dividend stream to turn it into accumulated totals 143 | let bootstrap = DividendBootstrap::new(&divs, &*rate, &*borrow, 144 | reference_spot, base_date, high_water_mark)?; 145 | 146 | Ok(EquityForward { 147 | settlement: settlement, 148 | rate: rate, 149 | borrow: borrow, 150 | div_yield: divs.div_yield(), 151 | bootstrap: bootstrap, 152 | reference_spot: reference_spot, 153 | base_log_discount: base_log_discount }) 154 | } 155 | } 156 | 157 | /// Within a forward, all discounting and growth is done using 158 | /// exp(-rt + qt), i.e. using both the discount curve and the borrow 159 | /// curve. This is because the forward model is funded by repoing out the 160 | /// stock, which costs rate minus borrow. Any change to funding, such as 161 | /// payment of a dividend, must similarly be discounted with the same 162 | /// curves. 163 | /// 164 | /// The base_qt_minus_rt is zero unless the base dates of the rate and 165 | /// borrow curve are different from the forward model. It is calculated 166 | /// using log_discount_with_borrow. 167 | pub fn discount_with_borrow(rate: &RateCurve, borrow: &RateCurve, 168 | base_qt_minus_rt: f64, date: Date) -> Result { 169 | 170 | let log_discount = log_discount_with_borrow(rate, borrow, date)?; 171 | Ok((log_discount - base_qt_minus_rt).exp()) 172 | } 173 | 174 | /// Precalculate the fixed offset to be passed into discount_with_borrow. 175 | /// Actually returns rt - qt where t is the time from the base date of the 176 | /// discount curve to the base date of the forward. 177 | pub fn log_discount_with_borrow(rate: &RateCurve, borrow: &RateCurve, 178 | date: Date) -> Result { 179 | 180 | let rt = rate.rt(date)?; 181 | let qt = borrow.rt(date)?; 182 | Ok(qt - rt) 183 | } 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use super::*; 188 | use std::sync::Arc; 189 | use math::numerics::approx_eq; 190 | use math::interpolation::Extrap; 191 | use math::interpolation::CubicSpline; 192 | use data::curves::RateCurveAct365; 193 | use data::divstream::Dividend; 194 | use dates::calendar::WeekdayCalendar; 195 | use dates::rules::BusinessDays; 196 | use dates::calendar::RcCalendar; 197 | 198 | #[test] 199 | fn driftless_forward() { 200 | let d = Date::from_ymd(2018, 05, 25); 201 | 202 | let fwd = DriftlessForward::new(123.4); 203 | assert_match(fwd.forward(d), 123.4); 204 | assert_match(fwd.forward(d+100), 123.4); 205 | } 206 | 207 | #[test] 208 | fn interpolated_forward() { 209 | let d = Date::from_ymd(2018, 05, 25); 210 | 211 | let points = [(d, 100.0), (d+30, 103.0), (d+60, 97.0), (d+90, 99.0), 212 | (d+120, 105.0)]; 213 | let cs = Box::new(CubicSpline::new(&points, 214 | Extrap::Natural, Extrap::Natural).unwrap()); 215 | let fwd = InterpolatedForward::new(cs); 216 | 217 | assert_match(fwd.forward(d), 100.0); 218 | assert_match(fwd.forward(d+30), 103.0); 219 | assert_match(fwd.forward(d+60), 97.0); 220 | assert_match(fwd.forward(d+90), 99.0); 221 | assert_match(fwd.forward(d+120), 105.0); 222 | } 223 | 224 | #[test] 225 | fn equity_forward() { 226 | let d = Date::from_ymd(2017, 01, 02); 227 | let spot = 97.0; 228 | let divs = create_sample_divstream(); 229 | let rate = create_sample_rate(); 230 | let borrow = create_sample_borrow(); 231 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar{})); 232 | let settlement = RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, 2))); 233 | 234 | let fwd = EquityForward::new(d, spot, settlement, rate, borrow, &divs, 235 | d + 1500).unwrap(); 236 | 237 | assert_match(fwd.forward(d), 97.0); 238 | assert_match(fwd.forward(d+27), 97.55511831033844); 239 | assert_match(fwd.forward(d+28), 96.35511831033844); 240 | assert_match(fwd.forward(d+60), 97.02249458204768); 241 | assert_match(fwd.forward(d+90), 97.61947501213612); 242 | assert_match(fwd.forward(d+120), 98.242454295387); 243 | assert_match(fwd.forward(d+150), 98.96059291192566); 244 | assert_match(fwd.forward(d+180), 99.64145192620384); 245 | assert_match(fwd.forward(d+209), 100.18493401723067); 246 | assert_match(fwd.forward(d+210), 99.18464159368386); 247 | assert_match(fwd.forward(d+240), 99.75346887674715); 248 | assert_match(fwd.forward(d+270), 100.34720455926485); 249 | assert_match(fwd.forward(d+300), 100.87344226359396); 250 | assert_match(fwd.forward(d+600), 104.51748569914179); 251 | assert_match(fwd.forward(d+900), 110.7100396163593); 252 | assert_match(fwd.forward(d+1200), 117.8483691785027); 253 | assert_match(fwd.forward(d+1500), 125.93011849243018); 254 | } 255 | 256 | fn create_sample_divstream() -> DividendStream { 257 | 258 | // Early divs are purely cash. Later ones are mixed cash/relative 259 | let d = Date::from_ymd(2017, 01, 02); 260 | let divs = [ 261 | Dividend::new(1.2, 0.0, d + 28, d + 30), 262 | Dividend::new(0.8, 0.002, d + 210, d + 212), 263 | Dividend::new(0.2, 0.008, d + 392, d + 394), 264 | Dividend::new(0.0, 0.01, d + 574, d + 576)]; 265 | 266 | // dividend yield for later-dated divs. Note that the base date 267 | // for the curve is after the last of the explicit dividends. 268 | let points = [(d + 365 * 2, 0.002), (d + 365 * 3, 0.004), 269 | (d + 365 * 5, 0.01), (d + 365 * 10, 0.015)]; 270 | let curve = RateCurveAct365::new(d + 365 * 2, &points, 271 | Extrap::Zero, Extrap::Flat).unwrap(); 272 | let div_yield = RcRateCurve::new(Arc::new(curve)); 273 | 274 | DividendStream::new(&divs, div_yield) 275 | } 276 | 277 | fn create_sample_rate() -> RcRateCurve { 278 | let d = Date::from_ymd(2016, 12, 30); 279 | let rate_points = [(d, 0.05), (d + 14, 0.08), (d + 182, 0.09), 280 | (d + 364, 0.085), (d + 728, 0.082)]; 281 | RcRateCurve::new(Arc::new(RateCurveAct365::new(d, &rate_points, 282 | Extrap::Flat, Extrap::Flat).unwrap())) 283 | } 284 | 285 | fn create_sample_borrow() -> RcRateCurve { 286 | let d = Date::from_ymd(2016, 12, 30); 287 | let borrow_points = [(d, 0.01), (d + 196, 0.012), 288 | (d + 364, 0.0125), (d + 728, 0.012)]; 289 | RcRateCurve::new(Arc::new(RateCurveAct365::new(d, &borrow_points, 290 | Extrap::Flat, Extrap::Flat).unwrap())) 291 | } 292 | 293 | fn assert_match(result: Result, expected: f64) { 294 | let v = result.unwrap(); 295 | assert!(approx_eq(v, expected, 1e-12), 296 | "result={} expected={}", v, expected); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/data/interfaces.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | 3 | /// Low-level interfaces to market data 4 | 5 | /// Discount curve. Returns the log of the discount rather than the discount 6 | /// itself, as this is very often what we want. It is easy to calculate the 7 | /// discount yourself if you need it, using exp. 8 | pub trait Discount { 9 | 10 | /// Returns the log of the discount factor from the given date to the 11 | /// base date. If you want the discount factor to any other date, you 12 | /// must ask for two discount factors and do a division (or subtraction 13 | /// in log space). 14 | fn log_df(&self, date: Date) -> Result; 15 | 16 | /// Returns the base date for discount factor calculations 17 | fn base(&self) -> Date; 18 | 19 | /// Convenience method that returns the discount factor, rather than 20 | /// its log 21 | fn df(&self, date: Date) -> Result { 22 | let log_df = self.log_df(date)?; 23 | log_df.exp() 24 | } 25 | } 26 | 27 | /// Forward curve 28 | pub trait Forward { 29 | 30 | /// Returns the forward on the given date. For example, this may be 31 | /// the equity forward. In almost all cases, forwards can be considered 32 | /// piecewise constant over a day. The exception is where there is a 33 | /// quanto correction. In that case, we use an alternative interface. 34 | fn forward(&self, date: Date) -> Result; 35 | } 36 | 37 | /// A volatility surface is externally represented as a variance, which means 38 | /// we do not have to deal in time outside the vol surface. 39 | pub trait Variance { 40 | 41 | /// Returns the variances on the given date and time fraction, for a 42 | /// range of strikes. The day fraction runs from zero at the very start of 43 | /// the day to one at the end of the day. Known times such as 'open' and 44 | /// 'close' are somewhere in this range, where the exact position depends 45 | /// on the asset. 46 | fn variances(&self, 47 | date: Date, 48 | day_fraction: f64, 49 | strikes: &[f64], 50 | variances: &mut[f64]) -> Result<(), qm::Error>; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bump; 2 | pub mod bumpdivs; 3 | pub mod bumpspot; 4 | pub mod bumpspotdate; 5 | pub mod bumpvol; 6 | pub mod bumpyield; 7 | pub mod curves; 8 | pub mod divstream; 9 | pub mod fixings; 10 | pub mod forward; 11 | pub mod voldecorators; 12 | pub mod volsmile; 13 | pub mod volsurface; 14 | -------------------------------------------------------------------------------- /src/data/volsmile.rs: -------------------------------------------------------------------------------- 1 | use math::interpolation::CubicSpline; 2 | use math::interpolation::Interpolate; 3 | use math::interpolation::Extrap; 4 | use core::qm; 5 | use std::f64::NAN; 6 | use std::fmt::Debug; 7 | use serde::Serialize; 8 | 9 | /// A VolSmile is a curve of volatilities by strike, all for a specific date. 10 | 11 | pub trait VolSmile : Serialize + Clone + Debug { 12 | 13 | /// These volatilities must be converted to variances by squaring and 14 | /// multiplying by some t. The t to use depends on the vol surface. We 15 | /// assume that the VolSmile simply provides volatilities, and it is up 16 | /// to the VolSurface to interpret these in terms of variances. 17 | fn volatilities( 18 | &self, 19 | strikes: &[f64], 20 | volatilities: &mut[f64]) -> Result<(), qm::Error>; 21 | 22 | /// Convenience function to fetch a single volatility. This does not 23 | /// have to be implemented by every implementer of the trait, though 24 | /// it could be for performance reasons. 25 | fn volatility(&self, strike: f64) -> Result { 26 | let strikes = [strike]; 27 | let mut vols = [NAN]; 28 | self.volatilities(&strikes, &mut vols)?; 29 | Ok(vols[0]) 30 | } 31 | } 32 | 33 | /// A flat smile, where the vol is the same for all strikes. (It may be 34 | /// different at other dates.) 35 | #[derive(Debug, Clone, Serialize, Deserialize)] 36 | pub struct FlatSmile { 37 | vol: f64 38 | } 39 | 40 | impl VolSmile for FlatSmile { 41 | 42 | fn volatilities( 43 | &self, 44 | strikes: &[f64], 45 | volatilities: &mut[f64]) -> Result<(), qm::Error> { 46 | 47 | let n = strikes.len(); 48 | assert!(n == volatilities.len()); 49 | 50 | for i in 0..n { 51 | volatilities[i] = self.vol; 52 | } 53 | Ok(()) 54 | } 55 | } 56 | 57 | impl FlatSmile { 58 | 59 | /// Creates a flat smile with the given volatility. 60 | pub fn new(vol: f64) -> Result { 61 | Ok(FlatSmile { vol: vol }) 62 | } 63 | } 64 | 65 | /// A simple implementation of a VolSmile in terms of a cubic spline. 66 | #[derive(Debug, Clone, Serialize, Deserialize)] 67 | pub struct CubicSplineSmile { 68 | smile: CubicSpline 69 | } 70 | 71 | impl VolSmile for CubicSplineSmile { 72 | 73 | fn volatilities( 74 | &self, 75 | strikes: &[f64], 76 | volatilities: &mut[f64]) -> Result<(), qm::Error> { 77 | 78 | let n = strikes.len(); 79 | assert!(n == volatilities.len()); 80 | 81 | for i in 0..n { 82 | volatilities[i] = self.smile.interpolate(strikes[i])?; 83 | } 84 | Ok(()) 85 | } 86 | } 87 | 88 | impl CubicSplineSmile { 89 | 90 | /// Creates a cubic spline smile that interpolates between the given 91 | /// pillar volatilities. The supplied vector is of (strike, volatility) 92 | /// pairs. 93 | pub fn new(pillars: &[(f64, f64)]) -> Result { 94 | let i = CubicSpline::new(pillars, Extrap::Natural, Extrap::Natural)?; 95 | Ok(CubicSplineSmile { smile: i }) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use math::numerics::approx_eq; 103 | 104 | #[test] 105 | fn test_flat_smile() { 106 | let vol = 0.2; 107 | let smile = FlatSmile::new(vol).unwrap(); 108 | 109 | let strikes = vec![60.0, 70.0, 80.0]; 110 | let mut vols = vec![0.0; strikes.len()]; 111 | 112 | smile.volatilities(&strikes, &mut vols).unwrap(); 113 | 114 | for i in 0..vols.len() { 115 | assert!(approx_eq(vols[i], vol, 1e-12), 116 | "vol={} expected={}", vols[i], vol); 117 | } 118 | } 119 | 120 | #[test] 121 | fn test_cubic_spline_smile() { 122 | let points = [(70.0, 0.4), (80.0, 0.3), (90.0, 0.22), (100.0, 0.25)]; 123 | let smile = CubicSplineSmile::new(&points).unwrap(); 124 | 125 | let strikes = vec![60.0, 70.0, 80.0, 85.0, 90.0, 95.0, 100.0, 110.0]; 126 | let mut vols = vec![0.0; strikes.len()]; 127 | 128 | smile.volatilities(&strikes, &mut vols).unwrap(); 129 | 130 | let expected = vec![0.5, 0.4, 0.3, 0.25025, 0.22, 0.2245, 0.25, 0.28]; 131 | 132 | for i in 0..vols.len() { 133 | assert!(approx_eq(vols[i], expected[i], 1e-12), 134 | "vol={} expected={}", vols[i], expected[i]); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/dates/datetime.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | use math::interpolation::Interpolable; 3 | use std::cmp::Ordering; 4 | use std::ops::Add; 5 | use std::ops::AddAssign; 6 | use std::fmt::Display; 7 | use std::fmt; 8 | 9 | /// We define some commonly used times of day. These map to different amounts 10 | /// of volatility day_fraction depending on the exchange etc. 11 | /// 12 | /// Strictly speaking, Exchange Delivery Settlement Price or EDSP is a 13 | /// methodology rather than a time. However, it results in a measurement 14 | /// at an expected amount of volatility time through the day (near the open 15 | /// for US derivatives, near the close for European ones). More times may be 16 | /// added to this list. 17 | /// 18 | /// We do assume an ordering of the enums here, matching the order they are 19 | /// expressed. If other values are added, such as LiborFixingTime, we may 20 | /// need to implement comparison functions manually, using Ord and PartialOrd. 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 22 | pub enum TimeOfDay { 23 | Open, 24 | EDSP, 25 | Close 26 | } 27 | 28 | impl Display for TimeOfDay { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | match *self { 31 | TimeOfDay::Open => write!(f, "Open"), 32 | TimeOfDay::Close => write!(f, "Close"), 33 | TimeOfDay::EDSP => write!(f, "EDSP") 34 | } 35 | } 36 | } 37 | 38 | /// Convenience struct that groups a date and a time of day. For example, this 39 | /// represents the time of a fixing. 40 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 41 | pub struct DateTime { 42 | date: Date, 43 | time_of_day: TimeOfDay 44 | } 45 | 46 | impl DateTime { 47 | pub fn new(date: Date, time_of_day: TimeOfDay) -> DateTime { 48 | DateTime { date: date, time_of_day: time_of_day } 49 | } 50 | 51 | pub fn date(&self) -> Date { self.date } 52 | pub fn time_of_day(&self) -> TimeOfDay { self.time_of_day } 53 | } 54 | 55 | impl Add for DateTime { 56 | type Output = DateTime; 57 | 58 | fn add(self, other: i32) -> DateTime { 59 | DateTime::new(self.date + other, self.time_of_day) 60 | } 61 | } 62 | 63 | impl AddAssign for DateTime { 64 | fn add_assign(&mut self, other: i32) { 65 | self.date += other; 66 | } 67 | } 68 | 69 | impl Display for DateTime { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | write!(f, "{}/{}", self.date, self.time_of_day) 72 | } 73 | } 74 | 75 | /// Do not implement Sub and SubAssign, as the difference between two 76 | /// TimeOfDay enums is not defined. 77 | 78 | /// Day-fractions are pretty much only used for volatilities and correlations. 79 | /// The time is a fraction between 0 and 1 that represents the fraction of 80 | /// volatility time of the current day. Vol time is a monotonic function of 81 | /// real time, but certainly not a linear one, and it varies depending on the 82 | /// location and even the underlier. 83 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] 84 | pub struct DateDayFraction { 85 | date: Date, 86 | day_fraction: f64 87 | } 88 | 89 | impl DateDayFraction { 90 | pub fn new(date: Date, day_fraction: f64) -> DateDayFraction { 91 | assert!(day_fraction >= 0.0 && day_fraction < 1.0); 92 | DateDayFraction { date: date, day_fraction: day_fraction } 93 | } 94 | 95 | pub fn date(&self) -> Date { self.date } 96 | pub fn day_fraction(&self) -> f64 { self.day_fraction } 97 | } 98 | 99 | impl Add for DateDayFraction { 100 | type Output = DateDayFraction; 101 | 102 | fn add(self, other: i32) -> DateDayFraction { 103 | DateDayFraction::new(self.date + other, self.day_fraction) 104 | } 105 | } 106 | 107 | impl AddAssign for DateDayFraction { 108 | fn add_assign(&mut self, other: i32) { 109 | self.date += other; 110 | } 111 | } 112 | 113 | impl Ord for DateDayFraction { 114 | fn cmp(&self, other: &DateDayFraction) -> Ordering { 115 | self.partial_cmp(&other).expect("Non-orderable day fraction found in DateDayFraction") 116 | } 117 | } 118 | 119 | impl Eq for DateDayFraction {} 120 | 121 | /// Do not implement Sub and SubAssign, as the result of these operations is 122 | /// unlikely to make sense. Note that the DayFraction is a measure of vol time, 123 | /// but the subtraction of the dates would give a measure of calendar time. 124 | 125 | /// We implement Interpolable for DateDayFraction but it is not very useful. 126 | /// The interp_diff function works in calendar days, which is correct for 127 | /// some vol surfaces (fx maybe), but in general business days would make 128 | /// more sense. The main reason we use it is for interp_cmp, which allows 129 | /// ordering of DateDayFraction. 130 | impl Interpolable for DateDayFraction { 131 | fn interp_diff(&self, other: DateDayFraction) -> f64 { 132 | (other.date - self.date) as f64 133 | + other.day_fraction - self.day_fraction 134 | } 135 | 136 | fn interp_cmp(&self, other: DateDayFraction) -> Ordering { 137 | 138 | // We cannot use self.cmp because day_fraction is an f64, 139 | // which only supports partial ordering. However, we know 140 | // the day fraction is not NaN (see DateDayFraction::new) 141 | // so we can just panic if the order does not exist. 142 | match self.partial_cmp(&other) { 143 | Some(order) => order, 144 | None => panic!("DateDayFraction contains NaN day-fraction") 145 | } 146 | } 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use super::*; 152 | 153 | #[test] 154 | fn equality_and_order_for_date_times() { 155 | 156 | let thursday = Date::from_ymd(2018, 05, 10); 157 | let thursday_early = DateTime::new(thursday, TimeOfDay::Open); 158 | let thursday_late = DateTime::new(thursday, TimeOfDay::Close); 159 | let friday_early = DateTime::new(thursday + 1, TimeOfDay::EDSP); 160 | let wednesday_late = DateTime::new(thursday - 1, TimeOfDay::Close); 161 | let thursday_early2 = DateTime::new(thursday, TimeOfDay::Open); 162 | 163 | assert!(thursday_early == thursday_early2); 164 | assert!(thursday_early != friday_early); 165 | assert!(thursday_late != thursday_early); 166 | assert!(thursday_late < friday_early); 167 | assert!(thursday_early < thursday_late); 168 | assert!(wednesday_late < thursday_early); 169 | assert!(friday_early > thursday_early); 170 | assert!(thursday_late <= friday_early); 171 | } 172 | 173 | #[test] 174 | fn equality_and_order_for_date_day_fractions() { 175 | 176 | let thursday = Date::from_ymd(2018, 05, 10); 177 | let thursday_early = DateDayFraction::new(thursday, 0.1); 178 | let thursday_late = DateDayFraction::new(thursday, 0.9); 179 | let friday_early = DateDayFraction::new(thursday + 1, 0.1); 180 | let wednesday_late = DateDayFraction::new(thursday - 1, 0.9); 181 | let thursday_early2 = DateDayFraction::new(thursday, 0.1); 182 | 183 | assert!(thursday_early == thursday_early2); 184 | assert!(thursday_early != friday_early); 185 | assert!(thursday_late != thursday_early); 186 | assert!(thursday_late < friday_early); 187 | assert!(thursday_early < thursday_late); 188 | assert!(wednesday_late < thursday_early); 189 | assert!(friday_early > thursday_early); 190 | assert!(thursday_late <= friday_early); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/dates/rules.rs: -------------------------------------------------------------------------------- 1 | use dates::Date; 2 | use dates::calendar::RcCalendar; 3 | use core::factories::TypeId; 4 | use core::factories::Registry; 5 | use core::factories::Qrc; 6 | use std::sync::Arc; 7 | use std::fmt::Debug; 8 | use erased_serde as esd; 9 | use serde as sd; 10 | use serde_tagged as sdt; 11 | use serde_tagged::de::BoxFnSeed; 12 | use serde::Deserialize; 13 | 14 | /// Date rules are used for rolling out schedules of dates and for adjusting 15 | /// dates to move them onto business dates. 16 | 17 | pub trait DateRule : esd::Serialize + TypeId + Sync + Send + Debug { 18 | 19 | /// Applies this date rule to the given date, returning an adjusted date. 20 | fn apply(&self, date: Date) -> Date; 21 | } 22 | 23 | 24 | // Get serialization to work recursively for rate curves by using the 25 | // technology defined in core/factories. RcDateRule is a container 26 | // class holding a DateRule 27 | pub type RcDateRule = Qrc; 28 | pub type TypeRegistry = Registry>; 29 | 30 | /// Implement deserialization for subclasses of the type 31 | impl<'de> sd::Deserialize<'de> for RcDateRule { 32 | fn deserialize(deserializer: D) -> Result 33 | where D: sd::Deserializer<'de> 34 | { 35 | sdt::de::external::deserialize(deserializer, get_registry()) 36 | } 37 | } 38 | 39 | /// Return the type registry required for deserialization. 40 | pub fn get_registry() -> &'static TypeRegistry { 41 | lazy_static! { 42 | static ref REG: TypeRegistry = { 43 | let mut reg = TypeRegistry::new(); 44 | reg.insert("NullRule", BoxFnSeed::new(NullRule::from_serial)); 45 | reg.insert("BusinessDays", BoxFnSeed::new(BusinessDays::from_serial)); 46 | reg.insert("ModifiedFollowing", BoxFnSeed::new(ModifiedFollowing::from_serial)); 47 | reg 48 | }; 49 | } 50 | ® 51 | } 52 | 53 | /// Null rule. Returns the date you give it 54 | #[derive(Serialize, Deserialize, Debug)] 55 | pub struct NullRule {} 56 | 57 | impl DateRule for NullRule { 58 | fn apply(&self, date: Date) -> Date { date } 59 | } 60 | 61 | impl TypeId for NullRule { 62 | fn get_type_id(&self) -> &'static str { "NullRule" } 63 | } 64 | 65 | impl NullRule { 66 | pub fn new() -> NullRule { NullRule { } } 67 | 68 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result { 69 | Ok(Qrc::new(Arc::new(NullRule::deserialize(de)?))) 70 | } 71 | } 72 | 73 | /// Move to the next business day in a given calendar 74 | #[derive(Serialize, Deserialize, Debug)] 75 | pub struct BusinessDays { 76 | calendar: RcCalendar, 77 | step: i32, 78 | slip_forward: bool 79 | } 80 | 81 | impl TypeId for BusinessDays { 82 | fn get_type_id(&self) -> &'static str { "BusinessDays" } 83 | } 84 | 85 | impl BusinessDays { 86 | 87 | /// Creates a rule that steps to the next business day or stays put if 88 | /// today is a business day. 89 | pub fn new_next(calendar: RcCalendar) -> BusinessDays { 90 | BusinessDays { 91 | calendar: calendar, 92 | step: 0, 93 | slip_forward: true } 94 | } 95 | 96 | /// Creates a rule that steps to the previous business day or stays put if 97 | /// today is a business day. 98 | pub fn new_prev(calendar: RcCalendar) -> BusinessDays { 99 | BusinessDays { 100 | calendar: calendar, 101 | step: 0, 102 | slip_forward: false } 103 | } 104 | 105 | /// Creates a rule that steps forward a given number of business days 106 | pub fn new_step(calendar: RcCalendar, step: u32) -> BusinessDays { 107 | BusinessDays { 108 | calendar: calendar, 109 | step: step as i32, 110 | slip_forward: true } 111 | } 112 | 113 | /// Creates a rule that steps backward a given number of business days 114 | pub fn new_back(calendar: RcCalendar, step: u32) -> BusinessDays { 115 | BusinessDays { 116 | calendar: calendar, 117 | step: -(step as i32), 118 | slip_forward: false } 119 | } 120 | 121 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result { 122 | Ok(Qrc::new(Arc::new(BusinessDays::deserialize(de)?))) 123 | } 124 | } 125 | 126 | impl DateRule for BusinessDays { 127 | fn apply(&self, date: Date) -> Date { 128 | self.calendar.step(date, self.step, self.slip_forward) 129 | } 130 | } 131 | 132 | /// Move to the next business day unless that would take us into a different 133 | /// month, in which case we move to the previous business day. 134 | #[derive(Serialize, Deserialize, Debug)] 135 | pub struct ModifiedFollowing { 136 | calendar: RcCalendar 137 | } 138 | 139 | impl TypeId for ModifiedFollowing { 140 | fn get_type_id(&self) -> &'static str { "ModifiedFollowing" } 141 | } 142 | 143 | impl ModifiedFollowing { 144 | pub fn new(calendar: RcCalendar) -> ModifiedFollowing { 145 | ModifiedFollowing { calendar: calendar } 146 | } 147 | 148 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result { 149 | Ok(Qrc::new(Arc::new(ModifiedFollowing::deserialize(de)?))) 150 | } 151 | } 152 | 153 | impl DateRule for ModifiedFollowing { 154 | fn apply(&self, date: Date) -> Date { 155 | let (_, m1, _) = date.ymd(); 156 | 157 | // try stepping forwards and return the result if in same month 158 | let forward = self.calendar.step(date, 0, true); 159 | let (_, m2, _) = forward.ymd(); 160 | if m2 == m1 { 161 | forward 162 | } else { 163 | // go backwards instead (assumes this is not a month of holidays) 164 | self.calendar.step(date, 0, false) 165 | } 166 | } 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use super::*; 172 | use dates::calendar::WeekdayCalendar; 173 | use std::str::FromStr; 174 | 175 | #[test] 176 | fn next_business_date() { 177 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar{})); 178 | let rule = BusinessDays::new_next(calendar); 179 | 180 | let start = Date::from_str("2017-01-01").unwrap(); 181 | let next = Date::from_str("2017-01-02").unwrap(); 182 | let step1 = rule.apply(start); 183 | assert_eq!(step1, next); 184 | 185 | let step2 = rule.apply(step1); 186 | assert_eq!(step2, next); 187 | } 188 | 189 | #[test] 190 | fn prev_business_date() { 191 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar{})); 192 | let rule = BusinessDays::new_prev(calendar); 193 | 194 | let start = Date::from_str("2017-01-01").unwrap(); 195 | let prev = Date::from_str("2016-12-30").unwrap(); 196 | let step1 = rule.apply(start); 197 | assert_eq!(step1, prev, "step1={}:{} prev={}:{} start={}:{}", 198 | step1.to_string(), step1.day_of_week(), 199 | prev.to_string(), prev.day_of_week(), 200 | start.to_string(), start.day_of_week()); 201 | 202 | let step2 = rule.apply(step1); 203 | assert_eq!(step2, prev); 204 | } 205 | 206 | #[test] 207 | fn step_forward_business_date() { 208 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar{})); 209 | let rule = BusinessDays::new_step(calendar, 2); 210 | 211 | let start = Date::from_str("2017-01-01").unwrap(); 212 | let step1 = rule.apply(start); 213 | assert_eq!(step1, Date::from_str("2017-01-04").unwrap()); 214 | 215 | let step2 = rule.apply(step1); 216 | assert_eq!(step2, Date::from_str("2017-01-06").unwrap()); 217 | 218 | let step3 = rule.apply(step2); 219 | assert_eq!(step3, Date::from_str("2017-01-10").unwrap()); 220 | } 221 | 222 | #[test] 223 | fn step_back_business_date() { 224 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar{})); 225 | let rule = BusinessDays::new_back(calendar, 2); 226 | 227 | let start = Date::from_str("2017-01-01").unwrap(); 228 | let step1 = rule.apply(start); 229 | assert_eq!(step1, Date::from_str("2016-12-28").unwrap()); 230 | 231 | let step2 = rule.apply(step1); 232 | assert_eq!(step2, Date::from_str("2016-12-26").unwrap()); 233 | } 234 | 235 | #[test] 236 | fn modified_following() { 237 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar{})); 238 | let rule = ModifiedFollowing::new(calendar); 239 | 240 | let start1 = Date::from_str("2016-12-31").unwrap(); 241 | let prev = Date::from_str("2016-12-30").unwrap(); 242 | let start2 = Date::from_str("2017-01-01").unwrap(); 243 | let next = Date::from_str("2017-01-02").unwrap(); 244 | let step1 = rule.apply(start1); 245 | assert_eq!(step1, prev); // step backwards at end of month 246 | 247 | let step2 = rule.apply(step1); 248 | assert_eq!(step2, prev); // no step if already on a business day 249 | 250 | let step3 = rule.apply(start2); 251 | assert_eq!(step3, next); // step forwards in same month 252 | 253 | let step4 = rule.apply(step3); 254 | assert_eq!(step4, next); // no step if already on a business day 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/facade/handle.rs: -------------------------------------------------------------------------------- 1 | use core::qm; 2 | use instruments::RcInstrument; 3 | use instruments::assets::RcCurrency; 4 | use risk::{RcReportGenerator, BoxReport}; 5 | use risk::marketdata::RcMarketData; 6 | use data::fixings::RcFixingTable; 7 | use pricers::RcPricerFactory; 8 | 9 | /// A handle is used when passing objects or errors into or out of 10 | /// quantmath to other languages such as C or Python, where they 11 | /// appear as a u64 (long in C). 12 | #[derive(Debug)] 13 | pub enum Handle { 14 | Empty, 15 | Instrument(RcInstrument), 16 | Currency(RcCurrency), 17 | MarketData(RcMarketData), 18 | FixingTable(RcFixingTable), 19 | PricerFactory(RcPricerFactory), 20 | ReportGenerator(RcReportGenerator), 21 | Reports(Vec), 22 | Err(qm::Error), 23 | } 24 | 25 | impl Handle { 26 | pub fn from_empty(result: Result<(), qm::Error>) -> Handle { 27 | match result { 28 | Ok(()) => Handle::Empty, 29 | Err(err) => Handle::Err(err) 30 | } 31 | } 32 | 33 | pub fn from_instrument(result: Result) -> Handle { 34 | match result { 35 | Ok(instr) => Handle::Instrument(instr), 36 | Err(err) => Handle::Err(err) 37 | } 38 | } 39 | 40 | pub fn from_currency(result: Result) -> Handle { 41 | match result { 42 | Ok(ccy) => Handle::Currency(ccy), 43 | Err(err) => Handle::Err(err) 44 | } 45 | } 46 | 47 | pub fn from_fixing_table(result: Result) -> Handle { 48 | match result { 49 | Ok(fix) => Handle::FixingTable(fix), 50 | Err(err) => Handle::Err(err) 51 | } 52 | } 53 | 54 | pub fn from_market_data(result: Result) -> Handle { 55 | match result { 56 | Ok(mkt) => Handle::MarketData(mkt), 57 | Err(err) => Handle::Err(err) 58 | } 59 | } 60 | 61 | pub fn from_pricer_factory(result: Result) -> Handle { 62 | match result { 63 | Ok(pf) => Handle::PricerFactory(pf), 64 | Err(err) => Handle::Err(err) 65 | } 66 | } 67 | 68 | pub fn from_report_generator(result: Result) -> Handle { 69 | match result { 70 | Ok(gen) => Handle::ReportGenerator(gen), 71 | Err(err) => Handle::Err(err) 72 | } 73 | } 74 | 75 | pub fn from_reports(result: Result, qm::Error>) -> Handle { 76 | match result { 77 | Ok(reports) => Handle::Reports(reports), 78 | Err(err) => Handle::Err(err) 79 | } 80 | } 81 | 82 | pub fn from_error(error: qm::Error) -> Handle { 83 | Handle::Err(error) 84 | } 85 | 86 | pub fn as_empty(&self) -> Result<(), qm::Error> { 87 | match self { 88 | &Handle::Empty => Ok(()), 89 | &Handle::Err(ref err) => Err(err.clone()), 90 | _ => Err(self.wrong_type("Empty")) 91 | } 92 | } 93 | 94 | pub fn as_instrument(&self) -> Result { 95 | match self { 96 | &Handle::Instrument(ref instr) => Ok(instr.clone()), 97 | &Handle::Err(ref err) => Err(err.clone()), 98 | _ => Err(self.wrong_type("Instrument")) 99 | } 100 | } 101 | 102 | pub fn as_currency(&self) -> Result { 103 | match self { 104 | &Handle::Currency(ref ccy) => Ok(ccy.clone()), 105 | &Handle::Err(ref err) => Err(err.clone()), 106 | _ => Err(self.wrong_type("Currency")) 107 | } 108 | } 109 | 110 | pub fn as_market_data(&self) -> Result { 111 | match self { 112 | &Handle::MarketData(ref mkt) => Ok(mkt.clone()), 113 | &Handle::Err(ref err) => Err(err.clone()), 114 | _ => Err(self.wrong_type("MarketData")) 115 | } 116 | } 117 | 118 | pub fn as_fixing_table(&self) -> Result { 119 | match self { 120 | &Handle::FixingTable(ref fix) => Ok(fix.clone()), 121 | &Handle::Err(ref err) => Err(err.clone()), 122 | _ => Err(self.wrong_type("FixingTable")) 123 | } 124 | } 125 | 126 | pub fn as_pricer_factory(&self) -> Result { 127 | match self { 128 | &Handle::PricerFactory(ref pf) => Ok(pf.clone()), 129 | &Handle::Err(ref err) => Err(err.clone()), 130 | _ => Err(self.wrong_type("PricerFactory")) 131 | } 132 | } 133 | 134 | pub fn as_report_generator(&self) -> Result { 135 | match self { 136 | &Handle::ReportGenerator(ref gen) => Ok(gen.clone()), 137 | &Handle::Err(ref err) => Err(err.clone()), 138 | _ => Err(self.wrong_type("ReportGenerator")) 139 | } 140 | } 141 | 142 | pub fn as_reports(self) -> Result, qm::Error> { 143 | match self { 144 | Handle::Reports(reports) => Ok(reports), 145 | Handle::Err(err) => Err(err), 146 | _ => Err(self.wrong_type("Reports")) 147 | } 148 | } 149 | 150 | pub fn as_error(&self) -> qm::Error { 151 | match self { 152 | &Handle::Err(ref err) => err.clone(), 153 | _ => self.wrong_type("Error") 154 | } 155 | } 156 | 157 | fn wrong_type(&self, requested: &str) -> qm::Error { 158 | 159 | let supplied = match self { 160 | &Handle::Empty => "Empty", 161 | &Handle::Instrument(_) => "Instrument", 162 | &Handle::Currency(_) => "Currency", 163 | &Handle::MarketData(_) => "MarketData", 164 | &Handle::FixingTable(_) => "FixingTable", 165 | &Handle::PricerFactory(_) => "PricerFactory", 166 | &Handle::ReportGenerator(_) => "ReportGenerator", 167 | &Handle::Reports(_) => "Reports", 168 | &Handle::Err(_) => "Error" 169 | }; 170 | 171 | qm::Error::new(&format!("Wrong handle type: {} required but {} supplied", requested, supplied)) 172 | } 173 | } 174 | 175 | impl Clone for Handle { 176 | /// Almost any type of handle can be cloned cleanly. The exception is a vector of reports, which 177 | /// is held in non-cloneable boxes. If a handle containing reports is cloned, the result is an 178 | /// error handle. 179 | fn clone(&self) -> Handle { 180 | match self { 181 | &Handle::Empty => Handle::Empty, 182 | &Handle::Instrument(ref instr) => Handle::Instrument(instr.clone()), 183 | &Handle::Currency(ref ccy) => Handle::Currency(ccy.clone()), 184 | &Handle::MarketData(ref mkt) => Handle::MarketData(mkt.clone()), 185 | &Handle::FixingTable(ref fix) => Handle::FixingTable(fix.clone()), 186 | &Handle::PricerFactory(ref pf) => Handle::PricerFactory(pf.clone()), 187 | &Handle::ReportGenerator(ref gen) => Handle::ReportGenerator(gen.clone()), 188 | &Handle::Reports(_) => Handle::Err(qm::Error::new("Reports cannot be cloned")), 189 | &Handle::Err(ref err) => Handle::Err(err.clone()) 190 | } 191 | } 192 | } 193 | 194 | pub mod extern_handle { 195 | use super::*; 196 | use instruments::RcInstrument; 197 | use instruments::assets::RcCurrency; 198 | use risk::RcReportGenerator; 199 | use risk::marketdata::RcMarketData; 200 | use data::fixings::RcFixingTable; 201 | use pricers::RcPricerFactory; 202 | use facade::write_results; 203 | use std::error::Error; 204 | use std::io::Cursor; 205 | 206 | /// Converts a result containing either a handle or an error 207 | /// into a u64. The u64 is the address of a small heap-allocated object 208 | /// that contains the reference counted pointer to the object. Thus, the 209 | /// handle *must* be freed or the reference count will remain incremented and 210 | /// the object itself will never be deleted. 211 | pub fn from_handle(result: Result) -> u64 { 212 | let boxed = Box::new( match result { 213 | Ok(handle) => handle, 214 | Err(err) => Handle::from_error(err) }); 215 | Box::into_raw(boxed) as u64 216 | } 217 | 218 | pub fn clone_handle(handle: u64) -> u64 { 219 | println!("clone_handle: {}", handle); 220 | // create a clone, which will be returned 221 | let cloned = Box::new(handle_from_ext(handle).clone()); 222 | 223 | // convert it into an ext handle 224 | Box::into_raw(cloned) as u64 225 | } 226 | 227 | /// Takes a handle as returned by from_instrument and converts it back to a 228 | /// reference-counted pointer to an instrument. The handle is not freed as part of 229 | /// this procedure, and must be freed later with a free_handle call. 230 | pub fn as_instrument(handle: u64) -> Result { 231 | handle_from_ext(handle).as_instrument() 232 | } 233 | 234 | /// See documentation for as_instrument 235 | pub fn as_currency(handle: u64) -> Result { 236 | handle_from_ext(handle).as_currency() 237 | } 238 | 239 | /// See documentation for as_instrument 240 | pub fn as_market_data(handle: u64) -> Result { 241 | handle_from_ext(handle).as_market_data() 242 | } 243 | 244 | /// See documentation for as_instrument 245 | pub fn as_fixing_table(handle: u64) -> Result { 246 | handle_from_ext(handle).as_fixing_table() 247 | } 248 | 249 | /// See documentation for as_instrument 250 | pub fn as_pricer_factory(handle: u64) -> Result { 251 | handle_from_ext(handle).as_pricer_factory() 252 | } 253 | 254 | /// See documentation for as_instrument 255 | pub fn as_report_generator(handle: u64) -> Result { 256 | handle_from_ext(handle).as_report_generator() 257 | } 258 | 259 | /// Converts the handle into a vector of reports. 260 | /// Unlike the other methods in this module, this also frees the handle that is passed in. 261 | pub fn as_reports(handle_which_is_freed: u64) -> Result, qm::Error> { 262 | let handle = unsafe { Box::from_raw(handle_which_is_freed as *mut Handle) }; 263 | handle.as_reports() 264 | } 265 | 266 | /// Converts a report into a JSON string. This call does not consume the handle 267 | /// that is passed in. 268 | pub fn reports_as_string(handle: u64) -> String { 269 | let handle_ptr = handle as *mut Handle; 270 | unsafe { 271 | match *handle_ptr { 272 | Handle::Reports(ref reports) => { 273 | // TODO this code panics too much. Need better error handling. 274 | let mut buffer = Vec::new(); 275 | write_results(&reports, true, &mut Cursor::new(&mut buffer)).unwrap(); 276 | String::from_utf8(buffer).unwrap() 277 | }, 278 | Handle::Err(ref err) => err.description().to_string(), 279 | _ => "reports_as_string: not a report handle".to_string() 280 | } 281 | } 282 | } 283 | 284 | /// Converts a handle into an error. Normally, this is called following a call to 285 | /// is_error, which has returned true, so we know the handle contains an error. If 286 | /// it is called when the handle does not contain an error, this is itself an 287 | /// error. 288 | /// 289 | /// The handle is not freed as a result of this call. 290 | pub fn as_error(handle: u64) -> qm::Error { 291 | handle_from_ext(handle).as_error() 292 | } 293 | 294 | /// Tests whether a handle contains an error. Never consumes the handle. This function 295 | /// must never panic, as it is invoked direct from C. 296 | pub fn is_error(handle: u64) -> bool { 297 | if let &Handle::Err(_) = handle_from_ext(handle) { true } else { false } 298 | } 299 | 300 | /// The handle is freed as a result of this call. This function 301 | /// must never panic, as it is invoked direct from C. 302 | pub fn free_handle(handle: u64) { 303 | unsafe { Box::from_raw(handle as *mut Handle) }; 304 | } 305 | 306 | /// Warning: the lifetime of the returned Handle claims to be static. In fact, it 307 | /// is the lifetime of the external handle, but there is no way of specifying this 308 | /// in Rust. 309 | fn handle_from_ext(handle: u64) -> &'static Handle { 310 | let handle_ptr = handle as *mut Handle; 311 | unsafe { &*handle_ptr } 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/instruments/basket.rs: -------------------------------------------------------------------------------- 1 | use core::factories::TypeId; 2 | use instruments::fix_all; 3 | use std::sync::Arc; 4 | use std::fmt::Display; 5 | use std::fmt; 6 | use std::cmp::Ordering; 7 | use std::hash::Hash; 8 | use std::hash::Hasher; 9 | use instruments::Instrument; 10 | use instruments::RcInstrument; 11 | use instruments::Priceable; 12 | use instruments::PricingContext; 13 | use instruments::DependencyContext; 14 | use instruments::SpotRequirement; 15 | use instruments::assets::Currency; 16 | use instruments::assets::RcCurrency; 17 | use dates::rules::RcDateRule; 18 | use dates::datetime::TimeOfDay; 19 | use dates::datetime::DateTime; 20 | use dates::datetime::DateDayFraction; 21 | use data::fixings::FixingTable; 22 | use core::qm; 23 | use core::factories::Qrc; 24 | use core::dedup::InstanceId; 25 | use erased_serde as esd; 26 | use serde::Deserialize; 27 | 28 | /// A basket of instruments, such as equities or composites. This uses 29 | /// the composite pattern (see Gang of Four Design Patterns). 30 | #[derive(Clone, Serialize, Deserialize, Debug)] 31 | pub struct Basket { 32 | id: String, 33 | credit_id: String, 34 | currency: RcCurrency, 35 | settlement: RcDateRule, 36 | basket: Vec<(f64, RcInstrument)> 37 | } 38 | 39 | impl TypeId for Basket { 40 | fn get_type_id(&self) -> &'static str { "Basket" } 41 | } 42 | 43 | impl InstanceId for Basket { 44 | fn id(&self) -> &str { &self.id } 45 | } 46 | 47 | impl Basket { 48 | pub fn new(id: &str, credit_id: &str, currency: RcCurrency, 49 | settlement: RcDateRule, basket: Vec<(f64, RcInstrument)>) 50 | -> Result { 51 | 52 | // validate that the basket members are all in the right currency 53 | 54 | Ok(Basket { id: id.to_string(), credit_id: credit_id.to_string(), 55 | currency: currency, settlement: settlement , basket: basket }) 56 | } 57 | 58 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result, esd::Error> { 59 | Ok(Qrc::new(Arc::new(Basket::deserialize(de)?))) 60 | } 61 | } 62 | 63 | impl Instrument for Basket { 64 | fn payoff_currency(&self) -> &Currency { &*self.currency } 65 | fn credit_id(&self) -> &str { &self.credit_id } 66 | fn settlement(&self) -> &RcDateRule { &self.settlement } 67 | 68 | fn dependencies(&self, context: &mut DependencyContext) 69 | -> SpotRequirement { 70 | for &(_, ref underlying) in self.basket.iter() { 71 | let spot_requirement = underlying.dependencies(context); 72 | match spot_requirement { 73 | SpotRequirement::NotRequired => {}, // nothing to do 74 | _ => context.spot(&underlying) 75 | }; 76 | } 77 | SpotRequirement::NotRequired 78 | } 79 | 80 | fn time_to_day_fraction(&self, date_time: DateTime) 81 | -> Result { 82 | 83 | // for now, we hard-code the conversion. Later we shall 84 | // allow this to be set per equity 85 | let day_fraction = match date_time.time_of_day() { 86 | TimeOfDay::Open => 0.0, 87 | TimeOfDay::EDSP => 0.0, 88 | TimeOfDay::Close => 0.8 }; 89 | Ok(DateDayFraction::new(date_time.date(), day_fraction)) 90 | } 91 | 92 | fn as_priceable(&self) -> Option<&Priceable> { 93 | Some(self) 94 | } 95 | 96 | fn fix(&self, fixing_table: &FixingTable) 97 | -> Result>, qm::Error> { 98 | 99 | match fix_all(&self.basket, fixing_table)? { 100 | Some(basket) => { 101 | let id = format!("{}:fixed", self.id()); 102 | let replacement : RcInstrument = RcInstrument::new(Qrc::new(Arc::new( 103 | Basket::new(&id, self.credit_id(), self.currency.clone(), self.settlement().clone(), basket)?))); 104 | Ok(Some(vec![(1.0, replacement)])) 105 | }, 106 | None => Ok(None) 107 | } 108 | } 109 | } 110 | 111 | impl Display for Basket { 112 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 113 | self.id.fmt(f) 114 | } 115 | } 116 | 117 | impl Ord for Basket { 118 | fn cmp(&self, other: &Basket) -> Ordering { 119 | self.id.cmp(&other.id) 120 | } 121 | } 122 | 123 | impl PartialOrd for Basket { 124 | fn partial_cmp(&self, other: &Basket) -> Option { 125 | Some(self.cmp(other)) 126 | } 127 | } 128 | 129 | impl PartialEq for Basket { 130 | fn eq(&self, other: &Basket) -> bool { 131 | self.id == other.id 132 | } 133 | } 134 | 135 | impl Eq for Basket {} 136 | 137 | impl Hash for Basket { 138 | fn hash(&self, state: &mut H) { 139 | self.id.hash(state); 140 | } 141 | } 142 | 143 | impl Priceable for Basket { 144 | fn as_instrument(&self) -> &Instrument { self } 145 | 146 | /// The price of a basket is the weighted sum of the components. 147 | fn prices(&self, context: &PricingContext, dates: &[DateTime], out: &mut [f64]) 148 | -> Result<(), qm::Error> { 149 | let n_dates = dates.len(); 150 | assert_eq!(dates.len(), out.len()); 151 | 152 | for o in out.iter_mut() { 153 | *o = 0.0; 154 | } 155 | 156 | let mut temp = vec!(0.0; n_dates); 157 | 158 | for &(weight, ref underlying) in self.basket.iter() { 159 | let priceable = underlying.as_priceable().ok_or_else(|| qm::Error::new( 160 | "The underlying of an basket must be priceable"))?; 161 | priceable.prices(context, dates, &mut temp)?; 162 | for (i, o) in temp.iter().zip(out.iter_mut()) { 163 | *o += i * weight; 164 | } 165 | } 166 | 167 | Ok(()) 168 | } 169 | } 170 | 171 | #[cfg(test)] 172 | pub mod tests { 173 | use super::*; 174 | use math::numerics::approx_eq; 175 | use math::interpolation::Extrap; 176 | use data::forward::Forward; 177 | use data::volsurface::RcVolSurface; 178 | use data::curves::RateCurveAct365; 179 | use data::curves::RcRateCurve; 180 | use dates::Date; 181 | use data::forward::EquityForward; 182 | use data::curves::ZeroRateCurve; 183 | use data::divstream::DividendStream; 184 | use std::sync::Arc; 185 | use instruments::assets::tests::sample_currency; 186 | use instruments::assets::tests::sample_equity; 187 | 188 | pub fn sample_basket(step: u32) -> Basket { 189 | let currency = RcCurrency::new(Arc::new(sample_currency(step))); 190 | let az = RcInstrument::new(Qrc::new(Arc::new(sample_equity(currency.clone(), "AZ.L", step)))); 191 | let bp = RcInstrument::new(Qrc::new(Arc::new(sample_equity(currency.clone(), "BP.L", step)))); 192 | let basket = vec![(0.4, az.clone()), (0.6, bp.clone())]; 193 | Basket::new("basket", az.credit_id(), currency, az.settlement().clone(), basket).unwrap() 194 | } 195 | 196 | struct SamplePricingContext { 197 | spot_az: f64, 198 | spot_bp: f64 199 | } 200 | 201 | impl PricingContext for SamplePricingContext { 202 | fn spot_date(&self) -> Date { 203 | Date::from_ymd(2018, 06, 01) 204 | } 205 | 206 | fn yield_curve(&self, _credit_id: &str, 207 | _high_water_mark: Date) -> Result { 208 | 209 | let d = Date::from_ymd(2018, 05, 30); 210 | let points = [(d, 0.05), (d + 14, 0.08), (d + 56, 0.09), 211 | (d + 112, 0.085), (d + 224, 0.082)]; 212 | let c = RateCurveAct365::new(d, &points, 213 | Extrap::Flat, Extrap::Flat)?; 214 | Ok(RcRateCurve::new(Arc::new(c))) 215 | } 216 | 217 | fn spot(&self, id: &str) -> Result { 218 | if id == "AZ.L" { 219 | Ok(self.spot_az) 220 | } else if id == "BP.L" { 221 | Ok(self.spot_bp) 222 | } else { 223 | Err(qm::Error::new(&format!("{} not recognised for spot", id))) 224 | } 225 | } 226 | 227 | fn forward_curve(&self, instrument: &Instrument, 228 | high_water_mark: Date) -> Result, qm::Error> { 229 | let spot = self.spot(instrument.id())?; 230 | let base_date = self.spot_date(); 231 | let settlement = instrument.settlement().clone(); 232 | let rate = self.yield_curve(instrument.credit_id(), high_water_mark)?; 233 | let borrow = RcRateCurve::new(Arc::new(ZeroRateCurve::new(base_date))); 234 | let divs = DividendStream::new(&Vec::new(), RcRateCurve::new(Arc::new(ZeroRateCurve::new(base_date)))); 235 | let forward = EquityForward::new( 236 | base_date, spot, settlement, rate, borrow, &divs, high_water_mark)?; 237 | Ok(Arc::new(forward)) 238 | } 239 | 240 | fn vol_surface(&self, _instrument: &Instrument, _high_water_mark: Date, 241 | _forward_fn: &Fn() -> Result, qm::Error>) 242 | -> Result { 243 | Err(qm::Error::new("unsupported")) 244 | } 245 | 246 | fn correlation(&self, _first: &Instrument, _second: &Instrument) 247 | -> Result { 248 | Err(qm::Error::new("unsupported")) 249 | } 250 | } 251 | 252 | fn sample_pricing_context(spot_az: f64, spot_bp: f64) -> SamplePricingContext { 253 | SamplePricingContext { spot_az, spot_bp } 254 | } 255 | 256 | #[test] 257 | fn test_basket() { 258 | 259 | let context = sample_pricing_context(120.0, 230.0); 260 | let basket = sample_basket(2); 261 | let date = context.spot_date(); 262 | let price = basket.price(&context, DateTime::new(date, TimeOfDay::Close)).unwrap(); 263 | assert_approx(price, 120.0 * 0.4 + 230.0 * 0.6); 264 | 265 | let forward = basket.price(&context, DateTime::new(date + 365, TimeOfDay::Close)).unwrap(); 266 | assert_approx(forward, 201.95832229014877); 267 | } 268 | 269 | fn assert_approx(value: f64, expected: f64) { 270 | assert!(approx_eq(value, expected, 1e-12), 271 | "value={} expected={}", value, expected); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/instruments/bonds.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::fmt; 3 | use std::hash::Hash; 4 | use std::hash::Hasher; 5 | use std::sync::Arc; 6 | use instruments::Instrument; 7 | use instruments::Priceable; 8 | use instruments::PricingContext; 9 | use instruments::DependencyContext; 10 | use instruments::SpotRequirement; 11 | use instruments::assets::Currency; 12 | use instruments::assets::RcCurrency; 13 | use dates::Date; 14 | use dates::datetime::DateTime; 15 | use dates::rules::RcDateRule; 16 | use core::qm; 17 | use core::factories::TypeId; 18 | use core::factories::Qrc; 19 | use core::dedup::InstanceId; 20 | use serde::Deserialize; 21 | use erased_serde as esd; 22 | 23 | /// Represents a unit amount of currency to be paid at a specific date. 24 | #[derive(Clone, Serialize, Deserialize, Debug)] 25 | pub struct ZeroCoupon { 26 | id: String, 27 | credit_id: String, 28 | currency: RcCurrency, 29 | ex_date: DateTime, 30 | payment_date: Date, 31 | settlement: RcDateRule 32 | } 33 | 34 | impl TypeId for ZeroCoupon { 35 | fn get_type_id(&self) -> &'static str { "ZeroCoupon" } 36 | } 37 | 38 | impl InstanceId for ZeroCoupon { 39 | fn id(&self) -> &str { &self.id } 40 | } 41 | 42 | impl ZeroCoupon { 43 | /// Creates a zero coupon bond. It must have an id that uniquely 44 | /// represents it. It is discounted according to the yield curve 45 | /// matching its credit_id: it can therefore represent a risky 46 | /// bond. It pays on its payment date, but a settlement rule must 47 | /// be supplied in case the user does not pass in a discount date 48 | /// to discount to. Normally, the settlement rule should be that of 49 | /// the instrument that span off the zero coupon. 50 | pub fn new(id: &str, credit_id: &str, currency: RcCurrency, 51 | ex_date: DateTime, payment_date: Date, settlement: RcDateRule) -> ZeroCoupon { 52 | 53 | ZeroCoupon { id: id.to_string(), credit_id: credit_id.to_string(), 54 | currency: currency, ex_date: ex_date, payment_date: payment_date, 55 | settlement: settlement } 56 | } 57 | 58 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result, esd::Error> { 59 | Ok(Qrc::new(Arc::new(ZeroCoupon::deserialize(de)?))) 60 | } 61 | } 62 | 63 | impl Instrument for ZeroCoupon { 64 | 65 | fn payoff_currency(&self) -> &Currency { 66 | &*self.currency 67 | } 68 | 69 | fn credit_id(&self) -> &str { 70 | &self.credit_id 71 | } 72 | 73 | fn settlement(&self) -> &RcDateRule { 74 | // A settlement period for a zero coupon does not really make sense, 75 | // as they have explicit settlement dates. However, we need to supply 76 | // one in case the user supplies a discount date of None. 77 | &self.settlement 78 | } 79 | 80 | fn dependencies(&self, context: &mut DependencyContext) 81 | -> SpotRequirement { 82 | 83 | context.yield_curve(&self.credit_id, self.payment_date); 84 | 85 | // for a zero coupon, the spot is always one 86 | // (in units of its own currency) 87 | SpotRequirement::NotRequired 88 | } 89 | 90 | fn is_pure_rates(&self) -> bool { 91 | true 92 | } 93 | 94 | fn as_priceable(&self) -> Option<&Priceable> { 95 | Some(self) 96 | } 97 | } 98 | 99 | impl Display for ZeroCoupon { 100 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 101 | self.id.fmt(f) 102 | } 103 | } 104 | 105 | impl PartialEq for ZeroCoupon { 106 | fn eq(&self, other: &ZeroCoupon) -> bool { 107 | self.id == other.id 108 | } 109 | } 110 | 111 | impl Eq for ZeroCoupon {} 112 | 113 | impl Hash for ZeroCoupon { 114 | fn hash(&self, state: &mut H) { 115 | self.id.hash(state); 116 | } 117 | } 118 | 119 | impl Priceable for ZeroCoupon { 120 | fn as_instrument(&self) -> &Instrument { self } 121 | 122 | /// Currency is worth one currency unit, but only if we are discounting 123 | /// to the date which is when we would receive the currency. 124 | fn prices(&self, context: &PricingContext, dates: &[DateTime], out: &mut [f64]) 125 | -> Result<(), qm::Error> { 126 | assert_eq!(dates.len(), out.len()); 127 | 128 | let yc = context.yield_curve(&self.credit_id, self.payment_date)?; 129 | 130 | for (date, output) in dates.iter().zip(out.iter_mut()) { 131 | *output = if *date <= self.ex_date { 132 | let settlement_date = self.settlement().apply(date.date()); 133 | yc.df(self.payment_date, settlement_date)? 134 | } else { 135 | 0.0 136 | }; 137 | } 138 | 139 | 140 | Ok(()) 141 | } 142 | } 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | use super::*; 147 | use math::numerics::approx_eq; 148 | use math::interpolation::Extrap; 149 | use data::curves::RateCurveAct365; 150 | use data::curves::RcRateCurve; 151 | use data::forward::Forward; 152 | use data::volsurface::RcVolSurface; 153 | use dates::calendar::WeekdayCalendar; 154 | use dates::calendar::RcCalendar; 155 | use dates::rules::BusinessDays; 156 | use dates::Date; 157 | use dates::datetime::TimeOfDay; 158 | use std::sync::Arc; 159 | 160 | fn sample_currency(step: u32) -> Currency { 161 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar::new())); 162 | let settlement = RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, step))); 163 | Currency::new("GBP", settlement) 164 | } 165 | 166 | fn sample_zero_coupon(currency: RcCurrency, step: u32) -> ZeroCoupon { 167 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar::new())); 168 | let settlement = RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, step))); 169 | ZeroCoupon::new("GBP.2018-07-05", "OPT", currency, 170 | DateTime::new(Date::from_ymd(2018, 07, 03), TimeOfDay::Open), 171 | Date::from_ymd(2018, 07, 05), settlement) 172 | } 173 | 174 | struct SamplePricingContext { 175 | } 176 | 177 | impl PricingContext for SamplePricingContext { 178 | fn spot_date(&self) -> Date { 179 | Date::from_ymd(2018, 06, 01) 180 | } 181 | 182 | fn yield_curve(&self, _credit_id: &str, 183 | _high_water_mark: Date) -> Result { 184 | 185 | let d = Date::from_ymd(2018, 05, 30); 186 | let points = [(d, 0.05), (d + 14, 0.08), (d + 56, 0.09), 187 | (d + 112, 0.085), (d + 224, 0.082)]; 188 | let c = RateCurveAct365::new(d, &points, 189 | Extrap::Flat, Extrap::Flat)?; 190 | Ok(RcRateCurve::new(Arc::new(c))) 191 | } 192 | 193 | fn spot(&self, _id: &str) -> Result { 194 | Err(qm::Error::new("Spot not supported")) 195 | } 196 | 197 | fn forward_curve(&self, _instrument: &Instrument, 198 | _high_water_mark: Date) -> Result, qm::Error> { 199 | Err(qm::Error::new("Forward not supported")) 200 | } 201 | 202 | fn vol_surface(&self, _instrument: &Instrument, _high_water_mark: Date, 203 | _forward_fn: &Fn() -> Result, qm::Error>) 204 | -> Result { 205 | Err(qm::Error::new("VolSurface not supported")) 206 | } 207 | 208 | fn correlation(&self, _first: &Instrument, _second: &Instrument) 209 | -> Result { 210 | Err(qm::Error::new("correlation not supported")) 211 | } 212 | } 213 | 214 | fn sample_pricing_context() 215 | -> SamplePricingContext { 216 | SamplePricingContext { } 217 | } 218 | 219 | #[test] 220 | fn zero_coupon() { 221 | let val_date = DateTime::new(Date::from_ymd(2018, 06, 05), TimeOfDay::Open); 222 | let currency = RcCurrency::new(Arc::new(sample_currency(2))); 223 | let zero = sample_zero_coupon(currency, 2); 224 | let context = sample_pricing_context(); 225 | let price = zero.price(&context, val_date).unwrap(); 226 | assert_approx(price, 0.9930885737840461); 227 | } 228 | 229 | fn assert_approx(value: f64, expected: f64) { 230 | assert!(approx_eq(value, expected, 1e-12), 231 | "value={} expected={}", value, expected); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate statrs; 2 | extern crate ndarray; 3 | extern crate nalgebra; 4 | extern crate rand; 5 | extern crate serde; 6 | //extern crate serde_state as serde; 7 | #[macro_use] 8 | extern crate serde_derive; 9 | //#[macro_use] 10 | //extern crate serde_derive_state; 11 | extern crate erased_serde; 12 | extern crate serde_tagged; 13 | extern crate serde_json; 14 | #[macro_use] 15 | extern crate lazy_static; 16 | extern crate void; 17 | extern crate libc; 18 | 19 | // listed in dependency order, though this is not essential for compilation 20 | pub mod core; 21 | pub mod math; 22 | pub mod dates; 23 | pub mod data; 24 | pub mod instruments; 25 | pub mod risk; 26 | pub mod models; 27 | pub mod pricers; 28 | pub mod solvers; 29 | pub mod facade; 30 | -------------------------------------------------------------------------------- /src/math/brent.rs: -------------------------------------------------------------------------------- 1 | use core::qm; 2 | use std::f64::NAN; 3 | use std::f64::EPSILON; 4 | 5 | /// Brent's method of root-finding, based on the implementation given in 6 | /// Numerical Recipes in C by Press, Teukolsky, Vetterling and Flannery. 7 | /// The algorithm was developed in the 1960s by van Wijngaarden, Dekker et 8 | /// al and later improved by Brent. The algorithm uses bisection and 9 | /// inverse quadratic interpolation and is guaranteed to find a root, so 10 | /// long as one exists in the given range, and as long as it is allowed 11 | /// sufficient iterations. For smooth functions, it converges very quickly. 12 | pub fn zbrent(x1: f64, x2: f64, tol: f64, max_iter: u32, func: &mut F) 13 | -> Result 14 | where F: FnMut(f64) -> Result { 15 | 16 | let mut a = x1; 17 | let mut b = x2; 18 | let mut c = x2; 19 | 20 | let mut fa = func(a)?; 21 | let mut fb = func(b)?; 22 | if (fa > 0.0 && fb > 0.0) || (fa < 0.0 && fb < 0.0) { 23 | return Err(qm::Error::new("Root must be bracketed in zbrent")) 24 | } 25 | 26 | let mut d = NAN; 27 | let mut e = NAN; 28 | let mut q; 29 | let mut r; 30 | let mut p; 31 | 32 | let mut fc = fb; 33 | for _ in 0..max_iter { 34 | if (fb > 0.0 && fc > 0.0) || (fb < 0.0 && fc < 0.0) { 35 | // rename a, b, c and adjust bounding interval d 36 | c = a; 37 | fc = fa; 38 | d = b - a; 39 | e = d; 40 | } 41 | if fc.abs() < fb.abs() { 42 | a = b; 43 | b = c; 44 | c = a; 45 | fa = fb; 46 | fb = fc; 47 | fc = fa; 48 | } 49 | // convergence check 50 | let tol1 = 2.0 * EPSILON * b.abs() + 0.5 * tol; 51 | let xm = 0.5 * (c - b); 52 | if xm.abs() <= tol1 || fb == 0.0 { 53 | return Ok(b) 54 | } 55 | if e.abs() >= tol1 && fa.abs() > fb.abs() { 56 | // attempt inverse quadratic interpolation 57 | let s = fb / fa; 58 | if a == c { 59 | p = 2.0 * xm * s; 60 | q = 1.0 - s; 61 | } else { 62 | q = fa / fc; 63 | r = fb / fc; 64 | p = s * (2.0 * xm * q * (q - r) - (b - a) * (r - 1.0)); 65 | q = (q - 1.0) * (r - 1.0) * (s - 1.0); 66 | } 67 | // check whether in bounds 68 | if p > 0.0 { 69 | q = -q; 70 | } 71 | p = p.abs(); 72 | let min1 = 3.0 * xm * q * (tol1 - q).abs(); 73 | let min2 = (e * q).abs(); 74 | if 2.0 * p < min1.min(min2) { 75 | // accept interpolation 76 | e = d; 77 | d = p / q; 78 | } else { 79 | // interpolation failed, use bisection 80 | d = xm; 81 | e = d; 82 | } 83 | } else { 84 | // bounds decreasing too slowly, use bisection 85 | d = xm; 86 | e = d; 87 | } 88 | 89 | // move last best guess to a 90 | a = b; 91 | fa = fb; 92 | // evaluate new trial root 93 | if d.abs() > tol1 { 94 | b += d; 95 | } else { 96 | b += tol1.abs() * xm.signum(); 97 | } 98 | fb = func(b)?; 99 | } 100 | 101 | Err(qm::Error::new("Maximum number of iterations exceeded in zbrent")) 102 | } 103 | 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::*; 108 | use math::numerics::approx_eq; 109 | use std::f64::consts::PI; 110 | 111 | #[test] 112 | fn brent_arcsin() { 113 | 114 | let samples = vec![0.0, 0.1, 0.2, 0.3]; 115 | let min = 0.0; 116 | let max = PI * 0.5; 117 | let tol = 1e-10; 118 | let max_iter = 100; 119 | 120 | for v in samples.iter() { 121 | let y = zbrent(min, max, tol, max_iter, &mut |x : f64| Ok(x.sin() - *v)).unwrap(); 122 | let expected = v.asin(); 123 | assert!(approx_eq(y, expected, tol), "result={} expected={}", v, expected); 124 | } 125 | } 126 | 127 | #[test] 128 | fn problem_of_the_day() { 129 | 130 | // Stretch a piece of rope around the equator. Now add one meter to the length of 131 | // the rope. How tall a tent-pole can you now stand on the equator such that the 132 | // rope goes over the top? 133 | 134 | let r = 6.371e6_f64; 135 | let min = 0.0; 136 | let max = 1e8; 137 | let max_iter = 100; 138 | let tol = 1e-10; 139 | 140 | // use Brent to find the answer 141 | let y = zbrent(min, max, tol, max_iter, &mut |h| 142 | Ok(1.0 + r * (r / (r + h)).acos() - (h * (2.0 * r + h)).sqrt())).unwrap(); 143 | 144 | // check the answer against a hard-coded number 145 | let expected = 192.80752497643797_f64; 146 | assert!(approx_eq(y, expected, tol), "result={} expected={}", y, expected); 147 | 148 | // check that our answer solves the problem 149 | let test_expected = 1.0 + r * (r / (r + expected)).acos() 150 | - (expected * (2.0 * r + expected)).sqrt(); 151 | assert!(approx_eq(test_expected, 0.0, 1e-8), "result={} expected={}", test_expected, 0.0); 152 | } 153 | } -------------------------------------------------------------------------------- /src/math/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod brent; 2 | pub mod interpolation; 3 | pub mod numerics; 4 | pub mod optionpricing; 5 | -------------------------------------------------------------------------------- /src/math/numerics.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fmt; 3 | 4 | /// Compares two floating point numbers for equality, with margin for error 5 | pub fn approx_eq(first: f64, second: f64, tolerance: f64) -> bool { 6 | let diff = first - second; 7 | diff.abs() < tolerance 8 | } 9 | 10 | /// Are two objects approximately equal? The two objects could be floating 11 | /// point numbers, or risk reports. Returns true if the objects are sufficiently 12 | /// close or false if they are not. The meaning of the tolerance parameters means 13 | /// different things to different objects. For a floating point number it means 14 | /// the largest acceptable difference, when scaled to the size of the largest 15 | /// number in the calculation. For example, if we are comparing (a - b) with c, 16 | /// the largest acceptable difference is max(a, b, c) * tol. This means 17 | /// that gamma calculations have a tolerance much greater than tol * gamma. 18 | pub trait ApproxEq { 19 | 20 | /// Are the two objects approximately the same? If so, return without doing anything. If they are 21 | /// different, write a description of the differences to the diffs parameter. If the diffs ends up 22 | /// not having been written to, treat this as success. Returns an error if the formatter fails or 23 | /// if the objects are so different they cannot be compared. The tol parameter changes its type and 24 | /// meaning depending on what is being valued. 25 | fn validate(self, other: Rhs, tol: &T, 26 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result; 27 | } 28 | 29 | /* 30 | * I cannot get this to work, I think because of the lifetime qualifier required on 31 | * ApproxEq for the underlying type. For now, we implement it specifically for 32 | * BoxReport, which is the only place that needs it. 33 | impl<'v, T, V> ApproxEq for &'v [V] 34 | where 35 | V: ApproxEq 36 | { 37 | fn validate(self, other: &'v [V], tol: &T, 38 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 39 | 40 | if self.len() != other.len() { 41 | write!(diffs, "Slice: length {} != {}", self.len(), other.len())?; 42 | } 43 | 44 | for (self_item, other_item) in self.iter().zip(other.iter()) { 45 | self_item.validate(other_item, tol, msg, diffs)?; 46 | } 47 | 48 | Ok(()) 49 | } 50 | } 51 | */ 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | 57 | #[test] 58 | fn approx_eq_tests() { 59 | assert!(approx_eq(123.456, 123.4562, 0.001)); 60 | assert!(!approx_eq(123.456, 123.4562, 0.0001)); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/math/optionpricing.rs: -------------------------------------------------------------------------------- 1 | use statrs::distribution::Normal; 2 | use statrs::distribution::Univariate; 3 | use core::qm; 4 | 5 | /// The 1976 reformulation of the Black-Scholes formula, where the price of 6 | /// a European option is expressed in terms of the Forward and the Strike. 7 | pub struct Black76 { 8 | normal: Normal 9 | } 10 | 11 | impl Black76 { 12 | pub fn new() -> Result { 13 | 14 | // For some reason we cannot use the ? operator for this sort of error. 15 | // as a workaround, do it manually for now. 16 | match Normal::new(0.0, 1.0) { 17 | Ok(normal) => Ok(Black76 { normal: normal }), 18 | Err(e) => Err(qm::Error::new(&format!("RSStat error: {}", e))) 19 | } 20 | } 21 | 22 | /// Calculates the PV of a European call option under Black Scholes 23 | pub fn call_price(&self, df: f64, forward: f64, strike: f64, 24 | sqrt_variance: f64) -> f64 { 25 | 26 | let log_moneyness = (forward / strike).ln(); 27 | let (d_plus, d_minus) = d_plus_minus(log_moneyness, sqrt_variance); 28 | 29 | df * (self.cdf(d_plus) * forward - self.cdf(d_minus) * strike) 30 | } 31 | 32 | /// Calculates the PV of a European put option under Black Scholes 33 | pub fn put_price(&self, df: f64, forward: f64, strike: f64, 34 | sqrt_variance: f64) -> f64 { 35 | 36 | let log_moneyness = (forward / strike).ln(); 37 | let (d_plus, d_minus) = d_plus_minus(log_moneyness, sqrt_variance); 38 | 39 | df * (self.cdf(-d_minus) * strike - self.cdf(-d_plus) * forward) 40 | } 41 | 42 | pub fn cdf(&self, x: f64) -> f64 { 43 | self.normal.cdf(x) 44 | } 45 | } 46 | 47 | /// Calculates the internal d_plus and d_minus values needed for many of the 48 | /// Black Scholes formulae. 49 | fn d_plus_minus(log_moneyness: f64, sqrt_variance: f64) -> (f64, f64) { 50 | let d_plus = log_moneyness / sqrt_variance + 0.5 * sqrt_variance; 51 | let d_minus = d_plus - sqrt_variance; 52 | (d_plus, d_minus) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | use math::numerics::approx_eq; 59 | 60 | #[test] 61 | fn test_cdf() { 62 | // checking values against those on danielsoper.com 63 | let black76 = Black76::new().unwrap(); 64 | assert_approx(black76.cdf(-4.0), 0.00003167, 1e-8, "cdf"); 65 | assert_approx(black76.cdf(-3.0), 0.00134990, 1e-8, "cdf"); 66 | assert_approx(black76.cdf(-2.0), 0.02275013, 1e-8, "cdf"); 67 | assert_approx(black76.cdf(-1.0), 0.15865525, 1e-8, "cdf"); 68 | assert_approx(black76.cdf(0.0), 0.5, 1e-8, "cdf"); 69 | assert_approx(black76.cdf(1.0), 0.84134475, 1e-8, "cdf"); 70 | assert_approx(black76.cdf(2.0), 0.97724987, 1e-8, "cdf"); 71 | assert_approx(black76.cdf(3.0), 0.99865010, 1e-8, "cdf"); 72 | assert_approx(black76.cdf(4.0), 0.99996833, 1e-8, "cdf"); 73 | } 74 | 75 | #[test] 76 | fn black76_price() { 77 | 78 | let forward = 100.0; 79 | let df = 0.99; 80 | let sqrt_var = 0.5; 81 | let black76 = Black76::new().unwrap(); 82 | 83 | for strike in [50.0, 70.0, 90.0, 100.0, 110.0, 130.0, 160.0].iter() { 84 | let call_price = black76.call_price(df, forward, *strike, sqrt_var); 85 | let put_price = black76.put_price(df, forward, *strike, sqrt_var); 86 | 87 | let call_intrinsic = df * (forward - *strike).max(0.0); 88 | let put_intrinsic = df * (*strike - forward).max(0.0); 89 | let parity = df * (forward - *strike) + put_price - call_price; 90 | 91 | assert!(call_price >= call_intrinsic); 92 | assert!(put_price >= put_intrinsic); 93 | assert_approx(parity, 0.0, 1e-12, "put/call parity"); 94 | } 95 | } 96 | 97 | fn assert_approx(value: f64, expected: f64, tolerance: f64, message: &str) { 98 | assert!(approx_eq(value, expected, tolerance), 99 | "{}: value={} expected={}", message, value, expected); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blackdiffusion; 2 | 3 | use models::blackdiffusion::BlackDiffusionFactory; 4 | use core::qm; 5 | use instruments::RcInstrument; 6 | use instruments::MonteCarloDependencies; 7 | use instruments::MonteCarloContext; 8 | use risk::Bumpable; 9 | use risk::BumpablePricingContext; 10 | use risk::marketdata::MarketData; 11 | use dates::Date; 12 | use dates::datetime::DateDayFraction; 13 | use core::factories::{TypeId, Qrc, Registry}; 14 | use std::collections::HashMap; 15 | use std::clone::Clone; 16 | use erased_serde as esd; 17 | use serde as sd; 18 | use serde_tagged as sdt; 19 | use serde_tagged::de::BoxFnSeed; 20 | use std::fmt::Debug; 21 | 22 | /// Interface that must be implemented by a model factory in order to support 23 | /// Monte-Carlo pricing. 24 | pub trait MonteCarloModelFactory : esd::Serialize + TypeId + Sync + Send + Debug { 25 | 26 | /// Given a timeline (which also specifies the underlyings we need to 27 | /// evolve), and a pricing context, create a Monte-Carlo model. 28 | fn factory(&self, timeline: &MonteCarloTimeline, 29 | context: Box) 30 | -> Result, qm::Error>; 31 | } 32 | 33 | // Get serialization to work recursively for instruments by using the 34 | // technology defined in core/factories. RcInstrument is a container 35 | // class holding an RcInstrument 36 | pub type TypeRegistry = Registry>>; 37 | 38 | /// Implement deserialization for subclasses of the type 39 | impl<'de> sd::Deserialize<'de> for Qrc { 40 | fn deserialize(deserializer: D) -> Result 41 | where D: sd::Deserializer<'de> 42 | { 43 | sdt::de::external::deserialize(deserializer, get_registry()) 44 | } 45 | } 46 | 47 | /// Return the type registry required for deserialization. 48 | pub fn get_registry() -> &'static TypeRegistry { 49 | lazy_static! { 50 | static ref REG: TypeRegistry = { 51 | let mut reg = TypeRegistry::new(); 52 | reg.insert("BlackDiffusionFactory", BoxFnSeed::new(BlackDiffusionFactory::from_serial)); 53 | reg 54 | }; 55 | } 56 | ® 57 | } 58 | 59 | pub type RcMonteCarloModelFactory = Qrc; 60 | 61 | /// Interface that must be implemented by a model in order to support 62 | /// Monte-Carlo pricing. 63 | pub trait MonteCarloModel : MonteCarloContext + Bumpable + MonteCarloModelClone { 64 | 65 | /// Converts this model to a MonteCarloContext that can be used for pricing 66 | fn as_mc_context(&self) -> &MonteCarloContext; 67 | 68 | /// Converts this model to a Bumpable that can be used for risk bumping 69 | fn as_bumpable(&self) -> &Bumpable; 70 | fn as_mut_bumpable(&mut self) -> &mut Bumpable; 71 | 72 | fn raw_market_data(&self) -> &MarketData; 73 | } 74 | 75 | pub trait MonteCarloModelClone { 76 | fn clone_box(&self) -> Box; 77 | } 78 | 79 | impl MonteCarloModelClone for T 80 | where T: 'static + MonteCarloModel + Clone, 81 | { 82 | fn clone_box(&self) -> Box { 83 | Box::new(self.clone()) 84 | } 85 | } 86 | 87 | impl Clone for Box { 88 | fn clone(&self) -> Box { 89 | self.clone_box() 90 | } 91 | } 92 | 93 | /// Timeline, which collects the information about an instrument that a model 94 | /// needs to generate paths for valuing it. 95 | pub struct MonteCarloTimeline { 96 | _spot_date: Date, 97 | observations: HashMap>, 98 | flows: Vec, 99 | collated: bool 100 | } 101 | 102 | impl MonteCarloTimeline { 103 | /// Creates an empty timeline. You must write to this timeline by 104 | /// passing it to mc_dependencies on the instrument or instruments you 105 | /// wish to value. Finally, invoke collate to ensure the timeline is 106 | /// sorted correctly. 107 | pub fn new(spot_date: Date) -> MonteCarloTimeline { 108 | MonteCarloTimeline { _spot_date: spot_date, 109 | observations: HashMap::new(), flows: Vec::new(), 110 | collated: false } 111 | } 112 | 113 | pub fn collate(&mut self) -> Result<(), qm::Error> { 114 | 115 | // Sort each of the observations vectors by date/day-fraction and 116 | // ensure there are no duplicates. 117 | 118 | // validate that the observations are all in the future 119 | 120 | // validate that the flows all make sense and all fix in the future 121 | 122 | self.collated = true; 123 | Ok(()) 124 | } 125 | 126 | pub fn observations(&self) -> &HashMap> { 127 | assert!(self.collated); 128 | &self.observations 129 | } 130 | 131 | pub fn flows(&self) -> &[RcInstrument] { 132 | assert!(self.collated); 133 | &self.flows 134 | } 135 | } 136 | 137 | impl MonteCarloDependencies for MonteCarloTimeline { 138 | 139 | fn observation(&mut self, instrument: &RcInstrument, 140 | date_time: DateDayFraction) { 141 | 142 | // Record the observations in the order the client specifies them 143 | // for any one instrument 144 | self.observations.entry(instrument.clone()) 145 | .or_insert(Vec::::new()).push(date_time); 146 | } 147 | 148 | fn flow(&mut self, instrument: &RcInstrument) { 149 | 150 | // We must record flows in the order the client specifies them, as 151 | // the client later relies on this order 152 | self.flows.push(instrument.clone()); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/pricers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod montecarlo; 2 | pub mod selfpricer; 3 | 4 | use pricers::montecarlo::MonteCarloPricerFactory; 5 | use pricers::selfpricer::SelfPricerFactory; 6 | use core::qm; 7 | use core::factories::{TypeId, Qrc, Registry}; 8 | use instruments::RcInstrument; 9 | use data::fixings::RcFixingTable; 10 | use risk::marketdata::RcMarketData; 11 | use risk::Pricer; 12 | use erased_serde as esd; 13 | use serde as sd; 14 | use serde_tagged as sdt; 15 | use serde_tagged::de::BoxFnSeed; 16 | use std::fmt::Debug; 17 | 18 | /// Pricers are always constructed using a pricer factory. This means that the 19 | /// code to create the pricer is independent of what sort of pricer it is. 20 | pub trait PricerFactory : esd::Serialize + TypeId + Sync + Send + Debug { 21 | /// Creates a pricer, given all the data that is needed to get a price. 22 | /// All the inputs are shared pointers to const objects, which allows them 23 | /// to be shared across multiple pricers. (Consider making them Arc rather 24 | /// than Rc, allowing multithreaded use across different pricers.) 25 | fn new(&self, instrument: RcInstrument, fixings: RcFixingTable, 26 | market_data: RcMarketData) -> Result, qm::Error>; 27 | } 28 | 29 | // Get serialization to work recursively for instruments by using the 30 | // technology defined in core/factories. RcInstrument is a container 31 | // class holding an RcInstrument 32 | pub type TypeRegistry = Registry>>; 33 | 34 | /// Implement deserialization for subclasses of the type 35 | impl<'de> sd::Deserialize<'de> for Qrc { 36 | fn deserialize(deserializer: D) -> Result 37 | where D: sd::Deserializer<'de> 38 | { 39 | sdt::de::external::deserialize(deserializer, get_registry()) 40 | } 41 | } 42 | 43 | /// Return the type registry required for deserialization. 44 | pub fn get_registry() -> &'static TypeRegistry { 45 | lazy_static! { 46 | static ref REG: TypeRegistry = { 47 | let mut reg = TypeRegistry::new(); 48 | reg.insert("MonteCarloPricerFactory", BoxFnSeed::new(MonteCarloPricerFactory::from_serial)); 49 | reg.insert("SelfPricerFactory", BoxFnSeed::new(SelfPricerFactory::from_serial)); 50 | reg 51 | }; 52 | } 53 | ® 54 | } 55 | 56 | pub type RcPricerFactory = Qrc; 57 | -------------------------------------------------------------------------------- /src/risk/bumptime.rs: -------------------------------------------------------------------------------- 1 | use risk::Bumpable; 2 | use dates::Date; 3 | use dates::datetime::DateTime; 4 | use core::qm; 5 | use std::collections::HashMap; 6 | use instruments::Instrument; 7 | use instruments::RcInstrument; 8 | use instruments::fix_all; 9 | use instruments::PricingContext; 10 | use data::fixings::FixingTable; 11 | use data::bumpspotdate::BumpSpotDate; 12 | use data::bumpspotdate::SpotDynamics; 13 | use data::bump::Bump; 14 | use risk::dependencies::DependencyCollector; 15 | 16 | /// Bump that defines all the supported bumps to the spot date and ex-from 17 | /// date. This bump has to live in risk rather than data, because it affects 18 | /// all market data, not just one curve at a time. 19 | #[derive(Serialize, Deserialize, Clone, Debug)] 20 | pub struct BumpTime { 21 | spot_date_bump: BumpSpotDate, 22 | _ex_from: Date 23 | } 24 | 25 | impl BumpTime { 26 | pub fn new(spot_date: Date, ex_from: Date, spot_dynamics: SpotDynamics) -> BumpTime { 27 | BumpTime { spot_date_bump: BumpSpotDate::new(spot_date, spot_dynamics), 28 | _ex_from: ex_from } 29 | } 30 | 31 | /// Applies the bump to the list of instruments. If the list of instruments has not 32 | /// changed, it also applies the bump to the model. If the list of instruments has 33 | /// changed, the model will need to be completely rebuilt. In that case, the method 34 | /// returns true. 35 | pub fn apply(&self, instruments: &mut Vec<(f64, RcInstrument)>, 36 | bumpable: &mut Bumpable) -> Result { 37 | 38 | // Modify the vector of instruments, if any fixings between the old and new spot dates 39 | // affect any of them. If any are updated, hold onto the updated list of dependencies. 40 | let modified = self.update_instruments( 41 | instruments, bumpable.context(), bumpable.dependencies()?)?; 42 | 43 | // Now apply a bump to the model, to shift the spot date. (TODO it may be inefficient to 44 | // completely refetch all dependent data if the model will need rebuilding anyway. Maybe 45 | // pass the modified flag into the new_spot_date bump so the model knows not to do the 46 | // work.) 47 | let bump = Bump::new_spot_date(self.spot_date_bump.clone()); 48 | bumpable.bump(&bump, None)?; 49 | 50 | // If the instruments have been modified, we may need to rebuild the model from scratch 51 | Ok(modified) 52 | } 53 | 54 | /// Creates a fixing table representing any fixings between the old and new spot dates, and 55 | /// applies it to the instruments, modifying the vector if necessary. If any have changed, 56 | /// returns true. 57 | pub fn update_instruments(&self, instruments: &mut Vec<(f64, RcInstrument)>, 58 | context: &PricingContext, dependencies: &DependencyCollector) -> Result { 59 | 60 | // are there any fixings between the old and new spot dates? 61 | let old_spot_date = context.spot_date(); 62 | let new_spot_date = self.spot_date_bump.spot_date(); 63 | 64 | // Create a fixing table with any fixings between the old and 65 | // new spot dates. Note that we do not have to bother with existing 66 | // fixings, as these have already been entirely taken into account 67 | // by the list of instruments. 68 | let mut fixing_map = HashMap::new(); 69 | for (id, instrument) in dependencies.instruments_iter() { 70 | for fixing in dependencies.fixings(id).iter() { 71 | let date = fixing.date(); 72 | if date >= old_spot_date && date < new_spot_date { 73 | let value = match self.spot_date_bump.spot_dynamics() { 74 | SpotDynamics::StickyForward => { 75 | // it looks inefficient to keep fetching the curves each time round 76 | // the loop, but by far the most common case has at most one fixing 77 | let inst: &Instrument = &*instrument.clone(); 78 | let curve = context.forward_curve(inst, new_spot_date)?; 79 | curve.forward(date)? }, 80 | SpotDynamics::StickySpot => { 81 | context.spot(id)? } 82 | }; 83 | 84 | fixing_map.entry(id.to_string()).or_insert(Vec::<(DateTime, f64)>::new()) 85 | .push((*fixing, value)); 86 | } 87 | } 88 | } 89 | 90 | // Apply the fixings to each of the instruments, and build up a new vector of them 91 | let mut any_changes = !fixing_map.is_empty(); 92 | if any_changes { 93 | let fixing_table = FixingTable::from_iter_known_until(new_spot_date, fixing_map.iter())?; 94 | if let Some(ref mut replacement) = fix_all(instruments, &fixing_table)? { 95 | instruments.clear(); 96 | instruments.append(replacement); 97 | } else { 98 | any_changes = false; 99 | } 100 | } 101 | 102 | Ok(any_changes) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/risk/dependencies.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Iter; 2 | use instruments::DependencyContext; 3 | use dates::Date; 4 | use dates::datetime::DateTime; 5 | use instruments::RcInstrument; 6 | use instruments::SpotRequirement; 7 | use std::collections::HashSet; 8 | use std::collections::HashMap; 9 | 10 | /// Collect the dependencies of an instrument 11 | pub struct DependencyCollector { 12 | spot_date: Date, 13 | spots: HashSet, 14 | yield_curves: HashMap, 15 | forward_curves: HashMap, 16 | vol_surfaces: HashMap, 17 | instruments: HashMap, 18 | forward_id_from_credit_id: HashMap>, 19 | fixings: HashMap>, 20 | empty: Vec, 21 | empty_fixings: Vec 22 | } 23 | 24 | impl DependencyCollector { 25 | pub fn new(spot_date: Date) -> DependencyCollector { 26 | DependencyCollector { 27 | spot_date: spot_date, 28 | spots: HashSet::new(), 29 | yield_curves: HashMap::new(), 30 | forward_curves: HashMap::new(), 31 | vol_surfaces: HashMap::new(), 32 | instruments: HashMap::new(), 33 | forward_id_from_credit_id: HashMap::new(), 34 | fixings: HashMap::new(), 35 | empty: Vec::::new(), 36 | empty_fixings: Vec::::new() 37 | } 38 | } 39 | 40 | pub fn has_spot(&self, instrument: &RcInstrument) -> bool { 41 | let key = instrument.clone(); 42 | self.spots.contains(&key) 43 | } 44 | 45 | pub fn yield_curve_hwm(&self, credit_id: &str) -> Option { 46 | get_hwm_by_str(&self.yield_curves, credit_id) 47 | } 48 | 49 | pub fn forward_curve_hwm(&self, instrument: &RcInstrument) 50 | -> Option { 51 | get_hwm(&self.forward_curves, instrument) 52 | } 53 | 54 | pub fn vol_surface_hwm(&self, instrument: &RcInstrument) 55 | -> Option { 56 | get_hwm(&self.vol_surfaces, instrument) 57 | } 58 | 59 | pub fn forward_curves(&self) -> &HashMap { 60 | &self.forward_curves 61 | } 62 | 63 | pub fn vol_surfaces(&self) -> &HashMap { 64 | &self.vol_surfaces 65 | } 66 | 67 | pub fn instrument_by_id(&self, id: &str) ->Option<&RcInstrument> { 68 | self.instruments.get(&id.to_string()) 69 | } 70 | 71 | pub fn forward_id_by_credit_id(&self, credit_id: &str) -> &[String] { 72 | if let Some(ids) 73 | = self.forward_id_from_credit_id.get(&credit_id.to_string()) { 74 | &ids 75 | } else { 76 | &self.empty 77 | } 78 | } 79 | 80 | pub fn instruments_iter(&self) -> Iter { 81 | self.instruments.iter() 82 | } 83 | 84 | pub fn fixings(&self, id: &str) -> &[DateTime] { 85 | if let Some(fixings) = self.fixings.get(&id.to_string()) { 86 | &fixings 87 | } else { 88 | &self.empty_fixings 89 | } 90 | } 91 | 92 | fn add_instrument(&mut self, instrument: &RcInstrument) { 93 | self.instruments.insert( 94 | instrument.id().to_string(), instrument.clone()); 95 | } 96 | 97 | pub fn instruments_clone(&self) -> Vec { 98 | // this rather unpleasant syntax forces the ids to be owned 99 | // by the resulting vector rather than the original hashmap 100 | self.instruments.keys().map(|id|id.to_string()).collect() 101 | } 102 | } 103 | 104 | fn get_hwm_by_str(map: &HashMap, id: &str) -> Option { 105 | match map.get(id) { 106 | Some(hwm) => Some(*hwm), 107 | None => None 108 | } 109 | } 110 | 111 | fn get_hwm(map: &HashMap, instrument: &RcInstrument) 112 | -> Option { 113 | let key = instrument.clone(); 114 | match map.get(&key) { 115 | Some(hwm) => Some(*hwm), 116 | None => None 117 | } 118 | } 119 | 120 | impl DependencyContext for DependencyCollector { 121 | fn spot_date(&self) -> Date { 122 | self.spot_date 123 | } 124 | 125 | fn yield_curve(&mut self, credit_id: &str, high_water_mark: Date) { 126 | set_hwm_by_str(credit_id, high_water_mark, &mut self.yield_curves); 127 | } 128 | 129 | fn spot(&mut self, instrument: &RcInstrument) { 130 | 131 | // recurse into this instrument 132 | let spot_requirement = instrument.dependencies(self); 133 | 134 | // if required, add a dependence on this spot 135 | if spot_requirement != SpotRequirement::NotRequired { 136 | let key = instrument.clone(); 137 | self.spots.insert(key); 138 | self.add_instrument(instrument); 139 | } 140 | } 141 | 142 | fn forward_curve(&mut self, instrument: &RcInstrument, 143 | high_water_mark: Date) { 144 | 145 | set_hwm(instrument, high_water_mark, &mut self.forward_curves); 146 | 147 | // also set the high water mark on the associated yield curve 148 | let credit_id = instrument.credit_id(); 149 | set_hwm_by_str(credit_id, high_water_mark, &mut self.yield_curves); 150 | { 151 | // this brace to avoid multiple mutable borrow 152 | let forward_ids = self.forward_id_from_credit_id.entry( 153 | credit_id.to_string()).or_insert(Vec::::new()); 154 | forward_ids.push(instrument.id().to_string()); 155 | } 156 | 157 | // and add a dependency on this spot (this adds the instrument) 158 | self.spot(instrument); 159 | } 160 | 161 | fn vol_surface(&mut self, instrument: &RcInstrument, 162 | high_water_mark: Date) { 163 | set_hwm(instrument, high_water_mark, &mut self.vol_surfaces); 164 | self.add_instrument(instrument); 165 | } 166 | 167 | fn fixing(&mut self, id: &str, date: DateTime) { 168 | self.fixings.entry(id.to_string()).or_insert(Vec::new()) 169 | .push(date) 170 | } 171 | 172 | } 173 | 174 | pub fn set_hwm_by_str(id: &str, high_water_mark: Date, 175 | map: &mut HashMap) { 176 | 177 | // The following string conversion is upsettingly inefficient. This 178 | // is something that the Rust developers are aware of and want to fix. 179 | let entry = map.entry(id.to_string()).or_insert(high_water_mark); 180 | if high_water_mark > *entry { 181 | *entry = high_water_mark; 182 | } 183 | } 184 | 185 | pub fn set_hwm(instrument: &RcInstrument, high_water_mark: Date, 186 | map: &mut HashMap) { 187 | 188 | let key = instrument.clone(); 189 | let entry = map.entry(key).or_insert(high_water_mark); 190 | if high_water_mark > *entry { 191 | *entry = high_water_mark; 192 | } 193 | } 194 | 195 | #[cfg(test)] 196 | mod tests { 197 | use super::*; 198 | use dates::calendar::WeekdayCalendar; 199 | use dates::calendar::RcCalendar; 200 | use dates::rules::BusinessDays; 201 | use dates::datetime::TimeOfDay; 202 | use dates::datetime::DateTime; 203 | use dates::Date; 204 | use instruments::assets::Currency; 205 | use instruments::assets::RcCurrency; 206 | use instruments::assets::Equity; 207 | use instruments::options::SpotStartingEuropean; 208 | use instruments::options::PutOrCall; 209 | use instruments::options::OptionSettlement; 210 | use dates::rules::RcDateRule; 211 | use core::factories::Qrc; 212 | use std::sync::Arc; 213 | 214 | fn sample_currency(step: u32) -> Currency { 215 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar::new())); 216 | let settlement = RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, step))); 217 | Currency::new("GBP", settlement) 218 | } 219 | 220 | fn sample_settlement(step: u32) -> RcDateRule { 221 | let calendar = RcCalendar::new(Arc::new(WeekdayCalendar::new())); 222 | RcDateRule::new(Arc::new(BusinessDays::new_step(calendar, step))) 223 | } 224 | 225 | fn sample_equity(currency: RcCurrency, step: u32) -> Equity { 226 | let settlement = sample_settlement(step); 227 | Equity::new("BP.L", "LSE", currency, settlement) 228 | } 229 | 230 | #[test] 231 | fn european_dependencies() { 232 | 233 | let strike = 100.0; 234 | let d = Date::from_ymd(2018, 01, 01); 235 | let short_expiry = DateTime::new(d+70, TimeOfDay::Close); 236 | let long_expiry = DateTime::new(d+210, TimeOfDay::Close); 237 | 238 | let currency = RcCurrency::new(Arc::new(sample_currency(2))); 239 | let settlement = sample_settlement(2); 240 | let equity: RcInstrument = RcInstrument::new(Qrc::new(Arc::new(sample_equity(currency, 2)))); 241 | let short_european: RcInstrument = RcInstrument::new(Qrc::new(Arc::new(SpotStartingEuropean::new( 242 | "ShortDatedEquity", 243 | "OPT", equity.clone(), settlement.clone(), short_expiry, 244 | strike, PutOrCall::Call, OptionSettlement::Cash).unwrap()))); 245 | let long_european: RcInstrument = RcInstrument::new(Qrc::new(Arc::new(SpotStartingEuropean::new( 246 | "LongDatedEquity", 247 | "OPT", equity.clone(), settlement, long_expiry, 248 | strike, PutOrCall::Call, OptionSettlement::Cash).unwrap()))); 249 | 250 | let mut c = DependencyCollector::new(d); 251 | c.spot(&short_european); 252 | 253 | assert!(c.has_spot(&equity)); 254 | assert_eq!(c.forward_curve_hwm(&equity), Some(d+70)); 255 | assert_eq!(c.vol_surface_hwm(&equity), Some(d+70)); 256 | assert_eq!(c.yield_curve_hwm("OPT"), Some(d+72)); 257 | assert_eq!(c.yield_curve_hwm("LSE"), Some(d+70)); 258 | 259 | c.spot(&long_european); 260 | 261 | assert!(c.has_spot(&equity)); 262 | assert_eq!(c.forward_curve_hwm(&equity), Some(d+210)); 263 | assert_eq!(c.vol_surface_hwm(&equity), Some(d+210)); 264 | assert_eq!(c.yield_curve_hwm("OPT"), Some(d+212)); 265 | assert_eq!(c.yield_curve_hwm("LSE"), Some(d+210)); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/risk/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod marketdata; 2 | pub mod dependencies; 3 | pub mod cache; 4 | pub mod bumptime; 5 | pub mod deltagamma; 6 | pub mod timebumped; 7 | pub mod vegavolga; 8 | 9 | use risk::timebumped::{TimeBumpedReportGenerator, TimeBumpedReport}; 10 | use risk::deltagamma::{DeltaGammaReportGenerator, DeltaGammaReport}; 11 | use risk::vegavolga::{VegaVolgaReportGenerator, VegaVolgaReport}; 12 | use core::qm; 13 | use core::factories::{Qrc, Qbox, TypeId, Registry}; 14 | use data::bump::Bump; 15 | use risk::bumptime::BumpTime; 16 | use risk::marketdata::MarketData; 17 | use instruments::PricingContext; 18 | use risk::dependencies::DependencyCollector; 19 | use erased_serde as esd; 20 | use serde as sd; 21 | use serde_tagged as sdt; 22 | use serde_tagged::de::BoxFnSeed; 23 | use std::fmt::Debug; 24 | use std::any::Any; 25 | use std::fmt; 26 | use std::ops::Deref; 27 | use math::numerics::ApproxEq; 28 | 29 | /// Interface that defines all bumps of simple underlying market data. This 30 | /// defines most risks that the analytics outputs. Most methods take a save 31 | /// parameter which is a Any class. This is normally a second 32 | /// instance of the Bumpable object, where it can copy any state that is bumped 33 | /// so it can be restored later. 34 | pub trait Bumpable { 35 | 36 | /// Applies a bump to market data or to anything derived from market data, 37 | /// such as a model or a pricer. Returns true if anything was bumped. 38 | fn bump(&mut self, bump: &Bump, save: Option<&mut Saveable>) 39 | -> Result; 40 | 41 | /// Optionally allows access to the dependencies that drive the bumpability. 42 | fn dependencies(&self) -> Result<&DependencyCollector, qm::Error>; 43 | 44 | /// Allows access to the pricing context that is to be bumped. This is useful 45 | /// for bumps that adjust their size according to the forward etc. 46 | fn context(&self) -> &PricingContext; 47 | 48 | /// Creates a save area to use with this bump 49 | fn new_saveable(&self) -> Box; 50 | 51 | /// Restores the state to what it was before the bump 52 | fn restore(&mut self, saved: &Saveable) -> Result<(), qm::Error>; 53 | } 54 | 55 | pub trait BumpablePricingContext: Bumpable + PricingContext + BumpablePricingContextClone { 56 | fn as_bumpable(&self) -> &Bumpable; 57 | fn as_mut_bumpable(&mut self) -> &mut Bumpable; 58 | fn as_pricing_context(&self) -> &PricingContext; 59 | fn raw_market_data(&self) -> &MarketData; 60 | } 61 | 62 | pub trait BumpablePricingContextClone { 63 | fn clone_box(&self) -> Box; 64 | } 65 | 66 | impl BumpablePricingContextClone for T 67 | where T: 'static + BumpablePricingContext + Clone, 68 | { 69 | fn clone_box(&self) -> Box { 70 | Box::new(self.clone()) 71 | } 72 | } 73 | 74 | impl Clone for Box { 75 | fn clone(&self) -> Box { 76 | self.clone_box() 77 | } 78 | } 79 | 80 | /// Time bumping is done to calculate theta or time-forward greeks, such as 81 | /// the delta as of the next market open. It is more complicated than other 82 | /// greeks, because it may involve changes to the instrument, which may have 83 | /// fixings before the theta date. 84 | pub trait TimeBumpable { 85 | /// Applies a time bump to this object. The object is modified with no 86 | /// save and restore facility, so you probably need to deep_clone the 87 | /// object first. 88 | fn bump_time(&mut self, bump: &BumpTime) -> Result<(), qm::Error>; 89 | } 90 | 91 | /// The basic pricing interface for qm. Returns a price from a pricer or a 92 | /// priceable instrument. The point of this interface is that it is bumpable, 93 | /// so it can be used to calculate risks and scenarios. 94 | pub trait Pricer : Bumpable + TimeBumpable + PricerClone { 95 | fn as_bumpable(&self) -> &Bumpable; 96 | fn as_mut_bumpable(&mut self) -> &mut Bumpable; 97 | fn as_mut_time_bumpable(&mut self) -> &mut TimeBumpable; 98 | 99 | /// Returns the present value. If no discount date is supplied, the value 100 | /// is discounted to the settlement date of the instrument being priced. 101 | /// This means that every listed instrument should give a price equal to 102 | /// the current screen price. If you supply a discount date, the value is 103 | /// discounted to that date. This allows you to view prices that are 104 | /// consistent across different exchanges, but it is not possible to choose 105 | /// a discount date such that all values equal their screen prices, unless 106 | /// all underlyings have the same settlement date. 107 | /// 108 | /// Discount date is currently disabled. 109 | fn price(&self /*, discount_date: Option*/) -> Result; 110 | } 111 | 112 | /// For some reason that I do not understand, the rust compiler runs into an 113 | /// infinite recursion issue if we try to implement this with generics, the 114 | /// same as clone_box is implemented elsewhere. Thus you need to implement 115 | /// this manually in each pricer. 116 | pub trait PricerClone { 117 | fn clone_box(&self) -> Box; 118 | } 119 | 120 | /// Interface that defines how market data or derived data can save itself 121 | /// during a bump, so it can restore itself later. The interface is largely 122 | /// a placeholder, as the means of save/restore are specific to the data. 123 | pub trait Saveable : Any { 124 | /// Convert to Any, so we can then convert to the concrete type 125 | /// specific to this saveable 126 | fn as_any(&self) -> &Any; 127 | fn as_mut_any(&mut self) -> &mut Any; 128 | 129 | /// Clears the saved state, so a restore operation is a no-op 130 | fn clear(&mut self); 131 | } 132 | 133 | /// A report is the result of a set of calculations, normally with bumped 134 | /// time and or market data. For example, a delta-gamma report shows the 135 | /// first and second differentials to all applicable underliers. 136 | /// 137 | /// Reports are designed to be nested and grouped together, to avoid 138 | /// unnecessary cloning and bumping. 139 | pub trait Report : esd::Serialize + ApproxEqReport + TypeId + Debug + Any { 140 | fn as_any(&self) -> &Any; 141 | } 142 | 143 | /// Redefine ApproxEqReport because Rust complains about circular type 144 | /// references otherwise 145 | pub trait ApproxEqReport { 146 | fn validate_report(&self, other: &Report, tol: &ReportTolerances, 147 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result; 148 | } 149 | 150 | /// Tolerances for comparing risk reports. The price_tol is used for comparing 151 | /// prices, and things that behave like prices such as bumped prices. The 152 | /// currency_risk_tol is used for comparing risks that are in units of currency 153 | /// and measure the change in price for a given bump, such as Vega or Volga. The 154 | /// unit_risk_tol is used for comparing risks that have no units, and measure the 155 | /// percentage change in price for a given bump, such as Delta or Gamma. 156 | pub struct ReportTolerances { 157 | price: f64, 158 | currency_risk: f64, 159 | unit_risk: f64 160 | } 161 | 162 | impl ReportTolerances { 163 | pub fn new(price: f64, currency_risk: f64, unit_risk: f64) -> ReportTolerances { 164 | ReportTolerances { price, currency_risk, unit_risk } 165 | } 166 | pub fn price(&self) -> f64 { self.price } 167 | pub fn currency_risk(&self) -> f64 { self.currency_risk } 168 | pub fn unit_risk(&self) -> f64 { self.unit_risk } 169 | } 170 | 171 | /// A report generator performs all the calculations needed to produce a 172 | /// report. 173 | pub trait ReportGenerator : esd::Serialize + TypeId + Sync + Send + Debug { 174 | /// Perform all the calculations, bumping, pricing and possibly cloning 175 | /// the input pricer to generate the result. Normally the pricer is left 176 | /// in the same state as it started, unless it documents otherwise. 177 | /// Similarly, the saveable is normally expected to be initially empty 178 | /// and is left empty on exit, unless documented otherwise. 179 | fn generate(&self, pricer: &mut Pricer, saveable: &mut Saveable, unbumped: f64) 180 | -> Result; 181 | } 182 | 183 | // Get serialization to work recursively for report generators by using the 184 | // technology defined in core/factories. 185 | pub type RcReportGenerator = Qrc; 186 | pub type GeneratorTypeRegistry = Registry>>; 187 | 188 | /// Implement deserialization for subclasses of the type 189 | impl<'de> sd::Deserialize<'de> for Qrc { 190 | fn deserialize(deserializer: D) -> Result 191 | where D: sd::Deserializer<'de> 192 | { 193 | sdt::de::external::deserialize(deserializer, get_generator_registry()) 194 | } 195 | } 196 | 197 | /// Return the type registry required for deserialization. 198 | pub fn get_generator_registry() -> &'static GeneratorTypeRegistry { 199 | lazy_static! { 200 | static ref REG: GeneratorTypeRegistry = { 201 | let mut reg = GeneratorTypeRegistry::new(); 202 | reg.insert("DeltaGammaReportGenerator", BoxFnSeed::new(DeltaGammaReportGenerator::from_serial)); 203 | reg.insert("VegaVolgaReportGenerator", BoxFnSeed::new(VegaVolgaReportGenerator::from_serial)); 204 | reg.insert("TimeBumpedReportGenerator", BoxFnSeed::new(TimeBumpedReportGenerator::from_serial)); 205 | reg 206 | }; 207 | } 208 | ® 209 | } 210 | 211 | // Get serialization to work recursively for instruments by using the 212 | // technology defined in core/factories. RcInstrument is a container 213 | // class holding an RcInstrument 214 | pub type BoxReport = Qbox; 215 | pub type ReportTypeRegistry = Registry>; 216 | 217 | /// Implement deserialization for subclasses of the type 218 | impl<'de> sd::Deserialize<'de> for BoxReport { 219 | fn deserialize(deserializer: D) -> Result 220 | where D: sd::Deserializer<'de> 221 | { 222 | sdt::de::external::deserialize(deserializer, get_report_registry()) 223 | } 224 | } 225 | 226 | impl<'v> ApproxEq for &'v BoxReport { 227 | fn validate(self, other: &'v BoxReport, tol: &ReportTolerances, 228 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 229 | 230 | let self_report : &Report = self.deref(); 231 | let other_report : &Report = other.deref(); 232 | self_report.validate_report(other_report, tol, msg, diffs) 233 | } 234 | } 235 | 236 | impl<'v> ApproxEq for &'v [BoxReport] { 237 | fn validate(self, other: &'v [BoxReport], tol: &ReportTolerances, 238 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 239 | 240 | if self.len() != other.len() { 241 | write!(diffs, "Slice: length {} != {}", self.len(), other.len())?; 242 | } 243 | 244 | for (self_item, other_item) in self.iter().zip(other.iter()) { 245 | self_item.validate(other_item, tol, msg, diffs)?; 246 | } 247 | 248 | Ok(()) 249 | } 250 | } 251 | 252 | /// Return the type registry required for deserialization. 253 | pub fn get_report_registry() -> &'static ReportTypeRegistry { 254 | lazy_static! { 255 | static ref REG: ReportTypeRegistry = { 256 | let mut reg = ReportTypeRegistry::new(); 257 | reg.insert("DeltaGammaReport", BoxFnSeed::new(DeltaGammaReport::from_serial)); 258 | reg.insert("VegaVolgaReport", BoxFnSeed::new(VegaVolgaReport::from_serial)); 259 | reg.insert("TimeBumpedReport", BoxFnSeed::new(TimeBumpedReport::from_serial)); 260 | reg 261 | }; 262 | } 263 | ® 264 | } 265 | 266 | /// Useful method for report generators. Bumps a pricer and reprices it if necessary, 267 | /// returning the bumped price. 268 | pub fn bumped_price(bump: &Bump, pricer: &mut Pricer, saveable: Option<&mut Saveable>, unbumped: f64) 269 | -> Result { 270 | 271 | if pricer.as_mut_bumpable().bump(bump, saveable)? { 272 | pricer.price() 273 | } else { 274 | Ok(unbumped) 275 | } 276 | } -------------------------------------------------------------------------------- /src/risk/timebumped.rs: -------------------------------------------------------------------------------- 1 | use risk::Report; 2 | use risk::BoxReport; 3 | use risk::ReportGenerator; 4 | use risk::RcReportGenerator; 5 | use risk::Pricer; 6 | use risk::Saveable; 7 | use risk::ApproxEqReport; 8 | use risk::bumptime::BumpTime; 9 | use risk::ReportTolerances; 10 | use std::any::Any; 11 | use std::sync::Arc; 12 | use std::fmt; 13 | use math::numerics::{ApproxEq, approx_eq}; 14 | use core::qm; 15 | use core::factories::TypeId; 16 | use core::factories::{Qrc, Qbox}; 17 | use serde::Deserialize; 18 | use erased_serde as esd; 19 | 20 | /// A report of price and risks calculated as of a future date. If no 21 | /// subreports are requested, it is just a Theta calculator. 22 | #[derive(Serialize, Deserialize, Debug)] 23 | pub struct TimeBumpedReport { 24 | price: f64, 25 | theta: f64, 26 | subreports: Vec 27 | } 28 | 29 | impl Report for TimeBumpedReport { 30 | fn as_any(&self) -> &dyn Any { self } 31 | 32 | } 33 | 34 | impl TypeId for TimeBumpedReport { 35 | fn get_type_id(&self) -> &'static str { "TimeBumpedReport" } 36 | } 37 | 38 | impl TimeBumpedReport { 39 | pub fn new(price: f64, theta: f64, subreports: Vec) -> TimeBumpedReport { 40 | TimeBumpedReport { price, theta, subreports } 41 | } 42 | 43 | pub fn from_serial<'de>(de: &mut dyn esd::Deserializer<'de>) -> Result, esd::Error> { 44 | Ok(Qbox::new(Box::new(TimeBumpedReport::deserialize(de)?))) 45 | } 46 | 47 | pub fn price(&self) -> f64 { self.price } 48 | pub fn theta(&self) -> f64 { self.theta } 49 | pub fn subreports(&self) -> &[BoxReport] { &self.subreports } 50 | } 51 | 52 | impl<'v> ApproxEq for &'v TimeBumpedReport { 53 | fn validate(self, other: &'v TimeBumpedReport, tol: &ReportTolerances, 54 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 55 | 56 | // Use the price tolerance for theta as well as bumped price, as Monte-Carlo may not use the 57 | // same random numbers for bumped and unbumped in this case. 58 | let tolerance = tol.price(); 59 | 60 | if !approx_eq(self.price, other.price, tolerance) { 61 | writeln!(diffs, "TimeBumpedReport: price {} != {} tol={}", self.price, other.price, tolerance)?; 62 | } 63 | if !approx_eq(self.theta, other.theta, tolerance) { 64 | writeln!(diffs, "TimeBumpedReport: theta {} != {} tol={}", self.theta, other.theta, tolerance)?; 65 | } 66 | 67 | if self.subreports.len() != other.subreports.len() { 68 | writeln!(diffs, "TimeBumpedReport: number of subreports {} != {}", self.subreports.len(), other.subreports.len())?; 69 | } 70 | 71 | for (subreport, other_subreport) in self.subreports.iter().zip(other.subreports.iter()) { 72 | subreport.validate(other_subreport, tol, msg, diffs)?; 73 | } 74 | 75 | Ok(()) 76 | } 77 | } 78 | 79 | impl ApproxEqReport for TimeBumpedReport { 80 | fn validate_report(&self, other: &dyn Report, tol: &ReportTolerances, 81 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 82 | if let Some(other_report) = other.as_any().downcast_ref::() { 83 | self.validate(other_report, tol, msg, diffs) 84 | } else { 85 | write!(diffs, "TimeBumpedReport: mismatching report {} != {}", self.get_type_id(), other.get_type_id())?; 86 | Ok(()) 87 | } 88 | } 89 | } 90 | 91 | /// Calculator for time-forward values. The date to bump to, and which dates 92 | /// to bump and how are specified in a BumpTime. 93 | #[derive(Serialize, Deserialize, Clone, Debug)] 94 | pub struct TimeBumpedReportGenerator { 95 | bump: BumpTime, 96 | subgenerators: Vec 97 | } 98 | 99 | impl TimeBumpedReportGenerator { 100 | /// Creates a new TimeBumpedReport generator, which initially just calculates 101 | /// theta. 102 | pub fn new(bump: BumpTime) -> TimeBumpedReportGenerator { 103 | TimeBumpedReportGenerator { bump, subgenerators: Vec::new() } 104 | } 105 | 106 | /// Adds a subgenerator, for example calculating delta within the time-forward 107 | /// context. 108 | pub fn add(&mut self, generator: RcReportGenerator) { 109 | self.subgenerators.push(generator); 110 | } 111 | 112 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result, esd::Error> { 113 | Ok(Qrc::new(Arc::new(TimeBumpedReportGenerator::deserialize(de)?))) 114 | } 115 | } 116 | 117 | impl TypeId for TimeBumpedReportGenerator { 118 | fn get_type_id(&self) -> &'static str { "TimeBumpedReportGenerator" } 119 | } 120 | 121 | impl ReportGenerator for TimeBumpedReportGenerator { 122 | fn generate(&self, pricer: &mut dyn Pricer, saveable: &mut dyn Saveable, unbumped: f64) 123 | -> Result { 124 | 125 | // The time bump irreversibly modifies the pricer. Make a clone of it, to ensure 126 | // we do not modify the original 127 | let mut pricer_clone = pricer.clone_box(); 128 | 129 | // apply the time bump to the cloned pricer 130 | pricer_clone.bump_time(&self.bump)?; 131 | 132 | // calculate the bumped price 133 | let time_bumped = pricer_clone.price()?; 134 | let theta = time_bumped - unbumped; 135 | let mut subreports = Vec::new(); 136 | 137 | // evaluate each of the bumped reports 138 | for subgenerator in self.subgenerators.iter() { 139 | let report = subgenerator.generate(&mut *pricer_clone, saveable, time_bumped)?; 140 | subreports.push(report); 141 | } 142 | 143 | Ok(Qbox::new(Box::new(TimeBumpedReport::new(time_bumped, theta, subreports)))) 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | use math::numerics::approx_eq; 151 | use risk::deltagamma::tests::sample_pricer; 152 | use risk::deltagamma::DeltaGammaReportGenerator; 153 | use risk::deltagamma::DeltaGammaReport; 154 | use risk::vegavolga::VegaVolgaReportGenerator; 155 | use risk::vegavolga::VegaVolgaReport; 156 | use data::bumpspotdate::SpotDynamics; 157 | use data::bumpvol::BumpVol; 158 | 159 | #[test] 160 | fn theta_european_call() { 161 | // create a pricer for a european at the money call 162 | let mut pricer = sample_pricer(); 163 | let unbumped = pricer.price().unwrap(); 164 | assert_approx(unbumped, 16.710717400832973, 1e-12); 165 | 166 | // calculate theta by bumping forward by one day 167 | let theta_date = pricer.as_bumpable().context().spot_date() + 1; 168 | let bump = BumpTime::new(theta_date, theta_date, SpotDynamics::StickyForward); 169 | let generator = TimeBumpedReportGenerator::new(bump); 170 | let mut save = pricer.as_bumpable().new_saveable(); 171 | let report = generator.generate(&mut *pricer, &mut *save, unbumped).unwrap(); 172 | let results = report.as_any().downcast_ref::().unwrap(); 173 | assert_approx(results.price(), 16.696665883860128, 1e-12); 174 | assert_approx(results.theta(), -0.014051516972845235, 1e-12); 175 | } 176 | 177 | #[test] 178 | fn time_forward_greeks_european_call() { 179 | // create a pricer for a european at the money call 180 | let mut pricer = sample_pricer(); 181 | let unbumped = pricer.price().unwrap(); 182 | 183 | // calculate theta and contained greeks by bumping forward by one day 184 | let theta_date = pricer.as_bumpable().context().spot_date() + 1; 185 | let bump = BumpTime::new(theta_date, theta_date, SpotDynamics::StickyForward); 186 | let mut generator = TimeBumpedReportGenerator::new(bump); 187 | generator.add(RcReportGenerator::new(Arc::new(DeltaGammaReportGenerator::new(0.01)))); 188 | generator.add(RcReportGenerator::new(Arc::new(VegaVolgaReportGenerator::new(BumpVol::new_flat_additive(0.01))))); 189 | let mut save = pricer.as_bumpable().new_saveable(); 190 | let report = generator.generate(&mut *pricer, &mut *save, unbumped).unwrap(); 191 | let results = report.as_any().downcast_ref::().unwrap(); 192 | assert_approx(results.price(), 16.696665883860128, 1e-12); 193 | assert_approx(results.theta(), -0.014051516972845235, 1e-12); 194 | 195 | let subreports = results.subreports(); 196 | assert_eq!(subreports.len(), 2); 197 | let delta_gammas = subreports[0].as_any().downcast_ref::().unwrap(); 198 | let vega_volgas = subreports[1].as_any().downcast_ref::().unwrap(); 199 | let delta_gamma = delta_gammas.results().get("BP.L").unwrap(); 200 | let vega_volga = vega_volgas.results().get("BP.L").unwrap(); 201 | assert_approx(delta_gamma.delta(), 0.6281208393656919, 1e-12); 202 | assert_approx(delta_gamma.gamma(), 0.010191192195037715, 1e-12); 203 | assert_approx(vega_volga.vega(), 42.43387126583844, 1e-12); 204 | assert_approx(vega_volga.volga(), 85.42405378449303, 1e-12); 205 | 206 | // validate that the original pricer is unchanged 207 | let finally = pricer.price().unwrap(); 208 | assert_approx(finally, unbumped, 1e-14); 209 | } 210 | 211 | fn assert_approx(value: f64, expected: f64, tolerance: f64) { 212 | assert!(approx_eq(value, expected, tolerance), 213 | "value={} expected={}", value, expected); 214 | } 215 | } -------------------------------------------------------------------------------- /src/risk/vegavolga.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::any::Any; 3 | use std::sync::Arc; 4 | use std::fmt; 5 | use math::numerics::{ApproxEq, approx_eq}; 6 | use risk::Report; 7 | use risk::BoxReport; 8 | use risk::ReportGenerator; 9 | use risk::ReportTolerances; 10 | use risk::Pricer; 11 | use risk::Saveable; 12 | use risk::bumped_price; 13 | use risk::ApproxEqReport; 14 | use data::bump::Bump; 15 | use data::bumpvol::BumpVol; 16 | use core::qm; 17 | use core::factories::TypeId; 18 | use core::factories::{Qrc, Qbox}; 19 | use serde::Deserialize; 20 | use erased_serde as esd; 21 | 22 | // Implementation note. This file is very similar to deltagamma, and one possibility 23 | // would have been to common code them, perhaps templated by bump type. However, 24 | // the implementations are likely to diverge. The list of underlyings affected by 25 | // delta may be different from vega, for example if you have a vol surface on a 26 | // basket. Delta may need different handling for cross-currency underlyings etc. 27 | 28 | /// Vega is the first derivative of price with respect to the volatility value 29 | /// of an underlying. Volga is the second derivative. This report shows 30 | /// the vega and volga with respect to each of the underlyings 31 | /// that affect the price. Note that the price is dependant on variance rather 32 | /// than volatility, so there is some flexibility in what we choose to define 33 | /// as volatility. We use the volatilities as used internally by the vol 34 | /// surface. See data::voldecorators for details. 35 | #[derive(Serialize, Deserialize, Debug)] 36 | pub struct VegaVolgaReport { 37 | bumpsize: f64, 38 | results: HashMap 39 | } 40 | 41 | impl Report for VegaVolgaReport { 42 | fn as_any(&self) -> &Any { self } 43 | } 44 | 45 | impl TypeId for VegaVolgaReport { 46 | fn get_type_id(&self) -> &'static str { "VegaVolgaReport" } 47 | } 48 | 49 | impl VegaVolgaReport { 50 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result, esd::Error> { 51 | Ok(Qbox::new(Box::new(VegaVolgaReport::deserialize(de)?))) 52 | } 53 | 54 | pub fn results(&self) -> &HashMap { &self.results } 55 | } 56 | 57 | impl<'v> ApproxEq for &'v VegaVolgaReport { 58 | fn validate(self, other: &'v VegaVolgaReport, tol: &ReportTolerances, 59 | _msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 60 | 61 | if self.results.len() != other.results.len() { 62 | write!(diffs, "VegaVolgaReport: number of reports {} != {}", self.results.len(), other.results.len())?; 63 | } 64 | 65 | // Both vega and volga are based on diffs, so should use the currency risk tolerance. 66 | let vega = tol.currency_risk() / self.bumpsize; 67 | let volga = vega / self.bumpsize; 68 | let tolerances = VegaVolgaTolerances { vega, volga }; 69 | 70 | for (id, ref vega_volga) in &self.results { 71 | if let Some(other_vega_volga) = other.results.get(id) { 72 | vega_volga.validate(other_vega_volga, &tolerances, &id, diffs)?; 73 | } else { 74 | write!(diffs, "VegaVolgaReport: {} is missing", id)?; 75 | } 76 | } 77 | 78 | Ok(()) 79 | } 80 | } 81 | 82 | impl ApproxEqReport for VegaVolgaReport { 83 | fn validate_report(&self, other: &Report, tol: &ReportTolerances, 84 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 85 | if let Some(other_report) = other.as_any().downcast_ref::() { 86 | self.validate(other_report, tol, msg, diffs) 87 | } else { 88 | write!(diffs, "VegaVolgaReport: mismatching report {} != {}", self.get_type_id(), other.get_type_id())?; 89 | Ok(()) 90 | } 91 | } 92 | } 93 | 94 | #[derive(Serialize, Deserialize, Clone, Debug)] 95 | pub struct VegaVolga { 96 | vega: f64, 97 | volga: f64 98 | } 99 | 100 | impl VegaVolga { 101 | pub fn vega(&self) -> f64 { self.vega } 102 | pub fn volga(&self) -> f64 { self.volga } 103 | } 104 | 105 | struct VegaVolgaTolerances { 106 | vega: f64, 107 | volga: f64 108 | } 109 | 110 | impl<'v> ApproxEq for &'v VegaVolga { 111 | fn validate(self, other: &'v VegaVolga, tol: &VegaVolgaTolerances, 112 | msg: &str, diffs: &mut fmt::Formatter) -> fmt::Result { 113 | 114 | if !approx_eq(self.vega, other.vega, tol.vega) { 115 | writeln!(diffs, "VegaVolga: {} vega {} != {} tol={}", msg, self.vega, other.vega, tol.vega)?; 116 | } 117 | if !approx_eq(self.volga, other.volga, tol.volga) { 118 | writeln!(diffs, "VegaVolga: {} volga {} != {} tol={}", msg, self.volga, other.volga, tol.volga)?; 119 | } 120 | Ok(()) 121 | } 122 | } 123 | 124 | /// Calculator for vega and volga by bumping. The bump size is specified as 125 | /// a fraction of the current spot. 126 | #[derive(Serialize, Deserialize, Debug)] 127 | pub struct VegaVolgaReportGenerator { 128 | bump: BumpVol 129 | } 130 | 131 | impl VegaVolgaReportGenerator { 132 | pub fn new(bump: BumpVol) -> VegaVolgaReportGenerator { 133 | VegaVolgaReportGenerator { bump: bump } 134 | } 135 | 136 | pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result, esd::Error> { 137 | Ok(Qrc::new(Arc::new(VegaVolgaReportGenerator::deserialize(de)?))) 138 | } 139 | } 140 | 141 | impl TypeId for VegaVolgaReportGenerator { 142 | fn get_type_id(&self) -> &'static str { "VegaVolgaReportGenerator" } 143 | } 144 | 145 | impl ReportGenerator for VegaVolgaReportGenerator { 146 | fn generate(&self, pricer: &mut Pricer, saveable: &mut Saveable, unbumped: f64) 147 | -> Result { 148 | 149 | let bumpsize = self.bump.bumpsize(); 150 | let bumpsize_2 = bumpsize.powi(2); 151 | 152 | // Find the underlyings we should have vega to. Note that we need to 153 | // clone the list of instruments, to avoid borrowing problems. 154 | let instruments = pricer.as_bumpable().dependencies()?.instruments_clone(); 155 | let mut results = HashMap::new(); 156 | for id in instruments.iter() { 157 | 158 | // bump up and reprice 159 | let bump = Bump::new_vol(id, self.bump.clone()); 160 | let upbumped = bumped_price(&bump, pricer, Some(saveable), unbumped)?; 161 | 162 | // bump down and reprice (do not save the result from this) 163 | let bump = Bump::new_vol(id, self.bump.opposite()); 164 | let downbumped = bumped_price(&bump, pricer, None, unbumped)?; 165 | 166 | pricer.as_mut_bumpable().restore(saveable)?; 167 | saveable.clear(); 168 | 169 | // vega and volga calculations 170 | let vega = (upbumped - downbumped) / (2.0 * bumpsize); 171 | let volga = (upbumped + downbumped - 2.0 * unbumped) / bumpsize_2; 172 | results.insert(id.to_string(), VegaVolga {vega, volga}); 173 | } 174 | 175 | Ok(Qbox::new(Box::new(VegaVolgaReport { bumpsize, results }))) 176 | } 177 | } 178 | 179 | #[cfg(test)] 180 | mod tests { 181 | use super::*; 182 | use math::numerics::approx_eq; 183 | use risk::deltagamma::tests::sample_pricer; 184 | 185 | #[test] 186 | fn vega_volga_european() { 187 | 188 | // create a pricer for a european at the money call 189 | let mut pricer = sample_pricer(); 190 | let unbumped = pricer.price().unwrap(); 191 | assert_approx(unbumped, 16.710717400832973, 1e-12); 192 | 193 | // calculate vega with a one percent flat bump 194 | let generator = VegaVolgaReportGenerator::new(BumpVol::new_flat_additive(0.01)); 195 | let mut save = pricer.as_bumpable().new_saveable(); 196 | let report = generator.generate(&mut *pricer, &mut *save, unbumped).unwrap(); 197 | let results = report.as_any().downcast_ref::().unwrap().results(); 198 | assert!(results.len() == 1); 199 | let vega_volga = results.get("BP.L").unwrap(); 200 | assert_approx(vega_volga.vega(), 42.48301957570515, 1e-12); 201 | assert_approx(vega_volga.volga(), 85.49648271277022, 1e-12); 202 | 203 | // calculate vega with a one bp bump (the results are very close to the 204 | // one percent bump) 205 | let generator = VegaVolgaReportGenerator::new(BumpVol::new_flat_additive(0.0001)); 206 | let report = generator.generate(&mut *pricer, &mut *save, unbumped).unwrap(); 207 | let results = report.as_any().downcast_ref::().unwrap().results(); 208 | assert!(results.len() == 1); 209 | let vega_volga = results.get("BP.L").unwrap(); 210 | assert_approx(vega_volga.vega(), 42.904622733885844, 1e-12); 211 | assert_approx(vega_volga.volga(), 86.34909534066537, 1e-12); 212 | } 213 | 214 | fn assert_approx(value: f64, expected: f64, tolerance: f64) { 215 | assert!(approx_eq(value, expected, tolerance), 216 | "value={} expected={}", value, expected); 217 | } 218 | } -------------------------------------------------------------------------------- /src/solvers/impliedvol.rs: -------------------------------------------------------------------------------- 1 | use solvers::OneDimensionalSolver; 2 | use risk::Pricer; 3 | use core::qm; 4 | use math::brent::zbrent; 5 | use data::bump::Bump; 6 | use data::bumpvol::BumpVol; 7 | 8 | /// Solves for implied volatility given a pricer. The pricer can be anything 9 | /// that gives a price with dependence on volatility, but analytic pricers 10 | /// work better, as the solution requires a non-noisy objective function. 11 | /// 12 | /// Internally, this solver uses Brent. 13 | pub struct ImpliedVol { 14 | tolerance: f64, 15 | max_iter: u32 16 | } 17 | 18 | impl ImpliedVol { 19 | /// Creates an implied vol solver that tries to find a vol within the 20 | /// supplied tolerance. The solver used is quadratic in its convergence, 21 | /// so the result is likely to be far closer than the specified tolerance. 22 | /// Each iteration doubles the number of digits of accuracy, roughly 23 | /// speaking. If more than max_iter iterations are used, the solver 24 | /// exits with an error. 25 | pub fn new(tolerance: f64, max_iter: u32) -> ImpliedVol { 26 | ImpliedVol { tolerance, max_iter } 27 | } 28 | } 29 | 30 | impl OneDimensionalSolver for ImpliedVol { 31 | fn solve(&self, pricer: &mut Pricer, target: f64, min: f64, max: f64) 32 | -> Result { 33 | 34 | // We need to know which vol to solve for. If it is not known unambiguously 35 | // from the pricer, then throw. 36 | let id = single_vol_id(pricer)?; 37 | 38 | zbrent(min, max, self.tolerance, self.max_iter, 39 | &mut | vol | Ok(price_given_vol(pricer, vol, &id)? - target)) 40 | } 41 | } 42 | 43 | fn price_given_vol(pricer: &mut Pricer, vol: f64, id: &str) -> Result { 44 | let bump = Bump::new_vol(id, BumpVol::new_replace(vol)); 45 | pricer.as_mut_bumpable().bump(&bump, None)?; 46 | pricer.price() 47 | } 48 | 49 | fn single_vol_id(pricer: &Pricer) -> Result { 50 | let dependencies = pricer.as_bumpable().dependencies()?; 51 | let vols = dependencies.vol_surfaces(); 52 | if vols.len() > 1 { 53 | return Err(qm::Error::new("Vol surface is not unambiguously defined")) 54 | } 55 | for (instrument, _) in vols { 56 | return Ok(instrument.id().to_string()) 57 | } 58 | 59 | Err(qm::Error::new("No vol surface to solve for")) 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | use math::numerics::approx_eq; 66 | use risk::deltagamma::tests::sample_pricer; 67 | 68 | #[test] 69 | fn implied_vol_european_call() { 70 | 71 | // create a pricer for a european at the money call 72 | let mut pricer = sample_pricer(); 73 | let unbumped = pricer.price().unwrap(); 74 | assert_approx(unbumped, 16.710717400832973, 1e-12); 75 | 76 | // find the vol to give it a price of 20.0 77 | let solver = ImpliedVol::new(1e-12, 100); 78 | let vol = solver.solve(&mut *pricer, 20.0, 0.0, 1.0).unwrap(); 79 | assert_approx(vol, 0.376721358056774, 1e-12); 80 | 81 | // check that this gives a price of 20.0 82 | let bumped = pricer.price().unwrap(); 83 | assert_approx(bumped, 20.0, 1e-10); 84 | } 85 | 86 | fn assert_approx(value: f64, expected: f64, tolerance: f64) { 87 | assert!(approx_eq(value, expected, tolerance), 88 | "value={} expected={}", value, expected); 89 | } 90 | } -------------------------------------------------------------------------------- /src/solvers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod impliedvol; 2 | 3 | use risk::Pricer; 4 | use core::qm; 5 | 6 | /// Solvers iteratively reprice with different data until they match the 7 | /// target price. A one-dimensional solver looks for a single value, such 8 | /// as a volatility. 9 | pub trait OneDimensionalSolver { 10 | /// Given a pricer, a target price and a bump, adjust the bump to 11 | /// hit the target. The minimum and maximum acceptable values must be 12 | /// supplied. These are normally chosen so that the pricer will 13 | /// function correctly within this range. For example, many pricers 14 | /// are unstable given extreme inputs. If the range does not bracket 15 | /// the root, an error is returned. (Some libraries such as QuantLib 16 | /// will iteratively extend the minimum and maximum values to try 17 | /// to bracket the root. This strikes me as dangerous, and a misuse 18 | /// of the idea of a safe range.) 19 | fn solve(&self, pricer: &mut Pricer, target: f64, min: f64, max: f64) 20 | -> Result; 21 | } --------------------------------------------------------------------------------