use super::{define_signed_value_type, Error, ParamKind, PositionSize, Quantity, Volume};
define_signed_value_type!(
Price,
ParamKind::Price
);
impl Price {
pub fn calculate_position_size(self, quantity: Quantity) -> Result<PositionSize, Error> {
self.to_decimal()
.checked_mul(quantity.to_decimal())
.ok_or(Error::Overflow {
param: ParamKind::Price,
})
.map(PositionSize::new)
}
pub fn calculate_volume(self, quantity: Quantity) -> Result<Volume, Error> {
let volume_decimal = self
.to_decimal()
.abs()
.checked_mul(quantity.to_decimal().abs())
.ok_or(Error::Overflow {
param: ParamKind::Price,
})?;
Volume::new(volume_decimal)
}
}
#[cfg(test)]
mod tests {
use super::Price;
use crate::param::{Error, ParamKind, Quantity};
use rust_decimal::Decimal;
fn d(value: &str) -> Decimal {
value
.parse()
.expect("decimal literal in tests must be valid")
}
#[test]
fn calculate_position_size_positive_price() {
let price = Price::from_str("100").expect("must be valid");
let qty = Quantity::from_str("2.5").expect("must be valid");
let pos = price
.calculate_position_size(qty)
.expect("must not overflow");
assert_eq!(pos.to_decimal(), d("250"));
}
#[test]
fn calculate_position_size_negative_price_preserves_sign() {
let price = Price::new(d("-50"));
let qty = Quantity::from_str("3").expect("must be valid");
let pos = price
.calculate_position_size(qty)
.expect("must not overflow");
assert_eq!(pos.to_decimal(), d("-150"));
}
#[test]
fn calculate_position_size_overflow_returns_error() {
let price = Price::new(Decimal::MAX);
let qty = Quantity::from_str("2").expect("must be valid");
assert_eq!(
price.calculate_position_size(qty),
Err(Error::Overflow {
param: ParamKind::Price
})
);
}
#[test]
fn calculate_volume_works() {
let price = Price::from_str("42350.75").expect("must be valid");
let quantity = Quantity::from_str("0.15").expect("must be valid");
let volume = price
.calculate_volume(quantity)
.expect("volume must be calculable");
assert_eq!(volume.to_decimal(), d("6352.6125"));
}
#[test]
fn calculate_volume_reports_overflow_for_extreme_values() {
let price = Price::new(Decimal::MAX);
let quantity = Quantity::from_str("2").expect("must be valid");
assert_eq!(
price.calculate_volume(quantity),
Err(Error::Overflow {
param: ParamKind::Price
})
);
}
}