Skip to content

Commit 035f2d1

Browse files
authored
Merge pull request #267 from joaquinbejar/219-implement-protective-put-strategy
feat: implement ProtectivePut strategy
2 parents c99c525 + 7f37b64 commit 035f2d1

File tree

5 files changed

+655
-41
lines changed

5 files changed

+655
-41
lines changed

src/prelude.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub use crate::strategies::{
4040
bull_put_spread::BullPutSpread,
4141
call_butterfly::CallButterfly,
4242
collar::Collar,
43+
covered_call::CoveredCall,
4344
custom::CustomStrategy,
4445
delta_neutral::{
4546
AdjustmentAction, AdjustmentConfig, AdjustmentError, AdjustmentOptimizer, AdjustmentPlan,
@@ -54,6 +55,7 @@ pub use crate::strategies::{
5455
long_strangle::LongStrangle,
5556
poor_mans_covered_call::PoorMansCoveredCall,
5657
probabilities::ProbabilityAnalysis,
58+
protective_put::ProtectivePut,
5759
short_butterfly_spread::ShortButterflySpread,
5860
short_call::ShortCall,
5961
short_put::ShortPut,

src/strategies/collar.rs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -381,14 +381,12 @@ impl Collar {
381381
if call_strike >= cost_basis {
382382
let capital_gain = (call_strike - cost_basis) * quantity;
383383
let total_profit = capital_gain.to_dec() + net_premium - total_fees.to_dec();
384-
Ok(Positive::new_decimal(total_profit.max(Decimal::ZERO))
385-
.unwrap_or(Positive::ZERO))
384+
Ok(Positive::new_decimal(total_profit.max(Decimal::ZERO)).unwrap_or(Positive::ZERO))
386385
} else {
387386
// Call strike below cost basis
388387
let capital_loss = (cost_basis - call_strike) * quantity;
389388
let total_profit = net_premium - capital_loss.to_dec() - total_fees.to_dec();
390-
Ok(Positive::new_decimal(total_profit.max(Decimal::ZERO))
391-
.unwrap_or(Positive::ZERO))
389+
Ok(Positive::new_decimal(total_profit.max(Decimal::ZERO)).unwrap_or(Positive::ZERO))
392390
}
393391
}
394392

@@ -405,14 +403,12 @@ impl Collar {
405403
if cost_basis >= put_strike {
406404
let capital_loss = (cost_basis - put_strike) * quantity;
407405
let total_loss = capital_loss.to_dec() - net_premium + total_fees.to_dec();
408-
Ok(Positive::new_decimal(total_loss.max(Decimal::ZERO))
409-
.unwrap_or(Positive::ZERO))
406+
Ok(Positive::new_decimal(total_loss.max(Decimal::ZERO)).unwrap_or(Positive::ZERO))
410407
} else {
411408
// Put strike above cost basis (unusual but possible)
412409
let capital_gain = (put_strike - cost_basis) * quantity;
413410
let total_loss = total_fees.to_dec() - net_premium - capital_gain.to_dec();
414-
Ok(Positive::new_decimal(total_loss.max(Decimal::ZERO))
415-
.unwrap_or(Positive::ZERO))
411+
Ok(Positive::new_decimal(total_loss.max(Decimal::ZERO)).unwrap_or(Positive::ZERO))
416412
}
417413
}
418414

@@ -497,7 +493,7 @@ impl BreakEvenable for Collar {
497493
let fees_per_share = self.total_fees().to_dec() / self.spot_leg.quantity.to_dec();
498494

499495
let break_even = self.spot_leg.cost_basis.to_dec() - net_premium_per_share + fees_per_share;
500-
496+
501497
if let Ok(be) = Positive::new_decimal(break_even) {
502498
self.break_even_points.push(be.round_to(2));
503499
}
@@ -601,7 +597,7 @@ impl BasicAble for Collar {
601597

602598
fn get_option_basic_type(&self) -> HashSet<OptionBasicType<'_>> {
603599
let mut hash_set = HashSet::new();
604-
600+
605601
let long_put = &self.long_put.option;
606602
hash_set.insert(OptionBasicType {
607603
option_style: &long_put.option_style,
@@ -623,7 +619,7 @@ impl BasicAble for Collar {
623619

624620
fn get_implied_volatility(&self) -> HashMap<OptionBasicType<'_>, &Positive> {
625621
let mut map = HashMap::new();
626-
622+
627623
let long_put = &self.long_put.option;
628624
map.insert(
629625
OptionBasicType {
@@ -651,7 +647,7 @@ impl BasicAble for Collar {
651647

652648
fn get_quantity(&self) -> HashMap<OptionBasicType<'_>, &Positive> {
653649
let mut map = HashMap::new();
654-
650+
655651
let long_put = &self.long_put.option;
656652
map.insert(
657653
OptionBasicType {
@@ -801,8 +797,11 @@ impl ProbabilityAnalysis for Collar {
801797
let risk_free_rate = option.risk_free_rate;
802798

803799
// Loss range: from put strike up to break-even
804-
let mut loss_range =
805-
ProfitLossRange::new(Some(self.put_strike()), Some(break_even_point), Positive::ZERO)?;
800+
let mut loss_range = ProfitLossRange::new(
801+
Some(self.put_strike()),
802+
Some(break_even_point),
803+
Positive::ZERO,
804+
)?;
806805

807806
loss_range.calculate_probability(
808807
&self.spot_leg.cost_basis,
@@ -844,22 +843,22 @@ mod tests {
844843
fn create_test_collar() -> Collar {
845844
Collar::new(
846845
"AAPL".to_string(),
847-
pos_or_panic!(150.0), // underlying price
848-
pos_or_panic!(145.0), // put strike
849-
pos_or_panic!(160.0), // call strike
846+
pos_or_panic!(150.0), // underlying price
847+
pos_or_panic!(145.0), // put strike
848+
pos_or_panic!(160.0), // call strike
850849
ExpirationDate::Days(pos_or_panic!(30.0)),
851-
pos_or_panic!(0.25), // implied volatility
852-
dec!(0.05), // risk-free rate
853-
pos_or_panic!(0.01), // dividend yield
854-
Positive::HUNDRED, // quantity
855-
pos_or_panic!(2.50), // put premium
856-
pos_or_panic!(3.00), // call premium
857-
Positive::ONE, // spot open fee
858-
Positive::ONE, // spot close fee
859-
pos_or_panic!(0.65), // put open fee
860-
pos_or_panic!(0.65), // put close fee
861-
pos_or_panic!(0.65), // call open fee
862-
pos_or_panic!(0.65), // call close fee
850+
pos_or_panic!(0.25), // implied volatility
851+
dec!(0.05), // risk-free rate
852+
pos_or_panic!(0.01), // dividend yield
853+
Positive::HUNDRED, // quantity
854+
pos_or_panic!(2.50), // put premium
855+
pos_or_panic!(3.00), // call premium
856+
Positive::ONE, // spot open fee
857+
Positive::ONE, // spot close fee
858+
pos_or_panic!(0.65), // put open fee
859+
pos_or_panic!(0.65), // put close fee
860+
pos_or_panic!(0.65), // call open fee
861+
pos_or_panic!(0.65), // call close fee
863862
)
864863
}
865864

@@ -996,7 +995,7 @@ mod tests {
996995
fn test_is_put_itm() {
997996
let collar = create_test_collar();
998997

999-
assert!(collar.is_put_itm(pos_or_panic!(140.0))); // Below put strike
998+
assert!(collar.is_put_itm(pos_or_panic!(140.0))); // Below put strike
1000999
assert!(!collar.is_put_itm(pos_or_panic!(145.0))); // At put strike
10011000
assert!(!collar.is_put_itm(pos_or_panic!(150.0))); // Above put strike
10021001
}
@@ -1007,7 +1006,7 @@ mod tests {
10071006

10081007
assert!(!collar.is_call_itm(pos_or_panic!(155.0))); // Below call strike
10091008
assert!(!collar.is_call_itm(pos_or_panic!(160.0))); // At call strike
1010-
assert!(collar.is_call_itm(pos_or_panic!(165.0))); // Above call strike
1009+
assert!(collar.is_call_itm(pos_or_panic!(165.0))); // Above call strike
10111010
}
10121011

10131012
#[test]

src/strategies/graph.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,5 +377,6 @@ impl_graph_for_payoff_strategy!(
377377
CallButterfly,
378378
crate::strategies::custom::CustomStrategy,
379379
crate::strategies::covered_call::CoveredCall,
380-
crate::strategies::collar::Collar
380+
crate::strategies::collar::Collar,
381+
crate::strategies::protective_put::ProtectivePut
381382
);

src/strategies/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ pub use bull_call_spread::BullCallSpread;
284284
pub use bull_put_spread::BullPutSpread;
285285
pub use call_butterfly::CallButterfly;
286286
pub use collar::Collar;
287+
pub use covered_call::CoveredCall;
287288
pub use delta_neutral::{
288289
AdjustmentAction, AdjustmentConfig, AdjustmentError, AdjustmentOptimizer, AdjustmentPlan,
289290
AdjustmentTarget, DELTA_THRESHOLD, DeltaAdjustment, DeltaInfo, DeltaNeutrality,
@@ -297,6 +298,7 @@ pub use long_put::LongPut;
297298
pub use long_straddle::LongStraddle;
298299
pub use long_strangle::LongStrangle;
299300
pub use poor_mans_covered_call::PoorMansCoveredCall;
301+
pub use protective_put::ProtectivePut;
300302
pub use shared::{
301303
ButterflyStrategy, CondorStrategy, SpreadStrategy, StraddleStrategy, StrangleStrategy,
302304
aggregate_fees, aggregate_premiums, calculate_profit_ratio, credit_spread_break_even,

0 commit comments

Comments
 (0)