use super::{define_non_negative_value_type, CashFlow, Error, ParamKind, Price, Quantity};
define_non_negative_value_type!(
Volume,
ParamKind::Volume
);
impl Volume {
pub fn to_cash_flow_inflow(self) -> CashFlow {
CashFlow::new(self.to_decimal())
}
pub fn to_cash_flow_outflow(self) -> CashFlow {
CashFlow::new(-self.to_decimal())
}
pub fn calculate_quantity(self, price: Price) -> Result<Quantity, Error> {
if price.is_zero() {
return Err(Error::InvalidPrice);
}
let quantity_decimal = self
.to_decimal()
.checked_div(price.to_decimal().abs())
.ok_or(Error::Overflow {
param: ParamKind::Volume,
})?;
Quantity::new(quantity_decimal).map_err(|_| Error::Underflow {
param: ParamKind::Quantity,
})
}
}
#[cfg(test)]
mod tests {
use super::Volume;
use crate::param::{CashFlow, Error, ParamKind, Price, Quantity};
use rust_decimal::Decimal;
fn d(value: &str) -> Decimal {
value
.parse()
.expect("decimal literal in tests must be valid")
}
fn v(value: &str) -> Volume {
Volume::from_str(value).expect("volume literal in tests must be valid")
}
#[test]
fn calculates_quantity() {
let volume = v("6352.6125");
let price = Price::new(d("42350.75"));
assert_eq!(
volume.calculate_quantity(price).expect("must be valid"),
Quantity::new(d("0.15")).expect("must be valid")
);
}
#[test]
fn calculate_quantity_reports_invalid_zero_price() {
let volume = v("1");
let zero_price = Price::new(Decimal::ZERO);
assert_eq!(
volume.calculate_quantity(zero_price),
Err(Error::InvalidPrice)
);
}
#[test]
fn converts_to_cash_flow() {
let volume = v("10.5");
assert_eq!(volume.to_cash_flow_inflow(), CashFlow::new(d("10.5")));
assert_eq!(volume.to_cash_flow_outflow(), CashFlow::new(d("-10.5")));
}
#[test]
fn calculate_quantity_reports_overflow_for_extreme_ratio() {
let volume = Volume::new(Decimal::MAX).expect("must be valid");
let tiny_price = Price::new(Decimal::new(1, 28));
assert_eq!(
volume.calculate_quantity(tiny_price),
Err(Error::Overflow {
param: ParamKind::Volume
})
);
}
#[test]
fn calculate_quantity_maps_internal_negative_result_to_underflow() {
let invalid_volume = Volume(d("-1"));
let price = Price::new(d("10"));
assert_eq!(
invalid_volume.calculate_quantity(price),
Err(Error::Underflow {
param: ParamKind::Quantity
})
);
}
}