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}