@@ -29,7 +29,7 @@ use rand_distr::{Distribution, Normal};
2929#[ derive( Debug , Default , Clone ) ]
3030pub struct Instrument {
3131 /// Current price of the underlying asset or future price at time 0.
32- pub spot : f64 ,
32+ pub spot : Vec < f64 > ,
3333 /// Maximum spot price of the underlying asset.
3434 pub max_spot : f64 ,
3535 /// Minimum spot price of the underlying asset.
@@ -54,7 +54,7 @@ impl Instrument {
5454 /// A new `Instrument`.
5555 pub fn new ( ) -> Self {
5656 Self {
57- spot : 0.0 ,
57+ spot : vec ! [ 0.0 ] ,
5858 max_spot : 0.0 ,
5959 min_spot : 0.0 ,
6060 continuous_dividend_yield : 0.0 ,
@@ -75,6 +75,20 @@ impl Instrument {
7575 ///
7676 /// The instrument with the spot price set.
7777 pub fn with_spot ( mut self , spot : f64 ) -> Self {
78+ self . spot = vec ! [ spot] ;
79+ self
80+ }
81+
82+ /// Set the spot price of the instrument.
83+ ///
84+ /// # Arguments
85+ ///
86+ /// * `spot` - The spot price of the instrument (i.e., the current price of the underlying asset or future price at time 0).
87+ ///
88+ /// # Returns
89+ ///
90+ /// The instrument with the spot price set.
91+ pub fn with_spots ( mut self , spot : Vec < f64 > ) -> Self {
7892 self . spot = spot;
7993 self
8094 }
@@ -173,7 +187,9 @@ impl Instrument {
173187
174188 let weight = 1.0 / assets. len ( ) as f64 ;
175189 self . assets = assets. iter ( ) . map ( |asset| ( asset. clone ( ) , weight) ) . collect ( ) ;
176- self . spot = self . assets . iter ( ) . map ( |( a, w) | a. spot * w) . sum :: < f64 > ( ) ;
190+ let new_spot = self . assets . iter ( ) . map ( |( a, w) | a. spot ( ) * w) . sum :: < f64 > ( ) ;
191+ self . spot = vec ! [ new_spot] ;
192+
177193 self . sort_assets_by_performance ( ) ;
178194 self
179195 }
@@ -201,7 +217,7 @@ impl Instrument {
201217 pub fn sort_assets_by_performance ( & mut self ) {
202218 self . assets
203219 . sort_by ( |a, b| b. 0 . spot . partial_cmp ( & a. 0 . spot ) . unwrap ( ) ) ;
204- self . spot = self . assets . iter ( ) . map ( |( a, w) | a. spot * w) . sum :: < f64 > ( ) ;
220+ self . spot = vec ! [ ( self . assets. iter( ) . map( |( a, w) | a. spot( ) * w) . sum:: <f64 >( ) ) ] ;
205221 self . sorted = true ;
206222 }
207223
@@ -236,6 +252,36 @@ impl Instrument {
236252 & self . assets . last ( ) . unwrap ( ) . 0
237253 }
238254
255+ /// Get the best-performing asset (mutable).
256+ ///
257+ /// # Returns
258+ ///
259+ /// The best-performing asset.
260+ pub fn best_performer_mut ( & mut self ) -> & mut Instrument {
261+ if self . assets . is_empty ( ) {
262+ return self ;
263+ }
264+ if !self . sorted {
265+ panic ! ( "Assets are not sorted" ) ;
266+ }
267+ & mut self . assets . first_mut ( ) . unwrap ( ) . 0
268+ }
269+
270+ /// Get the worst-performing asset (mutable).
271+ ///
272+ /// # Returns
273+ ///
274+ /// The worst-performing asset.
275+ pub fn worst_performer_mut ( & mut self ) -> & mut Instrument {
276+ if self . assets . is_empty ( ) {
277+ return self ;
278+ }
279+ if !self . sorted {
280+ panic ! ( "Assets are not sorted" ) ;
281+ }
282+ & mut self . assets . last_mut ( ) . unwrap ( ) . 0
283+ }
284+
239285 /// Calculate the adjusted spot price.
240286 ///
241287 /// # Arguments
@@ -248,7 +294,25 @@ impl Instrument {
248294 /// The adjusted spot price.
249295 pub fn calculate_adjusted_spot ( & self , ttm : f64 ) -> f64 {
250296 let n_dividends = self . dividend_times . iter ( ) . filter ( |& & t| t <= ttm) . count ( ) as f64 ;
251- self . spot * ( 1.0 - self . discrete_dividend_yield ) . powf ( n_dividends)
297+ self . spot ( ) * ( 1.0 - self . discrete_dividend_yield ) . powf ( n_dividends)
298+ }
299+
300+ /// Return current spot price.
301+ ///
302+ /// # Returns
303+ ///
304+ /// The current spot price.
305+ pub fn spot ( & self ) -> f64 {
306+ * self . spot . first ( ) . unwrap ( )
307+ }
308+
309+ /// Return terminal spot price.
310+ ///
311+ /// # Returns
312+ ///
313+ /// The terminal spot price.
314+ pub fn terminal_spot ( & self ) -> f64 {
315+ * self . spot . last ( ) . unwrap ( )
252316 }
253317
254318 /// Simulate random asset prices (Euler method)
@@ -267,11 +331,12 @@ impl Instrument {
267331 rng : & mut ThreadRng ,
268332 risk_free_rate : f64 ,
269333 volatility : f64 ,
334+ steps : usize ,
270335 ) -> Vec < f64 > {
271336 let normal = Normal :: new ( 0.0 , 1.0 ) . unwrap ( ) ;
272- let dt: f64 = 1.0 / 252.0 ; // Daily time step
273- let mut prices = vec ! [ self . spot; 252 ] ;
274- for i in 1 ..252 {
337+ let dt: f64 = 1.0 / steps as f64 ; // Daily time step
338+ let mut prices = vec ! [ self . spot( ) ; steps ] ;
339+ for i in 1 ..steps {
275340 let z = normal. sample ( rng) ;
276341 prices[ i] = prices[ i - 1 ]
277342 * ( 1.0
@@ -304,14 +369,14 @@ impl Instrument {
304369 ) -> Vec < f64 > {
305370 let dt = time_to_maturity / steps as f64 ; // Time step
306371 let normal: Normal < f64 > = Normal :: new ( 0.0 , dt. sqrt ( ) ) . unwrap ( ) ; // Adjusted standard deviation
307- let mut logs = vec ! [ self . spot. ln( ) ; steps] ;
372+ let mut logs = vec ! [ self . spot( ) . ln( ) ; steps] ;
308373 for i in 1 ..steps {
309374 let z = normal. sample ( rng) ;
310375 logs[ i] = logs[ i - 1 ]
311376 + ( risk_free_rate - self . continuous_dividend_yield - 0.5 * volatility. powi ( 2 ) ) * dt
312377 + volatility * z;
313378 }
314- logs
379+ logs. iter ( ) . map ( |log| log . exp ( ) ) . collect ( )
315380 }
316381
317382 /// Average asset prices
@@ -337,19 +402,15 @@ impl Instrument {
337402 risk_free_rate : f64 ,
338403 steps : usize ,
339404 ) -> f64 {
340- let prices: Vec < f64 > = match method {
405+ let prices = match method {
341406 SimMethod :: Milstein => unimplemented ! ( "Milstein method not implemented" ) ,
342- SimMethod :: Euler => self . euler_simulation ( rng, risk_free_rate, volatility) ,
407+ SimMethod :: Euler => self . euler_simulation ( rng, risk_free_rate, volatility, steps ) ,
343408 SimMethod :: Log => {
344409 self . log_simulation ( rng, volatility, time_to_maturity, risk_free_rate, steps)
345410 }
346411 } ;
347412
348- let res = prices. iter ( ) . sum :: < f64 > ( ) / ( prices. len ( ) ) as f64 ;
349- match method {
350- SimMethod :: Log => res. exp ( ) ,
351- _ => res,
352- }
413+ prices. iter ( ) . sum :: < f64 > ( ) / ( prices. len ( ) ) as f64
353414 }
354415
355416 /// Geometric average asset prices
@@ -375,18 +436,83 @@ impl Instrument {
375436 risk_free_rate : f64 ,
376437 steps : usize ,
377438 ) -> f64 {
378- let prices: Vec < f64 > = match method {
439+ let prices = match method {
379440 SimMethod :: Milstein => unimplemented ! ( "Milstein method not implemented" ) ,
380- SimMethod :: Euler => self . euler_simulation ( rng, risk_free_rate, volatility) ,
441+ SimMethod :: Euler => self . euler_simulation ( rng, risk_free_rate, volatility, steps ) ,
381442 SimMethod :: Log => {
382443 self . log_simulation ( rng, volatility, time_to_maturity, risk_free_rate, steps)
383444 }
384445 } ;
385446
386- match method {
387- SimMethod :: Log => ( prices. iter ( ) . sum :: < f64 > ( ) / prices. len ( ) as f64 ) . exp ( ) ,
388- _ => ( prices. iter ( ) . map ( |price| price. ln ( ) ) . sum :: < f64 > ( ) / prices. len ( ) as f64 ) . exp ( ) ,
389- }
447+ ( self . spot . iter ( ) . map ( |price| price. ln ( ) ) . sum :: < f64 > ( ) / self . spot . len ( ) as f64 ) . exp ( )
448+ }
449+
450+ /// Average asset prices (mutates the underlying instrument)
451+ ///
452+ /// # Arguments
453+ ///
454+ /// * `rng` - Random number generator.
455+ /// * `method` - Simulation method.
456+ /// * `volatility` - Volatility.
457+ /// * `time_to_maturity` - Time to maturity.
458+ /// * `risk_free_rate` - Risk-free rate.
459+ /// * `steps` - Number of steps.
460+ ///
461+ /// # Returns
462+ ///
463+ /// The average asset price.
464+ pub fn simulate_arithmetic_average_mut (
465+ & mut self ,
466+ rng : & mut ThreadRng ,
467+ method : SimMethod ,
468+ volatility : f64 ,
469+ time_to_maturity : f64 ,
470+ risk_free_rate : f64 ,
471+ steps : usize ,
472+ ) -> f64 {
473+ self . spot = match method {
474+ SimMethod :: Milstein => unimplemented ! ( "Milstein method not implemented" ) ,
475+ SimMethod :: Euler => self . euler_simulation ( rng, risk_free_rate, volatility, steps) ,
476+ SimMethod :: Log => {
477+ self . log_simulation ( rng, volatility, time_to_maturity, risk_free_rate, steps)
478+ }
479+ } ;
480+
481+ self . spot . iter ( ) . sum :: < f64 > ( ) / ( self . spot . len ( ) ) as f64
482+ }
483+
484+ /// Geometric asset prices (mutates the underlying instrument)
485+ ///
486+ /// # Arguments
487+ ///
488+ /// * `rng` - Random number generator.
489+ /// * `method` - Simulation method.
490+ /// * `volatility` - Volatility.
491+ /// * `time_to_maturity` - Time to maturity.
492+ /// * `risk_free_rate` - Risk-free rate.
493+ /// * `steps` - Number of steps.
494+ ///
495+ /// # Returns
496+ ///
497+ /// The geometric average asset price.
498+ pub fn simulate_geometric_average_mut (
499+ & mut self ,
500+ rng : & mut ThreadRng ,
501+ method : SimMethod ,
502+ volatility : f64 ,
503+ time_to_maturity : f64 ,
504+ risk_free_rate : f64 ,
505+ steps : usize ,
506+ ) -> f64 {
507+ self . spot = match method {
508+ SimMethod :: Milstein => unimplemented ! ( "Milstein method not implemented" ) ,
509+ SimMethod :: Euler => self . euler_simulation ( rng, risk_free_rate, volatility, steps) ,
510+ SimMethod :: Log => {
511+ self . log_simulation ( rng, volatility, time_to_maturity, risk_free_rate, steps)
512+ }
513+ } ;
514+
515+ ( self . spot . iter ( ) . map ( |price| price. ln ( ) ) . sum :: < f64 > ( ) / self . spot . len ( ) as f64 ) . exp ( )
390516 }
391517
392518 /// Directly simulate the asset price using the geometric Brownian motion formula
@@ -412,7 +538,7 @@ impl Instrument {
412538 ) -> f64 {
413539 let normal = Normal :: new ( 0.0 , 1.0 ) . unwrap ( ) ;
414540 let dt = time_to_maturity / steps as f64 ;
415- let mut price = self . spot ;
541+ let mut price = self . spot ( ) ;
416542 for _ in 0 ..steps {
417543 let z = normal. sample ( rng) ;
418544 price *= ( ( risk_free_rate - self . continuous_dividend_yield - 0.5 * volatility. powi ( 2 ) )
0 commit comments