Skip to content

Repository files navigation

OptionStratLib

Dual License Crates.io Downloads Stars Issues PRs Build Status Coverage Dependencies Documentation Wiki

OptionStratLib v0.24.4: Financial Options Library

Table of Contents

  1. Introduction
  2. Features
  3. Core Modules
  4. Workspace Crates
  5. Module Boundaries
  6. Trading Strategies
  7. Setup Instructions
  8. Library Usage
  9. Testing
  10. Examples

Introduction

OptionStratLib is a comprehensive Rust library for options trading and strategy development across multiple asset classes. This versatile toolkit enables traders, quants, and developers to model, analyze, and visualize options strategies with a robust, type-safe approach. The library focuses on precision with decimal-based calculations, extensive test coverage, and a modular architecture built on modern Rust 2024 edition.

Features

1. Pricing Models

  • Black-Scholes Model: European options pricing with full Greeks support
  • Black-76: European options on futures and forwards
  • Garman-Kohlhagen: European FX options
  • Binomial Tree Model: American and European options with early exercise capability
  • Monte Carlo Simulations: Complex pricing scenarios and path-dependent options
  • Telegraph Process Model: Monte-Carlo pricing under two-state regime-switching volatility
  • American Options: Barone-Adesi-Whaley approximation for early exercise
  • Exotic Options: Pricing for 12 exotic option types (see below)

2. Greeks Calculation

  • Complete Greeks suite: Delta, Gamma, Theta, Vega, Rho, Vanna, Vomma, Veta, Charm, Color
  • Real-time sensitivity analysis
  • Greeks visualization and risk profiling
  • Custom Greeks implementations with adjustable parameters

3. Volatility Models

  • Implied volatility by Newton's method with a bisection fallback, up to 500% per year
  • Historical estimators: constant, EWMA and GARCH volatility, with uncertain volatility bounds
  • Raw SVI slices with weighted calibration (calibrate_svi)
  • Butterfly and calendar static arbitrage checks on a grid (check_static_arbitrage)
  • An implied volatility surface calibrated from bid and ask quotes (ImpliedVolatilitySurface::calibrate): rejected quotes and unfitted expiries stay on record, reads outside the data follow a declared extrapolation policy, and it offers residuals, level, skew and term shocks, and a comparison against a flat baseline
  • Horizon volatility surfaces projected from one option chain (HorizonVolatilitySurface), which are not annualized or calibrated

4. Option Chain Management

  • Complete option chain construction and analysis
  • Strike price generation algorithms
  • Chain data import/export (CSV/JSON formats)
  • Advanced filtering and selection tools
  • Option data grouping and organization

5. Trading Strategies (25+ Strategies)

  • Single Leg: Long/Short Calls and Puts
  • Spreads: Bull/Bear Call/Put Spreads
  • Butterflies: Long/Short Butterfly Spreads (the long one is the textbook long call butterfly)
  • Ladders: Bull Call Ladder
  • Complex: Iron Condor, Iron Butterfly
  • Volatility: Long/Short Straddles and Strangles
  • Income: Covered Calls (with spot leg support), Poor Man's Covered Call
  • Protection: Protective Puts, Collars
  • Custom: Flexible custom strategy framework
  • Multi-Asset: Strategies combining options with spot, futures, or perpetuals

6. Risk Management & Analysis

  • Position tracking and management
  • Break-even analysis with multiple break-even points
  • Profit/Loss calculations at various price points
  • Risk profiles and comprehensive visualizations
  • Delta neutrality analysis and adjustment
  • Probability analysis for strategy outcomes
  • Risk-neutral densities extracted from option chains
  • SPAN-style margin calculation
  • Expiry, exercise and assignment obligations, cash or physical (settlement_obligation)

7. Backtesting Framework

  • Comprehensive backtesting engine
  • Performance metrics calculation
  • Strategy optimization tools
  • Historical analysis capabilities

8. Simulation Tools

  • Monte Carlo simulations for strategy testing
  • Random walks: Brownian, geometric Brownian, log-returns, mean-reverting, jump-diffusion, GARCH, Heston, telegraph, historical and custom
  • An Ornstein-Uhlenbeck path generator
  • The quadratic rough Heston model: seeded paths, Monte Carlo vanilla prices with their standard error, the VIX from the factor state with VIX futures and options, and a calibration to an implied volatility surface on a fixed grid and seed
  • Exit policies and path statistics for simulated positions
  • Parametrized simulations with adjustable inputs

9. Visualization & Plotting

  • Strategy payoff diagrams
  • Greeks visualization
  • 3D volatility surfaces
  • Risk profiles and P&L charts
  • Interactive charts (powered by plotly.rs)
  • Binomial tree visualization
  • Comprehensive plotting utilities

10. Data Management

  • Efficient decimal-based calculations using rust_decimal
  • CSV/JSON import/export functionality
  • Time series data handling
  • Price series management and manipulation
  • Robust data validation and error handling

11. Mathematical Tools

  • Curve interpolation techniques
  • Surface construction and analysis
  • Geometric operations for financial modeling
  • Advanced mathematical utilities for options pricing

12. Exotic Option Pricing

Complete pricing support for all exotic option types:

  • Asian: Arithmetic and geometric average price options
  • Barrier: Up/Down, In/Out barrier options with rebates
  • Binary: Cash-or-nothing and asset-or-nothing options
  • Lookback: Fixed and floating strike lookback options
  • Compound: Options on options
  • Chooser: Options to choose call or put at future date
  • Cliquet: Forward-starting options with local caps/floors
  • Rainbow: Multi-asset best-of/worst-of options
  • Spread: Kirk's approximation for price differentials
  • Quanto: Currency-protected options
  • Exchange: Margrabe's formula for asset exchange
  • Power: Non-linear payoff options

13. Builds Without a Wall Clock

The clock feature, on by default, can be dropped for simulators and replay engines that must be deterministic. A time the library needs then comes from an argument or the thread's reference datetime, or the call returns an error. See Optional Features.

Quality & Discipline

These rules are enforced crate-wide, and CHANGELOG.md records how each was introduced:

  • Checked Decimal arithmetic. Every monetary-path kernel routes through d_add / d_sub / d_mul / d_div / d_sum / d_sum_iter in model::decimal. Overflow on any monetary expression surfaces DecimalError::Overflow { operation, lhs, rhs } tagged with a static call-site string; no silent wraparound.
  • Non-finite f64 guards. Every f64 → Decimal boundary inside pricing, Greeks, volatility, and simulation is wrapped with finite_decimal(..) and surfaces a domain-specific NonFinite { context, value } variant (PricingError, GreeksError, VolatilityError, SimulationError) instead of collapsing silently to Decimal::ZERO.
  • NonZeroUsize step counts. price_binomial, monte_carlo_option_pricing, telegraph and related kernels take std::num::NonZeroUsize for steps / simulations; zero is structurally invalid at the type level. Use the nz!(N) macro at literal call sites.
  • Positive at every public boundary. Monetary values, strikes, quantities, volatilities are Positive (newtype around Decimal). Strategy-level P&L goes through Positive::new_decimal(..) at every point where a signed Decimal would otherwise be clamped to Positive, so inverted strikes or out-of-range optimizer candidates return typed StrategyError rather than panicking.
  • Zero unchecked indexing in production code. #![deny(clippy::indexing_slicing)] is enforced crate-wide with scoped, documented escapes per module. Tests stay permissive via #![cfg_attr(test, allow(..))]. Production paths use .get(..).ok_or_else(..) with typed errors.
  • Doc coverage floor. #![deny(missing_docs, rustdoc::broken_intra_doc_links)]. Every pub item has a /// summary; every Result returner documents its # Errors contract.
  • Structured tracing. #[tracing::instrument] on the public hot paths: pricing::black_scholes, pricing::monte_carlo_option_pricing, pricing::price_binomial, volatility::implied_volatility, and the strategy optimizer entry points get_best_ratio / get_best_area. No println! / eprintln! / dbg! / log:: anywhere in src/.
  • Compiler-attribute discipline. #[must_use] on every pure function and builder, #[inline] on small hot-path helpers, #[cold] #[inline(never)] on every error constructor, #[repr(u8)] on small stable enums, canonical #[derive] ordering.
  • Deterministic simulation tests. utils::deterministic_rng(seed) provides a canonical seeded StdRng for Monte-Carlo / simulation tests, so precision shifts in upstream arithmetic cannot flip assertions by luck.
  • Pricing-identity regression tests. crates/optionstratlib-pricing/tests/identities_test.rs locks put-call parity on a grid, CRR binomial convergence to Black-Scholes, and the Greek sanity identities (Γ_c = Γ_p, Vega_c = Vega_p, Δ_c − Δ_p ≈ e^{-qT}).

Arithmetic-Error Cascade

flowchart LR
    subgraph Kernels["Numeric kernels (model / pricing / greeks / volatility / simulation)"]
        DADD["d_add / d_sub / d_mul / d_div"]
        DSUM["d_sum / d_sum_iter"]
        FD["finite_decimal(f64)"]
    end

    subgraph Errors["Typed errors (error/*)"]
        DOV["DecimalError::Overflow { operation, lhs, rhs }"]
        PNF["PricingError::NonFinite { context, value }"]
        GNF["GreeksError::NonFinite"]
        VNF["VolatilityError::NonFinite"]
        SNF["SimulationError::NonFinite"]
    end

    DADD -- "checked_*" --> DOV
    DSUM -- "checked_*" --> DOV
    FD -- "NaN / ±∞ guard" --> PNF
    FD --> GNF
    FD --> VNF
    FD --> SNF

    DOV -- "#[from]" --> PNF
    DOV -- "#[from]" --> GNF
    DOV -- "#[from]" --> VNF
    DOV -- "#[from]" --> SNF
Loading

Core Modules

The library is organized into the following key modules:

Model (model/, defined by optionstratlib-core)

Core data structures and types for options trading:

  • option.rs: the option contract (Options): terms, validation, payoff
  • position.rs: positions with premium, fees and cost basis
  • expiration.rs: flexible expiration date handling (Days/DateTime)
  • positive_ext.rs: OptionStratLib extensions of positive::Positive
  • types.rs: common enums (OptionType, Side, OptionStyle)
  • trade.rs: trade records and their status
  • settlement.rs: expiry, exercise and assignment obligations
  • format.rs: data formatting utilities
  • leg/: Multi-instrument leg support for strategies
    • traits.rs: Common leg traits (LegAble, Marginable, Fundable, Expirable)
    • spot.rs: SpotPosition for underlying asset positions
    • perpetual.rs: PerpetualPosition for crypto perpetual swaps
    • future.rs: FuturePosition for exchange-traded futures
    • leg_enum.rs: Leg enum unifying all position types

Pricing Models (pricing/)

Advanced pricing engines for options valuation:

  • black_scholes_model.rs: European options pricing with Greeks
  • black_76.rs: European options on futures/forwards (Black 1976)
  • garman_kohlhagen.rs: European FX options (Garman-Kohlhagen 1983)
  • binomial_model.rs: American/European options with early exercise
  • monte_carlo.rs: Path-dependent and exotic options pricing
  • telegraph.rs: Regime-switching volatility pricing
  • payoff.rs: Payoff function implementations
  • american.rs: Barone-Adesi-Whaley approximation
  • Exotic Options:
    • asian.rs: Asian option pricing
    • barrier.rs: Barrier option pricing
    • binary.rs: Binary/Digital option pricing
    • lookback.rs: Lookback option pricing
    • compound.rs: Compound option pricing
    • chooser.rs: Chooser option pricing
    • cliquet.rs: Cliquet option pricing
    • rainbow.rs: Rainbow option pricing
    • spread.rs: Spread option pricing
    • quanto.rs: Quanto option pricing
    • exchange.rs: Exchange option pricing
    • power.rs: Power option pricing

Strategies (strategies/)

Comprehensive trading strategy implementations:

  • base.rs: Core traits (Strategable, BasicAble, Positionable, etc.)
  • Single Leg: long_call.rs, short_call.rs, long_put.rs, short_put.rs
  • Spreads: bull_call_spread.rs, bear_call_spread.rs, bull_put_spread.rs, bear_put_spread.rs
  • Ladders: bull_call_ladder.rs
  • Butterflies: long_butterfly_spread.rs, short_butterfly_spread.rs
  • Complex: iron_condor.rs, iron_butterfly.rs
  • Volatility: long_straddle.rs, short_straddle.rs, long_strangle.rs, short_strangle.rs
  • Income: covered_call.rs, poor_mans_covered_call.rs
  • Protection: protective_put.rs, collar.rs
  • custom.rs: Flexible custom strategy framework
  • probabilities/: Probability analysis for strategy outcomes
  • delta_neutral/: Delta neutrality analysis and adjustment

Volatility (volatility/)

Volatility modeling and analysis:

  • utils.rs: Implied volatility (Newton's method with a bisection fallback) and estimators
  • traits.rs: Volatility model interfaces
  • svi.rs: Raw SVI slices and their weighted calibration
  • arbitrage.rs: Butterfly and calendar checks of slices and surfaces, on a grid
  • surface.rs: A calibrated implied volatility surface built from bid and ask quotes

Greeks (greeks/)

Complete Greeks calculation suite:

  • Delta, Gamma, Theta, Vega, Rho, Vanna, Vomma, Veta, Charm, Color calculations
  • Real-time sensitivity analysis
  • Greeks-based risk management

Chains (chains/)

Option chain management and analysis:

  • chain.rs: Option chain construction and manipulation
  • utils.rs: Chain analysis and filtering tools
  • CSV/JSON import/export functionality
  • Strike price generation algorithms

Backtesting (backtesting/)

Strategy performance analysis:

  • metrics.rs: Performance metrics calculation
  • results.rs: Backtesting results management
  • types.rs: Backtesting data structures

Simulation (simulation/)

Monte Carlo and stochastic simulations:

  • Random walk implementations, including GARCH, Heston and telegraph walks
  • The Ornstein-Uhlenbeck path generator and exit policies
  • The quadratic rough Heston model, Monte Carlo vanilla prices on it, its VIX and its surface calibration
  • Custom simulation frameworks
  • Parametrized simulation tools

Visualization (visualization/)

Comprehensive plotting and charting (optionstratlib-visualization):

  • Graph: one chart contract on every feature surface, with Plotly rendering behind plotly and PNG/SVG export behind static_export
  • Strategy payoff diagrams
  • Greeks visualization
  • 3D volatility surfaces
  • Risk profile charts

Metrics (metrics/)

Performance, risk, and liquidity metrics analysis:

  • Price Metrics: Volatility skew curves
  • Risk Metrics:
    • Implied Volatility curves (by strike) and horizon projections (strike vs horizon)
    • Risk Reversal curves (by strike)
    • Dollar Gamma curves (by strike)
  • Composite Metrics:
    • Vanna-Volga Hedge surfaces (price vs volatility)
    • Delta-Gamma Profile curves (by strike) and surfaces (price vs time)
    • Smile Dynamics curves (by strike) and surfaces (strike vs time)
  • Liquidity Metrics:
    • Bid-Ask Spread curves (by strike)
    • Volume Profile curves (by strike) and surfaces (strike vs time)
    • Open Interest Distribution curves (by strike)
  • Stress Metrics:
    • Volatility Sensitivity curves (by strike) and surfaces (price vs volatility)
    • Time Decay Profile curves (by strike) and surfaces (price vs time)
    • Price Shock Impact curves (by strike) and surfaces (price vs volatility)
  • Temporal Metrics:
    • Theta curves (by strike) and surfaces (price vs time)
    • Charm (Delta Decay) curves (by strike) and surfaces (price vs time)
    • Color (Gamma Decay) curves (by strike) and surfaces (price vs time)

Risk Management (risk/)

Risk analysis and management tools:

  • Position risk metrics
  • Break-even analysis
  • Risk profile generation
  • SPAN-style margin (span.rs)

P&L (pnl/)

Profit and loss calculation:

  • Real-time P&L tracking
  • Historical P&L analysis
  • Performance attribution

Curves & Surfaces (curves/, surfaces/)

Mathematical tools for financial modeling:

  • Curve interpolation techniques
  • Surface construction and analysis
  • 3D visualization capabilities

Error Handling (error/)

Robust error management:

  • Comprehensive error types for each module
  • Type-safe error propagation
  • Detailed error reporting

Workspace Crates

OptionStratLib is nine component crates and this facade (since 0.22). Each component owns one layer, and the facade (optionstratlib) re-exports them, so a path through the facade and the same path through the component name the same type.

Facade or components. Depend on the facade for the whole library behind one version and one prelude, choosing capabilities with its features; its default enables every capability plus io, synthetic and schema. Depend on component crates directly when you want the smallest dependency graph or only one layer (a pricing service needs neither market data nor charts): each has only clock in its default features (optionstratlib-math has none and reads no clock), so you opt into io, async, synthetic, schema, plotly or static_export explicitly. Both routes give identical types, so they mix freely. Runnable direct-component programs, one per capability with the facade equivalent of each, are in examples/direct, and the ownership map gives the defining crate, direct import, facade path and feature of every public concept.

Moving from 0.23? The 0.24 guide lists what to change, starting with the new clock feature. Moving from 0.22? The 0.23 guide covers the one area that changed: PNG and SVG export, now drawn natively, and the typed GraphError it returns. Moving from 0.21? The 0.22 architecture and adoption guide maps each redesign to its replacement workflow; 0.22 keeps no compatibility with 0.21.

Crate Owns Own features Facade feature Facade paths
optionstratlib-core domain model, foundational re-exports, checked Decimal helpers clock, schema always model, utils, constants; the core errors in error (ClockError, DecimalError, DecimalResult, OptionsError, OptionsResult, PositionError, TradeError, SettlementError, OperationErrorKind and the error::position module); ExpirationDate, Options, OptionStyle, OptionType, RainbowType, Side at the root; the nz!, f2d!, f2du!, d2f!, d2fu! and assert_decimal_eq! macros; Positive, pos_or_panic!, spos! and assert_pos_relative_eq! in prelude
optionstratlib-math generic curves, surfaces, interpolation schema math curves, surfaces, geometrics; the math errors in error (CurveError, CurvesResult, SurfaceError, InterpolationError, MetricsError)
optionstratlib-pricing pricing models, Greeks, implied and historical volatility, SVI slices, static arbitrage checks, calibrated volatility surfaces clock, schema pricing (implies math) pricing, greeks, volatility; the pricing errors in error (PricingError, PricingResult, GreeksError, GreeksResult, VolatilityError, SviError, StaticArbitrageError, ImpliedVolatilitySurfaceError and the error::greeks module)
optionstratlib-simulation random walks, stochastic processes, the quadratic rough Heston model with its Monte Carlo vanilla prices, its VIX and its surface calibration, simulators, exit policies, generic path evaluation and statistics clock, schema simulation (implies pricing) simulation; SimulationError, SimulationResult and QuadraticRoughHestonError in error
optionstratlib-market option chains, option series, OHLCV candles clock, io, async, synthetic, schema market (implies pricing); io, async and synthetic forward to the market features chains, series; the market errors in error (ChainError, OhlcvError and the error::chains module)
optionstratlib-analytics P&L, SPAN margin, price-probability kernels, risk-neutral densities, option-chain metrics and projections clock, schema analytics (implies market) analytics, pnl, risk, metrics; the analytics errors in error (ProbabilityError, ProbabilityResult, ProjectionError, RNDError, TransactionError and the error::probability module)
optionstratlib-strategies spreads, butterflies, condors, straddles, strangles, custom strategies, delta neutrality, strategy probability analysis clock, schema strategies (implies analytics) strategies; StrategyError, StrategyResult, AdjustmentError and the error::strategies module in error
optionstratlib-backtest strategy backtests over simulated paths: per-path evaluation, run statistics, reports and metrics clock, schema backtest (implies strategies and simulation) backtesting; BacktestError in error
optionstratlib-visualization chart data, the Graph contract and its implementations, terminal reports, Plotly rendering and PNG/SVG export clock, plotly, static_export visualization (implies backtest); plotly and static_export forward to its features visualization; GraphError and the aggregate Error in error; the impl_graph_for_payoff_strategy! macro at the root

The facade also has schema (forwarded to every enabled component). Each facade path is an explicit module or item re-export (pub use optionstratlib_core::model;, pub use optionstratlib_core::error::DecimalError), never a glob over a component's root, so optionstratlib::model::Options is optionstratlib_core::model::Options. To keep the facade but only one layer, enable only its facade feature:

[dependencies]
# pricing, greeks and volatility, without market data, I/O or charts
optionstratlib = { version = "0.24.4", default-features = false, features = ["pricing"] }
rust_decimal = "1.43" # for `dec!`

or depend on the component itself:

[dependencies]
optionstratlib-core = "0.24.0"
optionstratlib-pricing = "0.24.2"

Canonical paths

Every public item has one defining owner, a component crate, and one canonical path: the shortest flat path its module exports. A type defined in a submodule and re-exported by its parent is canonical at the parent, optionstratlib::pricing::black_scholes rather than optionstratlib::pricing::black_scholes_model::black_scholes, and optionstratlib::error::PricingError for every error type. The defining submodules stay public as documentation anchors (each carries the module docs for its model or strategy), so both paths name the same item, but examples, docs and new code use the flat one. The error kind modules (error::position, error::greeks, error::chains, error::probability, error::strategies) are canonical for their ...Kind enums, which are not flattened because their names collide across crates. Paths kept only for 0.21 have been removed (#550).

The ownership map (docs/ownership.md, linked above) records every canonical path, and make check-graph checks it against the public API, the identity tests and the manifests, so it cannot go stale.

Every capability now lives in a component crate. The facade itself owns only the prelude and the unified error::Error, which wraps every component error including GraphError and therefore needs visualization; the facade default enables all of it.

Module Boundaries

The modules form a directed acyclic graph of layers. Every module belongs to exactly one layer and may reference only its own layer and the ones below it, except that strategies and backtesting skip math, which they reach only through pricing and analytics (ADR-0001 D9). This is the graph the 0.22 workspace split follows, so the layers, not the module names, are what a future crate boundary cuts along (ADR-0001 D9).

flowchart BT
    CORE["core — model, constants, utils"]
    MATH["math — geometrics, curves, surfaces"]
    PRICING["pricing — pricing, greeks, volatility"]
    SIM["simulation"]
    MARKET["market — chains, series"]
    ANALYTICS["analytics — analytics, pnl, risk, metrics"]
    STRAT["strategies"]
    BACKTEST["backtesting"]
    VIS["visualization"]

    MATH --> CORE
    PRICING --> MATH
    SIM --> PRICING
    MARKET --> PRICING
    MARKET -. "synthetic (optional)" .-> SIM
    ANALYTICS --> MARKET
    STRAT --> ANALYTICS
    BACKTEST --> STRAT
    BACKTEST --> SIM
    VIS --> BACKTEST
Loading

Two rules make the graph checkable rather than aspirational:

  • src/error is partitioned, not a layer of its own. Each file under src/error belongs to the layer that raises it, so a lower layer returning a higher layer's error is a forbidden edge even though both types live in the same directory. src/utils is partitioned the same way, per file.
  • The one optional edge is market -> simulation, gated by synthetic. Building with default-features = false, features = ["market"] leaves a market surface that names no simulation type, including through ChainError.

Enforcement

make check-graph           # layer DAG, error/utils partition, synthetic gate, ownership map
make check-feature-trees   # the dependency graph of each market surface

Both run in CI on every pull request. check-graph resolves each crate::error::Name reference to the file that defines it, follows re-exports and aliases, and ignores #[cfg(test)] items, so a violation cannot hide behind a bare import path or a test module. It also runs a self-test proving the scanner catches what it must, and checks the ownership map (docs/ownership.md) against the public-API snapshots, tests/unit/canonical_paths_test.rs and the manifests.

Known reverse edges whose removal is a breaking change wait in the script's DEFERRED table, scoped to the exact files that carry them and each naming the issue that removes it; the same module pair in any other file is a fresh violation, and an entry whose edge has disappeared is reported so the list gets pruned. python3 scripts/check_module_boundaries.py --inventory prints the current table, empty since #658.

Core Components

classDiagram
class Options {
+option_type: OptionType
+side: Side
+underlying_symbol: String
+strike_price: Positive
+expiration_date: ExpirationDate
+implied_volatility: Positive
+quantity: Positive
+underlying_price: Positive
+risk_free_rate: Decimal
+option_style: OptionStyle
+dividend_yield: Positive
+exotic_params: Option~ExoticParams~
+calculate_price_black_scholes()
+calculate_price_binomial()
+time_to_expiration()
+is_long()
+is_short()
+validate()
+to_plot()
+calculate_implied_volatility()
+delta()
+gamma()
+theta()
+vega()
+rho()
+vanna()
+vomma()
+veta()
+charm()
+color()
}

class Position {
+option: Options
+position_cost: Positive
+entry_date: DateTime<Utc>
+open_fee: Positive
+close_fee: Positive
+net_cost()
+net_premium_received()
+unrealized_pnl()
+pnl_at_expiration()
+validate()
}

class Leg {
<<enumeration>>
Option(Position)
Spot(SpotPosition)
Future(FuturePosition)
Perpetual(PerpetualPosition)
+is_option()
+is_spot()
+is_linear()
+delta()
+pnl_at_price()
}

class SpotPosition {
+symbol: String
+quantity: Positive
+cost_basis: Positive
+side: Side
+date: DateTime<Utc>
+open_fee: Positive
+close_fee: Positive
+pnl_at_price()
+delta()
+market_value()
+break_even_price()
}

class ExpirationDate {
+Days(Positive)
+Date(NaiveDate)
+get_years()
+get_date()
+get_date_string()
+from_string()
}

class Positive {
+value: Decimal
+ZERO: Positive
+ONE: Positive
+format_fixed_places()
+round_to_nice_number()
+is_positive()
}

class OptionStyle {
<<enumeration>>
Call
Put
}

class OptionType {
<<enumeration>>
European
American
Bermuda
Asian
Barrier
Binary
Lookback
Compound
Chooser
Cliquet
Rainbow
Spread
Quanto
Exchange
Power
}

class Side {
<<enumeration>>
Long
Short
}

class Graph {
<<interface>>
+graph_data()
+graph_config()
+to_plot()
+write_html()
+write_png()
+write_svg()
+write_jpeg()
}

class Greeks {
<<interface>>
+delta()
+gamma()
+theta()
+vega()
+rho()
+calculate_all_greeks()
}

Options --|> Greeks : implements
Options --|> Graph : implements
Position o-- Options : contains
Leg o-- Position : Option variant
Leg o-- SpotPosition : Spot variant
SpotPosition *-- Side : has
SpotPosition *-- Positive : uses
Options *-- OptionStyle : has
Options *-- OptionType : has
Options *-- Side : has
Options *-- ExpirationDate : has
Options *-- Positive : uses
Loading

Pricing Models Architecture

flowchart TB
    subgraph Standard["Standard Options"]
        EU[European]
        AM[American]
        BE[Bermuda]
    end

    subgraph PathDependent["Path-Dependent"]
        AS[Asian]
        LB[Lookback]
        BA[Barrier]
        CL[Cliquet]
    end

    subgraph MultiAsset["Multi-Asset"]
        RB[Rainbow]
        SP[Spread]
        EX[Exchange]
    end

    subgraph Special["Special Payoffs"]
        BI[Binary]
        PW[Power]
        QU[Quanto]
        CO[Compound]
        CH[Chooser]
    end

    subgraph Forward["Forward-Priced"]
        FUT[Future]
        FWD[Forward]
    end

    subgraph FX["FX / Currency"]
        FX_S[FX Spot]
    end

    BS[black_scholes] --> EU
    BS --> PathDependent
    BS --> MultiAsset
    BS --> Special
    B76[black_76] --> Forward
    GK[garman_kohlhagen] --> FX
    BAW[barone_adesi_whaley] --> AM
    BIN[binomial_model] --> AM
    BIN --> BE
    MC[monte_carlo] --> PathDependent
Loading

Strategy Traits System

classDiagram
    class Strategable {
        <<trait>>
        Master trait combining all capabilities
    }

    class BasicAble {
        <<trait>>
        +get_underlying_price()
        +get_underlying_symbol()
        +get_expiration()
        +get_title()
    }

    class Positionable {
        <<trait>>
        +get_positions()
        +add_position()
        +modify_position()
    }

    class Strategies {
        <<trait>>
        +get_net_premium_received()
        +get_max_profit()
        +get_max_loss()
        +get_total_cost()
    }

    class BreakEvenable {
        <<trait>>
        +get_break_even_points()
        +calculate_break_even()
    }

    class Profit {
        <<trait>>
        +get_point_at_price()
        +calculate_profit_at()
    }

    class Greeks {
        <<trait>>
        +delta()
        +gamma()
        +theta()
        +vega()
    }

    class DeltaNeutrality {
        <<trait>>
        +get_delta()
        +suggest_delta_adjustments()
    }

    Strategable --|> Strategies
    Strategable --|> StrategyConstructor
    Strategable --|> Profit
    Strategable --|> ProbabilityAnalysis
    Strategable --|> Greeks
    Strategable --|> DeltaNeutrality
    Strategable --|> PnLCalculator
    Strategies --|> Validable
    Strategies --|> Positionable
    Strategies --|> BreakEvenable
    Strategies --|> BasicAble
Loading

Metrics Framework

flowchart LR
    subgraph OptionChain
        OC[OptionChain]
    end

    subgraph Curves["Curve Metrics"]
        IV_C[IV Curve]
        RR_C[Risk Reversal]
        DG_C[Dollar Gamma]
        TH_C[Theta Curve]
        VA_C[Vanna Curve]
        SK_C[Skew Curve]
    end

    subgraph Surfaces["Surface Metrics"]
        IV_S[Horizon Volatility Surface]
        TH_S[Theta Surface]
        CH_S[Charm Surface]
        VS_S[Vol Sensitivity]
        TD_S[Time Decay]
    end

    OC --> Curves
    OC --> Surfaces
    Curves --> |"2D Analysis"| Analysis[Risk Analysis]
    Surfaces --> |"3D Analysis"| Analysis
Loading

Observability

Public hot paths are annotated with #[tracing::instrument]. Enable a subscriber in the consumer crate (the library itself never installs one) to surface structured spans:

flowchart LR
    APP[Consumer application] -- "installs" --> SUB["tracing_subscriber"]

    subgraph Spans["Instrumented public fns"]
        BS["pricing::black_scholes\n(strike, style, side)"]
        MC["pricing::monte_carlo_option_pricing\n(steps, simulations, strike, style, side)"]
        BI["pricing::price_binomial\n(strike, asset, steps, style, side)"]
        IV["volatility::implied_volatility\n(market_price, strike, max_iterations)"]
        OPT["Optimizable::get_best_ratio/area\n(side, criteria)"]
    end

    BS --> SUB
    MC --> SUB
    BI --> SUB
    IV --> SUB
    OPT --> SUB
Loading

Trading Strategies

OptionStratLib provides 25+ comprehensive trading strategies organized by complexity and market outlook:

Single Leg Strategies

Basic directional strategies for beginners:

  • Long Call: Bullish strategy with unlimited upside potential
  • Short Call: Bearish strategy collecting premium with limited profit
  • Long Put: Bearish strategy with high profit potential
  • Short Put: Bullish strategy collecting premium with assignment risk

Spread Strategies

Defined risk strategies with limited profit/loss:

  • Bull Call Spread: Moderately bullish with limited risk and reward
  • Bear Call Spread: Moderately bearish credit spread
  • Bull Put Spread: Moderately bullish credit spread
  • Bear Put Spread: Moderately bearish debit spread
  • Bull Call Ladder: A bull call spread with a second, higher short call; unlimited loss above the upper break-even

Butterfly Strategies

Market neutral strategies profiting from low volatility:

  • Long Butterfly Spread: Profits from price staying near middle strike
  • Short Butterfly Spread: Profits from price moving away from middle strike

Complex Multi-Leg Strategies

Advanced strategies for experienced traders:

  • Iron Condor: Market neutral strategy with wide profit zone
  • Iron Butterfly: Market neutral strategy with narrow profit zone

Volatility Strategies

Strategies that profit from volatility changes:

  • Long Straddle: Profits from high volatility in either direction
  • Short Straddle: Profits from low volatility (range-bound market)
  • Long Strangle: Similar to straddle but with different strikes
  • Short Strangle: Credit strategy profiting from low volatility

Income Generation Strategies

Strategies focused on generating regular income:

  • Covered Call: Stock/spot ownership with call selling for income (now with full spot leg support)
  • Poor Man's Covered Call: LEAPS-based covered call alternative

Protection Strategies

Risk management and hedging strategies:

  • Protective Put: Downside protection for stock positions
  • Collar: Combination of covered call and protective put

Custom Strategy Framework

  • Custom Strategy: Flexible framework for creating any multi-leg strategy
  • Supports unlimited number of legs
  • Full integration with all analysis tools
  • Complete trait implementation for consistency

Strategy Analysis Features

All strategies include comprehensive analysis capabilities:

  • Profit/Loss Analysis: P&L at any price point and time
  • Break-Even Points: Multiple break-even calculations
  • Greeks Analysis: Real-time risk metrics
  • Probability Analysis: Success probability calculations
  • Delta Neutrality: Delta-neutral position analysis
  • Visualization: Interactive payoff diagrams and risk profiles
  • Optimization: Find optimal strikes and expirations

Strategy Traits System

All strategies implement a comprehensive trait system:

  • Strategable: Master trait combining all strategy capabilities
  • BasicAble: Basic strategy information (symbol, price, etc.)
  • Positionable: Position management and modification
  • Strategies: Core strategy calculations (P&L, break-even, etc.)
  • Validable: Strategy validation and error checking
  • BreakEvenable: Break-even point calculations
  • Profit: Profit/loss analysis at various price points
  • Greeks: Greeks calculations for risk management
  • DeltaNeutrality: Delta-neutral analysis and adjustments
  • ProbabilityAnalysis: Outcome probability calculations
  • Graph: Visualization and plotting capabilities; a visualization trait every concrete strategy implements, not a supertrait of Strategable

Setup Instructions

Prerequisites

  • Rust 1.89 or higher (Rust 2024 edition): the rust-version every published crate declares, which the uuid and statrs versions they require need
  • Cargo package manager

Installation

Add OptionStratLib to your Cargo.toml:

[dependencies]
optionstratlib = "0.24.4"
rust_decimal = "1.43"

List rust_decimal as well when you write dec!, from the prelude or from rust_decimal_macros: the macro expands to ::rust_decimal paths, so the crate that writes it must depend on rust_decimal directly (#777).

Or use cargo to add it to your project:

cargo add optionstratlib

Optional Features

The library includes optional features for enhanced functionality:

[dependencies]
optionstratlib = { version = "0.24.4", features = ["plotly"] }
rust_decimal = "1.43"
  • math, pricing, market, analytics, strategies (default): the component crates of the same name and their facade paths (see Workspace Crates). Each implies the one below it, pricing alone resolves no market, I/O, async or visualization package, analytics alone resolves no strategy, simulation or plotting code, and strategies alone resolves no simulation, backtesting or plotting code
  • simulation (default): the optionstratlib-simulation crate (random walks, stochastic processes, simulators, exit policies, path statistics) and its facade paths; implies pricing but not market, and resolves no option chain, strategy, backtesting or plotting code
  • backtest (default): the optionstratlib-backtest crate (strategies evaluated over simulated paths, run statistics, reports and metrics) and its facade paths; implies strategies and simulation, and resolves no plotting code. Its progress is reported as tracing events
  • visualization (default): the optionstratlib-visualization crate (chart data, the Graph contract and its implementations for options, positions, curves, surfaces, simulations and strategies), GraphError and the unified error::Error; implies backtest and resolves no Plotly package
  • plotly: Enables interactive visualization using plotly.rs (implies visualization; forwards optionstratlib-visualization/plotly, which resolves no image-export or async package)
  • static_export: PNG / SVG export, drawn natively with Plotters since 0.23 (no browser, WebDriver, network or installed font; SVG text as glyph outlines of the bundled font; implies plotly; forwards optionstratlib-visualization/static_export). What the images are and how they differ from Plotly's: the visualization crate's README, section "Static export"
  • clock (default): the wall clock, see Building without the clock
  • io (default): CSV, JSON and ZIP file I/O for chains and OHLCV candles (OptionChain::save_to_csv and friends, read_ohlcv_from_zip, OhlcvError); default-features = false drops it, and csv and zip with it
  • async: asynchronous versions of that I/O (implies market and io; adds tokio through the market crate)
  • schema (default): utoipa::ToSchema derives on the domain types of every enabled component (forwards optionstratlib-core/schema and, weakly, schema of each other component, so it never adds a component). Additive: it changes no type. Without it no build resolves utoipa
  • synthetic (default): simulation-backed OptionChain and OptionSeries generators (chains::generator_optionchain, series::generator_optionseries, defined by optionstratlib-market behind its own synthetic feature), whose simulation failures arrive as ChainError::Generator; implies market and simulation. Leave it out (default-features = false, features = ["market"]) for a market surface that names no simulation type at all. make check-graph proves the gate holds and make check-feature-trees pins both dependency graphs
Feature routing

Every facade feature, with exactly what it switches on (ADR-0002 section 2). dep: names the optional component dependency, crate/feature a feature forwarded to a component, and "implies" another facade feature. Features only add: none replaces a type, and a type has the same defining crate whichever features are on.

Feature Enables Forwards Implies Default
math dep:optionstratlib-math via pricing
pricing dep:optionstratlib-pricing math yes
market dep:optionstratlib-market pricing yes
analytics dep:optionstratlib-analytics market yes
strategies dep:optionstratlib-strategies analytics yes
simulation dep:optionstratlib-simulation pricing yes
backtest dep:optionstratlib-backtest strategies, simulation yes
visualization dep:optionstratlib-visualization backtest yes
plotly optionstratlib-visualization/plotly visualization no
static_export optionstratlib-visualization/static_export plotly no
io optionstratlib-market/io market yes
async optionstratlib-market/async market, io no
synthetic optionstratlib-market/synthetic market, simulation yes
schema optionstratlib-core/schema, optionstratlib-{math,pricing,simulation,market,analytics,strategies,backtest}?/schema yes
clock optionstratlib-core/clock, optionstratlib-{pricing,simulation,market,analytics,strategies,backtest}?/clock yes

The facade depends on optionstratlib-core always and declares no other optional dependency: async adds tokio through the market crate, and plotly and static_export add their packages through the visualization crate. make check-graph fails when this routing drifts, when a lower capability enables visualization, plotly or static_export, or when any package other than the visualization crate declares Plotly, and make check-feature-trees pins the resolved graph of the facade with no capability, of each capability alone and of the default.

The default is every capability that 0.21 shipped without a feature flag: pricing, market, analytics, strategies, simulation, backtest, visualization, synthetic, io and schema, plus clock, which those capabilities always had before it was a feature, so a plain optionstratlib = "0.24.4" keeps the whole library and resolves no Plotly, image-export, async-runtime or HTTP package. The default is a compatibility contract since the 0.22 line, not a statement that a consumer needs all of it: opt out with default-features = false and name only the capability you use, or depend on a component crate directly.

Building without the clock

clock is on by default. With default-features = false and without naming it, no package in the build enables chrono/clock, so Utc::now does not exist and the build cannot read the wall clock, which simulators and replay engines need. make check-no-clock asserts this for every published crate and facade combination. What changes:

  • A Days expiration needs no time to price. Its time to expiry, price and Greeks come from its day count, to the same decimal in every build. It needs a time only where it must become a calendar date, and then the reference datetime is that time: build_chain and build_series, which store the date; expiration_date_string and resolve_expiration_date; and the P&L of a bare Options, which is stamped with the expiry. A Position counts from its own open date instead, so its P&L at expiration and a single-leg simulate need no reference at all.
  • A DateTime expiration needs a time for any time to expiry: the thread's reference datetime, from ExpirationDate::set_reference_datetime. Parallel sections carry the caller's reference to their workers. With none, a result that depends on that time to expiry and can report an error does, and is never computed from a made-up instant. The error is usually ExpirationDateError::InvalidDateTime inside the crate's error (for example PricingError::Options) or a Clock variant. A metric curve with no point left returns its construction error instead, and AdjustmentOptimizer::optimize carries the message in an AdjustmentError. What cannot report an error is listed below. A path that does not measure the expiry succeeds with no time: a search over quotes, a rebuild from positions, or a refresh from a chain row, whose record keeps the date it already carries.
  • The *_at forms take the time as an argument and exist in every build: Trade::new_at, Position::days_held_at, days_to_expiration_at and trade_at, OptionSeries::odte_at, OptionChain::get_random_positions_at, new_at on the strategies with a public new, SimulationResult::from_outcome_at, and on ExpirationDate itself get_years_at, get_days_at, get_date_at and cmp_at.
  • optionstratlib::utils::time has now(), which is ClockError without the clock, and years_to_expiration, days_to_expiration and expiration_date_string, the replacements for ExpirationDate::get_years, get_days and get_date_string, which exist only with the clock.
  • Constructors that return a Result stay and return an error: Trade::new and each strategy's new, as TradeError::Clock and StrategyError::Clock; PositionError, ChainError and TransactionError have the same variant. get_strategy works and keeps the date each leg already carries.
  • Filling a chain's Greeks reports a missing time: OptionChain::try_update_greeks and try_update_greek_snapshots fail on it, and delta_exposure and gamma_exposure fail on a strike with no Greek, where a build with the clock adds it as zero.
  • What cannot report an error needs the feature: the Default of Position, of the strategies, of TradeRecord and PnLMetrics; long and short on the spot, future and perpetual legs (new takes the date); create_sample_position, get_today_formatted and get_today_or_tomorrow_formatted; From<PathOutcome> for SimulationResult. So do the methods whose fallback would read as an answer: OptionSeries::odte (use odte_at), OptionChain::update_greeks and update_greek_snapshots (use the try_ forms), is_delta_neutral and meets_greek_targets (use delta and portfolio_greeks).
  • Without an error channel, three things still fall back, each logged and none a figure: Step::get_graph_x_in_days_left places a step at zero on a chart, the per-strike OptionData::calculate_delta and friends leave the Greek empty, and the chart of an option falls back to a default price range.
  • The two builds can differ on purpose in two places, both documented where they are defined: now_or stamps a record with the time of the call or, without the clock, the time it already carries; evaluation_time measures a DateTime from the wall clock or, without it, from the reference datetime. For the same answer in every build, use the *_at forms.

clock is not behaviourally additive. Features unify across a build: if any crate in the final graph enables it, this build has it, nothing fails to compile, and a replay engine driven by the reference datetime silently measures a DateTime from the wall clock instead. A consumer that depends on the reference should say so at startup: assert!(optionstratlib::utils::time::now().is_err()).

Combinations that do not exist. The implications are the whole dependency story, so some builds cannot be asked for:

  • There is no facade without optionstratlib-core.
  • There is no chart-only build: visualization implies backtest, because the visualization crate renders strategies and simulations, so it brings every capability below it.
  • There is no plotly or static_export without visualization. static_export no longer implies async as it did in 0.21 (#833): it resolves no io, csv or zip, and since 0.23 no async runtime either. Name async too for the market *_async wrappers.
  • There is no synthetic without simulation, no async without io, and no capability above pricing without pricing and math.
  • There is no sequential build: the parallel code paths use rayon unconditionally, and the reserved parallel feature is retired (#832).

The matrix CI runs: no features, default, all features, each capability alone (math, schema, pricing, market, io, async, synthetic, analytics, strategies, simulation, backtest, visualization) and the pairs market,simulation, analytics,simulation, strategies,simulation and market,synthetic, plus plotly and static_export as their own surfaces (make lint, make check-visualization).

Building from Source

Clone the repository and build using Cargo:

git clone https://github.com/joaquinbejar/OptionStratLib.git
cd OptionStratLib
cargo build --release

Run comprehensive test suite:

cargo test --all-features

Generate documentation:

cargo doc --open --all-features

Run benchmarks:

cargo bench

Library Usage

Basic Option Creation and Pricing

use optionstratlib::{Options, OptionStyle, OptionType, Side, ExpirationDate};
use positive::{pos_or_panic,Positive};
use rust_decimal_macros::dec;
use optionstratlib::greeks::Greeks;
use optionstratlib::pricing::OptionPricing;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a European call option
    let option = Options::new(
        OptionType::European,
        Side::Long,
        "AAPL".to_string(),
        pos_or_panic!(150.0),            // strike_price
        ExpirationDate::Days(pos_or_panic!(30.0)),
        pos_or_panic!(0.25),             // implied_volatility
        Positive::ONE,              // quantity
        pos_or_panic!(155.0),            // underlying_price
        dec!(0.05),             // risk_free_rate
        OptionStyle::Call,
        pos_or_panic!(0.02),             // dividend_yield
        None,                   // exotic_params
    );

    // Calculate option price using Black-Scholes
    let price = option.calculate_price_black_scholes()?;
    tracing::info!("Option price: ${:.2}", price);

    // Calculate Greeks for risk management. Every greek is the sensitivity
    // of the *position*: signed by its `Side` and scaled by its `quantity`,
    // so a short reports the negative of the equivalent long. `alpha` is the
    // one exception, being the ratio `gamma / theta`, which a short negates
    // in both terms.
    let delta = option.delta()?;
    let gamma = option.gamma()?;
    let theta = option.theta()?;
    let vega = option.vega()?;
    let vanna = option.vanna()?;
    let vomma = option.vomma()?;
    let veta = option.veta()?;
    let charm = option.charm()?;
    let color = option.color()?;
    tracing::info!("Greeks - Delta: {:.4}, Gamma: {:.4}, Theta: {:.4},
        Vega: {:.4}, Vanna: {:.4}, Vomma: {:.4}, Veta: {:.4}
        Charm: {:.4}, Color: {:.4}",
        delta, gamma, theta, vega, vanna, vomma, veta, charm, color);
    Ok(())
}

Working with Trading Strategies

use positive::{Positive, pos_or_panic};
use optionstratlib::ExpirationDate;
use optionstratlib::strategies::Strategies;
use optionstratlib::strategies::bull_call_spread::BullCallSpread;
use optionstratlib::strategies::base::{BreakEvenable, BasicAble};
use optionstratlib::visualization::Graph;
use rust_decimal_macros::dec;
use std::error::Error;

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

    // Create a Bull Call Spread strategy
    let strategy = BullCallSpread::new(
        "AAPL".to_string(),
        underlying_price,
        pos_or_panic!(95.0),   // long_strike
        pos_or_panic!(105.0),  // short_strike
        ExpirationDate::Days(pos_or_panic!(30.0)),
        pos_or_panic!(0.25),   // implied_volatility
        dec!(0.05),   // risk_free_rate
        pos_or_panic!(2.50),   // long_call_premium
        pos_or_panic!(2.50),   // long_call_open_fee
        pos_or_panic!(1.20),   // short_call_premium
        pos_or_panic!(1.20),   // short_call_close_fee
        Default::default(), Default::default(),
        Default::default(), Default::default()
    )?;

    // Analyze the strategy
    tracing::info!("Strategy: {}", strategy.get_title());
    tracing::info!("Break-even points: {:?}", strategy.get_break_even_points()?);
    tracing::info!("Max profit: ${:.2}", strategy.get_max_profit().unwrap_or(Positive::ZERO));
    tracing::info!("Max loss: ${:.2}", strategy.get_max_loss().unwrap_or(Positive::ZERO));
    tracing::info!("Net premium: ${:.2}", strategy.get_net_premium_received()?);

    // Calculate P&L at different price points
    let prices = vec![pos_or_panic!(90.0), pos_or_panic!(95.0), Positive::HUNDRED, pos_or_panic!(105.0), pos_or_panic!(110.0)];
    for price in prices {
        let pnl = strategy.get_point_at_price(&price)?;
        tracing::info!("P&L at ${}: ${:.2}", price, pnl.0);
    }

    // Generate visualization
    #[cfg(feature = "plotly")]
    {
        strategy.write_html("Draws/Visualization/bull_call_spread.html".as_ref())?;
    }

    Ok(())
}

Advanced Features: Volatility Analysis

use optionstratlib::prelude::*;
use optionstratlib::volatility::implied_volatility;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create an option for implied volatility calculation
    let mut option = Options::new(
        OptionType::European,
        Side::Long,
        "AAPL".to_string(),
        pos_or_panic!(105.0), // strike
        ExpirationDate::Days(pos_or_panic!(90.0)),
        pos_or_panic!(0.20), // initial IV guess
        Positive::ONE, // quantity
        Positive::HUNDRED, // underlying price
        dec!(0.05), // risk free rate
        OptionStyle::Call,
        pos_or_panic!(0.02), // dividend yield
        None,
    );

    let market_price = pos_or_panic!(5.50);
    let iv = implied_volatility(market_price, &mut option, 100)?;

    tracing::info!("Implied volatility: {:.2}%", iv.to_f64() * 100.0);
    Ok(())
}

Custom Strategy Creation

use optionstratlib::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Define common parameters
    let underlying_symbol = "DAX".to_string();
    let underlying_price = pos_or_panic!(24000.0);
    let expiration = ExpirationDate::Days(pos_or_panic!(30.0));
    let implied_volatility = pos_or_panic!(0.25);
    let risk_free_rate = dec!(0.05);
    let dividend_yield = pos_or_panic!(0.02);
    let fee = Positive::TWO;

    // Create a long put option
    let long_put_option = Options::new(
        OptionType::European,
        Side::Long,
        underlying_symbol.clone(),
        pos_or_panic!(24070.0), // strike
        expiration.clone(),
        implied_volatility,
        Positive::ONE, // quantity
        underlying_price,
        risk_free_rate,
        OptionStyle::Put,
        dividend_yield,
        None,
    );
    let long_put = Position::new(
        long_put_option,
        pos_or_panic!(150.0), // premium
        Utc::now(),
        fee,
        fee,
        None,
        None,
    );

    // Create a long call option
    let long_call_option = Options::new(
        OptionType::European,
        Side::Long,
        underlying_symbol.clone(),
        pos_or_panic!(24030.0), // strike
        expiration.clone(),
        implied_volatility,
        Positive::ONE, // quantity
        underlying_price,
        risk_free_rate,
        OptionStyle::Call,
        dividend_yield,
        None,
    );
    let long_call = Position::new(
        long_call_option,
        pos_or_panic!(120.0), // premium
        Utc::now(),
        fee,
        fee,
        None,
        None,
    );

    // Create CustomStrategy with the positions
    let positions = vec![long_call, long_put];
    let strategy = CustomStrategy::new(
        "DAX Straddle Strategy".to_string(),
        underlying_symbol,
        "A DAX long straddle strategy".to_string(),
        underlying_price,
        positions,
        Positive::ONE,
        30,
        implied_volatility,
    )?;

    tracing::info!("Strategy created: {}", strategy.get_title());
    Ok(())
}

Calibrating a Volatility Surface from Quotes

Every quote is either used or rejected with a reason, and passing the arbitrage report means only those checks, on that grid. Nothing here reads a clock: time runs from as_of.

use chrono::{TimeDelta, TimeZone, Utc};
use optionstratlib::pricing::black_scholes_model::black_scholes;
use optionstratlib::volatility::{
    ImpliedVolatilitySurface, ImpliedVolatilitySurfaceConfig, OptionQuote,
};
use optionstratlib::{ExpirationDate, OptionStyle, OptionType, Options, Side};
use positive::{Positive, pos_or_panic};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let as_of = Utc
        .with_ymd_and_hms(2030, 1, 1, 12, 0, 0)
        .single()
        .ok_or("a valid date")?;
    let (spot, rate) = (Positive::HUNDRED, dec!(0.03));

    // Illustrative quotes, 1% either side of a Black-Scholes price at a 20% volatility.
    let mut quotes = Vec::new();
    for days in [30_i64, 90, 180] {
        for strike in (85..=115).step_by(5) {
            let strike = Positive::new_decimal(Decimal::from(strike))?;
            let mid = black_scholes(&Options::new(
                OptionType::European,
                Side::Long,
                "XYZ".to_string(),
                strike,
                ExpirationDate::Days(Positive::new_decimal(Decimal::from(days))?),
                pos_or_panic!(0.2),
                Positive::ONE,
                spot,
                rate,
                OptionStyle::Call,
                Positive::ZERO,
                None,
            ))?;
            quotes.push(OptionQuote {
                expiry: as_of + TimeDelta::try_days(days).ok_or("days fit")?,
                strike,
                style: OptionStyle::Call,
                bid: Positive::new_decimal(mid * dec!(0.99))?,
                ask: Positive::new_decimal(mid * dec!(1.01))?,
                quoted_at: as_of,
            });
        }
    }

    let surface = ImpliedVolatilitySurface::calibrate(
        &quotes,
        spot,
        rate,
        Positive::ZERO,
        as_of,
        &ImpliedVolatilitySurfaceConfig::default(),
    )?;
    assert_eq!(surface.expiries().len(), 3);
    assert!(surface.rejections().is_empty() && surface.unfitted().is_empty());
    assert!(surface.arbitrage_report().passed());

    // A strike and an expiry inside the data, read between the first two slices.
    let expiry = as_of + TimeDelta::try_days(60).ok_or("days fit")?;
    let point = surface.point_at_strike(Positive::HUNDRED, expiry)?;
    assert!((point.implied_volatility.to_dec() - dec!(0.2)).abs() < dec!(0.001));
    Ok(())
}

calibrated_surface in examples_pricing also shows extrapolation, shocks and residuals.

Simulating the Quadratic Rough Heston Model

Every parameter below is illustrative, and none is calibrated to any underlying. One run does not show that a price has converged, even with a small standard error: compare seeds and step counts.

use optionstratlib::OptionStyle;
use optionstratlib::simulation::{
    QuadraticRoughHestonGrid, QuadraticRoughHestonKernel, QuadraticRoughHestonParams,
    QuadraticRoughHestonVix, QuadraticRoughHestonVixPayoff,
    price_vanilla_quadratic_rough_heston, price_vix_quadratic_rough_heston,
};
use positive::Positive;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::num::NonZeroUsize;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // `alpha`, the number of factors and the ratio of the partition.
    let kernel = QuadraticRoughHestonKernel::try_new(dec!(0.6), 5, dec!(5))?;
    // `a`, `b`, `c`, `lambda`, `eta`, the kernel, the initial factors and the spot.
    let params = QuadraticRoughHestonParams::try_new(
        dec!(0.5),
        dec!(0.1),
        dec!(0.01),
        dec!(1.5),
        dec!(1),
        kernel,
        vec![dec!(-0.05); 5],
        Positive::HUNDRED,
    )?;
    let grid = QuadraticRoughHestonGrid::try_new(dec!(0.25), 50)?;
    let estimate = price_vanilla_quadratic_rough_heston(
        &params,
        &grid,
        OptionStyle::Call,
        Positive::HUNDRED,
        NonZeroUsize::try_from(2_000_usize)?,
        958,
    )?;
    assert!(estimate.price() > Decimal::ZERO);

    // The VIX at the initial state, and the VIX future at the grid's horizon.
    let vix = QuadraticRoughHestonVix::thirty_days(&params)?;
    let today = vix.at(params.initial_factors())?;
    let future = price_vix_quadratic_rough_heston(
        &params,
        &grid,
        vix.window(),
        QuadraticRoughHestonVixPayoff::Future,
        NonZeroUsize::try_from(2_000_usize)?,
        958,
    )?;
    assert!(today > Positive::ZERO && future.price() > Decimal::ZERO);
    Ok(())
}

Settlement Obligations

settlement_obligation works out what expiry, exercise or assignment obliges a contract holder to pay and deliver, with no clock or ledger. settlement_obligations in examples_pricing covers cash and physical settlement on each side.

Testing

OptionStratLib ships with a large, fully deterministic test suite (3760 unit / integration tests + 205 doctests + property- and identity-based regressions):

Running Tests

Run all tests:

cargo test --all-features

Run tests for specific modules:

cargo test strategies::bull_call_spread
cargo test pricing::black_scholes
cargo test volatility::utils

Run tests with output:

cargo test -- --nocapture

Test Categories

  • Unit Tests: Individual function and method testing
  • Integration Tests: Cross-module functionality under tests/
  • Strategy Tests: Comprehensive strategy validation
  • Pricing Model Tests: Accuracy and performance testing
  • Greeks Tests: Mathematical precision validation
  • Visualization Tests: Chart generation and export testing
  • Property-Based Tests: Mathematical invariant testing with proptest (crates/optionstratlib-pricing/tests/put_call_parity_test.rs, greeks_bounds_test.rs)
  • Identity Regression Tests: crates/optionstratlib-pricing/tests/identities_test.rs locks put-call parity, CRR → Black-Scholes convergence, and Greek sanity (Γ_c = Γ_p, Vega_c = Vega_p, Δ_c − Δ_p ≈ e^{-qT}).
  • Deterministic Monte-Carlo Tests: Seeded via [utils::deterministic_rng] so arithmetic-precision shifts can't flip assertions.
  • Exotic Options Tests: Coverage for the exotic option types.

Benchmarking

Run performance benchmarks:

cargo bench

Generate test coverage report:

cargo tarpaulin --all-features --out Html

Examples

Examples live in self-contained sub-crates under examples/, each with its own Cargo.toml:

  • examples_strategies/: Strategy demos
  • examples_strategies_best/: Optimizer entry points (get_best_area / get_best_ratio) per strategy
  • examples_strategies_delta/: Delta-neutrality workflows
  • examples_chain/: Option chain construction, import/export, and async I/O
  • examples_curves/: Greek curves (charm, color, d1, d2, delta, gamma, rho, theta, …) and vector curves
  • examples_surfaces/: 3-D volatility surfaces
  • examples_metrics/: Price / risk / liquidity / stress / temporal / composite metric curves and surfaces
  • examples_volatility/: Implied-volatility solver walkthroughs
  • examples_simulation/: Monte-Carlo random-walk demos for LongCall, ShortPut, position / strategy simulators, random-walk-of-chain, the quadratic rough Heston model, its VIX and its calibration
  • examples_pricing/: Black-76, Garman-Kohlhagen, SVI calibration, static arbitrage checks, a calibrated surface, and settlement obligations
  • examples_exotics/: Exotic option pricing (barrier, cliquet, …)
  • examples_visualization/: Interactive chart wiring
  • direct/: One crate per component, using it without the facade

Run any binary with the usual cargo invocation (from the repo root, so relative data-fixture paths resolve correctly):

cargo run --manifest-path=examples/examples_strategies/Cargo.toml \
    --bin strategy_bull_call_spread
cargo run --manifest-path=examples/examples_simulation/Cargo.toml \
    --bin long_call_strategy_simulation --features plotly
cargo run --manifest-path=examples/examples_metrics/Cargo.toml \
    --bin horizon_volatility_surface

Simulation-heavy demos (*_strategy_simulation, position_simulator, strategy_simulator, random_walk_chain) use a demo-friendly hourly grid so cargo run finishes in a few seconds in debug mode; bump n_steps / n_simulations inside the binary if you want a finer sample.

Upgrading from 0.23

0.24 makes the wall clock optional, renames iv_surface to say what it computes, and adds a calibrated implied volatility surface, SVI slices, static arbitrage checks and settlement obligations. A build that sets default-features = false must add the clock feature to keep reading the time. The 0.24 migration guide lists what to change, and the 0.24 release notes summarise the release.

Upgrading from 0.22

0.23 changes one area: PNG and SVG export under static_export are drawn natively, with no browser, WebDriver or installed font, and GraphError is #[non_exhaustive] with typed variants for data the export will not repair. Every other API and result is unchanged. The 0.23 migration guide lists what to change, and the 0.23 release notes summarise the release.

Upgrading from 0.21

0.22 splits the library into nine component crates behind this facade and redesigns several APIs without keeping compatibility with 0.21. The 0.22 architecture and adoption guide explains the layers, facade versus component use, feature selection, and the replacement workflow for each redesign, including serialized data that no longer reads the same way.

Contribution and Contact

We welcome contributions to this project! If you would like to contribute, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Find the crate that owns what you are changing in the public concept ownership map: put new code in the lowest layer that can own it, export it from its module's flat path, and update the map with the code (make check-graph checks both the layering and the map).
  4. Make your changes and ensure that the project still builds and all tests pass.
  5. Commit your changes and push your branch to your forked repository.
  6. Submit a pull request to the main repository.

To build and test from source:

git clone https://github.com/joaquinbejar/OptionStratLib.git
cd OptionStratLib
cargo build --all-features
cargo test --all-features

Code must pass cargo clippy without warnings and be formatted with cargo fmt. Add tests for new functionality, and update the documentation when the API changes.

If you have any questions, issues, or would like to provide feedback, please feel free to contact the project maintainer:

Contact Information

We appreciate your interest and look forward to your contributions!

✍️ License

Licensed under MIT license

Related projects

Repositories by the same author that this project depends on, and repositories that depend on it.

Depends on

Repository Description
ExpirationDate Expiration date type and time-to-expiry calculations for every option.
financial_types Shared enums such as side, option style and asset class used across the API.
option_type Classification of vanilla and exotic option contracts used by the pricing models.
positive The Positive decimal type used for prices, strikes, volatilities and quantities.

Used by

Repository Description
ChainView Terminal UI for option chains, Greeks and volatility; uses OptionStratLib for pricing and Greeks.
fauxchange Exchange-in-a-box options venue simulator; uses OptionStratLib for pricing, Greeks and price walks.
IronCondor Options backtesting engine; uses OptionStratLib for strategy definitions and pricing.
market-maker-rs Quantitative market making library; uses OptionStratLib for options pricing and Greeks.
metalbook Cash-settled gold and silver options on Solana; uses OptionStratLib as the off-chain pricing reference.
optfix Fixed-point option pricing for Solana programs; uses OptionStratLib as its floating-point reference.
Option-Chain-OrderBook Option chain order book infrastructure; uses OptionStratLib for option contract types and pricing.
Option-Chain-OrderBook-Backend REST and WebSocket backend for option-chain-orderbook; uses OptionStratLib for option types and pricing.
OptionChain-Simulator RESTful option chain simulator; uses OptionStratLib to price and evolve the chains.

Releases

Packages

Used by

Contributors

Languages