@@ -328,7 +328,7 @@ impl OptionChain {
328328 /// # Examples
329329 ///
330330 /// ```
331- /// # fn run() -> Result<(), Box<dyn std ::error::Error> > {
331+ /// # fn run() -> Result<(), optionstratlib ::error::Error> {
332332 /// use rust_decimal_macros::dec;
333333 /// use optionstratlib::chains::utils::{OptionChainBuildParams, OptionDataPriceParams};
334334 /// use positive::{pos_or_panic, spos, Positive};
@@ -909,11 +909,9 @@ impl OptionChain {
909909 pub fn atm_option_data ( & self ) -> Result < & OptionData , ChainError > {
910910 // Check for empty option chain
911911 if self . options . is_empty ( ) {
912- return Err ( format ! (
913- "Cannot find ATM OptionData for empty option chain: {}" ,
914- self . symbol
915- )
916- . into ( ) ) ;
912+ return Err ( ChainError :: EmptyChainAtm {
913+ symbol : self . symbol . clone ( ) ,
914+ } ) ;
917915 }
918916
919917 // First check for exact match
@@ -936,11 +934,9 @@ impl OptionChain {
936934
937935 match option_data {
938936 Some ( opt) => Ok ( opt) ,
939- None => Err ( format ! (
940- "Failed to find ATM OptionData for option chain: {}" ,
941- self . symbol
942- )
943- . into ( ) ) ,
937+ None => Err ( ChainError :: AtmNotFound {
938+ symbol : self . symbol . clone ( ) ,
939+ } ) ,
944940 }
945941 }
946942
@@ -1185,7 +1181,9 @@ impl OptionChain {
11851181 let record = result?;
11861182 debug ! ( "To CSV: {:?}" , record) ;
11871183 let mut option_data = OptionData {
1188- strike_price : record[ 0 ] . parse ( ) ?,
1184+ strike_price : record[ 0 ] . parse :: < Positive > ( ) . map_err ( |e| {
1185+ ChainError :: invalid_strike ( record[ 0 ] . parse :: < f64 > ( ) . unwrap_or ( f64:: NAN ) , & e)
1186+ } ) ?,
11891187 call_bid : parse ( & record[ 1 ] ) ,
11901188 call_ask : parse ( & record[ 2 ] ) ,
11911189 put_bid : parse ( & record[ 3 ] ) ,
@@ -2817,11 +2815,9 @@ impl OptionChain {
28172815 pub fn get_optiondata_with_strike ( & self , price : & Positive ) -> Result < & OptionData , ChainError > {
28182816 // Check for empty option chain
28192817 if self . options . is_empty ( ) {
2820- return Err ( format ! (
2821- "Cannot find option data for empty option chain: {}" ,
2822- self . symbol
2823- )
2824- . into ( ) ) ;
2818+ return Err ( ChainError :: EmptyChain {
2819+ symbol : self . symbol . clone ( ) ,
2820+ } ) ;
28252821 }
28262822
28272823 // Find the option with strike price closest to the price parameter
@@ -2835,11 +2831,7 @@ impl OptionChain {
28352831
28362832 match option_data {
28372833 Some ( opt) => Ok ( opt) ,
2838- None => Err ( format ! (
2839- "Failed to find option data for price {} in chain: {}" ,
2840- price, self . symbol
2841- )
2842- . into ( ) ) ,
2834+ None => Err ( ChainError :: StrikeNotFound { strike : * price } ) ,
28432835 }
28442836 }
28452837
@@ -2943,25 +2935,29 @@ impl RNDAnalysis for OptionChain {
29432935
29442936 // Step 1: Validate parameters
29452937 if h == Positive :: ZERO {
2946- return Err ( "Derivative tolerance must be greater than zero"
2947- . to_string ( )
2948- . into ( ) ) ;
2938+ return Err ( ChainError :: invalid_parameters (
2939+ "derivative_tolerance" ,
2940+ "must be greater than zero" ,
2941+ ) ) ;
29492942 }
29502943
29512944 // Step 2: Get all available strikes
29522945 let strikes: Vec < Positive > = self . options . iter ( ) . map ( |opt| opt. strike_price ) . collect ( ) ;
29532946 if strikes. is_empty ( ) {
2954- return Err ( "No strikes available for RND calculation"
2955- . to_string ( )
2956- . into ( ) ) ;
2947+ return Err ( ChainError :: EmptyDensities ) ;
29572948 }
29582949
29592950 // Calculate minimum strike interval
29602951 let min_interval = strikes
29612952 . windows ( 2 )
29622953 . map ( |w| w[ 1 ] - w[ 0 ] )
29632954 . min ( )
2964- . ok_or ( "Cannot determine strike interval" ) ?;
2955+ . ok_or_else ( || {
2956+ ChainError :: invalid_parameters (
2957+ "strike_interval" ,
2958+ "cannot determine minimum strike interval" ,
2959+ )
2960+ } ) ?;
29652961
29662962 if h < min_interval. to_dec ( ) {
29672963 h = min_interval. to_dec ( ) ;
@@ -2970,7 +2966,12 @@ impl RNDAnalysis for OptionChain {
29702966 // Step 3: Calculate time to expiry
29712967 let expiry_date = NaiveDate :: parse_from_str ( & self . expiration_date , "%Y-%m-%d" ) ?
29722968 . and_hms_opt ( 23 , 59 , 59 )
2973- . ok_or ( "Invalid expiry date time" ) ?;
2969+ . ok_or_else ( || {
2970+ ChainError :: invalid_parameters (
2971+ "expiration_date" ,
2972+ "invalid expiration date/time components" ,
2973+ )
2974+ } ) ?;
29742975
29752976 let now = Utc :: now ( ) . naive_utc ( ) ;
29762977 let time_to_expiry =
@@ -3031,7 +3032,7 @@ impl RNDAnalysis for OptionChain {
30313032
30323033 // Step 6: Validate and normalize densities
30333034 if densities. is_empty ( ) {
3034- return Err ( "Failed to calculate valid densities" . to_string ( ) . into ( ) ) ;
3035+ return Err ( ChainError :: EmptyDensities ) ;
30353036 }
30363037
30373038 let total: Decimal = densities. values ( ) . sum ( ) ;
@@ -3070,7 +3071,7 @@ impl RNDAnalysis for OptionChain {
30703071 }
30713072
30723073 if skew. is_empty ( ) {
3073- return Err ( "No valid data for skew calculation" . to_string ( ) . into ( ) ) ;
3074+ return Err ( ChainError :: EmptySkewData ) ;
30743075 }
30753076
30763077 Ok ( skew)
@@ -7471,7 +7472,7 @@ mod rnd_analysis_tests {
74717472 result
74727473 . unwrap_err( )
74737474 . to_string( )
7474- . contains( "Derivative tolerance must be greater than zero " )
7475+ . contains( "derivative_tolerance " )
74757476 ) ;
74767477 }
74777478
@@ -7489,7 +7490,7 @@ mod rnd_analysis_tests {
74897490 result
74907491 . unwrap_err( )
74917492 . to_string( )
7492- . contains( "Derivative tolerance must be greater than zero " )
7493+ . contains( "derivative_tolerance " )
74937494 ) ;
74947495 }
74957496
@@ -7556,7 +7557,7 @@ mod rnd_analysis_tests {
75567557 result
75577558 . unwrap_err( )
75587559 . to_string( )
7559- . contains( "Cannot find ATM OptionData for empty option chain: TEST" )
7560+ . contains( "cannot find ATM option for empty option chain: TEST" )
75607561 ) ;
75617562 }
75627563
@@ -11950,10 +11951,14 @@ mod tests_get_strikes_and_optiondata {
1195011951 assert ! ( result. is_err( ) , "Should return error for empty chain" ) ;
1195111952
1195211953 let error = result. unwrap_err ( ) ;
11954+ assert ! (
11955+ matches!( error, ChainError :: EmptyChain { ref symbol } if symbol == "EMPTY" ) ,
11956+ "Should return ChainError::EmptyChain for EMPTY symbol, got: {error:?}"
11957+ ) ;
1195311958 let error_msg = format ! ( "{error}" ) ;
1195411959 assert ! (
11955- error_msg. contains( "empty option chain" ) ,
11956- "Error should mention empty chain"
11960+ error_msg. contains( "option chain is empty " ) ,
11961+ "Error should mention empty chain, got: {error_msg} "
1195711962 ) ;
1195811963 assert ! (
1195911964 error_msg. contains( "EMPTY" ) ,
0 commit comments