use core::factories::TypeId;
use instruments::fix_all;
use std::sync::Arc;
use std::fmt::Display;
use std::fmt;
use std::cmp::Ordering;
use std::hash::Hash;
use std::hash::Hasher;
use instruments::Instrument;
use instruments::RcInstrument;
use instruments::Priceable;
use instruments::PricingContext;
use instruments::DependencyContext;
use instruments::SpotRequirement;
use instruments::assets::Currency;
use instruments::assets::RcCurrency;
use dates::rules::RcDateRule;
use dates::datetime::TimeOfDay;
use dates::datetime::DateTime;
use dates::datetime::DateDayFraction;
use data::fixings::FixingTable;
use core::qm;
use core::factories::Qrc;
use core::dedup::InstanceId;
use erased_serde as esd;
use serde::Deserialize;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Basket {
id: String,
credit_id: String,
currency: RcCurrency,
settlement: RcDateRule,
basket: Vec<(f64, RcInstrument)>
}
impl TypeId for Basket {
fn type_id(&self) -> &'static str { "Basket" }
}
impl InstanceId for Basket {
fn id(&self) -> &str { &self.id }
}
impl Basket {
pub fn new(id: &str, credit_id: &str, currency: RcCurrency,
settlement: RcDateRule, basket: Vec<(f64, RcInstrument)>)
-> Result<Basket, qm::Error> {
Ok(Basket { id: id.to_string(), credit_id: credit_id.to_string(),
currency: currency, settlement: settlement , basket: basket })
}
pub fn from_serial<'de>(de: &mut esd::Deserializer<'de>) -> Result<Qrc<Instrument>, esd::Error> {
Ok(Qrc::new(Arc::new(Basket::deserialize(de)?)))
}
}
impl Instrument for Basket {
fn payoff_currency(&self) -> &Currency { &*self.currency }
fn credit_id(&self) -> &str { &self.credit_id }
fn settlement(&self) -> &RcDateRule { &self.settlement }
fn dependencies(&self, context: &mut DependencyContext)
-> SpotRequirement {
for &(_, ref underlying) in self.basket.iter() {
let spot_requirement = underlying.dependencies(context);
match spot_requirement {
SpotRequirement::NotRequired => {}, _ => context.spot(&underlying)
};
}
SpotRequirement::NotRequired
}
fn time_to_day_fraction(&self, date_time: DateTime)
-> Result<DateDayFraction, qm::Error> {
let day_fraction = match date_time.time_of_day() {
TimeOfDay::Open => 0.0,
TimeOfDay::EDSP => 0.0,
TimeOfDay::Close => 0.8 };
Ok(DateDayFraction::new(date_time.date(), day_fraction))
}
fn as_priceable(&self) -> Option<&Priceable> {
Some(self)
}
fn fix(&self, fixing_table: &FixingTable)
-> Result<Option<Vec<(f64, RcInstrument)>>, qm::Error> {
match fix_all(&self.basket, fixing_table)? {
Some(basket) => {
let id = format!("{}:fixed", self.id());
let replacement : RcInstrument = RcInstrument::new(Qrc::new(Arc::new(
Basket::new(&id, self.credit_id(), self.currency.clone(), self.settlement().clone(), basket)?)));
Ok(Some(vec![(1.0, replacement)]))
},
None => Ok(None)
}
}
}
impl Display for Basket {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.id.fmt(f)
}
}
impl Ord for Basket {
fn cmp(&self, other: &Basket) -> Ordering {
self.id.cmp(&other.id)
}
}
impl PartialOrd for Basket {
fn partial_cmp(&self, other: &Basket) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Basket {
fn eq(&self, other: &Basket) -> bool {
self.id == other.id
}
}
impl Eq for Basket {}
impl Hash for Basket {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl Priceable for Basket {
fn as_instrument(&self) -> &Instrument { self }
fn prices(&self, context: &PricingContext, dates: &[DateTime], out: &mut [f64])
-> Result<(), qm::Error> {
let n_dates = dates.len();
assert_eq!(dates.len(), out.len());
for o in out.iter_mut() {
*o = 0.0;
}
let mut temp = vec!(0.0; n_dates);
for &(weight, ref underlying) in self.basket.iter() {
let priceable = underlying.as_priceable().ok_or_else(|| qm::Error::new(
"The underlying of an basket must be priceable"))?;
priceable.prices(context, dates, &mut temp)?;
for (i, o) in temp.iter().zip(out.iter_mut()) {
*o += i * weight;
}
}
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use math::numerics::approx_eq;
use math::interpolation::Extrap;
use data::forward::Forward;
use data::volsurface::RcVolSurface;
use data::curves::RateCurveAct365;
use data::curves::RcRateCurve;
use dates::Date;
use data::forward::EquityForward;
use data::curves::ZeroRateCurve;
use data::divstream::DividendStream;
use std::sync::Arc;
use instruments::assets::tests::sample_currency;
use instruments::assets::tests::sample_equity;
pub fn sample_basket(step: u32) -> Basket {
let currency = RcCurrency::new(Arc::new(sample_currency(step)));
let az = RcInstrument::new(Qrc::new(Arc::new(sample_equity(currency.clone(), "AZ.L", step))));
let bp = RcInstrument::new(Qrc::new(Arc::new(sample_equity(currency.clone(), "BP.L", step))));
let basket = vec![(0.4, az.clone()), (0.6, bp.clone())];
Basket::new("basket", az.credit_id(), currency, az.settlement().clone(), basket).unwrap()
}
struct SamplePricingContext {
spot_az: f64,
spot_bp: f64
}
impl PricingContext for SamplePricingContext {
fn spot_date(&self) -> Date {
Date::from_ymd(2018, 06, 01)
}
fn yield_curve(&self, _credit_id: &str,
_high_water_mark: Date) -> Result<RcRateCurve, qm::Error> {
let d = Date::from_ymd(2018, 05, 30);
let points = [(d, 0.05), (d + 14, 0.08), (d + 56, 0.09),
(d + 112, 0.085), (d + 224, 0.082)];
let c = RateCurveAct365::new(d, &points,
Extrap::Flat, Extrap::Flat)?;
Ok(RcRateCurve::new(Arc::new(c)))
}
fn spot(&self, id: &str) -> Result<f64, qm::Error> {
if id == "AZ.L" {
Ok(self.spot_az)
} else if id == "BP.L" {
Ok(self.spot_bp)
} else {
Err(qm::Error::new(&format!("{} not recognised for spot", id)))
}
}
fn forward_curve(&self, instrument: &Instrument,
high_water_mark: Date) -> Result<Arc<Forward>, qm::Error> {
let spot = self.spot(instrument.id())?;
let base_date = self.spot_date();
let settlement = instrument.settlement().clone();
let rate = self.yield_curve(instrument.credit_id(), high_water_mark)?;
let borrow = RcRateCurve::new(Arc::new(ZeroRateCurve::new(base_date)));
let divs = DividendStream::new(&Vec::new(), RcRateCurve::new(Arc::new(ZeroRateCurve::new(base_date))));
let forward = EquityForward::new(
base_date, spot, settlement, rate, borrow, &divs, high_water_mark)?;
Ok(Arc::new(forward))
}
fn vol_surface(&self, _instrument: &Instrument, _high_water_mark: Date,
_forward_fn: &Fn() -> Result<Arc<Forward>, qm::Error>)
-> Result<RcVolSurface, qm::Error> {
Err(qm::Error::new("unsupported"))
}
fn correlation(&self, _first: &Instrument, _second: &Instrument)
-> Result<f64, qm::Error> {
Err(qm::Error::new("unsupported"))
}
}
fn sample_pricing_context(spot_az: f64, spot_bp: f64) -> SamplePricingContext {
SamplePricingContext { spot_az, spot_bp }
}
#[test]
fn test_basket() {
let context = sample_pricing_context(120.0, 230.0);
let basket = sample_basket(2);
let date = context.spot_date();
let price = basket.price(&context, DateTime::new(date, TimeOfDay::Close)).unwrap();
assert_approx(price, 120.0 * 0.4 + 230.0 * 0.6);
let forward = basket.price(&context, DateTime::new(date + 365, TimeOfDay::Close)).unwrap();
assert_approx(forward, 201.95832229014877);
}
fn assert_approx(value: f64, expected: f64) {
assert!(approx_eq(value, expected, 1e-12),
"value={} expected={}", value, expected);
}
}