use crate::error::strategies::StrategyError;
use crate::model::position::Position;
use positive::Positive;
use rust_decimal::Decimal;
pub trait SpreadStrategy {
fn lower_strike(&self) -> Positive;
fn upper_strike(&self) -> Positive;
fn spread_width(&self) -> Positive {
self.upper_strike() - self.lower_strike()
}
fn short_leg(&self) -> &Position;
fn long_leg(&self) -> &Position;
}
pub trait ButterflyStrategy {
fn wing_strikes(&self) -> (Positive, Positive);
fn body_strike(&self) -> Positive;
fn wing_width(&self) -> Positive {
let (lower, upper) = self.wing_strikes();
(upper - lower) / Decimal::TWO
}
fn get_butterfly_positions(&self) -> Vec<&Position>;
}
pub trait CondorStrategy {
fn strikes(&self) -> (Positive, Positive, Positive, Positive);
fn inner_width(&self) -> Positive {
let (_, lower_mid, upper_mid, _) = self.strikes();
upper_mid - lower_mid
}
fn outer_width(&self) -> Positive {
let (lowest, _, _, highest) = self.strikes();
highest - lowest
}
fn put_spread_width(&self) -> Positive {
let (lowest, lower_mid, _, _) = self.strikes();
lower_mid - lowest
}
fn call_spread_width(&self) -> Positive {
let (_, _, upper_mid, highest) = self.strikes();
highest - upper_mid
}
fn get_condor_positions(&self) -> Vec<&Position>;
}
pub trait StraddleStrategy {
fn strike(&self) -> Positive;
fn call_position(&self) -> &Position;
fn put_position(&self) -> &Position;
fn is_long(&self) -> bool;
}
pub trait StrangleStrategy {
fn call_strike(&self) -> Positive;
fn put_strike(&self) -> Positive;
fn strangle_width(&self) -> Positive {
self.call_strike() - self.put_strike()
}
fn call_position(&self) -> &Position;
fn put_position(&self) -> &Position;
fn is_long(&self) -> bool;
}
pub fn credit_spread_break_even(
short_strike: Positive,
net_credit: Positive,
is_call_spread: bool,
) -> Positive {
if is_call_spread {
short_strike + net_credit
} else {
short_strike - net_credit
}
}
pub fn debit_spread_break_even(
long_strike: Positive,
net_debit: Positive,
is_call_spread: bool,
) -> Positive {
if is_call_spread {
long_strike + net_debit
} else {
long_strike - net_debit
}
}
pub fn calculate_profit_ratio(
max_profit: Positive,
max_loss: Positive,
) -> Result<Decimal, StrategyError> {
if max_loss == Positive::ZERO {
return Ok(Decimal::MAX);
}
if max_profit == Positive::ZERO {
return Ok(Decimal::ZERO);
}
Ok(max_profit.to_dec() / max_loss.to_dec() * Decimal::ONE_HUNDRED)
}
pub fn aggregate_fees(positions: &[&Position]) -> Positive {
positions
.iter()
.map(|p| p.open_fee + p.close_fee)
.fold(Positive::ZERO, |acc, fee| acc + fee)
}
pub fn aggregate_premiums(positions: &[&Position]) -> Positive {
positions
.iter()
.map(|p| p.premium)
.fold(Positive::ZERO, |acc, premium| acc + premium)
}
#[cfg(test)]
mod tests_shared {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_credit_spread_break_even_call() {
let short_strike = Positive::new(100.0).unwrap();
let net_credit = Positive::new(5.0).unwrap();
let break_even = credit_spread_break_even(short_strike, net_credit, true);
assert_eq!(break_even, Positive::new(105.0).unwrap());
}
#[test]
fn test_credit_spread_break_even_put() {
let short_strike = Positive::new(100.0).unwrap();
let net_credit = Positive::new(5.0).unwrap();
let break_even = credit_spread_break_even(short_strike, net_credit, false);
assert_eq!(break_even, Positive::new(95.0).unwrap());
}
#[test]
fn test_debit_spread_break_even_call() {
let long_strike = Positive::new(100.0).unwrap();
let net_debit = Positive::new(3.0).unwrap();
let break_even = debit_spread_break_even(long_strike, net_debit, true);
assert_eq!(break_even, Positive::new(103.0).unwrap());
}
#[test]
fn test_debit_spread_break_even_put() {
let long_strike = Positive::new(100.0).unwrap();
let net_debit = Positive::new(3.0).unwrap();
let break_even = debit_spread_break_even(long_strike, net_debit, false);
assert_eq!(break_even, Positive::new(97.0).unwrap());
}
#[test]
fn test_calculate_profit_ratio() {
let max_profit = Positive::new(50.0).unwrap();
let max_loss = Positive::new(100.0).unwrap();
let ratio = calculate_profit_ratio(max_profit, max_loss).unwrap();
assert_eq!(ratio, dec!(50));
}
#[test]
fn test_calculate_profit_ratio_zero_loss() {
let max_profit = Positive::new(50.0).unwrap();
let max_loss = Positive::ZERO;
let ratio = calculate_profit_ratio(max_profit, max_loss).unwrap();
assert_eq!(ratio, Decimal::MAX);
}
#[test]
fn test_calculate_profit_ratio_zero_profit() {
let max_profit = Positive::ZERO;
let max_loss = Positive::new(100.0).unwrap();
let ratio = calculate_profit_ratio(max_profit, max_loss).unwrap();
assert_eq!(ratio, Decimal::ZERO);
}
}