22
33from functools import lru_cache
44
5- import numpy as np
65from numpy import ndarray , exp , abs , round , diff , flatnonzero , arange , inf
76from numpy .lib .scimath import log , sqrt
87from numpy .random import normal , laplace
@@ -25,9 +24,9 @@ def get_pl_profile(
2524 x : float ,
2625 val : float ,
2726 n : int ,
28- s : np . ndarray ,
27+ s : ndarray ,
2928 commission : float = 0.0 ,
30- ) -> tuple [np . ndarray , float ]:
29+ ) -> tuple [ndarray , float ]:
3130 """
3231 get_pl_profile(option_type, action, x, val, n, s, commission) -> returns the profit/loss
3332 profile and cost of an option trade at expiration.
@@ -60,8 +59,8 @@ def get_pl_profile(
6059
6160
6261def get_pl_profile_stock (
63- s0 : float , action : Action , n : int , s : np . ndarray , commission : float = 0.0
64- ) -> tuple [np . ndarray , float ]:
62+ s0 : float , action : Action , n : int , s : ndarray , commission : float = 0.0
63+ ) -> tuple [ndarray , float ]:
6564 """
6665 get_pl_profile_stock(s0, action, n, s, commission) -> returns the profit/loss
6766 profile and cost of a stock position.
@@ -94,7 +93,7 @@ def get_pl_profile_bs(
9493 target_to_maturity_years : float ,
9594 volatility : float ,
9695 n : int ,
97- s : np . ndarray ,
96+ s : ndarray ,
9897 y : float = 0.0 ,
9998 commission : float = 0.0 ,
10099):
@@ -137,7 +136,7 @@ def get_pl_profile_bs(
137136
138137
139138@lru_cache
140- def create_price_seq (min_price : float , max_price : float ) -> np . ndarray :
139+ def create_price_seq (min_price : float , max_price : float ) -> ndarray :
141140 """
142141 create_price_seq(min_price, max_price) -> generates a sequence of stock prices
143142 from 'min_price' to 'max_price' with increment $0.01.
@@ -164,7 +163,7 @@ def create_price_samples(
164163 distribution : Distribution = "black-scholes" ,
165164 y : float = 0.0 ,
166165 n : int = 100_000 ,
167- ) -> np . ndarray :
166+ ) -> ndarray :
168167 """
169168 create_price_samples(s0, volatility, years_to_maturity, r, distribution, y, n) -> generates
170169 random stock prices at maturity according to a statistical distribution.
@@ -177,28 +176,26 @@ def create_price_samples(
177176 r: annualized risk-free interest rate (default is 0.01). Used only if
178177 distribution is 'black-scholes'.
179178 distribution: statistical distribution used to generate random stock prices
180- at maturity. It can be 'black-scholes' (default), 'normal' or
181- 'laplace'.
179+ at maturity. It can be 'black-scholes' (default; 'normal' is
180+ equivalent) or 'laplace'.
182181 y: annualized dividend yield (default is zero).
183182 n: number of randomly generated terminal prices.
184183 """
185- if distribution == "normal" :
186- return exp (normal (log (s0 ), volatility * sqrt (years_to_maturity ), n ))
187- elif distribution == "black-scholes" :
188- drift = (r - y - 0.5 * volatility * volatility ) * years_to_maturity
184+ drift = (r - y - 0.5 * volatility * volatility ) * years_to_maturity
189185
186+ if distribution in ("black-scholes" , "normal" ):
190187 return exp (normal ((log (s0 ) + drift ), volatility * sqrt (years_to_maturity ), n ))
191188 elif distribution == "laplace" :
192189 return exp (
193- laplace (log (s0 ), (volatility * sqrt (years_to_maturity )) / sqrt (2.0 ), n )
190+ laplace (
191+ (log (s0 ) + drift ), (volatility * sqrt (years_to_maturity )) / sqrt (2.0 ), n
192+ )
194193 )
195194 else :
196195 raise ValueError ("Distribution not implemented yet!" )
197196
198197
199- def get_profit_range (
200- s : np .ndarray , profit : np .ndarray , target : float = 0.01
201- ) -> list [Range ]:
198+ def get_profit_range (s : ndarray , profit : ndarray , target : float = 0.01 ) -> list [Range ]:
202199 """
203200 get_profit_range(s, profit, target) -> returns pairs of stock prices, as a list,
204201 for which an option trade is expected to get the desired profit in between.
@@ -252,13 +249,12 @@ def get_pop(
252249 get_pop(profit_ranges, source, kwargs) -> estimates the probability of profit
253250 (PoP) of an option trade.
254251
255- * For 'source="normal "' or 'source="laplace"' : the probability of
256- profit is calculated assuming either a (log)normal or a (log)Laplace
257- distribution of terminal stock prices at maturity .
252+ * For 'source="black-scholes "' (default; 'normal' is equivalent) : the probability
253+ of profit is calculated assuming a (log)normal distribution as implemented in
254+ the Black-Scholes model .
258255
259- * For 'source="black-scholes"' (default): the probability of profit
260- is calculated assuming a (log)normal distribution with risk neutrality
261- as implemented in the Black-Scholes model.
256+ * For 'source="laplace"': the probability of profit is calculated assuming
257+ a (log)Laplace distribution of terminal stock prices at maturity.
262258
263259 * For 'source="array"': the probability of profit is calculated
264260 from a 1D numpy array of stock prices typically at maturity generated
@@ -282,24 +278,17 @@ def get_pop(
282278 stock_price = inputs .stock_price
283279 volatility = inputs .volatility
284280 years_to_maturity = inputs .years_to_maturity
285- drift = 0.0
286-
287- if inputs .source == "black-scholes" :
288- r = (
289- inputs .interest_rate or 0.0
290- ) # 'or' just for typing purposes, as `interest_rate` must be non-zero
291- y = inputs .dividend_yield
292-
293- drift = (r - y - 0.5 * volatility * volatility ) * years_to_maturity
294-
281+ r = (
282+ inputs .interest_rate or 0.0
283+ ) # 'or' just for typing purposes, as `interest_rate` must be non-zero
284+ y = inputs .dividend_yield
285+ drift = (r - y - 0.5 * volatility * volatility ) * years_to_maturity
295286 sigma = volatility * sqrt (years_to_maturity )
296287
297288 if sigma == 0.0 :
298289 sigma = 1e-10
299290
300- beta = 0.0
301- if inputs .source == "laplace" :
302- beta = sigma / sqrt (2.0 )
291+ beta = sigma / sqrt (2.0 )
303292
304293 for p_range in profit_ranges :
305294 lval = p_range [0 ]
@@ -314,8 +303,8 @@ def get_pop(
314303 ) - stats .norm .cdf ((log (lval / stock_price ) - drift ) / sigma )
315304 else :
316305 pop += stats .laplace .cdf (
317- log (hval / stock_price ) / beta
318- ) - stats .laplace .cdf (log (lval / stock_price ) / beta )
306+ ( log (hval / stock_price ) - drift ) / beta
307+ ) - stats .laplace .cdf (( log (lval / stock_price ) - drift ) / beta )
319308
320309 elif isinstance (inputs , ProbabilityOfProfitArrayInputs ):
321310 stocks = inputs .array
@@ -338,8 +327,8 @@ def get_pop(
338327
339328
340329def _get_pl_option (
341- option_type : OptionType , opvalue : float , action : Action , s : np . ndarray , x : float
342- ) -> np . ndarray :
330+ option_type : OptionType , opvalue : float , action : Action , s : ndarray , x : float
331+ ) -> ndarray :
343332 """
344333 getPLoption(option_type,opvalue,action,s,x) -> returns the profit (P) or loss
345334 (L) per option of an option trade at expiration.
@@ -361,7 +350,7 @@ def _get_pl_option(
361350 raise ValueError ("Action must be either 'sell' or 'buy'!" )
362351
363352
364- def _get_payoff (option_type : OptionType , s : np . ndarray , x : float ) -> np . ndarray :
353+ def _get_payoff (option_type : OptionType , s : ndarray , x : float ) -> ndarray :
365354 """
366355 get_payoff(option_type, s, x) -> returns the payoff of an option trade at expiration.
367356
@@ -380,7 +369,7 @@ def _get_payoff(option_type: OptionType, s: np.ndarray, x: float) -> np.ndarray:
380369 raise ValueError ("Option type must be either 'call' or 'put'!" )
381370
382371
383- def _get_pl_stock (s0 : float , action : Action , s : np . ndarray ) -> np . ndarray :
372+ def _get_pl_stock (s0 : float , action : Action , s : ndarray ) -> ndarray :
384373 """
385374 get_pl_stock(s0,action,s) -> returns the profit (P) or loss (L) of a stock
386375 position.
0 commit comments