Skip to content

Commit 33abfba

Browse files
committed
Add trade generation and price/volatility updates for strategies
Implemented `trade_from_delta_adjustment` to generate trades based on delta adjustments. Added methods to update underlying price, volatility, and expiration date across base and specific strategies. Adjusted test assertions to reflect updated calculations.
1 parent b767124 commit 33abfba

3 files changed

Lines changed: 215 additions & 2 deletions

File tree

src/strategies/base.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,75 @@ pub trait Strategies: Validable + Positionable + BreakEvenable {
694694
) -> Result<(), StrategyError> {
695695
unimplemented!("Set expiration date is not implemented for this strategy")
696696
}
697+
698+
/// Updates the underlying price for the strategy.
699+
///
700+
/// # Parameters
701+
/// - `_price`: A reference to a `Positive` value representing the new underlying price to update.
702+
///
703+
/// # Returns
704+
/// - `Ok(())` if the operation succeeds.
705+
/// - `Err(StrategyError)` if there is an error during the operation.
706+
///
707+
/// # Notes
708+
/// This function is currently unimplemented and will panic if called.
709+
/// It serves as a placeholder for strategies where updating the underlying
710+
/// price has not yet been implemented.
711+
///
712+
/// # Panics
713+
/// Always panics with the message "Update underlying price is not implemented for this strategy".
714+
///
715+
fn update_underlying_price(&mut self, _price: &Positive) -> Result<(), StrategyError> {
716+
unimplemented!("Update underlying price is not implemented for this strategy")
717+
}
718+
719+
/// Updates the volatility for the strategy.
720+
///
721+
/// # Parameters
722+
/// - `_volatility`: A reference to a `Positive` value representing the new volatility to set.
723+
///
724+
/// # Returns
725+
/// - `Ok(())`: If the update operation succeeds (currently unimplemented).
726+
/// - `Err(StrategyError)`: If there is an error during the update process (place-holder as functionality is not implemented).
727+
///
728+
/// # Notes
729+
/// This method is currently unimplemented, and calling it will result in the `unimplemented!` macro being triggered, which causes a panic.
730+
/// This function is a stub and should be implemented to handle setting the volatility specific to the strategy.
731+
///
732+
fn update_volatility(&mut self, _volatility: &Positive) -> Result<(), StrategyError> {
733+
unimplemented!("Update volatility is not implemented for this strategy")
734+
}
735+
736+
/// Updates the expiration date for the current strategy.
737+
///
738+
/// This function is designed to be overridden or modified in specific strategy implementations.
739+
/// As it stands, calling this function will result in a panic, as it is not implemented for the
740+
/// current strategy by default.
741+
///
742+
/// # Parameters
743+
/// - `_expiration_date`: The new `ExpirationDate` to update the strategy with. This argument is
744+
/// currently unused in this default implementation.
745+
///
746+
/// # Returns
747+
/// This function returns a `Result`:
748+
/// - `Ok(())` if the update is successful. However, as this method is unimplemented,
749+
/// the success branch is not reachable for the default implementation.
750+
/// - `Err(StrategyError)` if an error occurs. In this implementation, it will not return an error
751+
/// but rather panic.
752+
///
753+
/// # Errors
754+
/// None are returned because the function panics with an unimplemented message in this base
755+
/// implementation.
756+
///
757+
/// # Panics
758+
/// Always panics with the message `"Update expiration date is not implemented for this strategy"`.
759+
///
760+
fn update_expiration_date(
761+
&mut self,
762+
_expiration_date: ExpirationDate,
763+
) -> Result<(), StrategyError> {
764+
unimplemented!("Update expiration date is not implemented for this strategy")
765+
}
697766
}
698767

699768
/// Trait for strategies that can calculate and update break-even points.

src/strategies/delta_neutral/model.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use crate::error::{GreeksError, PositionError, StrategyError};
4343
use crate::greeks::Greeks;
4444
use crate::greeks::calculate_delta_neutral_sizes;
4545
use crate::model::types::{Action, OptionStyle};
46+
use crate::model::{Trade, TradeStatusAble};
4647
use crate::strategies::Strategies;
4748
use crate::strategies::base::Positionable;
4849
use crate::{Options, Positive, Side};
@@ -752,6 +753,104 @@ pub trait DeltaNeutrality: Greeks + Positionable + Strategies {
752753
}
753754
Ok(())
754755
}
756+
757+
/// Generates a `Trade` object based on the given delta adjustment action.
758+
///
759+
/// # Parameters
760+
/// - `_action`: An `Action` representing the delta adjustment based on which the trade will be formulated.
761+
///
762+
/// # Returns
763+
/// A `Trade` object derived from the delta adjustment logic.
764+
///
765+
/// # Notes
766+
/// - This function is currently unimplemented. Ensure to provide implementation logic
767+
/// that determines the `Trade` based on the `Action` delta adjustment.
768+
/// - Replace the `unimplemented!()` macro with the actual implementation when ready.
769+
///
770+
fn trade_from_delta_adjustment(
771+
&mut self,
772+
action: Action,
773+
) -> Result<Vec<Trade>, Box<dyn Error>> {
774+
let adjustments = self.delta_adjustments()?;
775+
let mut trades = Vec::new();
776+
777+
// Process a single BuyOptions or SellOptions adjustment
778+
let mut process_single_adjustment =
779+
|adj: &DeltaAdjustment| -> Result<Option<Trade>, Box<dyn Error>> {
780+
match adj {
781+
DeltaAdjustment::BuyOptions {
782+
quantity,
783+
strike,
784+
option_style,
785+
side,
786+
} => {
787+
if quantity.is_zero() {
788+
return Ok(None);
789+
}
790+
let positions = self.get_position(option_style, side, strike)?;
791+
if let Some(position) = positions.first() {
792+
let mut position_clone = (*position).clone();
793+
position_clone.option.quantity = *quantity;
794+
Ok(Some(position_clone.open()))
795+
} else {
796+
Ok(None)
797+
}
798+
}
799+
DeltaAdjustment::SellOptions {
800+
quantity,
801+
strike,
802+
option_style,
803+
side,
804+
} => {
805+
if quantity.is_zero() {
806+
return Ok(None);
807+
}
808+
let positions = self.get_position(option_style, side, strike)?;
809+
if let Some(position) = positions.first() {
810+
let mut position_clone = (*position).clone();
811+
position_clone.option.quantity = *quantity;
812+
Ok(Some(position_clone.close()))
813+
} else {
814+
Ok(None)
815+
}
816+
}
817+
_ => Ok(None),
818+
}
819+
};
820+
821+
for adjustment in adjustments {
822+
match (&action, adjustment) {
823+
// For Buy action, only process BuyOptions adjustments
824+
(Action::Buy, adj @ DeltaAdjustment::BuyOptions { .. }) => {
825+
if let Some(trade) = process_single_adjustment(&adj)? {
826+
trades.push(trade);
827+
}
828+
}
829+
830+
// For Sell action, only process SellOptions adjustments
831+
(Action::Sell, adj @ DeltaAdjustment::SellOptions { .. }) => {
832+
if let Some(trade) = process_single_adjustment(&adj)? {
833+
trades.push(trade);
834+
}
835+
}
836+
837+
// For Other action, process both adjustments in SameSize
838+
(Action::Other, DeltaAdjustment::SameSize(a, b)) => {
839+
if let Some(trade) = process_single_adjustment(&a)? {
840+
trades.push(trade);
841+
}
842+
if let Some(trade) = process_single_adjustment(&b)? {
843+
trades.push(trade);
844+
}
845+
}
846+
847+
// Ignore other combinations
848+
_ => {}
849+
}
850+
}
851+
852+
Ok(trades)
853+
}
755854
}
756855

757856
/// # DeltaNeutralResponse

src/strategies/strangle.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,51 @@ impl Strategies for ShortStrangle {
575575
self.short_put.option.expiration_date = expiration_date;
576576
Ok(())
577577
}
578+
579+
fn update_underlying_price(&mut self, price: &Positive) -> Result<(), StrategyError> {
580+
self.short_call.option.underlying_price = *price;
581+
self.short_put.option.underlying_price = *price;
582+
self.short_call.premium = Positive(
583+
self.short_call
584+
.option
585+
.calculate_price_black_scholes()?
586+
.abs(),
587+
);
588+
self.short_put.premium =
589+
Positive(self.short_put.option.calculate_price_black_scholes()?.abs());
590+
Ok(())
591+
}
592+
593+
fn update_volatility(&mut self, volatility: &Positive) -> Result<(), StrategyError> {
594+
self.short_call.option.implied_volatility = *volatility;
595+
self.short_put.option.implied_volatility = *volatility;
596+
self.short_call.premium = Positive(
597+
self.short_call
598+
.option
599+
.calculate_price_black_scholes()?
600+
.abs(),
601+
);
602+
self.short_put.premium =
603+
Positive(self.short_put.option.calculate_price_black_scholes()?.abs());
604+
Ok(())
605+
}
606+
607+
fn update_expiration_date(
608+
&mut self,
609+
expiration_date: ExpirationDate,
610+
) -> Result<(), StrategyError> {
611+
self.short_call.option.expiration_date = expiration_date;
612+
self.short_put.option.expiration_date = expiration_date;
613+
self.short_call.premium = Positive(
614+
self.short_call
615+
.option
616+
.calculate_price_black_scholes()?
617+
.abs(),
618+
);
619+
self.short_put.premium =
620+
Positive(self.short_put.option.calculate_price_black_scholes()?.abs());
621+
Ok(())
622+
}
578623
}
579624

580625
impl Validable for ShortStrangle {
@@ -4857,7 +4902,7 @@ mod tests_long_strangle_pnl {
48574902
// At the money, both options should have time value but no intrinsic value
48584903
// Initial cost is 2 * (premium + fees) = 2 * (5.0 + 1.0) = 12.0
48594904
assert_pos_relative_eq!(pnl.initial_costs, pos!(12.0), pos!(1e-6));
4860-
assert_decimal_eq!(pnl.unrealized.unwrap(), dec!(0.746072), dec!(1e-6));
4905+
assert_decimal_eq!(pnl.unrealized.unwrap(), dec!(0.748425), dec!(1e-6));
48614906
assert_eq!(pnl.initial_income, pos!(0.0));
48624907
// Unrealized loss should be less than full premium paid (time value remains)
48634908
assert!(pnl.unrealized.unwrap() > dec!(-12.0));
@@ -5019,7 +5064,7 @@ mod tests_short_strangle_pnl {
50195064
// Initial income is 2 * premium = 2 * 5.0 = 10.0
50205065
assert_pos_relative_eq!(pnl.initial_costs, pos!(2.0), pos!(1e-6));
50215066
assert_pos_relative_eq!(pnl.initial_income, pos!(10.0), pos!(1e-6));
5022-
assert_decimal_eq!(pnl.unrealized.unwrap(), dec!(-0.746072), dec!(1e-6));
5067+
assert_decimal_eq!(pnl.unrealized.unwrap(), dec!(-0.748425), dec!(1e-6));
50235068
// Unrealized loss should be less than max potential loss
50245069
assert!(pnl.unrealized.unwrap() > dec!(-100.0)); // Using a large number as max theoretical loss is unlimited
50255070
}

0 commit comments

Comments
 (0)