use optionstratlib::ExpirationDate;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::domain::{PriceCents, Quantity, Underlying};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum StrategySpec {
IronCondor(IronCondorSpec),
ShortStrangle(ShortStrangleSpec),
}
impl StrategySpec {
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::IronCondor(_) => "iron_condor",
Self::ShortStrangle(_) => "short_strangle",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IronCondorSpec {
pub underlying: Underlying,
pub underlying_price: PriceCents,
pub short_call_strike: PriceCents,
pub short_put_strike: PriceCents,
pub long_call_strike: PriceCents,
pub long_put_strike: PriceCents,
pub expiration: ExpirationDate,
pub implied_volatility: Decimal,
pub risk_free_rate: Decimal,
pub dividend_yield: Decimal,
pub quantity: Quantity,
pub premium_short_call: PriceCents,
pub premium_short_put: PriceCents,
pub premium_long_call: PriceCents,
pub premium_long_put: PriceCents,
pub open_fee: PriceCents,
pub close_fee: PriceCents,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShortStrangleSpec {
pub underlying: Underlying,
pub underlying_price: PriceCents,
pub call_strike: PriceCents,
pub put_strike: PriceCents,
pub expiration: ExpirationDate,
pub call_implied_volatility: Decimal,
pub put_implied_volatility: Decimal,
pub risk_free_rate: Decimal,
pub dividend_yield: Decimal,
pub quantity: Quantity,
pub premium_short_call: PriceCents,
pub premium_short_put: PriceCents,
pub open_fee_short_call: PriceCents,
pub close_fee_short_call: PriceCents,
pub open_fee_short_put: PriceCents,
pub close_fee_short_put: PriceCents,
}
#[cfg(test)]
mod tests {
use chrono::DateTime;
use optionstratlib::ExpirationDate;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use super::{IronCondorSpec, ShortStrangleSpec, StrategySpec};
use crate::domain::{PriceCents, Quantity, Underlying};
fn spec() -> StrategySpec {
let Ok(underlying) = Underlying::new("SPX") else {
panic!("SPX is a valid underlying");
};
let Ok(quantity) = Quantity::new(1) else {
panic!("1 is a valid quantity");
};
StrategySpec::IronCondor(IronCondorSpec {
underlying,
underlying_price: PriceCents::new(500_000),
short_call_strike: PriceCents::new(510_000),
short_put_strike: PriceCents::new(490_000),
long_call_strike: PriceCents::new(520_000),
long_put_strike: PriceCents::new(480_000),
expiration: ExpirationDate::DateTime(DateTime::from_timestamp_nanos(
1_750_291_200_000_000_000,
)),
implied_volatility: dec!(0.20),
risk_free_rate: dec!(0.05),
dividend_yield: Decimal::ZERO,
quantity,
premium_short_call: PriceCents::new(2_000),
premium_short_put: PriceCents::new(1_800),
premium_long_call: PriceCents::new(800),
premium_long_put: PriceCents::new(700),
open_fee: PriceCents::new(65),
close_fee: PriceCents::new(65),
})
}
fn strangle_spec() -> StrategySpec {
let Ok(underlying) = Underlying::new("SPX") else {
panic!("SPX is a valid underlying");
};
let Ok(quantity) = Quantity::new(1) else {
panic!("1 is a valid quantity");
};
StrategySpec::ShortStrangle(ShortStrangleSpec {
underlying,
underlying_price: PriceCents::new(500_000),
call_strike: PriceCents::new(520_000),
put_strike: PriceCents::new(480_000),
expiration: ExpirationDate::DateTime(DateTime::from_timestamp_nanos(
1_750_291_200_000_000_000,
)),
call_implied_volatility: dec!(0.20),
put_implied_volatility: dec!(0.20),
risk_free_rate: dec!(0.05),
dividend_yield: Decimal::ZERO,
quantity,
premium_short_call: PriceCents::new(800),
premium_short_put: PriceCents::new(700),
open_fee_short_call: PriceCents::new(65),
close_fee_short_call: PriceCents::new(65),
open_fee_short_put: PriceCents::new(65),
close_fee_short_put: PriceCents::new(65),
})
}
#[test]
fn test_strategy_spec_kind_is_iron_condor() {
assert_eq!(spec().kind(), "iron_condor");
}
#[test]
fn test_strategy_spec_kind_is_short_strangle() {
assert_eq!(strangle_spec().kind(), "short_strangle");
}
#[test]
fn test_strategy_spec_serde_round_trips() {
let original = spec();
let Ok(json) = serde_json::to_string(&original) else {
panic!("strategy spec serialises");
};
let Ok(decoded) = serde_json::from_str::<StrategySpec>(&json) else {
panic!("strategy spec deserialises");
};
assert_eq!(original, decoded);
}
#[test]
fn test_short_strangle_spec_serde_round_trips() {
let original = strangle_spec();
let Ok(json) = serde_json::to_string(&original) else {
panic!("short strangle spec serialises");
};
let Ok(decoded) = serde_json::from_str::<StrategySpec>(&json) else {
panic!("short strangle spec deserialises");
};
assert_eq!(original, decoded);
}
}