/******************************************************************************
Author: Joaquín Béjar García
Email: jb@taunais.com
Date: 20/8/24
******************************************************************************/
use crate::model::option::ExoticParams;
use crate::model::types::{AsianAveragingType, BarrierType, BinaryType, LookbackType};
use crate::model::{ExpirationDate, OptionType, Options, Position};
use crate::strategies::base::Strategy;
use crate::{OptionStyle, Side};
use chrono::{Duration, Utc};
use rust_decimal_macros::dec;
use std::fmt;
impl fmt::Display for Options {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"{} {} {}",
self.side, self.option_style, self.option_type
)?;
writeln!(
f,
"Underlying: {} @ ${:.2}",
self.underlying_symbol, self.underlying_price
)?;
writeln!(f, "Strike: ${:.2}", self.strike_price)?;
writeln!(f, "Expiration: {}", self.expiration_date)?;
writeln!(
f,
"Implied Volatility: {:.2}%",
self.implied_volatility * 100.0
)?;
writeln!(f, "Quantity: {}", self.quantity)?;
writeln!(
f,
"Risk-free Rate: {:.2}%",
self.risk_free_rate * dec!(100.0)
)?;
write!(f, "Dividend Yield: {:.2}%", self.dividend_yield * 100.0)?;
if let Some(exotic) = &self.exotic_params {
write!(f, "\nExotic Parameters: {exotic:?}")?;
}
Ok(())
}
}
impl fmt::Debug for Options {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Options")
.field("option_type", &self.option_type)
.field("side", &self.side)
.field("underlying_symbol", &self.underlying_symbol)
.field("strike_price", &self.strike_price)
.field("expiration_date", &self.expiration_date)
.field("implied_volatility", &self.implied_volatility)
.field("quantity", &self.quantity)
.field("underlying_price", &self.underlying_price)
.field("risk_free_rate", &self.risk_free_rate)
.field("option_style", &self.option_style)
.field("dividend_yield", &self.dividend_yield)
.field("exotic_params", &self.exotic_params)
.finish()
}
}
impl fmt::Display for ExoticParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fields = vec![];
if let Some(ref prices) = self.spot_prices {
fields.push(format!("Spot Prices: {prices:?}"));
}
if let Some(min) = self.spot_min {
fields.push(format!("Spot Min: {min:.2}"));
}
if let Some(max) = self.spot_max {
fields.push(format!("Spot Max: {max:.2}"));
}
write!(f, "{}", fields.join(", "))
}
}
impl fmt::Display for ExpirationDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExpirationDate::Days(days) => {
// Use the stored reference datetime if available, otherwise use get_date_with_options
if let Some(ref_dt) = ExpirationDate::get_reference_datetime() {
// Calculate the expiration date using the reference datetime
let expiration_date = ref_dt + Duration::days((*days).to_i64());
write!(f, "{}", expiration_date.format("%Y-%m-%d %H:%M:%S UTC"))
} else if let Ok(date) = self.get_date_with_options(false) {
// Use the date from get_date_with_options with current time
write!(f, "{}", date.format("%Y-%m-%d %H:%M:%S UTC"))
} else {
// Fallback if get_date_with_options fails
let duration = Duration::days((*days).to_i64());
let expiration = Utc::now() + duration;
write!(f, "{}", expiration.format("%Y-%m-%d %H:%M:%S UTC"))
}
}
ExpirationDate::DateTime(date_time) => {
write!(f, "{}", date_time.format("%Y-%m-%d %H:%M:%S UTC"))
}
}
}
}
impl fmt::Debug for ExpirationDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExpirationDate::Days(days) => write!(f, "ExpirationDate::Days({days:.2})"),
ExpirationDate::DateTime(date_time) => {
write!(f, "ExpirationDate::DateTime({date_time})")
}
}
}
}
impl fmt::Display for Side {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Side::Long => write!(f, "Long"),
Side::Short => write!(f, "Short"),
}
}
}
impl fmt::Debug for Side {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Side::Long => write!(f, "Side::Long"),
Side::Short => write!(f, "Side::Short"),
}
}
}
impl fmt::Display for OptionStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OptionStyle::Call => write!(f, "Call"),
OptionStyle::Put => write!(f, "Put"),
}
}
}
impl fmt::Debug for OptionStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OptionStyle::Call => write!(f, "OptionStyle::Call"),
OptionStyle::Put => write!(f, "OptionStyle::Put"),
}
}
}
impl fmt::Display for OptionType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
OptionType::European => write!(f, "European Option"),
OptionType::American => write!(f, "American Option"),
OptionType::Bermuda { exercise_dates } => {
write!(f, "Bermuda Option (Exercise Dates: {exercise_dates:?})")
}
OptionType::Asian { averaging_type } => {
write!(f, "Asian Option (Averaging Type: {averaging_type})")
}
OptionType::Barrier {
barrier_type,
barrier_level,
} => write!(
f,
"Barrier Option (Type: {barrier_type}, Level: {barrier_level})"
),
OptionType::Binary { binary_type } => {
write!(f, "Binary Option (Type: {binary_type})")
}
OptionType::Lookback { lookback_type } => {
write!(f, "Lookback Option (Type: {lookback_type})")
}
OptionType::Compound { underlying_option } => {
write!(f, "Compound Option (Underlying: {underlying_option})")
}
OptionType::Chooser { choice_date } => {
write!(f, "Chooser Option (Choice Date: {choice_date})")
}
OptionType::Cliquet { reset_dates } => {
write!(f, "Cliquet Option (Reset Dates: {reset_dates:?})")
}
OptionType::Rainbow { num_assets } => {
write!(f, "Rainbow Option (Number of Assets: {num_assets})")
}
OptionType::Spread { second_asset } => {
write!(f, "Spread Option (Second Asset: {second_asset})")
}
OptionType::Quanto { exchange_rate } => {
write!(f, "Quanto Option (Exchange Rate: {exchange_rate})")
}
OptionType::Exchange { second_asset } => {
write!(f, "Exchange Option (Second Asset: {second_asset})")
}
OptionType::Power { exponent } => write!(f, "Power Option (Exponent: {exponent})"),
}
}
}
impl fmt::Display for AsianAveragingType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AsianAveragingType::Arithmetic => write!(f, "Arithmetic Averaging"),
AsianAveragingType::Geometric => write!(f, "Geometric Averaging"),
}
}
}
impl fmt::Display for BarrierType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BarrierType::UpAndIn => write!(f, "Up-And-In Barrier"),
BarrierType::UpAndOut => write!(f, "Up-And-Out Barrier"),
BarrierType::DownAndIn => write!(f, "Down-And-In Barrier"),
BarrierType::DownAndOut => write!(f, "Down-And-Out Barrier"),
}
}
}
impl fmt::Display for BinaryType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BinaryType::CashOrNothing => write!(f, "Cash-Or-Nothing Binary Option"),
BinaryType::AssetOrNothing => write!(f, "Asset-Or-Nothing Binary Option"),
BinaryType::Gap => write!(f, "Gap Binary Option"),
}
}
}
impl fmt::Display for LookbackType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LookbackType::FixedStrike => write!(f, "Fixed-Strike Lookback Option"),
LookbackType::FloatingStrike => write!(f, "Floating-Strike Lookback Option"),
}
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Position Details:")?;
writeln!(f, "Option: {}", self.option)?;
writeln!(f, "Premium per contract: ${:.2}", self.premium)?;
writeln!(f, "Date: {}", self.date)?;
writeln!(f, "Open Fee per contract: ${:.2}", self.open_fee)?;
write!(f, "Close Fee per contract: ${:.2}", self.close_fee)
}
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Position")
.field("option", &self.option)
.field("premium", &self.premium)
.field("date", &self.date)
.field("open_fee", &self.open_fee)
.field("close_fee", &self.close_fee)
.finish()
}
}
impl fmt::Display for Strategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Strategy: {}", self.name)?;
writeln!(f, "Type: {:?}", self.kind)?;
writeln!(f, "Description: {}", self.description)?;
writeln!(f, "Legs:")?;
for leg in &self.legs {
writeln!(f, " {leg}")?;
}
if let Some(max_profit) = self.max_profit {
writeln!(f, "Max Profit: ${max_profit:.2}")?;
}
if let Some(max_loss) = self.max_loss {
writeln!(f, "Max Loss: ${max_loss:.2}")?;
}
writeln!(f, "Break-even Points:")?;
for point in &self.break_even_points {
writeln!(f, " ${point:.2}")?;
}
Ok(())
}
}
impl fmt::Debug for Strategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Strategy")
.field("name", &self.name)
.field("kind", &self.kind)
.field("description", &self.description)
.field("legs", &self.legs)
.field("max_profit", &self.max_profit)
.field("max_loss", &self.max_loss)
.field("break_even_points", &self.break_even_points)
.finish()
}
}
#[cfg(test)]
mod tests_options {
use super::*;
use crate::model::types::BarrierType;
use crate::pos;
use chrono::{NaiveDate, TimeZone, Utc};
#[test]
fn test_debug_options() {
let naive_date = NaiveDate::from_ymd_opt(2024, 8, 8)
.expect("Invalid date")
.and_hms_opt(0, 0, 0)
.expect("Invalid time");
let options = Options {
option_type: OptionType::European,
side: Side::Long,
underlying_symbol: "AAPL".to_string(),
strike_price: pos!(150.0),
expiration_date: ExpirationDate::DateTime(Utc.from_utc_datetime(&naive_date)),
implied_volatility: pos!(0.25),
quantity: pos!(10.0),
underlying_price: pos!(155.0),
risk_free_rate: dec!(0.01),
option_style: OptionStyle::Call,
dividend_yield: pos!(0.02),
exotic_params: None,
};
let debug_output = format!("{options:?}");
let expected_output = "Options { \
option_type: European, \
side: Side::Long, \
underlying_symbol: \"AAPL\", \
strike_price: 150, \
expiration_date: ExpirationDate::DateTime(2024-08-08 00:00:00 UTC), \
implied_volatility: 0.25, \
quantity: 10, \
underlying_price: 155, \
risk_free_rate: 0.01, \
option_style: OptionStyle::Call, \
dividend_yield: 0.02, \
exotic_params: None \
}";
assert_eq!(debug_output, expected_output);
}
#[test]
fn test_display_options() {
let naive_date = NaiveDate::from_ymd_opt(2024, 8, 8)
.expect("Invalid date")
.and_hms_opt(0, 0, 0)
.expect("Invalid time");
let options = Options {
option_type: OptionType::European,
side: Side::Long,
underlying_symbol: "AAPL".to_string(),
strike_price: pos!(150.0),
expiration_date: ExpirationDate::DateTime(Utc.from_utc_datetime(&naive_date)),
implied_volatility: pos!(0.25),
quantity: pos!(10.0),
underlying_price: pos!(155.0),
risk_free_rate: dec!(0.01),
option_style: OptionStyle::Call,
dividend_yield: pos!(0.02),
exotic_params: None,
};
let display_output = format!("{options}");
let expected_output = "\
Long Call European Option\n\
Underlying: AAPL @ $155\n\
Strike: $150\n\
Expiration: 2024-08-08 00:00:00 UTC\n\
Implied Volatility: 25%\n\
Quantity: 10\n\
Risk-free Rate: 1.00%\n\
Dividend Yield: 2%";
assert_eq!(display_output, expected_output);
}
#[test]
fn test_display_options_with_exotic_params() {
let exotic_params = ExoticParams {
spot_prices: None,
spot_min: None,
spot_max: None,
};
let naive_date = NaiveDate::from_ymd_opt(2024, 8, 8)
.expect("Invalid date")
.and_hms_opt(0, 0, 0)
.expect("Invalid time");
let options = Options {
option_type: OptionType::Barrier {
barrier_type: BarrierType::UpAndIn,
barrier_level: 100.0,
},
side: Side::Short,
underlying_symbol: "GOOGL".to_string(),
strike_price: pos!(2000.0),
expiration_date: ExpirationDate::DateTime(Utc.from_utc_datetime(&naive_date)),
implied_volatility: pos!(0.30),
quantity: pos!(5.0),
underlying_price: pos!(1900.0),
risk_free_rate: dec!(0.015),
option_style: OptionStyle::Call,
dividend_yield: pos!(0.01),
exotic_params: Some(exotic_params),
};
let display_output = format!("{options}");
let expected_output = "\
Short Call Barrier Option (Type: Up-And-In Barrier, Level: 100)\n\
Underlying: GOOGL @ $1900\n\
Strike: $2000\n\
Expiration: 2024-08-08 00:00:00 UTC\n\
Implied Volatility: 30%\n\
Quantity: 5\n\
Risk-free Rate: 1.50%\n\
Dividend Yield: 1%\n\
Exotic Parameters: ExoticParams { spot_prices: None, spot_min: None, spot_max: None }";
assert_eq!(display_output, expected_output);
}
}
#[cfg(test)]
mod tests_expiration_date {
use super::*;
use crate::pos;
use chrono::{Duration, NaiveDate, TimeZone};
use tracing::info;
#[test]
fn test_display_days() {
let expiration = ExpirationDate::Days(pos!(30.5));
let display_string = format!("{expiration}");
assert!(display_string.contains("UTC"));
}
#[test]
fn test_display_datetime() {
let future_date = Utc::now() + Duration::days(15) + Duration::minutes(1);
let expiration = ExpirationDate::DateTime(future_date);
let display_string = format!("{expiration}");
assert!(display_string.contains("UTC"));
assert!(display_string.contains(&future_date.format("%Y-%m-%d %H:%M:%S").to_string()));
}
#[test]
fn test_debug_days() {
let expiration = ExpirationDate::Days(pos!(45.75));
let debug_string = format!("{expiration:?}");
assert_eq!(debug_string, "ExpirationDate::Days(45.75)");
}
#[test]
fn test_debug_datetime() {
let date = Utc.from_utc_datetime(
&NaiveDate::from_ymd_opt(2023, 12, 31)
.expect("Invalid date")
.and_hms_opt(23, 59, 59)
.expect("Invalid Time"),
);
let expiration = ExpirationDate::DateTime(date);
let debug_string = format!("{expiration:?}");
assert_eq!(
debug_string,
"ExpirationDate::DateTime(2023-12-31 23:59:59 UTC)"
);
}
#[test]
fn test_display_past_date() {
let past_date = Utc::now() - Duration::days(5);
let expiration = ExpirationDate::DateTime(past_date);
let display_string = format!("{expiration}");
assert!(display_string.contains("UTC"));
assert!(display_string.contains(&past_date.format("%Y-%m-%d %H:%M:%S").to_string()));
}
#[test]
fn test_display_today() {
let today = Utc::now();
let expiration = ExpirationDate::DateTime(today);
let display_string = format!("{expiration}");
info!("{}", display_string);
assert!(display_string.contains(&today.format("%Y-%m-%d %H:%M:%S").to_string()));
}
}
#[cfg(test)]
mod tests_side_option_style_display_debug {
use super::*;
#[test]
fn test_side_display() {
assert_eq!(format!("{}", Side::Long), "Long");
assert_eq!(format!("{}", Side::Short), "Short");
}
#[test]
fn test_side_debug() {
assert_eq!(format!("{:?}", Side::Long), "Side::Long");
assert_eq!(format!("{:?}", Side::Short), "Side::Short");
}
#[test]
fn test_option_style_display() {
assert_eq!(format!("{}", OptionStyle::Call), "Call");
assert_eq!(format!("{}", OptionStyle::Put), "Put");
}
#[test]
fn test_option_style_debug() {
assert_eq!(format!("{:?}", OptionStyle::Call), "OptionStyle::Call");
assert_eq!(format!("{:?}", OptionStyle::Put), "OptionStyle::Put");
}
}
#[cfg(test)]
mod tests_option_type_display_debug {
use super::*;
#[test]
fn test_debug_european_option() {
let option = OptionType::European;
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "European");
}
#[test]
fn test_display_european_option() {
let option = OptionType::European;
let display_output = format!("{option}");
assert_eq!(display_output, "European Option");
}
#[test]
fn test_debug_american_option() {
let option = OptionType::American;
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "American");
}
#[test]
fn test_display_american_option() {
let option = OptionType::American;
let display_output = format!("{option}");
assert_eq!(display_output, "American Option");
}
#[test]
fn test_debug_bermuda_option() {
let option = OptionType::Bermuda {
exercise_dates: vec![1.0, 2.0, 3.0],
};
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Bermuda { exercise_dates: [1.0, 2.0, 3.0] }");
}
#[test]
fn test_display_bermuda_option() {
let option = OptionType::Bermuda {
exercise_dates: vec![1.0, 2.0, 3.0],
};
let display_output = format!("{option}");
assert_eq!(
display_output,
"Bermuda Option (Exercise Dates: [1.0, 2.0, 3.0])"
);
}
#[test]
fn test_debug_asian_option() {
let option = OptionType::Asian {
averaging_type: AsianAveragingType::Arithmetic,
};
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Asian { averaging_type: Arithmetic }");
}
#[test]
fn test_display_asian_option() {
let option = OptionType::Asian {
averaging_type: AsianAveragingType::Arithmetic,
};
let display_output = format!("{option}");
assert_eq!(
display_output,
"Asian Option (Averaging Type: Arithmetic Averaging)"
);
}
#[test]
fn test_debug_barrier_option() {
let option = OptionType::Barrier {
barrier_type: BarrierType::UpAndIn,
barrier_level: 100.0,
};
let debug_output = format!("{option:?}");
assert_eq!(
debug_output,
"Barrier { barrier_type: UpAndIn, barrier_level: 100.0 }"
);
}
#[test]
fn test_display_barrier_option() {
let option = OptionType::Barrier {
barrier_type: BarrierType::UpAndIn,
barrier_level: 100.0,
};
let display_output = format!("{option}");
assert_eq!(
display_output,
"Barrier Option (Type: Up-And-In Barrier, Level: 100)"
);
}
#[test]
fn test_debug_binary_option() {
let option = OptionType::Binary {
binary_type: BinaryType::CashOrNothing,
};
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Binary { binary_type: CashOrNothing }");
}
#[test]
fn test_display_binary_option() {
let option = OptionType::Binary {
binary_type: BinaryType::CashOrNothing,
};
let display_output = format!("{option}");
assert_eq!(
display_output,
"Binary Option (Type: Cash-Or-Nothing Binary Option)"
);
}
#[test]
fn test_debug_lookback_option() {
let option = OptionType::Lookback {
lookback_type: LookbackType::FixedStrike,
};
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Lookback { lookback_type: FixedStrike }");
}
#[test]
fn test_display_lookback_option() {
let option = OptionType::Lookback {
lookback_type: LookbackType::FixedStrike,
};
let display_output = format!("{option}");
assert_eq!(
display_output,
"Lookback Option (Type: Fixed-Strike Lookback Option)"
);
}
#[test]
fn test_debug_compound_option() {
let option = OptionType::Compound {
underlying_option: Box::new(OptionType::European),
};
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Compound { underlying_option: European }");
}
#[test]
fn test_display_compound_option() {
let option = OptionType::Compound {
underlying_option: Box::new(OptionType::European),
};
let display_output = format!("{option}");
assert_eq!(
display_output,
"Compound Option (Underlying: European Option)"
);
}
#[test]
fn test_debug_chooser_option() {
let option = OptionType::Chooser { choice_date: 2.0 };
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Chooser { choice_date: 2.0 }");
}
#[test]
fn test_display_chooser_option() {
let option = OptionType::Chooser { choice_date: 2.0 };
let display_output = format!("{option}");
assert_eq!(display_output, "Chooser Option (Choice Date: 2)");
}
#[test]
fn test_debug_cliquet_option() {
let option = OptionType::Cliquet {
reset_dates: vec![1.0, 2.0],
};
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Cliquet { reset_dates: [1.0, 2.0] }");
}
#[test]
fn test_display_cliquet_option() {
let option = OptionType::Cliquet {
reset_dates: vec![1.0, 2.0],
};
let display_output = format!("{option}");
assert_eq!(display_output, "Cliquet Option (Reset Dates: [1.0, 2.0])");
}
#[test]
fn test_debug_rainbow_option() {
let option = OptionType::Rainbow { num_assets: 3 };
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Rainbow { num_assets: 3 }");
}
#[test]
fn test_display_rainbow_option() {
let option = OptionType::Rainbow { num_assets: 3 };
let display_output = format!("{option}");
assert_eq!(display_output, "Rainbow Option (Number of Assets: 3)");
}
#[test]
fn test_debug_spread_option() {
let option = OptionType::Spread { second_asset: 50.0 };
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Spread { second_asset: 50.0 }");
}
#[test]
fn test_display_spread_option() {
let option = OptionType::Spread { second_asset: 50.0 };
let display_output = format!("{option}");
assert_eq!(display_output, "Spread Option (Second Asset: 50)");
}
#[test]
fn test_debug_quanto_option() {
let option = OptionType::Quanto { exchange_rate: 1.2 };
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Quanto { exchange_rate: 1.2 }");
}
#[test]
fn test_display_quanto_option() {
let option = OptionType::Quanto { exchange_rate: 1.2 };
let display_output = format!("{option}");
assert_eq!(display_output, "Quanto Option (Exchange Rate: 1.2)");
}
#[test]
fn test_debug_exchange_option() {
let option = OptionType::Exchange { second_asset: 75.0 };
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Exchange { second_asset: 75.0 }");
}
#[test]
fn test_display_exchange_option() {
let option = OptionType::Exchange { second_asset: 75.0 };
let display_output = format!("{option}");
assert_eq!(display_output, "Exchange Option (Second Asset: 75)");
}
#[test]
fn test_debug_power_option() {
let option = OptionType::Power { exponent: 2.5 };
let debug_output = format!("{option:?}");
assert_eq!(debug_output, "Power { exponent: 2.5 }");
}
#[test]
fn test_display_power_option() {
let option = OptionType::Power { exponent: 2.5 };
let display_output = format!("{option}");
assert_eq!(display_output, "Power Option (Exponent: 2.5)");
}
}
#[cfg(test)]
mod tests_position_type_display_debug {
use super::*;
use crate::pos;
use chrono::{DateTime, NaiveDate, TimeZone};
fn get_option() -> (Options, DateTime<Utc>) {
let naive_date = NaiveDate::from_ymd_opt(2024, 8, 8)
.expect("Invalid date")
.and_hms_opt(0, 0, 0)
.expect("Invalid time");
(
Options {
option_type: OptionType::European,
side: Side::Long,
underlying_symbol: "AAPL".to_string(),
strike_price: pos!(150.0),
expiration_date: ExpirationDate::DateTime(Utc.from_utc_datetime(&naive_date)),
implied_volatility: pos!(0.25),
quantity: pos!(10.0),
underlying_price: pos!(155.0),
risk_free_rate: dec!(0.01),
option_style: OptionStyle::Call,
dividend_yield: pos!(0.02),
exotic_params: None,
},
Utc.from_utc_datetime(&naive_date),
)
}
#[test]
fn test_position_display() {
let (option, naive_date) = get_option();
let position = Position {
option,
premium: pos!(5.75),
date: naive_date,
open_fee: pos!(0.50),
close_fee: pos!(0.45),
epic: Some("Epic123".to_string()),
extra_fields: None,
};
let expected_display = "Position Details:\n\
Option: Long Call European Option\n\
Underlying: AAPL @ $155\n\
Strike: $150\n\
Expiration: 2024-08-08 00:00:00 UTC\n\
Implied Volatility: 25%\n\
Quantity: 10\n\
Risk-free Rate: 1.00%\n\
Dividend Yield: 2%\n\
Premium per contract: $5.75\n\
Date: 2024-08-08 00:00:00 UTC\n\
Open Fee per contract: $0.50\n\
Close Fee per contract: $0.45";
assert_eq!(format!("{position}"), expected_display);
}
#[test]
fn test_position_debug() {
let (option, naive_date) = get_option();
let position = Position {
option,
premium: pos!(5.75),
date: naive_date,
open_fee: pos!(0.50),
close_fee: pos!(0.45),
epic: Some("Epic123".to_string()),
extra_fields: None,
};
let expected_debug = "Position { \
option: Options { \
option_type: European, \
side: Side::Long, \
underlying_symbol: \"AAPL\", \
strike_price: 150, \
expiration_date: ExpirationDate::DateTime(2024-08-08 00:00:00 UTC), \
implied_volatility: 0.25, \
quantity: 10, \
underlying_price: 155, \
risk_free_rate: 0.01, \
option_style: OptionStyle::Call, \
dividend_yield: 0.02, \
exotic_params: None \
}, \
premium: 5.75, \
date: 2024-08-08T00:00:00Z, \
open_fee: 0.5, \
close_fee: 0.45 \
}";
assert_eq!(format!("{position:?}"), expected_debug);
}
}
#[cfg(test)]
mod tests_strategy_type_display_debug {
use super::*;
use crate::model::utils::create_sample_option_with_date;
use crate::pos;
use crate::strategies::base::StrategyType;
use chrono::{NaiveDate, TimeZone};
use serde::Serialize;
#[test]
fn test_strategy_display() {
#[derive(Serialize)]
struct ExtraFields {
custom_field: String,
}
let extra_fields = ExtraFields {
custom_field: "Custom Value".to_string(),
};
let naive_date = NaiveDate::from_ymd_opt(2024, 8, 8)
.expect("Invalid date")
.and_hms_opt(0, 0, 0)
.expect("Invalid time");
let strategy = Strategy {
name: "Bull Call Spread".to_string(),
kind: StrategyType::BullCallSpread,
description: "A bullish options strategy".to_string(),
legs: vec![
Position::new(
create_sample_option_with_date(
OptionStyle::Call,
Side::Long,
pos!(100.0),
pos!(1.0),
pos!(100.0),
pos!(0.02),
naive_date,
),
pos!(5.75),
Utc.from_utc_datetime(&naive_date),
pos!(0.50),
pos!(0.45),
Some("Epic123".to_string()),
None,
),
Position::new(
create_sample_option_with_date(
OptionStyle::Call,
Side::Short,
pos!(100.0),
pos!(1.0),
pos!(100.0),
pos!(0.02),
naive_date,
),
pos!(5.75),
Utc.from_utc_datetime(&naive_date),
pos!(0.50),
pos!(0.45),
Some("Epic123".to_string()),
Some(serde_json::to_value(&extra_fields).unwrap()),
),
],
max_profit: Some(10.0),
max_loss: Some(5.0),
break_even_points: vec![pos!(102.0), pos!(108.0)],
};
let expected_output = "Strategy: Bull Call Spread\nType: BullCallSpread\nDescription: A bullish options strategy\nLegs:\n Position Details:\nOption: Long Call European Option\nUnderlying: AAPL @ $100\nStrike: $100\nExpiration: 2024-08-08 00:00:00 UTC\nImplied Volatility: 2%\nQuantity: 1\nRisk-free Rate: 5.00%\nDividend Yield: 1%\nPremium per contract: $5.75\nDate: 2024-08-08 00:00:00 UTC\nOpen Fee per contract: $0.50\nClose Fee per contract: $0.45\n Position Details:\nOption: Short Call European Option\nUnderlying: AAPL @ $100\nStrike: $100\nExpiration: 2024-08-08 00:00:00 UTC\nImplied Volatility: 2%\nQuantity: 1\nRisk-free Rate: 5.00%\nDividend Yield: 1%\nPremium per contract: $5.75\nDate: 2024-08-08 00:00:00 UTC\nOpen Fee per contract: $0.50\nClose Fee per contract: $0.45\nMax Profit: $10.00\nMax Loss: $5.00\nBreak-even Points:\n $102\n $108\n";
assert_eq!(format!("{strategy}"), expected_output);
}
#[test]
fn test_strategy_debug() {
#[derive(Serialize)]
struct ExtraFields {
custom_field: String,
}
let extra_fields = ExtraFields {
custom_field: "Custom Value".to_string(),
};
let naive_date = NaiveDate::from_ymd_opt(2024, 8, 8)
.expect("Invalid date")
.and_hms_opt(0, 0, 0)
.expect("Invalid time");
let strategy = Strategy {
name: "Bear Put Spread".to_string(),
kind: StrategyType::BearPutSpread,
description: "A bearish options strategy".to_string(),
legs: vec![
Position::new(
create_sample_option_with_date(
OptionStyle::Call,
Side::Long,
pos!(100.0),
pos!(1.0),
pos!(110.0),
pos!(0.02),
naive_date,
),
pos!(5.75),
Utc.from_utc_datetime(&naive_date),
pos!(0.50),
pos!(0.45),
Some("Epic123".to_string()),
None,
),
Position::new(
create_sample_option_with_date(
OptionStyle::Call,
Side::Short,
pos!(100.0),
pos!(1.0),
pos!(110.0),
pos!(0.02),
naive_date,
),
pos!(5.75),
Utc.from_utc_datetime(&naive_date),
pos!(0.50),
pos!(0.45),
Some("Epic123".to_string()),
Some(serde_json::to_value(&extra_fields).unwrap()),
),
],
max_profit: Some(8.0),
max_loss: Some(2.0),
break_even_points: vec![pos!(82.0), pos!(88.0)],
};
let expected_output = "Strategy { name: \"Bear Put Spread\", kind: BearPutSpread, description: \"A bearish options strategy\", legs: [Position { option: Options { option_type: European, side: Side::Long, underlying_symbol: \"AAPL\", strike_price: 110, expiration_date: ExpirationDate::DateTime(2024-08-08 00:00:00 UTC), implied_volatility: 0.02, quantity: 1, underlying_price: 100, risk_free_rate: 0.05, option_style: OptionStyle::Call, dividend_yield: 0.01, exotic_params: None }, premium: 5.75, date: 2024-08-08T00:00:00Z, open_fee: 0.5, close_fee: 0.45 }, Position { option: Options { option_type: European, side: Side::Short, underlying_symbol: \"AAPL\", strike_price: 110, expiration_date: ExpirationDate::DateTime(2024-08-08 00:00:00 UTC), implied_volatility: 0.02, quantity: 1, underlying_price: 100, risk_free_rate: 0.05, option_style: OptionStyle::Call, dividend_yield: 0.01, exotic_params: None }, premium: 5.75, date: 2024-08-08T00:00:00Z, open_fee: 0.5, close_fee: 0.45 }], max_profit: Some(8.0), max_loss: Some(2.0), break_even_points: [82, 88] }";
assert_eq!(format!("{strategy:?}"), expected_output);
}
}