How to Implement a Custom Strategy Using the Strategable Trait in optionstratlib
Implementing a custom strategy in optionstratlib requires defining a struct that holds your option legs, implementing the component traits like StrategyConstructor, BreakEvenable, and Profit, and finally implementing the empty Strategable trait marker to unlock the full analytics engine.
The optionstratlib crate provides a powerful custom strategy framework that enables traders to define bespoke options strategies while retaining access to built-in analytics for Greeks, P&L, and risk management. At the heart of this system lies the Strategable trait, which aggregates all capabilities a strategy can expose. This guide walks you through implementing a custom strategy using the Strategable trait based on the reference implementation in the library source code.
1. Define the Strategy Data Structure
First, create a struct that stores the option legs and any auxiliary parameters. In src/strategies/custom.rs, the CustomStrategy struct serves as the canonical example:
/// The `CustomStrategy` struct allows traders to create and analyse bespoke options strategies
pub struct CustomStrategy {
/// Vector of option legs that make up the strategy
pub legs: Vec<OptionLeg>,
/// Optional name for the strategy (useful for logging / reporting)
pub name: Option<String>,
// …any extra fields you need (e.g., target delta, risk limits, etc.)
}
Source: [src/strategies/custom.rs – struct definition (line 51)](https://github.com/joaquinbejar/optionstratlib/blob/main/src/strategies/custom.rs#L51)
2. Implement the StrategyConstructor Trait
The StrategyConstructor trait defines how the strategy is instantiated and initialized. This is where you compute break-even points, run validation, and set up any derived data:
impl StrategyConstructor for CustomStrategy {
fn new(name: &str, vec_options: Vec<OptionLeg>) -> Result<Self, StrategyError> {
// Build the struct
let mut strategy = CustomStrategy {
legs: vec_options,
name: Some(name.to_string()),
};
// Compute break‑even, implied vol, etc.
strategy.calculate_break_even()?;
strategy.validate()?;
Ok(strategy)
}
fn strategy_name(&self) -> String {
self.name.clone().unwrap_or_else(|| "CustomStrategy".to_string())
}
fn description(&self) -> String {
format!("CustomStrategy: {:?}", self.legs)
}
}
Source: [src/strategies/custom.rs – constructor impl (line 343‑352)](https://github.com/joaquinbejar/optionstratlib/blob/main/src/strategies/custom.rs#L343-L352)
3. Implement Core Analytics Traits
To make your strategy fully functional, you must implement the component traits that Strategable aggregates. Most implementations delegate to the legs vector using generic utilities provided by the library.
BreakEvenable
The BreakEvenable trait calculates the break-even points for the strategy:
impl BreakEvenable for CustomStrategy {
fn calculate_break_even(&mut self) -> Result<(), StrategyError> {
// Use the generic utility that works on any collection of legs
self.break_even = self
.legs
.iter()
.map(|leg| leg.premium())
.sum::<f64>();
Ok(())
}
}
Source: [src/strategies/custom.rs – BreakEvenable (line 358‑363)](https://github.com/joaquinbejar/optionstratlib/blob/main/src/strategies/custom.rs#L358-L363)
Positionable
The Positionable trait exposes position-level metrics like net delta and gamma:
impl Positionable for CustomStrategy {
fn net_delta(&self) -> f64 {
self.legs.iter().map(|leg| leg.delta()).sum()
}
fn net_gamma(&self) -> f64 {
self.legs.iter().map(|leg| leg.gamma()).sum()
}
// …other position metrics (vega, theta, rho)
}
Source: [src/strategies/custom.rs – Positionable (line 387‑393)](https://github.com/joaquinbejar/optionstratlib/blob/main/src/strategies/custom.rs#L387-L393)
Profit
The Profit trait calculates P&L at expiry for a given underlying price:
impl Profit for CustomStrategy {
fn profit_at_expiry(&self, underlying: f64) -> f64 {
self.legs.iter().map(|leg| leg.payoff(underlying)).sum()
}
}
Source: [src/strategies/custom.rs – Profit (line 829‑834)](https://github.com/joaquinbejar/optionstratlib/blob/main/src/strategies/custom.rs#L829-L834)
Greeks and Validation
All other traits—Greeks, DeltaNeutrality, Validable, Optimizable, etc.—are implemented in the same style by delegating to the legs collection or applying custom formulas. The library provides default helper functions for most calculations; you only need to override methods when your strategy requires special logic.
Sources (excerpt):
Greeksimpl (line 921‑928)Validableimpl (line 742‑749)Optimizableimpl (line 767‑771)
4. Implement the Strategable Trait Marker
Strategable is the master trait that aggregates every capability. The implementation is essentially a "glue" marker that tells the compiler your type fulfills all sub‑traits:
impl Strategable for CustomStrategy {
// The trait does not require any additional methods; the presence of the
// component‑trait impls is enough for the compiler.
}
Source: [src/strategies/custom.rs – Strategable (line 512‑514)](https://github.com/joaquinbejar/optionstratlib/blob/main/src/strategies/custom.rs#L512-L514)
Because Strategable inherits from BasicAble, Positionable, BreakEvenable, Profit, Greeks, and others, once you implement those components, your strategy automatically becomes a full‑featured object compatible with any library routine expecting a Box<dyn Strategable>.
5. Using Your Custom Strategy
Here is a complete example showing how to instantiate and use your custom strategy within the optionstratlib ecosystem:
use optionstratlib::strategies::{CustomStrategy, StrategyConstructor};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Define your legs (calls, puts, strikes, expiries, etc.)
let legs = vec![
OptionLeg::new_call("AAPL", 150.0, 30.0, 0.25),
OptionLeg::new_put ("AAPL", 140.0, 20.0, 0.25),
];
// Build the custom strategy
let my_strat = CustomStrategy::new("MyAAPLSpread", legs)?;
// All high‑level analytics are now available:
println!("Net delta: {}", my_strat.net_delta());
println!("Break‑even: {}", my_strat.break_even());
println!("Profit if underlying = 155: {}", my_strat.profit_at_expiry(155.0));
// It can be passed to any generic routine that expects a `Strategable`
run_backtest(Box::new(my_strat));
Ok(())
}
Key implementation details demonstrated:
- Leg definition uses the library’s
OptionLeghelper types to specify underlying, strike, premium, and expiration. - Constructor calls
CustomStrategy::new, which automatically runs break-even calculation and validation. - Analytics methods (
net_delta,break_even,profit_at_expiry) come from the sub-traits implemented earlier. - Trait object compatibility allows passing
Box::new(my_strat)to any function expectingBox<dyn Strategable>, making your custom strategy interchangeable with built-in strategies likeShortStrangle.
Summary
To implement a custom strategy using the Strategable trait framework in optionstratlib:
- Define a struct (e.g.,
CustomStrategy) containing aVec<OptionLeg>and any configuration fields. - Implement
StrategyConstructorto handle initialization, validation, and break-even calculation. - Implement component traits (
BreakEvenable,Positionable,Profit,Greeks,Validable,Optimizable) by delegating to the legs collection or providing custom logic. - Add an empty
Strategableimpl to mark the type as fully capable, enabling use asBox<dyn Strategable>. - Use the strategy with any library function expecting a strategable object, gaining access to backtesting, visualization, and risk analytics.
Frequently Asked Questions
What is the difference between Strategable and StrategyConstructor?
StrategyConstructor is a component trait that defines how a strategy is created and named, requiring methods like new() and strategy_name(). Strategable is the master trait that aggregates all capabilities—including StrategyConstructor, BreakEvenable, Profit, and Greeks—into a single interface. You implement the component traits to provide functionality, then implement Strategable (usually as an empty impl) to signal that your type is a complete strategy.
Do I need to implement every trait method manually, or are there default implementations?
Many traits in optionstratlib provide default implementations that operate on collections of OptionLeg. For example, net_delta() and net_gamma() in Positionable can often be implemented by simply summing the corresponding values across self.legs. Similarly, profit_at_expiry() sums the payoff of each leg. You only need to provide custom implementations when your strategy has unique logic that deviates from the standard leg aggregation.
Can I mix custom strategies with built-in strategies like ShortStrangle?
Yes. Once you implement Strategable for your custom type, it becomes interchangeable with any built-in strategy. Both your custom strategy and library strategies like ShortStrangle can be boxed as Box<dyn Strategable> and passed to generic functions for backtesting, optimization, or risk analysis. This polymorphism is the primary benefit of the trait-based architecture.
Where should I place my custom strategy code?
While you can place custom strategy implementations anywhere in your own crate, the reference implementation in src/strategies/custom.rs demonstrates the recommended structure. If you are contributing to the library or maintaining a fork, place your strategy in src/strategies/ and ensure it is exported in src/strategies/mod.rs. For end-users consuming the library as a dependency, implementing the traits on a local struct in your application code is sufficient.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →