use std::any::Any;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use rand;
use rand::StdRng;
use nalgebra::linalg::Cholesky;
use nalgebra::base::DMatrix;
use statrs::distribution::Distribution;
use statrs::distribution::Normal;
use ndarray::Array;
use ndarray::Array1;
use ndarray::Array2;
use ndarray::Array3;
use ndarray::ArrayView2;
use ndarray::ArrayViewMut2;
use ndarray::Axis;
use core::qm;
use instruments::Instrument;
use instruments::MonteCarloContext;
use instruments::PricingContext;
use instruments::RcInstrument;
use risk::BumpablePricingContext;
use risk::Bumpable;
use risk::Saveable;
use risk::marketdata::MarketData;
use risk::dependencies::DependencyCollector;
use data::bump::Bump;
use models::MonteCarloModel;
use models::MonteCarloTimeline;
use models::MonteCarloModelFactory;
use dates::datetime::DateDayFraction;
use dates::datetime::DateTime;
use dates::datetime::TimeOfDay;
use core::factories::TypeId;
use core::factories::Qrc;
use serde::Deserialize;
use erased_serde as esd;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BlackDiffusionFactory {
correlation_substep: usize,
path_substep: f64,
number_of_paths: usize
}
impl BlackDiffusionFactory {
pub fn new(correlation_substep: usize, path_substep: f64,
number_of_paths: usize) -> BlackDiffusionFactory {
BlackDiffusionFactory { correlation_substep: correlation_substep,
path_substep: path_substep, number_of_paths: number_of_paths }
}
pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result<Qrc<MonteCarloModelFactory>, esd::Error> {
Ok(Qrc::new(Arc::new(BlackDiffusionFactory::deserialize(de)?)))
}
}
impl TypeId for BlackDiffusionFactory {
fn type_id(&self) -> &'static str { "BlackDiffusionFactory" }
}
impl MonteCarloModelFactory for BlackDiffusionFactory {
fn factory(&self, timeline: &MonteCarloTimeline,
context: Box<BumpablePricingContext>)
-> Result<Box<MonteCarloModel>, qm::Error> {
let model = BlackDiffusion::new(timeline, context,
self.correlation_substep, self.path_substep, self.number_of_paths)?;
Ok(Box::new(model))
}
}
#[derive(Clone)]
pub struct BlackDiffusion {
observations: Vec<DateDayFraction>,
flows: Vec<RcInstrument>,
context: Box<BumpablePricingContext>,
key: HashMap<String, usize>,
instruments: Vec<RcInstrument>,
substepping: Vec<usize>,
correlated_gaussians: Array3<f64>,
paths: Array3<f64>
}
impl BlackDiffusion {
pub fn new(timeline: &MonteCarloTimeline,
context: Box<BumpablePricingContext>,
correlation_substep: usize,
path_substep: f64,
n_paths: usize)
-> Result<BlackDiffusion, qm::Error> {
let mut observations = Vec::new();
let mut key = HashMap::new();
let mut instruments = Vec::new();
for (asset, obs) in timeline.observations().iter() {
if observations.is_empty() {
observations = obs.to_vec();
}
key.insert(asset.id().to_string(), instruments.len());
instruments.push(asset.clone());
}
let substepping = calculate_substepping(&observations,
context.as_pricing_context(), &instruments, path_substep)?;
let correlated_gaussians = fetch_correlated_gaussians(
context.as_pricing_context(), &instruments,
correlation_substep, &substepping, n_paths)?;
let paths = fetch_paths(&observations, &correlated_gaussians,
context.as_pricing_context(), &instruments,
&substepping, n_paths)?;
Ok(BlackDiffusion {
observations: observations,
flows: timeline.flows().to_vec(),
context: context,
key: key,
instruments: instruments,
substepping: substepping,
correlated_gaussians: correlated_gaussians,
paths: paths })
}
pub fn refetch(&mut self, id: &str, bumped: bool,
saved_paths: Option<&mut HashMap<usize, Array2<f64>>>) -> Result<bool, qm::Error> {
if !bumped {
return Ok(false)
}
let id_string = id.to_string();
if let Some(asset) = self.key.get(&id_string) {
let path = self.paths.subview_mut(Axis(2), *asset);
if let Some(s) = saved_paths {
s.insert(*asset, path.to_owned());
}
fetch_path(self.instruments[*asset].deref(),
self.context.as_pricing_context(), &self.observations,
self.correlated_gaussians.subview(Axis(2), *asset),
&self.substepping,
path)?;
} else {
return Err(qm::Error::new("Failed to find asset"))
}
Ok(true)
}
pub fn refetch_all(&mut self) -> Result<(), qm::Error> {
let n_paths = self.paths.shape()[0];
self.paths = fetch_paths(&self.observations, &self.correlated_gaussians,
self.context.as_pricing_context(), &self.instruments,
&self.substepping, n_paths)?;
Ok(())
}
}
pub fn calculate_substepping(
observations: &[DateDayFraction],
context: &PricingContext,
instruments: &Vec<RcInstrument>,
path_substep: f64) -> Result<Vec<usize>, qm::Error> {
let n_obs = observations.len();
if n_obs == 0 {
return Err(qm::Error::new("No observations"))
}
let mut substepping = vec!(1_usize; n_obs);
let hwm = observations.last().unwrap().date();
for instrument in instruments.iter() {
let instr : &Instrument = instrument.deref();
let fwd = context.forward_curve(instr, hwm)?;
let surface = context.vol_surface(instr, hwm, &|| Ok(fwd.clone()))?;
let mut prev_var = 0.0;
for (obs, substep) in observations.iter().zip(substepping.iter_mut()) {
let atm = fwd.forward(obs.date())?;
let var = surface.variance(*obs, atm)?;
let fwd_var = var - prev_var;
prev_var = var;
if fwd_var < 0.0 {
return Err(qm::Error::new("Negative forward variance"))
}
let steps = (fwd_var / path_substep).ceil() as usize;
*substep = (*substep).max(steps);
}
}
Ok(substepping)
}
pub fn fetch_correlated_gaussians(
context: &PricingContext,
instruments: &Vec<RcInstrument>,
_correlation_substep: usize,
substepping: &[usize],
n_paths: usize) -> Result<Array3<f64>, qm::Error> {
let n_steps = substepping.iter().sum();
assert!(n_steps > 0);
let n_assets = instruments.len();
assert!(n_assets > 0);
assert!(n_paths > 0);
let mut result = Array3::<f64>::zeros((n_paths, n_steps, n_assets));
let mut correl = Array2::<f64>::eye(n_assets);
for i in 0..n_assets {
let first = instruments[i].deref();
for j in 0..i {
let second = instruments[j].deref();
let c = context.correlation(first, second)?;
correl[(i, j)] = c;
correl[(j, i)] = c;
}
}
let slice = correl.as_slice().ok_or_else(|| qm::Error::new(
"Correlation cannot be accessed as a slice"))?;
let correld = DMatrix::from_column_slice(n_assets, n_assets, slice);
let rootd = Cholesky::new(correld).ok_or_else(|| qm::Error::new(
"Correlation matrix is not positive semi-definite"))?;
let root_slice = rootd.unpack().as_slice().to_vec();
let root = Array::from_shape_vec((n_assets, n_assets), root_slice)?;
let mut rand = rand::StdRng::new().unwrap();
let normal = Normal::new(0.0, 1.0).unwrap();
let mut draws = Array1::zeros(n_assets);
for mut path in result.outer_iter_mut() {
for mut step in path.outer_iter_mut() {
for draw in draws.iter_mut() {
*draw = normal.sample::<StdRng>(&mut rand);
}
step.assign(&root.dot(&draws));
}
}
Ok(result)
}
pub fn fetch_paths(
observations: &[DateDayFraction],
correlated_gaussians: &Array3<f64>,
context: &PricingContext,
instruments: &Vec<RcInstrument>,
substepping: &[usize],
n_paths: usize) -> Result<Array3<f64>, qm::Error> {
let n_assets = instruments.len();
let n_obs = observations.len();
assert!(n_obs > 0);
assert!(n_assets > 0);
assert!(n_paths > 0);
let mut paths = Array3::<f64>::zeros((n_paths, n_obs, n_assets));
for ((asset, gaussians), path) in
instruments.iter().zip(
correlated_gaussians.axis_iter(Axis(2))).zip(
paths.axis_iter_mut(Axis(2))) {
let instr: &Instrument = asset.deref();
fetch_path(instr, context, &observations, gaussians,
substepping, path)?;
}
Ok(paths)
}
pub fn fetch_path(instrument: &Instrument, context: &PricingContext,
observations: &[DateDayFraction], correlated_gaussians: ArrayView2<f64>,
substepping: &[usize],
mut path: ArrayViewMut2<f64>) -> Result<(), qm::Error> {
let n_obs = observations.len();
assert!(n_obs > 0); let shape = correlated_gaussians.shape();
assert_eq!(shape.len(), 2);
assert_eq!(path.shape()[0], shape[0]);
assert!(shape[1] >= n_obs);
let hwm = observations.last().unwrap().date();
let forward_curve = context.forward_curve(instrument, hwm)?;
let vol_surface = context.vol_surface(instrument, hwm, &|| Ok(forward_curve.clone()))?;
let mut forwards = Vec::with_capacity(n_obs);
let mut variances = Vec::with_capacity(n_obs);
let mut displacements = Vec::with_capacity(n_obs);
for obs in observations.iter() {
let fwd = forward_curve.forward(obs.date())?;
variances.push(vol_surface.variance(*obs, fwd)?);
let displacement = vol_surface.displacement(obs.date())?;
displacements.push(displacement);
forwards.push(fwd - displacement);
}
let mut sigmas = Vec::with_capacity(n_obs);
let mut prev_var = 0.0;
for (var, substep) in variances.iter().zip(substepping.iter()) {
let fwd_var = (var - prev_var) / (*substep as f64);
if fwd_var < 0.0 {
return Err(qm::Error::new("Negative forward variance"))
}
sigmas.push(fwd_var.sqrt());
prev_var = *var;
}
for (ref gaussians, ref mut one_path) in
correlated_gaussians.outer_iter().zip(path.outer_iter_mut()) {
let mut point = 1.0;
let mut g = 0; for i in 0..n_obs {
let sigma = sigmas[i];
for _ in 0..substepping[i] {
point *= 1.0 + gaussians[g] * sigma;
g += 1;
}
one_path[i] = point * forwards[i] + displacements[i];
}
}
Ok(())
}
impl MonteCarloModel for BlackDiffusion {
fn as_mc_context(&self) -> &MonteCarloContext { self }
fn as_bumpable(&self) -> &Bumpable { self }
fn as_mut_bumpable(&mut self) -> &mut Bumpable { self }
fn raw_market_data(&self) -> &MarketData { self.context.raw_market_data() }
}
impl MonteCarloContext for BlackDiffusion {
fn paths(&self, instrument: &RcInstrument)
-> Result<ArrayView2<f64>, qm::Error> {
let id = instrument.id().to_string();
let asset = self.key.get(&id).ok_or_else(|| qm::Error::new(
&format!("BlackDiffusion does not know about '{}'", id)))?;
Ok(self.paths.subview(Axis(2), *asset))
}
fn evaluate_flows(&self, quantities: ArrayView2<f64>)
-> Result<f64, qm::Error> {
let flows_shape = quantities.shape();
let paths_shape = self.paths.shape();
let n_paths = paths_shape[0];
let n_paths_f64: f64 = n_paths as f64;
assert_eq!(flows_shape[0], n_paths);
assert_eq!(flows_shape[1], self.flows.len());
let val_date = DateTime::new(self.context.as_pricing_context().spot_date(), TimeOfDay::Open);
let mut total = 0.0;
for (flow, quantity) in self.flows.iter().zip(
quantities.axis_iter(Axis(1))) {
if flow.is_pure_rates() {
let average = quantity.scalar_sum() / n_paths_f64;
let pricer = flow.as_priceable().ok_or_else(|| qm::Error::new(
"All pure-rates flows must be priceable"))?;
let value = pricer.price(self.context.as_pricing_context(), val_date)?;
total += average * value;
} else {
return Err(qm::Error::new("not implemented"))
}
}
Ok(total)
}
fn pricing_context(&self) -> &PricingContext {
&*self.context.as_pricing_context()
}
}
impl Bumpable for BlackDiffusion {
fn bump(&mut self, bump: &Bump, any_saved: Option<&mut Saveable>)
-> Result<bool, qm::Error> {
let saved = to_saved(any_saved)?;
let (saved_data, saved_paths)
: (Option<&mut Saveable>, Option<&mut HashMap<usize, Array2<f64>>>)
= if let Some(s) = saved {
(Some(&mut *s.saved_data), Some(&mut s.paths))
} else {
(None, None)
};
let bumped = self.context.as_mut_bumpable().bump(bump, saved_data)?;
match bump {
&Bump::Spot(ref id, _) => self.refetch(&id, bumped, saved_paths),
&Bump::Divs(ref id, _) => self.refetch(&id, bumped, saved_paths),
&Bump::Borrow(ref id, _) => self.refetch(&id, bumped, saved_paths),
&Bump::Vol(ref id, _) => self.refetch(&id, bumped, saved_paths),
&Bump::Yield(ref credit_id, _) => {
let v = self.dependencies()?
.forward_id_by_credit_id(&credit_id).to_vec();
if let Some(s) = saved_paths {
for id in v.iter() {
self.refetch(&id, bumped, Some(s))?;
}
} else {
for id in v.iter() {
self.refetch(&id, bumped, None)?;
}
}
Ok(bumped)
},
&Bump::SpotDate(_) => {
if bumped {
self.refetch_all()?;
}
Ok(bumped)
}
}
}
fn new_saveable(&self) -> Box<Saveable> {
Box::new(SavedBlackDiffusion::new(
self.context.as_bumpable().new_saveable()))
}
fn dependencies(&self) -> Result<&DependencyCollector, qm::Error> {
self.context.dependencies()
}
fn context(&self) -> &PricingContext {
self.context.as_pricing_context()
}
fn restore(&mut self, any_saved: &Saveable) -> Result<(), qm::Error> {
if let Some(saved)
= any_saved.as_any().downcast_ref::<SavedBlackDiffusion>() {
self.context.as_mut_bumpable().restore(&*saved.saved_data)?;
for (asset, paths) in saved.paths.iter() {
let mut dest = self.paths.subview_mut(Axis(2), *asset);
dest.assign(paths);
}
Ok(())
} else {
Err(qm::Error::new("Mismatching save space for restore"))
}
}
}
fn to_saved(opt_saveable: Option<&mut Saveable>)
-> Result<Option<&mut SavedBlackDiffusion>, qm::Error> {
if let Some(saveable) = opt_saveable {
if let Some(saved) = saveable.as_mut_any().downcast_mut::<SavedBlackDiffusion>() {
Ok(Some(saved))
} else {
Err(qm::Error::new("Mismatching save space for black diffusion"))
}
} else {
Ok(None)
}
}
pub struct SavedBlackDiffusion {
saved_data: Box<Saveable>,
paths: HashMap<usize, Array2<f64>>
}
impl SavedBlackDiffusion {
pub fn new(saved_data: Box<Saveable>) -> SavedBlackDiffusion {
SavedBlackDiffusion {
saved_data: saved_data,
paths: HashMap::new() }
}
}
impl Saveable for SavedBlackDiffusion {
fn as_any(&self) -> &Any { self }
fn as_mut_any(&mut self) -> &mut Any { self }
fn clear(&mut self) {
self.saved_data.clear();
self.paths.clear();
}
}