optionstratlib_core/error/
settlement.rs1use crate::model::types::{OptionType, Side};
10use crate::model::{SettlementEvent, SettlementType};
11use chrono::{DateTime, Utc};
12use thiserror::Error;
13
14#[derive(Error, Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum SettlementError {
19 #[error(
22 "unsupported option type for settlement: {option_type} (supported: European, American)"
23 )]
24 UnsupportedOptionType {
25 option_type: OptionType,
27 },
28
29 #[error("missing settlement price for a {settlement_type:?} {event:?} event")]
35 MissingPrice {
36 settlement_type: SettlementType,
38 event: SettlementEvent,
40 },
41
42 #[error("settlement price observed at {observed_at} is after the cutoff {cutoff}")]
44 PriceAfterCutoff {
45 observed_at: DateTime<Utc>,
47 cutoff: DateTime<Utc>,
49 },
50
51 #[error("settlement event {event:?} is incompatible with a {side} position")]
54 EventSideMismatch {
55 event: SettlementEvent,
57 side: Side,
59 },
60
61 #[error("settlement arithmetic overflow in {operation}: {reason}")]
63 ArithmeticOverflow {
64 operation: &'static str,
66 reason: String,
68 },
69}
70
71#[cfg(test)]
72mod tests_settlement_error {
73 use super::*;
74 use chrono::TimeZone;
75
76 #[test]
77 fn test_settlement_error_unsupported_option_type_names_the_type() {
78 let error = SettlementError::UnsupportedOptionType {
79 option_type: OptionType::Cliquet {
80 reset_dates: Vec::new(),
81 },
82 };
83 assert_eq!(
84 error.to_string(),
85 "unsupported option type for settlement: Cliquet Option (Reset Dates: []) (supported: European, American)"
86 );
87 }
88
89 #[test]
90 fn test_settlement_error_missing_price_names_the_event() {
91 let error = SettlementError::MissingPrice {
92 settlement_type: SettlementType::Physical,
93 event: SettlementEvent::Expiry,
94 };
95 assert_eq!(
96 error.to_string(),
97 "missing settlement price for a Physical Expiry event"
98 );
99 }
100
101 #[test]
102 fn test_settlement_error_price_after_cutoff_names_both_instants() {
103 let cutoff = Utc.timestamp_opt(1_000, 0).single();
104 let observed_at = Utc.timestamp_opt(2_000, 0).single();
105 assert!(cutoff.is_some() && observed_at.is_some());
106 if let (Some(cutoff), Some(observed_at)) = (cutoff, observed_at) {
107 let text = SettlementError::PriceAfterCutoff {
108 observed_at,
109 cutoff,
110 }
111 .to_string();
112 assert!(text.contains(&observed_at.to_string()));
113 assert!(text.contains(&cutoff.to_string()));
114 }
115 }
116
117 #[test]
118 fn test_settlement_error_event_side_mismatch_names_event_and_side() {
119 let error = SettlementError::EventSideMismatch {
120 event: SettlementEvent::Assignment,
121 side: Side::Long,
122 };
123 assert_eq!(
124 error.to_string(),
125 "settlement event Assignment is incompatible with a Long position"
126 );
127 }
128
129 #[test]
130 fn test_settlement_error_overflow_names_the_operation() {
131 let error = SettlementError::ArithmeticOverflow {
132 operation: "physical cash",
133 reason: "out of range".to_string(),
134 };
135 assert_eq!(
136 error.to_string(),
137 "settlement arithmetic overflow in physical cash: out of range"
138 );
139 }
140}