use crate::error::DecimalError;
use num_traits::ToPrimitive;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
pub struct PnLRange {
pub lower: i32,
pub upper: i32,
}
impl PnLRange {
#[inline]
#[must_use]
pub fn new(lower: i32, upper: i32) -> Self {
Self { lower, upper }
}
pub fn new_decimal(lower: Decimal, upper: Decimal) -> Result<Self, DecimalError> {
let lower_i32 = lower
.to_i32()
.ok_or_else(|| DecimalError::ConversionError {
from_type: "Decimal".to_string(),
to_type: "i32".to_string(),
reason: format!("lower bound {lower} out of i32 range"),
})?;
let upper_i32 = upper
.to_i32()
.ok_or_else(|| DecimalError::ConversionError {
from_type: "Decimal".to_string(),
to_type: "i32".to_string(),
reason: format!("upper bound {upper} out of i32 range"),
})?;
Ok(Self {
lower: lower_i32,
upper: upper_i32,
})
}
}
impl Serialize for PnLRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("[{}, {})", self.lower, self.upper))
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::Decimal;
use serde_json;
use std::collections::HashMap;
#[test]
fn test_pnl_range_serialization() {
let range = PnLRange {
lower: -10,
upper: 20,
};
let serialized = serde_json::to_string(&range).unwrap();
assert_eq!(serialized, "\"[-10, 20)\"");
}
#[test]
fn test_pnl_range_as_hashmap_key() {
let mut map = HashMap::new();
map.insert(
PnLRange {
lower: -10,
upper: 0,
},
Decimal::new(25, 2), );
map.insert(
PnLRange {
lower: 0,
upper: 10,
},
Decimal::new(50, 2), );
map.insert(
PnLRange {
lower: 10,
upper: 20,
},
Decimal::new(25, 2), );
let serialized = serde_json::to_string(&map).unwrap();
assert!(serialized.contains("\"[-10, 0)\""));
assert!(serialized.contains("\"[0, 10)\""));
assert!(serialized.contains("\"[10, 20)\""));
}
#[test]
fn test_pnl_range_in_complex_structure() {
#[derive(Serialize)]
struct SimulationResult {
name: String,
distribution: HashMap<PnLRange, Decimal>,
}
let mut distribution = HashMap::new();
distribution.insert(
PnLRange {
lower: -5,
upper: 5,
},
Decimal::new(100, 2), );
let result = SimulationResult {
name: "Test Simulation".to_string(),
distribution,
};
let serialized = serde_json::to_string_pretty(&result).unwrap();
assert!(serialized.contains("\"name\": \"Test Simulation\""));
assert!(serialized.contains("\"distribution\": {"));
assert!(serialized.contains("\"[-5, 5)\""));
}
#[test]
fn test_pnl_range_with_large_values() {
let range = PnLRange {
lower: i32::MIN,
upper: i32::MAX,
};
let serialized = serde_json::to_string(&range).unwrap();
assert_eq!(serialized, format!("\"[{}, {})\"", i32::MIN, i32::MAX));
}
#[test]
fn test_pnl_range_array_serialization() {
let ranges = vec![
PnLRange {
lower: -10,
upper: 0,
},
PnLRange {
lower: 0,
upper: 10,
},
PnLRange {
lower: 10,
upper: 20,
},
];
let serialized = serde_json::to_string(&ranges).unwrap();
assert_eq!(serialized, "[\"[-10, 0)\",\"[0, 10)\",\"[10, 20)\"]");
}
#[test]
fn test_pnl_range_deserialization_error() {
let json_str = "\"[-10, 20)\"";
let result = serde_json::from_str::<PnLRange>(json_str);
assert!(result.is_err());
}
#[test]
fn test_pnl_range_equality() {
let range1 = PnLRange {
lower: -10,
upper: 20,
};
let range2 = PnLRange {
lower: -10,
upper: 20,
};
let range3 = PnLRange {
lower: 0,
upper: 10,
};
assert_eq!(range1, range2);
assert_ne!(range1, range3);
let mut map = HashMap::new();
map.insert(range1.clone(), true);
assert!(map.contains_key(&range2));
assert!(!map.contains_key(&range3));
}
}