├── .gitignore ├── .idea ├── aws.xml └── workspace.xml ├── AnalyticEngines ├── BetaZeroSabr │ ├── EuropeanOptionTools.py │ ├── Examples │ │ ├── AtmImpliedVolNormalSabr.py │ │ ├── ExactlyIntegrationExample.py │ │ ├── ImpliedVolVsVolSwap.py │ │ ├── LocalVolSabr.py │ │ ├── SkewAtmNormalSabr.py │ │ ├── SmileToMaturityNormalSabr.py │ │ ├── WatanabeAtmExample.py │ │ └── WatanabeSmileExample.py │ ├── ExpansionTools.py │ └── __init__.py ├── FourierMethod │ ├── COSMethod │ │ ├── COSBlocksOptions.py │ │ ├── COSRepresentation.py │ │ └── __init__.py │ ├── CharesticFunctions │ │ ├── HestonCharesticFunction.py │ │ ├── JumpDiffusionCharesticFunction.py │ │ └── __init__.py │ ├── Examples │ │ ├── Example_Bates.py │ │ ├── Example_CGMY.py │ │ ├── Example_COS_Method.py │ │ ├── Example_NIG.py │ │ └── Example_VG.py │ └── __init__.py ├── LocalVolatility │ ├── Dupire │ │ ├── DupireFormulas.py │ │ ├── NonParametricLV.py │ │ └── __init__.py │ ├── Hagan │ │ ├── ExpansionLocVol.py │ │ └── __init__.py │ └── __init__.py ├── MalliavinMethod │ ├── EuropeanOptionExpansion.py │ ├── ExpansionTools.py │ └── __init__.py ├── VolatilityTools │ ├── CEVMalliavinTools.py │ ├── HestonTool.py │ ├── NonParametricEstimatorSLV.py │ ├── VolatilityEstimators.py │ └── __init__.py └── __init__.py ├── Examples ├── Chapter1 │ ├── Data │ │ ├── HistoricalStoxx50E.txt │ │ └── SabrSurfaceParameter.txt │ ├── Example_1_2_6.py │ ├── Example_1_3_1.py │ └── Example_1_4_1.py ├── Chapter10 │ ├── Data │ │ └── VIX_Info.txt │ ├── Example_10_2_4.py │ ├── Example_10_2_5.py │ ├── Example_10_2_6.py │ ├── Example_10_2_7.py │ ├── Figure_10_1.py │ └── Old │ │ ├── atm_iv_vix_heston.py │ │ ├── atm_iv_vix_rbergomi.py │ │ ├── atm_iv_vix_sabr.py │ │ └── smile_vix_2fbergomi.py ├── Chapter2 │ ├── Data │ │ ├── ForwardsStoxx50e.txt │ │ ├── SPX_sample.txt │ │ ├── SabrParametersStoxx50e.txt │ │ └── VolAtmSabrStoxx50e.txt │ ├── Example_2_1_1.py │ ├── Example_2_1_3.py │ ├── Example_2_2_4.py │ ├── Example_2_2_5.py │ ├── Example_2_3_1.py │ ├── Example_2_3_1_short_term.py │ ├── Example_2_3_2.py │ ├── Example_2_3_2_short_term.py │ ├── Example_2_3_3.py │ ├── Example_2_4_1.py │ ├── Old │ │ └── Example11.py │ ├── Remark_2_2_6.py │ ├── skew_Heston_differents_time.py │ └── skew_rBergomi_differents_times.py ├── Chapter3 │ ├── Figure_3_1.py │ └── Figure_3_2.py ├── Chapter4 │ ├── Example_4_2_5.py │ └── Example_4_2_6.py ├── Chapter5 │ ├── Figure_5_3.py │ ├── Figure_5_4.py │ ├── Figure_5_5.py │ ├── Figure_5_5_1.py │ ├── Figure_5_5_2.py │ ├── Figure_5_6.py │ └── Old │ │ └── ExampleFbm.py ├── Chapter6 │ ├── Example_6_5_17.py │ ├── Example_6_5_18.py │ ├── Example_6_5_19.py │ ├── Example_6_5_20.py │ └── Old │ │ ├── HestonOptionApproximation.py │ │ ├── SabrOptionApproximation.py │ │ └── level_atm_rbergomi_multi_h.py ├── Chapter7 │ ├── Data │ │ ├── HistoricalStoxx50E.txt │ │ └── SabrSurfaceParameter.txt │ ├── Example_Elisa_Jorge.py │ ├── Example_Elisa_Jorge_2.py │ ├── Figure_7_2.py │ ├── Figure_7_3.py │ ├── Figure_7_4.py │ ├── Figure_7_5.py │ ├── Figure_7_7_Heston.py │ ├── Figure_7_7_Merton.py │ ├── Figure_7_7_RBergomi.py │ └── Old │ │ ├── atm_skew_heston_different_rho.py │ │ ├── skew_atm_cev_short_term.py │ │ ├── skew_atm_heston_short_term.py │ │ └── skew_atm_sabr_short_term.py ├── Chapter8 │ ├── Data │ │ └── SabrSurfaceParameter.txt │ ├── Eample_8_3_6.py │ ├── Example_8_4_6.py │ ├── Figure_8_1.py │ ├── Figure_8_2.py │ └── Old │ │ ├── Example_smile_atm_sabr_short_term.py │ │ ├── curvature_atm_rho_sabr.py │ │ ├── smile_atm_cev_short_term.py │ │ ├── smile_atm_heston_short_term.py │ │ └── smile_atm_rbergomi_term.py ├── Chapter9 │ ├── Example_9_4_5.py │ ├── Example_9_6_6.py │ └── Example_9_6_8.py └── DistributionApproximation │ └── BSDistributionApproximation.py ├── FractionalBrownian ├── ToolsFBM.py ├── __init__.py └── fBM.py ├── Instruments ├── EuropeanInstruments.py ├── ForwardStartEuropeanInstrument.py └── __init__.py ├── MCPricers ├── EuropeanPricers.py ├── ForwardStartEuropeanPricers.py └── __init__.py ├── MC_Engines ├── GenericSDE │ ├── EuropeanGreeksSimulation.py │ ├── SDE.py │ ├── SDESimulation.py │ └── __init__.py ├── MC_Heston │ ├── HestonTools.py │ ├── Heston_Engine.py │ ├── VarianceMC.py │ └── __init__.py ├── MC_LocalVol │ ├── LocalVolEngine.py │ ├── LocalVolFunctionals.py │ └── __init__.py ├── MC_MixedLogNormal │ ├── MixedLogNormalEngine.py │ └── __init__.py ├── MC_RBergomi │ ├── MixedRBergomi_Engine.py │ ├── RBergomi_Engine.py │ ├── RBergomi_Variance_Engine.py │ ├── ToolsVariance.py │ ├── ToolsVarianceMixedRBergomi.py │ ├── __init__.py │ ├── rExpOU1F_Engine.py │ └── readme.txt ├── MC_SABR │ ├── Examples │ │ └── Example_Check_Distribution.py │ ├── SABRTools.py │ ├── SABR_Engine.py │ ├── SABR_Normal_Engine.py │ ├── VarianceMC.py │ ├── VarianceSamplingMatchingMoment.py │ └── __init__.py ├── MC_SRoughVolatility │ ├── Examples │ │ ├── Example_cov_matrix.py │ │ └── SmileExample.py │ ├── SRoughVolatility_Engine.py │ ├── ToolsVariance.py │ └── __init__.py └── __init__.py ├── PyStochasticVolatility.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── README.md ├── Solvers ├── ODE.py ├── ODESolver.py ├── ODE_Solver │ ├── ODE.py │ ├── ODESolver.py │ └── __init__.py ├── PDE_Solver │ ├── BoundariesConditions.py │ ├── Examples │ │ ├── Example_1.py │ │ ├── Example_2.py │ │ └── __init__.py │ ├── Meshes.py │ ├── Operators.py │ ├── PDEOperators.py │ ├── PDESolvers.py │ ├── PDEs.py │ ├── Schemes.py │ ├── TerminalConditions.py │ ├── Tools.py │ ├── Types.py │ └── __init__.py └── __init__.py ├── Tools ├── AnalyticTools.py ├── Bachelier.py ├── Meshes.py ├── RNG.py ├── Types.py └── __init__.py ├── VolatilitySurface ├── IVParametric.py ├── TermStructureVolatility.py ├── Tools │ ├── ParameterTools.py │ ├── SABRTools.py │ ├── SVITools.py │ └── __init__.py └── __init__.py ├── dist └── PyStochasticVolatility-1.0-py3-none-any.whl └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.iml 3 | *.pyc 4 | .idea/dictionaries/Pc.xml 5 | .idea/inspectionProfiles/profiles_settings.xml 6 | .idea/inspectionProfiles/Project_Default.xml 7 | .idea/misc.xml 8 | .idea/modules.xml 9 | .idea/other.xml 10 | .idea/vcs.xml 11 | -------------------------------------------------------------------------------- /.idea/aws.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/AtmImpliedVolNormalSabr.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Normal_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from Tools.Bachelier import implied_volatility, bachelier 8 | from scipy.optimize import curve_fit 9 | from AnalyticEngines.MalliavinMethod.ExpansionTools import get_vol_swap_approximation_sabr 10 | from VolatilitySurface.Tools import SABRTools 11 | 12 | 13 | dt = np.arange(180, 360, 5) * 1.0 / 365.0 14 | no_dt_s = len(dt) 15 | 16 | # simulation info 17 | alpha = 0.3 18 | nu = 0.4 19 | rho = 0.0 20 | parameters = [alpha, nu, rho] 21 | no_time_steps = 100 22 | 23 | seed = 123456789 24 | no_paths = 500000 25 | delta_time = 1.0 / 365.0 26 | 27 | # random number generator 28 | rnd_generator = RNG.RndGenerator(seed) 29 | 30 | # option information 31 | f0 = 0.01 32 | options = [] 33 | implied_vol_atm = [] 34 | for d_i in dt: 35 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 36 | 37 | # outputs 38 | vol_swap_approximation = [] 39 | vol_swap_mc = [] 40 | implied_vol_atm = [] 41 | implied_vol_approx = [] 42 | output = [] 43 | 44 | for i in range(0, no_dt_s): 45 | rnd_generator.set_seed(seed) 46 | map_output = SABR_Normal_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, no_paths, no_time_steps, 47 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 48 | 49 | mc_option_price = options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 50 | implied_vol_atm.append(implied_volatility(mc_option_price[0], f0, f0, dt[i], 'c')) 51 | iv_hagan = SABRTools.sabr_normal_jit(f0, f0, alpha, rho, nu, dt[i]) 52 | option_bachelier_price = bachelier(f0, f0, dt[i], implied_vol_atm[-1], 'c') 53 | vol_swap_approximation.append(get_vol_swap_approximation_sabr(parameters, 0.0, dt[i], alpha)) 54 | vol_swap_mc.append(np.mean(np.sqrt(np.sum(map_output[Types.SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS], 1) / dt[i]))) 55 | output.append((implied_vol_atm[-1] - vol_swap_mc[-1])) 56 | 57 | # curve fit 58 | 59 | 60 | def f_law(x, a, b, c): 61 | return a + b * np.power(x, c) 62 | 63 | 64 | popt, pcov = curve_fit(f_law, dt, output) 65 | y_fit_values = f_law(dt, *popt) 66 | 67 | # plt.plot(dt, output, label='(I(t,f0) - E(v_t))', linestyle='--', color='black') 68 | plt.plot(dt, output, label='I(t,f0) - E(v_t)', linestyle='--') 69 | plt.plot(dt, y_fit_values, label="%s + %s * t^%s" % (round(popt[0], 5), round(popt[1], 5), round(popt[2], 5)), marker='.', 70 | linestyle='--') 71 | 72 | plt.title("rho=%s" % rho) 73 | plt.xlabel('T') 74 | plt.legend() 75 | 76 | plt.show() -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/ExactlyIntegrationExample.py: -------------------------------------------------------------------------------- 1 | import EuropeanOptionTools 2 | import numpy as np 3 | import matplotlib.pylab as plt 4 | 5 | 6 | # parameters 7 | f0 = 0.0435 8 | k = 0.0435 9 | alpha = 0.0068 10 | nu = 0.3691 11 | rho = - 0.0286 12 | t = 10.0 13 | 14 | b_min = EuropeanOptionTools.get_b_min(alpha, rho, nu, f0, k) 15 | b_max = 5.0 16 | no_points = 100 17 | b_i_s = np.linspace(b_min, b_max, no_points) 18 | h_i_s = np.zeros(no_points) 19 | g_i_s = np.zeros(no_points) 20 | prod_h_g_i_s = np.zeros(no_points) 21 | 22 | for i in range(0, no_points): 23 | h_i_s[i] = EuropeanOptionTools.h(b_i_s[i], alpha, rho, nu, f0, k) 24 | g_i_s[i] = EuropeanOptionTools.g(b_i_s[i], nu, t) 25 | prod_h_g_i_s[i] = h_i_s[i] * g_i_s[i] 26 | 27 | plt.plot(b_i_s, h_i_s, label="h*g") 28 | plt.legend() 29 | plt.show() 30 | 31 | option_value = EuropeanOptionTools.call_option_price(f0, k, t, alpha, rho, nu) 32 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/ImpliedVolVsVolSwap.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | 4 | from AnalyticEngines.BetaZeroSabr import ExpansionTools 5 | from AnalyticEngines.MalliavinMethod.ExpansionTools import get_vol_swap_approximation_sabr 6 | from scipy.optimize import curve_fit 7 | 8 | # option info 9 | f0 = 0.02 10 | strike = f0 11 | ts = np.linspace(0.05, 5, 200) 12 | 13 | # sabr parameters 14 | alpha = 0.007 15 | nu = 0.5 16 | rho = 0.0 17 | parameters = [alpha, nu, rho] 18 | 19 | iv_watanabe = [] 20 | vol_swap = [] 21 | diff = [] 22 | 23 | for i in range(0, len(ts)): 24 | iv_watanabe.append(ExpansionTools.get_iv_normal_sabr_watanabe_expansion(f0, strike, ts[i], alpha, nu, rho)) 25 | vol_swap.append(get_vol_swap_approximation_sabr(parameters, 0.0, ts[i], alpha)) 26 | diff.append(iv_watanabe[-1] - vol_swap[-1]) 27 | 28 | 29 | def f_law(x, a, b, c): 30 | return a + b * np.power(x, c) 31 | 32 | 33 | popt, pcov = curve_fit(f_law, ts, diff) 34 | y_fit_values = f_law(ts, *popt) 35 | 36 | plt.plot(ts, diff, label='v_t - iv_t', linestyle='dotted') 37 | plt.plot(ts, y_fit_values, label="%s + %s * t^%s" % (round(popt[0], 5), round(popt[1], 5), round(popt[2], 5)), linestyle='--') 38 | # plt.plot(ts, iv_watanabe, label='iv_t', linestyle='--') 39 | 40 | plt.title("v_t vs iv_t") 41 | 42 | plt.legend() 43 | plt.show() 44 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/SkewAtmNormalSabr.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Normal_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from Tools.Bachelier import implied_volatility 8 | from scipy.optimize import curve_fit 9 | 10 | dt = np.arange(7, 180, 2) * (1.0 / 365.0) 11 | no_dt_s = len(dt) 12 | 13 | # simulation info 14 | alpha = 0.5 15 | nu = 0.5 16 | rho = -0.4 17 | parameters = [alpha, nu, rho] 18 | no_time_steps = 100 19 | 20 | seed = 123456789 21 | no_paths = 750000 22 | delta_time = 1.0 / 365.0 23 | 24 | # random number generator 25 | rnd_generator = RNG.RndGenerator(seed) 26 | 27 | # option information 28 | f0 = 0.005 29 | options = [] 30 | options_shift_right = [] 31 | options_shift_left = [] 32 | shift_spot = 0.0001 33 | for d_i in dt: 34 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 35 | options_shift_right.append( 36 | EuropeanOption(f0 * (1.0 + shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 37 | options_shift_left.append( 38 | EuropeanOption(f0 * (1.0 - shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 39 | 40 | # outputs 41 | skew_atm_mc = [] 42 | vol_swap_mc = [] 43 | vol_swap_approx = [] 44 | diff_vol_swap = [] 45 | diff_vol_swap_approx = [] 46 | diff_log_error = [] 47 | 48 | for i in range(0, no_dt_s): 49 | rnd_generator.set_seed(seed) 50 | map_output = SABR_Normal_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, no_paths, no_time_steps, 51 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 52 | 53 | mc_option_price = options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 54 | 55 | mc_option_price_shift_right = options_shift_right[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 56 | 57 | mc_option_price_shift_left = options_shift_left[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 58 | 59 | implied_vol_atm = implied_volatility(mc_option_price[0], f0, f0, dt[i], 'c') 60 | 61 | implied_vol_shift_right = implied_volatility(mc_option_price_shift_right[0], f0, f0 * (1.0 + shift_spot), dt[i], 62 | 'c') 63 | 64 | implied_vol_shift_left = implied_volatility(mc_option_price_shift_left[0], f0, f0 * (1.0 - shift_spot), dt[i], 'c') 65 | 66 | skew_atm_mc.append((implied_vol_shift_right - implied_vol_shift_left) / (2.0 * shift_spot * f0)) 67 | 68 | 69 | def f_law(x, a, b): 70 | return a * np.power(x, b) 71 | 72 | 73 | popt, pcov = curve_fit(f_law, dt, skew_atm_mc) 74 | y_fit_values = f_law(dt, *popt) 75 | 76 | plt.plot(dt, skew_atm_mc, label='dIV(F0,T)/dF0', color='black', linestyle='--') 77 | plt.plot(dt, y_fit_values, label="%s * t ^ %s" % (round(popt[0], 5), round(popt[1], 5)), marker='.', 78 | linestyle='--', color='black') 79 | 80 | plt.xlabel('T') 81 | plt.legend() 82 | plt.show() 83 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/SmileToMaturityNormalSabr.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Normal_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from Tools.Bachelier import implied_volatility, bachelier 8 | 9 | # simulation info 10 | alpha = 0.07 11 | nu = 0.2 12 | rho = 0.0 13 | parameters = [alpha, nu, rho] 14 | no_time_steps = 100 15 | 16 | seed = 123456789 17 | no_paths = 500000 18 | delta_time = 1.0 / 365.0 19 | 20 | # random number generator 21 | rnd_generator = RNG.RndGenerator(seed) 22 | 23 | # option information 24 | f0 = 0.01 25 | t = 1.0 26 | options = [] 27 | 28 | strikes = np.linspace(-0.05, 0.05, 100) 29 | no_strikes = len(strikes) 30 | 31 | for k_i in strikes: 32 | options.append(EuropeanOption(k_i, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, t)) 33 | 34 | # outputs 35 | implied_vol = [] 36 | 37 | # paths 38 | rnd_generator.set_seed(seed) 39 | map_output = SABR_Normal_Engine.get_path_multi_step(0.0, t, parameters, f0, no_paths, no_time_steps, 40 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 41 | 42 | for i in range(0, no_strikes): 43 | npv = options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 44 | implied_vol.append(implied_volatility(npv[0], f0, strikes[i], t, 'c')) 45 | bachelier_npv = bachelier(f0, strikes[i], t, implied_vol[-1], 'c') 46 | 47 | 48 | plt.title("T=%s and nu=%s" % (t, nu)) 49 | plt.plot(strikes, implied_vol, label='iv', linestyle='--', color='black', marker='.') 50 | 51 | 52 | plt.xlabel('K') 53 | plt.legend() 54 | 55 | plt.show() 56 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/WatanabeAtmExample.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from AnalyticEngines.BetaZeroSabr import ExpansionTools 5 | from AnalyticEngines.MalliavinMethod.ExpansionTools import get_vol_swap_approximation_sabr 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from VolatilitySurface.Tools import SABRTools 8 | 9 | # option info 10 | f0 = 0.02 11 | strike = f0 12 | ts = np.linspace(0.05, 5, 200) 13 | strikes = [] 14 | options = [] 15 | for tsi in ts: 16 | options.append(EuropeanOption(strike, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, tsi)) 17 | 18 | # sabr parameters 19 | alpha = 0.007 20 | nu = 0.5 21 | rho = 0.0 22 | parameters = [alpha, nu, rho] 23 | 24 | no_options = len(options) 25 | iv_hagan = [] 26 | iv_watanabe = [] 27 | vol_swap = [] 28 | diff = [] 29 | 30 | for i in range(0, no_options): 31 | iv_hagan.append(SABRTools.sabr_normal_jit(f0, strike, alpha, rho, nu, ts[i])) 32 | iv_watanabe.append(ExpansionTools.get_iv_normal_sabr_watanabe_expansion(f0, strike, ts[i], alpha, nu, rho)) 33 | vol_swap.append(get_vol_swap_approximation_sabr(parameters, 0.0, ts[i], alpha)) 34 | diff.append(iv_watanabe[-1] - vol_swap[-1]) 35 | 36 | plt.plot(ts, iv_hagan, label='iv hagan atm', linestyle='dotted') 37 | plt.plot(ts, iv_watanabe, label='iv watanabe atm', linestyle='dashed') 38 | 39 | # plt.plot(ts, iv_watanabe, label='iv_t - v_t', linestyle='dashed') 40 | 41 | plt.title("Implied Vol ATM by maturity") 42 | 43 | plt.legend() 44 | plt.show() 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/Examples/WatanabeSmileExample.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | 3 | from AnalyticEngines.BetaZeroSabr import ExpansionTools 4 | from MC_Engines.MC_SABR import SABR_Normal_Engine 5 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 6 | from Tools import RNG, Types 7 | from VolatilitySurface.Tools import SABRTools 8 | from Tools.Bachelier import implied_volatility 9 | 10 | # option info 11 | f0 = 0.01 12 | t = 1.0 13 | spreads = [-200.0, -175.0, -150.0, -100.0, -75.0, -50.0, -25.0, -10.0, 0.0, 10.0, 25.0, 50.0, 75.0, 100.0, 150.0, 175.0, 200.0] 14 | 15 | strikes = [] 16 | options = [] 17 | for si in spreads: 18 | strikes.append(si / 10000.0 + f0) 19 | options.append(EuropeanOption(strikes[-1], 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, t)) 20 | 21 | # sabr parameters 22 | alpha = 0.007 23 | nu = 0.4 24 | rho = 0.5 25 | parameters = [alpha, nu, rho] 26 | 27 | # mc price 28 | seed = 123456789 29 | no_paths = 750000 30 | rnd_generator = RNG.RndGenerator(seed) 31 | no_time_steps = 200 32 | 33 | map_output = SABR_Normal_Engine.get_path_multi_step(0.0, t, parameters, f0, no_paths, no_time_steps, 34 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 35 | 36 | no_options = len(options) 37 | iv_hagan = [] 38 | iv_watanabe = [] 39 | iv_mc = [] 40 | 41 | for i in range(0, no_options): 42 | mc_option_price = options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 43 | mc_price = mc_option_price[0] 44 | iv_mc.append(implied_volatility(mc_price, f0, strikes[i], t, 'c')) 45 | iv_hagan.append(SABRTools.sabr_normal_jit(f0, strikes[i], alpha, rho, nu, t)) 46 | iv_watanabe.append(ExpansionTools.get_iv_normal_sabr_watanabe_expansion(f0, strikes[i], t, alpha, nu, rho)) 47 | 48 | plt.plot(strikes, iv_hagan, label='iv hagan', linestyle='dotted') 49 | plt.plot(strikes, iv_watanabe, label='iv watanabe', linestyle='dashed') 50 | plt.plot(strikes, iv_mc, label='iv mc') 51 | 52 | plt.title("T=%s, F= %s" % (t, f0)) 53 | 54 | plt.legend() 55 | plt.show() 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/ExpansionTools.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import Tools.AnalyticTools 3 | 4 | from scipy.special import ndtr 5 | from MC_Engines.MC_LocalVol import LocalVolFunctionals 6 | 7 | 8 | def G(y): 9 | return Tools.AnalyticTools.normal_pdf(0.0, 1.0, y) - y * ndtr(-y) 10 | 11 | 12 | def get_option_normal_sabr_watanabe_expansion(f0, k, t, alpha, nu, rho, option_type): 13 | y = (k - f0) / (alpha * np.sqrt(t)) 14 | rho_inv = np.sqrt(1.0 - rho * rho) 15 | phi_y = Tools.AnalyticTools.normal_pdf(0.0, 1.0, y) 16 | g_y = G(y) 17 | 18 | a_t = 0.5 * rho * nu * y 19 | b_t = 0.5 * np.power(nu * rho, 2.0) * (np.power(y, 2.0) / 3.0 + 1.0 / 6.0) + \ 20 | 0.5 * np.power(nu * rho, 2.0) * np.power(0.5 * (y * y - 1.0), 2.0) + \ 21 | 0.5 * np.power(rho_inv * nu, 2.0) * ((np.power(y, 2.0) + 2.0) / 3.0) - 0.25 * nu * nu 22 | 23 | if option_type == 'c': 24 | return alpha * np.sqrt(t) * (g_y + phi_y * np.sqrt(t) * a_t + phi_y * t * b_t) 25 | else: 26 | return alpha * np.sqrt(t) * (g_y + phi_y * np.sqrt(t) * a_t + phi_y * t * b_t) - (f0 - k) 27 | 28 | 29 | def get_iv_normal_sabr_watanabe_expansion(f0, k, t, alpha, nu, rho): 30 | y = (k - f0) / (alpha * np.sqrt(t)) 31 | rho_inv = np.sqrt(1.0 - rho * rho) 32 | a_t = 0.5 * rho * nu * y 33 | b_t = 0.5 * np.power(nu * rho, 2.0) * (1.0 - y * y) / 6.0 + \ 34 | 0.5 * np.power(nu * rho, 2.0) * np.power(0.25 * (y * y - 1.0), 2.0) + \ 35 | 0.5 * np.power(rho_inv * nu, 2.0) * ((np.power(y, 2.0) + 2.0) / 3.0) - 0.25 * nu * nu 36 | 37 | return alpha * (1.0 + np.sqrt(t) * a_t + t * b_t) 38 | 39 | 40 | def get_option_normal_sabr_loc_vol_expansion(f0, k, t, alpha, nu, rho, option_type): 41 | x = np.array([f0]) 42 | sigma_0 = LocalVolFunctionals.local_vol_normal_sabr(t, x, f0, alpha, rho, nu)[0] 43 | y = (k - f0) / (sigma_0 * np.sqrt(t)) 44 | phi_y = Tools.AnalyticTools.normal_pdf(0.0, 1.0, y) 45 | g_y = G(y) 46 | rho_inv = 1.0 - rho * rho 47 | 48 | a_t = 0.5 * rho * nu * y 49 | b_t = rho * nu * alpha * (2.0 * y * y + 1.0) / 6.0 + 0.25 * nu * nu * rho_inv + 0.125 * np.power(rho * nu, 2.0) * np.power(y * y - 1, 2.0) 50 | c_t = alpha * np.power(rho * nu, 2.0) * (0.25 * np.power(y, 5.0) - np.power(y, 3.0) / 3.0 + 0.25 * y) 51 | 52 | if option_type == 'c': 53 | return alpha * np.sqrt(t) * (g_y + phi_y * np.sqrt(t) * a_t + phi_y * t * b_t + phi_y * np.power(t, 1.5) * c_t) 54 | else: 55 | return alpha * np.sqrt(t) * (g_y + phi_y * np.sqrt(t) * a_t + phi_y * t * b_t + phi_y * np.power(t, 1.5) * c_t) - (f0 - k) 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /AnalyticEngines/BetaZeroSabr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/BetaZeroSabr/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/COSMethod/COSBlocksOptions.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | import numba as nb 17 | 18 | 19 | @nb.jit("f8[:](f8,f8,f8,f8,i8)", nopython=True, nogil=True, parallel=True) 20 | def digital_block(a: float, b: float, c: float, d: float, no_terms: int): 21 | alpha_d = (d - a) / (b - a) 22 | alpha_c = (c - a) / (b - a) 23 | output = np.zeros(no_terms) 24 | for i in range(0, no_terms): 25 | if i == 0: 26 | output[i] = d - c 27 | else: 28 | output[i] = (np.sin(i * np.pi * alpha_d) - np.sin(i * np.pi * alpha_c)) * (b - a) / (i * np.pi) 29 | 30 | return output 31 | 32 | 33 | @nb.jit("f8[:](f8,f8,f8,f8,i8)", nopython=True, nogil=True, parallel=True) 34 | def call_put_block(a: float, b: float, c: float, d: float, no_terms: int): 35 | alpha_d = (d - a) / (b - a) 36 | alpha_c = (c - a) / (b - a) 37 | exp_d = np.exp(d) 38 | exp_c = np.exp(c) 39 | output = np.zeros(no_terms) 40 | for i in range(0, no_terms): 41 | beta = np.pi * i / (b - a) 42 | a_k_1 = np.cos(i * np.pi * alpha_d) * exp_d - np.cos(i * np.pi * alpha_c) * exp_c 43 | a_k_2 = beta * (np.sin(i * np.pi * alpha_d) * exp_d - np.sin(i * np.pi * alpha_c) * exp_c) 44 | multiplier = 1.0 / (1 + beta * beta) 45 | output[i] = multiplier * (a_k_1 + a_k_2) 46 | 47 | return output 48 | -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/COSMethod/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/FourierMethod/COSMethod/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/CharesticFunctions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/FourierMethod/CharesticFunctions/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/Examples/Example_Bates.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction 3 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 4 | from functools import partial 5 | from Tools import Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | import time 8 | 9 | # European option price 10 | k_s = np.array([60.0, 80.0, 90.0, 100.0, 110.0, 120.0, 140.0, 160.0, 170.0]) 11 | no_strikes = len(k_s) 12 | f0 = 100.0 13 | x0 = np.log(f0) 14 | T = 2.0 15 | 16 | # Bates para 17 | r = 0.00 18 | v0 = 0.25 19 | k = 1.5 20 | theta = 0.2 21 | epsilon = 0.05 22 | rho = -0.8 23 | lambdaJ = 0.1 24 | muJ = 0.1 25 | sigmaJ = 0.2 26 | b2 = k 27 | u2 = -0.5 28 | 29 | # Upper and lower bound for cos integral 30 | a = -6.0 31 | b = 6.0 32 | 33 | cf_bates = partial(JumpDiffusionCharesticFunction.get_bates_cf, t=T, r_t=r, x=x0, v=v0, theta=theta, rho=rho, k=k, 34 | epsilon=epsilon, jump_mean=muJ, jump_std=sigmaJ, jump_intensity=lambdaJ, b=b2, u=u2) 35 | start_time = time.time() 36 | cos_price = COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 256, k_s, cf_bates) 37 | end_time = time.time() 38 | diff_time = end_time - start_time 39 | print(diff_time) 40 | 41 | start_time = time.time() 42 | price_cf_integration = [] 43 | for i in range(0, no_strikes): 44 | european_option = EuropeanOption(k_s[i], 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T) 45 | price_cf_integration.append(european_option.get_analytic_value(r, theta, rho, k, epsilon, v0, muJ, sigmaJ, lambdaJ, 46 | model_type=Types.ANALYTIC_MODEL.BATES_MODEL_LEWIS, 47 | compute_greek=False)) 48 | 49 | end_time = time.time() 50 | diff_time = end_time - start_time 51 | print(diff_time) 52 | -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/Examples/Example_CGMY.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction 3 | from functools import partial 4 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 5 | from Instruments.EuropeanInstruments import TypeEuropeanOption 6 | 7 | import time 8 | 9 | # European option price 10 | k_s = np.array([70, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0]) 11 | f0 = 100 12 | x0 = np.log(f0) 13 | T = 1 14 | 15 | # CGMY parameters 16 | r = 0.1 17 | sigma = 0.2 18 | C = 1.0 19 | G = 5.0 20 | M = 5.0 21 | Y = 0.5 22 | 23 | # Upper and lower bound for cos integral 24 | a = -8 * np.sqrt(T) 25 | b = 8 * np.sqrt(T) 26 | 27 | cf_CGMY = partial(JumpDiffusionCharesticFunction.get_CGMYB_cf, t=T, x=x0, r=r, sigma=sigma, C=C, G=G, M=M, Y=Y) 28 | start_time = time.time() 29 | cos_price = np.exp(-r * T) * COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 2 ** 14, k_s, 30 | cf_CGMY) 31 | end_time = time.time() 32 | diff_time = end_time - start_time 33 | -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/Examples/Example_COS_Method.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | import matplotlib.pylab as plt 17 | from AnalyticEngines.FourierMethod.CharesticFunctions import HestonCharesticFunction 18 | from functools import partial 19 | from Tools import Types 20 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 21 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 22 | 23 | import time 24 | 25 | # European option price 26 | k_s = np.array([60.0, 80.0, 90.0, 100.0, 110.0, 120.0, 140.0, 160.0, 170.0]) 27 | f0 = 100.0 28 | x0 = np.log(f0) 29 | T = 2.0 30 | 31 | # Heston parameters 32 | epsilon = 1.1 33 | k = 0.5 34 | rho = -0.9 35 | v0 = 0.05 36 | theta = 0.05 37 | b2 = k 38 | u2 = -0.5 39 | 40 | # Upper and lower bound for cos integral 41 | a = -2.0 42 | b = 2.0 43 | 44 | cf_heston = partial(HestonCharesticFunction.get_trap_cf, t=T, r_t=0.0, x=x0, v=v0, theta=theta, rho=rho, k=k, epsilon=epsilon, b=b2, u=u2) 45 | start_time = time.time() 46 | cos_price = COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 64, k_s, cf_heston) 47 | end_time = time.time() 48 | diff_time = end_time - start_time 49 | 50 | # Integration Heston´s charestic function 51 | price_cf_integration = [] 52 | no_strikes = len(k_s) 53 | 54 | start_time = time.time() 55 | for i in range(0, no_strikes): 56 | european_option = EuropeanOption(k_s[i], 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T) 57 | price_cf_integration.append(european_option.get_analytic_value(0.0, theta, rho, k, epsilon, v0, 0.0, 58 | model_type=Types.ANALYTIC_MODEL.HESTON_MODEL_LEWIS, 59 | compute_greek=False)) 60 | end_time = time.time() 61 | diff_time_cf = end_time - start_time 62 | 63 | print(diff_time) 64 | print(diff_time_cf) 65 | 66 | plt.plot(k_s, cos_price, label="cos method") 67 | plt.plot(k_s, price_cf_integration, label="integration cf") 68 | 69 | plt.legend() 70 | plt.show() 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/Examples/Example_NIG.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction 3 | from functools import partial 4 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 5 | from Instruments.EuropeanInstruments import TypeEuropeanOption 6 | 7 | import time 8 | 9 | # European option price 10 | k_s = np.array([70, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0]) 11 | f0 = 100 12 | x0 = np.log(f0) 13 | T = 1 14 | 15 | # NIG parameters 16 | r = 0.01 17 | sigma = 0 18 | alpha = 8.9932 19 | beta = 0 20 | delta = 1.1528 21 | 22 | # Upper and lower bound for cos integral 23 | a = -8 * np.sqrt(T) 24 | b = 8 * np.sqrt(T) 25 | 26 | cf_NIG = partial(JumpDiffusionCharesticFunction.get_NIGB_cf, t=T, x=x0, r=r, sigma=sigma, alpha=alpha, beta=beta, 27 | delta=delta) 28 | start_time = time.time() 29 | cos_price = np.exp(-r * T) * COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 2 ** 14, k_s, 30 | cf_NIG) 31 | end_time = time.time() 32 | diff_time = end_time - start_time 33 | -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/Examples/Example_VG.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction 3 | from functools import partial 4 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 5 | from Instruments.EuropeanInstruments import TypeEuropeanOption 6 | 7 | import time 8 | 9 | # European option price 10 | k_s = np.array([70, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0]) 11 | f0 = 100 12 | x0 = np.log(f0) 13 | T = 1 14 | 15 | # VG parameters 16 | r = 0.1 17 | sigma = 0.18 18 | theta = -0.13 19 | beta = 0.25 20 | 21 | # Upper and lower bound for cos integral 22 | a = -8 * np.sqrt(T) 23 | b = 8 * np.sqrt(T) 24 | 25 | cf_VG = partial(JumpDiffusionCharesticFunction.get_VG_cf, t=T, x=x0, r=r, sigma=sigma, beta=beta, theta=theta) 26 | start_time = time.time() 27 | cos_price = np.exp(-r * T) * COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 2 ** 14, k_s, 28 | cf_VG) 29 | end_time = time.time() 30 | diff_time = end_time - start_time 31 | -------------------------------------------------------------------------------- /AnalyticEngines/FourierMethod/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/FourierMethod/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/LocalVolatility/Dupire/DupireFormulas.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numba as nb 16 | import numpy as np 17 | 18 | 19 | @nb.jit("f8(f8,f8,f8,f8,f8)", nopython=True) 20 | def local_vol_from_variance(z, w_t, w_partial_z, w_partial_zz, w_partial_t): 21 | if w_t > 0: 22 | ratio_y_w = z / w_t 23 | # c_partial_z = - 0.25 - 1.0 / w_t + ratio_y_w * ratio_y_w 24 | c_partial_z = 0.25 + 1.0 / w_t 25 | # loc_vol_denominator = 1.0 - ratio_y_w * w_partial_z + 0.25 * c_partial_z * w_partial_z * w_partial_z \ 26 | # + 0.5 * w_partial_zz 27 | 28 | loc_vol_denominator = np.power(1.0 - 0.5 * ratio_y_w * w_partial_z, 2.0) - \ 29 | 0.25 * c_partial_z * w_partial_z * w_partial_z + 0.5 * w_partial_zz 30 | 31 | return w_partial_t / loc_vol_denominator 32 | else: 33 | return 0.0 34 | 35 | 36 | @nb.jit("f8(f8,f8,f8,f8,f8,f8,f8)", nopython=True) 37 | def local_vol_from_implied_vol(t, sigma_i, z, k, sigma_partial_t, sigma_partial_k, sigma_partial_k_k): 38 | sqrt_t = np.sqrt(t) 39 | sigma_i_t = sigma_i * sqrt_t 40 | d_1 = (z / sigma_i_t) + 0.5 * sigma_i_t 41 | 42 | numerator_loc_vol = (sigma_i / t) + 2.0 * sigma_partial_t 43 | 44 | denominator_loc_vol = k * k * (sigma_partial_k_k - d_1 * sqrt_t * sigma_partial_k * sigma_partial_k + 45 | (1.0 / sigma_i) * np.power(1.0 / (k * sqrt_t) + d_1 * sigma_partial_k, 2.0)) 46 | 47 | return np.sqrt(numerator_loc_vol / denominator_loc_vol) 48 | -------------------------------------------------------------------------------- /AnalyticEngines/LocalVolatility/Dupire/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/LocalVolatility/Dupire/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/LocalVolatility/Hagan/ExpansionLocVol.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | 17 | from typing import Callable 18 | 19 | 20 | class hagan_loc_vol(object): 21 | def __init__(self, a_t: Callable[[float], float], 22 | loc_vol: Callable[[float], float], 23 | loc_vol_first_derive: Callable[[float], float], 24 | loc_vol_second_derive: Callable[[float], float]): 25 | self._a_t = a_t 26 | self._loc_vol = loc_vol 27 | self._loc_vol_derive = loc_vol_first_derive 28 | self._loc_vol_second_derive = loc_vol_second_derive 29 | 30 | def get_implied_vol(self, t: float, f0: float, k: float) -> float: 31 | f_av = 0.5 * (f0 + k) 32 | loc_vol_value = self._loc_vol(f_av) 33 | r1 = self._loc_vol_derive(f_av) / loc_vol_value 34 | r2 = self._loc_vol_second_derive(f_av) / loc_vol_value 35 | a_t = self._a_t(t) 36 | 37 | multiplier = a_t * loc_vol_value / f_av 38 | order0 = (1.0 / 24.0) * (2.0 * r2 - r1 * r1 + 1.0 / (f_av * f_av)) * np.power(a_t * loc_vol_value, 2.0) * t 39 | order1 = (1.0 / 24.0) * (r2 - 2.0 * r1 * r1 + 2.0 / (f_av * f_av)) 40 | 41 | return multiplier * (1.0 + order0 + order1 * (f0 - k) * (f0 - k)) 42 | 43 | def update_a(self, a_t: Callable[[float], float]): 44 | self._a_t = a_t 45 | 46 | def loc_vol(self, 47 | loc_vol: Callable[[float], float], 48 | loc_vol_first_derive: Callable[[float], float], 49 | loc_vol_second_derive: Callable[[float], float]): 50 | 51 | self._loc_vol = loc_vol 52 | self._loc_vol_derive = loc_vol_first_derive 53 | self._loc_vol_second_derive = loc_vol_second_derive 54 | -------------------------------------------------------------------------------- /AnalyticEngines/LocalVolatility/Hagan/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/LocalVolatility/Hagan/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/LocalVolatility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/LocalVolatility/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/MalliavinMethod/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/MalliavinMethod/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/VolatilityTools/CEVMalliavinTools.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numba as nb 15 | from Tools.Types import ndarray 16 | import numpy as np 17 | 18 | 19 | @nb.jit("f8[:,:](f8, f8[:,:], f8[:,:])", nopython=True, nogil=True) 20 | def transform_cev_malliavin(rho: float, 21 | z_t_paths: ndarray, 22 | d_z_t: ndarray): 23 | dim = z_t_paths.shape 24 | path_d_z_t = np.empty(shape=dim) 25 | 26 | for i in range(0, dim[0]): 27 | for j in range(0, dim[1]): 28 | path_d_z_t[i, j] = d_z_t[i, j] * np.power(z_t_paths[i, j], rho / (1.0 - rho)) / (1.0 - rho) 29 | 30 | return path_d_z_t 31 | 32 | 33 | @nb.jit("f8[:](f8[:], f8[:])", nopython=True, nogil=True) 34 | def get_error(y_t_n, y_t): 35 | n = len(y_t_n) 36 | error = np.zeros(n) 37 | 38 | for i in range(0, n): 39 | error[i] = np.abs(y_t_n[i] - y_t[i]) 40 | 41 | return error 42 | 43 | 44 | @nb.jit("f8(f8[:], f8[:])", nopython=True, nogil=True) 45 | def get_mean_error(y_t_n, y_t): 46 | n = len(y_t_n) 47 | error = 0.0 48 | 49 | for i in range(0, n): 50 | error += np.abs(y_t_n[i] - y_t[i]) / n 51 | 52 | return error 53 | 54 | 55 | @nb.jit("f8(f8[:], f8[:])", nopython=True, nogil=True) 56 | def get_square_error(y_t_n, y_t): 57 | n = len(y_t_n) 58 | error = 0.0 59 | 60 | for i in range(0, n): 61 | error += np.power(y_t_n[i] - y_t[i], 2.0) / n 62 | 63 | return np.sqrt(error) 64 | -------------------------------------------------------------------------------- /AnalyticEngines/VolatilityTools/HestonTool.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numpy as np 15 | import numba as nb 16 | 17 | 18 | @nb.jit("f8(f8,f8,f8, f8)", nopython=True, nogil=True) 19 | def get_variance_swap(v0: float, k: float, theta: float, t: float): 20 | exp_t = np.exp(- k * t) 21 | first_term = v0 * (1.0 - exp_t) / k 22 | second_term = theta * (t - (1.0 - exp_t) / k) 23 | return np.sqrt((first_term + second_term) / t) 24 | 25 | 26 | @nb.jit("f8(f8,f8,f8,f8,f8)", nopython=True, nogil=True) 27 | def get_rho_term_var_swap(v0: float, k: float, theta: float, epsilon: float, t: float): 28 | exp_t = np.exp(- k * t) 29 | first_term = ((epsilon * v0) / k) * ((1.0 - exp_t) / k - exp_t * t) 30 | second_term = ((epsilon * theta) / k) * (t - 2.0 * (1.0 - exp_t) / k + t * exp_t) 31 | return first_term + second_term 32 | -------------------------------------------------------------------------------- /AnalyticEngines/VolatilityTools/NonParametricEstimatorSLV.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numpy as np 15 | import numba as nb 16 | from Tools import Types, AnalyticTools 17 | 18 | 19 | @nb.jit("f8[:](f8[:],f8)", nopython=True, nogil=True) 20 | def gaussian_kernel(x: Types.ndarray, h: float): 21 | return np.exp(-0.5 * np.power(x, 2.0) / h) / np.sqrt(2.0 * np.pi * h) 22 | 23 | 24 | @nb.jit("f8[:](f8[:],f8)", nopython=True, nogil=True) 25 | def quartic_kernel(x: Types.ndarray, h: float): 26 | no_x = len(x) 27 | output = np.zeros(no_x) 28 | 29 | for i in range(0, no_x): 30 | z = x[i] / h 31 | if np.abs(z) < 1.0: 32 | output[i] = (15.0 / (16.0 * h)) * np.power(1.0 - np.power(x[i] / h, 2.0), 2.0) 33 | 34 | return output 35 | 36 | 37 | @nb.jit("f8[:](f8[:],f8[:],f8[:],f8)", nopython=True, nogil=True) 38 | def gaussian_kernel_estimator_slv(v_t: Types.ndarray, x_t: Types.ndarray, x: Types.ndarray, h: float): 39 | no_x = len(x) 40 | estimator = np.zeros(no_x) 41 | 42 | for i in range(0, no_x): 43 | k_x_i = gaussian_kernel(x_t - x[i], h) 44 | estimator[i] = AnalyticTools.scalar_product(v_t, k_x_i) / np.sum(k_x_i) 45 | 46 | return estimator 47 | 48 | 49 | @nb.jit("f8[:](f8[:],f8[:],f8[:],f8)", nopython=True, nogil=True) 50 | def quartic_kernel_estimator_slv(v_t: Types.ndarray, x_t: Types.ndarray, x: Types.ndarray, h: float): 51 | no_x = len(x) 52 | estimator = np.zeros(no_x) 53 | 54 | for i in range(0, no_x): 55 | k_x_i = quartic_kernel(x_t - x[i], h) 56 | estimator[i] = AnalyticTools.scalar_product(v_t, k_x_i) / np.sum(k_x_i) 57 | 58 | return estimator 59 | -------------------------------------------------------------------------------- /AnalyticEngines/VolatilityTools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/VolatilityTools/__init__.py -------------------------------------------------------------------------------- /AnalyticEngines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/AnalyticEngines/__init__.py -------------------------------------------------------------------------------- /Examples/Chapter1/Data/SabrSurfaceParameter.txt: -------------------------------------------------------------------------------- 1 | value_date;date;alpha;rho;nu 2 | 44060;44092;0.207175168860505;-0.786504078991285;2.38201308756965 3 | 44060;44120;0.215004531276577;-0.783668127451219;1.82297757326436 4 | 44060;44134;0.2164862346751;-0.782250168007554;1.66735217790679 5 | 44060;44155;0.232256944372669;-0.780123249249841;1.49922157701687 6 | 44060;44183;0.233319990843443;-0.777287395667093;1.34318447479491 7 | 44060;44211;0.229332951407152;-0.774451585620116;1.23094634441589 8 | 44060;44274;0.224975439314478;-0.768071172189486;1.06123494976654 9 | 44060;44365;0.217497512250909;-0.75885540854126;0.912739146981752 10 | 44060;44547;0.21246769457468;-0.740425260664132;0.747999151100984 11 | 44060;44729;0.207037030002359;-0.721996951898076;0.653513651225508 12 | 44060;44911;0.204247313902739;-0.703570482105471;0.589952808655709 13 | 44060;45093;0.201045499545822;-0.685145851148703;0.543289569347041 14 | 44060;45275;0.200381602582207;-0.666723058890168;0.507075353792462 15 | 44060;45646;0.205559317356653;-0.629174599119059;0.452781629774561 16 | 44060;46010;0.208313434373327;-0.592342024684021;0.414729273435066 17 | 44060;46374;0.210424541476372;-0.555516802269231;0.385650675726592 18 | 44060;46738;0.211177348261666;-0.518698930774337;0.362454342544779 19 | 44060;47102;0.210935789854446;-0.481888409099131;0.34336673413014 20 | 44060;47473;0.209008674289607;-0.444377554829854;0.327001373199886 21 | -------------------------------------------------------------------------------- /Examples/Chapter1/Example_1_2_6.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | 4 | from py_vollib.black_scholes_merton import black_scholes_merton 5 | from py_vollib.black_scholes_merton.greeks import analytical 6 | from Tools import RNG 7 | 8 | T = 2.0 9 | no_time_steps = int(T * 365) 10 | t_i_s = np.linspace(0.0, T, no_time_steps) 11 | diff_t_i_s = np.diff(t_i_s) 12 | 13 | # We will do the dynamic hedge of option call under BS model. 14 | k = 100.0 15 | spot = 120.0 16 | r = 0.02 17 | q = 0.00 18 | sigma = 0.7 19 | delta_time = T 20 | notional = 1000.0 21 | 22 | portfolio_t_i = np.zeros(no_time_steps) 23 | option_t_i = np.zeros(no_time_steps) 24 | 25 | alpha_t_i_1 = analytical.delta('c', spot, k, delta_time, r, sigma, q) * notional 26 | option_t_i[0] = black_scholes_merton('c', spot, k, delta_time, r, sigma, q) * notional 27 | beta_t = option_t_i[0] - alpha_t_i_1 * spot 28 | 29 | portfolio_t_i[0] = (alpha_t_i_1 * spot + beta_t) 30 | 31 | s_t_i_1 = spot 32 | s_t_i = 0.0 33 | alpha_t_i = 0.0 34 | 35 | 36 | rebalanced_index = list(map(lambda x: int(x), list(np.arange(0.0, no_time_steps, 30)))) 37 | # rebalanced_index = np.arange(1, no_time_steps) 38 | 39 | rng = RNG.RndGenerator(123) 40 | z_s = rng.normal(0.0, 1.0, no_time_steps - 1) 41 | 42 | for i in range(1, no_time_steps): 43 | s_t_i = s_t_i_1 * np.exp((r - q) * diff_t_i_s[i - 1] - 0.5 * sigma * sigma * diff_t_i_s[i - 1] + 44 | np.sqrt(diff_t_i_s[i - 1]) * sigma * z_s[i - 1]) 45 | 46 | delta_time = T - t_i_s[i] 47 | option_t_i[i] = notional * black_scholes_merton('c', s_t_i, k, delta_time, r, sigma, q) 48 | 49 | if i in rebalanced_index: 50 | alpha_t_i = analytical.delta('c', s_t_i, k, delta_time, r, sigma, q) * notional 51 | beta_t = beta_t * (1 + r * diff_t_i_s[i - 1]) + alpha_t_i_1 * s_t_i_1 * q * diff_t_i_s[i - 1] + \ 52 | (alpha_t_i_1 - alpha_t_i) * s_t_i 53 | 54 | portfolio_t_i[i] = (alpha_t_i * s_t_i + beta_t) 55 | alpha_t_i_1 = alpha_t_i 56 | else: 57 | beta_t = beta_t * (1 + r * diff_t_i_s[i - 1]) + alpha_t_i_1 * s_t_i_1 * q * diff_t_i_s[i - 1] 58 | portfolio_t_i[i] = (alpha_t_i_1 * s_t_i + beta_t) 59 | 60 | s_t_i_1 = s_t_i 61 | 62 | plt.plot(t_i_s, portfolio_t_i, label='hedge', linestyle='dashed', color='black', linewidth=1) 63 | plt.plot(t_i_s, option_t_i, label='call option', color='black', linewidth=1) 64 | 65 | plt.legend() 66 | plt.title('Dynamic Hedge') 67 | plt.show() 68 | -------------------------------------------------------------------------------- /Examples/Chapter1/Example_1_3_1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pylab as plt 5 | import statsmodels.api as sm 6 | from pathlib import Path 7 | 8 | current_directory = os.path.dirname(os.path.realpath(__file__)) 9 | folder_directory = Path(current_directory) 10 | historical_data_path = os.path.join(folder_directory, 'Data', 'HistoricalStoxx50E.txt') 11 | 12 | historical_data = pd.read_csv(historical_data_path, header=None, names=["date", "price"], sep=";") 13 | no_dates = len(historical_data['date']) 14 | historical_data['price'][1:] = historical_data['price'][1:].apply(lambda x: float(x)) 15 | log_increments = np.diff(np.log(list(historical_data['price'][1:]))) 16 | 17 | mean_log_increments = log_increments.mean() 18 | std_log_increments = log_increments.std() 19 | log_increments_normalized = (log_increments - mean_log_increments) / std_log_increments 20 | 21 | plt.figure(figsize=(20, 10)) 22 | pp = sm.ProbPlot(log_increments_normalized, fit=True) 23 | qq = pp.qqplot(marker='.', markerfacecolor='k', markeredgecolor='k', alpha=0.3) 24 | sm.qqline(qq.axes[0], line='45', fmt='k--') 25 | 26 | plt.show() 27 | -------------------------------------------------------------------------------- /Examples/Chapter1/Example_1_4_1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pylab as plt 5 | import QuantLib as ql 6 | 7 | from pathlib import Path 8 | from VolatilitySurface.Tools import SABRTools 9 | from scipy.optimize import curve_fit 10 | 11 | current_directory = os.path.dirname(os.path.realpath(__file__)) 12 | folder_directory = Path(current_directory) 13 | sabr_parameter_paths = os.path.join(folder_directory, 'Data', 'SabrSurfaceParameter.txt') 14 | 15 | parameters = pd.read_csv(sabr_parameter_paths, header=None, names=["value_date", "date", "alpha", "rho", "nu"], sep=";") 16 | no_dates = len(parameters['date']) 17 | 18 | no_z_i = 100 19 | z_i = np.linspace(-0.5, 0.5, no_z_i) 20 | 21 | sabr_iv_map = {} 22 | for i in range(1, no_dates): 23 | alpha_i = float(parameters['alpha'][i]) 24 | rho_i = float(parameters['rho'][i]) 25 | nu_i = float(parameters['nu'][i]) 26 | dti = (float(parameters['date'][i]) - float(parameters['value_date'][i])) / 365.0 27 | iv = [] 28 | for j in range(0, no_z_i): 29 | iv.append(SABRTools.sabr_vol_jit(alpha_i, rho_i, nu_i, z_i[j], dti)) 30 | sabr_iv_map[int(parameters['date'][i])] = iv 31 | 32 | nu_param = [] 33 | rho_param = [] 34 | delta_time = [] 35 | for i in range(1, no_dates): 36 | delta_time.append((float(parameters['date'][i]) - float(parameters['value_date'][i])) / 365.0) 37 | nu_param.append(float(parameters['nu'][i])) 38 | rho_param.append(float(parameters['rho'][i])) 39 | 40 | # To plot the skew for diferent maturities 41 | # plt.plot(delta_time, nu_param, label="vol-of_vol parameter", color="black", linestyle="dashed") 42 | # 43 | # 44 | # def f_law(x, a, b): 45 | # return a * np.power(x, -b) 46 | # 47 | # 48 | # popt, pcov = curve_fit(f_law, delta_time, nu_param) 49 | # y_fit_values = f_law(delta_time, *popt) 50 | # 51 | # plt.plot(delta_time, y_fit_values, label="%s * t^-%s)" % (round(popt[0], 5), round(popt[1], 5)), color="black", linestyle="dashed", 52 | # marker='.') 53 | # 54 | # plt.legend() 55 | # plt.show() 56 | 57 | fig, axs = plt.subplots(2, 3, figsize=(10, 5)) 58 | index = [0, 3, 6, 8, 12, 18] 59 | for i in range(0, 3): 60 | date_str = str(ql.Date(int(parameters['date'][index[i] + 1]))) 61 | date_str = date_str.replace('th', '') 62 | axs[0, i].set(xlabel='z', ylabel='iv') 63 | axs[0, i].plot(z_i, sabr_iv_map[int(parameters['date'][index[i] + 1])], label=parameters['date'][index[i] + 1], 64 | linestyle='dashed', color='black') 65 | 66 | axs[0, i].set_ylim([0.0, 0.7]) 67 | axs[0, i].set_title(date_str) 68 | 69 | 70 | for i in range(0, 3): 71 | date_str = str(ql.Date(int(parameters['date'][index[i + 3] + 1]))) 72 | date_str = date_str.replace('th', '') 73 | axs[1, i].set(xlabel='z', ylabel='iv') 74 | axs[1, i].plot(z_i, sabr_iv_map[int(parameters['date'][index[i + 3] + 1])], 75 | label=parameters['date'][index[i + 3] + 1], linestyle='dashed', color='black') 76 | axs[1, i].set_ylim([0.0, 0.7]) 77 | axs[1, i].set_title(date_str) 78 | 79 | plt.show() 80 | -------------------------------------------------------------------------------- /Examples/Chapter10/Example_10_2_4.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Engine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | 8 | # simulation info 9 | alpha = 0.5 10 | nu = 0.7 11 | rho = -0.4 12 | parameters = [alpha, nu, rho] 13 | no_time_steps = 10 14 | f0 = 100 15 | 16 | seed = 123456789 17 | no_paths = 1000000 18 | 19 | delta_vix = 1.0 / 12.0 20 | T = 0.1 21 | 22 | beta_vix = np.sqrt(np.exp(nu * nu * delta_vix) - 1) / (np.sqrt(delta_vix) * nu) 23 | vix_t0 = alpha * beta_vix 24 | 25 | no_strikes = 30 26 | strikes = np.linspace(0.8, 1.2, no_strikes) * vix_t0 27 | 28 | # random number generator 29 | rnd_generator = RNG.RndGenerator(seed) 30 | 31 | option_vix_price = [] 32 | implied_vol_vix = [] 33 | 34 | for i in range(0, no_strikes): 35 | rnd_generator.set_seed(seed) 36 | map_output = SABR_Engine.get_path_multi_step(0.0, T, parameters, f0, no_paths, no_time_steps, 37 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 38 | 39 | index_t_i = np.searchsorted(map_output[Types.SABR_OUTPUT.TIMES], T) 40 | price = np.mean(np.maximum(beta_vix * map_output[Types.SABR_OUTPUT.SIGMA_PATHS][:, index_t_i] - strikes[i], 0.0)) 41 | option_vix_price.append(price) 42 | implied_vol_vix.append(round(implied_volatility(option_vix_price[-1], vix_t0, strikes[i], T, 0.0, 0.0, 'c'), 5)) 43 | 44 | plt.plot(strikes, implied_vol_vix, linestyle='--', label='Implied Vol VIX', color='black', marker='.') 45 | plt.ylim([0.5, 1.0]) 46 | plt.xlabel('K') 47 | plt.legend() 48 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter10/Example_10_2_5.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_Heston import Heston_Engine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | 8 | # simulation info 9 | epsilon = 0.8 10 | k = 3.0 11 | rho = -0.6 12 | v0 = 0.05 13 | sigma_0 = np.sqrt(v0) 14 | theta = 0.09 15 | 16 | parameters = [k, theta, epsilon, rho, v0] 17 | no_time_steps = 100 18 | f0 = 100 19 | 20 | seed = 123456789 21 | no_paths = 1000000 22 | 23 | delta_vix = 1.0 / 12.0 24 | T = 0.1 25 | 26 | beta_vix = (1.0 - np.exp(- k * delta_vix)) / (delta_vix * k) 27 | vix_t0 = np.sqrt((v0 - theta) * beta_vix + theta) 28 | 29 | no_strikes = 30 30 | strikes = np.linspace(0.98, 1.03, no_strikes) * vix_t0 31 | 32 | # random number generator 33 | rnd_generator = RNG.RndGenerator(seed) 34 | map_output = Heston_Engine.get_path_multi_step(0.0, T, parameters, f0, v0, no_paths, no_time_steps, 35 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 36 | 37 | option_vix_price = [] 38 | implied_vol_vix = [] 39 | analytic_value = [] 40 | 41 | for i in range(0, no_strikes): 42 | 43 | index_t_i = np.searchsorted(map_output[Types.HESTON_OUTPUT.TIMES], T) 44 | price = np.mean(np.maximum(np.sqrt(beta_vix * (map_output[Types.HESTON_OUTPUT.SPOT_VARIANCE_PATHS][:, index_t_i] - 45 | theta) + theta) - strikes[i], 0.0)) 46 | 47 | analytic_value.append(0.5 * (epsilon * sigma_0 / (k * vix_t0 * vix_t0)) * beta_vix) 48 | option_vix_price.append(price) 49 | implied_vol_vix.append(implied_volatility(option_vix_price[-1], vix_t0, strikes[i], T, 0.0, 0.0, 'c')) 50 | 51 | plt.plot(strikes, implied_vol_vix, linestyle='--', label='Implied Vol VIX', color='black', ) 52 | plt.xlabel('K') 53 | plt.legend() 54 | plt.show() 55 | -------------------------------------------------------------------------------- /Examples/Chapter10/Example_10_2_6.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_MixedLogNormal import MixedLogNormalEngine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | from py_vollib.black_scholes_merton import black_scholes_merton 8 | 9 | # simulation info 10 | nu_1 = 0.8 11 | nu_2 = 0.7 12 | theta = 0.7 13 | rho = -0.6 14 | 15 | parameters = [nu_1, nu_2, theta, rho] 16 | 17 | f0 = 100 18 | v0 = 0.25 19 | 20 | seed = 123 21 | no_paths = 1000000 22 | no_time_steps = 100 23 | 24 | delta_vix = 1.0 / 12.0 25 | T = 0.1 26 | 27 | # random number generator 28 | rnd_generator = RNG.RndGenerator(seed) 29 | map_output = MixedLogNormalEngine.get_path_multi_step(0.0, T, parameters, f0, v0, no_paths, no_time_steps, 30 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 31 | 32 | # VIX spot 33 | index_t_i = np.searchsorted(map_output[Types.MIXEDLOGNORMAL_OUTPUT.TIMES], T) 34 | vix_t0 = np.mean(np.sqrt(map_output[Types.MIXEDLOGNORMAL_OUTPUT.SPOT_VARIANCE_PATHS][:, index_t_i])) 35 | 36 | no_strikes = 50 37 | strikes = np.linspace(0.90, 1.10, no_strikes) * vix_t0 38 | 39 | # Check model is good 40 | mean_var = np.mean(map_output[Types.MIXEDLOGNORMAL_OUTPUT.SPOT_VARIANCE_PATHS], axis=0) 41 | mean_asset = np.mean(map_output[Types.MIXEDLOGNORMAL_OUTPUT.PATHS], axis=0) 42 | 43 | option_vix_price = [] 44 | implied_vol_vix = [] 45 | analytic_value = [] 46 | 47 | for i in range(0, no_strikes): 48 | price = np.mean(np.maximum(np.sqrt(map_output[Types.MIXEDLOGNORMAL_OUTPUT.SPOT_VARIANCE_PATHS][:, index_t_i]) 49 | - strikes[i], 0.0)) 50 | 51 | std_error = np.std(np.maximum(np.sqrt(map_output[Types.MIXEDLOGNORMAL_OUTPUT.SPOT_VARIANCE_PATHS][:, index_t_i]) 52 | - strikes[i], 0.0)) / np.sqrt(no_paths) 53 | 54 | # price_bs_theta_0 = black_scholes_merton('c', np.exp(- nu_1 * nu_1 * T / 8.0) * vix_t0, strikes[i], T, 0.0, 0.5 * nu_1, 0.0) 55 | # price_bs_theta_1 = black_scholes_merton('c', np.exp(- nu_2 * nu_2 * T / 8.0) * vix_t0, strikes[i], T, 0.0, 0.5 * nu_2, 0.0) 56 | 57 | implied_vol_vix.append(implied_volatility(price, vix_t0, strikes[i], T, 0.0, 0.0, 'c')) 58 | 59 | plt.ylim([0.364, 0.366]) 60 | plt.plot(strikes, implied_vol_vix, linestyle='--', label='Implied Vol VIX', color='black', marker='.') 61 | plt.title('delta = %s' % theta) 62 | plt.xlabel('K') 63 | plt.legend() 64 | plt.show() 65 | -------------------------------------------------------------------------------- /Examples/Chapter10/Example_10_2_7.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_RBergomi import RBergomi_Engine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | from scipy.integrate import quad_vec 8 | from AnalyticEngines.MalliavinMethod import ExpansionTools 9 | 10 | # simulation info 11 | h = 0.3 12 | nu = 0.5 13 | rho = -0.6 14 | v0 = 0.05 15 | sigma_0 = np.sqrt(v0) 16 | 17 | parameters = [nu, rho, h] 18 | 19 | no_time_steps = 10 20 | f0 = 100 21 | 22 | seed = 123456789 23 | no_paths = 1000000 24 | 25 | delta_vix = 1.0 / 12.0 26 | T = 0.1 27 | 28 | # random number generator 29 | rnd_generator = RNG.RndGenerator(seed) 30 | 31 | option_vix_price = [] 32 | implied_vol_vix = [] 33 | 34 | value = quad_vec(lambda x: np.exp(nu * nu * np.power(x, 2.0 * h)), 0.0, 1.0 / 12.0) 35 | beta_vix_0 = np.sqrt(value[0] / delta_vix) 36 | vix_t0 = sigma_0 * beta_vix_0 37 | 38 | vix_t_0_aux = ExpansionTools.get_vix_rbergomi_t(0.000001, delta_vix + 0.000001, delta_vix, nu, h, 39 | np.asfortranarray(v0), 40 | v0, 200) 41 | 42 | no_strikes = 30 43 | strikes = np.linspace(0.95, 1.05, no_strikes) * vix_t0 44 | 45 | map_output = RBergomi_Engine.get_path_multi_step(0.0, T, parameters, f0, sigma_0, no_paths, no_time_steps, 46 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 47 | 48 | for i in range(0, no_strikes): 49 | t_i_vix = T + delta_vix 50 | 51 | index_t_i = np.searchsorted(map_output[Types.RBERGOMI_OUTPUT.TIMES], T) 52 | vix_t = ExpansionTools.get_vix_rbergomi_t(T, t_i_vix, delta_vix, nu, h, 53 | map_output[Types.RBERGOMI_OUTPUT.VARIANCE_SPOT_PATHS][:, index_t_i], 54 | v0, 200) 55 | price = np.mean(np.maximum(vix_t - 56 | strikes[i], 0.0)) 57 | option_vix_price.append(price) 58 | 59 | implied_vol_vix.append(implied_volatility(option_vix_price[-1], vix_t0, strikes[i], T, 0.0, 0.0, 'c')) 60 | 61 | 62 | analytic_value = nu * np.sqrt(2.0 * h) * v0 * np.power(delta_vix, h - 0.5) / ((h + 0.5) * vix_t0 * vix_t0) 63 | plt.plot(strikes, implied_vol_vix, linestyle='--', label='Implied Vol VIX', color='black', marker='.') 64 | 65 | plt.xlabel('K') 66 | plt.legend() 67 | plt.show() 68 | -------------------------------------------------------------------------------- /Examples/Chapter10/Figure_10_1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import matplotlib.pylab as plt 4 | from pathlib import Path 5 | 6 | current_directory = os.path.dirname(os.path.realpath(__file__)) 7 | folder_directory = Path(current_directory) 8 | historical_data_path = os.path.join(folder_directory, 'Data', 'VIX_Info.txt') 9 | 10 | vix_data = pd.read_csv(historical_data_path, header=None, names=["Maturity", "Strike", "IV"], sep=";") 11 | 12 | strikes = list(map(lambda x: float(x), list(vix_data[vix_data["Maturity"] == "43572"]["Strike"]))) 13 | iv = list(map(lambda x: float(x), list(vix_data[vix_data["Maturity"] == "43572"]["IV"]))) 14 | 15 | plt.plot(strikes, iv, label='T = %s' % '17/04/2019', color='black', linestyle='--', marker='.') 16 | 17 | plt.xlabel('K') 18 | plt.legend() 19 | plt.show() 20 | 21 | -------------------------------------------------------------------------------- /Examples/Chapter10/Old/atm_iv_vix_heston.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_Heston import Heston_Engine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | 8 | # simulation info 9 | epsilon = 0.3 10 | k = 1.0 11 | rho = -0.9 12 | v0 = 0.05 13 | sigma_0 = np.sqrt(0.05) 14 | theta = 0.06 15 | 16 | parameters = [k, theta, epsilon, rho, v0] 17 | no_time_steps = 100 18 | f0 = 100 19 | 20 | seed = 123456789 21 | no_paths = 1000000 22 | 23 | delta_vix = 1.0 / 12.0 24 | T_VIX = [0.01, 0.025, 0.05, 0.075, 0.1] 25 | 26 | # random number generator 27 | rnd_generator = RNG.RndGenerator(seed) 28 | 29 | option_vix_price = [] 30 | implied_vol_vix = [] 31 | analytic_value = [] 32 | 33 | for i in range(0, len(T_VIX)): 34 | rnd_generator.set_seed(seed) 35 | map_output = Heston_Engine.get_path_multi_step(0.0, T_VIX[i], parameters, f0, v0, no_paths, no_time_steps, 36 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 37 | t_i_vix = T_VIX[i] + delta_vix 38 | beta_vix = (1.0 - np.exp(- k * delta_vix)) / (delta_vix * k) 39 | vix_t0 = np.sqrt((v0 - theta) * beta_vix + theta) 40 | 41 | index_t_i = np.searchsorted(map_output[Types.HESTON_OUTPUT.TIMES], T_VIX[i]) 42 | price = np.mean(np.maximum(np.sqrt(beta_vix * (map_output[Types.HESTON_OUTPUT.SPOT_VARIANCE_PATHS][:, index_t_i] - 43 | theta) + theta) - vix_t0, 0.0)) 44 | 45 | analytic_value.append(0.5 * (epsilon * sigma_0 / (k * vix_t0 * vix_t0)) * beta_vix) 46 | option_vix_price.append(price) 47 | implied_vol_vix.append(implied_volatility(option_vix_price[-1], vix_t0, vix_t0, T_VIX[i], 0.0, 0.0, 'c')) 48 | 49 | plt.plot(T_VIX, implied_vol_vix, linestyle='--', label='ATM IV VIX', color='black') 50 | plt.xlabel('T') 51 | plt.legend() 52 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter10/Old/atm_iv_vix_rbergomi.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_RBergomi import RBergomi_Engine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | from scipy.integrate import quad_vec 8 | from AnalyticEngines.MalliavinMethod import ExpansionTools 9 | 10 | # simulation info 11 | h = 0.3 12 | nu = 0.5 13 | rho = -0.6 14 | v0 = 0.05 15 | sigma_0 = np.sqrt(v0) 16 | 17 | parameters = [nu, rho, h] 18 | 19 | no_time_steps = 10 20 | f0 = 100 21 | 22 | seed = 123456789 23 | no_paths = 1000000 24 | 25 | delta_vix = 1.0 / 12.0 26 | # T_VIX = [0.01, 0.025, 0.035, 0.045, 0.05, 0.065, 0.075, 0.1] 27 | T_VIX = np.linspace(0.01, 0.1, 20) 28 | 29 | # random number generator 30 | rnd_generator = RNG.RndGenerator(seed) 31 | 32 | option_vix_price = [] 33 | implied_vol_vix = [] 34 | 35 | value = quad_vec(lambda x: np.exp(nu * nu * np.power(x, 2.0 * h)), 0.0, 1.0 / 12.0) 36 | beta_vix_0 = np.sqrt(value[0] / delta_vix) 37 | vix_t0 = sigma_0 * beta_vix_0 38 | 39 | 40 | for i in range(0, len(T_VIX)): 41 | rnd_generator.set_seed(seed) 42 | map_output = RBergomi_Engine.get_path_multi_step(0.0, T_VIX[i], parameters, f0, sigma_0, no_paths, no_time_steps, 43 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator, ) 44 | t_i_vix = T_VIX[i] + delta_vix 45 | 46 | index_t_i = np.searchsorted(map_output[Types.RBERGOMI_OUTPUT.TIMES], T_VIX[i]) 47 | 48 | vix_t = ExpansionTools.get_vix_rbergomi_t(T_VIX[i], t_i_vix, delta_vix, nu, h, 49 | map_output[Types.RBERGOMI_OUTPUT.VARIANCE_SPOT_PATHS][:, index_t_i], 50 | v0, 200) 51 | price = np.mean(np.maximum(vix_t - 52 | vix_t0, 0.0)) 53 | option_vix_price.append(price) 54 | 55 | implied_vol_vix.append(implied_volatility(option_vix_price[-1], vix_t0, vix_t0, T_VIX[i], 0.0, 0.0, 'c')) 56 | 57 | 58 | analytic_value = nu * np.sqrt(2.0 * h) * v0 * np.power(delta_vix, h - 0.5) / ((h + 0.5) * vix_t0 * vix_t0) 59 | plt.plot(T_VIX, implied_vol_vix, linestyle='--', label='ATM IV VIX', color='black', marker='.') 60 | 61 | plt.xlabel('T') 62 | plt.legend() 63 | plt.show() 64 | -------------------------------------------------------------------------------- /Examples/Chapter10/Old/atm_iv_vix_sabr.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Engine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | 8 | # simulation info 9 | alpha = 0.5 10 | nu = 0.7 11 | rho = -0.4 12 | parameters = [alpha, nu, rho] 13 | no_time_steps = 100 14 | f0 = 100 15 | 16 | seed = 123456789 17 | no_paths = 1000000 18 | 19 | delta_vix = 1.0 / 12.0 20 | T_VIX = [0.01, 0.025, 0.05, 0.075, 0.1] 21 | # markers = ['.', '*', '^', '+', 'v', ','] 22 | 23 | 24 | # random number generator 25 | rnd_generator = RNG.RndGenerator(seed) 26 | 27 | option_vix_price = [] 28 | implied_vol_vix = [] 29 | 30 | for i in range(0, len(T_VIX)): 31 | rnd_generator.set_seed(seed) 32 | map_output = SABR_Engine.get_path_multi_step(0.0, T_VIX[i], parameters, f0, no_paths, no_time_steps, 33 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 34 | t_i_vix = T_VIX[i] + delta_vix 35 | beta_vix = np.sqrt(np.exp(nu * nu * delta_vix) - 1) / (np.sqrt(delta_vix) * nu) 36 | vix_t0 = alpha * beta_vix 37 | # strikes = np.linspace(0.8, 1.2) * vix_t0 38 | 39 | index_t_i = np.searchsorted(map_output[Types.SABR_OUTPUT.TIMES], T_VIX[i]) 40 | price = np.mean(np.maximum(beta_vix * map_output[Types.SABR_OUTPUT.SIGMA_PATHS][:, index_t_i] - vix_t0, 0.0)) 41 | option_vix_price.append(price) 42 | implied_vol_vix.append(round(implied_volatility(option_vix_price[-1], vix_t0, vix_t0, T_VIX[i], 0.0, 0.0, 'c'), 5)) 43 | 44 | plt.plot(T_VIX, implied_vol_vix, linestyle='--', label='ATM IV VIX', color='black', marker='.') 45 | plt.ylim([0.6, 0.8]) 46 | plt.xlabel('K') 47 | plt.legend() 48 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter10/Old/smile_vix_2fbergomi.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_Bergomi2F import Bergomi2fEngine 5 | from Tools import RNG, Types 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | 8 | # simulation info 9 | nu_x = 0.5 10 | nu_y = 0.6 11 | rho_xy = 0.99 12 | rho_xf = -0.5 13 | rho_yf = -0.5 14 | theta = 0.7 15 | parameters = [theta, nu_x, nu_y, rho_xy, rho_xf, rho_yf] 16 | 17 | 18 | f0 = 100 19 | v0 = 0.25 20 | 21 | seed = 123456789 22 | no_paths = 1000000 23 | no_time_steps = 100 24 | 25 | # VIX spot 26 | vix_t0 = np.sqrt(v0) 27 | 28 | delta_vix = 1.0 / 12.0 29 | T = 0.1 30 | 31 | no_strikes = 30 32 | strikes = np.linspace(0.98, 1.03, no_strikes) * vix_t0 33 | 34 | # random number generator 35 | rnd_generator = RNG.RndGenerator(seed) 36 | map_output = Bergomi2fEngine.get_path_multi_step(0.0, T, parameters, f0, v0, no_paths, no_time_steps, 37 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 38 | 39 | # Check model is good 40 | mean_var = np.mean(map_output[Types.BERGOMI2F_OUTPUT.SPOT_VARIANCE_PATHS], axis=0) 41 | mean_asset = np.mean(map_output[Types.BERGOMI2F_OUTPUT.PATHS], axis=0) 42 | 43 | option_vix_price = [] 44 | implied_vol_vix = [] 45 | analytic_value = [] 46 | 47 | for i in range(0, no_strikes): 48 | 49 | index_t_i = np.searchsorted(map_output[Types.BERGOMI2F_OUTPUT.TIMES], T) 50 | price = np.mean(np.maximum(np.sqrt(map_output[Types.BERGOMI2F_OUTPUT.SPOT_VARIANCE_PATHS][:, index_t_i]) 51 | - strikes[i], 0.0)) 52 | 53 | # analytic_value.append(0.5 * (epsilon * sigma_0 / (k * vix_t0 * vix_t0)) * beta_vix) 54 | option_vix_price.append(price) 55 | implied_vol_vix.append(implied_volatility(price, vix_t0, strikes[i], T, 0.0, 0.0, 'c')) 56 | 57 | plt.plot(strikes, implied_vol_vix, linestyle='--', label='ATM IV VIX', color='black', marker='.') 58 | plt.title('theta = %s' % theta) 59 | plt.xlabel('K') 60 | plt.legend() 61 | plt.show() 62 | -------------------------------------------------------------------------------- /Examples/Chapter2/Data/ForwardsStoxx50e.txt: -------------------------------------------------------------------------------- 1 | 44081;44092;3247.00380451482 2 | 44081;44104;3243.6380886965 3 | 44081;44120;3241.67519977596 4 | 44081;44134;3240.69446288073 5 | 44081;44155;3238.04879375212 6 | 44081;44165;3238.41079406892 7 | 44081;44183;3235.74788131022 8 | 44081;44211;3231.48107348719 9 | 44081;44246;3224.11312640478 10 | 44081;44274;3222.43722878921 11 | 44081;44365;3171.73104551861 12 | 44081;44456;3159.53167135146 13 | 44081;44547;3147.79410529006 14 | 44081;44638;3137.29431703708 15 | 44081;44729;3083.42264781623 16 | 44081;44820;3072.60965976133 17 | 44081;44911;3060.9215505229 18 | 44081;45093;3000.60658453955 19 | 44081;45275;2978.67365269684 20 | 44081;45646;2904.05924178252 21 | 44081;46010;2835.34653981875 22 | 44081;46374;2767.08841194905 23 | 44081;46738;2702.69565297224 24 | 44081;47102;2637.97005977874 25 | 44081;47473;2576.16013736023 -------------------------------------------------------------------------------- /Examples/Chapter2/Data/SabrParametersStoxx50e.txt: -------------------------------------------------------------------------------- 1 | parameter;rho;nu 2 | a;-0.9874084321195522;0.011851106614211238 3 | b;0.27878536509888363;0.8744502105558788 4 | c;0.018722137432071795;0.00010000000012630307 5 | d;0.00010000000015565554;0.45680157900963253 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/Chapter2/Data/VolAtmSabrStoxx50e.txt: -------------------------------------------------------------------------------- 1 | 44081;44092;0.27818480228195 2 | 44081;44120;0.26891136370893 3 | 44081;44155;0.281044256487248 4 | 44081;44183;0.277922490964211 5 | 44081;44211;0.268323850234455 6 | 44081;44246;0.258326132830529 7 | 44081;44274;0.252776928813224 8 | 44081;44365;0.236343069300558 9 | 44081;44456;0.227304586906861 10 | 44081;44547;0.221094772698528 11 | 44081;44638;0.215217951198402 12 | 44081;44729;0.211638276949843 13 | 44081;44820;0.208196027582281 14 | 44081;44911;0.204801472940198 15 | 44081;45093;0.199583067625777 16 | 44081;45275;0.200106014726732 17 | 44081;45646;0.203284970943582 18 | 44081;46010;0.205654842462022 19 | 44081;46374;0.207357970013142 20 | 44081;46738;0.20806820338248 21 | 44081;47102;0.207881083925487 22 | 44081;47473;0.206199535874902 23 | -------------------------------------------------------------------------------- /Examples/Chapter2/Example_2_1_1.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pylab as plt 5 | 6 | from pathlib import Path 7 | from AnalyticEngines.VolatilityTools import VolatilityEstimators 8 | 9 | current_directory = os.path.dirname(os.path.realpath(__file__)) 10 | folder_directory = Path(current_directory) 11 | historical_data_path = os.path.join(folder_directory, 'Data', 'SPX_sample.txt') 12 | 13 | historical_data = pd.read_csv(historical_data_path, header=None, names=["DateTime", "Open", "High", "Low", "Close"], 14 | sep=",") 15 | 16 | sampling_dates = sorted(list(set(historical_data["DateTime"].apply(lambda x: x[0:10])))) 17 | 18 | rv_i_s = [] 19 | end_date_rv_i_s = [] 20 | end_date_spot_i_s = [] 21 | rv_i_s_fourier = [] 22 | spot_vol_i_s_fourier = [] 23 | 24 | spot_vol = [0] 25 | alpha_t_min = 24.0 * 60.0 * 253 26 | sum_rv_i_1 = 0.0 27 | sum_rv_i_1_fourier = 0.0 28 | i_cont = 0 29 | for date in sampling_dates: 30 | log_price_intraday = np.log(np.array(historical_data["Close"][historical_data["DateTime"].apply(lambda x: x[0:10] == date)])) 31 | 32 | # fourier estimator 33 | no_time_steps = len(log_price_intraday) 34 | delta_time = no_time_steps/(60 * 24) 35 | log_paths = np.zeros(shape=(1, no_time_steps)) 36 | log_paths[0] = log_price_intraday 37 | frequency = 1 38 | t_k = np.linspace(i_cont * delta_time, (i_cont + 1) * delta_time, no_time_steps) 39 | rv_i_s_fourier.append(VolatilityEstimators.get_integrated_variance_fourier(log_paths, t_k, frequency, 1)[0] + 40 | sum_rv_i_1_fourier) 41 | 42 | spot_vol_i_s_fourier.append(VolatilityEstimators.get_spot_variance_fourier(log_paths, t_k, 1, t_k[-1])[0] * 253.0) 43 | 44 | aux_rv = list(np.cumsum(np.power(np.diff(log_price_intraday), 2.0)) + sum_rv_i_1) 45 | rv_i_s += aux_rv 46 | end_date_rv_i_s.append(rv_i_s[-1]) 47 | spot_vol += [spot_vol[-1]] + list(np.diff(np.cumsum(np.power(np.diff(log_price_intraday), 2.0))) * alpha_t_min) 48 | end_date_spot_i_s.append((rv_i_s[-1] - sum_rv_i_1) * 253.0) 49 | sum_rv_i_1 = rv_i_s[-1] 50 | sum_rv_i_1_fourier = rv_i_s_fourier[-1] 51 | i_cont += 1 52 | days = np.arange(0, len(sampling_dates)) 53 | # rv estimator 54 | # plt.plot(days, end_date_rv_i_s, label="rv_estimator_integrated_variance", color="black") 55 | # plt.plot(days, rv_i_s_fourier, label="fourier_estimator_integrated_variance", color="black", linestyle="dashed") 56 | 57 | # spot vol estimator 58 | plt.plot(days, end_date_spot_i_s, label="rv_estimator_spot_vol", color="black") 59 | plt.plot(days, spot_vol_i_s_fourier, label="fourier_estimator_spot_vol", color="black", linestyle="dashed") 60 | 61 | plt.xlabel('$\it{t}$'+ ' (traiding days)') 62 | plt.legend() 63 | plt.show() 64 | 65 | -------------------------------------------------------------------------------- /Examples/Chapter2/Example_2_3_3.py: -------------------------------------------------------------------------------- 1 | from MC_Engines.MC_SABR import SABR_Engine 2 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 3 | from Tools import Types 4 | from Tools import RNG 5 | from VolatilitySurface.Tools import SABRTools 6 | from py_vollib.black_scholes_merton import black_scholes_merton 7 | 8 | import numpy as np 9 | import matplotlib.pylab as plt 10 | 11 | # parameters 12 | nu = 1.1 13 | alpha = 0.4 14 | rho = - 0.6 15 | parameters = [alpha, nu, rho] 16 | 17 | f0 = 100 18 | seed = 123456789 19 | no_paths = 1000000 20 | T = 3.0 21 | delta = 1.0 / 100.0 22 | no_time_steps = int(T / delta) 23 | 24 | delta_strike = 0.1 25 | k_s = np.arange(30.0, 230.0, delta_strike) 26 | notional = 1.0 27 | 28 | rnd_generator = RNG.RndGenerator(seed) 29 | 30 | european_options = [] 31 | 32 | for k_i in k_s: 33 | european_options.append(EuropeanOption(k_i, notional, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T)) 34 | 35 | map_output = SABR_Engine.get_path_multi_step(0.0, T, parameters, f0, no_paths, no_time_steps, 36 | Types.TYPE_STANDARD_NORMAL_SAMPLING.REGULAR_WAY, 37 | rnd_generator) 38 | 39 | option_prices_mc = [] 40 | option_prices_hagan = [] 41 | density_mc = [] 42 | density_hagan = [] 43 | no_options = len(european_options) 44 | for i in range(0, no_options): 45 | result = european_options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 46 | option_prices_mc.append(result[0]) 47 | z = np.log(f0 / k_s[i]) 48 | iv_hagan = SABRTools.sabr_vol_jit(alpha, rho, nu, z, T) 49 | option_prices_hagan.append(black_scholes_merton('c', f0, k_s[i], T, 0.0, iv_hagan, 0.0)) 50 | 51 | pdf_hagan = [] 52 | pdf_mc = [] 53 | plt.figure(figsize=(8, 5)) 54 | for i in range(1, no_options - 1): 55 | pdf_hagan.append((option_prices_hagan[i+1] - 2.0 * option_prices_hagan[i] + option_prices_hagan[i-1]) / (delta_strike * delta_strike)) 56 | pdf_mc.append((option_prices_mc[i+1] - 2.0 * option_prices_mc[i] + option_prices_mc[i-1]) / (delta_strike * delta_strike)) 57 | 58 | plt.plot(k_s[1:no_options-1], pdf_hagan, label="Hagan's density", linestyle='--', color='black', linewidth=0.5) 59 | plt.plot(k_s[1:no_options-1], pdf_mc, label="MC's density", marker='.', linestyle='-', markersize=3, color='black', linewidth=0.5) 60 | 61 | plt.xlabel("strike") 62 | plt.title("Negative values for Hagan's density") 63 | plt.legend() 64 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter2/Example_2_4_1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | from MC_Engines.MC_Heston import Heston_Engine 4 | from Tools import Types 5 | from Tools import RNG 6 | from AnalyticEngines.VolatilityTools import NonParametricEstimatorSLV 7 | 8 | epsilon = 0.9 9 | k = 0.5 10 | rho = -0.9 11 | v0 = 0.05 12 | theta = 0.05 13 | 14 | f0 = 100 15 | T = 0.25 16 | 17 | seed = 123456789 18 | 19 | delta = 1.0 / 32.0 20 | no_time_steps = int(T / delta) 21 | no_paths = 100000 22 | strike = 120.0 23 | 24 | rnd_generator = RNG.RndGenerator(seed) 25 | 26 | parameters = [k, theta, epsilon, rho] 27 | 28 | # Heston paths simulation 29 | map_heston_output = Heston_Engine.get_path_multi_step(0.0, T, parameters, f0, v0, no_paths, 30 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 31 | rnd_generator) 32 | 33 | # Compute the conditional expected by kernel estimators 34 | x = np.linspace(60.0, 120.0, 500) 35 | 36 | # Julien Guyon paper https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1885032&download=yes 37 | h = 1.5 * np.sqrt(T) * np.power(no_paths, - 0.2) 38 | 39 | gaussian_estimator_t = NonParametricEstimatorSLV.gaussian_kernel_estimator_slv(map_heston_output[Types.HESTON_OUTPUT.SPOT_VARIANCE_PATHS][:, -1], 40 | map_heston_output[Types.HESTON_OUTPUT.PATHS][:, -1], 41 | x, 42 | h) 43 | 44 | 45 | plt.plot(x, gaussian_estimator_t, label="gaussian kernel estimator", color="black", linestyle="dotted") 46 | plt.xlabel("S_t") 47 | plt.ylabel("E(V_t|S_t=x)") 48 | 49 | 50 | plt.legend() 51 | plt.show() 52 | -------------------------------------------------------------------------------- /Examples/Chapter2/Old/Example11.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numba as nb 3 | import matplotlib.pylab as plt 4 | 5 | # from ncephes import ndtri 6 | from scipy.special import ndtri 7 | from AnalyticEngines.VolatilityTools import VolatilityEstimators 8 | 9 | 10 | # numba function to get market paths 11 | @nb.jit("(f8,f8,f8,f8,f8,f8,i8,i8,i8)", nopython=True, nogil=True) 12 | def get_paths(p0: float, sigma0: float, t: float, theta: float, w: float, k: float, 13 | no_paths: int, no_time_steps: int, seed: int): 14 | 15 | paths = np.empty(shape=(no_paths, no_time_steps)) 16 | v_t = np.empty(shape=(no_paths, no_time_steps)) 17 | 18 | paths[:, 0] = p0 19 | v_t[:, 0] = sigma0 * sigma0 20 | 21 | t_i_s = np.linspace(0.0, t, no_time_steps) 22 | np.random.rand(seed) 23 | nu = np.sqrt(2.0 * k * theta) 24 | 25 | for i in range(0, no_paths): 26 | u_s = np.random.rand(no_time_steps) 27 | u_sigma = np.random.rand(no_time_steps) 28 | 29 | for j in range(1, no_time_steps): 30 | delta_time = (t_i_s[j] - t_i_s[j - 1]) 31 | z_sigma_i = ndtri(u_sigma[j]) 32 | z_s_i = ndtri(u_s[j]) 33 | exp_t = np.exp(- theta * delta_time) 34 | v_t[i, j] = v_t[i, j - 1] * exp_t + w * (1.0 - exp_t) + \ 35 | nu * np.sqrt(0.5 * ((1.0 - exp_t * exp_t) / theta)) * v_t[i, j - 1] * z_sigma_i 36 | paths[i, j] = paths[i, j - 1] + np.sqrt(v_t[i, j - 1]) * np.sqrt(delta_time) * z_s_i 37 | 38 | return paths, v_t, t_i_s 39 | 40 | 41 | # market simulation 42 | theta = 0.035 43 | w = 0.6365 44 | k = 0.2962 45 | 46 | p0 = np.log(100) 47 | sigma0 = np.sqrt(0.6365) 48 | 49 | t = 1.0 50 | seed = 123456 51 | 52 | no_time_steps = 365 * 2 53 | no_paths = 1 54 | 55 | # Simulated integrated variance 56 | paths, v_t, t_i_s = get_paths(p0, sigma0, t, theta, w, k, no_paths, no_time_steps, seed) 57 | 58 | # We compute the fourier spot volatility estimator and we will compare with the simulated path. 59 | spot_volatility_estimator = [] 60 | simulated_path_vol = [] 61 | t_j = [] 62 | 63 | for i in range(1, no_time_steps): 64 | t_j.append(t_i_s[i]) 65 | estimator = VolatilityEstimators.get_spot_variance_fourier(paths, t_i_s, no_paths, t_i_s[i]) 66 | simulated_path_vol.append(v_t[0, i]) 67 | spot_volatility_estimator.append(estimator) 68 | 69 | plt.plot(t_j, np.array(spot_volatility_estimator), color='black', label='estimator_path') 70 | plt.plot(t_j, np.array(simulated_path_vol), linestyle='dashed', color='black', label='simulated_path',) 71 | plt.legend() 72 | plt.show() 73 | -------------------------------------------------------------------------------- /Examples/Chapter2/Remark_2_2_6.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | 4 | from functools import partial 5 | from MC_Engines.MC_LocalVol import LocalVolFunctionals, LocalVolEngine 6 | from scipy.interpolate import interp1d 7 | from Solvers.PDE_Solver import PDESolvers 8 | from Solvers.PDE_Solver import PDEOperators 9 | from Solvers.PDE_Solver.Meshes import uniform_mesh, Mesh, LnUnderlyingMesh 10 | from Solvers.PDE_Solver.PDEs import LN_FORWARD_LOCAL_VOL_PDE, PDE 11 | from Solvers.PDE_Solver.Types import BoundaryConditionType, np_ndarray, SchemeType 12 | from Solvers.PDE_Solver.TerminalConditions import TerminalCondition 13 | from Solvers.PDE_Solver.BoundariesConditions import Zero_Laplacian_BC 14 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 15 | from AnalyticEngines.LocalVolatility.Hagan import ExpansionLocVol 16 | 17 | 18 | def f_ln_payoff(mesh: Mesh, k: float) -> np_ndarray: 19 | return np.maximum(np.exp(mesh.get_points()) - k, 0.0) 20 | 21 | 22 | def get_vetor_iv_cev(nu: float, alpha: float, T: float, f0: float): 23 | # Smile curve with cev 24 | mesh_t = Mesh(uniform_mesh, 100, 0.0, T) 25 | mesh_x = LnUnderlyingMesh(0.0, 0.0, nu, f0, T, 0.999, uniform_mesh, 200) 26 | log_diffusion = partial(LocalVolFunctionals.log_cev_diffusion, beta=alpha - 1, sigma=nu) 27 | cev_pde = PDE.from_ipde_terms(LN_FORWARD_LOCAL_VOL_PDE(log_diffusion)) 28 | 29 | k_s = np.arange(f0 - 3.0, f0 + 3.0, 0.5) 30 | # k_s = [f0] 31 | tc_s = [TerminalCondition(partial(f_ln_payoff, k=k_i)) for k_i in k_s] 32 | bc = Zero_Laplacian_BC() 33 | operator_exp = PDEOperators.LinearPDEOperator(mesh_x, cev_pde, bc) 34 | operator_impl = PDEOperators.LinearPDEOperator(mesh_x, cev_pde, bc) 35 | operators = [operator_exp, operator_impl] 36 | 37 | pde_price = [] 38 | 39 | for tc_i in tc_s: 40 | pd_solver = PDESolvers.FDSolver(mesh_t, 41 | mesh_x, 42 | operators, 43 | SchemeType.CRANK_NICOLSON, 44 | BoundaryConditionType.ZERO_DIFFUSION, 45 | tc_i) 46 | 47 | pd_solver.solver() 48 | f = interp1d(mesh_x.get_points(), pd_solver._u_grid[:, 0], kind='linear', fill_value='extrapolate') 49 | pde_price.append(float(f(np.log(f0)))) 50 | 51 | # Compute the iv 52 | no_elements = len(pde_price) 53 | 54 | # From hagan 55 | iv_fd = [] 56 | z_s = [] 57 | for i in range(0, no_elements): 58 | z_s.append(np.log(k_s[i] / f0)) 59 | iv_fd.append(implied_volatility(pde_price[i], f0, k_s[i], T, 0.0, 0.0, 'c')) 60 | 61 | return z_s, iv_fd 62 | 63 | 64 | T = 1 65 | 66 | alpha = 0.3 67 | nu = 0.4 68 | 69 | # CEV parameter 70 | styles = ['o', '*', 'x', '^', '.'] 71 | f0_s = [10.0, 10.25, 10.5, 10.75, 11.0] 72 | no_f0 = len(f0_s) 73 | plt.figure() 74 | plt.title("CEV smile dynamic for gamma = %s and sigma = %s " % (nu, alpha)) 75 | for i in range(0, no_f0): 76 | z_s, iv_fd = get_vetor_iv_cev(nu, alpha, T, f0_s[i]) 77 | plt.plot(z_s, iv_fd, label="S0=" + str(f0_s[i]), linestyle='--', marker=styles[i], color='black') 78 | 79 | plt.xlabel("ln(k/f)") 80 | plt.legend() 81 | plt.show() 82 | -------------------------------------------------------------------------------- /Examples/Chapter2/skew_Heston_differents_time.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction, HestonCharesticFunction 4 | from functools import partial 5 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 8 | 9 | # European option price 10 | no_strikes = 22 11 | k_s = np.linspace(70.0, 130.0, no_strikes) 12 | f0 = 100.0 13 | x0 = np.log(f0) 14 | 15 | # Heston parameters 16 | epsilon = 0.75 17 | k = 0.6 18 | rho = -0.5 19 | v0 = 0.25 20 | theta = 0.5 21 | b2 = k 22 | u2 = -0.5 23 | 24 | # Upper and lower bound for cos integral 25 | a = -2.0 26 | b = 2.0 27 | 28 | # maturities 29 | T = [0.2, 0.4, 0.6, 1.0] 30 | markers = ['.', '+', '*', '^'] 31 | no_maturities = len(T) 32 | 33 | for i in range(0, no_maturities): 34 | cf_heston = partial(HestonCharesticFunction.get_trap_cf, t=T[i], r_t=0.0, x=x0, v=v0, theta=theta, rho=rho, k=k, epsilon=epsilon, b=b2, u=u2) 35 | 36 | cos_price_heston = COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 128, k_s, cf_heston) 37 | 38 | iv_smile_heston = [] 39 | for j in range(0, no_strikes): 40 | iv_smile_heston.append(implied_volatility(cos_price_heston[j], f0, f0, T[i], 0.0, 0.0, 'c')) 41 | 42 | plt.plot(k_s, iv_smile_heston, label='T=%s' % T[i], linestyle='--', color='black', marker=markers[i]) 43 | 44 | plt.ylim([0.0, 2.0]) 45 | plt.xlabel('K') 46 | plt.legend() 47 | plt.show() 48 | -------------------------------------------------------------------------------- /Examples/Chapter2/skew_rBergomi_differents_times.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_RBergomi import RBergomi_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | 9 | 10 | # simulation info 11 | nu = 0.8 12 | rho = -0.4 13 | v0 = 0.15 14 | h = 0.1 15 | sigma_0 = np.sqrt(v0) 16 | parameters = [nu, rho, h] 17 | 18 | seed = 123456789 19 | no_paths = 1000000 20 | no_time_steps = 100 21 | 22 | # random number generator 23 | rnd_generator = RNG.RndGenerator(seed) 24 | 25 | # option information 26 | f0 = 100.0 27 | T_s = [0.1, 0.2, 0.4, 0.6, 1.0] 28 | markers = ['.', '+', '*', '^', 'v'] 29 | no_strikes = 22 30 | k_s = np.linspace(80.0, 120.0, no_strikes) 31 | 32 | no_k_s = len(k_s) 33 | no_T_s = len(T_s) 34 | 35 | options = [] 36 | for t_i in T_s: 37 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, t_i)) 38 | 39 | for i in range(0, no_T_s): 40 | 41 | rnd_generator.set_seed(seed) 42 | 43 | map_output = RBergomi_Engine.get_path_multi_step(0.0, T_s[i], parameters, f0, v0, no_paths, 44 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 45 | rnd_generator) 46 | 47 | options = [] 48 | for k_i in k_s: 49 | options.append(EuropeanOption(k_i, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T_s[i])) 50 | 51 | implied_vol = [] 52 | for j in range(0, no_k_s): 53 | mc_option_price = options[j].get_price_control_variate(map_output[Types.RBERGOMI_OUTPUT.PATHS][:, -1], 54 | map_output[Types.RBERGOMI_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 55 | implied_vol.append(implied_volatility(mc_option_price[0], f0, k_s[j], T_s[i], 0.0, 0.0, 'c')) 56 | 57 | plt.plot(k_s, implied_vol, label="T=%s" % round(T_s[i], 5), linestyle='--', marker=markers[i], color='black') 58 | 59 | plt.xlabel('K') 60 | plt.ylabel('iv') 61 | plt.legend() 62 | plt.show() 63 | -------------------------------------------------------------------------------- /Examples/Chapter5/Figure_5_3.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | from FractionalBrownian import fBM 4 | from Tools import RNG 5 | 6 | no_paths = 1 7 | no_time_steps = 2 ** 10 8 | t0 = 0.0 9 | t1 = 1.0 10 | z0 = 0.0 11 | seed = 123456789 12 | rng = RNG.RndGenerator(seed) 13 | 14 | # Low Hurst parameter 15 | low_hurst_parameter = 0.2 16 | paths_low_hurst_parameter = fBM.cholesky_method(t0, t1, z0, rng, low_hurst_parameter, no_paths, int(no_time_steps * t1)) 17 | 18 | # Medium Hurst parameter 19 | rng.set_seed(seed) 20 | medium_hurst_parameter = 0.5 21 | paths_medium_hurst_parameter = fBM.cholesky_method(t0, t1, z0, rng, medium_hurst_parameter, no_paths, 22 | int(no_time_steps * t1)) 23 | 24 | # Large Hurst parameter 25 | rng.set_seed(seed) 26 | large_hurst_parameter = 0.8 27 | paths_large_hurst_parameter = fBM.cholesky_method(t0, t1, z0, rng, large_hurst_parameter, no_paths, 28 | int(no_time_steps * t1)) 29 | 30 | paths_aggregated = paths_low_hurst_parameter[0, :] + paths_large_hurst_parameter[0, :] 31 | 32 | fig, axs = plt.subplots(3, 1, figsize=(5, 5)) 33 | 34 | t = np.linspace(t0, t1, no_time_steps) 35 | axs[0].plot(t, paths_low_hurst_parameter[0, :].reshape(t.shape), color='black') 36 | axs[0].set_title('H=' + str(low_hurst_parameter)) 37 | axs[0].set_xticks(np.arange(t0, t1 + 0.5, 0.5)) 38 | 39 | 40 | axs[1].plot(t, paths_medium_hurst_parameter[0, :].reshape(t.shape), color='black') 41 | axs[1].set_title('H=' + str(medium_hurst_parameter)) 42 | axs[1].set_xticks(np.arange(t0, t1 + 0.5, 0.5)) 43 | 44 | 45 | axs[2].plot(t, paths_large_hurst_parameter[0, :].reshape(t.shape), color='black') 46 | axs[2].set_title('H=' + str(large_hurst_parameter)) 47 | axs[2].set_xticks(np.arange(t0, t1 + 0.5, 0.5)) 48 | 49 | 50 | plt.show() 51 | 52 | -------------------------------------------------------------------------------- /Examples/Chapter5/Figure_5_4.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | from Tools import RNG 4 | from FractionalBrownian import fBM 5 | 6 | no_paths = 1 7 | no_time_steps = 2**10 8 | t0 = 0.0 9 | t1 = 1.0 10 | z0 = 0.0 11 | seed = 123456789 12 | rng = RNG.RndGenerator(seed) 13 | 14 | # Time steps to compute 15 | t = np.linspace(t0, t1, int(no_time_steps * t1)) 16 | 17 | # Low Hurst parameter 18 | low_hurst_parameter = 0.3 19 | paths_low_hurst_parameter = fBM.cholesky_method(t0, t1, z0, rng, low_hurst_parameter, no_paths, int(no_time_steps * t1)) 20 | 21 | # Medium Hurst parameter 22 | rng.set_seed(seed) 23 | medium_hurst_parameter = 0.5 24 | paths_medium_hurst_parameter = fBM.cholesky_method(t0, t1, z0, rng, medium_hurst_parameter, no_paths, int(no_time_steps * t1)) 25 | 26 | # Large Hurst parameter 27 | rng.set_seed(seed) 28 | large_hurst_parameter = 0.7 29 | paths_large_hurst_parameter = fBM.cholesky_method(t0, t1, z0, rng, large_hurst_parameter, no_paths, int(no_time_steps * t1)) 30 | 31 | 32 | fig, axs = plt.subplots(1, 3, figsize=(7, 3)) 33 | 34 | axs[0].plot(t, paths_low_hurst_parameter[0, :].reshape(t.shape), color='black') 35 | axs[0].set_title('$\it{H}=' + str(low_hurst_parameter)) 36 | axs[0].set_xticks(np.arange(t0, t1 + 0.5, 0.5)) 37 | 38 | axs[1].plot(t, paths_medium_hurst_parameter[0, :].reshape(t.shape), color='black') 39 | axs[1].set_title('\it{H}=' + str(medium_hurst_parameter)) 40 | axs[1].set_xticks(np.arange(t0, t1 + 0.5, 0.5)) 41 | 42 | axs[2].plot(t, paths_large_hurst_parameter[0, :].reshape(t.shape), color='black') 43 | axs[2].set_title('\it{H}=' + str(large_hurst_parameter)) 44 | axs[2].set_xticks(np.arange(t0, t1 + 0.5, 0.5)) 45 | 46 | plt.show() 47 | -------------------------------------------------------------------------------- /Examples/Chapter5/Figure_5_5.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | from Tools import RNG 3 | from FractionalBrownian import fBM, ToolsFBM 4 | 5 | no_paths = 1 6 | no_time_steps = 2 ** 11 7 | t0 = 0.0 8 | t1 = 1.0 9 | z0 = 0.0 10 | seed = 123456789 11 | rng = RNG.RndGenerator(seed) 12 | 13 | fig, axs = plt.subplots(1, 3, figsize=(7, 3)) 14 | 15 | # Low Hurst parameter 16 | low_hurst_parameter = 0.3 17 | low_hurst_parameter_paths = fBM.cholesky_method(t0, t1, z0, rng, low_hurst_parameter, no_paths, int(no_time_steps * t1)) 18 | low_output_estimation = ToolsFBM.get_estimator_rs(low_hurst_parameter_paths[0, :], 5, 10) 19 | 20 | axs[0].plot(low_output_estimation[2], low_output_estimation[3], color='black') 21 | axs[0].plot(low_output_estimation[2], low_output_estimation[4], linestyle='dashed', color='black') 22 | axs[0].set_title('y='+str(round(low_output_estimation[0], 4)) + '+' + str(round(low_output_estimation[1], 4)) + '*x') 23 | 24 | # Medium Hurst parameter 25 | rng.set_seed(seed) 26 | medium_hurst_parameter = 0.5 27 | medium_hurst_parameter_paths = fBM.cholesky_method(t0, t1, z0, rng, medium_hurst_parameter, no_paths, int(no_time_steps * t1)) 28 | medium_output_estimation = ToolsFBM.get_estimator_rs(medium_hurst_parameter_paths[0, :], 5, 10) 29 | 30 | axs[1].plot(medium_output_estimation[2], medium_output_estimation[3], color='black') 31 | axs[1].plot(medium_output_estimation[2], medium_output_estimation[4], linestyle='dashed', color='black') 32 | axs[1].set_title('y='+str(round(medium_output_estimation[0], 4)) + '+' + str(round(medium_output_estimation[1], 4)) + '*x') 33 | 34 | # Large Hurst parameter 35 | rng.set_seed(seed) 36 | large_hurst_parameter = 0.7 37 | large_hurst_parameter_paths = fBM.cholesky_method(t0, t1, z0, rng, large_hurst_parameter, no_paths, int(no_time_steps * t1)) 38 | large_output_estimation = ToolsFBM.get_estimator_rs(large_hurst_parameter_paths[0, :], 5, 10) 39 | 40 | axs[2].plot(large_output_estimation[2], large_output_estimation[3], color='black') 41 | axs[2].plot(large_output_estimation[2], large_output_estimation[4], linestyle='dashed', color='black') 42 | 43 | axs[2].set_title('y='+str(round(large_output_estimation[0], 4)) + '+' + str(round(large_output_estimation[1], 4)) + '*x') 44 | 45 | 46 | plt.show() 47 | -------------------------------------------------------------------------------- /Examples/Chapter5/Figure_5_5_1.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from Tools import RNG 5 | from FractionalBrownian import fBM 6 | from statsmodels.graphics.tsaplots import plot_acf 7 | 8 | # simulation info 9 | h = 0.8 10 | no_time_steps = 2000 11 | T = 1.0 12 | d_t = np.linspace(0.0, T, no_time_steps) 13 | seed = 2357575 14 | 15 | # random number generator 16 | rnd_generator = RNG.RndGenerator(seed) 17 | 18 | rnd_generator.set_seed(seed) 19 | path = fBM.truncated_fbm(0.0, T, 0.0, rnd_generator, h, 1, no_time_steps) 20 | plot_acf(np.diff(path[0, :].reshape(d_t.shape)), lags=30, title='$\it{H}=$' + str(h)) 21 | 22 | plt.show() 23 | -------------------------------------------------------------------------------- /Examples/Chapter5/Figure_5_5_2.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from Tools import RNG 5 | from FractionalBrownian import fBM 6 | from statsmodels.graphics.tsaplots import plot_acf 7 | 8 | # simulation info 9 | h_s = [0.1, 0.3, 0.6, 0.8] 10 | no_time_steps = 2000 11 | T = 1.0 12 | d_t = np.linspace(0.0, T, no_time_steps) 13 | seed = 2357575 14 | 15 | # random number generator 16 | rnd_generator = RNG.RndGenerator(seed) 17 | 18 | fig, axs = plt.subplots(4, 1) 19 | 20 | for i in range(0, len(h_s)): 21 | rnd_generator.set_seed(seed) 22 | path = fBM.truncated_fbm(0.0, T, 0.0, rnd_generator, h_s[i], 1, no_time_steps) 23 | axs[i].plot(d_t, path[0, :].reshape(d_t.shape), color='black') 24 | axs[i].set_title('$\it{H}=$' + str(h_s[i])) 25 | 26 | plt.show() 27 | -------------------------------------------------------------------------------- /Examples/Chapter5/Figure_5_6.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | from Tools import RNG 4 | from FractionalBrownian import fBM, ToolsFBM 5 | 6 | no_paths = 1 7 | no_time_steps = 2 ** 11 8 | t0 = 0.0 9 | t1 = 1.0 10 | z0 = 0.0 11 | seed = 123456789 12 | rng = RNG.RndGenerator(seed) 13 | 14 | fig, axs = plt.subplots(1, 3, figsize=(7, 3)) 15 | 16 | # Low Hurst parameter 17 | low_hurst_parameter = 0.3 18 | low_hurst_parameter_paths = fBM.cholesky_method(t0, t1, z0, rng, low_hurst_parameter, no_paths, int(no_time_steps * t1)) 19 | low_output_estimation = ToolsFBM.get_estimator_rs(np.diff(low_hurst_parameter_paths[0, :]), 5, 10) 20 | axs[0].plot(low_output_estimation[2], low_output_estimation[3], color='black') 21 | axs[0].plot(low_output_estimation[2], low_output_estimation[4], linestyle='dashed', color='black') 22 | axs[0].set_title('y='+str(round(low_output_estimation[0], 4)) + '+' + str(round(low_output_estimation[1], 4)) + '*x') 23 | 24 | # Medium Hurst parameter 25 | rng.set_seed(seed) 26 | medium_hurst_parameter = 0.5 27 | medium_hurst_parameter_paths = fBM.cholesky_method(t0, t1, z0, rng, medium_hurst_parameter, no_paths, int(no_time_steps * t1)) 28 | medium_output_estimation = ToolsFBM.get_estimator_rs(np.diff(medium_hurst_parameter_paths[0, :]), 5, 10) 29 | 30 | axs[1].plot(medium_output_estimation[2], medium_output_estimation[3], color='black') 31 | axs[1].plot(medium_output_estimation[2], medium_output_estimation[4], linestyle='dashed', color='black') 32 | axs[1].set_title('y='+str(round(medium_output_estimation[0], 4)) + '+' + str(round(medium_output_estimation[1], 4)) + '*x') 33 | 34 | # Large Hurst parameter 35 | rng.set_seed(seed) 36 | large_hurst_parameter = 0.7 37 | large_hurst_parameter_paths = fBM.cholesky_method(t0, t1, z0, rng, large_hurst_parameter, no_paths, int(no_time_steps * t1)) 38 | large_output_estimation = ToolsFBM.get_estimator_rs(np.diff(large_hurst_parameter_paths[0, :]), 5, 10) 39 | 40 | axs[2].plot(large_output_estimation[2], large_output_estimation[3], color='black') 41 | axs[2].plot(large_output_estimation[2], large_output_estimation[4], linestyle='dashed', color='black') 42 | 43 | axs[2].set_title('y='+str(round(large_output_estimation[0], 4)) + '+' + str(round(large_output_estimation[1], 4)) + '*x') 44 | 45 | 46 | plt.show() 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Examples/Chapter5/Old/ExampleFbm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from Tools import RNG, AnalyticTools 3 | from FractionalBrownian import fBM 4 | 5 | no_paths = 100000 6 | no_time_steps = 2**6 7 | t0 = 0.0 8 | t1 = 1.0 9 | z0 = 0.0 10 | hurst_parameter = 0.5 11 | seed = 123456789 12 | 13 | rng = RNG.RndGenerator(seed) 14 | paths = fBM.cholesky_method(t0, t1, z0, rng, hurst_parameter, no_paths, int(no_time_steps * t1)) 15 | 16 | # Time steps to compute 17 | t = np.linspace(t0, t1, int(no_time_steps * t1)) 18 | 19 | # Compute mean 20 | empirical_mean = np.mean(paths, axis=0) 21 | 22 | # Compute variance 23 | empirical_variance = np.var(paths, axis=0) 24 | exact_variance = [fBM.covariance(t_i, t_i, hurst_parameter) for t_i in t] 25 | 26 | # Compute covariance 27 | no_full_time_steps = len(t) 28 | empirical_covariance = np.zeros(shape=(no_time_steps, no_full_time_steps)) 29 | exact_covariance = np.zeros(shape=(no_time_steps, no_full_time_steps)) 30 | 31 | for i in range(0, no_time_steps): 32 | for j in range(0, i): 33 | empirical_covariance[i, j] = np.mean(AnalyticTools.dot_wise(paths[:, i], paths[:, j])) - \ 34 | empirical_mean[i] * empirical_mean[j] 35 | exact_covariance[i, j] = fBM.covariance(t[i], t[j], hurst_parameter) 36 | empirical_covariance[j, i] = empirical_covariance[i, j] 37 | exact_covariance[j, i] = exact_covariance[i, j] 38 | 39 | error_covariance = np.max(np.abs(empirical_covariance - exact_covariance)) 40 | error_variance = np.max(np.abs(exact_variance - empirical_variance)) 41 | error_mean = np.max(np.abs(empirical_mean)) 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Examples/Chapter6/Example_6_5_17.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | from scipy.optimize import curve_fit 9 | from AnalyticEngines.MalliavinMethod import ExpansionTools 10 | 11 | 12 | dt = np.arange(7, 30, 1) * 1.0 / 365.0 13 | no_dt_s = len(dt) 14 | 15 | # simulation info 16 | alpha = 0.3 17 | nu = 0.6 18 | rho = -0.6 19 | parameters = [alpha, nu, rho] 20 | no_time_steps = 100 21 | 22 | seed = 123456789 23 | no_paths = 500000 24 | delta_time = 1.0 / 365.0 25 | 26 | # random number generator 27 | rnd_generator = RNG.RndGenerator(seed) 28 | 29 | # option information 30 | f0 = 100.0 31 | options = [] 32 | implied_vol_atm = [] 33 | for d_i in dt: 34 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 35 | 36 | # outputs 37 | vol_swap_approximation = [] 38 | vol_swap_mc = [] 39 | implied_vol_atm = [] 40 | implied_vol_approx = [] 41 | output = [] 42 | 43 | for i in range(0, no_dt_s): 44 | rnd_generator.set_seed(seed) 45 | map_output = SABR_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, no_paths, no_time_steps, 46 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 47 | 48 | mc_option_price = options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 49 | implied_vol_approx.append(ExpansionTools.get_iv_atm_sabr_approximation(np.array(parameters), dt[i])) 50 | implied_vol_atm.append(implied_volatility(mc_option_price[0], f0, f0, dt[i], 0.0, 0.0, 'c')) 51 | vol_swap_mc.append(np.mean(np.sqrt(np.sum(map_output[Types.SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS], 1) / dt[i]))) 52 | output.append((implied_vol_atm[-1] - vol_swap_mc[-1])) 53 | 54 | # curve fit 55 | 56 | 57 | def f_law(x, a, b): 58 | return a + b * x 59 | 60 | 61 | popt, pcov = curve_fit(f_law, dt, output) 62 | y_fit_values = f_law(dt, *popt) 63 | 64 | plt.plot(dt, output, label='(I(0,f0) - E(v_0))', linestyle='--', color='black') 65 | plt.plot(dt, y_fit_values, label="%s %s * T" % (round(popt[0], 5), round(popt[1], 5)), marker='.', 66 | linestyle='--', color='black') 67 | 68 | plt.xlabel('T') 69 | plt.legend() 70 | 71 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter6/Example_6_5_18.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_Heston import Heston_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | from scipy.optimize import curve_fit 9 | from AnalyticEngines.MalliavinMethod import ExpansionTools 10 | 11 | 12 | dt = np.arange(5, 30, 1) * 1.0 / 365.0 13 | no_dt_s = len(dt) 14 | 15 | # simulation info 16 | epsilon = 0.3 17 | k = 1.0 18 | rho = -0.9 19 | v0 = 0.05 20 | sigma_0 = np.sqrt(0.05) 21 | theta = 0.06 22 | 23 | parameters = [k, theta, epsilon, rho, v0] 24 | 25 | seed = 123456789 26 | no_paths = 250000 27 | delta_time = 1.0 / 365.0 28 | no_time_steps = 100 29 | 30 | # random number generator 31 | rnd_generator = RNG.RndGenerator(seed) 32 | 33 | # option information 34 | f0 = 200.0 35 | options = [] 36 | implied_vol_atm = [] 37 | for d_i in dt: 38 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 39 | 40 | # outputs 41 | vol_swap_approximation = [] 42 | vol_swap_mc = [] 43 | implied_vol_atm = [] 44 | implied_vol_approx = [] 45 | output = [] 46 | 47 | for i in range(0, no_dt_s): 48 | # no_time_steps = int(dt[i] / delta_time) 49 | rnd_generator.set_seed(seed) 50 | map_output = Heston_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, v0, no_paths, 51 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 52 | rnd_generator) 53 | 54 | mc_option_price = options[i].get_price(map_output[Types.HESTON_OUTPUT.PATHS][:, -1]) 55 | option_price = options[i].get_analytic_value(0.0, theta, rho, k, epsilon, v0, 0.0, 56 | model_type=Types.ANALYTIC_MODEL.HESTON_MODEL_ATTARI, 57 | compute_greek=False) 58 | 59 | implied_vol_approx.append(ExpansionTools.get_iv_atm_heston_approximation(np.array(parameters), dt[i])) 60 | implied_vol_atm.append(implied_volatility(option_price, f0, f0, dt[i], 0.0, 0.0, 'c')) 61 | vol_swap_approximation.append(ExpansionTools.get_vol_swap_approximation_heston(np.array(parameters), 0.0, dt[i], sigma_0)) 62 | vol_swap_mc.append(np.mean(np.sqrt(np.sum(map_output[Types.HESTON_OUTPUT.INTEGRAL_VARIANCE_PATHS], 1) / dt[i]))) 63 | output.append((implied_vol_atm[i] - vol_swap_mc[i])) 64 | 65 | # curve fit 66 | 67 | 68 | def f_law(x, a, b): 69 | return a + b * x 70 | 71 | 72 | popt, pcov = curve_fit(f_law, dt, output) 73 | y_fit_values = f_law(dt, *popt) 74 | 75 | plt.plot(dt, output, label='(I(t,f0) - E(v_t))', linestyle='--', color='black') 76 | plt.plot(dt, y_fit_values, label="%s + %s * t" % (round(popt[0], 5), round(popt[1], 5)), marker='.', 77 | linestyle='--', color='black') 78 | 79 | plt.xlabel('T') 80 | plt.legend() 81 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter6/Old/HestonOptionApproximation.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | 3 | from MC_Engines.MC_Heston import Heston_Engine 4 | from Tools import RNG, Types 5 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 6 | from AnalyticEngines.MalliavinMethod import EuropeanOptionExpansion 7 | 8 | # option datas (We will suppose that r=0) 9 | dt = [1.0 / 52, 1.0 / 12.0, 0.25, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0] 10 | f0 = 100.0 11 | 12 | # simulation info 13 | epsilon = 0.5 14 | k = 0.5 15 | rho = -0.9 16 | v0 = 0.05 17 | theta = 0.06 18 | 19 | parameters = [k, theta, epsilon, rho, v0] 20 | 21 | seed = 123456789 22 | no_paths = 100000 23 | delta_time = 1.0 / 365.0 24 | 25 | # random number generator 26 | rnd_generator = RNG.RndGenerator(seed) 27 | 28 | # ouputs container 29 | mc_option_price = [] 30 | var_swap_apprx_price = [] 31 | 32 | no_dt = len(dt) 33 | 34 | for i in range(0, no_dt): 35 | no_time_steps = int(dt[i] / delta_time) 36 | rnd_generator.set_seed(seed) 37 | european_option = EuropeanOption(f0, 1, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, dt[i]) 38 | 39 | map_output = Heston_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, v0, no_paths, 40 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 41 | rnd_generator) 42 | 43 | characteristic_function_price = european_option.get_analytic_value(0.0, theta, rho, k, epsilon, v0, 0.0, 44 | model_type=Types.ANALYTIC_MODEL.HESTON_MODEL_REGULAR, 45 | compute_greek=False) 46 | 47 | results = european_option.get_price(map_output[Types.HESTON_OUTPUT.PATHS]) 48 | mc_option_price.append(results[0]) 49 | 50 | # price the option with var swap approximation 51 | analytic_price = EuropeanOptionExpansion.get_var_swap_apprx_price(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, 52 | f0, dt[i], parameters, Types.TypeModel.HESTON) 53 | var_swap_apprx_price.append(analytic_price) 54 | 55 | 56 | plt.plot(dt, mc_option_price, label='mc price') 57 | plt.plot(dt, var_swap_apprx_price, label='variance swap approximation') 58 | 59 | plt.legend() 60 | plt.title('ATM option price') 61 | plt.show() -------------------------------------------------------------------------------- /Examples/Chapter6/Old/SabrOptionApproximation.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | 3 | from Tools import RNG, Types 4 | from VolatilitySurface.Tools import SABRTools 5 | from Instruments.EuropeanInstruments import TypeSellBuy, TypeEuropeanOption 6 | from AnalyticEngines.MalliavinMethod import EuropeanOptionExpansion 7 | from py_vollib.black_scholes_merton import black_scholes_merton 8 | 9 | # option info (We will suppose that r=0) 10 | dt = [1.0 / 52, 1.0 / 12.0, 0.25, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0] 11 | f0 = 100.0 12 | 13 | # simulation info 14 | parameters = [0.3, -0.5, 0.5] 15 | seed = 123456789 16 | no_paths = 100000 17 | delta_time = 1.0 / 365.0 18 | 19 | # random number generator 20 | rnd_generator = RNG.RndGenerator(seed) 21 | 22 | # outputs container 23 | mc_option_price = [] 24 | var_swap_approximation_price = [] 25 | hagan_approximation_price = [] 26 | 27 | no_dt = len(dt) 28 | 29 | for i in range(0, no_dt): 30 | no_time_steps = int(dt[i] / delta_time) 31 | rnd_generator.set_seed(seed) 32 | # european_option = EuropeanOption(f0, 1, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, dt[i]) 33 | # map_output = SABR_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, no_paths, no_time_steps, 34 | # Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 35 | # rnd_generator) 36 | # 37 | # results = european_option.get_price(map_output[Types.SABR_OUTPUT.PATHS]) 38 | # mc_option_price.append(results[0]) 39 | 40 | # price the option with var swap approximation 41 | analytic_price = EuropeanOptionExpansion.get_var_swap_apprx_price(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, 42 | f0, dt[i], parameters, Types.TypeModel.SABR) 43 | var_swap_approximation_price.append(analytic_price) 44 | 45 | # hagan's price 46 | iv_hagan = SABRTools.sabr_vol_jit(parameters[0], parameters[1], parameters[2], 0.0, dt[i]) 47 | hagan_price = black_scholes_merton('c', f0, f0, dt[i], 0.0, iv_hagan, 0.0) 48 | hagan_approximation_price.append(hagan_price) 49 | 50 | plt.plot(dt, mc_option_price, label='mc price') 51 | plt.plot(dt, var_swap_approximation_price, label='variance swap approximation') 52 | plt.plot(dt, hagan_approximation_price, label='Hagan approximation') 53 | 54 | plt.legend() 55 | plt.title('ATM option price') 56 | plt.show() 57 | -------------------------------------------------------------------------------- /Examples/Chapter7/Data/SabrSurfaceParameter.txt: -------------------------------------------------------------------------------- 1 | value_date;date;alpha;rho;nu 2 | 44060;44092;0.207175168860505;-0.786504078991285;2.38201308756965 3 | 44060;44120;0.215004531276577;-0.783668127451219;1.82297757326436 4 | 44060;44134;0.2164862346751;-0.782250168007554;1.66735217790679 5 | 44060;44155;0.232256944372669;-0.780123249249841;1.49922157701687 6 | 44060;44183;0.233319990843443;-0.777287395667093;1.34318447479491 7 | 44060;44211;0.229332951407152;-0.774451585620116;1.23094634441589 8 | 44060;44274;0.224975439314478;-0.768071172189486;1.06123494976654 9 | 44060;44365;0.217497512250909;-0.75885540854126;0.912739146981752 10 | 44060;44547;0.21246769457468;-0.740425260664132;0.747999151100984 11 | 44060;44729;0.207037030002359;-0.721996951898076;0.653513651225508 12 | 44060;44911;0.204247313902739;-0.703570482105471;0.589952808655709 13 | 44060;45093;0.201045499545822;-0.685145851148703;0.543289569347041 14 | 44060;45275;0.200381602582207;-0.666723058890168;0.507075353792462 15 | 44060;45646;0.205559317356653;-0.629174599119059;0.452781629774561 16 | 44060;46010;0.208313434373327;-0.592342024684021;0.414729273435066 17 | 44060;46374;0.210424541476372;-0.555516802269231;0.385650675726592 18 | 44060;46738;0.211177348261666;-0.518698930774337;0.362454342544779 19 | 44060;47102;0.210935789854446;-0.481888409099131;0.34336673413014 20 | 44060;47473;0.209008674289607;-0.444377554829854;0.327001373199886 21 | -------------------------------------------------------------------------------- /Examples/Chapter7/Figure_7_2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pylab as plt 5 | 6 | from pathlib import Path 7 | from VolatilitySurface.Tools import SABRTools 8 | from scipy.optimize import curve_fit 9 | 10 | current_directory = os.path.dirname(os.path.realpath(__file__)) 11 | folder_directory = Path(current_directory) 12 | sabr_parameter_paths = os.path.join(folder_directory, 'Data', 'SabrSurfaceParameter.txt') 13 | 14 | parameters = pd.read_csv(sabr_parameter_paths, header=None, names=["value_date", "date", "alpha", "rho", "nu"], sep=";") 15 | no_dates = len(parameters['date']) 16 | 17 | no_z_i = 100 18 | z_i = np.linspace(-0.5, 0.5, no_z_i) 19 | 20 | sabr_iv_map = {} 21 | for i in range(1, no_dates): 22 | alpha_i = float(parameters['alpha'][i]) 23 | rho_i = float(parameters['rho'][i]) 24 | nu_i = float(parameters['nu'][i]) 25 | dti = (float(parameters['date'][i]) - float(parameters['value_date'][i])) / 365.0 26 | iv = [] 27 | for j in range(0, no_z_i): 28 | iv.append(SABRTools.sabr_vol_jit(alpha_i, rho_i, nu_i, z_i[j], dti)) 29 | sabr_iv_map[int(parameters['date'][i])] = iv 30 | 31 | nu_param = [] 32 | rho_param = [] 33 | delta_time = [] 34 | for i in range(1, no_dates): 35 | delta_time.append((float(parameters['date'][i]) - float(parameters['value_date'][i])) / 365.0) 36 | nu_param.append(float(parameters['nu'][i])) 37 | rho_param.append(float(parameters['rho'][i])) 38 | 39 | # To plot the skew for diferent maturities 40 | plt.plot(delta_time, nu_param, label="skew atm", color="black", linestyle="dashed") 41 | 42 | 43 | def f_law(x, a, b): 44 | return a * np.power(x, -b) 45 | 46 | 47 | popt, pcov = curve_fit(f_law, delta_time, nu_param) 48 | y_fit_values = f_law(delta_time, *popt) 49 | 50 | plt.plot(delta_time, y_fit_values, label="%s * T^-%s)" % (round(popt[0], 5), round(popt[1], 5)), color="black", linestyle="dashed", 51 | marker='.') 52 | 53 | plt.xlabel("T") 54 | plt.legend() 55 | plt.show() 56 | 57 | -------------------------------------------------------------------------------- /Examples/Chapter7/Figure_7_3.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 4 | from Tools import Types 5 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 6 | import matplotlib.pylab as plt 7 | 8 | # Parameters 9 | epsilon = 1.1 10 | k = 0.5 11 | rho = -0.9 12 | v0 = 0.05 13 | theta = 0.05 14 | 15 | # options 16 | strikes = np.arange(80.0, 120.0, 1.0) 17 | no_strikes = len(strikes) 18 | f0 = 100 19 | T = 0.1 20 | notional = 1.0 21 | options = [] 22 | for k_i in strikes: 23 | options.append(EuropeanOption(k_i, notional, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T)) 24 | 25 | 26 | iv_vol_rho = [] 27 | iv_rho_rho_zero = [] 28 | 29 | for i in range(0, no_strikes): 30 | price_rho_no_zero = options[i].get_analytic_value(0.0, theta, rho, k, epsilon, v0, 0.0, 31 | model_type=Types.ANALYTIC_MODEL.HESTON_MODEL_ATTARI, 32 | compute_greek=False) 33 | 34 | price_rho_zero = options[i].get_analytic_value(0.0, theta, 0.0, k, epsilon, v0, 0.0, 35 | model_type=Types.ANALYTIC_MODEL.HESTON_MODEL_ATTARI, 36 | compute_greek=False) 37 | 38 | iv_vol_rho.append(implied_volatility(price_rho_no_zero, f0, strikes[i], T, 0.0, 0.0, 'c')) 39 | iv_rho_rho_zero.append(implied_volatility(price_rho_zero, f0, strikes[i], T, 0.0, 0.0, 'c')) 40 | 41 | 42 | plt.plot(strikes, iv_vol_rho, label="rho=%s" % rho, marker=".", linestyle="--", color="black") 43 | plt.plot(strikes, iv_rho_rho_zero, label="rho=0.0", marker="+", linestyle="--", color="black") 44 | 45 | plt.xlabel("K") 46 | plt.legend() 47 | plt.show() 48 | 49 | 50 | -------------------------------------------------------------------------------- /Examples/Chapter7/Figure_7_7_Heston.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction, HestonCharesticFunction 4 | from functools import partial 5 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 8 | 9 | # European option price 10 | no_strikes = 22 11 | k_s = np.linspace(70.0, 130.0, no_strikes) 12 | f0 = 100.0 13 | x0 = np.log(f0) 14 | 15 | # Heston parameters 16 | epsilon = 0.75 17 | k = 0.6 18 | rho = -0.5 19 | v0 = 0.25 20 | theta = 0.5 21 | b2 = k 22 | u2 = -0.5 23 | 24 | # Upper and lower bound for cos integral 25 | a = -2.0 26 | b = 2.0 27 | 28 | # maturities 29 | T = [0.2, 0.4, 0.6, 1.0] 30 | markers = ['.', '+', '*', '^'] 31 | no_maturities = len(T) 32 | 33 | for i in range(0, no_maturities): 34 | cf_heston = partial(HestonCharesticFunction.get_trap_cf, t=T[i], r_t=0.0, x=x0, v=v0, theta=theta, rho=rho, k=k, epsilon=epsilon, b=b2, u=u2) 35 | 36 | cos_price_heston = COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 128, k_s, cf_heston) 37 | 38 | iv_smile_heston = [] 39 | for j in range(0, no_strikes): 40 | iv_smile_heston.append(implied_volatility(cos_price_heston[j], f0, f0, T[i], 0.0, 0.0, 'c')) 41 | 42 | plt.plot(k_s, iv_smile_heston, label='T=%s' % T[i], linestyle='--', color='black', marker=markers[i]) 43 | 44 | plt.ylim([0.0, 2.0]) 45 | plt.xlabel('K') 46 | plt.legend() 47 | plt.show() 48 | -------------------------------------------------------------------------------- /Examples/Chapter7/Figure_7_7_Merton.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pylab as plt 3 | from AnalyticEngines.FourierMethod.CharesticFunctions import JumpDiffusionCharesticFunction 4 | from functools import partial 5 | from AnalyticEngines.FourierMethod.COSMethod import COSRepresentation 6 | from Instruments.EuropeanInstruments import TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | 9 | 10 | # European option price 11 | no_strikes = 22 12 | k_s = np.linspace(70.0, 130.0, no_strikes) 13 | f0 = 100.0 14 | x0 = np.log(f0) 15 | 16 | # Merton parameters 17 | sigma = 0.5 18 | lambda_t = 1.7 19 | jumpmean = -0.4 20 | jumpstd = 1.5 21 | 22 | # Upper and lower bound for cos integral 23 | a = -10.0 24 | b = 10.0 25 | 26 | # maturities 27 | T = [0.2, 0.4, 0.6, 1.0] 28 | markers = ['.', '+', '*', '^'] 29 | no_maturities = len(T) 30 | 31 | 32 | for i in range(0, no_maturities): 33 | cf_merton = partial(JumpDiffusionCharesticFunction.get_merton_cf, t=T[i], x=x0, sigma=sigma, jumpmean=jumpmean, 34 | jumpstd=jumpstd, lambda_t=lambda_t) 35 | 36 | # check martingale 37 | aux = cf_merton(np.asfortranarray(3.0)) 38 | 39 | cos_price_merton = COSRepresentation.get_european_option_price(TypeEuropeanOption.CALL, a, b, 256, k_s, cf_merton) 40 | 41 | iv_smile_merton = [] 42 | for k in range(0, no_strikes): 43 | iv_smile_merton.append(implied_volatility(cos_price_merton[k], f0, f0, T[i], 0.0, 0.0, 'c')) 44 | 45 | plt.plot(k_s, iv_smile_merton, label='T=%s' % T[i], linestyle='--', color='black', marker=markers[i]) 46 | 47 | 48 | # plt.ylim([0.0, 1.0]) 49 | plt.xlabel('K') 50 | plt.legend() 51 | plt.show() 52 | -------------------------------------------------------------------------------- /Examples/Chapter7/Figure_7_7_RBergomi.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | import os 4 | 5 | from MC_Engines.MC_RBergomi import RBergomi_Engine 6 | from Tools import RNG, Types 7 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 8 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 9 | 10 | T = np.array([3, 7, 15, 30, 60, 180, 365]) * 1.0 / 365.0 11 | # labels = ['3 days', '7 days', '15 days', '1 month', '2 months', '3 months'] 12 | markers = ['.', '^', '+', '*', 'v', ',', '>'] 13 | no_maturities = len(T) 14 | strikes = np.linspace(80.0, 120.0, 30) 15 | 16 | # simulation info 17 | h = 0.1 18 | nu = 0.8 19 | rho = -0.4 20 | v0 = 0.15 21 | sigma_0 = np.sqrt(v0) 22 | 23 | # Mc info 24 | no_paths = 1000000 25 | 26 | parameters = [nu, rho, h] 27 | 28 | # random number generator 29 | seed = 123 30 | rnd_generator = RNG.RndGenerator(seed) 31 | 32 | # option information 33 | f0 = 100.0 34 | 35 | for i in range(0, no_maturities): 36 | options = [] 37 | iv_smile = [] 38 | rnd_generator.set_seed(seed) 39 | no_time_steps = np.maximum(int(T[i] * 365.0), 5) 40 | map_output = RBergomi_Engine.get_path_multi_step(0.0, T[i], parameters, f0, sigma_0, no_paths, 41 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 42 | rnd_generator) 43 | for k_i in strikes: 44 | options.append(EuropeanOption(k_i, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T[i])) 45 | mc_option_price = options[-1].get_price_control_variate(map_output[Types.RBERGOMI_OUTPUT.PATHS][:, -1], 46 | map_output[Types.RBERGOMI_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 47 | 48 | iv_smile.append(implied_volatility(mc_option_price[0], f0, k_i, T[i], 0.0, 0.0, 'c')) 49 | 50 | plt.plot(strikes, iv_smile, label='T=%s' % round(T[i], 6), color='black', marker=markers[i], linestyle="--") 51 | # path_to_keep = os.path.join("C://Users//david//OneDrive//Desktop//graficos", "rbergomi_smile_%s" % i + ".png") 52 | 53 | plt.xlabel('K') 54 | plt.legend() 55 | plt.ylim([0.0, 0.9]) 56 | # plt.savefig(path_to_keep) 57 | 58 | plt.show() 59 | -------------------------------------------------------------------------------- /Examples/Chapter7/Old/skew_atm_cev_short_term.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_LocalVol import LocalVolEngine, LocalVolFunctionals 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | from functools import partial 9 | 10 | dt = np.linspace(0.01, 0.1, 10) 11 | no_dt_s = len(dt) 12 | 13 | # simulation info 14 | sigma = 0.3 15 | beta = 0.4 16 | local_vol_mc = partial(LocalVolFunctionals.log_cev_diffusion, beta=beta - 1.0, sigma=sigma) 17 | 18 | parameters = [sigma, beta] 19 | 20 | seed = 123456789 21 | no_paths = 1000000 22 | # delta_time = 1.0 / 365.0 23 | no_time_steps = 100 24 | 25 | # random number generator 26 | rnd_generator = RNG.RndGenerator(seed) 27 | 28 | # option information 29 | f0 = 100.0 30 | shift_spot = 0.0001 31 | options = [] 32 | options_shift_right = [] 33 | options_shift_left = [] 34 | implied_vol_atm = [] 35 | implied_vol_atm_shift_right = [] 36 | implied_vol_atm_shift_left = [] 37 | 38 | for d_i in dt: 39 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 40 | options_shift_left.append( 41 | EuropeanOption(f0 * (1.0 - shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 42 | options_shift_right.append( 43 | EuropeanOption(f0 * (1.0 + shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 44 | 45 | # outputs 46 | skew_atm_mc = [] 47 | 48 | for i in range(0, no_dt_s): 49 | rnd_generator.set_seed(seed) 50 | map_output = LocalVolEngine.get_path_multi_step(0.0, dt[i], f0, no_paths, no_time_steps, 51 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 52 | local_vol_mc, rnd_generator) 53 | # implied vol by MC and asymptotic expansion 54 | mc_option_price = options[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 55 | mc_option_price_right = options_shift_right[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 56 | mc_option_price_left = options_shift_left[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 57 | 58 | implied_vol_atm = implied_volatility(mc_option_price[0], f0, f0, dt[i], 0.0, 0.0, 'c') 59 | implied_vol_atm_shift_left = implied_volatility(mc_option_price_left[0], f0, f0 * (1.0 - shift_spot), dt[i], 0.0, 0.0, 'c') 60 | implied_vol_atm_shift_right = implied_volatility(mc_option_price_right[0], f0, f0 * (1.0 + shift_spot), dt[i], 0.0, 0.0, 'c') 61 | 62 | skew_atm_mc.append(f0 * (implied_vol_atm_shift_right - implied_vol_atm_shift_left) / (2.0 * shift_spot * f0)) 63 | 64 | asymptotic_limit = 0.5 * sigma * (beta - 1) * np.power(f0, beta - 1) 65 | 66 | plt.plot(dt, skew_atm_mc, label='skew atm CEV', color='black', linestyle='--') 67 | plt.plot(dt, np.ones(len(dt)) * asymptotic_limit, label='asymptotic limit', 68 | color='black', marker='.', linestyle='--') 69 | 70 | plt.xlabel('T') 71 | plt.legend() 72 | plt.show() 73 | -------------------------------------------------------------------------------- /Examples/Chapter7/Old/skew_atm_heston_short_term.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_Heston import Heston_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | 9 | 10 | dt = np.linspace(0.01, 0.1, 20) 11 | no_dt_s = len(dt) 12 | 13 | # simulation info 14 | epsilon = 0.15 15 | k = 1.0 16 | rho = -0.9 17 | v0 = 0.05 18 | sigma_0 = np.sqrt(0.05) 19 | theta = 0.06 20 | 21 | parameters = [k, theta, epsilon, rho, v0] 22 | 23 | seed = 123456789 24 | no_paths = 1000000 25 | delta_time = 1.0 / 365.0 26 | no_time_steps = 100 27 | 28 | 29 | # option information 30 | f0 = 100.0 31 | options = [] 32 | options_shift_right = [] 33 | options_shift_left = [] 34 | shift_spot = 0.000001 35 | 36 | # random number generator 37 | rnd_generator = RNG.RndGenerator(seed) 38 | 39 | for d_i in dt: 40 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 41 | options_shift_right.append(EuropeanOption(f0 * (1.0 + shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, 42 | 0, d_i)) 43 | options_shift_left.append(EuropeanOption(f0 * (1.0 - shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, 44 | f0, d_i)) 45 | 46 | # outputs 47 | implied_vol_atm = [] 48 | implied_vol_atm_shift_right = [] 49 | implied_vol_atm_shift_left = [] 50 | skew_atm = [] 51 | 52 | for i in range(0, no_dt_s): 53 | rnd_generator.set_seed(seed) 54 | map_output = Heston_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, v0, no_paths, 55 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 56 | rnd_generator) 57 | 58 | option_price_mc = options[i].get_price(map_output[Types.HESTON_OUTPUT.PATHS][:, -1]) 59 | option_price_right_mc = options_shift_right[i].get_price(map_output[Types.HESTON_OUTPUT.PATHS][:, -1]) 60 | option_price_left_mc = options_shift_left[i].get_price(map_output[Types.HESTON_OUTPUT.PATHS][:, -1]) 61 | 62 | implied_vol_atm.append(implied_volatility(option_price_mc[0], f0, f0, dt[i], 0.0, 0.0, 'c')) 63 | implied_vol_atm_shift_right.append(implied_volatility(option_price_right_mc[0], f0, f0 * (1.0 + shift_spot), dt[i], 64 | 0.0, 0.0, 'c')) 65 | implied_vol_atm_shift_left.append(implied_volatility(option_price_left_mc[0], f0, f0 * (1.0 - shift_spot), dt[i], 66 | 0.0, 0.0, 'c')) 67 | # skew_atm.append(f0 * (implied_vol_atm_shift_right[i] - implied_vol_atm_shift_left[i]) / (2.0 * shift_spot * f0)) 68 | skew_atm.append(f0 * (implied_vol_atm_shift_right[i] - implied_vol_atm[i]) / (shift_spot * f0)) 69 | 70 | asymptotic_limit = 0.25 * rho * epsilon / sigma_0 71 | 72 | plt.plot(dt, skew_atm, label='skew atm heston', color='black', linestyle='--') 73 | plt.plot(dt, np.ones(len(dt)) * asymptotic_limit, label='asymptotic limit', 74 | color='black', marker='.', linestyle='--') 75 | 76 | 77 | plt.xlabel('T') 78 | plt.legend() 79 | plt.show() 80 | 81 | -------------------------------------------------------------------------------- /Examples/Chapter7/Old/skew_atm_sabr_short_term.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | 9 | dt = np.linspace(0.01, 0.1, 30) 10 | no_dt_s = len(dt) 11 | 12 | # simulation info 13 | alpha = 0.5 14 | nu = 0.5 15 | rho = -0.6 16 | parameters = [alpha, nu, rho] 17 | no_time_steps = 100 18 | 19 | seed = 123456789 20 | no_paths = 750000 21 | delta_time = 1.0 / 365.0 22 | 23 | # random number generator 24 | rnd_generator = RNG.RndGenerator(seed) 25 | 26 | # option information 27 | f0 = 100.0 28 | options = [] 29 | options_shift_right = [] 30 | options_shift_left = [] 31 | shift_spot = 0.0001 32 | for d_i in dt: 33 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 34 | options_shift_right.append( 35 | EuropeanOption(f0 * (1.0 + shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 36 | options_shift_left.append( 37 | EuropeanOption(f0 * (1.0 - shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 38 | 39 | # outputs 40 | implied_vol_atm = [] 41 | 42 | skew_atm_mc = [] 43 | 44 | for i in range(0, no_dt_s): 45 | rnd_generator.set_seed(seed) 46 | map_output = SABR_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, no_paths, no_time_steps, 47 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator) 48 | 49 | mc_option_price = options[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 50 | mc_option_price_shift_right = options_shift_right[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 51 | mc_option_price_shift_left = options_shift_left[i].get_price(map_output[Types.SABR_OUTPUT.PATHS][:, -1]) 52 | implied_vol_base = implied_volatility(mc_option_price[0], f0, f0, dt[i], 0.0, 0.0, 'c') 53 | implied_vol_shift_right = implied_volatility(mc_option_price_shift_right[0], f0, f0 * (1.0 + shift_spot), dt[i], 54 | 0.0, 0.0, 'c') 55 | implied_vol_shift_left = implied_volatility(mc_option_price_shift_left[0], f0, f0 * (1.0 - shift_spot), dt[i], 0.0, 56 | 0.0, 'c') 57 | skew_atm_mc.append(f0 * (implied_vol_shift_right - implied_vol_shift_left) / (2.0 * shift_spot * f0)) 58 | # skew_atm_mc.append(f0 * (implied_vol_shift_right - implied_vol_base) / (shift_spot * f0)) 59 | 60 | asymptotic_limit = 0.5 * rho * nu 61 | 62 | plt.plot(dt, skew_atm_mc, label='skew atm SABR', color='black', linestyle='--') 63 | plt.plot(dt, np.ones(len(dt)) * asymptotic_limit, label='asymptotic limit', 64 | color='black', marker='.', linestyle='--') 65 | 66 | plt.xlabel('T') 67 | plt.legend() 68 | plt.show() 69 | -------------------------------------------------------------------------------- /Examples/Chapter8/Data/SabrSurfaceParameter.txt: -------------------------------------------------------------------------------- 1 | value_date;date;alpha;rho;nu 2 | 44152;44183;0.167987502656796;-0.641736610477645;2.56442792807169 3 | 44153;44211;0.192970591564795;-0.640392398672238;1.94913126564539 4 | 44154;44225;0.192124799982209;-0.639745192678464;1.78392815907551 5 | 44155;44246;0.191664241241415;-0.638749500158148;1.60020833366507 6 | 44156;44274;0.203434446496555;-0.637405332576598;1.42811122615118 7 | 44157;44302;0.190815341351462;-0.636061184895427;1.30489599094847 8 | 44158;44365;0.189295509081653;-0.632974698867982;1.11654929865052 9 | 44159;44456;0.191554264121843;-0.628494502636979;0.953297265675727 10 | 44160;44547;0.190741001769343;-0.624014527505032;0.848982913185727 11 | 44161;44638;0.189234856070748;-0.619534773463961;0.774719884180401 12 | 44162;44729;0.191208696013974;-0.615055240505588;0.718265313905842 13 | 44163;44820;0.189776414596027;-0.610575928621734;0.673408379034286 14 | 44164;44911;0.199149620650714;-0.606096837804219;0.636612406650516 15 | 44165;45093;0.192526430937764;-0.597089557813729;0.578945006900514 16 | 44166;45275;0.190187609369246;-0.588083171841461;0.53552036577454 17 | 44167;45646;0.209903031940026;-0.569675109024228;0.47214392024362 18 | 44167;46010;0.192430695603704;-0.551569200007504;0.428826567790463 19 | 44167;46374;0.190322123812589;-0.533466905327703;0.396329378208736 20 | 44167;46738;0.191753729029156;-0.515368224444027;0.370745464516719 21 | 44167;47102;0.194611305381688;-0.497273156815745;0.34990187700934 22 | 44167;47473;0.207093693610397;-0.478833824709817;0.332171598551432 -------------------------------------------------------------------------------- /Examples/Chapter8/Eample_8_3_6.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 4 | from Tools import Types 5 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 6 | import matplotlib.pylab as plt 7 | 8 | # Parameters 9 | epsilon = 2.5 10 | k = 0.3 11 | # rho = -0.9 12 | rho = -0.2 13 | v0 = 0.2 14 | theta = 0.4 15 | 16 | # options 17 | strikes = np.linspace(70.0, 130.0, 30) 18 | no_strikes = len(strikes) 19 | f0 = 100 20 | T = 0.1 21 | notional = 1.0 22 | options = [] 23 | for k_i in strikes: 24 | options.append(EuropeanOption(k_i, notional, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T)) 25 | 26 | 27 | iv_vol = [] 28 | 29 | for i in range(0, no_strikes): 30 | price = options[i].get_analytic_value(0.0, theta, rho, k, epsilon, v0, 0.0, 31 | model_type=Types.ANALYTIC_MODEL.HESTON_MODEL_ATTARI, 32 | compute_greek=False) 33 | 34 | iv_vol.append(implied_volatility(price, f0, strikes[i], T, 0.0, 0.0, 'c')) 35 | 36 | 37 | plt.plot(strikes, iv_vol, label="rho=%s" % rho, marker=".", linestyle="--", color="black") 38 | 39 | plt.xlabel("K") 40 | plt.legend() 41 | plt.show() 42 | 43 | 44 | -------------------------------------------------------------------------------- /Examples/Chapter8/Example_8_4_6.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | import os 4 | 5 | from MC_Engines.MC_RBergomi import RBergomi_Engine 6 | from Tools import RNG, Types 7 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 8 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 9 | 10 | T = np.array([3, 7, 15, 30, 60, 90]) * 1.0 / 365.0 11 | labels = ['3 days', '7 days', '15 days', '1 month', '2 months', '3 months'] 12 | markers = ['.', '^', '+', '*', 'v', ',', '>'] 13 | no_maturities = len(T) 14 | strikes = np.linspace(80.0, 120.0, 30) 15 | 16 | # simulation info 17 | h = 0.1 18 | nu = 0.8 19 | rho = -0.4 20 | v0 = 0.15 21 | sigma_0 = np.sqrt(v0) 22 | 23 | # Mc info 24 | no_paths = 1000000 25 | 26 | parameters = [nu, rho, h] 27 | 28 | # random number generator 29 | seed = 123 30 | rnd_generator = RNG.RndGenerator(seed) 31 | 32 | # option information 33 | f0 = 100.0 34 | 35 | for i in range(0, no_maturities): 36 | options = [] 37 | iv_smile = [] 38 | rnd_generator.set_seed(seed) 39 | no_time_steps = np.maximum(int(T[i] * 365.0), 5) 40 | map_output = RBergomi_Engine.get_path_multi_step(0.0, T[i], parameters, f0, sigma_0, no_paths, 41 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 42 | rnd_generator) 43 | for k_i in strikes: 44 | options.append(EuropeanOption(k_i, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T[i])) 45 | mc_option_price = options[-1].get_price_control_variate(map_output[Types.RBERGOMI_OUTPUT.PATHS][:, -1], 46 | map_output[Types.RBERGOMI_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 47 | 48 | iv_smile.append(implied_volatility(mc_option_price[0], f0, k_i, T[i], 0.0, 0.0, 'c')) 49 | 50 | plt.plot(strikes, iv_smile, label=labels[i], color='black', marker=markers[i], linestyle="--") 51 | path_to_keep = os.path.join("D://GitHubRepository//Python//Graficos//Chapter8", "rbergomi_smile_%s" % i + ".png") 52 | 53 | plt.xlabel('K', fontsize=14) 54 | plt.legend(fontsize=14) 55 | plt.ylim([0.0, 0.9]) 56 | plt.savefig(path_to_keep) 57 | plt.clf() 58 | 59 | plt.show() 60 | -------------------------------------------------------------------------------- /Examples/Chapter8/Figure_8_2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pylab as plt 5 | 6 | from pathlib import Path 7 | from VolatilitySurface.Tools import SABRTools 8 | from scipy.optimize import curve_fit 9 | 10 | current_directory = os.path.dirname(os.path.realpath(__file__)) 11 | folder_directory = Path(current_directory) 12 | sabr_parameter_paths = os.path.join(folder_directory, 'Data', 'SabrSurfaceParameter.txt') 13 | 14 | parameters = pd.read_csv(sabr_parameter_paths, header=None, names=["value_date", "date", "alpha", "rho", "nu"], sep=";") 15 | no_dates = len(parameters['date']) 16 | 17 | no_z_i = 100 18 | z_i = np.linspace(-0.5, 0.5, no_z_i) 19 | 20 | sabr_iv_map = {} 21 | for i in range(1, no_dates): 22 | alpha_i = float(parameters['alpha'][i]) 23 | rho_i = float(parameters['rho'][i]) 24 | nu_i = float(parameters['nu'][i]) 25 | dti = (float(parameters['date'][i]) - float(parameters['value_date'][i])) / 365.0 26 | iv = [] 27 | for j in range(0, no_z_i): 28 | iv.append(SABRTools.sabr_vol_jit(alpha_i, rho_i, nu_i, z_i[j], dti)) 29 | sabr_iv_map[int(parameters['date'][i])] = iv 30 | 31 | nu_param = [] 32 | nu_square_param = [] 33 | rho_param = [] 34 | delta_time = [] 35 | for i in range(1, no_dates): 36 | delta_time.append((float(parameters['date'][i]) - float(parameters['value_date'][i])) / 365.0) 37 | nu_param.append(float(parameters['nu'][i])) 38 | rho_param.append(float(parameters['rho'][i])) 39 | nu_square_param.append(float(parameters['nu'][i]) * float(parameters['nu'][i])) 40 | 41 | # To plot the skew for diferent maturities 42 | plt.plot(delta_time, nu_square_param, label="vol-of_vol parameter ^ 2", color="black", linestyle="dashed") 43 | 44 | 45 | def f_law(x, a, b): 46 | return a * np.power(x, -b) 47 | 48 | 49 | popt, pcov = curve_fit(f_law, delta_time, nu_square_param) 50 | y_fit_values = f_law(delta_time, *popt) 51 | 52 | plt.plot(delta_time, y_fit_values, label="%s * t^-%s)" % (round(popt[0], 5), round(popt[1], 5)), color="black", linestyle="dashed", 53 | marker='.') 54 | 55 | plt.xlabel("T") 56 | 57 | plt.legend() 58 | plt.show() 59 | -------------------------------------------------------------------------------- /Examples/Chapter8/Old/curvature_atm_rho_sabr.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | from scipy.optimize import curve_fit 9 | 10 | dt = np.linspace(0.00001, 0.001, 15) 11 | no_dt_s = len(dt) 12 | 13 | # simulation info 14 | alpha = 0.4 15 | nu = 1.1 16 | rho = - 0.999 17 | parameters = [alpha, nu, rho] 18 | no_time_steps = 3 19 | 20 | seed = 123456789 21 | no_paths = 10000000 22 | delta_time = 1.0 / 365.0 23 | 24 | # random number generator 25 | rnd_generator = RNG.RndGenerator(seed) 26 | 27 | # option information 28 | # options 29 | strikes = np.linspace(70.0, 130.0, 30) 30 | no_strikes = len(strikes) 31 | f0 = 100 32 | T = 0.1 33 | notional = 1.0 34 | options = [] 35 | for k_i in strikes: 36 | options.append(EuropeanOption(k_i, notional, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T)) 37 | 38 | 39 | map_output = SABR_Engine.get_path_multi_step(0.0, T, parameters, f0, no_paths, no_time_steps, 40 | Types.TYPE_STANDARD_NORMAL_SAMPLING.REGULAR_WAY, rnd_generator) 41 | 42 | iv_vol = [] 43 | for i in range(0, no_strikes): 44 | rnd_generator.set_seed(seed) 45 | mc_option_price = options[i].get_price_control_variate(map_output[Types.SABR_OUTPUT.PATHS][:, -1], 46 | map_output[Types.SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 47 | 48 | iv_vol.append(implied_volatility(mc_option_price[0], f0, strikes[i], T, 0.0, 0.0, 'c')) 49 | 50 | 51 | plt.plot(strikes, iv_vol, label="rho=%s" % rho, marker=".", linestyle="--", color="black") 52 | 53 | plt.xlabel("K") 54 | plt.legend() 55 | plt.show() 56 | -------------------------------------------------------------------------------- /Examples/Chapter8/Old/smile_atm_cev_short_term.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_LocalVol import LocalVolEngine, LocalVolFunctionals 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | from functools import partial 9 | 10 | dt = np.linspace(0.01, 0.1, 10) 11 | no_dt_s = len(dt) 12 | 13 | # simulation info 14 | sigma = 0.3 15 | beta = 0.4 16 | local_vol_mc = partial(LocalVolFunctionals.log_cev_diffusion, beta=beta - 1.0, sigma=sigma) 17 | 18 | parameters = [sigma, beta] 19 | 20 | seed = 123456789 21 | no_paths = 500000 22 | # delta_time = 1.0 / 365.0 23 | no_time_steps = 3 24 | 25 | # random number generator 26 | rnd_generator = RNG.RndGenerator(seed) 27 | 28 | # option information 29 | f0 = 100.0 30 | shift_spot = 0.001 31 | options = [] 32 | options_shift_right = [] 33 | options_shift_left = [] 34 | implied_vol_atm = [] 35 | implied_vol_atm_shift_right = [] 36 | implied_vol_atm_shift_left = [] 37 | 38 | for d_i in dt: 39 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 40 | options_shift_left.append( 41 | EuropeanOption(f0 * (1.0 - shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 42 | options_shift_right.append( 43 | EuropeanOption(f0 * (1.0 + shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 44 | 45 | # outputs 46 | smile_atm_mc = [] 47 | 48 | for i in range(0, no_dt_s): 49 | rnd_generator.set_seed(seed) 50 | map_output = LocalVolEngine.get_path_multi_step(0.0, dt[i], f0, no_paths, no_time_steps, 51 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 52 | local_vol_mc, rnd_generator) 53 | # implied vol by MC and asymptotic expansion 54 | mc_option_price = options[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 55 | mc_option_price_right = options_shift_right[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 56 | mc_option_price_left = options_shift_left[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 57 | 58 | implied_vol_atm = implied_volatility(mc_option_price[0], f0, f0, dt[i], 0.0, 0.0, 'c') 59 | implied_vol_atm_shift_left = implied_volatility(mc_option_price_left[0], f0, f0 * (1.0 - shift_spot), dt[i], 0.0, 0.0, 'c') 60 | implied_vol_atm_shift_right = implied_volatility(mc_option_price_right[0], f0, f0 * (1.0 + shift_spot), dt[i], 0.0, 0.0, 'c') 61 | 62 | smile_atm_mc.append((implied_vol_atm_shift_right - 2.0 * implied_vol_atm + 63 | implied_vol_atm_shift_left) / (shift_spot * shift_spot)) 64 | 65 | asymptotic_limit = (sigma / 6.0) * np.power(beta - 1.0, 2.0) * np.exp((beta - 1.0) * np.log(f0)) 66 | 67 | plt.plot(dt, smile_atm_mc, label='smile atm CEV', color='black', linestyle='--') 68 | plt.plot(dt, np.ones(len(dt)) * asymptotic_limit, label='asymptotic limit', 69 | color='black', marker='.', linestyle='--') 70 | 71 | plt.xlabel('T') 72 | plt.legend() 73 | plt.show() 74 | -------------------------------------------------------------------------------- /Examples/Chapter8/Old/smile_atm_rbergomi_term.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_RBergomi import RBergomi_Engine 5 | from Tools import RNG, Types 6 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 7 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 8 | from scipy.optimize import curve_fit 9 | 10 | dt = np.linspace(0.0001, 0.02, 15) 11 | no_dt_s = len(dt) 12 | 13 | # simulation info 14 | h = 0.3 15 | nu = 0.5 16 | rho = -0.6 17 | v0 = 0.05 18 | sigma_0 = np.sqrt(v0) 19 | 20 | parameters = [nu, rho, h] 21 | 22 | seed = 123456789 23 | no_paths = 1000000 24 | 25 | delta_time = 1.0 / 365.0 26 | no_time_steps = 100 27 | 28 | # random number generator 29 | rnd_generator = RNG.RndGenerator(seed) 30 | 31 | # option information 32 | f0 = 100.0 33 | shift_spot = 0.0001 34 | options = [] 35 | options_shift_right = [] 36 | options_shift_left = [] 37 | for d_i in dt: 38 | options.append(EuropeanOption(f0, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 39 | options_shift_left.append( 40 | EuropeanOption(f0 * (1.0 - shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 41 | options_shift_right.append( 42 | EuropeanOption(f0 * (1.0 + shift_spot), 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, d_i)) 43 | 44 | # outputs 45 | smile_atm_mc = [] 46 | 47 | for i in range(0, no_dt_s): 48 | rnd_generator.set_seed(seed) 49 | map_output = RBergomi_Engine.get_path_multi_step(0.0, dt[i], parameters, f0, v0, no_paths, 50 | no_time_steps, Types.TYPE_STANDARD_NORMAL_SAMPLING.REGULAR_WAY, 51 | rnd_generator) 52 | 53 | mc_option_price = options[i].get_price(map_output[Types.RBERGOMI_OUTPUT.PATHS][:, -1]) 54 | mc_option_price_shift_left = options_shift_left[i].get_price(map_output[Types.RBERGOMI_OUTPUT.PATHS][:, -1]) 55 | mc_option_price_shift_right = options_shift_right[i].get_price(map_output[Types.RBERGOMI_OUTPUT.PATHS][:, -1]) 56 | 57 | implied_vol_atm = implied_volatility(mc_option_price[0], f0, f0, dt[i], 0.0, 0.0, 'c') 58 | implied_vol_atm_shift_right = implied_volatility(mc_option_price_shift_right[0], f0, f0 * (1.0 + shift_spot), 59 | dt[i], 0.0, 0.0, 'c') 60 | implied_vol_atm_shift_left = implied_volatility(mc_option_price_shift_left[0], f0, f0 * (1.0 - shift_spot), 61 | dt[i], 0.0, 0.0, 'c') 62 | 63 | smile_atm_mc.append((implied_vol_atm_shift_right - 2.0 * implied_vol_atm + 64 | implied_vol_atm_shift_left) / (f0 * f0 * shift_spot * shift_spot)) 65 | 66 | 67 | def f_law(x, a, b): 68 | return a * np.power(x, b) 69 | 70 | 71 | popt, pcov = curve_fit(f_law, dt, smile_atm_mc) 72 | y_fit_values = f_law(dt, *popt) 73 | 74 | plt.plot(dt, smile_atm_mc, label='curvature atm rBergomi', color='black', linestyle='--') 75 | plt.plot(dt, y_fit_values, label='%s t^%s'% (round(popt[0], 5), round(popt[1], 5)), color='black', linestyle='--', marker='.') 76 | 77 | plt.xlabel('T') 78 | plt.legend() 79 | plt.show() 80 | -------------------------------------------------------------------------------- /Examples/Chapter9/Example_9_6_6.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_LocalVol import LocalVolEngine, LocalVolFunctionals 5 | from Tools import RNG, Types 6 | from Instruments.ForwardStartEuropeanInstrument import ForwardStartEuropeanOption 7 | from Instruments.EuropeanInstruments import EuropeanOption 8 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 9 | from functools import partial 10 | from AnalyticEngines.LocalVolatility.Hagan import ExpansionLocVol 11 | 12 | # simulation info 13 | sigma = 0.7 14 | beta = 0.3 15 | local_vol_mc = partial(LocalVolFunctionals.log_cev_diffusion, beta=beta - 1.0, sigma=sigma) 16 | 17 | no_time_steps = 100 18 | 19 | seed = 123456789 20 | no_paths = 1000000 21 | d_t_forward = 0.9 22 | T = 1.0 23 | 24 | # random number generator 25 | rnd_generator = RNG.RndGenerator(seed) 26 | 27 | # option information 28 | f0 = 10.0 29 | options = [] 30 | normal_options = [] 31 | no_strikes = 30 32 | strikes = np.linspace(0.9, 1.1, no_strikes) 33 | # strikes = [1.0] 34 | 35 | for k_i in strikes: 36 | normal_options.append(EuropeanOption(k_i * f0, 1.0, Types.TypeSellBuy.BUY, Types.TypeEuropeanOption.CALL, f0, T)) 37 | options.append(ForwardStartEuropeanOption(k_i, 1.0, Types.TypeSellBuy.BUY, Types.TypeEuropeanOption.CALL, 38 | f0, d_t_forward, T)) 39 | 40 | # outputs 41 | implied_vol_forward = [] 42 | # implied_vol_hagan = [] 43 | implied_vol_spot = [] 44 | 45 | rnd_generator.set_seed(seed) 46 | map_output = LocalVolEngine.get_path_multi_step(0.0, T, f0, no_paths, no_time_steps, 47 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, local_vol_mc, 48 | rnd_generator, extra_sampling_points=[d_t_forward]) 49 | 50 | # expansion_hagan = ExpansionLocVol.hagan_loc_vol(lambda t: sigma, 51 | # lambda x: np.power(x, beta), 52 | # lambda x: beta * np.power(x, beta - 1.0), 53 | # lambda x: beta * (beta - 1.0) * np.power(x, beta - 2.0)) 54 | 55 | for i in range(0, no_strikes): 56 | index_normal_option = np.searchsorted(np.array(map_output[Types.LOCAL_VOL_OUTPUT.TIMES]), d_t_forward) 57 | mc_normal_options_price = normal_options[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS][:, -1]) 58 | 59 | options[i].update_forward_start_date_index(np.array(map_output[Types.LOCAL_VOL_OUTPUT.TIMES])) 60 | mc_option_price = options[i].get_price(map_output[Types.LOCAL_VOL_OUTPUT.PATHS]) 61 | 62 | # implied_vol_hagan.append(expansion_hagan.get_implied_vol(T, f0, f0 * strikes[i])) 63 | 64 | implied_vol_forward.append(implied_volatility(mc_option_price[0] / f0, 1.0, strikes[i], T - d_t_forward, 0.0, 0.0, 'c')) 65 | implied_vol_spot.append(implied_volatility(mc_normal_options_price[0] / f0, 1.0, strikes[i], T, 0.0, 0.0, 'c')) 66 | 67 | plt.plot(strikes, implied_vol_forward, label='forward smile CEV', color='black', linestyle='--') 68 | # plt.plot(np.log(strikes), implied_vol_hagan, label='spot smile CEV Hagan', color='black', linestyle='--') 69 | plt.plot(strikes, implied_vol_spot, label='spot smile CEV', color='black', linestyle='--', marker='.') 70 | 71 | plt.xlabel('K') 72 | plt.legend() 73 | plt.show() 74 | -------------------------------------------------------------------------------- /Examples/Chapter9/Example_9_6_8.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | import numpy as np 3 | 4 | from MC_Engines.MC_SABR import SABR_Engine 5 | from Tools import RNG, Types 6 | from Instruments.ForwardStartEuropeanInstrument import ForwardStartEuropeanOption 7 | from Instruments.EuropeanInstruments import EuropeanOption 8 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 9 | 10 | # simulation info 11 | alpha = 0.5 12 | nu = 0.7 13 | rho = 0.6 14 | parameters = [alpha, nu, rho] 15 | no_time_steps = 200 16 | 17 | seed = 123456789 18 | no_paths = 1000000 19 | d_t_forward = 0.9 20 | T = 1.0 21 | 22 | # random number generator 23 | rnd_generator = RNG.RndGenerator(seed) 24 | 25 | # option information 26 | f0 = 100.0 27 | options = [] 28 | normal_options = [] 29 | no_strikes = 30 30 | strikes = np.linspace(0.7, 1.3, no_strikes) 31 | 32 | for k_i in strikes: 33 | normal_options.append(EuropeanOption(k_i * f0, 1.0, Types.TypeSellBuy.BUY, Types.TypeEuropeanOption.CALL, f0, T)) 34 | options.append(ForwardStartEuropeanOption(k_i, 1.0, Types.TypeSellBuy.BUY, Types.TypeEuropeanOption.CALL, 35 | f0, d_t_forward, T)) 36 | 37 | 38 | # outputs 39 | implied_vol_forward = [] 40 | implied_vol_spot = [] 41 | 42 | rnd_generator.set_seed(seed) 43 | map_output = SABR_Engine.get_path_multi_step(0.0, T, parameters, f0, no_paths, no_time_steps, 44 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, rnd_generator, 45 | extra_sampling_points=[d_t_forward]) 46 | 47 | for i in range(0, no_strikes): 48 | index_normal_option = np.searchsorted(np.array(map_output[Types.SABR_OUTPUT.TIMES]), T) 49 | mc_normal_options_price = normal_options[i].get_price_control_variate(map_output[Types.SABR_OUTPUT.PATHS][:, -1], 50 | map_output[Types.SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 51 | 52 | options[i].update_forward_start_date_index(np.array(map_output[Types.SABR_OUTPUT.TIMES])) 53 | mc_option_price = options[i].get_price_control_variate(map_output[Types.SABR_OUTPUT.PATHS], 54 | map_output[Types.SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 55 | 56 | implied_vol_forward.append(implied_volatility(mc_option_price[0] / f0, 1.0, strikes[i], T - d_t_forward, 0.0, 0.0, 'c')) 57 | implied_vol_spot.append(implied_volatility(mc_normal_options_price[0] / f0, 1.0, strikes[i], T, 0.0, 0.0, 'c')) 58 | 59 | 60 | plt.plot(strikes, implied_vol_forward, label='forward smile SABR', color='black', linestyle='--') 61 | plt.plot(strikes, implied_vol_spot, label='spot smile SABR', color='black', linestyle='--', marker='.') 62 | 63 | plt.xlabel('K') 64 | plt.legend() 65 | plt.show() 66 | -------------------------------------------------------------------------------- /Examples/DistributionApproximation/BSDistributionApproximation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from py_vollib.black_scholes_merton import black_scholes_merton, implied_volatility 3 | from Tools import AnalyticTools 4 | import matplotlib.pylab as plt 5 | 6 | # Underlying information 7 | s0 = 100.0 8 | r = 0.0 9 | q = 0.0 10 | 11 | # Options information 12 | t = 1.0 13 | strikes = [60.0, 70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0, 140.0, 150.0, 160] 14 | vols = [0.75, 0.7, 0.65, 0.6, 0.55, 0.50, 0.55, 0.6, 0.65, 0.7, 0.75] 15 | 16 | # Compute BS distribution 17 | no_strikes = len(strikes) 18 | bs_distribution = [] 19 | bs_distr_approximation = [] 20 | for i in range(0, no_strikes): 21 | bs_distribution.append(1.0 - AnalyticTools.bs_distribution(r, q, t, vols[i], s0, strikes[i])) 22 | bs_distr_approximation.append(AnalyticTools.bs_approximation_distribution(r, q, t, vols[i], s0, strikes[i])) 23 | 24 | plt.plot(strikes, bs_distribution, label='bs_distribution') 25 | plt.plot(strikes, bs_distr_approximation, label='bs_distr_approximation') 26 | 27 | plt.legend() 28 | plt.show() 29 | -------------------------------------------------------------------------------- /FractionalBrownian/ToolsFBM.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | 17 | from Tools import Types 18 | from scipy.optimize import curve_fit 19 | 20 | 21 | def get_mean_ratio_rs(x_t: Types.ndarray, chunksize: int = 1): 22 | no_elements = len(x_t) 23 | index = list(range(0, no_elements, chunksize)) 24 | no_packages = len(index) 25 | 26 | mean_n = np.zeros(no_packages - 1) 27 | r_n = np.zeros(no_packages - 1) 28 | s_n = np.zeros(no_packages - 1) 29 | ratio = np.zeros(no_packages - 1) 30 | 31 | for i in range(1, no_packages): 32 | mean_n[i - 1] = np.mean(x_t[index[i - 1]:index[i]]) 33 | s_n[i - 1] = np.std(x_t[index[i - 1]:index[i]]) 34 | z_n = np.cumsum(x_t[index[i - 1]:index[i]] - mean_n[i - 1]) 35 | r_n[i - 1] = z_n.max() - z_n.min() 36 | ratio[i - 1] = r_n[i - 1] / s_n[i - 1] 37 | 38 | return np.mean(ratio) 39 | 40 | 41 | def get_estimator_rs(x_t: Types.ndarray, lower_chunksize: int = 0, upper_chunksize: int = 1): 42 | rs = [] 43 | log_no_elements = [] 44 | 45 | for i in range(lower_chunksize, upper_chunksize + 1): 46 | rs.append(get_mean_ratio_rs(x_t, 2 ** i)) 47 | log_no_elements.append(i * np.log(2)) 48 | 49 | def func(x, a, b): 50 | return a + b * x 51 | 52 | popt, pcov = curve_fit(func, log_no_elements, np.log(rs)) 53 | estimated_rs = func(np.array(log_no_elements), *popt) 54 | 55 | return popt[0], popt[1], log_no_elements, np.log(rs), estimated_rs 56 | 57 | 58 | def get_estimator_pe(x_t: Types.ndarray, size_f: int): 59 | no_elements = len(x_t) 60 | f_i = np.linspace(-0.5, 0.5, size_f) 61 | sr_n = np.zeros(size_f) 62 | t_i = np.arange(0, no_elements, 1) 63 | 64 | for i in range(0, size_f): 65 | z_n = x_t - np.mean(x_t) 66 | out = np.exp(- 2.0 * np.pi * 1j * t_i * f_i[i]) * z_n 67 | sr_n[i] = np.power(np.sum(np.abs(out)), 2.0) / no_elements 68 | 69 | mid_point = int((size_f + 1) * 0.5) 70 | max_size_f = int(np.power(mid_point, 4 / 5)) 71 | log_filtered_f_i = np.log(f_i[mid_point + 1: mid_point + 2 + max_size_f]) 72 | log_filtered_sr_n = np.log(sr_n[mid_point + 1: mid_point + 2 + max_size_f]) 73 | 74 | def func(x, a, b): 75 | return a + b * x 76 | 77 | popt, pcov = curve_fit(func, log_filtered_f_i, log_filtered_sr_n) 78 | estimated_gamma = func(log_filtered_f_i, *popt) 79 | hurst_parameter = 0.5 * (1-popt[1]) 80 | 81 | return popt[0], popt[1], hurst_parameter, log_filtered_f_i, log_filtered_sr_n, estimated_gamma 82 | -------------------------------------------------------------------------------- /FractionalBrownian/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/FractionalBrownian/__init__.py -------------------------------------------------------------------------------- /Instruments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/Instruments/__init__.py -------------------------------------------------------------------------------- /MCPricers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MCPricers/__init__.py -------------------------------------------------------------------------------- /MC_Engines/GenericSDE/SDE.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | from Tools.Types import min_value, ndarray 16 | import numpy as np 17 | 18 | 19 | def derive_cev_drift(mu: float, t: float, x: ndarray): 20 | return mu * x 21 | 22 | 23 | def derive_cev_sigma(sigma: float, rho: float, t: float, x: ndarray): 24 | x_epsilon = np.maximum(min_value, x) 25 | return sigma * rho * np.power(x_epsilon, rho - 1.0) 26 | 27 | 28 | def cev_drift(mu: float, t: float, x: ndarray): 29 | return mu * x 30 | 31 | 32 | def cev_sigma(sigma: float, rho: float, t: float, x: ndarray): 33 | return sigma * np.power(x, rho) 34 | 35 | 36 | def z_drift(mu: float, rho: float, sigma: float, t: float, x: ndarray): 37 | x_epsilon = np.maximum(min_value, x) 38 | return (1.0 - rho) * np.add(mu * x_epsilon, np.divide(-0.5 * rho * sigma * sigma, x_epsilon)) 39 | 40 | 41 | def z_sigma(sigma: float, rho: float, t: float, x: ndarray): 42 | return (1.0 - rho) * sigma * np.ones(len(x)) 43 | 44 | 45 | def bs_drift_flat(t: float, x: ndarray, rate_t: float, dividend_t: float,): 46 | return (rate_t - dividend_t) * x 47 | 48 | 49 | def bs_sigma_flat(t: float, x: ndarray, sigma_t: float): 50 | return sigma_t * x 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /MC_Engines/GenericSDE/SDESimulation.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | import numba as nb 17 | 18 | 19 | from Tools.Types import ndarray, EULER_SCHEME_TYPE 20 | from typing import Callable 21 | 22 | 23 | def sde_euler_simulation(s: float, 24 | t: float, 25 | s0: float, 26 | no_steps: int, 27 | no_paths: int, 28 | z: ndarray, 29 | drift: Callable[[float, ndarray], ndarray], 30 | sigma: Callable[[float, ndarray], ndarray], 31 | euler_scheme: EULER_SCHEME_TYPE): 32 | 33 | t_i = np.linspace(s, t, no_steps) 34 | delta_t_i = np.diff(t_i) 35 | 36 | paths = np.empty(shape=(no_paths, no_steps)) 37 | paths[:, 0] = s0 38 | 39 | if euler_scheme == EULER_SCHEME_TYPE.STANDARD: 40 | generator_step = get_euler_step 41 | elif euler_scheme == EULER_SCHEME_TYPE.LOG_NORMAL: 42 | generator_step = get_ln_euler_step 43 | else: 44 | raise Exception("The euler scheme " + str(euler_scheme) + "is " + str(EULER_SCHEME_TYPE.UNKNOWN)) 45 | 46 | drift_i = np.empty(no_paths) 47 | sigma_i = np.empty(no_paths) 48 | 49 | for j in range(1, no_steps): 50 | drift_i = drift(t_i[j - 1], paths[:, j - 1]) 51 | sigma_i = sigma(t_i[j - 1], paths[:, j - 1]) 52 | paths[:, j] = generator_step(paths[:, j - 1], delta_t_i[j-1], z[:, j - 1], drift_i, sigma_i) 53 | 54 | return paths 55 | 56 | 57 | @nb.jit("f8[:](f8[:], f8, f8[:], f8[:], f8[:])", nopython=True, nogil=True) 58 | def get_euler_step(s_i_1, delta_i, z_i, drift_i, sigma_i): 59 | no_paths = len(s_i_1) 60 | s_i = np.empty(no_paths) 61 | for i in range(0, no_paths): 62 | s_i[i] = np.maximum(s_i_1[i] + delta_i * drift_i[i] + np.sqrt(delta_i) * sigma_i[i] * z_i[i], 0.0) 63 | 64 | return s_i 65 | 66 | 67 | @nb.jit("f8[:](f8[:], f8, f8[:], f8[:], f8[:])", nopython=True, nogil=True) 68 | def get_ln_euler_step(s_i_1, delta_i, z_i, drift_i, sigma_i): 69 | no_paths = len(s_i_1) 70 | s_i = np.empty(no_paths) 71 | for i in range(0, no_paths): 72 | s_i[i] = s_i_1[i] * np.exp(delta_i * (drift_i[i] / s_i_1[i]) - 0.5 * (sigma_i[i] * sigma_i[i]) / (s_i_1[i] * s_i_1[i]) * delta_i 73 | + np.sqrt(delta_i) * (sigma_i[i] / s_i_1[i]) * z_i[i]) 74 | 75 | return s_i 76 | -------------------------------------------------------------------------------- /MC_Engines/GenericSDE/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/GenericSDE/__init__.py -------------------------------------------------------------------------------- /MC_Engines/MC_Heston/VarianceMC.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | from numba import jit 17 | 18 | from Tools.Types import ndarray 19 | from MC_Engines.MC_Heston import HestonTools 20 | # from ncephes import ndtri 21 | from scipy.special import ndtri 22 | 23 | 24 | @jit("f8[:](f8,f8,f8,f8,f8,f8,f8[:],f8[:], i8)", nopython=True, nogil=True) 25 | def get_variance(k: float, 26 | theta: float, 27 | epsilon: float, 28 | phi_switch_level: float, 29 | t_i_1: float, 30 | t_i: float, 31 | v_t_i_1: ndarray, 32 | u_i: ndarray, 33 | no_paths: int): 34 | 35 | # no_paths = len(v_t_i_1) 36 | paths = np.zeros(no_paths) 37 | 38 | for i in range(0, no_paths): 39 | s_2_i = HestonTools.v_t_conditional_variance(k, theta, epsilon, v_t_i_1[i], t_i_1, t_i) 40 | m_i = HestonTools.v_t_conditional_mean(k, theta, v_t_i_1[i], t_i_1, t_i) 41 | phi = s_2_i / (m_i * m_i) 42 | 43 | if phi < phi_switch_level: 44 | parameters = HestonTools.matching_qe_moments_qg(m_i, s_2_i) 45 | z_i = ndtri(u_i[i]) 46 | paths[i] = parameters[1] * np.power(parameters[0] + z_i, 2.0) 47 | else: 48 | parameters = HestonTools.matching_qe_moments_exp(m_i, s_2_i) 49 | paths[i] = HestonTools.inv_exp_heston(parameters[0], parameters[1], u_i[i]) 50 | 51 | return paths 52 | -------------------------------------------------------------------------------- /MC_Engines/MC_Heston/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/MC_Heston/__init__.py -------------------------------------------------------------------------------- /MC_Engines/MC_LocalVol/LocalVolFunctionals.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numba as nb 16 | import numpy as np 17 | 18 | from Tools import Types 19 | 20 | 21 | @nb.jit("f8[:](f8,f8[:],f8,f8)", nopython=True, nogil=True) 22 | def cev_diffusion(t: float, x: Types.ndarray, beta: float, sigma: float): 23 | no_elements = len(x) 24 | output = np.zeros(no_elements) 25 | for i in range(0, no_elements): 26 | output[i] = sigma * np.power(x[i], beta) 27 | 28 | return output 29 | 30 | 31 | @nb.jit("f8[:](f8,f8[:],f8,f8,f8,f8)", nopython=True, nogil=True) 32 | def local_vol_normal_sabr(t: float, x: Types.ndarray, x0: float, alpha: float, rho: float, nu: float): 33 | no_elements = len(x) 34 | output = np.zeros(no_elements) 35 | for i in range(0, no_elements): 36 | y_i = (x[i] - x0) / alpha 37 | output[i] = alpha * np.sqrt(1.0 + 2.0 * rho * nu * y_i + nu * nu * y_i * y_i) 38 | 39 | return output 40 | 41 | 42 | @nb.jit("f8[:](f8,f8[:],f8,f8)", nopython=True, nogil=True) 43 | def first_derive_cev_diffusion(t: float, x: Types.ndarray, beta: float, sigma: float): 44 | no_elements = len(x) 45 | output = np.zeros(no_elements) 46 | for i in range(0, no_elements): 47 | output[i] = sigma * beta * np.power(x[i], beta - 1.0) 48 | 49 | return output 50 | 51 | 52 | @nb.jit("f8[:](f8,f8[:],f8,f8)", nopython=True, nogil=True) 53 | def second_derive_cev_diffusion(t: float, x: Types.ndarray, beta: float, sigma: float): 54 | no_elements = len(x) 55 | output = np.zeros(no_elements) 56 | for i in range(0, no_elements): 57 | output[i] = sigma * beta * (beta - 1.0) * np.power(x[i], beta - 2.0) 58 | 59 | return output 60 | 61 | 62 | @nb.jit("f8[:](f8,f8[:],f8,f8)", nopython=True, nogil=True) 63 | def log_cev_diffusion(t: float, x: Types.ndarray, beta: float, sigma: float): 64 | no_elements = len(x) 65 | output = np.zeros(no_elements) 66 | for i in range(0, no_elements): 67 | output[i] = sigma * np.power(np.exp(np.maximum(x[i], 0.00001)), beta) 68 | 69 | return output 70 | -------------------------------------------------------------------------------- /MC_Engines/MC_LocalVol/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/MC_LocalVol/__init__.py -------------------------------------------------------------------------------- /MC_Engines/MC_MixedLogNormal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/MC_MixedLogNormal/__init__.py -------------------------------------------------------------------------------- /MC_Engines/MC_RBergomi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/MC_RBergomi/__init__.py -------------------------------------------------------------------------------- /MC_Engines/MC_RBergomi/rExpOU1F_Engine.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | 17 | from MC_Engines.MC_RBergomi import ToolsVariance 18 | from Tools.Types import Vector, ndarray, TYPE_STANDARD_NORMAL_SAMPLING, REXPOU1F_OUTPUT 19 | 20 | 21 | def get_v_t_sampling(t: float, 22 | h: float, 23 | z: ndarray): 24 | 25 | std_t = np.sqrt(ToolsVariance.get_volterra_covariance(t, h)) 26 | return std_t * z 27 | 28 | 29 | def get_path_multi_step(t0: float, 30 | t1: float, 31 | parameters: Vector, 32 | f0: float, 33 | sigma_0: float, 34 | no_paths: int, 35 | no_time_steps: int, 36 | type_random_number: TYPE_STANDARD_NORMAL_SAMPLING, 37 | rnd_generator): 38 | 39 | nu = parameters[0] 40 | rho = parameters[1] 41 | h = parameters[2] 42 | 43 | no_paths = 2 * no_paths if type_random_number == TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC else no_paths 44 | 45 | t_i_s = np.linspace(t0, t1, no_time_steps) 46 | 47 | s_t = np.empty((no_paths, no_time_steps)) 48 | s_t[:, 0] = f0 49 | 50 | z_i_s = rnd_generator.normal(mu=0.0, sigma=1.0, size=(2 * (no_time_steps - 1), no_paths), 51 | sampling_type=type_random_number) 52 | map_out_put = {} 53 | outputs = ToolsVariance.generate_paths_rexpou1f(f0, 54 | sigma_0, 55 | nu, 56 | h, 57 | z_i_s, 58 | np.linalg.cholesky(ToolsVariance.get_covariance_matrix(t_i_s[1:], h, 59 | rho)), 60 | t_i_s, 61 | no_paths) 62 | 63 | map_out_put[REXPOU1F_OUTPUT.PATHS] = outputs[0] 64 | map_out_put[REXPOU1F_OUTPUT.SPOT_VOLATILITY_PATHS] = outputs[1] 65 | map_out_put[REXPOU1F_OUTPUT.INTEGRAL_VARIANCE_PATHS] = outputs[2] 66 | 67 | return map_out_put 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /MC_Engines/MC_RBergomi/readme.txt: -------------------------------------------------------------------------------- 1 | I have to finish the engine rExpOU1F_Engine. -------------------------------------------------------------------------------- /MC_Engines/MC_SABR/Examples/Example_Check_Distribution.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | import matplotlib.pylab as plt 4 | 5 | from MC_Engines.MC_SABR import VarianceSamplingMatchingMoment 6 | from MC_Engines.MC_SABR import SABR_Engine 7 | from Tools.Types import TYPE_STANDARD_NORMAL_SAMPLING, SABR_OUTPUT 8 | from Tools import RNG 9 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 10 | 11 | # model parameters 12 | alpha = 0.5 13 | nu = 0.8 14 | rho = -0.6 15 | parameters = [alpha, nu, rho] 16 | f0 = 100.0 17 | t = 2.0 18 | 19 | # simulation info 20 | no_paths = 500000 21 | no_time_steps = 300 22 | seed = 12345 23 | rnd_generator = RNG.RndGenerator(seed) 24 | 25 | start_time = time.time() 26 | output = SABR_Engine.get_path_multi_step(0.0, t, parameters, f0, no_paths, no_time_steps, 27 | TYPE_STANDARD_NORMAL_SAMPLING.REGULAR_WAY, rnd_generator) 28 | end_time = time.time() 29 | diff = (end_time - start_time) 30 | print(diff) 31 | 32 | sampling_I_t = np.sum(output[SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS], axis=1) 33 | 34 | # one step 35 | rnd_generator.set_seed(seed) 36 | z_int_t = rnd_generator.normal(0.0, 1.0, no_paths) 37 | z_t = rnd_generator.normal(0.0, 1.0, no_paths) 38 | alpha_t0 = np.full(no_paths, alpha, dtype=np.float) 39 | alpha_t = SABR_Engine.get_vol_sampling(0.0, t, alpha_t0, nu, z_t) 40 | 41 | approx_I_t = VarianceSamplingMatchingMoment.get_variance(alpha_t0, nu, alpha_t, t, z_int_t) 42 | 43 | # paths one step 44 | rnd_generator.set_seed(seed) 45 | start_time = time.time() 46 | paths_one_step = SABR_Engine.get_path_one_step(0.0, t, parameters, f0, no_paths, rnd_generator) 47 | end_time = time.time() 48 | diff = (end_time - start_time) 49 | print(diff) 50 | 51 | # comparison of distributions 52 | # a = np.minimum(np.min(sampling_I_t), np.min(approx_I_t)) 53 | # b = np.maximum(np.max(sampling_I_t), np.max(approx_I_t)) 54 | # b=15 55 | # 56 | # bins = np.linspace(a, b, 200) 57 | # 58 | # plt.hist(approx_I_t, bins, label='approximation') 59 | # plt.hist(sampling_I_t, bins, label='empirical') 60 | # 61 | # plt.legend() 62 | # plt.show() 63 | 64 | # option price comparison 65 | no_strikes = 40 66 | strikes = np.linspace(50.0, 160.0, no_strikes) 67 | prices_multistep = [] 68 | prices_onestep = [] 69 | 70 | for i in range(0, no_strikes): 71 | option = EuropeanOption(strikes[i], 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, t) 72 | prices_multistep.append(option.get_price_control_variate(output[SABR_OUTPUT.PATHS][:, -1], 73 | output[SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS])[0]) 74 | prices_onestep.append(option.get_price(paths_one_step[:])[0]) 75 | 76 | 77 | plt.plot(strikes, prices_onestep, label='sabr one step') 78 | plt.plot(strikes, prices_multistep, label='sabr multi step') 79 | plt.title("T= %s" % t) 80 | plt.xlabel("K") 81 | 82 | plt.legend() 83 | plt.show() 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /MC_Engines/MC_SABR/SABRTools.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | import numba as nb 17 | 18 | from Tools.Types import ndarray 19 | 20 | 21 | @nb.jit("(f8,f8,f8[:],f8[:],f8[:],f8[:])", nopython=True, nogil=True) 22 | def get_delta_weight(t_i_1, t_i, sigma_t_i_1, sigma_t_i, w_i_f, delta_weight): 23 | alpha1 = 1.0 24 | alpha2 = 0.0 25 | no_paths = len(w_i_f) 26 | sqr_delta_time = np.sqrt(t_i - t_i_1) 27 | for i in range(0, no_paths): 28 | delta_weight[i] += w_i_f[i] * sqr_delta_time * ((alpha1 / sigma_t_i_1[i]) + (alpha2 / sigma_t_i[i])) 29 | 30 | 31 | @nb.jit("(f8,f8,f8[:],f8[:],f8[:], f8[:])", nopython=True, nogil=True) 32 | def get_var_weight(t_i_1, t_i, sigma_t_i_1, sigma_t_i, w_i_f, var_weight): 33 | alpha1 = 1.0 34 | alpha2 = 0.0 35 | no_paths = len(w_i_f) 36 | sqr_delta_time = np.sqrt(t_i - t_i_1) 37 | for i in range(0, no_paths): 38 | v_i_i = sigma_t_i_1[i] * sigma_t_i_1[i] 39 | v_i = sigma_t_i[i] * sigma_t_i[i] 40 | var_weight[i] += w_i_f[i] * sqr_delta_time * ((alpha1 / v_i_i) + (alpha2 / v_i)) 41 | 42 | 43 | @nb.jit("(f8[:],f8[:],f8[:],f8,f8,f8[:])", nopython=True, nogil=True) 44 | def get_gamma_weight(delta_weight, var_weight, inv_variance, rho, t, gamma_weight): 45 | no_paths = len(delta_weight) 46 | rho_c = np.sqrt(1.0 - rho * rho) 47 | for i in range(0, no_paths): 48 | gamma_weight[i] = np.power(var_weight[i], 2.0) - inv_variance[i] - rho_c * t * delta_weight[i] 49 | 50 | 51 | @nb.jit("f8[:](f8,f8,f8[:],f8[:],f8,f8)", nopython=True, nogil=True, parallel=True) 52 | def get_integral_variance(t_i_1: float, 53 | t_i: float, 54 | sigma_t_i_1: ndarray, 55 | sigma_t_i: ndarray, 56 | w_i_1: float, 57 | w_i: float): 58 | delta = (t_i - t_i_1) 59 | v_i_1 = sigma_t_i_1 * sigma_t_i_1 60 | v_i = sigma_t_i * sigma_t_i 61 | return delta * (w_i_1 * v_i_1 + w_i * v_i) 62 | -------------------------------------------------------------------------------- /MC_Engines/MC_SABR/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/MC_SABR/__init__.py -------------------------------------------------------------------------------- /MC_Engines/MC_SRoughVolatility/Examples/Example_cov_matrix.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | from MC_Engines.MC_SRoughVolatility import ToolsVariance 4 | 5 | no_points = 10 6 | beta = 1.4 7 | T = np.exp(-beta - 1) 8 | t = np.linspace(0.0, T, no_points) 9 | 10 | variance = ToolsVariance.get_variance(t, beta) 11 | 12 | cov = np.zeros(shape=(no_points, no_points)) 13 | 14 | 15 | # plot kernel 16 | # s = 0.01 17 | # t = 0.03 18 | # u_i = np.linspace(0, 0.9999, 100) 19 | # k_i = [] 20 | # for u in u_i: 21 | # k_i.append(ToolsVariance.get_kernel(u, s, t, beta)) 22 | # 23 | # plt.plot(u_i, k_i) 24 | # plt.show() 25 | 26 | var_from_cov = [] 27 | for i in range(0, no_points): 28 | var_from_cov.append(ToolsVariance.get_volterra_covariance(t[i], t[i], beta)) 29 | error = variance[i] - var_from_cov[-1] 30 | 31 | plt.plot(t, variance) 32 | plt.plot(t, var_from_cov) 33 | plt.show() 34 | 35 | 36 | for i in range(0, no_points): 37 | for j in range(0, i + 1): 38 | cov[i, j] = ToolsVariance.get_volterra_covariance(t[i], t[j], 1.4) 39 | cov[j, i] = cov[i, j] 40 | if i == j: 41 | error = variance[i] - cov[i, i] 42 | 43 | 44 | print(cov) 45 | -------------------------------------------------------------------------------- /MC_Engines/MC_SRoughVolatility/Examples/SmileExample.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pylab as plt 2 | 3 | from MC_Engines.MC_SRoughVolatility import SRoughVolatility_Engine 4 | from Tools import RNG, Types 5 | from Instruments.EuropeanInstruments import EuropeanOption, TypeSellBuy, TypeEuropeanOption 6 | from py_vollib.black_scholes_merton.implied_volatility import implied_volatility 7 | 8 | 9 | # simulation info 10 | beta = 1.1 11 | nu = 0.5 12 | rho = -0.4 13 | parameters = [nu, rho, beta] 14 | no_time_steps = 100 15 | 16 | seed = 123456789 17 | no_paths = 10 18 | T = 0.5 19 | 20 | # random number generator 21 | rnd_generator = RNG.RndGenerator(seed) 22 | 23 | # option information 24 | f0 = 100.0 25 | sigma_0 = 0.3 26 | implied_vol = [] 27 | options_price = [] 28 | options = [] 29 | strikes = [50.0, 60.0, 70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0, 140.0, 150.0] 30 | no_strikes = len(strikes) 31 | 32 | for k_i in strikes: 33 | options.append(EuropeanOption(k_i, 1.0, TypeSellBuy.BUY, TypeEuropeanOption.CALL, f0, T)) 34 | 35 | 36 | map_output = SRoughVolatility_Engine.get_path_exp_multi_step(0.0, T, parameters, f0, sigma_0, no_paths, no_time_steps, 37 | Types.TYPE_STANDARD_NORMAL_SAMPLING.ANTITHETIC, 38 | rnd_generator) 39 | 40 | for i in range(0, no_strikes): 41 | 42 | mc_option_price = options[i].get_price_control_variate(map_output[Types.SABR_OUTPUT.PATHS][:, -1], 43 | map_output[Types.SABR_OUTPUT.INTEGRAL_VARIANCE_PATHS]) 44 | 45 | options_price.append(mc_option_price) 46 | 47 | implied_vol.append(implied_volatility(mc_option_price[0], f0, strikes[i], T, 0.0, 0.0, 'c')) 48 | 49 | 50 | plt.plot(strikes, implied_vol, label='implied vol', color='black', linestyle='--') 51 | 52 | plt.xlabel('k') 53 | plt.legend() 54 | plt.show() 55 | -------------------------------------------------------------------------------- /MC_Engines/MC_SRoughVolatility/ToolsVariance.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numba as nb 3 | 4 | from scipy.integrate import quad_vec, quad, quadrature 5 | from Tools.Types import ndarray 6 | from functools import partial 7 | 8 | 9 | @nb.jit("f8[:](f8[:], f8)", nopython=True, nogil=True) 10 | def get_variance(t: ndarray, beta: float): 11 | return np.power(-np.log(t), -beta) 12 | 13 | 14 | @nb.jit("f8(f8, f8)", nopython=True, nogil=True) 15 | def get_kernel(x, beta): 16 | return np.sqrt(x * np.power(-np.log(x), beta + 1.0)) 17 | 18 | 19 | @nb.jit("f8(f8, f8, f8, f8)", nopython=True, nogil=True) 20 | def get_kernel_cov(u, s, t, beta): 21 | if s < t: 22 | d_t_s = (t - s) 23 | return np.power((d_t_s + u) * u, -0.5) * np.power(np.log(u) * np.log(d_t_s + u), -0.5 * (beta + 1.0)) 24 | else: 25 | d_t_s = (s - t) 26 | return np.power((d_t_s + u) * u, -0.5) * np.power(np.log(u) * np.log(d_t_s + u), -0.5 * (beta + 1.0)) 27 | 28 | 29 | def get_volterra_covariance(s: float, t: float, beta: float): 30 | if s > 0.0 and t > 0.0: 31 | f_kernel = partial(get_kernel_cov, s=s, t=t, beta=beta) 32 | min_t_s = np.minimum(t, s) 33 | integral_value = quad(f_kernel, 0.0, min_t_s) 34 | 35 | return integral_value[0] * beta 36 | 37 | else: 38 | return 0.0 39 | 40 | 41 | def get_covariance_w_v_w_t(s: float, t: float, rho: float, beta: float): 42 | if s > 0.0 and t > 0.0: 43 | f_kernel = partial(get_kernel, beta=beta) 44 | max_s_t = np.maximum(t - s, 0.0) 45 | integral_value = quad(f_kernel, 0.0, max_s_t) 46 | return np.sqrt(beta) * rho * integral_value[0] 47 | else: 48 | return 0.0 49 | 50 | 51 | def get_covariance_matrix(t_i_s: ndarray, beta: float, rho: float): 52 | no_time_steps = len(t_i_s) 53 | cov = np.zeros(shape=(2 * no_time_steps, 2 * no_time_steps)) 54 | 55 | for i in range(0, no_time_steps): 56 | for j in range(0, no_time_steps): 57 | cov[i, j] = np.minimum(t_i_s[i], t_i_s[j]) 58 | 59 | for i in range(0, no_time_steps): 60 | for j in range(no_time_steps, 2 * no_time_steps): 61 | cov[i, j] = get_covariance_w_v_w_t(t_i_s[j - no_time_steps], t_i_s[i], rho, beta) 62 | cov[j, i] = cov[i, j] 63 | 64 | for i in range(0, no_time_steps): 65 | for j in range(0, no_time_steps): 66 | cov[i + no_time_steps, j + no_time_steps] = get_volterra_covariance(t_i_s[i], t_i_s[j], beta) 67 | 68 | return cov 69 | -------------------------------------------------------------------------------- /MC_Engines/MC_SRoughVolatility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/MC_SRoughVolatility/__init__.py -------------------------------------------------------------------------------- /MC_Engines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/MC_Engines/__init__.py -------------------------------------------------------------------------------- /PyStochasticVolatility.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: PyStochasticVolatility 3 | Version: 1.0 4 | Summary: Financial library creted to give support the book Malliavin Calculus and Stochastic Volatility. 5 | Home-page: https://github.com/Dagalon/PyStochasticVolatility.git 6 | Author: David Garcia Lorite 7 | Author-email: david.garcia.lorite@outlook.com 8 | License: http://www.apache.org/licenses/LICENSE-2.0 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /PyStochasticVolatility.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /PyStochasticVolatility.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | AnalyticEngines 2 | FractionalBrownian 3 | Instruments 4 | MCPricers 5 | MC_Engines 6 | Solvers 7 | Tools 8 | VolatilitySurface 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyStochasticVolatility 2 | This repository contains different tools to simulate underlyings under SV dynamics. As well, we have implemented several tools for computing option price under SV. 3 | 4 | ## Packages you need to include 5 | The next packages are using of a intesive way: 6 | * numpy~=1.19.1 7 | * numba~=0.41.0 8 | * ncephes~=1.1.0 9 | * matplotlib~=3.2.2 10 | * scipy~=1.5.0 11 | * pandas~=1.1.0 12 | * statsmodels~=0.11.1 13 | * tabulate~=0.8.3 14 | * prettytable~=0.7.2 15 | * setuptools~=49.2.1 16 | 17 | ## Module description 18 | We will a brief description of each module of the library. 19 | 20 | ### AnalyticEngines 21 | In this module, the reader can find two numerical methods Fourier inversion or COS method to price options under dynamics with known characteristic function. Currently, we have implemented the characteristic function for the Heston and Merton model. In addition, we have included the different approximations for implied volatility, vol swap, variance swap $\cdots$ that we have obtained using Malliavin tools. 22 | 23 | ### Examples 24 | Here the reader can find the numerical experiments performed for each chapter of the book. 25 | 26 | ### FractionalBrownian 27 | Tools for sampling the fractional brownian motion and the truncated fractional brownian. 28 | 29 | ### Instruments 30 | In this module, the reader can find the different instruments that we have used in the book. These instruments are independent of the model, for this reason, we cause the same instrument for different MC engines. 31 | 32 | ### MCPricers 33 | This module contains the different MC pricers that the user can use in any MC Engine. 34 | 35 | ### Solvers 36 | We have included a one dimensional PDE solver. Under the local volatility model, this numerical is so stable and fast. The user can use generic boundary conditions and solver the PDE using explicit, implicit, or theta scheme. 37 | 38 | ### Tools 39 | This module contains generic functionalities (random number generator, numba functions, ...) that are used in several parts of the library. 40 | 41 | ### VolatilitySurface 42 | The term struct for building volatility surface. We have implemented SVI and SABR. 43 | 44 | 45 | -------------------------------------------------------------------------------- /Solvers/ODE.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numpy as np 15 | 16 | from Tools.Types import ndarray, min_value 17 | 18 | 19 | def a_cev(rho: float, mu: float, sigma: float, z_t: float, t: float, x: float): 20 | x_epsilon = np.maximum(min_value, x) 21 | return (1.0 - rho) * (mu + 0.5 * rho * sigma * sigma * np.power(x_epsilon, -2.0)) 22 | 23 | 24 | def f_analytic_cev(rho: float, mu: float, sigma: float, z_t: float, t: ndarray): 25 | multiplier = (1.0 - rho) * (mu + 0.5 * rho * sigma * sigma * np.power(z_t, -2.0)) 26 | return z_t * np.exp(multiplier * t) 27 | -------------------------------------------------------------------------------- /Solvers/ODESolver.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numpy as np 15 | 16 | from Tools.Types import ndarray 17 | from typing import Callable 18 | 19 | 20 | def ode_euler_solver_malliavin(s: float, 21 | t: float, 22 | s0: float, 23 | no_steps: int, 24 | z_t: ndarray, 25 | a_f: Callable[[float, float], float]): 26 | path = np.empty(z_t.shape) 27 | path[:, 0] = s0 28 | 29 | t_i = np.linspace(s, t, no_steps) 30 | delta_i = np.diff(t_i) 31 | 32 | for j in range(1, no_steps): 33 | path[:, j] = path[:, j - 1] + a_f(t_i[j - 1], z_t[:, j-1]) * delta_i[j - 1] 34 | 35 | return path 36 | -------------------------------------------------------------------------------- /Solvers/ODE_Solver/ODE.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # See the License for the specific language governing permissions and limitations under the License. 13 | # 14 | 15 | import numpy as np 16 | 17 | from Tools.Types import ndarray 18 | 19 | 20 | def a_cev(rho: float, mu: float, sigma: float, z_t: float, t: float, x: float): 21 | return (1.0 - rho) * (mu + 0.5 * rho * sigma * sigma * np.power(x, -2.0)) 22 | 23 | 24 | def f_analytic_cev(rho: float, mu: float, sigma: float, z_t: float, t: ndarray): 25 | multiplier = (1.0 - rho) * (mu + 0.5 * rho * sigma * sigma * np.power(z_t, -2.0)) 26 | return z_t * np.exp(multiplier * t) 27 | -------------------------------------------------------------------------------- /Solvers/ODE_Solver/ODESolver.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numpy as np 15 | 16 | from Tools.Types import ndarray 17 | from typing import Callable 18 | 19 | 20 | def ode_euler_solver_malliavin(s: float, 21 | t: float, 22 | s0: float, 23 | no_steps: int, 24 | z_t: ndarray, 25 | a_f: Callable[[float, float], float]): 26 | path = np.empty(z_t.shape) 27 | path[:, 0] = s0 28 | 29 | t_i = np.linspace(s, t, no_steps) 30 | delta_i = np.diff(t_i) 31 | 32 | for j in range(1, no_steps): 33 | path[:, j] = path[:, j - 1] + a_f(t_i[j - 1], z_t[:, j-1]) * delta_i[j - 1] 34 | 35 | return path 36 | -------------------------------------------------------------------------------- /Solvers/ODE_Solver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/Solvers/ODE_Solver/__init__.py -------------------------------------------------------------------------------- /Solvers/PDE_Solver/BoundariesConditions.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from Solvers.PDE_Solver.Types import BoundaryConditionType 3 | 4 | 5 | class BoundaryCondition(object): 6 | def __init__(self, bc_type: BoundaryConditionType): 7 | self._bc_type = bc_type 8 | 9 | def get_type(self): 10 | return self._bc_type 11 | 12 | @abstractmethod 13 | def apply_boundary_condition(self, **kwargs): 14 | pass 15 | 16 | @abstractmethod 17 | def apply_boundary_condition_after_update(self, **kwargs): 18 | pass 19 | 20 | 21 | class Zero_Laplacian_BC(BoundaryCondition): 22 | def __init__(self): 23 | BoundaryCondition.__init__(self, BoundaryConditionType.ZERO_LAPLACIAN) 24 | 25 | def apply_boundary_condition(self, **kwargs): 26 | operator = kwargs['operator'] 27 | operator.diagonal()[0] = 0.0 28 | operator.diagonal()[-1] = 0.0 29 | 30 | def apply_boundary_condition_after_update(self, **kwargs): 31 | operator = kwargs['operator'] 32 | mesh = operator.get_mesh() 33 | no_nodes = mesh.get_size() 34 | 35 | delta_r_0 = mesh.get_shift()[0] 36 | delta_l_0 = mesh.get_shift()[1] 37 | 38 | delta_r_N = mesh.get_shift()[-2] 39 | delta_l_N = mesh.get_shift()[-1] 40 | 41 | w_l_0 = 2.0 / (delta_l_0 * (delta_r_0 + delta_l_0)) 42 | w_c_0 = - 2.0 / (delta_l_0 * delta_r_0) 43 | w_u_0 = 2.0 / (delta_r_0 * (delta_r_0 + delta_l_0)) 44 | 45 | w_l_N = 2.0 / (delta_l_N * (delta_r_N + delta_l_N)) 46 | w_c_N = - 2.0 / (delta_l_N * delta_r_N) 47 | w_u_N = 2.0 / (delta_r_N * (delta_r_N + delta_l_N)) 48 | 49 | kwargs['u_t'][0] = - (w_c_0 / w_l_0) * kwargs['u_t'][1] - (w_u_0 / w_l_0) * kwargs['u_t'][2] 50 | kwargs['u_t'][no_nodes - 1] = - (w_l_N / w_u_N) * kwargs['u_t'][no_nodes - 3] - \ 51 | (w_c_N / w_u_N) * kwargs['u_t'][no_nodes - 2] 52 | 53 | 54 | class RobinCondition(BoundaryCondition): 55 | def __init__(self, mu: float): 56 | BoundaryCondition.__init__(self, BoundaryConditionType.ROBIN) 57 | self._mu = mu 58 | 59 | def apply_boundary_condition(self, **kwargs): 60 | pass 61 | 62 | def apply_boundary_condition_after_update(self, **kwargs): 63 | pass 64 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/Examples/Example_1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | 4 | from Solvers.PDE_Solver import PDESolvers 5 | from Solvers.PDE_Solver import PDEOperators 6 | from scipy.interpolate import interp1d 7 | from Solvers.PDE_Solver.Meshes import uniform_mesh, Mesh, LnUnderlyingMesh 8 | from Solvers.PDE_Solver.PDEs import LN_BS_PDE, PDE 9 | from Solvers.PDE_Solver.Types import BoundaryConditionType, np_ndarray, SchemeType 10 | from Solvers.PDE_Solver.TerminalConditions import TerminalCondition 11 | from Solvers.PDE_Solver.BoundariesConditions import Zero_Laplacian_BC 12 | from py_vollib.black_scholes_merton import black_scholes_merton 13 | 14 | try: 15 | import matplotlib.pyplot as plt 16 | from mpl_toolkits.mplot3d import Axes3D 17 | from matplotlib import cm 18 | except ImportError: 19 | print('The import of matplotlib is not working.') 20 | 21 | T = 4.0 22 | mesh_t = Mesh(uniform_mesh, 50, 0.0, T) 23 | 24 | r = 0.03 25 | q = 0.01 26 | sigma = 0.3 27 | 28 | S0 = 100.0 29 | K = np.exp((r - q) * T) * S0 + 10 30 | log_K = np.log(K) 31 | 32 | f = np.exp((r - q) * T) * S0 33 | df = np.exp(-r * T) 34 | 35 | start_time = time.time() 36 | analytic_price = df * black_scholes_merton('c', f, K, T, 0.0, sigma, 0.0) 37 | end_time = time.time() 38 | print(end_time - start_time) 39 | print(analytic_price) 40 | 41 | mesh_x = LnUnderlyingMesh(r, q, sigma, S0, T, 0.999, uniform_mesh, 100) 42 | 43 | bs_pde = PDE.from_ipde_terms(LN_BS_PDE(r, q, sigma)) 44 | bc = Zero_Laplacian_BC() 45 | 46 | operator_exp = PDEOperators.LinearPDEOperator(mesh_x, bs_pde, bc) 47 | operator_impl = PDEOperators.LinearPDEOperator(mesh_x, bs_pde, bc) 48 | operators = [operator_exp, operator_impl] 49 | 50 | 51 | def f_ln_payoff(mesh: Mesh) -> np_ndarray: 52 | return np.maximum(np.exp(mesh.get_points()) - K, 0.0) 53 | 54 | 55 | tc = TerminalCondition(f_ln_payoff) 56 | 57 | pd_solver = PDESolvers.FDSolver(mesh_t, 58 | mesh_x, 59 | operators, 60 | SchemeType.CRANK_NICOLSON, 61 | BoundaryConditionType.ZERO_DIFFUSION, 62 | tc) 63 | start_time = time.time() 64 | pd_solver.solver() 65 | end_time = time.time() 66 | f = interp1d(mesh_x.get_points(), pd_solver._u_grid[:, 0], kind='linear', fill_value='extrapolate') 67 | pde_price = float(f(np.log(S0))) 68 | print(end_time - start_time) 69 | print(pde_price) 70 | 71 | 72 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/Examples/Example_2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from Solvers.PDE_Solver.Meshes import Mesh, finite_volume_mesh, uniform_mesh 4 | 5 | x_max = 1.0 6 | x_min = -1.0 7 | T = 3.0 8 | 9 | mesh_t = Mesh(uniform_mesh, 50, 0.0, T) 10 | mesh_x = Mesh(finite_volume_mesh, 100, x_min, x_max) 11 | 12 | r = 0.03 13 | q = 0.01 14 | sigma = 0.3 15 | 16 | S0 = 100.0 17 | K = np.exp((r - q) * T) * S0 + 10 18 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/Examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/Solvers/PDE_Solver/Examples/__init__.py -------------------------------------------------------------------------------- /Solvers/PDE_Solver/Operators.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from Solvers.PDE_Solver.Meshes import Mesh 3 | from Solvers.PDE_Solver.Types import np_ndarray 4 | from Solvers.PDE_Solver.BoundariesConditions import BoundaryCondition 5 | from typing import Callable 6 | 7 | 8 | class Operator(object): 9 | def __init__(self, mesh: Mesh, bc: BoundaryCondition): 10 | self._diagonal_upper = np.zeros(mesh.get_size() - 1) 11 | self._diagonal_lower = np.zeros(mesh.get_size() - 1) 12 | self._diagonal = np.zeros(mesh.get_size()) 13 | self._mesh = mesh 14 | self._bc = bc 15 | 16 | def diagonal_upper(self): 17 | return self._diagonal_upper 18 | 19 | def diagonal_lower(self): 20 | return self._diagonal_lower 21 | 22 | def diagonal(self): 23 | return self._diagonal 24 | 25 | def get_mesh(self): 26 | return self._mesh 27 | 28 | def modify_operator(self, functional: Callable[[np_ndarray], None]): 29 | functional(self._diagonal) 30 | functional(self._diagonal_lower) 31 | functional(self._diagonal_upper) 32 | pass 33 | 34 | def apply_implicit_operator(self, delta: float, u_i: np_ndarray) -> np_ndarray: 35 | pass 36 | 37 | def apply_explicit_operator(self, delta: float, u_i: np_ndarray) -> np_ndarray: 38 | pass 39 | 40 | def update_operator(self, t: float, mesh: Mesh): 41 | pass 42 | 43 | def apply_boundary_condition(self, **kwargs): 44 | pass 45 | 46 | def apply_boundary_condition_after_update(self, **kwargs): 47 | pass 48 | 49 | 50 | class Gradient(Operator): 51 | 52 | def __init__(self, mesh: Mesh, bc: BoundaryCondition): 53 | super().__init__(mesh, bc) 54 | 55 | def update_operator(self, t: float, mesh: Mesh): 56 | for i in range(1, self._mesh.get_size() - 1): 57 | delta_r = self._mesh.get_shift()[i] 58 | delta_l = self._mesh.get_shift()[i - 1] 59 | 60 | self._diagonal_lower[i-1] = - delta_r / (delta_l * (delta_r + delta_l)) 61 | self._diagonal[i] = - (delta_l - delta_r) / (delta_r * delta_l) 62 | self._diagonal_upper[i] = delta_l / (delta_r * (delta_r + delta_l)) 63 | 64 | 65 | class Laplacian(Operator): 66 | def __init__(self, mesh: Mesh, bc: BoundaryCondition): 67 | super().__init__(mesh, bc) 68 | 69 | def update_operator(self, t: float, mesh: Mesh): 70 | for i in range(1, self._mesh.get_size() - 1): 71 | delta_r = self._mesh.get_shift()[i] 72 | delta_l = self._mesh.get_shift()[i - 1] 73 | self._diagonal_lower[i - 1] = 2.0 / (delta_l * (delta_r + delta_l)) 74 | self._diagonal[i] = - 2.0 / (delta_l * delta_r) 75 | self._diagonal_upper[i] = 2.0 / (delta_r * (delta_r + delta_l)) 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/PDEOperators.py: -------------------------------------------------------------------------------- 1 | from Solvers.PDE_Solver import Tools 2 | 3 | from Solvers.PDE_Solver.Operators import Operator, Laplacian, Gradient 4 | from Solvers.PDE_Solver.PDEs import PDE 5 | from Solvers.PDE_Solver.Types import BoundaryConditionType, np_ndarray 6 | from Solvers.PDE_Solver.Meshes import Mesh 7 | from Solvers.PDE_Solver.BoundariesConditions import BoundaryCondition 8 | 9 | 10 | class LinearPDEOperator(Operator): 11 | 12 | def __init__(self, mesh: Mesh, pde: PDE, bc: BoundaryCondition): 13 | Operator.__init__(self, mesh, bc) 14 | self._pde = pde 15 | self._laplacian = Laplacian(mesh, BoundaryCondition(BoundaryConditionType.ZERO_LAPLACIAN)) 16 | self._gradient = Gradient(mesh, BoundaryCondition(BoundaryConditionType.ZERO_GRADIENT)) 17 | 18 | def apply_implicit_operator(self, delta: float, u_i: np_ndarray) -> np_ndarray: 19 | return Tools.tdr_system_solver((1.0 - delta * self.diagonal()), 20 | - delta * self.diagonal_lower(), 21 | - delta * self.diagonal_upper(), 22 | u_i) 23 | 24 | def apply_explicit_operator(self, delta: float, u_i: np_ndarray) -> np_ndarray: 25 | return Tools.apply_tdr((1.0 + delta * self.diagonal()), 26 | delta * self.diagonal_lower(), 27 | delta * self.diagonal_upper(), 28 | u_i) 29 | 30 | def get_pde(self): 31 | return self._pde 32 | 33 | def apply_boundary_condition(self, **kwargs): 34 | self._bc.apply_boundary_condition(**kwargs) 35 | 36 | def apply_boundary_condition_after_update(self, **kwargs): 37 | self._bc.apply_boundary_condition_after_update(**kwargs) 38 | 39 | def update_operator(self, t: float, mesh: Mesh): 40 | diffusion = self._pde.diffusion(t, mesh.get_points()) 41 | convection = self._pde.convection(t, mesh.get_points(), diffusion) 42 | source = self._pde.source(t, mesh.get_points()) 43 | 44 | self._laplacian.update_operator(t, mesh) 45 | self._gradient.update_operator(t, mesh) 46 | 47 | Tools.update_diagonal(diffusion, 48 | convection, 49 | source, 50 | self._gradient.diagonal(), 51 | self._laplacian.diagonal(), 52 | self.diagonal()) 53 | 54 | Tools.update_diagonal_upper(diffusion, 55 | convection, 56 | self._gradient.diagonal_upper(), 57 | self._laplacian.diagonal_upper(), 58 | self.diagonal_upper()) 59 | 60 | Tools.update_diagonal_lower(diffusion, 61 | convection, 62 | self._gradient.diagonal_lower(), 63 | self._laplacian.diagonal_lower(), 64 | self.diagonal_lower()) 65 | 66 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/PDESolvers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from Solvers.PDE_Solver.PDEOperators import LinearPDEOperator 4 | from Solvers.PDE_Solver.BoundariesConditions import BoundaryCondition 5 | from Solvers.PDE_Solver.TerminalConditions import TerminalCondition 6 | from Solvers.PDE_Solver.Types import SchemeType, BoundaryConditionType 7 | from Solvers.PDE_Solver.Meshes import Mesh 8 | from Solvers.PDE_Solver import Schemes 9 | from typing import List 10 | 11 | 12 | class FDSolver(object): 13 | 14 | def __init__(self, 15 | mesh_t: Mesh, 16 | mesh_x: Mesh, 17 | operators: List[LinearPDEOperator], 18 | scheme_type: SchemeType, 19 | bc_type: BoundaryConditionType, 20 | tc: TerminalCondition): 21 | 22 | self._operators = operators 23 | self._mesh_t = mesh_t 24 | self._mesh_x = mesh_x 25 | self._u_grid = np.zeros(shape=(mesh_x.get_size(), mesh_t.get_size())) 26 | 27 | if scheme_type == SchemeType.EXPLICIT: 28 | self._scheme = Schemes.ExplicitScheme(operators[0]) 29 | elif scheme_type == SchemeType.IMPLICIT: 30 | self._scheme = Schemes.ImplicitScheme(operators[0]) 31 | elif scheme_type == SchemeType.CRANK_NICOLSON: 32 | self._scheme = Schemes.ThetaScheme(operators, 0.5) 33 | else: 34 | raise ValueError("The operator type " + str(scheme_type)) 35 | 36 | self._bc = BoundaryCondition(bc_type) 37 | self._tc = tc 38 | 39 | def get_solution_grid(self): 40 | return self._u_grid 41 | 42 | def solver(self): 43 | u_i = np.zeros(self._mesh_x.get_size()) 44 | u_i_1 = np.zeros(self._mesh_x.get_size()) 45 | 46 | u_i = self._tc.get_value(self._mesh_x) 47 | np.copyto(self._u_grid[:, -1], u_i) 48 | no_t_i = self._mesh_t.get_size() 49 | 50 | for i in range(no_t_i-2, -1, -1): 51 | self._scheme.step_solver(self._mesh_x, 52 | self._mesh_t.get_point(i), 53 | self._mesh_t.get_point(i+1), 54 | u_i_1, 55 | u_i) 56 | 57 | np.copyto(u_i, u_i_1) 58 | np.copyto(self._u_grid[:, i], u_i_1) 59 | 60 | def update_terminal_condition(self, tc: TerminalCondition): 61 | self._tc = tc 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/PDEs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from typing import Callable 4 | from Tools.Types import ndarray 5 | from abc import abstractmethod 6 | 7 | 8 | class IPDETerms(object): 9 | 10 | @abstractmethod 11 | def source(self, t: float, x: ndarray): 12 | pass 13 | 14 | @abstractmethod 15 | def convection(self, t: float, x: ndarray, v: ndarray): 16 | pass 17 | 18 | @abstractmethod 19 | def diffusion(self, t: float, x: ndarray): 20 | pass 21 | 22 | 23 | class BS_forward_PDE(IPDETerms): 24 | def __init__(self, sigma): 25 | self._sigma = sigma 26 | 27 | def source(self, t: float, x: ndarray): 28 | return np.zeros(x.size) 29 | 30 | def convection(self, t: float, x: ndarray, v: ndarray): 31 | return np.zeros(x.size) 32 | 33 | def diffusion(self, t: float, x: ndarray): 34 | return 0.5 * np.power(self._sigma * x, 2.0) * np.ones(x.size) 35 | 36 | 37 | class LN_BS_PDE(IPDETerms): 38 | def __init__(self, r: float, q: float, sigma: float): 39 | self._r = r 40 | self._q = q 41 | self._sigma = sigma 42 | 43 | def source(self, t: float, x: ndarray): 44 | return -self._r * np.ones(x.size) 45 | 46 | def convection(self, t: float, x: ndarray, v: ndarray): 47 | return (self._r - self._q - 0.5 * np.power(self._sigma, 2.0)) * np.ones(x.size) 48 | 49 | def diffusion(self, t: float, x: ndarray): 50 | return 0.5 * np.power(self._sigma, 2.0) * np.ones(x.size) 51 | 52 | 53 | class LN_FORWARD_LOCAL_VOL_PDE(IPDETerms): 54 | def __init__(self, f_local_vol: Callable[[float, ndarray], ndarray]): 55 | self._sigma = f_local_vol 56 | 57 | def source(self, t: float, x: ndarray): 58 | return np.zeros(x.size) 59 | 60 | def diffusion(self, t: float, x: ndarray): 61 | return 0.5 * np.power(self._sigma(t, x), 2.0) 62 | 63 | def convection(self, t: float, x: ndarray, v: ndarray): 64 | return - v 65 | 66 | 67 | class NORMAL_LOCAL_VOL_PDE(IPDETerms): 68 | def __init__(self, f_local_vol: Callable[[float, ndarray], ndarray]): 69 | self._sigma = f_local_vol 70 | 71 | def source(self, t: float, x: ndarray): 72 | return np.zeros(x.size) 73 | 74 | def diffusion(self, t: float, x: ndarray): 75 | return 0.5 * np.power(self._sigma(t, x), 2.0) 76 | 77 | def convection(self, t: float, x: ndarray, v: ndarray): 78 | return np.zeros(x.size) 79 | 80 | 81 | class PDE(object): 82 | def __init__(self, 83 | source: Callable[[float, ndarray], ndarray], 84 | convection: Callable[[float, ndarray, ndarray], ndarray], 85 | diffusion: Callable[[float, ndarray], ndarray]): 86 | self._source = source 87 | self._convection = convection 88 | self._diffusion = diffusion 89 | 90 | def source(self, t: float, x: ndarray) -> ndarray: 91 | return self._source(t, x) 92 | 93 | def convection(self, t: float, x: ndarray, v: ndarray) -> ndarray: 94 | return self._convection(t, x, v) 95 | 96 | def diffusion(self, t: float, x: ndarray) -> ndarray: 97 | return self._diffusion(t, x) 98 | 99 | @classmethod 100 | def from_ipde_terms(cls, ipde: IPDETerms): 101 | return cls(ipde.source, 102 | ipde.convection, 103 | ipde.diffusion) 104 | 105 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/TerminalConditions.py: -------------------------------------------------------------------------------- 1 | from Solvers.PDE_Solver.Meshes import Mesh 2 | from Tools.Types import ndarray 3 | from typing import Callable 4 | 5 | 6 | class TerminalCondition(object): 7 | def __init__(self, functional: Callable[[Mesh], ndarray]): 8 | self._functional = functional 9 | 10 | def get_value(self, mesh: Mesh) -> ndarray: 11 | return self._functional(mesh) 12 | 13 | def update(self, functional: Callable[[Mesh], ndarray]): 14 | self._functional = functional 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/Tools.py: -------------------------------------------------------------------------------- 1 | import numba as nb 2 | import numpy as np 3 | 4 | 5 | @nb.jit("(f8[:], f8[:], f8[:], f8[:], f8[:], f8[:])", nopython=True, nogil=True) 6 | def update_diagonal(diffusion, 7 | convection, 8 | source, 9 | diagonal_gradient, 10 | diagonal_laplacian, 11 | diagonal_update): 12 | no_nodes = len(diffusion) 13 | for i in range(1, no_nodes - 1): 14 | diagonal_update[i] = diffusion[i] * diagonal_laplacian[i] + \ 15 | convection[i] * diagonal_gradient[i] + \ 16 | source[i] 17 | 18 | 19 | @nb.jit("(f8[:], f8[:], f8[:], f8[:], f8[:])", nopython=True, nogil=True) 20 | def update_diagonal_lower(diffusion, 21 | convection, 22 | diagonal_lower_gradient, 23 | diagonal_lower_laplacian, 24 | diagonal_lower_update): 25 | no_nodes = len(diffusion) 26 | for i in range(1, no_nodes - 1): 27 | diagonal_lower_update[i - 1] = diffusion[i] * diagonal_lower_laplacian[i - 1] + \ 28 | convection[i] * diagonal_lower_gradient[i - 1] 29 | 30 | 31 | @nb.jit("(f8[:], f8[:], f8[:], f8[:], f8[:])", nopython=True, nogil=True) 32 | def update_diagonal_upper(diffusion, 33 | convection, 34 | diagonal_upper_gradient, 35 | diagonal_upper_laplacian, 36 | diagonal_upper_update): 37 | no_nodes = len(diffusion) 38 | for i in range(1, no_nodes - 1): 39 | diagonal_upper_update[i] = diffusion[i] * diagonal_upper_laplacian[i] + \ 40 | convection[i] * diagonal_upper_gradient[i] 41 | 42 | 43 | @nb.jit("(f8[:], f8[:], f8[:], f8[:])", nopython=True, nogil=True) 44 | def tdr_system_solver(diagonal, 45 | diagonal_lower, 46 | diagonal_upper, 47 | b): 48 | no_nodes = len(diagonal) 49 | x = np.zeros(no_nodes) 50 | gamma = np.zeros(no_nodes) 51 | rho = np.zeros(no_nodes) 52 | gamma[0] = diagonal_upper[0] / diagonal[0] 53 | rho[0] = b[0] / diagonal[0] 54 | 55 | for i in range(1, no_nodes - 1): 56 | gamma[i] = diagonal_upper[i] / (diagonal[i] - diagonal_lower[i - 1] * gamma[i - 1]) 57 | rho[i] = (b[i] - diagonal_lower[i - 1] * rho[i - 1]) / (diagonal[i] - diagonal_lower[i - 1] * gamma[i - 1]) 58 | 59 | x[no_nodes - 1] = (b[no_nodes - 1] - diagonal_lower[no_nodes - 2] * rho[no_nodes - 2]) / \ 60 | (diagonal[no_nodes - 1] - diagonal_lower[no_nodes - 2] * gamma[no_nodes - 2]) 61 | 62 | for i in range(no_nodes - 2, -1, -1): 63 | x[i] = rho[i] - gamma[i] * x[i + 1] 64 | 65 | return x 66 | 67 | 68 | @nb.jit("f8[:](f8[:], f8[:], f8[:], f8[:])", nopython=True, nogil=True) 69 | def apply_tdr(diagonal, 70 | diagonal_lower, 71 | diagonal_upper, 72 | b): 73 | no_nodes = len(diagonal) 74 | y = np.zeros(no_nodes) 75 | y[0] = diagonal[0] * b[0] + diagonal_upper[0] * b[1] 76 | y[no_nodes - 1] = diagonal_lower[no_nodes - 2] * b[no_nodes - 2] + diagonal[no_nodes - 1] * b[no_nodes - 1] 77 | for i in range(0, no_nodes - 1): 78 | y[i] = diagonal_lower[i - 1] * b[i - 1] + diagonal[i] * b[i] + diagonal_upper[i] * b[i + 1] 79 | 80 | return y 81 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/Types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import NewType 3 | from numpy import ndarray 4 | 5 | np_ndarray = NewType('np_ndarray', type(ndarray)) 6 | 7 | 8 | class BoundaryConditionType(Enum): 9 | UNKNOWN = -1, 10 | ZERO_LAPLACIAN = 0, 11 | ZERO_DIFFUSION = 1, 12 | ZERO_GRADIENT = 2, 13 | ROBIN = 3 14 | 15 | def __str__(self): 16 | return self.name 17 | 18 | 19 | class SchemeType(Enum): 20 | UNKNOWN = -1, 21 | IMPLICIT = 0, 22 | EXPLICIT = 1, 23 | THETA = 2 24 | CRANK_NICOLSON = 3 25 | 26 | def __str__(self): 27 | return self.name 28 | 29 | 30 | -------------------------------------------------------------------------------- /Solvers/PDE_Solver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/Solvers/PDE_Solver/__init__.py -------------------------------------------------------------------------------- /Solvers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/Solvers/__init__.py -------------------------------------------------------------------------------- /Tools/Bachelier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import Tools.AnalyticTools 3 | import numba as nb 4 | 5 | from scipy.special import ndtr 6 | 7 | CALL = 'c' 8 | PUT = 'p' 9 | 10 | binary_flag = {CALL: 1, PUT: -1} 11 | phi_hat_c = -0.001882039271 12 | 13 | 14 | @nb.jit("f8(f8)", nopython=True, nogil=True) 15 | def gamma_inc_inv(x): 16 | 17 | return ndtr(x) + Tools.AnalyticTools.normal_pdf(0.0, 1.0, x) / x 18 | 19 | 20 | def bachelier(f, k, t, sigma, flag): 21 | 22 | if f == k: 23 | return sigma * np.sqrt(t) * 1.0 / np.sqrt(2.0 * np.pi) 24 | else: 25 | x = - binary_flag[flag] * (k - f) / (sigma * np.sqrt(t)) 26 | return - binary_flag[flag] * (k - f) * gamma_inc_inv(x) 27 | 28 | 29 | def implied_volatility(price, f, k, t, flag): 30 | 31 | if f == k: 32 | return np.sqrt(2.0 * np.pi / t) * price 33 | 34 | phi_hat_target = - np.abs(price - np.maximum(binary_flag[flag] * (f - k), 0.0)) / np.abs(k - f) 35 | 36 | if phi_hat_target < phi_hat_c: 37 | g = 1.0 / (phi_hat_target - 0.5) 38 | psi_hat_numerator = 0.032114372355 - g * g * ( 39 | 0.016969777977 - g * g * (2.6207332461E-3 - 9.6066952861E-5 * g * g)) 40 | psi_hat_denominator = 1.0 - g * g * (0.6635646938 - g * g * (0.14528712196 - 0.010472855461 * g * g)) 41 | psi_hat = psi_hat_numerator / psi_hat_denominator 42 | x_hat = g * (1.0 / np.sqrt(2.0 * np.pi) + psi_hat * g * g) 43 | 44 | else: 45 | h = np.sqrt(-np.log(-phi_hat_target)) 46 | x_hat_numerator = 9.4883409779 - h * (9.6320903635 - h * (0.58556997323 + 2.1464093351 * h)) 47 | x_hat_denominator = 1.0 - h * (0.65174820867 + h * (1.5120247828 + 6.6437847132e-05 * h)) 48 | x_hat = x_hat_numerator / x_hat_denominator 49 | 50 | q = (gamma_inc_inv(x_hat) - phi_hat_target) / Tools.AnalyticTools.normal_pdf(0.0, 1.0, x_hat) 51 | x_root_numerator = 3.0 * q * x_hat * x_hat * (2.0 - q * x_hat * (2.0 + x_hat * x_hat)) 52 | x_root_denominator = 6.0 + q * x_hat * ( 53 | -12.0 + x_hat * (6.0 * q + x_hat * (-6.0 * q * x_hat * (3.0 + x_hat * x_hat)))) 54 | x_root = x_hat + x_root_numerator / x_root_denominator 55 | 56 | return np.abs(k - f) / np.abs(x_root * np.sqrt(t)) 57 | -------------------------------------------------------------------------------- /Tools/Meshes.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | from typing import Callable, List 15 | from numpy import linspace 16 | 17 | 18 | def uniform_mesh(no_points: int, T0: float, T1: float): 19 | return linspace(T0, T1, no_points).tolist() 20 | 21 | 22 | class Mesh(object): 23 | def __init__(self, 24 | generator: Callable[[int, float, float], List[float]], 25 | T0: float, 26 | T1: float, 27 | no_points: int): 28 | 29 | self._generator = generator 30 | self._T0 = T0 31 | self._T1 = T1 32 | self._no_points = no_points 33 | self._points = generator(no_points, T0, T1) 34 | 35 | @property 36 | def left_boundary(self): 37 | return self._T0 38 | 39 | @property 40 | def right_boundary(self): 41 | return self._T1 42 | 43 | @property 44 | def nodes(self): 45 | return self._points 46 | 47 | def update(self, no_points: int): 48 | self._points = self._generator(no_points, self.left_boundary, self.right_boundary) 49 | -------------------------------------------------------------------------------- /Tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/Tools/__init__.py -------------------------------------------------------------------------------- /VolatilitySurface/IVParametric.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import abc 15 | import numpy as np 16 | 17 | from VolatilitySurface.Tools import SVITools, SABRTools 18 | 19 | 20 | class ParametricImpliedVolatility(object): 21 | __metaclass__ = abc.ABCMeta 22 | 23 | def __init__(self): 24 | pass 25 | 26 | @staticmethod 27 | def get_implied_volatility(*args, f=0.0, k=0.0, t=0.0): 28 | pass 29 | 30 | @staticmethod 31 | def get_variance(*args, f=0.0, k=0.0, t=0.0): 32 | pass 33 | 34 | @staticmethod 35 | def get_gradient_iv_to_parameters(*args, t): 36 | pass 37 | 38 | @staticmethod 39 | def get_derive_to_forward(*args, t): 40 | pass 41 | 42 | 43 | class SVI(ParametricImpliedVolatility): 44 | 45 | def __init__(self): 46 | ParametricImpliedVolatility.__init__(self) 47 | pass 48 | 49 | @staticmethod 50 | def get_variance(*args, f=0.0, k=0.0): 51 | x = np.log(k / f) 52 | var = SVITools.svi_total_imp_var_jit(args[0], args[1], args[2], args[3], args[4], x) 53 | 54 | return var 55 | 56 | @staticmethod 57 | def get_implied_volatility(*args, f=0.0, k=0.0, t=0.0): 58 | return np.sqrt(SVI.get_variance(args[0], args[1], args[2], args[3], args[4], f, k) / t) 59 | 60 | @staticmethod 61 | def svi_total_imp_var(*args, z=0.0): 62 | return SVITools.svi_total_imp_var_jit(args[0], args[1], args[2], args[3], args[4], z) 63 | 64 | @staticmethod 65 | def get_gradient_iv_to_parameters(*args): 66 | return SVITools.get_gradient_svi_iv_to_parameters_jit(args[0], args[1], args[2], args[3], args[4]) 67 | 68 | @staticmethod 69 | def get_derive_to_forward(*args, strike=0.0, t=0.0): 70 | # return SVITools.get_derive_svi_to_forward_jit(args[0], args[1], args[2], args[3], args[4], strike, t) 71 | pass 72 | 73 | 74 | class SABR(ParametricImpliedVolatility): 75 | 76 | def __init__(self): 77 | ParametricImpliedVolatility.__init__(self) 78 | pass 79 | 80 | @staticmethod 81 | def get_implied_volatility(*args, f=0.0, k=0.0, t=0.0): 82 | return SABRTools.sabr_vol_jit(args[0], args[1], args[2], np.log(f / k), t) 83 | 84 | @staticmethod 85 | def get_variance(*args, f=0.0, k=0.0, t=None): 86 | return t * SABR.get_implied_volatility(args[0], args[1], args[2], f, k, t) ** 2 87 | 88 | @staticmethod 89 | def get_gradient_iv_to_parameters(*args, t=0.0): 90 | pass 91 | 92 | @staticmethod 93 | def get_derive_to_forward(*args, t=0.0): 94 | pass 95 | -------------------------------------------------------------------------------- /VolatilitySurface/Tools/ParameterTools.py: -------------------------------------------------------------------------------- 1 | __author__ = 'David Garcia Lorite' 2 | 3 | # 4 | # Copyright 2020 David Garcia Lorite 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 7 | # License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # 12 | # 13 | 14 | import numba as nb 15 | import numpy as np 16 | 17 | 18 | @nb.jit("f8[:](f8,f8,f8,f8,f8, f8, f8)",nopython=True, nogil=True) 19 | def get_modify_parameters(a, b, rho, sigma, m, spot, delta_t_T): 20 | b = b / delta_t_T 21 | m = m + np.log(spot) 22 | a = a / delta_t_T 23 | 24 | y = a 25 | x = m 26 | 27 | slope_l = b * (rho - 1.0) 28 | slope_r = b * (1.0 + rho) 29 | 30 | return np.array([x, y, slope_l, slope_r, sigma]) 31 | 32 | 33 | @nb.jit("f8[:](f8,f8,f8,f8,f8)", nopython=True, nogil=True) 34 | def get_base_parameter_from_term_struct(v_t, b_t, rho_t, x_t, lambda_t): 35 | b = b_t 36 | rho = rho_t 37 | sigma = lambda_t * np.sqrt(1.0 - rho * rho) 38 | m = x_t + rho_t * lambda_t 39 | a = v_t - b * sigma * np.sqrt(1.0 - rho * rho) 40 | 41 | return np.array([a, b, rho, sigma, m]) 42 | 43 | 44 | @nb.jit("f8[:](f8,f8,f8,f8,f8, f8, f8)", nopython=True, nogil=True) 45 | def get_modify_parameters_from_desk_param(x, y, slope_left, slope_right, sigma, spot, delta_t_T): 46 | a = y * delta_t_T 47 | m = x - np.log(spot) 48 | rho = (slope_left - slope_right) / (slope_left + slope_right) 49 | b = 0.5 * delta_t_T * (slope_right - slope_left) 50 | 51 | return np.array([a, b, rho, sigma, m]) 52 | 53 | 54 | @nb.jit("f8(f8,f8,f8,f8)", nopython=True, nogil=True) 55 | def alpha_atm_sabr(rho, v, sigma_atm, t): 56 | a = 1 + t * np.power(v, 2) * (2.0 - 3.0 * np.power(rho, 2)) / 24 57 | multiplier = t * v * rho 58 | val = (-a + np.sqrt(np.power(a, 2) + sigma_atm * multiplier)) / (0.5 * multiplier) 59 | return val 60 | 61 | 62 | @nb.jit("f8(f8[:],f8)", nopython=True, nogil=True) 63 | def nu_sabr(p, t): 64 | # delta_t_bound = 2.0 / 52.0 65 | # if t < delta_t_bound: 66 | # return p[0] + (p[1] + p[2] * delta_t_bound) * np.power(delta_t_bound, - p[3]) 67 | #else: 68 | return p[0] + (p[1] + p[2] * t) * np.power(t, - p[3]) 69 | 70 | 71 | @nb.jit("f8(f8[:],f8)", nopython=True, nogil=True) 72 | def rho_sabr(p, t): 73 | return p[0] + (p[1] + p[2] * t) * np.exp(- p[3] * t) 74 | 75 | 76 | @nb.jit("f8(f8[:],f8)", nopython=True, nogil=True) 77 | def nu_sabr_partial_t(p, t): 78 | # delta_t_bound = 2.0 / 52.0 79 | # if t < delta_t_bound: 80 | # return 0.0 81 | # else: 82 | pow_t = np.power(t, - p[3]) 83 | return p[2] * pow_t - p[3] * (p[1] + p[2] * t) * (pow_t / t) 84 | 85 | 86 | @nb.jit("f8(f8[:],f8)", nopython=True, nogil=True) 87 | def rho_sabr_partial_t(p, t): 88 | return (p[2] - p[3] * (p[1] + p[2] * t)) * np.exp(- p[3] * t) 89 | -------------------------------------------------------------------------------- /VolatilitySurface/Tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/VolatilitySurface/Tools/__init__.py -------------------------------------------------------------------------------- /VolatilitySurface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/VolatilitySurface/__init__.py -------------------------------------------------------------------------------- /dist/PyStochasticVolatility-1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/PyStochasticVolatility/20771be7daabf194d9f10f259e0abebb60ee382e/dist/PyStochasticVolatility-1.0-py3-none-any.whl -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='PyStochasticVolatility', 5 | version='1.0', 6 | packages=['Tools', 'Solvers', 'Solvers.ODE_Solver', 'Solvers.PDE_Solver', 7 | 'Solvers.PDE_Solver.Examples', 'MCPricers', 'MC_Engines', 'MC_Engines.MC_SABR', 'MC_Engines.MC_Heston', 8 | 'MC_Engines.GenericSDE', 'MC_Engines.MC_RBergomi', 'MC_Engines.MC_MixedLogNormal', 9 | 'MC_Engines.MC_LocalVol', 'Instruments', 'AnalyticEngines', 10 | 'AnalyticEngines.FourierMethod', 'AnalyticEngines.FourierMethod.COSMethod', 11 | 'AnalyticEngines.FourierMethod.CharesticFunctions', 'AnalyticEngines.LocalVolatility', 12 | 'AnalyticEngines.LocalVolatility.Hagan', 'AnalyticEngines.LocalVolatility.Dupire', 'AnalyticEngines.VolatilityTools', 13 | 'AnalyticEngines.MalliavinMethod', 'VolatilitySurface', 'VolatilitySurface.Tools', 'FractionalBrownian'], 14 | url='https://github.com/Dagalon/PyStochasticVolatility.git', 15 | license='http://www.apache.org/licenses/LICENSE-2.0', 16 | author='David Garcia Lorite', 17 | author_email='david.garcia.lorite@outlook.com', 18 | description='Financial library creted to give support the book Malliavin Calculus and Stochastic Volatility.' 19 | ) 20 | --------------------------------------------------------------------------------