use dates::Date;
use dates::datetime::DateTime;
use core::qm;
use std::collections::HashMap;
use std::sync::Arc;
use std::ops::Deref;
use serde as sd;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FixingTable {
fixings_known_until: Date,
fixings_by_id: HashMap<String, Fixings>
}
impl FixingTable {
pub fn from_fixings(fixings_known_until: Date,
fixings: &[(&str, &[(DateTime, f64)])])
-> Result<FixingTable, qm::Error> {
let mut fixing_table = FixingTable::new(fixings_known_until);
for fixing in fixings.iter() {
let fixing_curve = Fixings::new(fixing.0, fixing.1)?;
fixing_table.insert(fixing.0, fixing_curve)?;
}
Ok(fixing_table)
}
pub fn from_iter_known_until<T, U, V>(fixings_known_until: Date, iter: T) -> Result<FixingTable, qm::Error>
where
T: IntoIterator<Item = (U, V)>,
U: AsRef<str>,
V: AsRef<[(DateTime, f64)]> {
let mut fixing_table = FixingTable::new(fixings_known_until);
for fixing in iter {
let fixing_curve = Fixings::new(fixing.0.as_ref(), fixing.1.as_ref())?;
fixing_table.insert(fixing.0.as_ref(), fixing_curve)?;
}
Ok(fixing_table)
}
pub fn new(fixings_known_until: Date) -> FixingTable {
FixingTable { fixings_known_until: fixings_known_until,
fixings_by_id: HashMap::new() }
}
pub fn insert(&mut self, id: &str, fixings: Fixings)
-> Result<(), qm::Error> {
if let Some(_) = self.fixings_by_id.insert(id.to_string(), fixings) {
return Err(duplicate_fixing_curve(id))
}
Ok(())
}
pub fn get(&self, id: &str, date_time: DateTime)
-> Result<Option<f64>, qm::Error> {
match self.get_optional(id, date_time) {
Some(fixing) => Ok(Some(fixing)),
None => { if date_time.date() < self.fixings_known_until {
Err(missing_fixing(id, date_time)) } else { Ok(None) }
}
}
}
pub fn get_optional(&self, id: &str, date_time: DateTime) -> Option<f64> {
match self.get_fixings(id) {
Some(fixings) => fixings.get_optional(date_time),
None => None
}
}
pub fn get_fixings(&self, id: &str) -> Option<&Fixings> {
self.fixings_by_id.get(&id.to_string())
}
pub fn fixings_known_until(&self) -> Date {
self.fixings_known_until
}
}
pub fn missing_fixing(id: &str, date_time: DateTime) -> qm::Error {
qm::Error::new(&format!("Missing fixing for \"{}\" at {}", id, date_time))
}
fn duplicate_fixing_curve(id: &str) -> qm::Error {
qm::Error::new(&format!("Duplicate fixing curve supplied for {}", id))
}
#[derive(Clone, Debug)]
pub struct RcFixingTable(Arc<FixingTable>);
impl RcFixingTable {
pub fn new(table: Arc<FixingTable>) -> RcFixingTable {
RcFixingTable(table)
}
}
impl Deref for RcFixingTable {
type Target = FixingTable;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl sd::Serialize for RcFixingTable {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: sd::Serializer
{
self.0.serialize(serializer)
}
}
impl<'de> sd::Deserialize<'de> for RcFixingTable {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: sd::Deserializer<'de> {
let stream = FixingTable::deserialize(deserializer)?;
Ok(RcFixingTable::new(Arc::new(stream)))
}
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct Fixings {
#[serde(with = "map_as_pairs")]
fixing_by_date: HashMap<DateTime, f64>,
}
mod map_as_pairs {
use std::fmt;
use serde::ser::Serializer;
use serde::de::{Deserializer, Visitor, SeqAccess};
use std::collections::HashMap;
use dates::datetime::DateTime;
pub fn serialize<S>(map: &HashMap<DateTime, f64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_seq(map)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<DateTime, f64>, D::Error>
where
D: Deserializer<'de>,
{
struct MapVisitor {}
impl<'de> Visitor<'de> for MapVisitor {
type Value = HashMap<DateTime, f64>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of key-value pairs")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut map = HashMap::new();
loop {
let next : Option<(DateTime, f64)> = seq.next_element()?;
if let Some((k, v)) = next {
map.insert(k, v);
} else {
break;
}
}
Ok(map)
}
}
deserializer.deserialize_seq(MapVisitor {})
}
}
impl Fixings {
pub fn new(id: &str, fixings: &[(DateTime, f64)])
-> Result<Fixings, qm::Error> {
let mut fixing_by_date = HashMap::new();
for fixing in fixings.iter() {
if let Some(value) = fixing_by_date.insert(fixing.0, fixing.1) {
return Err(duplicate_fixing(id, value, fixing.1, fixing.0))
}
}
Ok(Fixings { fixing_by_date: fixing_by_date} )
}
pub fn get_optional(&self, date_time: DateTime) -> Option<f64> {
if let Some(fixing) = self.fixing_by_date.get(&date_time) {
Some(*fixing)
} else {
None
}
}
}
fn duplicate_fixing(id: &str, v1: f64, v2: f64, date_time: DateTime)
-> qm::Error {
qm::Error::new(&format!("Duplicate fixing for \"{}\": \
{} and {} both present at {}", id, v1, v2, date_time))
}
#[cfg(test)]
mod tests {
use super::*;
use dates::datetime::TimeOfDay;
use serde_json;
fn sample_fixings() -> FixingTable {
let today = Date::from_ymd(2018, 01, 01);
FixingTable::from_fixings(today, &[
("BT.L", &[
(DateTime::new(today, TimeOfDay::Open), 123.4),
(DateTime::new(today - 7, TimeOfDay::Close), 123.3),
(DateTime::new(today - 7, TimeOfDay::Open), 123.2),
(DateTime::new(today - 9, TimeOfDay::Open), 123.1)]),
(&"GSK.L", &[
(DateTime::new(today, TimeOfDay::Open), 223.4),
(DateTime::new(today - 7, TimeOfDay::Close), 223.3),
(DateTime::new(today - 7, TimeOfDay::Open), 223.2)])]).unwrap()
}
#[test]
fn find_fixing_today() {
let fixings = sample_fixings();
let today = fixings.fixings_known_until();
let fixing = fixings.get("BT.L",
DateTime::new(today, TimeOfDay::Open)).unwrap();
if let Some(f) = fixing {
assert_eq!(f, 123.4);
} else {
assert!(false, "missing fixing");
}
}
#[test]
fn missing_fixing_today() {
let fixings = sample_fixings();
let today = fixings.fixings_known_until();
let fixing = fixings.get("BT.L",
DateTime::new(today, TimeOfDay::Close)).unwrap();
if let Some(_) = fixing {
assert!(false, "fixing present");
}
}
#[test]
#[should_panic]
fn missing_fixing_past() {
let fixings = sample_fixings();
let today = fixings.fixings_known_until();
let _ = fixings.get("BT.L",
DateTime::new(today - 9, TimeOfDay::Close)).unwrap();
}
#[test]
fn find_fixing_past() {
let fixings = sample_fixings();
let today = fixings.fixings_known_until();
let fixing = fixings.get("BT.L",
DateTime::new(today - 7, TimeOfDay::Open)).unwrap();
if let Some(f) = fixing {
assert_eq!(f, 123.2);
} else {
assert!(false, "missing fixing");
}
}
#[test]
fn missing_optional_fixing_past() {
let fixings = sample_fixings();
let today = fixings.fixings_known_until();
let fixing = fixings.get_optional("BT.L",
DateTime::new(today - 9, TimeOfDay::Close));
if let Some(_) = fixing {
assert!(false, "fixing present");
}
}
#[test]
fn find_optional_fixing_past() {
let fixings = sample_fixings();
let today = fixings.fixings_known_until();
let fixing = fixings.get_optional("BT.L",
DateTime::new(today - 9, TimeOfDay::Open));
if let Some(f) = fixing {
assert_eq!(f, 123.1);
} else {
assert!(false, "missing optional fixing");
}
}
#[test]
fn serde_fixing_table_roundtrip() {
let fixings = sample_fixings();
let serialized = serde_json::to_string_pretty(&fixings).unwrap();
print!("serialized: {}\n", serialized);
let deserialized: FixingTable = serde_json::from_str(&serialized).unwrap();
let today = deserialized.fixings_known_until();
let fixing = deserialized.get("BT.L", DateTime::new(today, TimeOfDay::Open)).unwrap();
if let Some(f) = fixing {
assert_eq!(f, 123.4);
} else {
assert!(false, "missing fixing");
}
let fixing = deserialized.get("BT.L", DateTime::new(today - 7, TimeOfDay::Open)).unwrap();
if let Some(f) = fixing {
assert_eq!(f, 123.2);
} else {
assert!(false, "missing fixing");
}
}
}