#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Some auxiliary finance related functions. @author: bayerc """ import numpy as np from py_vollib_vectorized.implied_volatility import vectorized_implied_volatility from py_vollib_vectorized.models import vectorized_black_scholes def compute_call_price(St, K): """Compute European call prices. Parameters ---------- St : ndarray Array of samples of the stock price at time t, array of shape (M). K : ndarray Array of strike prices, array of shape (k). Return ------ price : ndarray Array of prices of the option, of shape (k). stat : ndarray Array of statistical error estimates for prices of the option, of shape (k). """ k = len(K) M = len(St) price = np.empty(k) stat = np.empty(k) for i in range(k): payoff = np.maximum(St - K[i], 0.0) price[i] = np.mean(payoff) stat[i] = 3 * np.std(payoff) / np.sqrt(M) return price, stat def implied_volatility_smile_call(price, K, t): """Compute the implied volatility. This function is based on the py_vollib_vectorized package. Parameters ---------- price : ndarray Array of option prices of for different moneyness, array of shape (k). K : ndarray Array of moneyness values, array of shape (k). t : scalar The options' maturrity. Return ------ vol : ndarray Array of implied volatilities of the options, of shape (k). """ return vectorized_implied_volatility(price, S=1, K=K, t=t, r=0, flag='c', q=None, on_error='warn', model='black_scholes', return_as="array") def compute_call_prices_vols(T: float, ST: np.ndarray, K_list: np.ndarray)\ -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Compute a smile of call prices. Parameters ---------- T: float Maturity of the options. ST: array Array of samples of the prices of the underlying at the maturity, array of shape (M). K_list: array Array of strike prices, of length L. Return ------ prices: array Array of call option prices at the maturity and the strikes, shape (L). stats: array Array of statistical errors corresponding to the prices, shape (L), vols: array Array of implied vols corresponding to the prices, of shape (L). vols_up: array Implied vols corresponding to prices + stats, array of shape (L). vols_down: array Implied vols corresponding to prices - stats, array of shape (L).""" prices, stats = compute_call_price(ST, K_list) vols = implied_volatility_smile_call(prices, K_list, T) vols_up = implied_volatility_smile_call(prices + stats, K_list, T) vols_down = implied_volatility_smile_call(\ np.maximum(prices-stats, 1e-8), K_list, T) return prices,stats,vols,vols_up,vols_down def compute_call_surface(T: np.ndarray, ST: np.ndarray, K_list: np.ndarray) \ -> tuple[np.ndarray,np.ndarray,np.ndarray,np.ndarray,np.ndarray]: """ Compute a surface of call prices. Parameters ---------- T: array Array of maturities of length N. ST: array Array of samples of the prices of the underlying at the maturities, array of shape (M, N). K_list: array Array of strike prices, of length L. Return ------ prices: array Array of call option prices at the maturities and strikes, shape (N,L). stats: array Array of statistical errors corresponding to the prices, shape (N,L), vols: array Array of implied vols corresponding to the prices, of shape (N,L). vols_up: array Implied vols corresponding to prices + stats, array of shape (N,L). vols_down: array Implied vols corresponding to prices - stats, array of shape (N,L).""" _, N = ST.shape L = len(K_list) prices = np.zeros((N,L)) stats = np.zeros_like(prices) vols = np.zeros_like(prices) vols_up = np.zeros_like(prices) vols_down = np.zeros_like(prices) for k in range(N): prices[k,:], stats[k,:] = compute_call_price(ST[:,k], K_list) vols[k,:] = implied_volatility_smile_call(prices[k,:], K_list, T[k]) vols_up[k,:] = implied_volatility_smile_call(prices[k,:] + stats[k,:], K_list, T[k]) vols_down[k,:] = implied_volatility_smile_call(\ np.maximum(prices[k,:]-stats[k,:], 1e-8), K_list, T[k]) return prices, stats, vols, vols_up, vols_down def compute_call_surface_scaled(T: np.ndarray, ST: np.ndarray, S0: float, K_list: np.ndarray) \ -> tuple[np.ndarray,np.ndarray,np.ndarray,np.ndarray,np.ndarray]: """ Compute a surface of call prices, where log-moneyness is scaled with sqrt of maturity. Parameters ---------- T: array Array of maturities of length N. ST: array Array of samples of the prices of the underlying at the maturities, array of shape (M, N). S0: float Spot price. K_list: array Array of strike prices, of length L. Return ------ prices: array Array of call option prices at the maturities and strikes, shape (N,L). stats: array Array of statistical errors corresponding to the prices, shape (N,L), vols: array Array of implied vols corresponding to the prices, of shape (N,L). vols_up: array Implied vols corresponding to prices + stats, array of shape (N,L). vols_down: array Implied vols corresponding to prices - stats, array of shape (N,L).""" _, N = ST.shape L = len(K_list) prices = np.zeros((N,L)) stats = np.zeros_like(prices) vols = np.zeros_like(prices) vols_up = np.zeros_like(prices) vols_down = np.zeros_like(prices) for k in range(N): K_scaled = S0 * (K_list / S0) ** np.sqrt(T[k]) prices[k,:], stats[k,:] = compute_call_price(ST[:,k], K_scaled) vols[k,:] = implied_volatility_smile_call(prices[k,:], K_scaled, T[k]) vols_up[k,:] = implied_volatility_smile_call(prices[k,:] + stats[k,:], K_scaled, T[k]) vols_down[k,:] = implied_volatility_smile_call(\ np.maximum(prices[k,:]-stats[k,:], 1e-8), K_scaled, T[k]) return prices, stats, vols, vols_up, vols_down def main(): t = 0.5 S0 = 1 sigma = 0.2 r = 0 K = np.array([0.95, 1.0, 1.05]) price = vectorized_black_scholes('c', S0, K, t, r, sigma, return_as="array") vol = implied_volatility_smile_call(price, K, t) print(f"For {sigma = } and {K = }:") print(f"{price = }") print(f"{vol =}") if __name__ == '__main__': main()