optionstratlib/pnl/
traits.rs

1use crate::error::{PricingError, TransactionError};
2use crate::model::Position;
3use crate::pnl::transaction::Transaction;
4use crate::pnl::utils::PnL;
5use crate::strategies::DeltaAdjustment;
6use crate::{ExpirationDate, Positive};
7
8/// Defines the interface for profit and loss (PnL) calculation on financial instruments.
9///
10/// This trait provides methods to calculate the profit and loss of financial instruments
11/// (particularly options) under different scenarios: at current market conditions and
12/// at expiration. Implementations of this trait can provide specific PnL calculation
13/// logic for different types of financial instruments or strategies.
14///
15pub trait PnLCalculator {
16    /// Calculates the current PnL based on market conditions.
17    ///
18    /// This method computes the profit and loss of a financial instrument given
19    /// the current underlying price, time to expiration, and implied volatility.
20    /// It returns a complete PnL structure with realized and unrealized values.
21    ///
22    /// # Parameters
23    /// * `_underlying_price` - The current market price of the underlying asset
24    /// * `_expiration_date` - The expiration date of the instrument
25    /// * `_implied_volatility` - The current implied volatility
26    ///
27    /// # Returns
28    /// * `Result<PnL, PricingError>` - The calculated PnL or an error
29    fn calculate_pnl(
30        &self,
31        _underlying_price: &Positive,
32        _expiration_date: ExpirationDate,
33        _implied_volatility: &Positive,
34    ) -> Result<PnL, PricingError>;
35
36    /// Calculates the PnL at the expiration of the instrument.
37    ///
38    /// This method computes the final profit and loss at the expiration date,
39    /// which is typically simpler than the pre-expiration calculation since
40    /// time value and volatility no longer factor into the price.
41    ///
42    /// # Parameters
43    /// * `_underlying_price` - The price of the underlying asset at expiration
44    ///
45    /// # Returns
46    /// * `Result<PnL, PricingError>` - The calculated PnL at expiration or an error
47    fn calculate_pnl_at_expiration(
48        &self,
49        _underlying_price: &Positive,
50    ) -> Result<PnL, PricingError>;
51
52    /// Calculates the Profit and Loss (PnL) for a series of delta adjustments in a trading strategy.
53    ///
54    /// # Arguments
55    ///
56    /// * `_adjustments` - A vector of `DeltaAdjustment` instances representing the adjustments made
57    ///   to maintain delta neutrality in a trading strategy.
58    ///
59    /// # Returns
60    ///
61    /// * `Result<PnL, PricingError>` - If successful, returns a `PnL` object containing information
62    ///   about realized and unrealized profits/losses, costs, and income.
63    ///   Otherwise, returns an error.
64    ///
65    /// # Panics
66    ///
67    /// This function always panics with the message "adjustments_pnl is not implemented for this Strategy."
68    /// It serves as a placeholder or trait method that must be implemented by specific strategy implementations.
69    ///
70    fn adjustments_pnl(&self, _adjustments: &DeltaAdjustment) -> Result<PnL, PricingError> {
71        panic!("adjustments_pnl is not implemented for this Strategy.")
72    }
73
74    /// Calculates the profit and loss (PnL) for a given trading position.
75    ///
76    /// # Parameters
77    /// - `_position`: A reference to a trading position (`Position`) for which the PnL is to be calculated.
78    ///
79    /// # Returns
80    /// - `Result<PnL, PricingError>`: This function is intended to return a `PnL` value on success,
81    ///   or an error wrapped in a `PricingError` on failure.
82    ///
83    /// # Errors
84    /// This method will always return an error because it is not implemented.
85    /// A call to this function will result in a panic with the message:
86    /// `"from_position_pnl is not implemented for this Strategy."`
87    ///
88    /// # Panics
89    /// This function will unconditionally panic when called. It serves as a placeholder to indicate
90    /// that the logic for calculating the PnL based on a given position has not been implemented yet.
91    ///
92    /// # Notes
93    /// Override this method in subclasses or implementations of the `Strategy` trait to provide the
94    /// desired functionality for calculating position PnL.
95    fn diff_position_pnl(&self, _position: &Position) -> Result<PnL, PricingError> {
96        panic!("from_position_pnl is not implemented for this Strategy.")
97    }
98}
99
100/// # TransactionAble
101///
102/// A trait that defines the ability to manage financial transactions within an entity.
103///
104/// This trait provides a standardized interface for adding and retrieving transaction records,
105/// enabling consistent transaction management across different implementations.
106///
107/// ## Required Methods
108///
109/// - `add_transaction`: Adds a new transaction to the implementing entity
110/// - `get_transactions`: Retrieves all transactions from the implementing entity
111///
112/// ## Error Handling
113///
114/// Both methods return a `Result` type that may contain a `TransactionError` if the operation fails.
115/// This allows for proper error propagation and handling in transaction-related operations.
116///
117pub trait TransactionAble {
118    /// Adds a new transaction to the implementing entity.
119    fn add_transaction(&mut self, transaction: Transaction) -> Result<(), TransactionError>;
120
121    /// Retrieves all transactions from the implementing entity.
122    fn get_transactions(&self) -> Result<Vec<Transaction>, TransactionError>;
123}
124
125#[cfg(test)]
126mod tests_pnl_calculator {
127    use super::*;
128    use crate::pos;
129    use chrono::Utc;
130    use rust_decimal_macros::dec;
131
132    #[test]
133    fn test_pnl_new() {
134        let now = Utc::now();
135        let pnl = PnL::new(
136            Some(dec!(100.0)),
137            Some(dec!(50.0)),
138            pos!(25.0),
139            pos!(75.0),
140            now,
141        );
142
143        assert_eq!(pnl.realized, Some(dec!(100.0)));
144        assert_eq!(pnl.unrealized, Some(dec!(50.0)));
145        assert_eq!(pnl.initial_costs, 25.0);
146        assert_eq!(pnl.initial_income, 75.0);
147        assert_eq!(pnl.date_time, now);
148    }
149
150    #[test]
151    fn test_pnl_with_none_values() {
152        let now = Utc::now();
153        let pnl = PnL::new(None, None, pos!(10.0), pos!(20.0), now);
154
155        assert_eq!(pnl.realized, None);
156        assert_eq!(pnl.unrealized, None);
157        assert_eq!(pnl.initial_costs, pos!(10.0));
158        assert_eq!(pnl.initial_income, pos!(20.0));
159        assert_eq!(pnl.date_time, now);
160    }
161
162    struct DummyOption;
163
164    impl TransactionAble for DummyOption {
165        fn add_transaction(&mut self, _transaction: Transaction) -> Result<(), TransactionError> {
166            unimplemented!()
167        }
168
169        fn get_transactions(&self) -> Result<Vec<Transaction>, TransactionError> {
170            unimplemented!()
171        }
172    }
173
174    impl PnLCalculator for DummyOption {
175        fn calculate_pnl(
176            &self,
177            market_price: &Positive,
178            expiration_date: ExpirationDate,
179            _implied_volatility: &Positive,
180        ) -> Result<PnL, PricingError> {
181            Ok(PnL::new(
182                Some(market_price.into()),
183                None,
184                pos!(10.0),
185                pos!(20.0),
186                expiration_date.get_date()?,
187            ))
188        }
189
190        fn calculate_pnl_at_expiration(
191            &self,
192            underlying_price: &Positive,
193        ) -> Result<PnL, PricingError> {
194            let underlying_price = underlying_price.to_dec();
195            Ok(PnL::new(
196                Some(underlying_price),
197                None,
198                pos!(10.0),
199                pos!(20.0),
200                Utc::now(),
201            ))
202        }
203    }
204
205    #[test]
206    fn test_pnl_calculator() {
207        let dummy = DummyOption;
208        let now = ExpirationDate::Days(pos!(3.0));
209
210        let pnl = dummy
211            .calculate_pnl(&pos!(100.0), now, &pos!(100.0))
212            .unwrap();
213        assert_eq!(pnl.realized, Some(dec!(100.0)));
214        assert_eq!(pnl.unrealized, None);
215        assert_eq!(pnl.initial_costs, pos!(10.0));
216        assert_eq!(pnl.initial_income, pos!(20.0));
217        assert_eq!(
218            pnl.date_time.format("%Y-%m-%d").to_string(),
219            now.get_date_string().unwrap()
220        );
221
222        let pnl_at_expiration = dummy.calculate_pnl_at_expiration(&pos!(150.0)).unwrap();
223        assert_eq!(pnl_at_expiration.realized, Some(dec!(150.0)));
224        assert_eq!(pnl_at_expiration.unrealized, None);
225        assert_eq!(pnl_at_expiration.initial_costs, pos!(10.0));
226        assert_eq!(pnl_at_expiration.initial_income, pos!(20.0));
227    }
228}