# Third party imports import numpy as np from scipy.stats import norm import matplotlib.pyplot as plt # Local package imports from .Base import OptionPricingModel class MonteCarloPricing(OptionPricingModel): """ Class implementing calculation for European option price using Monte Carlo Simulation. We simulate underlying asset price on expiry date using random stochastic process - Brownian motion. For the simulation generated prices at maturity, we calculate and sum up their payoffs, average them and discount the final value. That value represents option price """ def __init__(self, underlying_spot_price, strike_price, days_to_maturity, risk_free_rate, sigma, number_of_simulations): """ Initializes variables used in Black-Scholes formula . underlying_spot_price: current stock or other underlying spot price strike_price: strike price for option cotract days_to_maturity: option contract maturity/exercise date risk_free_rate: returns on risk-free assets (assumed to be constant until expiry date) sigma: volatility of the underlying asset (standard deviation of asset's log returns) number_of_simulations: number of potential random underlying price movements """ # Parameters for Brownian process self.S_0 = underlying_spot_price self.K = strike_price self.T = days_to_maturity / 365 self.r = risk_free_rate self.sigma = sigma # Parameters for simulation self.N = number_of_simulations self.num_of_steps = days_to_maturity self.dt = self.T / self.num_of_steps def simulate_prices(self): """ Simulating price movement of underlying prices using Brownian random process. Saving random results. """ np.random.seed(20) self.simulation_results = None # Initializing price movements for simulation: rows as time index and columns as different random price movements. S = np.zeros((self.num_of_steps, self.N)) # Starting value for all price movements is the current spot price S[0] = self.S_0 for t in range(1, self.num_of_steps): # Random values to simulate Brownian motion (Gaussian distibution) Z = np.random.standard_normal(self.N) # Updating prices for next point in time S[t] = S[t - 1] * np.exp((self.r - 0.5 * self.sigma ** 2) * self.dt + (self.sigma * np.sqrt(self.dt) * Z)) self.simulation_results_S = S def _calculate_call_option_price(self): """ Call option price calculation. Calculating payoffs for simulated prices at expiry date, summing up, averiging them and discounting. Call option payoff (it's exercised only if the price at expiry date is higher than a strike price): max(S_t - K, 0) """ if self.simulation_results_S is None: return -1 return np.exp(-self.r * self.T) * 1 / self.N * np.sum(np.maximum(self.simulation_results_S[-1] - self.K, 0)) def _calculate_put_option_price(self): """ Put option price calculation. Calculating payoffs for simulated prices at expiry date, summing up, averiging them and discounting. Put option payoff (it's exercised only if the price at expiry date is lower than a strike price): max(K - S_t, 0) """ if self.simulation_results_S is None: return -1 return np.exp(-self.r * self.T) * 1 / self.N * np.sum(np.maximum(self.K - self.simulation_results_S[-1], 0)) def plot_simulation_results(self, num_of_movements): """Plots specified number of simulated price movements.""" plt.figure(figsize=(12,8)) plt.plot(self.simulation_results_S[:,0:num_of_movements]) plt.axhline(self.K, c='k', xmin=0, xmax=self.num_of_steps, label='Strike Price') plt.xlim([0, self.num_of_steps]) plt.ylabel('Simulated price movements') plt.xlabel('Days in future') plt.title(f'First {num_of_movements}/{self.N} Random Price Movements') plt.legend(loc='best') plt.show()