Skip to content

M1: Eliminate .unwrap()/.expect() from src/strategies/ (#316)#347

Merged
joaquinbejar merged 10 commits intomainfrom
issue-316-strategies-panic-free
Apr 16, 2026
Merged

M1: Eliminate .unwrap()/.expect() from src/strategies/ (#316)#347
joaquinbejar merged 10 commits intomainfrom
issue-316-strategies-panic-free

Conversation

@joaquinbejar
Copy link
Copy Markdown
Owner

Closes #316.

Summary

Remove every production .unwrap() and .expect() from src/strategies/ (M1 — Panic-Free Core, 0.16.0). Total: 236 sites across 29 files → 0.

Acceptance grep is clean:

for f in $(find src/strategies -name '*.rs'); do
  awk '/#\[cfg\(test\)\]/{exit} {print}' "$f" | grep -nE '\.unwrap\(\)|\.expect\('
done
# (no output)

Approach

  • Extended StrategyError with three typed variants (NumericConversion { value: f64 }, MissingGreek { name: &'static str }, EmptyCollection { context: String }) plus #[cold] constructors.
  • Tightened Optimizable::create_strategy(...) from -> Self::Strategy to -> Result<Self::Strategy, StrategyError> (default impl now returns OperationError(NotSupported) instead of unimplemented!()).
  • Tightened every leaf strategy ::new(...) from Self to Result<Self, StrategyError> for the strategies whose constructors used .expect("Invalid <leg>") / .expect("Unable to update break even points"). All in-tree callers (tests, examples, benches, dependent strategies) updated.
  • Inside Result-returning fns: ? propagation. Option.ok_or_else(StrategyError::...)? mapped to the most specific variant.
  • Sort comparators (partial_cmp(...).unwrap()): unwrap_or(Ordering::Equal) with SAFETY comment.
  • F-blocker is_valid_optimal_option: rewritten with is_some_and so no panic on missing greeks.
  • F-blocker refine_break_even_point() -> Option<Positive>: short-circuits via .ok()?.

panic! / unreachable! / unimplemented! / assert! are intentionally left in place — they belong to issue #292.

Public API impact (intentional)

The following constructors changed return type from Self to Result<Self, StrategyError>:

ShortStrangle::new, LongStrangle::new, IronCondor::new, IronButterfly::new, CustomStrategy::new, CallButterfly::new, PoorMansCoveredCall::new, ShortStraddle::new, LongStraddle::new, BullCallSpread::new, BearCallSpread::new, BullPutSpread::new, BearPutSpread::new, LongButterflySpread::new, ShortButterflySpread::new, LongCall::new, LongPut::new (private), ShortCall::new (private), ShortPut::new, ProtectivePut::new, CoveredCall::new, Collar::new.

Plus trait-level: Optimizable::create_strategy is now fallible (covers all impls).

Verification

  • cargo build --all-targets --all-features — clean.
  • cargo clippy --all-targets --all-features --workspace -- -D warnings — clean.
  • cargo fmt --all --check — clean.
  • cargo test --lib (default features) — 3723 passed; 0 failed; 5 ignored.
  • cargo test --features plotly --lib — 3719 passed; 0 failed; 5 ignored.
  • cargo build --release — clean.

tests/unit/visualization::plotly_render_test::* PNG/SVG render tests fail in environments without the plotly-static Chrome/Kaleido binary; this is pre-existing and unrelated.

Commit map

  1. feat(error): add NumericConversion, MissingGreek, EmptyCollection variants
  2. refactor(strategies): make Optimizable::create_strategy fallible (40 sites)
  3. refactor(strategies): panic-free base + short/long strangle (58 sites)
  4. refactor(strategies): panic-free iron condor/butterfly + custom (35 sites)
  5. refactor(strategies): panic-free butterflies + pmcc + straddles (32 sites)
  6. refactor(strategies): panic-free spreads + butterfly_spreads + graph (44)
  7. refactor(strategies): panic-free probabilities, delta_neutral, singletons (27)
  8. test(strategies): add negative tests for new StrategyError variants + fmt+clippy

Test plan

  • cargo test --lib --all-features clean
  • cargo clippy --all-targets --all-features --workspace -- -D warnings clean
  • cargo fmt --all --check clean
  • cargo build --release clean
  • Reviewer confirms the constructor API changes are acceptable for the 0.16.0 cut

…iants

Extend StrategyError with three typed variants needed by the strategies/
panic-free refactor (issue #316):
- NumericConversion { value: f64 } for failed f64 → Decimal conversions
  at the numeric kernel boundary.
- MissingGreek { name: &'static str } for options that lack a greek the
  caller requires (delta-neutral validators, etc.).
- EmptyCollection { context: String } for unexpectedly empty option
  chains, break-even point sets, or profit ranges.

Add #[cold] #[inline(never)] constructors. Wire ProbabilityError::From
arms for the three new variants so the existing conversion stays total.
Tighten the `Optimizable::create_strategy` trait signature in
src/strategies/base.rs from `-> Self::Strategy` to
`-> Result<Self::Strategy, StrategyError>`. The default impl now returns
`OperationError(NotSupported)` instead of `unimplemented!()`.

Update all 14 concrete impls (bear/bull call/put spreads, iron condor/
butterfly, call/long/short butterfly, long/short strangle, long/short
straddle, poor_mans_covered_call) to:

- Wrap the constructed strategy in `Ok(...)`.
- Replace `.unwrap()` on `OptionData::call_bid` / `call_ask` /
  `put_bid` / `put_ask` with `.ok_or_else(...)?` mapped to
  `StrategyError::operation_not_supported(..., "missing <quote> ...")`.
- Replace `.expect("Invalid position")` on builder helpers
  (`add_position`, `update_break_even_points`) with `?`.
- Add a `# Errors` section to the doc comment.

Update all ~40 callers:
- Inside `filter_combinations` filter closures: convert
  `let strategy = ...; strategy.validate() && ...` to a `match`
  returning `false` on `Err(_)`.
- Inside `find_optimal` for-loops: extract the strategy via `match`
  with `tracing::warn!(error = %e, "skipping invalid strategy
  combination"); continue;` on `Err`.
- Test callers (`#[cfg(test)]`) propagate via `.unwrap()` /
  `.expect(...)` per project rules (out of scope of issue #316 grep).
- The two intentionally-bad-leg tests (`call_butterfly`,
  `iron_butterfly`, `iron_condor`) now assert `.is_err()` rather than
  catching a panic.

Existing `panic!` / `unreachable!` / `unimplemented!` are intentionally
left in place — they belong to issue #292.

`cargo build --all-targets --all-features` clean.
`cargo test --lib strategies::` 1202 passed.

Eliminates ~40 of the 236 production `.unwrap()/.expect()` sites under
src/strategies/ (236 → 196). Issue #316 progress.
Eliminate every production .unwrap() / .expect() from
src/strategies/base.rs, short_strangle.rs, and long_strangle.rs.

Highlights:

- base.rs::is_valid_optimal_option (DeltaRange arm) rewritten with
  is_some_and so it can no longer panic on missing greeks.
- base.rs sort comparators use unwrap_or(Ordering::Equal) with a SAFETY
  comment; Decimal/Positive are total-ordered, f64 fallback is stable.
- base.rs::get_total_cost / get_net_cost / get_net_premium_received
  switched to ?-propagation via for-loops (signatures unchanged).
- short_strangle.rs and long_strangle.rs delta-neutral filter closures
  bind greeks once and short-circuit to false on None.
- find_optimal loops in both strangles match the new fallible
  create_strategy and continue with tracing::warn! on Err.
- get_profit_area / get_profit_ratio map Decimal::from_f64 NaN/Inf to
  StrategyError::numeric_conversion(...).

API change (intentional, M1 panic-free milestone):

- ShortStrangle::new(...) and LongStrangle::new(...) now return
  Result<Self, StrategyError> (was Self). The constructors used to
  .expect(...) on their own builder helpers; that pattern is replaced
  with ? propagation. All in-tree callers (tests, examples, dependent
  strategies) updated.

Build: cargo build --all-targets --all-features clean.
Tests: cargo test --lib strategies:: 1202 passed; 0 failed.

Production unwrap/expect under src/strategies/: 196 -> 138
(58 eliminated this commit). Issue #316 progress.
…ites)

Eliminate every production .unwrap() / .expect() from
src/strategies/iron_condor.rs, iron_butterfly.rs, and custom.rs.

Highlights:

- iron_condor / iron_butterfly: sort comparators in get_strategy use
  unwrap_or(Ordering::Equal); find_optimal loops match/continue with
  tracing::warn! on unscorable metrics; Decimal::from_f64 in
  get_profit_area maps to StrategyError::numeric_conversion(...);
  fee_per_leg bound once via ? in create_strategy.
- custom.rs::refine_break_even_point() rewritten so calculate_profit_at
  short-circuits via .ok()? instead of panicking; eliminated a latent
  duplicated f_x recompute.
- custom.rs::calculate_pnl / calculate_pnl_at_expiration rewritten as
  for-loop with ?; probability for_each(...).unwrap() blocks turned
  into for ... { ... ? } chains.

API changes (intentional, M1 panic-free milestone):

- IronCondor::new(...) now returns Result<Self, StrategyError> (was
  Self). Drops three .expect("Invalid <leg>") and one
  .expect("Unable to update break even points").
- IronButterfly::new(...) same change, same rationale.
- CustomStrategy::new(...) now returns Result<Self, StrategyError> (was
  Self). Drops .expect("Unable to update break even points"). The
  panic!("Invalid strategy: No positions provided") invariant remains
  (issue #292) and is documented in # Panics.

All in-tree callers (tests, examples, dependent strategies) updated.

Build: cargo build --all-targets --all-features clean.
Tests: cargo test --lib strategies:: 1202 passed; 0 failed.

Production unwrap/expect under src/strategies/: 138 -> 103
(35 eliminated this commit). Issue #316 progress.
…ites)

Eliminate every production .unwrap() / .expect() from
src/strategies/{call_butterfly, poor_mans_covered_call, short_straddle,
long_straddle}.rs.

Highlights:

- Sort comparators in get_strategy paths use
  unwrap_or(Ordering::Equal).
- find_optimal loops match/continue with tracing::warn! on unscorable
  metrics.
- Decimal::from_f64 in get_profit_area / get_profit_ratio maps to
  StrategyError::numeric_conversion(...).
- call_butterfly::get_profit_ratio chains self.get_max_loss() via ?.

API changes (intentional, M1 panic-free milestone):

- CallButterfly::new(...), PoorMansCoveredCall::new(...),
  ShortStraddle::new(...), and LongStraddle::new(...) now return
  Result<Self, StrategyError> (were Self). Drops
  .expect("Invalid <leg>") and
  .expect("Unable to update break even points") from each constructor
  body. All in-tree call sites (tests, examples, dependent strategies,
  benches) updated.

Build: cargo build --all-targets --all-features clean.
Tests: cargo test --lib strategies:: 1202 passed; 0 failed.

Production unwrap/expect under src/strategies/: 103 -> 71
(32 eliminated this commit). Issue #316 progress.
…(44)

Eliminate every production .unwrap() / .expect() from
src/strategies/{bull,bear}_{call,put}_spread.rs,
{long,short}_butterfly_spread.rs, and graph.rs.

Highlights:

- Sort comparators in every spread's get_strategy path use
  unwrap_or(Ordering::Equal) with a SAFETY comment.
- find_optimal loops match/continue with tracing::warn! on unscorable
  metrics.
- graph.rs: range.first()/last().unwrap() rewritten as if-let guard;
  calculate_profit_at(&price).unwrap() in the per-price loop matches
  and continues with tracing::warn! on Err; current_sign.unwrap()
  bindings folded into match/let-else patterns.

API changes (intentional, M1 panic-free milestone):

- BullCallSpread::new(...), BearCallSpread::new(...),
  BullPutSpread::new(...), BearPutSpread::new(...),
  LongButterflySpread::new(...), and ShortButterflySpread::new(...)
  now return Result<Self, StrategyError> (were Self). Drops
  .expect("Invalid <leg>") and
  .expect("Unable to update break even points") from each constructor
  body. All in-tree call sites (tests, examples, dependent strategies,
  benches) updated.

Build: cargo build --all-targets --all-features clean.
Tests: cargo test --lib strategies:: 1202 passed; 0 failed.

Production unwrap/expect under src/strategies/: 71 -> 27
(44 eliminated this commit). Issue #316 progress.
…tons

Eliminate the final 27 production .unwrap() / .expect() under
src/strategies/. Total panic-free count for the milestone: 0.

Highlights:

- probabilities/core.rs: HashMap .values().next().unwrap() →
  StrategyError::empty_collection(...); get_break_even_points /
  get_best_range / get_profit_ratio chained via ?. Decimal::to_f64() in
  expected_value rewritten as a for-loop with NumericConversion
  fallback.
- probabilities/utils.rs: risk_free.to_f64() bound once per arm with
  ?-propagation into ProbabilityError::StdError; f2du! and big_n()
  unwraps replaced by ? (existing #[from] DecimalError chain).
- delta_neutral/optimizer.rs: best_plan.as_ref().unwrap() rewritten
  with is_none_or; partial_cmp(...).unwrap() sort comparators →
  unwrap_or(Ordering::Equal) with SAFETY comment.
- delta_neutral/model.rs: per-leg option.delta().unwrap() folded into
  a for-loop with ? and bound once (was duplicated greek call).
- Single-leg / spot-paired strategies (long/short_call, long/short_put,
  protective_put, covered_call, collar): add_position(...).expect(...)
  → ?; Positive::new(0.001).unwrap() in simulator path → ?;
  ProgressStyle::default_bar().template(...).expect(...) → map_err to
  SimulationError::OtherError.
- Doctests in probabilities/mod.rs rewrapped with `# fn run() ->
  Result<(), Box<dyn std::error::Error>>` to keep ? working after the
  constructor / analyze_probabilities API tightening.

API changes (intentional, M1 panic-free milestone):

- LongCall::new(...), LongPut::new(...) (private), ShortCall::new(...)
  (private), ShortPut::new(...), ProtectivePut::new(...),
  CoveredCall::new(...), and Collar::new(...) now return
  Result<Self, StrategyError> (were Self). The trio of single-name
  spot-paired constructors (protective_put / covered_call / collar)
  also drop their #[must_use] (Result already carries it).

All workspace callers (tests, examples, benches) updated.

Build: cargo build --all-targets --all-features clean.
Tests: cargo test --lib strategies:: 1202 passed; 0 failed.
Doctests: probabilities 6 passed.

ACCEPTANCE GREP — production unwrap/expect under src/strategies/: 0.
Issue #316 implementation complete.
… fmt+clippy

- Add tests_panic_free_variants module in src/error/strategies.rs covering
  NumericConversion (NaN, Inf), MissingGreek (constructor + display), and
  EmptyCollection (str + String inputs), plus end-to-end routing through
  ProbabilityError::From.
- Apply cargo fmt --all and cargo clippy --fix --all-targets
  --all-features --workspace to the post-refactor strategy files. No
  behavioural changes — purely formatting and clippy-suggested
  simplifications (collapsing match arms into single expressions,
  splitting builder calls onto fresh lines).

Acceptance grep on src/strategies/: 0 production .unwrap()/.expect()
sites remain.

cargo build --all-targets --all-features: clean.
cargo clippy --all-targets --all-features --workspace -- -D warnings:
clean.
cargo test --lib (default features): 3723 passed; 0 failed; 5 ignored.

Note: tests/unit visualization tests
(visualization::plotly_render_test::*) require the plotly-static
Chrome/Kaleido binary to render PNG/SVG and fail in environments where
it is unavailable. Those failures pre-date this PR (verified on main
prior to branching) and are unrelated to the panic-free refactor.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 56.11111% with 316 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/strategies/custom.rs 6.66% 42 Missing ⚠️
src/strategies/short_strangle.rs 44.06% 33 Missing ⚠️
src/strategies/long_strangle.rs 45.65% 25 Missing ⚠️
src/strategies/iron_condor.rs 58.33% 20 Missing ⚠️
src/strategies/iron_butterfly.rs 60.41% 19 Missing ⚠️
src/strategies/call_butterfly.rs 58.97% 16 Missing ⚠️
src/strategies/long_butterfly_spread.rs 54.28% 16 Missing ⚠️
src/strategies/short_butterfly_spread.rs 54.28% 16 Missing ⚠️
src/strategies/base.rs 65.78% 13 Missing ⚠️
src/strategies/bear_call_spread.rs 59.37% 13 Missing ⚠️
... and 14 more
Files with missing lines Coverage Δ
benches/model/strategy.rs 100.00% <ø> (ø)
src/error/probability.rs 72.30% <100.00%> (+2.81%) ⬆️
src/error/strategies.rs 76.47% <100.00%> (+3.13%) ⬆️
src/lib.rs 100.00% <ø> (ø)
src/strategies/collar.rs 46.22% <100.00%> (ø)
src/strategies/covered_call.rs 34.42% <100.00%> (ø)
src/strategies/delta_neutral/model.rs 62.29% <100.00%> (+0.24%) ⬆️
src/strategies/protective_put.rs 56.91% <100.00%> (ø)
src/strategies/long_call.rs 70.88% <80.00%> (-0.32%) ⬇️
src/strategies/long_put.rs 71.65% <80.00%> (-0.32%) ⬇️
... and 22 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Update version references across library documentation, README, and `Cargo.toml` to reflect v0.16.0.
- Refactor: simplify match arm in `delta_neutral::optimizer.rs`.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR advances the “panic-free core” initiative by eliminating production .unwrap()/.expect() usage under src/strategies/ and making key strategy construction/optimization paths fallible via Result<_, StrategyError>.

Changes:

  • Introduces new typed StrategyError variants and uses them to replace prior panics/unwraps at f64Decimal, missing greeks, and empty-collection boundaries.
  • Makes Optimizable::create_strategy and many strategy constructors return Result, updating in-tree callers (tests/examples/benches) accordingly.
  • Reworks several optimization/sorting/graph/probability code paths to avoid panics by using ?, ok_or_else, and safe comparator fallbacks.

Reviewed changes

Copilot reviewed 94 out of 177 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/unit/strategies/simple/strategy_short_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_short_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_short_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_poor_mans_covered_call.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_long_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_long_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_long_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_iron_condor.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_iron_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_graph.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_call_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_bull_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_bull_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_bear_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/simple/strategy_bear_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/protective_put_test.rs Update helper to unwrap new fallible constructor.
tests/unit/strategies/custom_test.rs Update helpers to unwrap new fallible constructor.
tests/unit/strategies/optimal_center/strategy_short_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_short_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_short_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_poor_mans_covered_call.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_long_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_long_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_long_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_iron_condor.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_iron_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_call_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_bull_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_bull_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_bear_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal_center/strategy_bear_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_short_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_short_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_short_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_poor_mans_covered_call.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_long_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_long_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_long_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_iron_condor.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_iron_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_call_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_bull_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_bull_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_bear_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/optimal/strategy_bear_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_short_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_short_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_short_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_poor_mans_covered_call.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_long_strangle.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_long_straddle.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_long_butterfly_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_iron_condor.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_iron_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_call_butterfly.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_bull_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_bull_call_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_bear_put_spread.rs Propagate fallible constructor via ?.
tests/unit/strategies/delta/strategy_bear_call_spread.rs Propagate fallible constructor via ?.
src/strategies/short_strangle.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/short_straddle.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/short_butterfly_spread.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/poor_mans_covered_call.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/long_strangle.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/long_straddle.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/call_butterfly.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/bull_put_spread.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/bull_call_spread.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/bear_put_spread.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/iron_butterfly.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/protective_put.rs Make constructor fallible; remove break-even unwrap/expect.
src/strategies/covered_call.rs Make constructor fallible; remove break-even unwrap/expect.
src/strategies/collar.rs Make constructor fallible; remove break-even unwrap/expect.
src/strategies/custom.rs Make constructor fallible; remove unwraps across optimization/probability/pnl.
src/strategies/short_put.rs Make constructor fallible; remove simulation/template and small-positive unwraps.
src/strategies/short_call.rs Make constructor fallible; remove simulation/template and small-positive unwraps.
src/strategies/long_call.rs Make constructor fallible; remove simulation/template and small-positive unwraps.
src/strategies/long_put.rs Make constructor fallible; remove simulation/template and small-positive unwraps.
src/strategies/long_butterfly_spread.rs Make constructor fallible; remove unwraps; harden optimizer/scoring.
src/strategies/graph.rs Avoid panics during range/profit plotting; skip unscorable points.
src/strategies/base.rs Remove unwraps in shared strategy helpers; make default create_strategy fallible.
src/strategies/probabilities/mod.rs Update rustdoc examples to propagate new fallible APIs.
src/strategies/probabilities/utils.rs Remove unwraps around Decimal/f64 conversions and distribution helpers.
src/strategies/probabilities/core.rs Remove unwraps in probability analysis; add empty-collection error paths.
src/strategies/delta_neutral/optimizer.rs Remove unwraps; safe comparisons; avoid best-plan unwrap.
src/strategies/delta_neutral/model.rs Remove unwraps when collecting per-position deltas.
src/error/strategies.rs Add new typed StrategyError variants plus cold constructors and tests.
src/error/probability.rs Map new StrategyError variants into ProbabilityError.
examples/examples_strategies_delta/src/bin/strategy_short_strangle_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_short_strangle_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_short_straddle_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_short_straddle_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_short_butterfly_spread_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_short_butterfly_spread_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_pmcc_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_pmcc_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_long_strangle_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_long_strangle_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_long_straddle_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_long_straddle_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_long_butterfly_spread_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_long_butterfly_spread_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_iron_condor_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_iron_condor_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_iron_butterfly_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_iron_butterfly_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_call_butterfly_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_call_butterfly_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bull_put_spread_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bull_put_spread_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bull_call_spread_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bull_call_spread_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bear_put_spread_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bear_put_spread_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bear_call_spread_extended_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_delta/src/bin/strategy_bear_call_spread_delta.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_short_strangle_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_short_strangle_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_short_straddle_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_short_straddle_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_short_butterfly_spread_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_short_butterfly_spread_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_poor_mans_covered_call_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_poor_mans_covered_call_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_long_strangle_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_long_strangle_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_long_straddle_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_long_straddle_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_long_butterfly_spread_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_long_butterfly_spread_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_iron_condor_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_iron_condor_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_iron_butterfly_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_iron_butterfly_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_call_butterfly_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_call_butterfly_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bull_put_spread_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bull_put_spread_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bull_call_spread_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bull_call_spread_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bear_put_spread_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bear_put_spread_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bear_call_spread_best_ratio.rs Propagate fallible constructor via ?.
examples/examples_strategies_best/src/bin/strategy_bear_call_spread_best_area.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_short_strangle_delta_simple.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_short_strangle.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_short_straddle.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_short_butterfly_spread.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_poor_mans_covered_call.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_long_strangle.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_long_straddle.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_long_butterfly_spread.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_iron_condor.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_iron_butterfly.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_graph.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_custom_simple.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_custom_short_strangle.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_custom_dax.rs Use .unwrap() to adapt to fallible constructor.
examples/examples_strategies/src/bin/strategy_custom_complex.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_call_butterfly.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_bull_put_spread.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_bull_call_spread.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_bear_put_spread.rs Propagate fallible constructor via ?.
examples/examples_strategies/src/bin/strategy_bear_call_spread.rs Propagate fallible constructor via ?.
examples/examples_simulation/src/bin/strategy_simulator.rs Propagate fallible constructor via ?.
examples/examples_simulation/src/bin/short_put_strategy_simulation.rs Propagate fallible constructor via ?.
examples/examples_simulation/src/bin/long_call_strategy_simulation.rs Propagate fallible constructor via ?.
examples/examples_chain/src/bin/option_chain_raw_delta.rs Propagate fallible constructor via ?.
examples/examples_chain/src/bin/option_chain_raw.rs Propagate fallible constructor via ?.
examples/examples_chain/src/bin/option_chain_ger40.rs Propagate fallible constructor via ?.
examples/examples_chain/src/bin/creator.rs Propagate fallible constructor via ?.
benches/model/strategy.rs Adapt benchmarks to fallible constructors via .unwrap().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +175 to +181
let mut expected_value = 0.0_f64;
for (price, prob) in range.iter().zip(probabilities.iter()) {
let profit_dec = self.calculate_profit_at(price)?;
let profit = profit_dec
.to_f64()
.ok_or_else(|| StrategyError::numeric_conversion(0.0))?;
expected_value += profit * prob.to_f64();
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expected_value maps a Decimal -> f64 conversion failure to StrategyError::numeric_conversion(0.0), which (a) hard-codes an unrelated value (0.0) into the error and (b) uses an error variant/message that describes the opposite direction (f64 -> Decimal). Please return an error that preserves the failing profit_dec (or introduce a dedicated DecimalToF64Conversion/contextful variant) so callers get an accurate diagnostic.

Copilot uses AI. Check for mistakes.
Three doctests at the public-API surface still called the old
infallible constructors and were left chaining methods on the new
Result return value:

- src/lib.rs (Working with Trading Strategies): BullCallSpread::new
  now requires `?` to extract the strategy.
- src/lib.rs (Custom Strategy Creation): CustomStrategy::new gains
  .expect("valid custom strategy") so the trailing get_title() call
  resolves.
- src/strategies/mod.rs (Bull Call Spread + Iron Condor examples):
  BullCallSpread::new and IronCondor::new gain .expect(...) for the
  same reason.
- src/strategies/bull_put_spread.rs::filter_combinations example:
  BullPutSpread::new gains .expect("valid bull put spread") so the
  filter_combinations call works on the resolved strategy.

cargo test --doc: 203 passed; 0 failed; 61 ignored.
@joaquinbejar joaquinbejar merged commit d260c87 into main Apr 16, 2026
11 of 12 checks passed
joaquinbejar added a commit that referenced this pull request Apr 16, 2026
`make readme` (cargo readme) picks up the BullCallSpread::new(...)? in
the "Working with Trading Strategies" example added in PR #347 to
match the new fallible constructor. Trivial 4-line diff.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eliminate .unwrap()/.expect() in src/strategies/

2 participants