Skip to content

Commit cb38c72

Browse files
authored
Merge pull request #369 from joaquinbejar/M4/issue-333-eliminate-string-box-errors
M4 #333: Eliminate Box<dyn Error> and String errors from public surface
2 parents e28d796 + a49bce5 commit cb38c72

72 files changed

Lines changed: 1164 additions & 1267 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ use positive::{pos_or_panic,Positive};
736736
use rust_decimal_macros::dec;
737737
use optionstratlib::greeks::Greeks;
738738

739-
fn main() -> Result<(), Box<dyn std::error::Error>> {
739+
fn main() -> Result<(), optionstratlib::error::Error> {
740740
// Create a European call option
741741
let option = Options::new(
742742
OptionType::European,
@@ -787,7 +787,7 @@ use optionstratlib::visualization::Graph;
787787
use rust_decimal_macros::dec;
788788
use std::error::Error;
789789

790-
fn main() -> Result<(), Box<dyn Error>> {
790+
fn main() -> Result<(), optionstratlib::error::Error> {
791791
use optionstratlib::pricing::Profit;
792792
let underlying_price = Positive::HUNDRED;
793793

@@ -837,7 +837,7 @@ let underlying_price = Positive::HUNDRED;
837837
```rust
838838
use optionstratlib::prelude::*;
839839

840-
fn main() -> Result<(), Box<dyn std::error::Error>> {
840+
fn main() -> Result<(), optionstratlib::error::Error> {
841841
// Create an option for implied volatility calculation
842842
let mut option = Options::new(
843843
OptionType::European,
@@ -867,7 +867,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
867867
```rust
868868
use optionstratlib::prelude::*;
869869

870-
fn main() -> Result<(), Box<dyn std::error::Error>> {
870+
fn main() -> Result<(), optionstratlib::error::Error> {
871871
// Define common parameters
872872
let underlying_symbol = "DAX".to_string();
873873
let underlying_price = pos_or_panic!(24000.0);

examples/examples_chain/src/bin/creator.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ fn main() -> Result<(), optionstratlib::error::Error> {
1919
info!("{}", option_chain);
2020

2121
let underlying_price = option_chain.underlying_price;
22-
let expiration_date = option_chain
23-
.get_expiration()
24-
.ok_or("No expiration date found")?;
22+
let expiration_date = option_chain.get_expiration().ok_or_else(|| {
23+
optionstratlib::error::ChainError::invalid_parameters(
24+
"expiration_date",
25+
"missing on option chain",
26+
)
27+
})?;
2528
let symbol = option_chain.symbol.clone();
2629

2730
info!("Underlying Price: {}", underlying_price);

examples/examples_strategies/src/bin/strategy_iron_butterfly.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ fn main() -> Result<(), Error> {
2424
pos_or_panic!(0.96), // close_fee
2525
)?;
2626
if !strategy.validate() {
27-
return Err("Invalid strategy".into());
27+
return Err(optionstratlib::error::Error::EmptyCollection {
28+
context: "iron_butterfly_validation",
29+
});
2830
}
2931
let range = strategy.break_even_points[1] - strategy.break_even_points[0];
3032

examples/examples_strategies/src/bin/strategy_iron_condor.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ fn main() -> Result<(), Error> {
2525
pos_or_panic!(0.1), // close_fee
2626
)?;
2727
if !strategy.validate() {
28-
return Err("Invalid strategy".into());
28+
return Err(optionstratlib::error::Error::EmptyCollection {
29+
context: "iron_condor_validation",
30+
});
2931
}
3032

3133
let range = strategy.break_even_points[1] - strategy.break_even_points[0];

src/chains/chain.rs

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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"),

src/chains/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//! ## Example Usage
2222
//!
2323
//! ```rust
24-
//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
24+
//! # fn run() -> Result<(), optionstratlib::error::Error> {
2525
//! use rust_decimal::Decimal;
2626
//! use rust_decimal_macros::dec;
2727
//! use optionstratlib::chains::OptionChain;
@@ -116,7 +116,7 @@
116116
//! ## Usage Example
117117
//!
118118
//! ```rust
119-
//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
119+
//! # fn run() -> Result<(), optionstratlib::error::Error> {
120120
//! use rust_decimal_macros::dec;
121121
//! use tracing::info;
122122
//! use optionstratlib::chains::{RNDParameters, RNDAnalysis};

src/chains/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl OptionsInStrike {
9393
///
9494
/// # Returns
9595
///
96-
/// * `Result<DeltasInStrike, Box<dyn Error>>` - A Result containing delta values for all
96+
/// * `Result<DeltasInStrike, ChainError>` - A Result containing delta values for all
9797
/// four option positions if successful, or an error if any delta calculation fails.
9898
///
9999
/// # Errors

src/chains/rnd.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
//! ## Usage Example
3333
//!
3434
//! ```rust
35-
//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
35+
//! # fn run() -> Result<(), optionstratlib::error::Error> {
3636
//! use rust_decimal::Decimal;
3737
//! use rust_decimal_macros::dec;
3838
//! use tracing::info;
@@ -601,7 +601,7 @@ mod tests {
601601
result
602602
.unwrap_err()
603603
.to_string()
604-
.contains("Failed to calculate valid densities")
604+
.contains("failed to calculate any valid risk-neutral density value")
605605
);
606606
}
607607

@@ -616,7 +616,7 @@ mod tests {
616616
result
617617
.unwrap_err()
618618
.to_string()
619-
.contains("Derivative tolerance must be greater than zero")
619+
.contains("derivative_tolerance")
620620
);
621621
}
622622

@@ -634,7 +634,7 @@ mod tests {
634634
result
635635
.unwrap_err()
636636
.to_string()
637-
.contains("Derivative tolerance must be greater than zero")
637+
.contains("derivative_tolerance")
638638
);
639639
}
640640

@@ -654,7 +654,7 @@ mod tests {
654654
result
655655
.unwrap_err()
656656
.to_string()
657-
.contains("Failed to calculate valid densities")
657+
.contains("failed to calculate any valid risk-neutral density value")
658658
);
659659
}
660660
}
@@ -687,7 +687,7 @@ mod tests {
687687
result
688688
.unwrap_err()
689689
.to_string()
690-
.contains("Cannot find ATM OptionData for empty option chain: TEST")
690+
.contains("cannot find ATM option for empty option chain: TEST")
691691
);
692692
}
693693

@@ -767,7 +767,7 @@ mod tests {
767767
rnd_result
768768
.unwrap_err()
769769
.to_string()
770-
.contains("Failed to calculate valid densities")
770+
.contains("failed to calculate any valid risk-neutral density value")
771771
);
772772
}
773773

@@ -824,7 +824,7 @@ mod tests {
824824
rnd_result
825825
.unwrap_err()
826826
.to_string()
827-
.contains("Failed to calculate valid densities")
827+
.contains("failed to calculate any valid risk-neutral density value")
828828
);
829829
}
830830
}
@@ -1001,7 +1001,7 @@ mod additional_tests {
10011001
result
10021002
.unwrap_err()
10031003
.to_string()
1004-
.contains("Failed to calculate valid densities")
1004+
.contains("failed to calculate any valid risk-neutral density value")
10051005
);
10061006
}
10071007

@@ -1020,7 +1020,7 @@ mod additional_tests {
10201020
result
10211021
.unwrap_err()
10221022
.to_string()
1023-
.contains("Failed to calculate valid densities")
1023+
.contains("failed to calculate any valid risk-neutral density value")
10241024
);
10251025
}
10261026

@@ -1048,7 +1048,7 @@ mod additional_tests {
10481048
result
10491049
.unwrap_err()
10501050
.to_string()
1051-
.contains("Failed to calculate valid densities")
1051+
.contains("failed to calculate any valid risk-neutral density value")
10521052
);
10531053
}
10541054
}

0 commit comments

Comments
 (0)