use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use super::{Instrument, InstrumentId};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SettlementUnit {
#[default]
BusinessDays = 0,
CalendarDays = 1,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SettlementLag {
n: u64,
unit: SettlementUnit,
}
impl SettlementLag {
pub const fn new(n: u64, unit: SettlementUnit) -> Self {
Self { n, unit }
}
pub const fn n(self) -> u64 {
self.n
}
pub const fn unit(self) -> SettlementUnit {
self.unit
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SettlementScheme {
delivery: SettlementLag,
payment: SettlementLag,
}
impl SettlementScheme {
pub const fn new(delivery: SettlementLag, payment: SettlementLag) -> Self {
Self { delivery, payment }
}
pub const fn uniform(n: u64) -> Self {
let lag = SettlementLag::new(n, SettlementUnit::BusinessDays);
Self::new(lag, lag)
}
pub const fn delivery(self) -> SettlementLag {
self.delivery
}
pub const fn payment(self) -> SettlementLag {
self.payment
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ReferenceBookRegistrationError {
DuplicateId {
instrument_id: InstrumentId,
},
DuplicateInstrument {
instrument: Instrument,
},
}
impl Display for ReferenceBookRegistrationError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::DuplicateId { instrument_id } => {
write!(f, "instrument id {} is already registered", instrument_id.0)
}
Self::DuplicateInstrument { instrument } => {
write!(f, "instrument {:?} is already registered", instrument)
}
}
}
}
impl std::error::Error for ReferenceBookRegistrationError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UnknownReferenceBookInstrumentId {
pub instrument_id: InstrumentId,
}
impl Display for UnknownReferenceBookInstrumentId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"unknown reference-book instrument id: {}",
self.instrument_id.0
)
}
}
impl std::error::Error for UnknownReferenceBookInstrumentId {}
struct ReferenceBookEntry {
settlement_scheme: Option<SettlementScheme>,
}
#[derive(Default)]
pub struct ReferenceBook {
by_id: HashMap<InstrumentId, ReferenceBookEntry>,
by_instrument: HashMap<Instrument, InstrumentId>,
next_auto_id: u64,
}
impl ReferenceBook {
pub fn new() -> Self {
Self::default()
}
pub fn register(
&mut self,
instrument: Instrument,
) -> Result<InstrumentId, ReferenceBookRegistrationError> {
if self.by_instrument.contains_key(&instrument) {
return Err(ReferenceBookRegistrationError::DuplicateInstrument { instrument });
}
let instrument_id = self.next_auto_id();
self.insert(instrument, instrument_id);
Ok(instrument_id)
}
pub fn register_with_id(
&mut self,
instrument: Instrument,
instrument_id: InstrumentId,
) -> Result<InstrumentId, ReferenceBookRegistrationError> {
if self.by_instrument.contains_key(&instrument) {
return Err(ReferenceBookRegistrationError::DuplicateInstrument { instrument });
}
if self.by_id.contains_key(&instrument_id) {
return Err(ReferenceBookRegistrationError::DuplicateId { instrument_id });
}
self.insert(instrument, instrument_id);
Ok(instrument_id)
}
pub fn resolve(&self, instrument: &Instrument) -> Option<InstrumentId> {
self.by_instrument.get(instrument).copied()
}
pub fn set_settlement_scheme(
&mut self,
instrument_id: InstrumentId,
settlement_scheme: SettlementScheme,
) -> Result<(), UnknownReferenceBookInstrumentId> {
let entry = self
.by_id
.get_mut(&instrument_id)
.ok_or(UnknownReferenceBookInstrumentId { instrument_id })?;
entry.settlement_scheme = Some(settlement_scheme);
Ok(())
}
pub fn clear_settlement_scheme(
&mut self,
instrument_id: InstrumentId,
) -> Result<(), UnknownReferenceBookInstrumentId> {
let entry = self
.by_id
.get_mut(&instrument_id)
.ok_or(UnknownReferenceBookInstrumentId { instrument_id })?;
entry.settlement_scheme = None;
Ok(())
}
pub fn settlement_scheme(
&self,
instrument_id: InstrumentId,
) -> Result<Option<SettlementScheme>, UnknownReferenceBookInstrumentId> {
self.by_id
.get(&instrument_id)
.map(|entry| entry.settlement_scheme)
.ok_or(UnknownReferenceBookInstrumentId { instrument_id })
}
fn next_auto_id(&mut self) -> InstrumentId {
loop {
let candidate = InstrumentId(self.next_auto_id);
self.next_auto_id += 1;
if !self.by_id.contains_key(&candidate) {
return candidate;
}
}
}
fn insert(&mut self, instrument: Instrument, instrument_id: InstrumentId) {
self.by_instrument.insert(instrument, instrument_id);
self.by_id.insert(
instrument_id,
ReferenceBookEntry {
settlement_scheme: None,
},
);
if self.next_auto_id == instrument_id.0 {
self.next_auto_id += 1;
}
}
}
#[cfg(test)]
mod tests {
use crate::param::Asset;
use super::{
Instrument, InstrumentId, ReferenceBook, ReferenceBookRegistrationError, SettlementLag,
SettlementScheme, SettlementUnit, UnknownReferenceBookInstrumentId,
};
fn instrument() -> Instrument {
Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
)
}
#[test]
fn settlement_defaults_to_zero_business_days() {
assert_eq!(SettlementUnit::default(), SettlementUnit::BusinessDays);
assert_eq!(SettlementLag::default().n(), 0);
assert_eq!(
SettlementLag::default().unit(),
SettlementUnit::BusinessDays
);
assert_eq!(SettlementScheme::default(), SettlementScheme::uniform(0));
}
#[test]
fn uniform_scheme_uses_business_days_for_both_legs() {
let scheme = SettlementScheme::uniform(2);
assert_eq!(
scheme.delivery(),
SettlementLag::new(2, SettlementUnit::BusinessDays)
);
assert_eq!(
scheme.payment(),
SettlementLag::new(2, SettlementUnit::BusinessDays)
);
}
#[test]
fn resolves_instruments_and_preserves_separate_legs() {
let mut book = ReferenceBook::new();
let instrument = instrument();
let instrument_id = InstrumentId::new(42);
let scheme = SettlementScheme::new(
SettlementLag::new(2, SettlementUnit::BusinessDays),
SettlementLag::new(1, SettlementUnit::CalendarDays),
);
assert_eq!(
book.register_with_id(instrument.clone(), instrument_id),
Ok(instrument_id)
);
assert_eq!(book.resolve(&instrument), Some(instrument_id));
assert_eq!(book.settlement_scheme(instrument_id), Ok(None));
assert_eq!(book.set_settlement_scheme(instrument_id, scheme), Ok(()));
assert_eq!(book.settlement_scheme(instrument_id), Ok(Some(scheme)));
}
#[test]
fn rejects_duplicate_ids_and_instruments() {
let mut book = ReferenceBook::new();
let aapl = instrument();
let msft = Instrument::new(
Asset::new("MSFT").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
);
let id = InstrumentId::new(42);
assert_eq!(book.register_with_id(aapl.clone(), id), Ok(id));
assert_eq!(
book.register_with_id(msft, id),
Err(ReferenceBookRegistrationError::DuplicateId { instrument_id: id })
);
assert_eq!(
book.register_with_id(aapl.clone(), InstrumentId::new(43)),
Err(ReferenceBookRegistrationError::DuplicateInstrument { instrument: aapl })
);
}
#[test]
fn reports_unknown_ids_for_settlement_configuration() {
let mut book = ReferenceBook::new();
let id = InstrumentId::new(99);
let error = UnknownReferenceBookInstrumentId { instrument_id: id };
assert_eq!(book.settlement_scheme(id), Err(error));
assert_eq!(
book.set_settlement_scheme(id, SettlementScheme::uniform(1)),
Err(error)
);
assert_eq!(book.clear_settlement_scheme(id), Err(error));
}
#[test]
fn marketdata_instrument_id_is_the_core_type() {
let core_id = InstrumentId::new(7);
let marketdata_id: crate::marketdata::InstrumentId = core_id;
assert_eq!(marketdata_id, core_id);
}
}