Risk Management

Volatility Forecasting: Estimating market risk with statistical and implied volatility models under conditions of time varying volatility, leptokurtotic and skewed distributions and leverage effects.



Paul McAteer, MSc, MBA

pcm353@stern.nyu.edu


CONTENTS

  1. Introduction
  2. Volatility Estimation with the Generalised Autoregressive Conditional Heteroskedasticity (GARCH) model
  3. Volatility Estimation with the GJR-GARCH model
  4. VaR Estimation with Implied Volatility
  5. Implied Volatility: Understanding the limitations
  6. Risk metrics derived from Implied Volatility Surface
  7. Determining Implied Risk-Neutral Distributions from the volatility smile with the Breeden-Litzenburger approach
  8. Generating simulated values for the volatility level (and stock price) with the Heston Process
  9. Implementation


1. Introduction

In order to estimate the potential future losses on an option it is necessary to determine the change in the current instrument price arising from a change in the current value of the input variables, that is, changes in the value of the underlying, implied volatility, the discount rate, the expected dividend and time to expiration. For risk management purposes, we refer to these inputs as the risk factors. All the current risk factors are observable except for the implied volatility. Implied volatility must be derived from the current option price and the observable risk factors.

A naive approach to risk management would then involve applying arbitrary stresses to relevant risk factors for each instrument in the portfolio, usually, the spot of the underlying and the implied vol, in order to obtain a new price for each instrument and thus the P&L of each position, which is simply the current position value minus the initial premium incurred to enter the position.The naive approach is obviously unsatisfactory for a number of reasons. Firstly, even on a stand-alone basis, the assumption that the "spot" and "implied vol" risk factors for a single instrument are independent is clearly incorrect. Changes in the implied vols of a particular security tend to exhibits a strong relationship with changes in the spot price of the security.Secondly, the approach should, but often fails to, incorporate reasonable "shocks" to the risk factors, based on empirical or simulated price processes.

Nevertheless, this methodology is applied by brokers to calculate the margining requirements for their counterparties, based on both a so-called "Rules-Based" approach (informed by the "Regulation T" framework) and "Risk-Based" approach (informed by the "Portfolio Margining" regulations). The materialization of collateralisation obligations due to the mechanical application of these simple models has liquidity implications for the holder of the options positions. The likelihood and magnitude of future margining obligations and liquidity demands will however need to take into consideration the expected P&L of the options positions. In other words, an estimation of liquidity risk over a specified time horizon requires a reasonable estimation of the market risk of the portfolio over the same time horizon. A reasonable estimation of market risk of a portfolio must incorporate the expected volatilities (and correlations) of the portfolio's assets. For an options portfolio, this implies estimating the volatilities (and correlations) of the all the risk factors for all the constituent positions.

The virtue and necessity of volatility estimation is not limited to the valuation and risk management of options portfolios. The effective computation of the ex-ante Value-at-Risk of a portfolio of cash equities requires the construction of covariance matrices which incorporate the updated information content available from the statistical distribution of asset price returns or the the implied distribution which can be extracted from traded option prices. The simple standard deviation of historic returns as employed in Markowitz's semimal paper on efficient portfolio formation is inadequate. One characteristic of VaR measues relying on this simple defintion of volaility is a so-called "Ghost VaR" where sustained periods of low volatility have an excessive weight when the variance applies equal weighting to historic observations, which leads to understimation of risk during short episodes of elevated volatility. The "Ghost effect" refers to the VaR computation being haunted by a former low volatility regime and the failure to effectively update the volatility forecast with current price dynamics.


2. Volatility Estimation with the Generalised Autoregressive Conditional Heteroskedasticity (GARCH) model


Recall that simple volatility estimation by statistical means assume equal weights to all returns measured over the period. We know that over 1-day, the mean return is small as compared to standard deviation. If we consider a simple m-period moving average, where $σ_n$ is the volatility of return on day $n$, then with $\bar{\mu } \approx 0$, we have:

$$\sigma ^{2}_n=\frac{1}{m} \sum_{i=1}^{m}{\mu ^{2}_{n-i}}$$
where: $m$ is the number of returns, $\mu$ is return and $\sigma ^{2}$ is the variance. Volatility is the square root of variance.


Some phenomena are systematically observed in asset return time series. A good volatility estimation model should be able to capture most of these empirical "stylised" facts.

Volatility Clustering: The volatility is more likely to be high at time n if it was also high at time n−1. That is, a shock at time n−1 increases not only the variance at time n−1 but also the variance at time n. In other words, the markets are more volatile in some periods, and they are more tranquil in others. The volatilities are clustered in time.

Fat Tails: Return time series generally present fat tails, also known as excess kurtosis, or leptokurtosis. That is, their kurtosis (the fourth central moment normalized by the square of the variance) is usually greater than three, the kurtosis of a Gaussian random variable. In fact, a popular statistical test for the hypothesis of Gaussianity of a distribution, the Jarque-Bera Test, jointly test both if the distribution is symmetric and if the distribution presents kurtosis equal to three. If returns are fat-tailed, the probability of having extreme events (very high or very low returns) is higher than it would be in the normal (Gaussian) case.

Any large return within this n period will elevate the variance or standard deviation of returns until it drops out of the sample. This is often referred to as the""Ghost Effect". To address this effect, we adopt weighting schemes.

One weighting scheme popularized by RiskMetrics is the Exponentially Weighted Moving Average (EWMA) where we weight the squared returns in an exponentially declining fashion. The rate of the exponetial decline of the weights is controlled by the $\lambda$ parameter, often called the persistence parameter, which following the RiskMetrics document is often set at 0.94. The recursive formula for EWMA Volatility is the following:

$$\sigma _{n}^{2}=\lambda \sigma _{n-1}^{2}+(1-\lambda )\mu _{n-1}^{2}$$

where: $\lambda$ is applied to the lagged variance of the previous day and $1 - \lambda$ is applied to the squared return of the previous day. The lower the persitence parameter, $\lambda$ , the greater the weight of recent volatility in this weighting scheme.

Under the assumption that volatility is mean-reverting, the GARCH model adds a third variable, Long Run Variance, denoted by $V_L$ and a third coeffcient, the rate of mean reversion, denoted by $\gamma$. $\beta$ and $\alpha$ subsitute for $\lambda$ and $1 - \lambda$ respectively. The three coeffcients sum to one. Thus:

$$\sigma _{n}^{2}=\gamma V_{L}+\beta \sigma _{n-1}^{2}+\alpha\mu _{n-1}^{2}$$

For notational economy, the long run variance and the rate of mean reversion are often represented by the single input, $\omega$:

$$\sigma _{n}^{2}=\omega+\beta \sigma _{n-1}^{2}+\alpha\mu _{n-1}^{2}$$

Maximum Likeihood Estimation (MLE) is a statistical method used for fitting the data to a model. When using MLE, we first assume a distribution and then seek to determine the model parameters. To estimate GARCH(1,1) parameters, we assume distribution of returns conditional on variance are normally distributed.

To derive ω , α and β, we maximize: $$\sum_{i=1}^{n}{\log \left[ \frac{1}{\sqrt{2\pi \sigma _{i}^{2}}} e^{\frac{-(\mu _{i}-\bar{\mu })^{2}}{2\sigma _{i}^{2}}}\right]}$$


3. Volatility Estimation with the GJR-GARCH model


The GJR model, proposed by Glosten, Jagannathan and Runkle, is a GARCH variant that includes leverage terms for modeling asymmetric volatility clustering. In the GJR formulation, large negative changes are more likely to be clustered than positive changes in line with the the stylized fact that negative shocks at time $t_{-1}$ have a stronger impact in the variance at time $t$ than positive shocks. This asymmetry tends to be called the leverage effect because the increase in volatility was traditionally ascribed to to the increased leverage induced by a negative shock to market value, that is: as market value falls, leverage, $debt/mktvalue$ increases and, therefore, risk also increases.

The GJR-GARCH model relies on two equations for the variance at time $t$, $\epsilon_{t} = R_{t} - \mu_{t}$ following negative and positive expected return i.e.where $\epsilon_{t-1} < 0$ and $\epsilon_{t-1} > 0$.

In the case of a positive surprise, we take the usual GARCH equation:

$$\sigma _{n}^{2}=\omega+\beta \sigma _{n-1}^{2}+\alpha\epsilon_{n-1}^{2}$$

In the case of a negative surprise, the predicted variance should be higher than after a positive surprise, which requires a larger coefficient multiplying the squared prediction error, namely $(\alpha+\gamma)\epsilon_{n-1}$ rather than $\alpha\epsilon_{n-1}$:

$$\sigma _{n}^{2}=\omega+\beta \sigma _{n-1}^{2}+(\alpha+\gamma)\epsilon_{n-1}^{2}$$

We can unify these two formulae in a single equation thus:

$$\sigma _{n}^{2}=\omega+\beta \sigma _{n-1}^{2}+(\alpha+\gamma I_{n-1})\epsilon_{n-1}^{2}$$
Where $I$ is zero if $\epsilon_{t-1} > 0$ and 1 if $\epsilon_{t-1} < 0$

We estimate all the parameters(μ,ω,α,γ,β) simultaneously, by maximizing the log likelihood


4. VaR Estimation with Implied Volatility

Black and Scholes proposed the following stochastic differential equation to model asset prices. Here the Weiner process $W_t$ is a standard Brownian Motion. The values of $S_t$ are log-normally distributed and the returns $dS_t / S_t$ are normally distributed. The instantaneous change in the price of an asset is driven by a deterministic drift component, $(r-q)$, that is, the risk free rate minus the dividend yield in a risk neutral world, and a random component given by the normal random variable and a constant volatility $\sigma$.

$$dS_{t} = (r-q) S_{t}dt + \sigma S_{t} d W_{t}$$

According to the Black-Scholes derivation, we can further state:

$$\frac{\partial V}{\partial t}+ \frac 1{2}{\sigma^2 S^2} \frac{\partial^2 V}{\partial S^2}+ r S \frac{\partial V}{\partial S}= rV$$

Or equivalently:

$$\frac{\partial V}{\partial t}+ \frac 1{2}{\sigma^2 S^2} \frac{\partial^2 V}{\partial S^2}+ r S \frac{\partial V}{\partial S}- rV = 0$$

Where $V$ is the option value

We know that the value of a call option is:

$$C = SN(d_1) - Ke^{-rt}N(d_2)$$

and, the corresponding put option price is: $$P = Ke^{-rt}N(-d_2) - SN(-d_1)$$

Where:

$d_1= \frac{1}{\sigma \sqrt{t}}\left[\ln{\left(\frac{S}{K}\right)} +{\left(r + \frac{\sigma^2}{2}\right)}t\right]$

$d_2= d_1 - \sigma \sqrt{t}$

$S$ is the spot price of the underlying asset

$K$ is the strike price

$r$ is the annualized continuous compounded risk free rate

$σ$ is the volatility of returns of the underlying asset

$t$ is time to maturity (expressed in years)

$N(.)$ is the standard normal cumulative distribution

We can consider call and put equation as a function of the volatility parameter $σ$ . Finding implied volatility thus requires solving the nonlinear problem $f(x)=0$ where $x=σ$ given a starting estimate and $P$ and $C$ are the put and call market prices.

For call options:
$$f(x) = SN(d_1(x)) - Ke^{-rt}N(d_2(x)) - C$$

For put options:
$$f(x) = Ke^{-rt}N(-d_2(x)) - SN(-d_1(x)) -P$$


The underlying assumptions are that returns are normally distributed and the risk-neutral probability density function of $S_T$ is log normal with time-scaled implied volatility informing the shape parameter and a scale parameter informed by the expected return over time to expiry $T$, that is, $ln(S_0) + [r - \frac{\sigma^2}{2}]T$

The risk neutral pdf of $S$ time to expiry $T$: $$g(S_T) = \frac{1}{S \sigma \sqrt{2\pi t}} e^{ \frac{-\left(\ln(S) - \ln(S_0) - \left[r - \frac{\sigma^2}{2}\right] \right)^2}{2 \sigma^2 t}} $$

Evidently, we are assuming a single unique and constant implied volatility to estimate the distribution of stock prices over the period. In reality, implied volatilities vary with both time and strike.


In the Black-Scholes framework, where the underlying follows a log-normal distribution, the risk neutral probability $P_K$ of being above a strike is $N(d_2)$, where $N(.)$ is the standard normal cumulative distribution function and $$\begin{equation*} d_{2} = \frac{Log(\frac{F}{K}) - \frac{\sigma_{K}^{2}}{2}t}{\sigma_{K} \sqrt{t}} \end{equation*}$$

with $F$, being the forward price:
$$F = S_0 e^{rt}$$
Thus, the risk neutral probability of being below a strike is:

$$1 - N(d_2)$$

Which means we can obtain an implied Value-at-Risk from the risk-neutral density function at a desired confidence level.


5. Implied Volatility: Understanding the limitations

Whilst implied volatilities are certainly useful in gauging market expectations of risk, they should be used with caution as as predictive input to model stock returns or generate forward-looking valuations of derivatives for risk management purposes. The the assumptions of constant volatility and normally distributed stock returns (lognormally distributed prices) are violated by the empirical observations of price behaviour in both the cash and options markets, notably:

  • Leptokurtosis. It is observed that the empirical distribution of asset returns is fat-tailed or leptokurtic meaning that the fourth moment about the mean is greater than the same statistic for a normal distribution with the same variance. The normal distribution understates the probability of extreme moves.
  • Volatility Clustering and Persistence. Financial time series reveals alternating periods or regimes of high volatility and periods of low volatility
  • Leverage Effect. An empirical feature of many financial time series is that when the price drops, the future volatility increases. This negative correlation between the financial return and future volatility processes was initially addressed in Black 76 and explained based on financial leverage, or a firm's debt-to-equity ratio: when the price drops, financial leverage increases, the firm becomes riskier, and hence, the future expected volatility increases. The phenomenon is, therefore, traditionally been named the leverage effect. In market lore the same phenomenon is often explained by the fear and greed effect. When the market sentiment is positive and prices are rising, traders are content to hold positions as their P&L s increase. Lower volume trading means lower volatility. When prices start dropping however, traders (and their clients) may panick and seek cover their positions.
  • Term structure of volatility. When we plot the implied volatility against strike and maturity, we obtain an implied volatility surface. If the BSM model assumptions were to hold in reality, the should be able to match all options with a single unique $σ$ input. The implied volatilities would be the same across all $K$ and $τ$ . I The surface would be flat. However, implied volatilities vary with strike and term to expiry. A plot of the implied volatility of an option as a function of maturity reveals the term structure of volatility. This reflects expectations of the evolution of volatility over the time horizon. An upward sloping curve reflects traders expectations of increasing volatility over time and speculative or defensive positioning in accordance with this sentiment. Conversely downward sloping curve reflects expectations of decreasing volatility over time and relatively greater demand for options of shorter maturities versus longer maturities. A humped volatility term structure may arise around key earnings events where volatility is expected to be higher at some interim point in time.
  • Dependence on Moneyness. Implied volatilities vary with strike levels and moneyness. A plot of the implied volatilities options as a function of their strike prices reveals the volatility smile . Essentially the volatility smile shows that options which are further into or out of the money are undervalued by the Black-Scholes formula. This shows that the market expects the options to be exercised with greater probability than indicated by the lognormality assumption. This is a direct consequence of the non-lognormality displayed by the price process discussed in the first point.
  • Skewness. An additional feature in some markets is the fact that this smile is asymmetric resulting in a the so-called volatility skew , or volatility smirk. A a reverse-skew smirk common in equity markets demonstrates a relative demand that is greater for options at lower strikes than at higher strikes. This means that the in-the-money calls and out-of-the-money puts are more expensive than the out-of-the-money calls and in-the-money puts. A popular explanation for the manifestation of the reverse volatility skew is that investors, at least since the crash of 1987, generally assign more probability to the asymmetric and fat-tailed distribution of asset returns due to market crashes than that captured by the gaussian probability distribution function. This results in in higher demand to buy OTM puts for insurance purposes. Another possible explanation is that in-the-money calls have become popular alternatives to outright stock purchases as they offer leverage and, hence, increased ROI. This leads to greater demand for ITM calls and therefore, increased IV. Another possible explanation for the smile in equity options concerns leverage. As a company’s equity declines in value, the company’s leverage increases. This means that the equity becomes more risky and its volatility increases. As a company’s equity increases in value, leverage decreases. The equity then becomes less risky and its volatility decreases. This argument suggests that we can expect the volatility of a stock to be a decreasing function of the stock price.
  • Risk Reversal Spread. Due to the effects of skewness described above, a Risk Reversal Spread exists because OTM puts typically have higher implied volatilities (and are thus more expensive) than similar OTM calls. The relative bearishness or bullishness of market sentiment can be monitored by tracking the expansion or contraction of this spread.
  • Butterfly Spread. If Risk Reversal Spread reflects reflects the skewness of the underlying return distribution, the butterfly spread reflects the excess kurtosis of the distribution. As demand for, and the implied volatility of, OTM options increases relative to ATM options, the butterfly spread will increase. This, in turn is reflective of market participations increased expectations of extreme moves (fat tails).

  • 6. Risk metrics derived from Implied Volatility Surface

    The asymmetry of the option-implied distribution of asset returns allows us extract a number of useful metrics which provide an indication of the bullish or bearish sentiment of market participants.

  • Call-put implied volatility spread. For American options (on single name stocks), there only exists a put-call inequality. A high call-put implied volatility spread $(CVol–PVol>0)$ implies that the call option prices exceed the levels implied by the put option prices and the put-call parity. Ofek, Richardson, and Whitelaw (2004) argue that such violations could arise if irrational investors move stock prices (but not options prices) away from their fundamental values. If this is true, then stocks with relatively more expensive calls (stocks with high CVol–PVol) are expected to generate higher returns than stocks with relatively more expensive puts (stocks with low CVol–PVol). Since higher CVol–PVol spread indicates that call options are more expensive than put options, higher spread suggests that investors expect the stock price to be higher in the future. Put differently, the call-put implied volatility spread reflects expected future price increase of the underlying stock.
  • Risk-Reversal Spread (Implied Skew). Equity Skew measures the difference in the implied volatility of Out-of-the-Money Puts versus similar Out-of-the-Money Calls. It is an indication of market participant's assumptions regading the asymmetrical nature of the underlying security return distribution. High skew means excessive demand for OTM puts relative to calls and is considered an expression of bearish sentiment arising from speculative bets on, or protective hedges against, downward moves in the underlying. Flat skew indicates less demand for puts (or greater demand for calls). The options will have either comparable moneyness or delta. The skew can be quoted on an absolute or "normalised" basis. To normalize, we divide the absolute differences by either the the ATM volatility or 50-delta implied volatility. For example:

    $${AbsEquitySkew}_{5\%}=\ {IV}_{95\%\ OTM\ PUT}\ -\ {IV}_{105\%\ OTM\ CALL}$$
    $${NormEquitySkew}_{5\%}=\ \frac{{IV}_{95\%\ OTM\ PUT}\ -\ {IV}_{105\%\ OTM\ CALL}}{{IV}_{ATM\ }}$$

  • Butterfly Spread (Implied Kurtosis). The Butterfly Spread compares the difference in the implied volatility of OTM Puts and Calls versus ATM implied volatility. The Butterfly spread is an indication of market participant's assumptions regading the "fat tailedness", or formally, the leptokurtosis of the underlying security return distribution. The higher the Butterfly Spread, the greater the assumption of risk concentrated in the tail of the distribution, that is, the greater the speculativity or hedging activity focused on extreme moves in the underlying i.e. the greater the demand for OTM puts and calls relative to that for ATM options.

    $${AbsBFsprd}_{5\%}= [({IV}_{95\%\ OTM\ PUT}\ +\ {IV}_{105\%\ OTM\ CALL})\div 2] \ -\ {{IV}_{ATM\ }}$$
    $${NormBFsprd}_{5\%}= \ \frac{0.5({IV}_{95\%\ OTM\ PUT}\ +\ {IV}_{105\%\ OTM\ CALL})\ -\ {{IV}_{ATM\ }}}{{IV}_{ATM\ }}$$

  • 7. Determining Implied Risk-Neutral Distributions from the volatility smile with the Breeden-Litzenburger approach

    The price of a European Call option with strike K, as with any asset price, is simply the discounted value of the expected return:

    $$ c\ =\ e^{-rt}\int_0^{\infty{}}max\left(S_T\ -K\ ,\ 0\right)g\left(S_T\right)dS_T $$

    where r is the risk-free rate, $\left(S_T\right)$is the asset price at time T, $g$is the risk-neutral probability density function of $\left(S_T\right)$.

    Equivalently, removing the zero term: $$ c\ =\ e^{-rt}\int_K^{\infty{}}\left(S_T\ -\ K\right)g\left(S_T\right)dS_T $$

    In other words the fair value for the call price is the weighted sum of all possible outcomes multiplied by their probability of occurring.

    If we differentiate the call price function with respect to the strike price, we obtain:

    $$ \frac{\partial{}c}{\partial{}K}=\ e^{-rt}\int_K^{\infty{}}g\left(S_T\right)dS_T $$

    Differentiating the call price function a second time with respect to the strike price, gives:

    $$ \frac{{\partial{}}^2c}{\partial{}K^2}=\ e^{-rt}\ g\left(K\right) $$

    Which implies:

    $$ g\left(K\right)=\ e^{-rt}\ \frac{{\partial{}}^2c}{\partial{}K^2}\ \ $$

    Thus the risk-neutral density can be obtained from the discounted second derivative of the call price function.

    This result allows risk-neutral probability distributions to be estimated from volatility smiles. Supposing that $c_1$,$\ c_2,c_3$ are the prices of T-year European call options with strike prices of respectively where is a small value. An estimate of is obtained by approximating the discounted second derivative:

    $$ g\left(K\right)\approx{}\ e^{-rt}\ \frac{\left(c_1-c_2\right)+\left(c_3-c_2\right)}{{\delta{}}^2}\ \ \approx{}\ e^{-rt}\ \frac{\left(c_1+c_3\right)-\left(2c_2\right)}{{\delta{}}^2}\ \ $$

    A perhaps more intuitive way to understand the above is to view the combination of the four positions -- long $c_1$,$\ c_3$ with strikes of $K-\delta{}\ ,\ K+\delta{}$and short 2 $c_2$ with strikes of -- as a butterfly spread.

    Considering the payoff triangle of the butterfly spread, we note that it has the dimensions of height, $\delta{}$, reflecting the maximum payoff and width $2\delta{}$. The area under the spike therefore is: $$ (0.5\ *\ 2\delta{})\ *\ \delta{}=\ {\delta{}}^2 $$

    Dividing the costs of these trades $\left(c_1+c_3\right)-\left(2c_2\right)\ $by their payoffs ${\delta{}}^2$, and adjusting for the time value of money, yields the future probability distribution of the stock as priced by the options market:

    $$ g\left(K\right)\ =\ e^{-rt}\ \frac{\left(c_1+c_3\right)-\left(2c_2\right)}{{\delta{}}^2}\ \ $$

    Rearranging, we note that the price of the Butterfly Spread strategy is of course , the discounted value $e^{-rt}$ of the expected return: $$ e^{-rt}\ g\left(K\right){\delta{}}^2\ =\ \ \left(c_1+c_3\right)-\left(2c_2\right)\ \ $$


    8. Generating simulated values for the volatility level (and stock price) with the Heston Process

    Given the restrictive nature of the Black-Scholes framework, a number of extensions to the model have been proposed to reflect the empirical evidence which indicates that volatility in asset prices is non-normal, time-varying, dependent on stock price dynamics notably in the form of the leverage effect as well as to reconcile the fact that the volatilities implied by traded option prices exhibit a term structure (dependence on time to expiry) and a volatility smirk (dependence on moneyness).


    Practitioners came to focus on stochastic volatility models. These models assume that the asset price and its volatility are both assumed to be random processes and can change over time. The most popular stochastic volatility model was introduced by Heston. It factors in a possible correlation between a stock's price and its volatility, it conveys volatility as reverting to the mean, it gives a closed-form solution for option pricing and it does not require that stock price follow a lognormal probability distribution.

    In his influential paper Heston he proposed that that the risk neutral dynamics of the asset price are governed by the following system of stochastic differential equations (SDEs):

    $$dS_{t} = (r-q) S_{t}dt + V_{t} S_{t} dW_{t}$$
    $$dV_{t} = \kappa (\theta -V_{t}) dt + \xi \sqrt{V_{t}} dZ_{t}$$
    $$dW_{t}, dZ_{t} = \rho dt$$

    Where $S_{t}$is the price of a dividend paying stock at time $t$ and $V_{t}$ is its instantaneous variance. The parameter $r$ is the risk-free interest rate, and $q$ is the dividend yield. Both are assumed to be constant over time. $\kappa$ is the rate of mean reversion to the mean reversion level $\theta$, representing long run variance and and $\xi$ is the so-called volatility of volatility. $W_{t}$ and $Z_{t}$ are two correlated Brownian motions with $dW_{t}, dZ_{t} = \rho dt$ where $\rho$ is the correlation coefficient.

    This mean-reverting variance process is of course square-root diffusion the process proposed by Cox, Ingersoll and Ross in their 1985 paper to model the short interest rate.

    In the Heston model shocks to the asset price and volatility process may be negatively correlated, as empirical evidence suggests, meaning that a down move in the asset is accompanied by an up move in volatility and vice versa. This allows us to account for the aforementioned stylized fact called the leverage effect, which in essence states that volatility goes up in times of stress (declining markets) and goes down in times of a bull market (rising markets). Within the context of model parametrization, the more negatively correlated the stock and vol processes, the more negatively skewed the implied distribution of stock prices. If we were to reverse-engineer the Black-Scholes implied vols from the option prices generated by the Heston Process, one would expect a more pronounced risk reversal spread, that is, a greater difference between the implied vols at lower stock prices and those at higher prices. This makes intuitive sense given that we assuming a higher leverage effect by inputting a more negative correlation coefficient. A greater vol-of-vol parameter results in a higher kurtosis in the implied distribution of stock prices. Again, if we were to reverse-engineer the Black-Scholes implied vols from the option prices generated by the Heston Process, one would expect a more pronounced volatility smile. A higher volatility smile increases the the implied volatility curvature resulting, ceteris paribus, in higher butterfly spreads. A higher mean reversion factor implies a faster rate of reversion to long term vol, which in turn implies a flatter volatility term structure.


    Calibration of the Heston Model: Heston allows that "One could estimate $\theta$ and other parameters by using values implied by option prices. Alternatively, one could estimate $\theta$ and $\kappa$ from the true spot-price process" It is computationally efficient to estimate the parameters with the quantlib process. Here, the calibration of the model is the process of seeking the model parameters for which the model results best match the market option data. This data usually consists of Black-Scholes implied volatilities derived from market quoted prices. Calibrating the Heston model is equivalent to solving the non-linear constrained optimization problem: Minimize the error between the market quotes of options and the options price estimated by Heston model. The object function could be the sum of squared differences.

    Formally, Using the risk-neutral measure, the Heston model has five unknown parameters:

    $$\Omega = \lbrace V_{t} ,\theta , \kappa , \xi , \rho \rbrace $$

    By calibrating these parameters values, we seek to obtain an evolution for the underlying asset that is consistent with the current prices of plain vanilla options. In order to find the optimal parameter set, $ {\Omega }$, we need to (i) define a measure to quantify the distance between model and market prices; and (ii) run an optimization scheme to determine the parameter values that minimize such distance.

    $$\min \sum_{i=1}^{N}{\frac{1}{N}}\lbrack C_{i}^{\Omega }(K_{i},T_{i})-C_{i}^{Mkt}(K_{i},T_{i})\rbrack ^{2}$$

    Where $C_{i}^{\Omega }(K_{i},T_{i})$ are the option values using the parameter set , for all strikes and expiries, and $C_{i}^{Mkt}(K_{i},T_{i})$ are the market observed option price, for all strikes and expiries

    9. IMPLEMENTATION



    A) Comparing S&P's GARCH volatility and VIX

    1) Import Libraries

    In [1]:
    # Standard Python libraries
    import pandas as pd
    import numpy as np
    
    # For plotting
    %matplotlib inline
    import matplotlib.pyplot as plt
    from matplotlib import cm
    from mpl_toolkits.mplot3d import Axes3D
    plt.style.use('seaborn')
    from matplotlib.pyplot import rcParams
    rcParams['figure.figsize'] = 16, 8
    
    # Import arch library
    from arch import arch_model
    
    import warnings
    warnings.filterwarnings('ignore')
    

    2) Retrieve Price Data

    In [2]:
    # Load locally stored data
    df = pd.read_csv('vol.csv', parse_dates=True, index_col=0, dayfirst=True)
    
    # Check first 5 values 
    df.head()
    
    Out[2]:
    VIX S&P 500
    DATE
    1990-01-02 18.19 386.162
    1990-01-03 18.19 385.171
    1990-01-04 19.22 382.018
    1990-01-05 20.11 378.299
    1990-01-08 20.26 380.039

    3) Visualize Index Price and VIX Price

    In [3]:
    figure, axis_1 = plt.subplots()
    
    axis_1.plot(df['S&P 500'], color='green', label='S&P 500')
    axis_1.legend(['S&P 500'],loc='best')
    plt.title('Prices')
    plt.grid(True)
    
    axis_2 = axis_1.twinx()
    axis_2.plot(df['VIX'], color='red', label='VIX')
    axis_2.legend(['VIX'],loc='best');
    

    4) Calculate log returns of index

    In [4]:
    returns = np.log(df['S&P 500']).diff().fillna(0)
    

    5) Visualize daily log returns

    In [5]:
    # Visualize S&P Index daily returns
    plt.plot(returns, color='orange')
    plt.title('S&P 500 Index Returns')
    plt.grid(True)
    

    4) Define and fit models

    In [6]:
    #Define the model (Normal Distribution)
    am_n = arch_model(returns, mean = 'zero', vol='Garch', p=1, o=0, q=1, dist='Normal')
    #Fit the model
    garch1_1 = am_n.fit()
    
    #Define the model (t-Distribution)
    am_t = arch_model(returns, mean = 'zero', vol='Garch', p=1, o=0, q=1, dist='StudentsT')
    tgarch1_1 = am_t.fit()
    
    Iteration:      1,   Func. Count:      5,   Neg. LLF: -8585.448810648937
    Iteration:      2,   Func. Count:     14,   Neg. LLF: -8586.069358190143
    Iteration:      3,   Func. Count:     20,   Neg. LLF: -8588.393939229207
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: -8588.393940307446
                Iterations: 7
                Function evaluations: 20
                Gradient evaluations: 3
    Iteration:      1,   Func. Count:      6,   Neg. LLF: -8645.457530372776
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: -8645.457530371184
                Iterations: 5
                Function evaluations: 6
                Gradient evaluations: 1
    

    5) Obtain Model Output

    In [7]:
    print(garch1_1)
    
                           Zero Mean - GARCH Model Results                        
    ==============================================================================
    Dep. Variable:                S&P 500   R-squared:                       0.000
    Mean Model:                 Zero Mean   Adj. R-squared:                  0.000
    Vol Model:                      GARCH   Log-Likelihood:                8588.39
    Distribution:                  Normal   AIC:                          -17170.8
    Method:            Maximum Likelihood   BIC:                          -17153.3
                                            No. Observations:                 2528
    Date:                Mon, Jul 26 2021   Df Residuals:                     2525
    Time:                        18:54:16   Df Model:                            3
                                  Volatility Model                              
    ============================================================================
                     coef    std err          t      P>|t|      95.0% Conf. Int.
    ----------------------------------------------------------------------------
    omega      1.6961e-06  1.577e-09   1075.827      0.000 [1.693e-06,1.699e-06]
    alpha[1]       0.0901  1.911e-02      4.715  2.414e-06   [5.266e-02,  0.128]
    beta[1]        0.8920  1.575e-02     56.640      0.000     [  0.861,  0.923]
    ============================================================================
    
    Covariance estimator: robust
    
    In [8]:
    print(tgarch1_1)
    
                              Zero Mean - GARCH Model Results                           
    ====================================================================================
    Dep. Variable:                      S&P 500   R-squared:                       0.000
    Mean Model:                       Zero Mean   Adj. R-squared:                  0.000
    Vol Model:                            GARCH   Log-Likelihood:                8645.46
    Distribution:      Standardized Student's t   AIC:                          -17282.9
    Method:                  Maximum Likelihood   BIC:                          -17259.6
                                                  No. Observations:                 2528
    Date:                      Mon, Jul 26 2021   Df Residuals:                     2524
    Time:                              18:54:16   Df Model:                            4
                                  Volatility Model                              
    ============================================================================
                     coef    std err          t      P>|t|      95.0% Conf. Int.
    ----------------------------------------------------------------------------
    omega      1.5885e-06  3.789e-09    419.243      0.000 [1.581e-06,1.596e-06]
    alpha[1]       0.1000  1.476e-02      6.776  1.239e-11   [7.107e-02,  0.129]
    beta[1]        0.8800  1.405e-02     62.647      0.000     [  0.852,  0.908]
                                  Distribution                              
    ========================================================================
                     coef    std err          t      P>|t|  95.0% Conf. Int.
    ------------------------------------------------------------------------
    nu             6.6396      0.327     20.302  1.228e-91 [  5.999,  7.281]
    ========================================================================
    
    Covariance estimator: robust
    

    6) Visualize Historic Annualized Conditional Vol

    In [9]:
    # Plot annualised vol
    fig1 = garch1_1 .plot(annualize='D')
    
    In [10]:
    fig2 = tgarch1_1.plot(annualize='D')
    
    In [11]:
    garch1_1.conditional_volatility*np.sqrt(252)
    
    Out[11]:
    DATE
    1990-01-02    0.158414
    1990-01-03    0.151040
    1990-01-04    0.144663
    1990-01-05    0.143631
    1990-01-08    0.144926
                    ...   
    1999-12-27    0.137821
    1999-12-28    0.131863
    1999-12-29    0.126259
    1999-12-30    0.122597
    1999-12-31    0.117670
    Name: cond_vol, Length: 2528, dtype: float64
    In [12]:
    tgarch1_1.conditional_volatility*np.sqrt(252)
    
    Out[12]:
    DATE
    1990-01-02    0.158157
    1990-01-03    0.149707
    1990-01-04    0.142441
    1990-01-05    0.141271
    1990-01-08    0.142740
                    ...   
    1999-12-27    0.135294
    1999-12-28    0.128556
    1999-12-29    0.122259
    1999-12-30    0.118229
    1999-12-31    0.112755
    Name: cond_vol, Length: 2528, dtype: float64

    7) Visualize Historic Annualized Conditional Vol vs long run vol

    In [13]:
    # Model params
    garch1_1.params
    
    Out[13]:
    omega       0.000002
    alpha[1]    0.090115
    beta[1]     0.892040
    Name: params, dtype: float64
    In [14]:
    # long run variance from model forecast
    lrv = garch1_1.params[0]/(1-garch1_1.params[1]-garch1_1.params[2])
    
    # long run variance
    np.sqrt(lrv*252)*100
    
    Out[14]:
    15.476530711084429
    In [15]:
    plt.plot(garch1_1.conditional_volatility*np.sqrt(252), color='blue')
    
    plt.axhline(y=np.sqrt(lrv*252), color='red');
    

    7) Visualize Historic Annualized Conditional Vol vs VIX

    In [16]:
    # Visualise GARCH volatility and VIX
    plt.title('Annualized Volatility')
    plt.plot(returns.index, garch1_1.conditional_volatility*np.sqrt(252)*100, color='blue', label='GARCH')
    plt.plot(returns.index, df['VIX'], color='orange', label = 'VIX')
    plt.legend(loc=2)
    plt.grid(True)
    

    8) Forecast Volatility

    In [17]:
    # Forecast for next 60 days
    model_forecast = garch1_1.forecast(horizon=60)
    
    In [18]:
    # Subsume forecast values into a dataframe
    forecast_df = pd.DataFrame(np.sqrt(model_forecast.variance.dropna().T *252)*100)
    forecast_df.columns = ['Cond_Vol']
    forecast_df.head()
    
    Out[18]:
    Cond_Vol
    h.01 11.411012
    h.02 11.496164
    h.03 11.579188
    h.04 11.660155
    h.05 11.739133
    In [19]:
    # Plot volatility forecast over a 60-day horizon
    plt.plot(forecast_df, color='blue')
    plt.axhline(y=np.sqrt(lrv*252)*100, color='red',)
    plt.xlim(0,60)
    plt.xticks(rotation=90)
    plt.xlabel('Horizon (in days)')
    plt.ylabel('Volatility (%)')
    plt.title('Volatility Forecast : 60-days Ahead');
    plt.grid(True)
    


    B) Comparing S&P's GJR GARCH volatility and VIX

    In [20]:
    #Define the model (Normal Distribution)
    am_lev = arch_model(returns, mean = 'zero', vol='Garch', p=1, o=1, q=1, dist='Normal')
    #Fit the model
    GJRgarch1_1 = am_lev.fit()
    
    Iteration:      1,   Func. Count:      6,   Neg. LLF: -8618.017630526512
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: -8618.017621644634
                Iterations: 5
                Function evaluations: 6
                Gradient evaluations: 1
    
    In [21]:
    print(GJRgarch1_1)
    
                         Zero Mean - GJR-GARCH Model Results                      
    ==============================================================================
    Dep. Variable:                S&P 500   R-squared:                       0.000
    Mean Model:                 Zero Mean   Adj. R-squared:                  0.000
    Vol Model:                  GJR-GARCH   Log-Likelihood:                8618.02
    Distribution:                  Normal   AIC:                          -17228.0
    Method:            Maximum Likelihood   BIC:                          -17204.7
                                            No. Observations:                 2528
    Date:                Mon, Jul 26 2021   Df Residuals:                     2524
    Time:                        18:54:18   Df Model:                            4
                                   Volatility Model                              
    =============================================================================
                     coef    std err          t      P>|t|       95.0% Conf. Int.
    -----------------------------------------------------------------------------
    omega      1.5885e-06  4.804e-09    330.677      0.000  [1.579e-06,1.598e-06]
    alpha[1]       0.0100  7.168e-03      1.395      0.163 [-4.049e-03,2.405e-02]
    gamma[1]       0.1000  6.632e-02      1.508      0.132   [-2.998e-02,  0.230]
    beta[1]        0.9200  2.677e-02     34.366 8.183e-259      [  0.868,  0.972]
    =============================================================================
    
    Covariance estimator: robust
    
    In [22]:
    # Plot annualised vol
    fig2 = GJRgarch1_1 .plot(annualize='D')
    
    In [23]:
    GJRgarch1_1.conditional_volatility*np.sqrt(252)
    
    Out[23]:
    DATE
    1990-01-02    0.158157
    1990-01-03    0.153012
    1990-01-04    0.148738
    1990-01-05    0.150421
    1990-01-08    0.154497
                    ...   
    1999-12-27    0.114769
    1999-12-28    0.111976
    1999-12-29    0.109252
    1999-12-30    0.106883
    1999-12-31    0.104458
    Name: cond_vol, Length: 2528, dtype: float64
    In [24]:
    # Model params
    GJRgarch1_1.params
    
    Out[24]:
    omega       0.000002
    alpha[1]    0.010000
    gamma[1]    0.100000
    beta[1]     0.920000
    Name: params, dtype: float64
    In [25]:
    # long run variance from model forecast
    lrv_GJR = GJRgarch1_1.params[0]/(1-GJRgarch1_1.params[1]- (GJRgarch1_1.params[2]/2) - GJRgarch1_1.params[3])
    
    # long run variance
    np.sqrt(lrv_GJR*252)*100
    
    Out[25]:
    14.147324575938763
    In [26]:
    plt.plot(GJRgarch1_1.conditional_volatility*np.sqrt(252), color='blue')
    
    plt.axhline(y=np.sqrt(lrv_GJR*252), color='red')
    
    Out[26]:
    <matplotlib.lines.Line2D at 0x16ea1b19550>
    In [27]:
    # Visualise GARCH volatility and VIX
    plt.title('Annualized Volatility')
    plt.plot(returns.index, GJRgarch1_1.conditional_volatility*np.sqrt(252)*100, color='blue', label='GJR - GARCH')
    plt.plot(returns.index, df['VIX'], color='orange', label = 'VIX')
    plt.legend(loc=2)
    plt.grid(True)
    
    In [28]:
    # Forecast for next 60 days
    model_forecastGJR = GJRgarch1_1.forecast(horizon=60)
    
    In [29]:
    # Subsume forecast values into a dataframe
    GJRforecast_df = pd.DataFrame(np.sqrt(model_forecastGJR.variance.dropna().T *252)*100)
    GJRforecast_df.columns = ['Cond_Vol']
    GJRforecast_df.head()
    
    Out[29]:
    Cond_Vol
    h.01 10.230262
    h.02 10.323179
    h.03 10.413434
    h.04 10.501131
    h.05 10.586369


    C) Comparing S&P's GJR-GARCH and GARCH volatilities and VIX

    In [30]:
    # Plot volatility forecast over a 60-day horizon
    plt.plot(GJRforecast_df, color='blue',label='GJR - GARCH',)
    plt.plot(forecast_df, color='green',label='GARCH')
    plt.axhline(y=np.sqrt(lrv_GJR*252)*100, color='blue',label='Long Run Vol:GJR - GARCH',
                linestyle='dashed',linewidth=4)
    plt.axhline(y=np.sqrt(lrv*252)*100, color='green',label='Long Run Vol:GARCH',
                linestyle='dashed',linewidth=4)
    plt.xlim(0,60)
    plt.xticks(rotation=90)
    plt.xlabel('Horizon (in days)')
    plt.ylabel('Volatility (%)')
    plt.title('Volatility Forecast : 60-days Ahead');
    plt.legend(loc=4)
    plt.grid(True)
    


    4. GARCH model performance during Great Financial Crisis

    In [31]:
    import yfinance as yf
    
    SECURITIES = ['^GSPC', '^VIX']
    START_DATE = '2005-07-18'
    END_DATE = '2010-08-13'
    
    raw_df = yf.download(SECURITIES, start=START_DATE, 
                     end=END_DATE, adjusted=True)
    print(f'Downloaded {raw_df.shape[0]} rows of data.')
    
    [*********************100%***********************]  2 of 2 completed
    Downloaded 1278 rows of data.
    
    In [32]:
    df = raw_df['Adj Close']
    df = df.dropna()
    df.columns = ['S&P 500','VIX']
    df.head()
    
    Out[32]:
    S&P 500 VIX
    Date
    2005-07-18 1221.130005 10.77
    2005-07-19 1229.349976 10.45
    2005-07-20 1235.199951 10.23
    2005-07-21 1227.040039 10.97
    2005-07-22 1233.680054 10.52
    In [33]:
    figure, axis_1 = plt.subplots()
    
    axis_1.plot(df['S&P 500'], color='green', label='S&P 500')
    axis_1.legend(['S&P 500'],loc='best')
    plt.title('Prices')
    plt.grid(True)
    
    axis_2 = axis_1.twinx()
    axis_2.plot(df['VIX'], color='red', label='VIX')
    plt.legend(loc=2)
    
    Out[33]:
    <matplotlib.legend.Legend at 0x16e9f2f8160>

    4) Calculate log returns of index

    In [34]:
    returns = np.log(df['S&P 500']).diff().fillna(0)
    

    5) Visualize daily returns

    In [35]:
    # Visualize S&P Index daily returns
    plt.plot(returns, color='orange')
    plt.title('S&P 500 Index Returns')
    plt.grid(True)
    

    4) Define and fit models

    In [36]:
    #Define the model (Normal Distribution)
    am_n = arch_model(returns, mean = 'zero', vol='Garch', p=1, o=0, q=1, dist='Normal')
    #Fit the model
    garch1_1 = am_n.fit()
    
    #Define the model (t-Distribution)
    am_t = arch_model(returns, mean = 'zero', vol='Garch', p=1, o=0, q=1, dist='StudentsT')
    tgarch1_1 = am_t.fit()
    
    Iteration:      1,   Func. Count:      5,   Neg. LLF: -3923.824368317185
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: -3923.824378198754
                Iterations: 5
                Function evaluations: 5
                Gradient evaluations: 1
    Iteration:      1,   Func. Count:      6,   Neg. LLF: -3946.983006033869
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: -3946.983006059958
                Iterations: 5
                Function evaluations: 6
                Gradient evaluations: 1
    

    5) Obtain Model Output

    In [37]:
    print(garch1_1)
    
                           Zero Mean - GARCH Model Results                        
    ==============================================================================
    Dep. Variable:                S&P 500   R-squared:                       0.000
    Mean Model:                 Zero Mean   Adj. R-squared:                  0.001
    Vol Model:                      GARCH   Log-Likelihood:                3923.82
    Distribution:                  Normal   AIC:                          -7841.65
    Method:            Maximum Likelihood   BIC:                          -7826.19
                                            No. Observations:                 1278
    Date:                Mon, Jul 26 2021   Df Residuals:                     1275
    Time:                        18:54:22   Df Model:                            3
                                  Volatility Model                              
    ============================================================================
                     coef    std err          t      P>|t|      95.0% Conf. Int.
    ----------------------------------------------------------------------------
    omega      4.8307e-06  1.061e-12  4.553e+06      0.000 [4.831e-06,4.831e-06]
    alpha[1]       0.1000  5.917e-04    169.005      0.000   [9.884e-02,  0.101]
    beta[1]        0.8800  5.258e-03    167.355      0.000     [  0.870,  0.890]
    ============================================================================
    
    Covariance estimator: robust
    
    In [38]:
    print(tgarch1_1)
    
                              Zero Mean - GARCH Model Results                           
    ====================================================================================
    Dep. Variable:                      S&P 500   R-squared:                       0.000
    Mean Model:                       Zero Mean   Adj. R-squared:                  0.001
    Vol Model:                            GARCH   Log-Likelihood:                3946.98
    Distribution:      Standardized Student's t   AIC:                          -7885.97
    Method:                  Maximum Likelihood   BIC:                          -7865.35
                                                  No. Observations:                 1278
    Date:                      Mon, Jul 26 2021   Df Residuals:                     1274
    Time:                              18:54:22   Df Model:                            4
                                  Volatility Model                              
    ============================================================================
                     coef    std err          t      P>|t|      95.0% Conf. Int.
    ----------------------------------------------------------------------------
    omega      4.8307e-06  1.905e-09   2536.007      0.000 [4.827e-06,4.834e-06]
    alpha[1]       0.1000  4.018e-03     24.888 1.010e-136   [9.212e-02,  0.108]
    beta[1]        0.8800  7.517e-03    117.071      0.000     [  0.865,  0.895]
                                  Distribution                              
    ========================================================================
                     coef    std err          t      P>|t|  95.0% Conf. Int.
    ------------------------------------------------------------------------
    nu             8.6231      1.904      4.529  5.934e-06 [  4.891, 12.355]
    ========================================================================
    
    Covariance estimator: robust
    

    6) Visualize Historic Annualized Conditional Vol

    In [39]:
    # Plot annualised vol
    fig1 = garch1_1 .plot(annualize='D')
    
    In [40]:
    fig2 = tgarch1_1.plot(annualize='D')
    
    In [41]:
    garch1_1.conditional_volatility*np.sqrt(252)
    
    Out[41]:
    Date
    2005-07-18    0.092398
    2005-07-19    0.093436
    2005-07-20    0.100171
    2005-07-21    0.103031
    2005-07-22    0.108009
                    ...   
    2010-08-06    0.189114
    2010-08-09    0.181760
    2010-08-10    0.176191
    2010-08-11    0.171575
    2010-08-12    0.218430
    Name: cond_vol, Length: 1278, dtype: float64
    In [42]:
    tgarch1_1.conditional_volatility*np.sqrt(252)
    
    Out[42]:
    Date
    2005-07-18    0.092398
    2005-07-19    0.093436
    2005-07-20    0.100171
    2005-07-21    0.103031
    2005-07-22    0.108009
                    ...   
    2010-08-06    0.189114
    2010-08-09    0.181760
    2010-08-10    0.176191
    2010-08-11    0.171575
    2010-08-12    0.218430
    Name: cond_vol, Length: 1278, dtype: float64

    7) Visualize Historic Annualized Conditional Vol vs long run vol

    In [43]:
    # Model params
    garch1_1.params
    
    Out[43]:
    omega       0.000005
    alpha[1]    0.100000
    beta[1]     0.880000
    Name: params, dtype: float64
    In [44]:
    # long run variance from model forecast
    lrv = garch1_1.params[0]/(1-garch1_1.params[1]-garch1_1.params[2])
    
    daily_lrvol = np.sqrt(lrv)*100
    
    # long run variance
    ann_lrvol = np.sqrt(lrv*252)*100
    
    lrv, daily_lrvol, ann_lrvol
    
    Out[44]:
    (0.00024153726654988433, 1.5541469253255442, 24.671317591602367)
    In [45]:
    plt.plot(garch1_1.conditional_volatility*np.sqrt(252), color='blue')
    
    plt.axhline(y=np.sqrt(lrv*252), color='red')
    
    Out[45]:
    <matplotlib.lines.Line2D at 0x16e9f99ba90>

    7) Visualize Historic Annualized Conditional Vol vs VIX

    In [46]:
    # Visualise GARCH volatility and VIX
    plt.title('Annualized Volatility')
    plt.plot(returns.index, garch1_1.conditional_volatility*np.sqrt(252)*100, color='blue', label='GARCH')
    plt.plot(returns.index, df['VIX'], color='orange', label = 'VIX')
    plt.legend(loc=2)
    plt.grid(True)
    

    8) Forecast Volatility

    In [47]:
    # Forecast for next 60 days
    model_forecast = garch1_1.forecast(horizon=60)
    
    In [48]:
    # Subsume forecast values into a dataframe
    forecast_df = pd.DataFrame(np.sqrt(model_forecast.variance.dropna().T *252)*100)
    forecast_df.columns = ['Cond_Vol']
    forecast_df.head()
    
    Out[48]:
    Cond_Vol
    h.01 20.961043
    h.02 21.041661
    h.03 21.120368
    h.04 21.197217
    h.05 21.272260
    In [49]:
    # Plot volatility forecast over a 60-day horizon
    plt.plot(forecast_df, color='blue')
    plt.axhline(y=np.sqrt(lrv*252)*100, color='red',)
    plt.xlim(0,60)
    plt.xticks(rotation=90)
    plt.xlabel('Horizon (in days)')
    plt.ylabel('Volatility (%)')
    plt.title('Volatility Forecast : 60-days Ahead');
    plt.grid(True)
    

    <
    D) GJR-GARCH model performance during Great Financial Crisis

    In [50]:
    #Define the model (Normal Distribution)
    am_lev = arch_model(returns, mean = 'zero', vol='Garch', p=1, o=1, q=1, dist='Normal')
    #Fit the model
    GJRgarch1_1 = am_lev.fit()
    
    Iteration:      1,   Func. Count:      6,   Neg. LLF: -3950.5344201220696
    Optimization terminated successfully.    (Exit mode 0)
                Current function value: -3950.534427862999
                Iterations: 5
                Function evaluations: 6
                Gradient evaluations: 1
    
    In [51]:
    print(GJRgarch1_1)
    
                         Zero Mean - GJR-GARCH Model Results                      
    ==============================================================================
    Dep. Variable:                S&P 500   R-squared:                       0.000
    Mean Model:                 Zero Mean   Adj. R-squared:                  0.001
    Vol Model:                  GJR-GARCH   Log-Likelihood:                3950.53
    Distribution:                  Normal   AIC:                          -7893.07
    Method:            Maximum Likelihood   BIC:                          -7872.46
                                            No. Observations:                 1278
    Date:                Mon, Jul 26 2021   Df Residuals:                     1274
    Time:                        18:54:24   Df Model:                            4
                                   Volatility Model                              
    =============================================================================
                     coef    std err          t      P>|t|       95.0% Conf. Int.
    -----------------------------------------------------------------------------
    omega      4.8307e-06  1.205e-09   4009.922      0.000  [4.828e-06,4.833e-06]
    alpha[1]       0.0100  2.632e-02      0.380      0.704 [-4.160e-02,6.160e-02]
    gamma[1]       0.2000  4.365e-02      4.582  4.598e-06      [  0.114,  0.286]
    beta[1]        0.8700  8.540e-03    101.870      0.000      [  0.853,  0.887]
    =============================================================================
    
    Covariance estimator: robust
    
    In [52]:
    # Plot annualised vol
    fig2 = GJRgarch1_1 .plot(annualize='D')
    
    In [53]:
    GJRgarch1_1.conditional_volatility*np.sqrt(252)
    
    Out[53]:
    Date
    2005-07-18    0.092398
    2005-07-19    0.092978
    2005-07-20    0.094084
    2005-07-21    0.094738
    2005-07-22    0.106540
                    ...   
    2010-08-06    0.156199
    2010-08-09    0.152225
    2010-08-10    0.146468
    2010-08-11    0.147570
    2010-08-12    0.251793
    Name: cond_vol, Length: 1278, dtype: float64
    In [54]:
    # Model params
    GJRgarch1_1.params
    
    Out[54]:
    omega       0.000005
    alpha[1]    0.010000
    gamma[1]    0.200000
    beta[1]     0.870000
    Name: params, dtype: float64
    In [55]:
    # long run variance from model forecast
    lrv_GJR = GJRgarch1_1.params[0]/(1-GJRgarch1_1.params[1]- (GJRgarch1_1.params[2]/2) - GJRgarch1_1.params[3])
    
    # long run variance
    np.sqrt(lrv_GJR*252)*100
    
    Out[55]:
    24.671317591602367
    In [56]:
    plt.plot(GJRgarch1_1.conditional_volatility*np.sqrt(252), color='blue')
    
    plt.axhline(y=np.sqrt(lrv_GJR*252), color='red')
    
    Out[56]:
    <matplotlib.lines.Line2D at 0x16ea0c3f358>
    In [57]:
    # Visualise GARCH volatility and VIX
    plt.title('Annualized Volatility')
    plt.plot(returns.index, GJRgarch1_1.conditional_volatility*np.sqrt(252)*100, color='blue', label='GJR - GARCH')
    plt.plot(returns.index, df['VIX'], color='orange', label = 'VIX')
    plt.legend(loc=2)
    plt.grid(True)
    
    In [58]:
    # Forecast for next 60 days
    model_forecastGJR = GJRgarch1_1.forecast(horizon=60)
    
    In [59]:
    # Subsume forecast values into a dataframe
    GJRforecast_df = pd.DataFrame(np.sqrt(model_forecastGJR.variance.dropna().T *252)*100)
    GJRforecast_df.columns = ['Cond_Vol']
    GJRforecast_df.head()
    
    Out[59]:
    Cond_Vol
    h.01 24.065391
    h.02 24.077659
    h.03 24.089676
    h.04 24.101446
    h.05 24.112975
    In [60]:
    # Plot volatility forecast over a 60-day horizon
    plt.plot(GJRforecast_df, color='blue',label='GJR - GARCH',)
    plt.plot(forecast_df, color='green',label='GARCH')
    plt.axhline(y=np.sqrt(lrv_GJR*252)*100, color='blue',label='Long Run Vol:GJR - GARCH',
                linestyle='dashed',linewidth=4)
    plt.axhline(y=np.sqrt(lrv*252)*100, color='green',label='Long Run Vol:GARCH',
                linestyle='dashed',linewidth=4)
    plt.xlim(0,60)
    plt.xticks(rotation=90)
    plt.xlabel('Horizon (in days)')
    plt.ylabel('Volatility (%)')
    plt.title('Volatility Forecast : 60-days Ahead');
    plt.legend(loc=4)
    plt.grid(True)
    


    E) VaR Estimation with Implied Volatility

    1) Import libraries and specify stock

    In [61]:
    import yfinance as yf
    import QuantLib as ql
    import pandas as pd
    import math
    import numpy as np
    
    aapl = yf.Ticker("aapl")
    

    2) Retrieve price and dividend yield

    In [62]:
    # q=0.02
    #spot= 146.39
    q = aapl.info['dividendYield']
    spot = aapl.info['ask']
    print("Spot:",spot)
    print("Div. Yield::",q)
    
    Spot: 149.27
    Div. Yield:: 0.0058999998
    

    3) Retrieve interest rate term structure and select appropriate risk free rate

    In [63]:
    import pandas_datareader as web
    import datetime
    
    start = datetime.datetime(2021, 6, 30)
    end = datetime.datetime(2021, 7, 23)
    
    int_rates = web.DataReader(["DGS1MO", "DGS3MO","DGS6MO", "DGS1",
                                "DGS2", "DGS3","DGS5","DGS7", "DGS10","DGS20","DGS30"], "fred", start, end)
    int_rates = int_rates.dropna()
    int_rates
    
    Out[63]:
    DGS1MO DGS3MO DGS6MO DGS1 DGS2 DGS3 DGS5 DGS7 DGS10 DGS20 DGS30
    DATE
    2021-06-30 0.05 0.05 0.06 0.07 0.25 0.46 0.87 1.21 1.45 2.00 2.06
    2021-07-01 0.05 0.05 0.05 0.09 0.25 0.47 0.89 1.24 1.48 2.01 2.07
    2021-07-02 0.05 0.05 0.05 0.08 0.24 0.45 0.86 1.19 1.44 1.98 2.05
    2021-07-06 0.05 0.05 0.06 0.07 0.22 0.42 0.81 1.13 1.37 1.92 2.00
    2021-07-07 0.05 0.05 0.05 0.08 0.22 0.41 0.79 1.09 1.33 1.87 1.94
    2021-07-08 0.06 0.06 0.06 0.07 0.19 0.37 0.74 1.06 1.30 1.84 1.91
    2021-07-09 0.06 0.06 0.05 0.08 0.23 0.41 0.79 1.12 1.37 1.91 1.99
    2021-07-12 0.05 0.05 0.06 0.08 0.23 0.43 0.81 1.13 1.38 1.93 2.00
    2021-07-13 0.05 0.05 0.06 0.08 0.26 0.47 0.85 1.16 1.42 1.96 2.04
    2021-07-14 0.06 0.06 0.05 0.08 0.23 0.44 0.80 1.11 1.37 1.91 1.98
    2021-07-15 0.05 0.05 0.05 0.07 0.23 0.43 0.78 1.07 1.31 1.85 1.92
    2021-07-16 0.05 0.05 0.05 0.08 0.25 0.43 0.79 1.08 1.31 1.86 1.93
    2021-07-19 0.05 0.05 0.06 0.07 0.21 0.38 0.70 0.97 1.19 1.74 1.81
    2021-07-20 0.05 0.05 0.06 0.08 0.20 0.37 0.69 0.98 1.23 1.79 1.88
    2021-07-21 0.04 0.05 0.05 0.07 0.22 0.39 0.74 1.05 1.30 1.87 1.94
    2021-07-22 0.04 0.05 0.05 0.07 0.20 0.37 0.71 1.02 1.27 1.82 1.90
    In [64]:
    # 1 year risk free rate
    r = int_rates.iloc[-1,3]/100
    r
    
    Out[64]:
    0.0007000000000000001

    4) Within the QuantLib library, define some of the basic data conventions such as the day_count, calendar, valuation date. Construct some of the basic dependencies such as the yield and dividend term structures.

    In [65]:
    day_count = ql.Actual365Fixed()
    calendar = ql.UnitedStates()
    
    calculation_date = ql.Date(str(end), '%Y-%m-%d')
    ql.Settings.instance().evaluationDate = calculation_date
    
    spot
    
    dividend_yield = ql.QuoteHandle(ql.SimpleQuote(0.0))
    risk_free_rate = r
    dividend_rate = q
    
    flat_ts = ql.YieldTermStructureHandle(
        ql.FlatForward(calculation_date, risk_free_rate, day_count))
    dividend_ts = ql.YieldTermStructureHandle(
        ql.FlatForward(calculation_date, dividend_rate, day_count))
    

    5) Retrieve expiration dates for option chain (as string)

    In [66]:
    expiration_dates = aapl.options
    expiration_dates =list(expiration_dates)
    # Except for nearest expiries
    expiration_dates = expiration_dates[9:]
    

    6) Parse dates with various methods for future use

    In [67]:
    import datetime
    from dateutil.parser import parse
    
    
    date_time_str = expiration_dates
    
    # 1 string parsed as datetime.datetime (necessary)
    date_time_obj = [datetime.datetime.strptime(x, "%Y-%m-%d") for x in date_time_str]
    
    # 2 string parsed as datetime.date (necessary)
    date_obj = [datetime.datetime.strptime(x, "%Y-%m-%d").date() for x in date_time_str]
    
    # 3. string parsed witrh ql.Date (necessary)
    quantDate_obj = [ql.Date(x, '%Y-%m-%d') for x in date_time_str]
    
    date_time_obj, date_obj, quantDate_obj
    
    Out[67]:
    ([datetime.datetime(2021, 12, 17, 0, 0),
      datetime.datetime(2022, 1, 21, 0, 0),
      datetime.datetime(2022, 3, 18, 0, 0),
      datetime.datetime(2022, 6, 17, 0, 0),
      datetime.datetime(2022, 9, 16, 0, 0),
      datetime.datetime(2023, 1, 20, 0, 0),
      datetime.datetime(2023, 3, 17, 0, 0),
      datetime.datetime(2023, 6, 16, 0, 0),
      datetime.datetime(2023, 9, 15, 0, 0)],
     [datetime.date(2021, 12, 17),
      datetime.date(2022, 1, 21),
      datetime.date(2022, 3, 18),
      datetime.date(2022, 6, 17),
      datetime.date(2022, 9, 16),
      datetime.date(2023, 1, 20),
      datetime.date(2023, 3, 17),
      datetime.date(2023, 6, 16),
      datetime.date(2023, 9, 15)],
     [Date(17,12,2021),
      Date(21,1,2022),
      Date(18,3,2022),
      Date(17,6,2022),
      Date(16,9,2022),
      Date(20,1,2023),
      Date(17,3,2023),
      Date(16,6,2023),
      Date(15,9,2023)])

    5) Retrieve call and put option chains

    In [68]:
    pd.set_option('display.max_rows', None)
    pd.set_option('display.max_columns', None)
    
    aapl_call = aapl.option_chain().calls
    aapl_put = aapl.option_chain().puts
    aapl_call
    
    Out[68]:
    contractSymbol lastTradeDate strike lastPrice bid ask change percentChange volume openInterest impliedVolatility inTheMoney contractSize currency
    0 AAPL210730C00065000 2021-07-23 13:44:06 65.0 84.60 84.25 84.35 2.000000 2.421308 6 82 3.238283 True REGULAR USD
    1 AAPL210730C00070000 2021-07-22 18:17:27 70.0 77.21 79.25 79.35 0.000000 0.000000 78 153 2.972659 True REGULAR USD
    2 AAPL210730C00075000 2021-07-22 19:57:13 75.0 73.45 74.20 74.30 1.699997 2.369334 1 197 2.609378 True REGULAR USD
    3 AAPL210730C00080000 2021-07-22 14:59:05 80.0 67.80 69.20 69.35 0.000000 0.000000 67 133 2.441410 True REGULAR USD
    4 AAPL210730C00085000 2021-07-26 14:04:41 85.0 64.65 64.25 64.30 3.250000 5.293159 1 180 2.222661 True REGULAR USD
    5 AAPL210730C00090000 2021-07-22 13:40:05 90.0 56.05 59.20 59.35 0.000000 0.000000 1 529 2.019536 True REGULAR USD
    6 AAPL210730C00095000 2021-07-20 13:36:53 95.0 48.53 54.20 54.30 0.000000 0.000000 4 198 1.781251 True REGULAR USD
    7 AAPL210730C00100000 2021-07-23 16:26:50 100.0 49.25 49.20 49.35 1.049999 2.178422 18 318 1.640627 True REGULAR USD
    8 AAPL210730C00105000 2021-07-22 19:57:57 105.0 41.76 44.20 44.35 0.000000 0.000000 294 313 1.464846 True REGULAR USD
    9 AAPL210730C00110000 2021-07-26 13:33:21 110.0 39.65 39.25 39.30 2.490002 6.700758 15 236 1.292972 True REGULAR USD
    10 AAPL210730C00113000 2021-07-23 18:07:38 113.0 35.50 36.25 36.35 0.000000 0.000000 39 77 1.222660 True REGULAR USD
    11 AAPL210730C00114000 2021-07-26 14:18:51 114.0 35.10 35.20 35.30 0.949997 2.781836 5 119 1.128911 True REGULAR USD
    12 AAPL210730C00115000 2021-07-23 18:09:02 115.0 33.62 34.20 34.30 0.000000 0.000000 5 272 1.097661 True REGULAR USD
    13 AAPL210730C00116000 2021-07-23 19:03:44 116.0 32.34 33.25 33.30 0.000000 0.000000 25 29 1.095708 True REGULAR USD
    14 AAPL210730C00117000 2021-07-22 13:57:03 117.0 30.00 32.25 32.35 0.000000 0.000000 8 63 1.089848 True REGULAR USD
    15 AAPL210730C00118000 2021-07-26 14:03:30 118.0 31.65 31.25 31.35 2.850000 9.895835 25 72 1.056645 True REGULAR USD
    16 AAPL210730C00119000 2021-07-26 13:47:41 119.0 30.45 30.25 30.35 7.150002 30.686703 26 11 1.023442 True REGULAR USD
    17 AAPL210730C00120000 2021-07-26 16:24:09 120.0 29.40 29.20 29.30 0.779999 2.725363 52 520 0.941407 True REGULAR USD
    18 AAPL210730C00121000 2021-07-26 13:47:41 121.0 28.45 28.25 28.30 1.590000 5.919583 5 49 0.935548 True REGULAR USD
    19 AAPL210730C00122000 2021-07-26 16:38:12 122.0 27.25 27.25 27.35 0.650000 2.443608 8 240 0.927735 True REGULAR USD
    20 AAPL210730C00123000 2021-07-26 15:02:25 123.0 26.40 26.25 26.30 1.150000 4.554454 3 78 0.873048 True REGULAR USD
    21 AAPL210730C00124000 2021-07-26 13:56:19 124.0 25.45 25.30 25.35 0.850000 3.455286 2 191 0.884767 True REGULAR USD
    22 AAPL210730C00125000 2021-07-26 16:27:59 125.0 24.45 24.25 24.35 0.800001 3.382669 97 2012 0.832033 True REGULAR USD
    23 AAPL210730C00126000 2021-07-26 14:14:25 126.0 23.41 23.25 23.30 1.010000 4.508930 25 675 0.781252 True REGULAR USD
    24 AAPL210730C00127000 2021-07-26 14:17:39 127.0 22.40 22.25 22.35 0.850000 3.944318 63 518 0.769534 True REGULAR USD
    25 AAPL210730C00128000 2021-07-26 14:39:42 128.0 21.50 21.25 21.35 0.900000 4.368930 93 988 0.738284 True REGULAR USD
    26 AAPL210730C00129000 2021-07-23 18:22:32 129.0 20.27 20.25 20.35 0.670000 3.418368 6 819 0.707034 True REGULAR USD
    27 AAPL210730C00130000 2021-07-26 16:31:36 130.0 19.35 19.25 19.35 0.700001 3.753356 144 5132 0.675784 True REGULAR USD
    28 AAPL210730C00131000 2021-07-26 16:37:41 131.0 18.29 18.25 18.35 0.740002 4.216534 43 1566 0.644535 True REGULAR USD
    29 AAPL210730C00132000 2021-07-26 16:28:53 132.0 17.47 17.25 17.35 0.809999 4.861941 302 1735 0.613285 True REGULAR USD
    30 AAPL210730C00133000 2021-07-26 14:50:09 133.0 16.85 16.25 16.35 1.260000 8.082106 78 3336 0.582035 True REGULAR USD
    31 AAPL210730C00134000 2021-07-26 16:13:05 134.0 15.55 15.30 15.40 0.820001 5.566874 287 3536 0.579106 True REGULAR USD
    32 AAPL210730C00135000 2021-07-26 16:38:10 135.0 14.40 14.30 14.40 0.700000 5.109488 458 14629 0.546880 True REGULAR USD
    33 AAPL210730C00136000 2021-07-26 16:38:40 136.0 13.35 13.35 13.45 0.670000 5.283912 158 2876 0.537114 True REGULAR USD
    34 AAPL210730C00137000 2021-07-26 16:26:38 137.0 12.55 12.35 12.45 0.830000 7.081911 134 3107 0.503911 True REGULAR USD
    35 AAPL210730C00138000 2021-07-26 16:24:52 138.0 11.59 11.40 11.45 0.740000 6.820274 164 3490 0.489263 True REGULAR USD
    36 AAPL210730C00139000 2021-07-26 16:25:09 139.0 10.62 10.40 10.50 0.690000 6.948636 154 6560 0.472173 True REGULAR USD
    37 AAPL210730C00140000 2021-07-26 16:38:48 140.0 9.55 9.50 9.60 0.700000 7.909603 20877 46551 0.466802 True REGULAR USD
    38 AAPL210730C00141000 2021-07-26 16:36:25 141.0 8.68 8.55 8.65 0.490001 5.982915 372 3342 0.442388 True REGULAR USD
    39 AAPL210730C00142000 2021-07-26 16:38:59 142.0 7.80 7.70 7.80 0.460000 6.267031 906 5276 0.439459 True REGULAR USD
    40 AAPL210730C00143000 2021-07-26 16:31:36 143.0 6.96 6.90 6.95 0.410000 6.259540 2073 8252 0.430181 True REGULAR USD
    41 AAPL210730C00144000 2021-07-26 16:37:12 144.0 6.17 6.10 6.20 0.350000 6.013744 1056 20221 0.433599 True REGULAR USD
    42 AAPL210730C00145000 2021-07-26 16:38:40 145.0 5.40 5.40 5.45 0.300000 5.882357 5201 32167 0.429205 True REGULAR USD
    43 AAPL210730C00146000 2021-07-26 16:35:07 146.0 4.79 4.75 4.80 0.290000 6.444444 2803 18399 0.433599 True REGULAR USD
    44 AAPL210730C00147000 2021-07-26 16:38:49 147.0 4.19 4.15 4.20 0.290000 7.435896 5678 16658 0.437750 True REGULAR USD
    45 AAPL210730C00148000 2021-07-26 16:38:58 148.0 3.67 3.60 3.70 0.370000 11.212125 9645 28911 0.448492 True REGULAR USD
    46 AAPL210730C00149000 2021-07-26 16:39:11 149.0 3.15 3.10 3.15 0.250000 8.620689 19915 24402 0.444341 True REGULAR USD
    47 AAPL210730C00150000 2021-07-26 16:39:23 150.0 2.70 2.70 2.71 0.250000 10.204082 83528 76461 0.448736 False REGULAR USD
    48 AAPL210730C00152500 2021-07-26 16:38:43 152.5 1.72 1.74 1.75 0.180000 11.688316 26961 13744 0.448492 False REGULAR USD
    49 AAPL210730C00155000 2021-07-26 16:39:13 155.0 1.04 1.03 1.04 0.110000 11.827951 74493 67709 0.442877 False REGULAR USD
    50 AAPL210730C00157500 2021-07-26 16:39:00 157.5 0.61 0.59 0.60 0.080000 15.094349 10120 7512 0.444341 False REGULAR USD
    51 AAPL210730C00160000 2021-07-26 16:39:13 160.0 0.34 0.33 0.34 0.020000 6.250004 45578 69371 0.449224 False REGULAR USD
    52 AAPL210730C00162500 2021-07-26 16:38:12 162.5 0.20 0.20 0.21 0.000000 0.000000 4276 3842 0.466802 False REGULAR USD
    53 AAPL210730C00165000 2021-07-26 16:39:01 165.0 0.13 0.12 0.13 0.000000 0.000000 4704 27851 0.483404 False REGULAR USD
    54 AAPL210730C00167500 2021-07-26 16:28:47 167.5 0.09 0.07 0.08 0.000000 0.000000 1832 2731 0.498052 False REGULAR USD
    55 AAPL210730C00170000 2021-07-26 16:29:19 170.0 0.06 0.05 0.06 0.000000 0.000000 5851 26105 0.521489 False REGULAR USD
    56 AAPL210730C00172500 2021-07-26 16:32:37 172.5 0.04 0.04 0.05 -0.010000 -20.000004 593 2129 0.554692 False REGULAR USD
    57 AAPL210730C00175000 2021-07-26 16:38:19 175.0 0.03 0.03 0.04 0.000000 0.000000 2653 1859 0.582035 False REGULAR USD
    58 AAPL210730C00180000 2021-07-26 16:31:16 180.0 0.03 0.02 0.03 0.000000 0.000000 367 1792 0.644535 False REGULAR USD
    59 AAPL210730C00185000 2021-07-26 16:21:06 185.0 0.01 0.01 0.02 -0.020000 -66.666670 1455 1726 0.687503 False REGULAR USD

    6) Filter option chain

    In [69]:
    # Set expiry list - filter by dates if desired
    expiry_list = expiration_dates
    
    
    # Initialise empty list to collect option vols and values
    data_callvols = []
    data_callvals = []
    
    # Set expiry list - filter by dates if desired
    
    for expiry in expiry_list:
        sub_set = aapl.option_chain(expiry).calls
        sub_set = sub_set[(sub_set.strike>60) & (sub_set.strike<300)]
        sub_set.set_index('strike', inplace=True)
        data_callvols.append(sub_set['impliedVolatility'])
        data_callvals.append(sub_set['ask'])
    

    7) Obtain quoted volatility surface

    In [70]:
    # Collect subset of implied vols in dataframe (filtered by expiration and strike)
    call_vols = pd.DataFrame(data_callvols)
    
    # Create datetime index for expiration dates to allow time interpolation
    call_vols.index = date_time_obj
    
    call_vols
    
    Out[70]:
    strike 61.25 62.50 63.75 65.00 66.25 67.50 70.00 72.50 75.00 76.25 77.50 78.75 80.00 81.25 82.50 83.75 85.00 86.25 87.50 90.00 92.50 95.00 97.50 100.00 102.50 103.75 105.00 106.25 107.50 108.75 110.00 111.25 112.50 113.75 115.00 116.25 117.50 118.75 120.00 121.25 122.50 125.00 126.25 127.50 128.75 130.00 135.00 140.00 145.00 150.00 155.00 160.00 165.00 170.00 175.00 180.00 185.00 190.00 195.00 200.00 205.00 210.00 215.00 220.00 225.00 230.00 235.00 240.00 245.00 250.00 255.00 260.00 265.00 270.00 280.00 290.00
    2021-12-17 NaN NaN NaN 0.598149 NaN NaN 0.585942 NaN 0.544438 NaN NaN NaN 0.504888 NaN NaN NaN 0.500737 NaN NaN 0.471685 NaN 0.441168 NaN 0.415289 NaN NaN 0.391730 NaN NaN NaN 0.369025 NaN NaN NaN 0.349250 NaN NaN NaN 0.330329 NaN NaN 0.313056 NaN NaN NaN 0.299445 0.286811 0.280891 0.273567 0.268379 0.263588 0.262275 0.261299 0.262275 0.263496 0.266975 0.271736 0.277351 0.284309 0.291511 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
    2022-01-21 0.601566 0.597172 0.576664 0.572270 0.581547 0.569340 0.545903 0.534184 0.517095 NaN 0.504399 NaN 0.507573 NaN 0.492681 NaN 0.477788 NaN 0.468755 0.453130 0.442633 0.426764 0.419439 0.402960 0.394049 0.387457 0.377326 0.373908 0.370246 0.363166 0.358893 0.354499 0.352362 0.342109 0.339484 0.336432 0.333014 0.326972 0.325080 0.320624 0.313911 0.309272 0.305427 0.302863 0.299934 0.298164 0.286323 0.279670 0.272651 0.268440 0.264351 0.261787 0.261543 0.260811 0.262092 0.263557 0.266975 0.271003 0.275886 0.281379 0.287117 0.294197 0.301032 0.307990 0.314460 0.321784 0.0 0.335944 0.0 0.351081 0.0 0.365485 0.0 0.000000 0.000000 0.000000
    2022-03-18 NaN NaN NaN NaN NaN NaN NaN NaN 0.490239 NaN NaN NaN 0.467779 NaN NaN NaN 0.443365 NaN NaN 0.418097 NaN 0.399176 NaN 0.377936 NaN NaN 0.362860 NaN NaN NaN 0.348822 NaN NaN NaN 0.331122 NaN NaN NaN 0.320624 NaN NaN 0.308418 NaN NaN NaN 0.301032 0.292976 0.286201 0.281135 0.278450 0.274910 0.272834 0.271553 0.271614 0.271553 0.273262 0.275093 0.276435 0.279853 0.283393 0.287361 0.291877 0.296516 0.302375 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
    2022-06-17 NaN 0.506597 0.496099 0.478521 NaN 0.479009 0.470953 0.451544 0.452520 NaN 0.438116 NaN 0.427984 NaN 0.417608 NaN 0.413702 NaN 0.402228 0.393561 0.387091 0.377326 0.371893 0.363410 0.356391 0.353644 0.350592 0.347419 0.344001 0.343757 0.338263 0.337470 0.333259 0.330451 0.330268 0.326911 0.323371 0.322333 0.319678 0.318061 0.314948 0.311591 NaN NaN NaN 0.305122 0.299537 0.293617 0.289955 0.287391 0.285072 0.283088 0.281715 0.281227 0.281074 0.280616 0.281288 0.282539 0.284919 0.286262 NaN 0.291389 NaN 0.297614 0.300788 0.304450 NaN 0.312751 NaN 0.321418 0.0 0.328254 NaN 0.000000 0.000000 0.000000
    2022-09-16 NaN 0.459234 NaN 0.451422 NaN 0.443121 0.438482 0.424810 0.422369 0.413702 0.405035 0.406622 0.404303 0.39588 0.399054 0.393439 0.390387 0.387091 0.378790 0.374152 0.370795 0.362494 0.359320 0.351691 0.345282 NaN 0.341315 NaN 0.337958 NaN 0.333686 NaN 0.329841 NaN 0.325263 NaN 0.322150 NaN 0.319312 NaN 0.315559 0.312049 NaN NaN NaN 0.307288 0.302680 0.298957 0.295967 0.293678 0.291297 0.289772 0.288429 0.288246 0.287910 0.287574 0.287483 0.287971 0.289283 0.289741 NaN 0.293189 NaN 0.296882 0.299262 0.301765 NaN 0.307014 NaN 0.313239 NaN 0.320075 NaN 0.000000 0.000000 0.000000
    2023-01-20 NaN NaN NaN 0.417486 NaN NaN 0.402106 NaN 0.390997 NaN NaN NaN 0.377265 NaN NaN NaN 0.365973 NaN NaN 0.357917 NaN 0.347052 NaN 0.338324 NaN NaN 0.332068 NaN NaN NaN 0.324134 NaN NaN NaN 0.315803 NaN NaN NaN 0.310737 NaN NaN 0.307227 NaN NaN NaN 0.304115 0.301277 0.297981 0.296363 0.294288 0.292549 0.291191 0.290351 0.290138 0.289298 0.288703 0.288459 0.288032 0.289161 0.289649 NaN 0.290779 NaN 0.293678 NaN 0.295417 NaN 0.299323 NaN 0.302192 NaN 0.306648 NaN NaN NaN NaN
    2023-03-17 NaN NaN NaN 0.408575 NaN NaN 0.398321 NaN 0.380133 NaN NaN NaN 0.370368 NaN NaN NaN 0.361823 NaN NaN 0.350715 NaN 0.343512 NaN 0.337470 NaN NaN 0.329902 NaN NaN NaN 0.325141 NaN NaN NaN 0.319556 NaN NaN NaN 0.314124 NaN NaN 0.310340 NaN NaN NaN 0.306342 0.304298 0.301170 0.299140 0.298164 0.296958 0.295600 0.294868 0.293556 0.293739 0.294288 0.293205 0.293403 0.293647 0.294074 NaN 0.295112 NaN 0.296821 NaN 0.299262 NaN 0.301399 NaN 0.305122 NaN 0.307807 NaN NaN NaN NaN
    2023-06-16 NaN NaN NaN 0.390387 NaN NaN 0.381842 NaN 0.370246 NaN NaN NaN 0.362555 NaN NaN NaN 0.353888 NaN NaN 0.344367 NaN 0.338080 NaN 0.331611 NaN NaN 0.325934 NaN NaN NaN 0.320716 NaN NaN NaN 0.316657 NaN NaN NaN 0.313422 NaN NaN 0.310065 NaN NaN NaN 0.307349 0.305152 0.302772 0.301536 0.299568 0.299415 0.298652 0.297401 0.296958 0.296821 0.296455 0.296607 0.296760 0.297004 0.296760 NaN 0.297950 NaN 0.298988 NaN 0.301460 NaN 0.303626 NaN 0.305946 NaN 0.310188 NaN 0.311286 0.315559 0.319831
    2023-09-15 NaN NaN NaN NaN NaN NaN NaN NaN 0.414679 NaN NaN NaN 0.402106 NaN NaN NaN 0.389594 NaN NaN 0.381781 NaN 0.374213 NaN 0.334388 NaN NaN 0.342444 NaN NaN NaN 0.342963 NaN NaN NaN 0.334601 NaN NaN NaN 0.316016 NaN NaN 0.318580 NaN NaN NaN 0.312049 0.314094 0.315864 0.306129 0.312080 0.302848 0.303138 0.303672 0.311988 0.320212 0.317451 0.309318 0.325019 0.325599 0.305915 0.311835 0.305946 NaN 0.325873 NaN 0.333106 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

    7) Obtain quoted volatility surface for most traded options

    In [71]:
    max_K = call_vols.columns.max()
    min_K = call_vols.columns.min()
    max_K , min_K
    
    Out[71]:
    (290.0, 61.25)
    In [72]:
    sel_K = np.arange(65,235,5)
    sel_K
    
    Out[72]:
    array([ 65,  70,  75,  80,  85,  90,  95, 100, 105, 110, 115, 120, 125,
           130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190,
           195, 200, 205, 210, 215, 220, 225, 230])
    In [73]:
    call_vols = call_vols[sel_K]
    call_vols
    
    Out[73]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    2021-12-17 0.598149 0.585942 0.544438 0.504888 0.500737 0.471685 0.441168 0.415289 0.391730 0.369025 0.349250 0.330329 0.313056 0.299445 0.286811 0.280891 0.273567 0.268379 0.263588 0.262275 0.261299 0.262275 0.263496 0.266975 0.271736 0.277351 0.284309 0.291511 NaN NaN NaN NaN NaN NaN
    2022-01-21 0.572270 0.545903 0.517095 0.507573 0.477788 0.453130 0.426764 0.402960 0.377326 0.358893 0.339484 0.325080 0.309272 0.298164 0.286323 0.279670 0.272651 0.268440 0.264351 0.261787 0.261543 0.260811 0.262092 0.263557 0.266975 0.271003 0.275886 0.281379 0.287117 0.294197 0.301032 0.307990 0.314460 0.321784
    2022-03-18 NaN NaN 0.490239 0.467779 0.443365 0.418097 0.399176 0.377936 0.362860 0.348822 0.331122 0.320624 0.308418 0.301032 0.292976 0.286201 0.281135 0.278450 0.274910 0.272834 0.271553 0.271614 0.271553 0.273262 0.275093 0.276435 0.279853 0.283393 0.287361 0.291877 0.296516 0.302375 NaN NaN
    2022-06-17 0.478521 0.470953 0.452520 0.427984 0.413702 0.393561 0.377326 0.363410 0.350592 0.338263 0.330268 0.319678 0.311591 0.305122 0.299537 0.293617 0.289955 0.287391 0.285072 0.283088 0.281715 0.281227 0.281074 0.280616 0.281288 0.282539 0.284919 0.286262 NaN 0.291389 NaN 0.297614 0.300788 0.304450
    2022-09-16 0.451422 0.438482 0.422369 0.404303 0.390387 0.374152 0.362494 0.351691 0.341315 0.333686 0.325263 0.319312 0.312049 0.307288 0.302680 0.298957 0.295967 0.293678 0.291297 0.289772 0.288429 0.288246 0.287910 0.287574 0.287483 0.287971 0.289283 0.289741 NaN 0.293189 NaN 0.296882 0.299262 0.301765
    2023-01-20 0.417486 0.402106 0.390997 0.377265 0.365973 0.357917 0.347052 0.338324 0.332068 0.324134 0.315803 0.310737 0.307227 0.304115 0.301277 0.297981 0.296363 0.294288 0.292549 0.291191 0.290351 0.290138 0.289298 0.288703 0.288459 0.288032 0.289161 0.289649 NaN 0.290779 NaN 0.293678 NaN 0.295417
    2023-03-17 0.408575 0.398321 0.380133 0.370368 0.361823 0.350715 0.343512 0.337470 0.329902 0.325141 0.319556 0.314124 0.310340 0.306342 0.304298 0.301170 0.299140 0.298164 0.296958 0.295600 0.294868 0.293556 0.293739 0.294288 0.293205 0.293403 0.293647 0.294074 NaN 0.295112 NaN 0.296821 NaN 0.299262
    2023-06-16 0.390387 0.381842 0.370246 0.362555 0.353888 0.344367 0.338080 0.331611 0.325934 0.320716 0.316657 0.313422 0.310065 0.307349 0.305152 0.302772 0.301536 0.299568 0.299415 0.298652 0.297401 0.296958 0.296821 0.296455 0.296607 0.296760 0.297004 0.296760 NaN 0.297950 NaN 0.298988 NaN 0.301460
    2023-09-15 NaN NaN 0.414679 0.402106 0.389594 0.381781 0.374213 0.334388 0.342444 0.342963 0.334601 0.316016 0.318580 0.312049 0.314094 0.315864 0.306129 0.312080 0.302848 0.303138 0.303672 0.311988 0.320212 0.317451 0.309318 0.325019 0.325599 0.305915 0.311835 0.305946 NaN 0.325873 NaN 0.333106
    In [74]:
    # Interpolate filtered vols
    
    # First: Linear interpolation by strike (horizontally)
    call_vols = call_vols.interpolate(method='linear',axis=1)
    call_vols
    
    Out[74]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    2021-12-17 0.598149 0.585942 0.544438 0.504888 0.500737 0.471685 0.441168 0.415289 0.391730 0.369025 0.349250 0.330329 0.313056 0.299445 0.286811 0.280891 0.273567 0.268379 0.263588 0.262275 0.261299 0.262275 0.263496 0.266975 0.271736 0.277351 0.284309 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    2022-01-21 0.572270 0.545903 0.517095 0.507573 0.477788 0.453130 0.426764 0.402960 0.377326 0.358893 0.339484 0.325080 0.309272 0.298164 0.286323 0.279670 0.272651 0.268440 0.264351 0.261787 0.261543 0.260811 0.262092 0.263557 0.266975 0.271003 0.275886 0.281379 0.287117 0.294197 0.301032 0.307990 0.314460 0.321784
    2022-03-18 NaN NaN 0.490239 0.467779 0.443365 0.418097 0.399176 0.377936 0.362860 0.348822 0.331122 0.320624 0.308418 0.301032 0.292976 0.286201 0.281135 0.278450 0.274910 0.272834 0.271553 0.271614 0.271553 0.273262 0.275093 0.276435 0.279853 0.283393 0.287361 0.291877 0.296516 0.302375 0.302375 0.302375
    2022-06-17 0.478521 0.470953 0.452520 0.427984 0.413702 0.393561 0.377326 0.363410 0.350592 0.338263 0.330268 0.319678 0.311591 0.305122 0.299537 0.293617 0.289955 0.287391 0.285072 0.283088 0.281715 0.281227 0.281074 0.280616 0.281288 0.282539 0.284919 0.286262 0.288825 0.291389 0.294502 0.297614 0.300788 0.304450
    2022-09-16 0.451422 0.438482 0.422369 0.404303 0.390387 0.374152 0.362494 0.351691 0.341315 0.333686 0.325263 0.319312 0.312049 0.307288 0.302680 0.298957 0.295967 0.293678 0.291297 0.289772 0.288429 0.288246 0.287910 0.287574 0.287483 0.287971 0.289283 0.289741 0.291465 0.293189 0.295036 0.296882 0.299262 0.301765
    2023-01-20 0.417486 0.402106 0.390997 0.377265 0.365973 0.357917 0.347052 0.338324 0.332068 0.324134 0.315803 0.310737 0.307227 0.304115 0.301277 0.297981 0.296363 0.294288 0.292549 0.291191 0.290351 0.290138 0.289298 0.288703 0.288459 0.288032 0.289161 0.289649 0.290214 0.290779 0.292228 0.293678 0.294547 0.295417
    2023-03-17 0.408575 0.398321 0.380133 0.370368 0.361823 0.350715 0.343512 0.337470 0.329902 0.325141 0.319556 0.314124 0.310340 0.306342 0.304298 0.301170 0.299140 0.298164 0.296958 0.295600 0.294868 0.293556 0.293739 0.294288 0.293205 0.293403 0.293647 0.294074 0.294593 0.295112 0.295967 0.296821 0.298042 0.299262
    2023-06-16 0.390387 0.381842 0.370246 0.362555 0.353888 0.344367 0.338080 0.331611 0.325934 0.320716 0.316657 0.313422 0.310065 0.307349 0.305152 0.302772 0.301536 0.299568 0.299415 0.298652 0.297401 0.296958 0.296821 0.296455 0.296607 0.296760 0.297004 0.296760 0.297355 0.297950 0.298469 0.298988 0.300224 0.301460
    2023-09-15 NaN NaN 0.414679 0.402106 0.389594 0.381781 0.374213 0.334388 0.342444 0.342963 0.334601 0.316016 0.318580 0.312049 0.314094 0.315864 0.306129 0.312080 0.302848 0.303138 0.303672 0.311988 0.320212 0.317451 0.309318 0.325019 0.325599 0.305915 0.311835 0.305946 0.315910 0.325873 0.329490 0.333106
    In [75]:
    #Second: Time interpolation by expiration (vertically)
    call_vols = call_vols.interpolate(method='time',axis=0)
    
    call_vols 
    
    #call_vols = call_vols.dropna("columns")
    #call_vols
    
    Out[75]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    2021-12-17 0.598149 0.585942 0.544438 0.504888 0.500737 0.471685 0.441168 0.415289 0.391730 0.369025 0.349250 0.330329 0.313056 0.299445 0.286811 0.280891 0.273567 0.268379 0.263588 0.262275 0.261299 0.262275 0.263496 0.266975 0.271736 0.277351 0.284309 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    2022-01-21 0.572270 0.545903 0.517095 0.507573 0.477788 0.453130 0.426764 0.402960 0.377326 0.358893 0.339484 0.325080 0.309272 0.298164 0.286323 0.279670 0.272651 0.268440 0.264351 0.261787 0.261543 0.260811 0.262092 0.263557 0.266975 0.271003 0.275886 0.281379 0.287117 0.294197 0.301032 0.307990 0.314460 0.321784
    2022-03-18 0.536556 0.517350 0.490239 0.467779 0.443365 0.418097 0.399176 0.377936 0.362860 0.348822 0.331122 0.320624 0.308418 0.301032 0.292976 0.286201 0.281135 0.278450 0.274910 0.272834 0.271553 0.271614 0.271553 0.273262 0.275093 0.276435 0.279853 0.283393 0.287361 0.291877 0.296516 0.302375 0.302375 0.302375
    2022-06-17 0.478521 0.470953 0.452520 0.427984 0.413702 0.393561 0.377326 0.363410 0.350592 0.338263 0.330268 0.319678 0.311591 0.305122 0.299537 0.293617 0.289955 0.287391 0.285072 0.283088 0.281715 0.281227 0.281074 0.280616 0.281288 0.282539 0.284919 0.286262 0.288825 0.291389 0.294502 0.297614 0.300788 0.304450
    2022-09-16 0.451422 0.438482 0.422369 0.404303 0.390387 0.374152 0.362494 0.351691 0.341315 0.333686 0.325263 0.319312 0.312049 0.307288 0.302680 0.298957 0.295967 0.293678 0.291297 0.289772 0.288429 0.288246 0.287910 0.287574 0.287483 0.287971 0.289283 0.289741 0.291465 0.293189 0.295036 0.296882 0.299262 0.301765
    2023-01-20 0.417486 0.402106 0.390997 0.377265 0.365973 0.357917 0.347052 0.338324 0.332068 0.324134 0.315803 0.310737 0.307227 0.304115 0.301277 0.297981 0.296363 0.294288 0.292549 0.291191 0.290351 0.290138 0.289298 0.288703 0.288459 0.288032 0.289161 0.289649 0.290214 0.290779 0.292228 0.293678 0.294547 0.295417
    2023-03-17 0.408575 0.398321 0.380133 0.370368 0.361823 0.350715 0.343512 0.337470 0.329902 0.325141 0.319556 0.314124 0.310340 0.306342 0.304298 0.301170 0.299140 0.298164 0.296958 0.295600 0.294868 0.293556 0.293739 0.294288 0.293205 0.293403 0.293647 0.294074 0.294593 0.295112 0.295967 0.296821 0.298042 0.299262
    2023-06-16 0.390387 0.381842 0.370246 0.362555 0.353888 0.344367 0.338080 0.331611 0.325934 0.320716 0.316657 0.313422 0.310065 0.307349 0.305152 0.302772 0.301536 0.299568 0.299415 0.298652 0.297401 0.296958 0.296821 0.296455 0.296607 0.296760 0.297004 0.296760 0.297355 0.297950 0.298469 0.298988 0.300224 0.301460
    2023-09-15 0.390387 0.381842 0.414679 0.402106 0.389594 0.381781 0.374213 0.334388 0.342444 0.342963 0.334601 0.316016 0.318580 0.312049 0.314094 0.315864 0.306129 0.312080 0.302848 0.303138 0.303672 0.311988 0.320212 0.317451 0.309318 0.325019 0.325599 0.305915 0.311835 0.305946 0.315910 0.325873 0.329490 0.333106

    8) Compute continuous volatility surface with QuantLib

    In [76]:
    # Quantlib datetime format 
    call_vols.index = [quantDate_obj]
    call_vols
    
    Out[76]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    December 17th, 2021 0.598149 0.585942 0.544438 0.504888 0.500737 0.471685 0.441168 0.415289 0.391730 0.369025 0.349250 0.330329 0.313056 0.299445 0.286811 0.280891 0.273567 0.268379 0.263588 0.262275 0.261299 0.262275 0.263496 0.266975 0.271736 0.277351 0.284309 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    January 21st, 2022 0.572270 0.545903 0.517095 0.507573 0.477788 0.453130 0.426764 0.402960 0.377326 0.358893 0.339484 0.325080 0.309272 0.298164 0.286323 0.279670 0.272651 0.268440 0.264351 0.261787 0.261543 0.260811 0.262092 0.263557 0.266975 0.271003 0.275886 0.281379 0.287117 0.294197 0.301032 0.307990 0.314460 0.321784
    March 18th, 2022 0.536556 0.517350 0.490239 0.467779 0.443365 0.418097 0.399176 0.377936 0.362860 0.348822 0.331122 0.320624 0.308418 0.301032 0.292976 0.286201 0.281135 0.278450 0.274910 0.272834 0.271553 0.271614 0.271553 0.273262 0.275093 0.276435 0.279853 0.283393 0.287361 0.291877 0.296516 0.302375 0.302375 0.302375
    June 17th, 2022 0.478521 0.470953 0.452520 0.427984 0.413702 0.393561 0.377326 0.363410 0.350592 0.338263 0.330268 0.319678 0.311591 0.305122 0.299537 0.293617 0.289955 0.287391 0.285072 0.283088 0.281715 0.281227 0.281074 0.280616 0.281288 0.282539 0.284919 0.286262 0.288825 0.291389 0.294502 0.297614 0.300788 0.304450
    September 16th, 2022 0.451422 0.438482 0.422369 0.404303 0.390387 0.374152 0.362494 0.351691 0.341315 0.333686 0.325263 0.319312 0.312049 0.307288 0.302680 0.298957 0.295967 0.293678 0.291297 0.289772 0.288429 0.288246 0.287910 0.287574 0.287483 0.287971 0.289283 0.289741 0.291465 0.293189 0.295036 0.296882 0.299262 0.301765
    January 20th, 2023 0.417486 0.402106 0.390997 0.377265 0.365973 0.357917 0.347052 0.338324 0.332068 0.324134 0.315803 0.310737 0.307227 0.304115 0.301277 0.297981 0.296363 0.294288 0.292549 0.291191 0.290351 0.290138 0.289298 0.288703 0.288459 0.288032 0.289161 0.289649 0.290214 0.290779 0.292228 0.293678 0.294547 0.295417
    March 17th, 2023 0.408575 0.398321 0.380133 0.370368 0.361823 0.350715 0.343512 0.337470 0.329902 0.325141 0.319556 0.314124 0.310340 0.306342 0.304298 0.301170 0.299140 0.298164 0.296958 0.295600 0.294868 0.293556 0.293739 0.294288 0.293205 0.293403 0.293647 0.294074 0.294593 0.295112 0.295967 0.296821 0.298042 0.299262
    June 16th, 2023 0.390387 0.381842 0.370246 0.362555 0.353888 0.344367 0.338080 0.331611 0.325934 0.320716 0.316657 0.313422 0.310065 0.307349 0.305152 0.302772 0.301536 0.299568 0.299415 0.298652 0.297401 0.296958 0.296821 0.296455 0.296607 0.296760 0.297004 0.296760 0.297355 0.297950 0.298469 0.298988 0.300224 0.301460
    September 15th, 2023 0.390387 0.381842 0.414679 0.402106 0.389594 0.381781 0.374213 0.334388 0.342444 0.342963 0.334601 0.316016 0.318580 0.312049 0.314094 0.315864 0.306129 0.312080 0.302848 0.303138 0.303672 0.311988 0.320212 0.317451 0.309318 0.325019 0.325599 0.305915 0.311835 0.305946 0.315910 0.325873 0.329490 0.333106
    In [77]:
    # Collect implied vols in list of lists
    row_data = call_vols.values.tolist()
    
    In [78]:
    # Collect strikes in list and convert to float
    strikes = call_vols.columns.values.tolist()
    strikes = [float(i) for i in strikes]
    len(strikes)
    
    Out[78]:
    34
    In [79]:
    # Error Checking: 
    expiries_after_dropna = call_vols.index.values.tolist()
    len(expiries_after_dropna) == len(quantDate_obj)
    
    Out[79]:
    True
    In [80]:
    # Rearrange quoted vol matrix for input to black_var_surface
    implied_vols = ql.Matrix(len(strikes), len(quantDate_obj))
    for i in range(implied_vols.rows()):
        for j in range(implied_vols.columns()):
            implied_vols[i][j] = row_data[j][i]
    
    In [81]:
    # Create continously interpolated black variance surface
    black_var_surface = ql.BlackVarianceSurface(calculation_date, calendar, quantDate_obj, strikes, implied_vols, day_count)
    

    9) Compute ATM Volatility

    In [82]:
    # Obtain ATM implied volatility from surface with black_var_surface taking 2 arguments (time to expiry and strike)
    ATMstrike = spot
    ATMexpiry = 0.5# years
    ATMvol = black_var_surface.blackVol(ATMexpiry, ATMstrike)
    ATMvol 
    
    Out[82]:
    0.2691748787202101

    10) Obtain log normal probability density function and cumulative density function of stock price

    In [83]:
    def lognormal(x, sigma, r, t, S0):
        exp_num = -(np.log(x) - np.log(S0) - (r - sigma**2 / 2) * t)**2
        exp_dem = 2 * sigma**2 * t
        
        exp_term = np.exp(exp_num / exp_dem)
        prefactor = 1 / (x * sigma * np.sqrt(2 * np.pi * t))
        
        return np.multiply(prefactor, exp_term)
    
    In [84]:
    from scipy.stats import lognorm, norm
    x = np.linspace(0.5*spot, 1.5*spot, 1000)
    sigma = ATMvol
    r = r
    t = ATMexpiry
    S0 = spot
    
    std = sigma * np.sqrt(t)
    mean = np.log(S0) + (r - sigma**2 / 2) * t
    
    In [85]:
    # pdf
    f = lognorm.pdf(x, std, loc = 0, scale = np.exp(mean))
    # cdf
    F = lognorm.cdf(x, std, loc = 0, scale = np.exp(mean))
    df_density = pd.DataFrame(f, columns=['PDF'],index=x)
    df_density['CDF'] = F
    df_density.head()
    
    Out[85]:
    PDF CDF
    74.635000 0.000052 0.000194
    74.784419 0.000054 0.000202
    74.933839 0.000056 0.000210
    75.083258 0.000058 0.000218
    75.232678 0.000060 0.000227

    11) Compute VaR at desired confidence level(s)

    In [86]:
    from scipy.stats import lognorm,norm
    probs = [0.01, 0.05,0.10]
    F_inv = lognorm(std, scale=np.exp(mean)).ppf(probs)
    F_inv_5pc_val= F_inv[1]
    F_inv_pc_losses = F_inv/spot-1
    F_inv_5pc_loss = F_inv_pc_losses[1] 
    F_inv_5pc_loss
    
    Out[86]:
    -0.2816782010341795

    12) Visualize density functions and VaR

    In [87]:
    figure, axis_1 = plt.subplots()
    
    axis_1.plot(x, f ,color='green', label='pdf')
    
    plt.title('Lognormal Distribution of Stock Prices')
    plt.grid(False);
    
    axis_2 = axis_1.twinx()
    axis_2.plot(x, F ,color='blue', label='cdf')
    
    axis_1.set_xlabel("Stock Price", color='black')
    axis_1.set_ylabel("Probability Density Function", color='green')
    axis_2.set_ylabel("Cumulative Density Function", color='blue');
    
    
    plt.axvline(x=F_inv[1], color='red',label='5% VaR',linestyle='dashed',linewidth=4);
    
    print(f"Based on the 6 month ATM implied volatility of {ATMvol:.4f} :")
    
    print(f"- There is a probability of 5 percent that the stock price will fall to {F_inv_5pc_val:.2f} or below in the next 6 months")
    print(f"- This equates to a loss equal to or greater than {F_inv_5pc_loss:.2f}")
    
    Based on the 6 month ATM implied volatility of 0.2692 :
    - There is a probability of 5 percent that the stock price will fall to 107.22 or below in the next 6 months
    - This equates to a loss equal to or greater than -0.28
    

    13) Compute VaR with closed form formula

    In [88]:
    import math as math
    
    spot
    pc_impact = np.linspace(0.6,1.4,1000)
    
    
    
    # Probability of any security level may be input
    K_5pc = F_inv_5pc_val 
    Fwd = spot*math.exp(r*t)
    d_2 = (np.log(Fwd/K_5pc) - (((ATMvol**2)/2)*t)) / (ATMvol*math.sqrt(t))
    N_d_2 = norm.cdf(d_2)
    probK = 1- N_d_2 
    
    print(f"There is a risk-neutral probability of {probK:.6f} that the stock price will fall from the current value of {spot:.2f}")
    print (f"to a value equal to or less than {K_5pc:.2f} over the next 6 months")
    
    There is a risk-neutral probability of 0.050000 that the stock price will fall from the current value of 149.27
    to a value equal to or less than 107.22 over the next 6 months
    
    In [89]:
    K_range = np.linspace(strikes[0],strikes[-1],1000) 
    column_names = ['cdf']
    df_density = pd.DataFrame(index= K_range,columns=['Prob ITM','Prob OTM'])
    
    
    for K in K_range:
        d_2_range = (np.log(Fwd/K_range ) - (((ATMvol**2)/2)*t)) / (ATMvol*math.sqrt(t))
        N_d_2_range = norm.cdf(d_2_range)
        cum_prob_K_range = 1-N_d_2_range
        df_density['Prob ITM'] = N_d_2_range 
        df_density['Prob OTM'] = cum_prob_K_range
        
    
    
    df_density.head()
    
    Out[89]:
    Prob ITM Prob OTM
    65.000000 0.999990 0.000010
    65.165165 0.999990 0.000010
    65.330330 0.999989 0.000011
    65.495495 0.999989 0.000011
    65.660661 0.999988 0.000012
    In [90]:
    fig, ax_1 = plt.subplots()
    
    ax_1.bar(K_range, df_density['Prob OTM'] ,color='red', label='Probability Loss')
    ax_1.set_ylim(0, 0.52)
    
    plt.title('Probaility of Max Loss')
    plt.grid(True);
    
    ax_1.set_xlabel("Stock Price", color='black')
    ax_1.set_ylabel("Cumulative Probability of Price Decline", color='red')
    
    
    
    plt.axvline(spot, color='blue',label='Current Spot',linestyle='dashed',linewidth=4);
    
    
    ax_1.set_ylim(0, 0.52)
    ax_1.set_xlim(K_range[0], spot)
    
    Out[90]:
    (65.0, 149.27)


    F) VOLATILITY TERM STRUCTURE: Compare interpolated values for multiple strikes - ITM, ATM, OTM - as a function of time

    In [91]:
    def vol_ts(vol_surface, plot_years, plot_strikes):
        
        plot_strikes.append(plot_strikes[0])
        np.array(plot_strikes)
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        
        df = pd.DataFrame(Z,index=plot_years,columns=plot_strikes)
        df = df.iloc[:,0:-1]
        
        return df
    
    In [92]:
    # Strikes
    
    
    list_K = [max(strikes),spot, min(strikes)]
    
    
    # Compare interpolated values for several selected strikes
    max_maturity = (quantDate_obj[-1] - calculation_date)/365
    min_maturity = 0.25
    
    expiration_grid = np.linspace(min_maturity, max_maturity,1000)
    
    df_vol_ts = vol_ts(vol_surface = black_var_surface, 
                                     plot_years=expiration_grid, 
                                     plot_strikes=list_K)
    df_vol_ts.head()
    
    Out[92]:
    230.00 149.27 65.00
    0.250000 0.291511 0.269143 0.598149
    0.251900 0.291511 0.269143 0.598149
    0.253800 0.291511 0.269143 0.598149
    0.255700 0.291511 0.269143 0.598149
    0.257599 0.291511 0.269143 0.598149
    In [93]:
    def vol_ts_plt(vol_surface, plot_years, plot_strikes):
        
        plot_strikes.append(plot_strikes[0])
        np.array(plot_strikes)
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        
        df = pd.DataFrame(Z,index=plot_years,columns=plot_strikes)
        df = df.iloc[:,0:-1]
        
        ax = plt.axes()
        ax.plot(df)
        plt.legend([round(num, 2) for num in plot_strikes])
    
        ax.set(xlabel='Term to Expiry', ylabel='Implied Volatility', title='Volatility Term Structure for specified strikes');
    
    In [94]:
    # Strikes
    list_K = [spot*1.15,spot, spot*0.85]
    
    # Compare interpolated values for several selected strikes
    max_maturity = (quantDate_obj[-1] - calculation_date)/365
    min_maturity = 0.25
    
    expiration_grid = np.linspace(min_maturity, max_maturity,1000)
    
    df_vol_ts_plt = vol_ts_plt(vol_surface = black_var_surface, 
                                     plot_years=expiration_grid, 
                                     plot_strikes=list_K)
    df_vol_ts_plt
    

    Note that implied volatility is generally upward sloping beyond the half year point. Differences between the implied volatilities of the options with varying strike levels is at its most pronounced for shorter dated options due to the potential for extreme moves (jumps) and relatively less time for recovery. ATM and OTM have a relatively greater possibility of suffering total loss due to the risk of jumps. ITM options are less likely to suffer total loss due to short term extreme moves. The difference narrows as time to expiry increases, illustrating the lower exposure to jump risk. The volatility skew is also observed: implied volatility is generally higher acroos all maturities as we move deeper into the money.


    G) VOLATILITY SMIRK : Compare implied volatilities for multiple time to expiries as function of strike

    In [95]:
    def vol_smile(vol_surface, plot_years, plot_strikes):
        
        plot_years.append(plot_years[0])
        np.array(plot_years)
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        
        df = pd.DataFrame(Z,index=plot_years,columns=plot_strikes)
        df = df.iloc[0:-1,:]
        
        return df
    
    In [96]:
    list_tte = [0.5,1, 1.5]
    strikes_grid = np.linspace(strikes[0], strikes[-1],100)
    
    df_vol_smile = vol_smile(vol_surface = black_var_surface, 
                                     plot_years=list_tte, 
                                     plot_strikes=strikes_grid)
    df_vol_smile
    
    Out[96]:
    65.000000 66.666667 68.333333 70.000000 71.666667 73.333333 75.000000 76.666667 78.333333 80.000000 81.666667 83.333333 85.000000 86.666667 88.333333 90.000000 91.666667 93.333333 95.000000 96.666667 98.333333 100.000000 101.666667 103.333333 105.000000 106.666667 108.333333 110.000000 111.666667 113.333333 115.000000 116.666667 118.333333 120.000000 121.666667 123.333333 125.000000 126.666667 128.333333 130.000000 131.666667 133.333333 135.000000 136.666667 138.333333 140.000000 141.666667 143.333333 145.000000 146.666667 148.333333 150.000000 151.666667 153.333333 155.000000 156.666667 158.333333 160.000000 161.666667 163.333333 165.000000 166.666667 168.333333 170.000000 171.666667 173.333333 175.000000 176.666667 178.333333 180.000000 181.666667 183.333333 185.000000 186.666667 188.333333 190.000000 191.666667 193.333333 195.000000 196.666667 198.333333 200.000000 201.666667 203.333333 205.000000 206.666667 208.333333 210.000000 211.666667 213.333333 215.000000 216.666667 218.333333 220.000000 221.666667 223.333333 225.000000 226.666667 228.333333 230.000000
    0.5 0.571867 0.563241 0.554480 0.545579 0.536155 0.526561 0.516790 0.513589 0.510369 0.507128 0.497417 0.487512 0.477402 0.469325 0.461106 0.452738 0.444149 0.435391 0.426453 0.418678 0.410756 0.402678 0.394355 0.385854 0.377160 0.371134 0.365008 0.358777 0.352433 0.345972 0.339388 0.334670 0.329884 0.325028 0.319859 0.314605 0.309262 0.305618 0.301931 0.298197 0.294318 0.290387 0.286401 0.284201 0.281983 0.279747 0.277435 0.275103 0.272752 0.271361 0.269964 0.268559 0.267205 0.265844 0.264476 0.263626 0.262774 0.261918 0.261833 0.261747 0.261662 0.261421 0.261180 0.260939 0.261361 0.261783 0.262204 0.262695 0.263184 0.263672 0.264810 0.265943 0.267071 0.268410 0.269742 0.271067 0.272699 0.274320 0.275933 0.277768 0.279591 0.281403 0.283321 0.285227 0.287119 0.289489 0.291838 0.294170 0.296457 0.298727 0.300980 0.303313 0.305628 0.307926 0.310072 0.312204 0.314322 0.316755 0.319169 0.321565
    1.0 0.466380 0.463096 0.459789 0.456458 0.450731 0.444929 0.439052 0.431946 0.424721 0.417371 0.412719 0.408015 0.403256 0.397214 0.391080 0.384847 0.380173 0.375440 0.370648 0.366520 0.362346 0.358123 0.354258 0.350351 0.346400 0.343030 0.339626 0.336187 0.333480 0.330751 0.327999 0.325195 0.322366 0.319512 0.316962 0.314391 0.311800 0.309915 0.308018 0.306110 0.304407 0.302694 0.300972 0.299344 0.297706 0.296060 0.294946 0.293829 0.292707 0.291897 0.291084 0.290270 0.289489 0.288707 0.287922 0.287333 0.286742 0.286150 0.285698 0.285245 0.284791 0.284675 0.284559 0.284443 0.284364 0.284285 0.284206 0.284073 0.283939 0.283805 0.283911 0.284018 0.284125 0.284425 0.284725 0.285025 0.285656 0.286286 0.286914 0.287227 0.287539 0.287851 0.288579 0.289306 0.290030 0.290759 0.291485 0.292210 0.293057 0.293902 0.294745 0.295593 0.296438 0.297281 0.298222 0.299160 0.300095 0.301144 0.302189 0.303231
    1.5 0.417227 0.412212 0.407135 0.401995 0.398259 0.394489 0.390682 0.386196 0.381657 0.377063 0.373363 0.369627 0.365852 0.363157 0.360442 0.357707 0.354157 0.350571 0.346949 0.344090 0.341207 0.338299 0.336214 0.334116 0.332005 0.329412 0.326798 0.324164 0.321437 0.318688 0.315914 0.314231 0.312539 0.310837 0.309669 0.308497 0.307320 0.306277 0.305230 0.304180 0.303245 0.302307 0.301366 0.300273 0.299176 0.298075 0.297533 0.296990 0.296445 0.295766 0.295085 0.294403 0.293830 0.293255 0.292679 0.292227 0.291775 0.291321 0.291043 0.290764 0.290485 0.290403 0.290321 0.290239 0.289970 0.289700 0.289430 0.289243 0.289056 0.288869 0.288780 0.288690 0.288600 0.288464 0.288328 0.288192 0.288560 0.288927 0.289294 0.289457 0.289619 0.289781 0.289969 0.290156 0.290344 0.290532 0.290720 0.290907 0.291385 0.291862 0.292339 0.292817 0.293294 0.293771 0.294064 0.294358 0.294651 0.294945 0.295238 0.295531
    In [97]:
    def vol_smile_plot(vol_surface, plot_years, plot_strikes):
        
        plot_years.append(plot_years[0])
        np.array(plot_years)
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        
        df = pd.DataFrame(Z,index=plot_years,columns=plot_strikes)
        df = df.iloc[0:-1,:]
        
        ax = plt.axes()
        ax.plot(df.T)
        plt.legend(df.index);
        ax.set(xlabel='Strike', ylabel='Implied Volatility', title='Volatility Smile for specified terms to expiry');
     
    
    In [98]:
    list_tte = [0.5,1, 1.5]
    
    vol_smile_plot(vol_surface = black_var_surface, 
                                     plot_years=list_tte, 
                                     plot_strikes=strikes_grid)
    

    Note that the volatility skew is relatively more pronounced for shorter expiries than longer expiries


    H) The volatility surface: Implied Volatilities as a function of strike and time to expiry

    In [99]:
    def matrix_vol_surface_strike(vol_surface, plot_years, plot_strikes):
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        df = pd.DataFrame(Z,index=plot_years,columns=plot_strikes)
        return df
    
    In [100]:
    # Surface as dataframe
    df_vol_surf_strike = matrix_vol_surface_strike(vol_surface = black_var_surface,
                                                   plot_years=np.arange(0.25, 2, 0.1), 
                                                   plot_strikes=strikes_grid)
    df_vol_surf_strike
    
    Out[100]:
    65.000000 66.666667 68.333333 70.000000 71.666667 73.333333 75.000000 76.666667 78.333333 80.000000 81.666667 83.333333 85.000000 86.666667 88.333333 90.000000 91.666667 93.333333 95.000000 96.666667 98.333333 100.000000 101.666667 103.333333 105.000000 106.666667 108.333333 110.000000 111.666667 113.333333 115.000000 116.666667 118.333333 120.000000 121.666667 123.333333 125.000000 126.666667 128.333333 130.000000 131.666667 133.333333 135.000000 136.666667 138.333333 140.000000 141.666667 143.333333 145.000000 146.666667 148.333333 150.000000 151.666667 153.333333 155.000000 156.666667 158.333333 160.000000 161.666667 163.333333 165.000000 166.666667 168.333333 170.000000 171.666667 173.333333 175.000000 176.666667 178.333333 180.000000 181.666667 183.333333 185.000000 186.666667 188.333333 190.000000 191.666667 193.333333 195.000000 196.666667 198.333333 200.000000 201.666667 203.333333 205.000000 206.666667 208.333333 210.000000 211.666667 213.333333 215.000000 216.666667 218.333333 220.000000 221.666667 223.333333 225.000000 226.666667 228.333333 230.000000
    0.25 0.598149 0.594107 0.590039 0.585942 0.572442 0.558615 0.544438 0.531582 0.518407 0.504888 0.503508 0.502125 0.500737 0.491244 0.481564 0.471685 0.461737 0.451569 0.441168 0.432713 0.424091 0.415289 0.407587 0.399737 0.391730 0.384311 0.376745 0.369025 0.362553 0.355963 0.349250 0.343059 0.336754 0.330329 0.324673 0.318918 0.313056 0.308586 0.304050 0.299445 0.295294 0.291084 0.286811 0.284852 0.282878 0.280891 0.278471 0.276030 0.273567 0.271849 0.270119 0.268379 0.266791 0.265194 0.263588 0.263151 0.262714 0.262275 0.261950 0.261625 0.261299 0.261625 0.261950 0.262275 0.262683 0.263090 0.263496 0.264661 0.265821 0.266975 0.268571 0.270158 0.271736 0.273620 0.275492 0.277351 0.279690 0.282009 0.284309 0.286730 0.289130 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    0.35 0.598149 0.594107 0.590039 0.585942 0.572442 0.558615 0.544438 0.531582 0.518407 0.504888 0.503508 0.502125 0.500737 0.491244 0.481564 0.471685 0.461737 0.451569 0.441168 0.432713 0.424091 0.415289 0.407587 0.399737 0.391730 0.384311 0.376745 0.369025 0.362553 0.355963 0.349250 0.343059 0.336754 0.330329 0.324673 0.318918 0.313056 0.308586 0.304050 0.299445 0.295294 0.291084 0.286811 0.284852 0.282878 0.280891 0.278471 0.276030 0.273567 0.271849 0.270119 0.268379 0.266791 0.265194 0.263588 0.263151 0.262714 0.262275 0.261950 0.261625 0.261299 0.261625 0.261950 0.262275 0.262683 0.263090 0.263496 0.264661 0.265821 0.266975 0.268571 0.270158 0.271736 0.273620 0.275492 0.277351 0.279690 0.282009 0.284309 0.286730 0.289130 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    0.45 0.584158 0.577656 0.571080 0.564428 0.553088 0.541511 0.529680 0.522021 0.514248 0.506356 0.500422 0.494417 0.488338 0.479605 0.470711 0.461644 0.452413 0.442989 0.433361 0.425268 0.417018 0.408602 0.400547 0.392327 0.383930 0.377252 0.370453 0.363527 0.357121 0.350598 0.343951 0.338547 0.333056 0.327473 0.322074 0.316583 0.310995 0.306967 0.302884 0.298746 0.294735 0.290669 0.286545 0.284454 0.282347 0.280225 0.277860 0.275474 0.273067 0.271524 0.269973 0.268412 0.266951 0.265482 0.264005 0.263341 0.262676 0.262009 0.261817 0.261625 0.261432 0.261447 0.261462 0.261476 0.261895 0.262313 0.262730 0.263527 0.264322 0.265114 0.266465 0.267809 0.269146 0.270741 0.272326 0.273903 0.275862 0.277808 0.279741 0.281850 0.283944 0.286022 0.287058 0.288091 0.289119 0.290412 0.291699 0.292981 0.294242 0.295498 0.296749 0.298046 0.299337 0.300623 0.301839 0.303051 0.304258 0.305649 0.307034 0.308412
    0.55 0.558367 0.550608 0.542737 0.534752 0.525537 0.516158 0.506605 0.501837 0.497023 0.492162 0.483095 0.473854 0.464430 0.456290 0.448002 0.439558 0.431858 0.424019 0.416032 0.408569 0.400966 0.393217 0.386162 0.378976 0.371651 0.366162 0.360590 0.354930 0.348795 0.342550 0.336190 0.331955 0.327665 0.323319 0.318596 0.313802 0.308933 0.305758 0.302549 0.299306 0.295905 0.292464 0.288982 0.286766 0.284532 0.282281 0.280219 0.278143 0.276050 0.274858 0.273660 0.272457 0.271175 0.269886 0.268592 0.267806 0.267018 0.266227 0.266006 0.265784 0.265562 0.265425 0.265288 0.265152 0.265397 0.265643 0.265888 0.266410 0.266931 0.267452 0.268380 0.269305 0.270227 0.271212 0.272194 0.273173 0.274612 0.276043 0.277468 0.279047 0.280618 0.282180 0.283868 0.285546 0.287213 0.289249 0.291271 0.293278 0.295281 0.297271 0.299248 0.301439 0.303614 0.305774 0.307095 0.308410 0.309719 0.311228 0.312729 0.314223
    0.65 0.536935 0.530585 0.524158 0.517652 0.508770 0.499730 0.490523 0.483198 0.475760 0.468205 0.460192 0.452037 0.443732 0.435475 0.427058 0.418471 0.412235 0.405902 0.399469 0.392508 0.385421 0.378201 0.373207 0.368145 0.363012 0.358379 0.353685 0.348927 0.343123 0.337219 0.331209 0.327734 0.324222 0.320671 0.316642 0.312561 0.308426 0.305972 0.303498 0.301003 0.298329 0.295631 0.292908 0.290668 0.288410 0.286135 0.284450 0.282755 0.281049 0.280152 0.279252 0.278349 0.277172 0.275990 0.274803 0.274112 0.273418 0.272723 0.272300 0.271876 0.271452 0.271469 0.271487 0.271505 0.271489 0.271473 0.271457 0.272027 0.272596 0.273164 0.273781 0.274396 0.275011 0.275468 0.275924 0.276380 0.277529 0.278673 0.279813 0.281005 0.282191 0.283373 0.284707 0.286036 0.287358 0.288880 0.290395 0.291901 0.293463 0.295017 0.296563 0.298532 0.300489 0.302433 0.302456 0.302479 0.302502 0.302528 0.302555 0.302581
    0.75 0.509979 0.505357 0.500693 0.495985 0.488381 0.480656 0.472806 0.465144 0.457354 0.449429 0.442923 0.436320 0.429615 0.422114 0.414477 0.406697 0.400889 0.394995 0.389012 0.383150 0.377197 0.371148 0.366532 0.361857 0.357121 0.352761 0.348348 0.343877 0.339548 0.335163 0.330719 0.327243 0.323730 0.320178 0.316796 0.313377 0.309920 0.307621 0.305304 0.302970 0.300695 0.298402 0.296092 0.293985 0.291863 0.289726 0.288269 0.286805 0.285333 0.284460 0.283585 0.282707 0.281726 0.280741 0.279754 0.279078 0.278401 0.277723 0.277282 0.276840 0.276397 0.276330 0.276262 0.276194 0.276159 0.276124 0.276089 0.276312 0.276536 0.276758 0.277185 0.277610 0.278035 0.278469 0.278901 0.279334 0.280311 0.281286 0.282257 0.283091 0.283922 0.284751 0.285856 0.286957 0.288053 0.289256 0.290454 0.291647 0.292959 0.294266 0.295567 0.297098 0.298621 0.300137 0.300634 0.301131 0.301627 0.302205 0.302781 0.303357
    0.85 0.488153 0.484985 0.481796 0.478586 0.472046 0.465415 0.458689 0.450775 0.442721 0.434517 0.429254 0.423926 0.418531 0.411653 0.404658 0.397540 0.392060 0.386502 0.380863 0.375891 0.370852 0.365744 0.361403 0.357009 0.352560 0.348409 0.344207 0.339954 0.336801 0.333617 0.330403 0.326916 0.323392 0.319828 0.316943 0.314031 0.311092 0.308903 0.306699 0.304479 0.302502 0.300513 0.298510 0.296506 0.294488 0.292457 0.291170 0.289877 0.288579 0.287720 0.286860 0.285996 0.285163 0.284328 0.283490 0.282826 0.282160 0.281492 0.281040 0.280587 0.280133 0.279999 0.279864 0.279729 0.279683 0.279637 0.279591 0.279549 0.279508 0.279467 0.279751 0.280034 0.280318 0.280740 0.281162 0.281583 0.282433 0.283280 0.284125 0.284688 0.285250 0.285811 0.286741 0.287669 0.288594 0.289555 0.290512 0.291466 0.292589 0.293707 0.294821 0.296009 0.297193 0.298372 0.299264 0.300153 0.301040 0.302071 0.303099 0.304123
    0.95 0.472259 0.469353 0.466429 0.463486 0.457596 0.451628 0.445581 0.438025 0.430337 0.422509 0.417831 0.413100 0.408314 0.401999 0.395584 0.389063 0.384067 0.379005 0.373875 0.369527 0.365128 0.360675 0.356638 0.352554 0.348423 0.344718 0.340974 0.337188 0.334511 0.331813 0.329092 0.325956 0.322790 0.319592 0.316983 0.314352 0.311700 0.309691 0.307670 0.305635 0.303861 0.302077 0.300283 0.298495 0.296697 0.294887 0.293725 0.292558 0.291386 0.290556 0.289724 0.288889 0.288113 0.287335 0.286555 0.285932 0.285307 0.284681 0.284227 0.283772 0.283316 0.283177 0.283039 0.282901 0.282835 0.282770 0.282704 0.282561 0.282419 0.282276 0.282438 0.282601 0.282764 0.283120 0.283476 0.283832 0.284542 0.285250 0.285956 0.286334 0.286711 0.287088 0.287878 0.288666 0.289451 0.290241 0.291029 0.291815 0.292756 0.293693 0.294628 0.295569 0.296507 0.297442 0.298441 0.299436 0.300428 0.301563 0.302692 0.303818
    1.05 0.460997 0.457362 0.453698 0.450004 0.444428 0.438781 0.433060 0.426370 0.419574 0.412667 0.408040 0.403359 0.398624 0.392835 0.386959 0.380992 0.376615 0.372186 0.367704 0.363778 0.359810 0.355797 0.352091 0.348346 0.344560 0.341494 0.338401 0.335280 0.332545 0.329788 0.327007 0.324504 0.321982 0.319439 0.316943 0.314427 0.311890 0.310117 0.308333 0.306539 0.304900 0.303251 0.301594 0.300109 0.298617 0.297117 0.296047 0.294974 0.293896 0.293104 0.292310 0.291514 0.290729 0.289943 0.289154 0.288595 0.288034 0.287472 0.287022 0.286571 0.286119 0.286023 0.285928 0.285832 0.285741 0.285650 0.285559 0.285433 0.285307 0.285181 0.285238 0.285294 0.285350 0.285600 0.285850 0.286099 0.286660 0.287219 0.287778 0.288032 0.288286 0.288540 0.289212 0.289883 0.290553 0.291226 0.291897 0.292566 0.293330 0.294091 0.294851 0.295614 0.296376 0.297136 0.298024 0.298910 0.299792 0.300764 0.301733 0.302699
    1.15 0.451482 0.447214 0.442906 0.438555 0.433249 0.427877 0.422436 0.416497 0.410471 0.404355 0.399770 0.395132 0.390439 0.385100 0.379687 0.374195 0.370346 0.366457 0.362527 0.358960 0.355357 0.351717 0.348291 0.344831 0.341335 0.338808 0.336262 0.333696 0.330912 0.328105 0.325274 0.323299 0.321312 0.319313 0.316910 0.314488 0.312048 0.310468 0.308880 0.307284 0.305755 0.304218 0.302674 0.301436 0.300194 0.298946 0.297952 0.296954 0.295954 0.295193 0.294429 0.293664 0.292873 0.292080 0.291284 0.290776 0.290267 0.289757 0.289310 0.288863 0.288414 0.288353 0.288292 0.288231 0.288119 0.288007 0.287895 0.287783 0.287671 0.287559 0.287529 0.287500 0.287470 0.287633 0.287796 0.287959 0.288398 0.288836 0.289274 0.289427 0.289580 0.289734 0.290310 0.290885 0.291460 0.292036 0.292611 0.293186 0.293803 0.294420 0.295035 0.295652 0.296269 0.296884 0.297680 0.298474 0.299266 0.300103 0.300938 0.301771
    1.25 0.440033 0.435506 0.430932 0.426309 0.421540 0.416717 0.411838 0.406368 0.400824 0.395203 0.390903 0.386555 0.382158 0.377706 0.373202 0.368643 0.364886 0.361090 0.357253 0.353916 0.350548 0.347147 0.344178 0.341182 0.338160 0.335603 0.333026 0.330428 0.327655 0.324858 0.322037 0.320165 0.318281 0.316386 0.314403 0.312406 0.310397 0.309004 0.307605 0.306199 0.304871 0.303537 0.302198 0.301010 0.299818 0.298621 0.297784 0.296945 0.296103 0.295367 0.294628 0.293888 0.293170 0.292450 0.291729 0.291240 0.290751 0.290261 0.289872 0.289482 0.289092 0.289028 0.288963 0.288898 0.288729 0.288559 0.288389 0.288247 0.288105 0.287964 0.287915 0.287867 0.287819 0.287877 0.287934 0.287992 0.288409 0.288825 0.289241 0.289397 0.289553 0.289709 0.290152 0.290594 0.291035 0.291478 0.291920 0.292362 0.292933 0.293503 0.294072 0.294643 0.295214 0.295783 0.296406 0.297027 0.297647 0.298298 0.298947 0.299595
    1.35 0.430019 0.425259 0.420446 0.415576 0.411290 0.406958 0.402580 0.397527 0.392408 0.387222 0.383175 0.379085 0.374951 0.371287 0.367586 0.363848 0.360168 0.356450 0.352693 0.349557 0.346393 0.343199 0.340629 0.338039 0.335429 0.332842 0.330235 0.327607 0.324843 0.322055 0.319243 0.317456 0.315660 0.313853 0.312235 0.310608 0.308973 0.307741 0.306504 0.305262 0.304107 0.302947 0.301783 0.300637 0.299487 0.298333 0.297630 0.296926 0.296220 0.295505 0.294787 0.294068 0.293413 0.292757 0.292098 0.291626 0.291154 0.290680 0.290341 0.290001 0.289660 0.289593 0.289525 0.289458 0.289238 0.289019 0.288799 0.288632 0.288465 0.288297 0.288234 0.288171 0.288108 0.288075 0.288043 0.288010 0.288409 0.288807 0.289205 0.289364 0.289523 0.289682 0.290010 0.290338 0.290665 0.290994 0.291322 0.291649 0.292181 0.292712 0.293243 0.293775 0.294306 0.294836 0.295310 0.295782 0.296255 0.296744 0.297232 0.297719
    1.45 0.421195 0.416223 0.411191 0.406097 0.402244 0.398353 0.394425 0.389743 0.385005 0.380208 0.376386 0.372526 0.368625 0.365662 0.362676 0.359664 0.356051 0.352402 0.348714 0.345755 0.342770 0.339759 0.337540 0.335305 0.333056 0.330443 0.327810 0.325155 0.322399 0.319619 0.316814 0.315103 0.313383 0.311653 0.310354 0.309049 0.307740 0.306647 0.305551 0.304451 0.303446 0.302437 0.301425 0.300315 0.299202 0.298084 0.297498 0.296910 0.296321 0.295624 0.294925 0.294224 0.293623 0.293020 0.292417 0.291959 0.291500 0.291041 0.290744 0.290447 0.290149 0.290079 0.290009 0.289939 0.289677 0.289415 0.289152 0.288963 0.288774 0.288584 0.288508 0.288432 0.288356 0.288246 0.288136 0.288026 0.288409 0.288792 0.289174 0.289336 0.289498 0.289659 0.289888 0.290117 0.290346 0.290576 0.290805 0.291034 0.291532 0.292029 0.292526 0.293024 0.293521 0.294017 0.294361 0.294705 0.295048 0.295397 0.295745 0.296093
    1.55 0.414165 0.409722 0.405231 0.400690 0.396165 0.391587 0.386956 0.382911 0.378824 0.374691 0.371299 0.367876 0.364421 0.361384 0.358321 0.355231 0.352092 0.348925 0.345728 0.343173 0.340598 0.338004 0.335770 0.333521 0.331257 0.329024 0.326776 0.324512 0.322099 0.319667 0.317217 0.315491 0.313757 0.312012 0.310813 0.309608 0.308399 0.307255 0.306106 0.304953 0.304109 0.303262 0.302414 0.301340 0.300263 0.299181 0.298592 0.298001 0.297408 0.296856 0.296303 0.295748 0.295237 0.294725 0.294211 0.293759 0.293307 0.292853 0.292587 0.292321 0.292054 0.291845 0.291635 0.291425 0.291274 0.291124 0.290973 0.290919 0.290865 0.290812 0.290624 0.290437 0.290249 0.290186 0.290123 0.290059 0.290324 0.290589 0.290853 0.291008 0.291163 0.291318 0.291500 0.291683 0.291865 0.292048 0.292230 0.292412 0.292821 0.293229 0.293637 0.294045 0.294454 0.294861 0.295196 0.295530 0.295864 0.296198 0.296532 0.296866
    1.65 0.408519 0.405132 0.401716 0.398270 0.392308 0.386253 0.380102 0.376877 0.373625 0.370343 0.367517 0.364669 0.361798 0.358135 0.354434 0.350695 0.348311 0.345912 0.343495 0.341493 0.339478 0.337452 0.334950 0.332429 0.329889 0.328310 0.326722 0.325127 0.323278 0.321418 0.319547 0.317749 0.315941 0.314122 0.312866 0.311605 0.310339 0.309014 0.307683 0.306346 0.305665 0.304984 0.304300 0.303262 0.302220 0.301175 0.300501 0.299825 0.299148 0.298822 0.298495 0.298168 0.297768 0.297367 0.296966 0.296515 0.296063 0.295610 0.295366 0.295121 0.294876 0.294440 0.294004 0.293566 0.293627 0.293688 0.293749 0.293931 0.294113 0.294295 0.293936 0.293576 0.293216 0.293282 0.293348 0.293414 0.293495 0.293577 0.293658 0.293800 0.293941 0.294083 0.294256 0.294429 0.294602 0.294775 0.294948 0.295121 0.295406 0.295690 0.295974 0.296259 0.296544 0.296828 0.297235 0.297642 0.298049 0.298456 0.298863 0.299269
    1.75 0.400708 0.397559 0.394386 0.391187 0.386137 0.381020 0.375833 0.372900 0.369945 0.366965 0.364122 0.361256 0.358368 0.354928 0.351455 0.347948 0.345694 0.343426 0.341143 0.339080 0.337004 0.334915 0.332682 0.330433 0.328169 0.326525 0.324871 0.323210 0.321578 0.319938 0.318289 0.316806 0.315315 0.313817 0.312623 0.311423 0.310220 0.309079 0.307934 0.306784 0.306082 0.305378 0.304672 0.303742 0.302809 0.301873 0.301314 0.300753 0.300192 0.299722 0.299251 0.298780 0.298532 0.298285 0.298037 0.297672 0.297307 0.296941 0.296621 0.296301 0.295980 0.295671 0.295361 0.295051 0.295065 0.295079 0.295093 0.295142 0.295191 0.295239 0.295060 0.294880 0.294700 0.294760 0.294819 0.294879 0.294960 0.295041 0.295123 0.295167 0.295210 0.295254 0.295438 0.295622 0.295806 0.295991 0.296175 0.296359 0.296595 0.296830 0.297066 0.297301 0.297537 0.297772 0.298182 0.298591 0.299000 0.299409 0.299818 0.300227
    1.85 0.393610 0.390682 0.387732 0.384759 0.380548 0.376290 0.371984 0.369317 0.366631 0.363926 0.361067 0.358186 0.355281 0.352044 0.348777 0.345479 0.343343 0.341194 0.339031 0.336913 0.334782 0.332637 0.330646 0.328643 0.326628 0.324924 0.323212 0.321490 0.320054 0.318612 0.317163 0.315962 0.314755 0.313545 0.312405 0.311261 0.310113 0.309137 0.308157 0.307175 0.306453 0.305729 0.305004 0.304169 0.303333 0.302494 0.302037 0.301579 0.301121 0.300523 0.299924 0.299324 0.299212 0.299101 0.298989 0.298701 0.298412 0.298124 0.297737 0.297350 0.296962 0.296765 0.296567 0.296369 0.296342 0.296315 0.296287 0.296218 0.296149 0.296079 0.296059 0.296039 0.296018 0.296072 0.296125 0.296179 0.296260 0.296342 0.296423 0.296380 0.296338 0.296295 0.296489 0.296683 0.296877 0.297071 0.297265 0.297459 0.297651 0.297843 0.298035 0.298228 0.298420 0.298612 0.299024 0.299435 0.299845 0.300257 0.300668 0.301079
    1.95 0.390387 0.387560 0.384711 0.381842 0.381490 0.381138 0.380785 0.377847 0.374886 0.371901 0.368728 0.365529 0.362301 0.359295 0.356264 0.353206 0.351022 0.348824 0.346612 0.341889 0.337101 0.332243 0.331415 0.330586 0.329754 0.328474 0.327189 0.325898 0.324214 0.322520 0.320818 0.318566 0.316298 0.314013 0.313350 0.312685 0.312018 0.310824 0.309626 0.308422 0.308017 0.307611 0.307204 0.306734 0.306264 0.305792 0.304727 0.303658 0.302584 0.302541 0.302497 0.302453 0.301703 0.300951 0.300198 0.300024 0.299850 0.299676 0.299396 0.299116 0.298836 0.299370 0.299903 0.300435 0.301054 0.301672 0.302289 0.301976 0.301662 0.301348 0.300747 0.300144 0.299539 0.300833 0.302122 0.303405 0.303513 0.303622 0.303730 0.302116 0.300494 0.298862 0.299477 0.300091 0.300703 0.300397 0.300090 0.299784 0.300697 0.301608 0.302515 0.303446 0.304373 0.305297 0.305903 0.306507 0.307111 0.307717 0.308322 0.308927
    In [101]:
    from matplotlib import rcParams
    rcParams['axes.labelpad'] = 20
    
    # Utility function to plot vol surfaces (passing in ql.BlackVarianceSurface objects). 
    # K, t serve to calculate and annotate specific imp vol on surface
    
    def plot_vol_surface_strike(K,t,vol_surface, plot_years, plot_strikes):
        fig = plt.figure(figsize=(14,10))
        fig.suptitle('Implied Volatility Surface - As Function of Time to Expiry and Strike', fontsize=20)
        ax = fig.gca(projection='3d')
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        
        
        surf = ax.plot_surface(X,Y,Z, rstride=2, cstride=2,
                           cmap=plt.cm.coolwarm, linewidth=0.5,
                           antialiased=True,alpha = 0.9)
        
        sig = vol_surface.blackVol(t, K)
    
        
        ax.text(K, t, sig, f'← K={K}, T={t}, σ={sig:.3f}', color='black', size=12)
        ax.set_xlabel('Strike')
        ax.set_ylabel('Expiration (Years)')
        ax.set_zlabel('Implied Volatility')
        ax.set(facecolor='white')
        
        ax.dist = 10
        
        fig.colorbar(surf, shrink=1, aspect=10)
    
    In [102]:
    plot_vol_surface_strike(K=spot, t=1.0,
                            vol_surface=black_var_surface,  
                            plot_years=np.arange(0.5, 2, 0.1), 
                            plot_strikes=strikes_grid)
    


    I) The volatility surface: Implied Volatilities as a function of moneyness and time to expiry

    In [103]:
    def matrix_vol_surface_moneyness(S,vol_surface, plot_years, plot_strikes):
        
        moneyness = plot_strikes/S
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        df = pd.DataFrame(Z,index=plot_years,columns=moneyness)
        return df
    
    In [104]:
    df_vol_surf_moneyness = matrix_vol_surface_moneyness(S=spot,
                                                   vol_surface = black_var_surface,
                                                   plot_years=np.arange(0.25, 2, 0.1), 
                                                   plot_strikes=strikes_grid)
    df_vol_surf_moneyness
    
    Out[104]:
    0.435453 0.446618 0.457783 0.468949 0.480114 0.491280 0.502445 0.513611 0.524776 0.535942 0.547107 0.558272 0.569438 0.580603 0.591769 0.602934 0.614100 0.625265 0.636431 0.647596 0.658762 0.669927 0.681092 0.692258 0.703423 0.714589 0.725754 0.736920 0.748085 0.759251 0.770416 0.781581 0.792747 0.803912 0.815078 0.826243 0.837409 0.848574 0.859740 0.870905 0.882071 0.893236 0.904401 0.915567 0.926732 0.937898 0.949063 0.960229 0.971394 0.982560 0.993725 1.004890 1.016056 1.027221 1.038387 1.049552 1.060718 1.071883 1.083049 1.094214 1.105380 1.116545 1.127710 1.138876 1.150041 1.161207 1.172372 1.183538 1.194703 1.205869 1.217034 1.228199 1.239365 1.250530 1.261696 1.272861 1.284027 1.295192 1.306358 1.317523 1.328689 1.339854 1.351019 1.362185 1.373350 1.384516 1.395681 1.406847 1.418012 1.429178 1.440343 1.451508 1.462674 1.473839 1.485005 1.496170 1.507336 1.518501 1.529667 1.540832
    0.25 0.598149 0.594107 0.590039 0.585942 0.572442 0.558615 0.544438 0.531582 0.518407 0.504888 0.503508 0.502125 0.500737 0.491244 0.481564 0.471685 0.461737 0.451569 0.441168 0.432713 0.424091 0.415289 0.407587 0.399737 0.391730 0.384311 0.376745 0.369025 0.362553 0.355963 0.349250 0.343059 0.336754 0.330329 0.324673 0.318918 0.313056 0.308586 0.304050 0.299445 0.295294 0.291084 0.286811 0.284852 0.282878 0.280891 0.278471 0.276030 0.273567 0.271849 0.270119 0.268379 0.266791 0.265194 0.263588 0.263151 0.262714 0.262275 0.261950 0.261625 0.261299 0.261625 0.261950 0.262275 0.262683 0.263090 0.263496 0.264661 0.265821 0.266975 0.268571 0.270158 0.271736 0.273620 0.275492 0.277351 0.279690 0.282009 0.284309 0.286730 0.289130 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    0.35 0.598149 0.594107 0.590039 0.585942 0.572442 0.558615 0.544438 0.531582 0.518407 0.504888 0.503508 0.502125 0.500737 0.491244 0.481564 0.471685 0.461737 0.451569 0.441168 0.432713 0.424091 0.415289 0.407587 0.399737 0.391730 0.384311 0.376745 0.369025 0.362553 0.355963 0.349250 0.343059 0.336754 0.330329 0.324673 0.318918 0.313056 0.308586 0.304050 0.299445 0.295294 0.291084 0.286811 0.284852 0.282878 0.280891 0.278471 0.276030 0.273567 0.271849 0.270119 0.268379 0.266791 0.265194 0.263588 0.263151 0.262714 0.262275 0.261950 0.261625 0.261299 0.261625 0.261950 0.262275 0.262683 0.263090 0.263496 0.264661 0.265821 0.266975 0.268571 0.270158 0.271736 0.273620 0.275492 0.277351 0.279690 0.282009 0.284309 0.286730 0.289130 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511 0.291511
    0.45 0.584158 0.577656 0.571080 0.564428 0.553088 0.541511 0.529680 0.522021 0.514248 0.506356 0.500422 0.494417 0.488338 0.479605 0.470711 0.461644 0.452413 0.442989 0.433361 0.425268 0.417018 0.408602 0.400547 0.392327 0.383930 0.377252 0.370453 0.363527 0.357121 0.350598 0.343951 0.338547 0.333056 0.327473 0.322074 0.316583 0.310995 0.306967 0.302884 0.298746 0.294735 0.290669 0.286545 0.284454 0.282347 0.280225 0.277860 0.275474 0.273067 0.271524 0.269973 0.268412 0.266951 0.265482 0.264005 0.263341 0.262676 0.262009 0.261817 0.261625 0.261432 0.261447 0.261462 0.261476 0.261895 0.262313 0.262730 0.263527 0.264322 0.265114 0.266465 0.267809 0.269146 0.270741 0.272326 0.273903 0.275862 0.277808 0.279741 0.281850 0.283944 0.286022 0.287058 0.288091 0.289119 0.290412 0.291699 0.292981 0.294242 0.295498 0.296749 0.298046 0.299337 0.300623 0.301839 0.303051 0.304258 0.305649 0.307034 0.308412
    0.55 0.558367 0.550608 0.542737 0.534752 0.525537 0.516158 0.506605 0.501837 0.497023 0.492162 0.483095 0.473854 0.464430 0.456290 0.448002 0.439558 0.431858 0.424019 0.416032 0.408569 0.400966 0.393217 0.386162 0.378976 0.371651 0.366162 0.360590 0.354930 0.348795 0.342550 0.336190 0.331955 0.327665 0.323319 0.318596 0.313802 0.308933 0.305758 0.302549 0.299306 0.295905 0.292464 0.288982 0.286766 0.284532 0.282281 0.280219 0.278143 0.276050 0.274858 0.273660 0.272457 0.271175 0.269886 0.268592 0.267806 0.267018 0.266227 0.266006 0.265784 0.265562 0.265425 0.265288 0.265152 0.265397 0.265643 0.265888 0.266410 0.266931 0.267452 0.268380 0.269305 0.270227 0.271212 0.272194 0.273173 0.274612 0.276043 0.277468 0.279047 0.280618 0.282180 0.283868 0.285546 0.287213 0.289249 0.291271 0.293278 0.295281 0.297271 0.299248 0.301439 0.303614 0.305774 0.307095 0.308410 0.309719 0.311228 0.312729 0.314223
    0.65 0.536935 0.530585 0.524158 0.517652 0.508770 0.499730 0.490523 0.483198 0.475760 0.468205 0.460192 0.452037 0.443732 0.435475 0.427058 0.418471 0.412235 0.405902 0.399469 0.392508 0.385421 0.378201 0.373207 0.368145 0.363012 0.358379 0.353685 0.348927 0.343123 0.337219 0.331209 0.327734 0.324222 0.320671 0.316642 0.312561 0.308426 0.305972 0.303498 0.301003 0.298329 0.295631 0.292908 0.290668 0.288410 0.286135 0.284450 0.282755 0.281049 0.280152 0.279252 0.278349 0.277172 0.275990 0.274803 0.274112 0.273418 0.272723 0.272300 0.271876 0.271452 0.271469 0.271487 0.271505 0.271489 0.271473 0.271457 0.272027 0.272596 0.273164 0.273781 0.274396 0.275011 0.275468 0.275924 0.276380 0.277529 0.278673 0.279813 0.281005 0.282191 0.283373 0.284707 0.286036 0.287358 0.288880 0.290395 0.291901 0.293463 0.295017 0.296563 0.298532 0.300489 0.302433 0.302456 0.302479 0.302502 0.302528 0.302555 0.302581
    0.75 0.509979 0.505357 0.500693 0.495985 0.488381 0.480656 0.472806 0.465144 0.457354 0.449429 0.442923 0.436320 0.429615 0.422114 0.414477 0.406697 0.400889 0.394995 0.389012 0.383150 0.377197 0.371148 0.366532 0.361857 0.357121 0.352761 0.348348 0.343877 0.339548 0.335163 0.330719 0.327243 0.323730 0.320178 0.316796 0.313377 0.309920 0.307621 0.305304 0.302970 0.300695 0.298402 0.296092 0.293985 0.291863 0.289726 0.288269 0.286805 0.285333 0.284460 0.283585 0.282707 0.281726 0.280741 0.279754 0.279078 0.278401 0.277723 0.277282 0.276840 0.276397 0.276330 0.276262 0.276194 0.276159 0.276124 0.276089 0.276312 0.276536 0.276758 0.277185 0.277610 0.278035 0.278469 0.278901 0.279334 0.280311 0.281286 0.282257 0.283091 0.283922 0.284751 0.285856 0.286957 0.288053 0.289256 0.290454 0.291647 0.292959 0.294266 0.295567 0.297098 0.298621 0.300137 0.300634 0.301131 0.301627 0.302205 0.302781 0.303357
    0.85 0.488153 0.484985 0.481796 0.478586 0.472046 0.465415 0.458689 0.450775 0.442721 0.434517 0.429254 0.423926 0.418531 0.411653 0.404658 0.397540 0.392060 0.386502 0.380863 0.375891 0.370852 0.365744 0.361403 0.357009 0.352560 0.348409 0.344207 0.339954 0.336801 0.333617 0.330403 0.326916 0.323392 0.319828 0.316943 0.314031 0.311092 0.308903 0.306699 0.304479 0.302502 0.300513 0.298510 0.296506 0.294488 0.292457 0.291170 0.289877 0.288579 0.287720 0.286860 0.285996 0.285163 0.284328 0.283490 0.282826 0.282160 0.281492 0.281040 0.280587 0.280133 0.279999 0.279864 0.279729 0.279683 0.279637 0.279591 0.279549 0.279508 0.279467 0.279751 0.280034 0.280318 0.280740 0.281162 0.281583 0.282433 0.283280 0.284125 0.284688 0.285250 0.285811 0.286741 0.287669 0.288594 0.289555 0.290512 0.291466 0.292589 0.293707 0.294821 0.296009 0.297193 0.298372 0.299264 0.300153 0.301040 0.302071 0.303099 0.304123
    0.95 0.472259 0.469353 0.466429 0.463486 0.457596 0.451628 0.445581 0.438025 0.430337 0.422509 0.417831 0.413100 0.408314 0.401999 0.395584 0.389063 0.384067 0.379005 0.373875 0.369527 0.365128 0.360675 0.356638 0.352554 0.348423 0.344718 0.340974 0.337188 0.334511 0.331813 0.329092 0.325956 0.322790 0.319592 0.316983 0.314352 0.311700 0.309691 0.307670 0.305635 0.303861 0.302077 0.300283 0.298495 0.296697 0.294887 0.293725 0.292558 0.291386 0.290556 0.289724 0.288889 0.288113 0.287335 0.286555 0.285932 0.285307 0.284681 0.284227 0.283772 0.283316 0.283177 0.283039 0.282901 0.282835 0.282770 0.282704 0.282561 0.282419 0.282276 0.282438 0.282601 0.282764 0.283120 0.283476 0.283832 0.284542 0.285250 0.285956 0.286334 0.286711 0.287088 0.287878 0.288666 0.289451 0.290241 0.291029 0.291815 0.292756 0.293693 0.294628 0.295569 0.296507 0.297442 0.298441 0.299436 0.300428 0.301563 0.302692 0.303818
    1.05 0.460997 0.457362 0.453698 0.450004 0.444428 0.438781 0.433060 0.426370 0.419574 0.412667 0.408040 0.403359 0.398624 0.392835 0.386959 0.380992 0.376615 0.372186 0.367704 0.363778 0.359810 0.355797 0.352091 0.348346 0.344560 0.341494 0.338401 0.335280 0.332545 0.329788 0.327007 0.324504 0.321982 0.319439 0.316943 0.314427 0.311890 0.310117 0.308333 0.306539 0.304900 0.303251 0.301594 0.300109 0.298617 0.297117 0.296047 0.294974 0.293896 0.293104 0.292310 0.291514 0.290729 0.289943 0.289154 0.288595 0.288034 0.287472 0.287022 0.286571 0.286119 0.286023 0.285928 0.285832 0.285741 0.285650 0.285559 0.285433 0.285307 0.285181 0.285238 0.285294 0.285350 0.285600 0.285850 0.286099 0.286660 0.287219 0.287778 0.288032 0.288286 0.288540 0.289212 0.289883 0.290553 0.291226 0.291897 0.292566 0.293330 0.294091 0.294851 0.295614 0.296376 0.297136 0.298024 0.298910 0.299792 0.300764 0.301733 0.302699
    1.15 0.451482 0.447214 0.442906 0.438555 0.433249 0.427877 0.422436 0.416497 0.410471 0.404355 0.399770 0.395132 0.390439 0.385100 0.379687 0.374195 0.370346 0.366457 0.362527 0.358960 0.355357 0.351717 0.348291 0.344831 0.341335 0.338808 0.336262 0.333696 0.330912 0.328105 0.325274 0.323299 0.321312 0.319313 0.316910 0.314488 0.312048 0.310468 0.308880 0.307284 0.305755 0.304218 0.302674 0.301436 0.300194 0.298946 0.297952 0.296954 0.295954 0.295193 0.294429 0.293664 0.292873 0.292080 0.291284 0.290776 0.290267 0.289757 0.289310 0.288863 0.288414 0.288353 0.288292 0.288231 0.288119 0.288007 0.287895 0.287783 0.287671 0.287559 0.287529 0.287500 0.287470 0.287633 0.287796 0.287959 0.288398 0.288836 0.289274 0.289427 0.289580 0.289734 0.290310 0.290885 0.291460 0.292036 0.292611 0.293186 0.293803 0.294420 0.295035 0.295652 0.296269 0.296884 0.297680 0.298474 0.299266 0.300103 0.300938 0.301771
    1.25 0.440033 0.435506 0.430932 0.426309 0.421540 0.416717 0.411838 0.406368 0.400824 0.395203 0.390903 0.386555 0.382158 0.377706 0.373202 0.368643 0.364886 0.361090 0.357253 0.353916 0.350548 0.347147 0.344178 0.341182 0.338160 0.335603 0.333026 0.330428 0.327655 0.324858 0.322037 0.320165 0.318281 0.316386 0.314403 0.312406 0.310397 0.309004 0.307605 0.306199 0.304871 0.303537 0.302198 0.301010 0.299818 0.298621 0.297784 0.296945 0.296103 0.295367 0.294628 0.293888 0.293170 0.292450 0.291729 0.291240 0.290751 0.290261 0.289872 0.289482 0.289092 0.289028 0.288963 0.288898 0.288729 0.288559 0.288389 0.288247 0.288105 0.287964 0.287915 0.287867 0.287819 0.287877 0.287934 0.287992 0.288409 0.288825 0.289241 0.289397 0.289553 0.289709 0.290152 0.290594 0.291035 0.291478 0.291920 0.292362 0.292933 0.293503 0.294072 0.294643 0.295214 0.295783 0.296406 0.297027 0.297647 0.298298 0.298947 0.299595
    1.35 0.430019 0.425259 0.420446 0.415576 0.411290 0.406958 0.402580 0.397527 0.392408 0.387222 0.383175 0.379085 0.374951 0.371287 0.367586 0.363848 0.360168 0.356450 0.352693 0.349557 0.346393 0.343199 0.340629 0.338039 0.335429 0.332842 0.330235 0.327607 0.324843 0.322055 0.319243 0.317456 0.315660 0.313853 0.312235 0.310608 0.308973 0.307741 0.306504 0.305262 0.304107 0.302947 0.301783 0.300637 0.299487 0.298333 0.297630 0.296926 0.296220 0.295505 0.294787 0.294068 0.293413 0.292757 0.292098 0.291626 0.291154 0.290680 0.290341 0.290001 0.289660 0.289593 0.289525 0.289458 0.289238 0.289019 0.288799 0.288632 0.288465 0.288297 0.288234 0.288171 0.288108 0.288075 0.288043 0.288010 0.288409 0.288807 0.289205 0.289364 0.289523 0.289682 0.290010 0.290338 0.290665 0.290994 0.291322 0.291649 0.292181 0.292712 0.293243 0.293775 0.294306 0.294836 0.295310 0.295782 0.296255 0.296744 0.297232 0.297719
    1.45 0.421195 0.416223 0.411191 0.406097 0.402244 0.398353 0.394425 0.389743 0.385005 0.380208 0.376386 0.372526 0.368625 0.365662 0.362676 0.359664 0.356051 0.352402 0.348714 0.345755 0.342770 0.339759 0.337540 0.335305 0.333056 0.330443 0.327810 0.325155 0.322399 0.319619 0.316814 0.315103 0.313383 0.311653 0.310354 0.309049 0.307740 0.306647 0.305551 0.304451 0.303446 0.302437 0.301425 0.300315 0.299202 0.298084 0.297498 0.296910 0.296321 0.295624 0.294925 0.294224 0.293623 0.293020 0.292417 0.291959 0.291500 0.291041 0.290744 0.290447 0.290149 0.290079 0.290009 0.289939 0.289677 0.289415 0.289152 0.288963 0.288774 0.288584 0.288508 0.288432 0.288356 0.288246 0.288136 0.288026 0.288409 0.288792 0.289174 0.289336 0.289498 0.289659 0.289888 0.290117 0.290346 0.290576 0.290805 0.291034 0.291532 0.292029 0.292526 0.293024 0.293521 0.294017 0.294361 0.294705 0.295048 0.295397 0.295745 0.296093
    1.55 0.414165 0.409722 0.405231 0.400690 0.396165 0.391587 0.386956 0.382911 0.378824 0.374691 0.371299 0.367876 0.364421 0.361384 0.358321 0.355231 0.352092 0.348925 0.345728 0.343173 0.340598 0.338004 0.335770 0.333521 0.331257 0.329024 0.326776 0.324512 0.322099 0.319667 0.317217 0.315491 0.313757 0.312012 0.310813 0.309608 0.308399 0.307255 0.306106 0.304953 0.304109 0.303262 0.302414 0.301340 0.300263 0.299181 0.298592 0.298001 0.297408 0.296856 0.296303 0.295748 0.295237 0.294725 0.294211 0.293759 0.293307 0.292853 0.292587 0.292321 0.292054 0.291845 0.291635 0.291425 0.291274 0.291124 0.290973 0.290919 0.290865 0.290812 0.290624 0.290437 0.290249 0.290186 0.290123 0.290059 0.290324 0.290589 0.290853 0.291008 0.291163 0.291318 0.291500 0.291683 0.291865 0.292048 0.292230 0.292412 0.292821 0.293229 0.293637 0.294045 0.294454 0.294861 0.295196 0.295530 0.295864 0.296198 0.296532 0.296866
    1.65 0.408519 0.405132 0.401716 0.398270 0.392308 0.386253 0.380102 0.376877 0.373625 0.370343 0.367517 0.364669 0.361798 0.358135 0.354434 0.350695 0.348311 0.345912 0.343495 0.341493 0.339478 0.337452 0.334950 0.332429 0.329889 0.328310 0.326722 0.325127 0.323278 0.321418 0.319547 0.317749 0.315941 0.314122 0.312866 0.311605 0.310339 0.309014 0.307683 0.306346 0.305665 0.304984 0.304300 0.303262 0.302220 0.301175 0.300501 0.299825 0.299148 0.298822 0.298495 0.298168 0.297768 0.297367 0.296966 0.296515 0.296063 0.295610 0.295366 0.295121 0.294876 0.294440 0.294004 0.293566 0.293627 0.293688 0.293749 0.293931 0.294113 0.294295 0.293936 0.293576 0.293216 0.293282 0.293348 0.293414 0.293495 0.293577 0.293658 0.293800 0.293941 0.294083 0.294256 0.294429 0.294602 0.294775 0.294948 0.295121 0.295406 0.295690 0.295974 0.296259 0.296544 0.296828 0.297235 0.297642 0.298049 0.298456 0.298863 0.299269
    1.75 0.400708 0.397559 0.394386 0.391187 0.386137 0.381020 0.375833 0.372900 0.369945 0.366965 0.364122 0.361256 0.358368 0.354928 0.351455 0.347948 0.345694 0.343426 0.341143 0.339080 0.337004 0.334915 0.332682 0.330433 0.328169 0.326525 0.324871 0.323210 0.321578 0.319938 0.318289 0.316806 0.315315 0.313817 0.312623 0.311423 0.310220 0.309079 0.307934 0.306784 0.306082 0.305378 0.304672 0.303742 0.302809 0.301873 0.301314 0.300753 0.300192 0.299722 0.299251 0.298780 0.298532 0.298285 0.298037 0.297672 0.297307 0.296941 0.296621 0.296301 0.295980 0.295671 0.295361 0.295051 0.295065 0.295079 0.295093 0.295142 0.295191 0.295239 0.295060 0.294880 0.294700 0.294760 0.294819 0.294879 0.294960 0.295041 0.295123 0.295167 0.295210 0.295254 0.295438 0.295622 0.295806 0.295991 0.296175 0.296359 0.296595 0.296830 0.297066 0.297301 0.297537 0.297772 0.298182 0.298591 0.299000 0.299409 0.299818 0.300227
    1.85 0.393610 0.390682 0.387732 0.384759 0.380548 0.376290 0.371984 0.369317 0.366631 0.363926 0.361067 0.358186 0.355281 0.352044 0.348777 0.345479 0.343343 0.341194 0.339031 0.336913 0.334782 0.332637 0.330646 0.328643 0.326628 0.324924 0.323212 0.321490 0.320054 0.318612 0.317163 0.315962 0.314755 0.313545 0.312405 0.311261 0.310113 0.309137 0.308157 0.307175 0.306453 0.305729 0.305004 0.304169 0.303333 0.302494 0.302037 0.301579 0.301121 0.300523 0.299924 0.299324 0.299212 0.299101 0.298989 0.298701 0.298412 0.298124 0.297737 0.297350 0.296962 0.296765 0.296567 0.296369 0.296342 0.296315 0.296287 0.296218 0.296149 0.296079 0.296059 0.296039 0.296018 0.296072 0.296125 0.296179 0.296260 0.296342 0.296423 0.296380 0.296338 0.296295 0.296489 0.296683 0.296877 0.297071 0.297265 0.297459 0.297651 0.297843 0.298035 0.298228 0.298420 0.298612 0.299024 0.299435 0.299845 0.300257 0.300668 0.301079
    1.95 0.390387 0.387560 0.384711 0.381842 0.381490 0.381138 0.380785 0.377847 0.374886 0.371901 0.368728 0.365529 0.362301 0.359295 0.356264 0.353206 0.351022 0.348824 0.346612 0.341889 0.337101 0.332243 0.331415 0.330586 0.329754 0.328474 0.327189 0.325898 0.324214 0.322520 0.320818 0.318566 0.316298 0.314013 0.313350 0.312685 0.312018 0.310824 0.309626 0.308422 0.308017 0.307611 0.307204 0.306734 0.306264 0.305792 0.304727 0.303658 0.302584 0.302541 0.302497 0.302453 0.301703 0.300951 0.300198 0.300024 0.299850 0.299676 0.299396 0.299116 0.298836 0.299370 0.299903 0.300435 0.301054 0.301672 0.302289 0.301976 0.301662 0.301348 0.300747 0.300144 0.299539 0.300833 0.302122 0.303405 0.303513 0.303622 0.303730 0.302116 0.300494 0.298862 0.299477 0.300091 0.300703 0.300397 0.300090 0.299784 0.300697 0.301608 0.302515 0.303446 0.304373 0.305297 0.305903 0.306507 0.307111 0.307717 0.308322 0.308927
    In [105]:
    from matplotlib import rcParams
    rcParams['axes.labelpad'] = 20
    
    # Utility function to plot vol surfaces (passing in ql.BlackVarianceSurface objects). 
    # K, t serve to calculate and annotate specific imp vol on surface
    
    def plot_vol_surface_moneyness(K,t, S, vol_surface, plot_years, plot_strikes):
        fig = plt.figure(figsize=(14,10))
        fig.suptitle('Implied Volatility Surface - As Function of Time to Expiry and Moneyness', fontsize=20)
        ax = fig.gca(projection='3d')
    
        moneyness = plot_strikes/S
    
        X, Y = np.meshgrid(plot_strikes, plot_years)
        Z = np.array([vol_surface.blackVol(float(y), float(x)) 
                      for xr, yr in zip(X, Y) 
                          for x, y in zip(xr,yr) ]
                     ).reshape(len(X), len(X[0]))
        
        
        surf = ax.plot_surface(moneyness,Y,Z, rstride=2, cstride=2,
                           cmap=plt.cm.coolwarm, linewidth=0.5,
                           antialiased=True,alpha = 0.9)
        
        sig = vol_surface.blackVol(t, K)
        
        moneyness_level = K/S
    
        
        ax.text(moneyness_level, t, sig, f'← K/S={K/S:.2f}, T={t}, σ={sig:.3f}', color='black', size=12)
        ax.set_xlabel('Strike')
        ax.set_ylabel('Expiration (Years)')
        ax.set_zlabel('Implied Volatility')
        ax.set(facecolor='white')
        
        ax.dist = 10
        
        fig.colorbar(surf, shrink=1, aspect=10)
    
    In [106]:
    plot_vol_surface_moneyness(S=spot, K=spot, t=1.0,
                            vol_surface=black_var_surface,  
                            plot_years=np.arange(0.5, 2, 0.1), 
                            plot_strikes=strikes_grid)
    


    J) Call-put implied volatility spread

    1) Retrieve put option chain

    In [107]:
    aapl_put
    
    Out[107]:
    contractSymbol lastTradeDate strike lastPrice bid ask change percentChange volume openInterest impliedVolatility inTheMoney contractSize currency
    0 AAPL210730P00065000 2021-07-22 17:10:30 65.0 0.01 0.00 0.01 0.000000 0.000000 23 55 2.250004 False REGULAR USD
    1 AAPL210730P00070000 2021-07-19 13:47:57 70.0 0.01 0.00 0.01 0.000000 0.000000 10 94 2.062505 False REGULAR USD
    2 AAPL210730P00075000 2021-07-21 18:47:09 75.0 0.01 0.00 0.01 0.000000 0.000000 35 75 1.875001 False REGULAR USD
    3 AAPL210730P00080000 2021-07-22 14:42:49 80.0 0.01 0.00 0.01 0.000000 0.000000 400 614 1.687502 False REGULAR USD
    4 AAPL210730P00085000 2021-07-22 18:52:42 85.0 0.01 0.00 0.01 0.000000 0.000000 411 832 1.531252 False REGULAR USD
    5 AAPL210730P00090000 2021-07-22 15:37:31 90.0 0.01 0.00 0.01 0.000000 0.000000 3 326 1.375003 False REGULAR USD
    6 AAPL210730P00095000 2021-07-22 17:58:24 95.0 0.01 0.00 0.01 0.000000 0.000000 380 3943 1.250004 False REGULAR USD
    7 AAPL210730P00100000 2021-07-23 18:01:24 100.0 0.01 0.00 0.01 0.000000 0.000000 2 3931 1.125004 False REGULAR USD
    8 AAPL210730P00105000 2021-07-23 17:49:25 105.0 0.01 0.00 0.01 0.000000 0.000000 208 1299 0.984375 False REGULAR USD
    9 AAPL210730P00110000 2021-07-26 16:15:23 110.0 0.01 0.00 0.01 0.000000 0.000000 23 2151 0.875001 False REGULAR USD
    10 AAPL210730P00113000 2021-07-26 14:50:30 113.0 0.01 0.00 0.01 -0.010000 -50.000000 3 840 0.781252 False REGULAR USD
    11 AAPL210730P00114000 2021-07-23 19:44:33 114.0 0.01 0.00 0.01 0.000000 0.000000 1 502 0.781252 False REGULAR USD
    12 AAPL210730P00115000 2021-07-26 15:01:52 115.0 0.01 0.00 0.01 -0.020000 -66.666670 12 1108 0.750003 False REGULAR USD
    13 AAPL210730P00116000 2021-07-23 19:08:26 116.0 0.01 0.00 0.01 0.000000 0.000000 5 918 0.718753 False REGULAR USD
    14 AAPL210730P00117000 2021-07-26 13:53:54 117.0 0.01 0.00 0.01 -0.010000 -50.000000 65 2238 0.703128 False REGULAR USD
    15 AAPL210730P00118000 2021-07-26 14:08:36 118.0 0.02 0.00 0.01 0.000000 0.000000 24 850 0.687503 False REGULAR USD
    16 AAPL210730P00119000 2021-07-26 15:12:41 119.0 0.01 0.00 0.01 0.000000 0.000000 65 1130 0.656253 False REGULAR USD
    17 AAPL210730P00120000 2021-07-26 14:23:04 120.0 0.01 0.01 0.02 -0.010000 -50.000000 143 2308 0.703128 False REGULAR USD
    18 AAPL210730P00121000 2021-07-26 16:31:56 121.0 0.02 0.01 0.02 0.000000 0.000000 6 2691 0.687503 False REGULAR USD
    19 AAPL210730P00122000 2021-07-26 15:59:18 122.0 0.01 0.01 0.02 -0.020000 -66.666670 12 1551 0.656253 False REGULAR USD
    20 AAPL210730P00123000 2021-07-26 14:02:46 123.0 0.02 0.01 0.02 -0.010000 -33.333336 85 3435 0.632816 False REGULAR USD
    21 AAPL210730P00124000 2021-07-26 16:32:38 124.0 0.01 0.01 0.02 -0.020000 -66.666670 53 1912 0.609379 False REGULAR USD
    22 AAPL210730P00125000 2021-07-26 16:30:36 125.0 0.02 0.01 0.02 -0.010000 -33.333336 127 5160 0.585942 False REGULAR USD
    23 AAPL210730P00126000 2021-07-26 16:08:52 126.0 0.02 0.01 0.02 -0.020000 -50.000000 55 1775 0.562504 False REGULAR USD
    24 AAPL210730P00127000 2021-07-26 16:11:56 127.0 0.02 0.02 0.03 -0.030000 -60.000004 431 2510 0.570317 False REGULAR USD
    25 AAPL210730P00128000 2021-07-26 15:39:17 128.0 0.03 0.02 0.03 -0.020000 -40.000004 197 1847 0.546880 False REGULAR USD
    26 AAPL210730P00129000 2021-07-26 16:30:30 129.0 0.03 0.02 0.03 -0.030000 -50.000000 125 2756 0.523442 False REGULAR USD
    27 AAPL210730P00130000 2021-07-26 16:23:33 130.0 0.03 0.04 0.04 -0.040000 -57.142853 748 13961 0.527348 False REGULAR USD
    28 AAPL210730P00131000 2021-07-26 16:32:37 131.0 0.05 0.03 0.04 -0.040000 -44.444447 1202 1801 0.503911 False REGULAR USD
    29 AAPL210730P00132000 2021-07-26 16:33:17 132.0 0.05 0.04 0.05 -0.050000 -50.000000 173 1750 0.492193 False REGULAR USD
    30 AAPL210730P00133000 2021-07-26 16:08:10 133.0 0.06 0.05 0.06 -0.050000 -45.454548 295 2979 0.478521 False REGULAR USD
    31 AAPL210730P00134000 2021-07-26 16:22:55 134.0 0.06 0.06 0.07 -0.070000 -53.846150 282 7454 0.462896 False REGULAR USD
    32 AAPL210730P00135000 2021-07-26 16:38:52 135.0 0.09 0.08 0.09 -0.080000 -47.058823 1293 5584 0.455084 False REGULAR USD
    33 AAPL210730P00136000 2021-07-26 16:37:48 136.0 0.10 0.09 0.10 -0.110000 -52.380950 2712 7929 0.433599 False REGULAR USD
    34 AAPL210730P00137000 2021-07-26 16:34:45 137.0 0.13 0.12 0.13 -0.110000 -45.833336 1179 3691 0.425787 False REGULAR USD
    35 AAPL210730P00138000 2021-07-26 16:37:54 138.0 0.16 0.15 0.16 -0.150000 -48.387100 1337 3688 0.413092 False REGULAR USD
    36 AAPL210730P00139000 2021-07-26 16:39:21 139.0 0.21 0.20 0.21 -0.170000 -44.736843 1951 4208 0.405768 False REGULAR USD
    37 AAPL210730P00140000 2021-07-26 16:37:33 140.0 0.28 0.26 0.27 -0.200000 -41.666664 3894 39748 0.396979 False REGULAR USD
    38 AAPL210730P00141000 2021-07-26 16:38:11 141.0 0.37 0.36 0.37 -0.240000 -39.344260 1858 7878 0.395514 False REGULAR USD
    39 AAPL210730P00142000 2021-07-26 16:37:54 142.0 0.50 0.49 0.50 -0.260000 -34.210526 2063 6404 0.394537 False REGULAR USD
    40 AAPL210730P00143000 2021-07-26 16:39:00 143.0 0.67 0.66 0.67 -0.330000 -33.000000 3053 5204 0.395026 False REGULAR USD
    41 AAPL210730P00144000 2021-07-26 16:38:11 144.0 0.89 0.89 0.90 -0.360000 -28.800001 2054 5887 0.398688 False REGULAR USD
    42 AAPL210730P00145000 2021-07-26 16:38:48 145.0 1.18 1.16 1.18 -0.390000 -24.840770 6675 11963 0.402594 False REGULAR USD
    43 AAPL210730P00146000 2021-07-26 16:39:17 146.0 1.51 1.52 1.53 -0.400000 -20.942408 3392 6920 0.409186 False REGULAR USD
    44 AAPL210730P00147000 2021-07-26 16:37:24 147.0 1.91 1.91 1.92 -0.440000 -18.723402 6335 8013 0.412848 False REGULAR USD
    45 AAPL210730P00148000 2021-07-26 16:38:15 148.0 2.38 2.35 2.37 -0.470000 -16.491220 7302 3425 0.417242 False REGULAR USD
    46 AAPL210730P00149000 2021-07-26 16:37:27 149.0 2.87 2.86 2.88 -0.480000 -14.328359 5023 2402 0.422125 False REGULAR USD
    47 AAPL210730P00150000 2021-07-26 16:37:37 150.0 3.41 3.40 3.45 -0.510000 -13.010204 4046 27911 0.427740 True REGULAR USD
    48 AAPL210730P00152500 2021-07-26 16:38:16 152.5 4.99 4.90 5.00 -0.460000 -8.440368 507 2259 0.427496 True REGULAR USD
    49 AAPL210730P00155000 2021-07-26 16:34:00 155.0 6.75 6.75 6.85 -0.600000 -8.163264 417 7380 0.428961 True REGULAR USD
    50 AAPL210730P00157500 2021-07-26 15:46:47 157.5 8.63 8.75 8.85 -0.970000 -10.104170 23 1050 0.409674 True REGULAR USD
    51 AAPL210730P00160000 2021-07-26 16:31:37 160.0 11.03 11.00 11.10 -0.730001 -6.207487 128 1164 0.402350 True REGULAR USD
    52 AAPL210730P00162500 2021-07-26 15:38:57 162.5 12.91 13.40 13.45 -1.430000 -9.972108 211 52 0.375006 True REGULAR USD
    53 AAPL210730P00165000 2021-07-26 14:19:43 165.0 15.70 15.80 15.85 -0.950000 -5.705705 109 61 0.000010 True REGULAR USD
    54 AAPL210730P00167500 2021-07-26 15:50:48 167.5 18.10 18.25 18.35 -1.100000 -5.729168 53 20 0.000010 True REGULAR USD
    55 AAPL210730P00170000 2021-07-26 14:06:05 170.0 20.49 20.75 20.85 -1.010000 -4.697676 49 742 0.000010 True REGULAR USD
    56 AAPL210730P00172500 2021-07-26 13:50:39 172.5 23.25 23.20 23.30 -0.799999 -3.326400 21 5 0.000010 True REGULAR USD
    57 AAPL210730P00175000 2021-07-26 14:59:12 175.0 25.65 25.75 25.80 -0.950001 -3.571431 35 49 0.000010 True REGULAR USD
    58 AAPL210730P00180000 2021-07-26 14:05:52 180.0 30.25 30.70 30.75 -6.250000 -17.123287 1 2 0.000010 True REGULAR USD
    59 AAPL210730P00185000 2021-07-23 19:59:13 185.0 36.45 35.70 35.75 0.000000 0.000000 43 25 0.000010 True REGULAR USD
    In [108]:
    # Set expiry list - filter by dates if desired
    expiry_list = expiration_dates
    
    
    # Initialise empty list to collect option vols and values
    data_putvols = []
    data_putvals = []
    
    # Set expiry list - filter by dates if desired
    
    for expiry in expiry_list:
        sub_set_p = aapl.option_chain(expiry).puts
        sub_set_p = sub_set_p[(sub_set_p.strike>60) & (sub_set_p.strike<300)]
        sub_set_p.set_index('strike', inplace=True)
        data_putvols.append(sub_set_p['impliedVolatility'])
        data_putvals.append(sub_set_p['ask'])
    
    In [109]:
    # Collect subset of implied vols in dataframe (filtered by expiration and strike)
    put_vols = pd.DataFrame(data_putvols)
    
    # Create datetime index for expiration dates to allow time interpolation
    put_vols.index = date_time_obj
    
    put_vols
    
    Out[109]:
    strike 61.25 62.50 63.75 65.00 66.25 67.50 70.00 72.50 75.00 76.25 77.50 78.75 80.00 81.25 82.50 83.75 85.00 86.25 87.50 90.00 92.50 95.00 97.50 100.00 102.50 103.75 105.00 106.25 107.50 108.75 110.00 111.25 112.50 113.75 115.00 116.25 117.50 118.75 120.00 121.25 122.50 125.00 126.25 127.50 128.75 130.00 135.00 140.00 145.00 150.00 155.00 160.00 165.00 170.00 175.00 180.00 185.00 190.00 195.00 200.00 205.00 210.00 215.00 220.00 225.00 230.00 235.00 240.00 245.00 250.00 255.00 260.00 265.00 270.00 280.00 290.00
    2021-12-17 NaN NaN NaN 0.610355 NaN NaN 0.580082 NaN 0.549321 NaN NaN NaN 0.519536 NaN NaN NaN 0.494146 NaN NaN 0.466314 NaN 0.439215 NaN 0.413580 NaN NaN 0.389532 NaN NaN NaN 0.366461 NaN NaN NaN 0.345832 NaN NaN NaN 0.328010 NaN NaN 0.311591 NaN NaN NaN 0.298591 0.287727 0.280281 0.273018 0.267891 0.265815 0.263191 0.262336 0.261726 0.265266 0.272163 0.275764 0.284919 NaN 0.295417 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
    2022-01-21 0.599125 0.593266 0.583989 0.578129 0.568852 0.562504 0.548833 0.532720 0.519780 NaN 0.506108 NaN 0.495611 NaN 0.481695 NaN 0.468999 NaN 0.455816 0.443609 0.429693 0.418463 0.406744 0.394537 0.384772 0.379279 0.374274 0.369025 0.364264 0.359259 0.353400 0.349128 0.344611 0.339850 0.335334 0.331428 0.327277 0.323310 0.319404 0.315559 0.312263 0.306648 0.302863 0.300422 0.297553 0.294258 0.284309 0.276558 0.270973 0.266823 0.262703 0.261390 0.258491 0.260383 0.261665 0.263435 0.265266 0.268501 0.275276 0.278816 0.285652 0.294197 0.301032 0.306037 0.308845 0.327888 0.00001 0.325202 0.00001 0.363776 0.00001 0.378180 0.00001 0.000010 0.00001 0.00001
    2022-03-18 NaN NaN NaN NaN NaN NaN NaN NaN 0.485357 NaN NaN NaN 0.461431 NaN NaN NaN 0.438116 NaN NaN 0.416998 NaN 0.396857 NaN 0.378424 NaN NaN 0.360724 NaN NaN NaN 0.345221 NaN NaN NaN 0.332282 NaN NaN NaN 0.319953 NaN NaN 0.309333 NaN NaN NaN 0.299262 0.292579 0.285835 0.280830 0.276069 0.274665 0.271522 0.271309 0.270271 0.270088 0.270271 0.276374 0.274238 0.278572 0.279914 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
    2022-06-17 NaN 0.501470 0.495611 0.489507 NaN 0.478521 0.467046 0.456548 0.446539 NaN 0.437017 NaN 0.427008 NaN 0.418219 NaN 0.409430 NaN 0.400885 0.392950 0.384894 0.377814 0.370001 0.363410 0.356452 0.352851 0.349860 0.346686 0.345099 0.341437 0.339301 0.335334 0.332709 0.331367 0.328315 0.326423 0.324287 0.321906 0.319251 0.317634 0.315803 0.311256 NaN NaN NaN 0.304817 0.298286 0.293403 0.289741 0.286323 0.284004 0.282081 0.280677 0.281135 0.278999 0.279487 0.278999 0.281318 0.281013 0.282081 NaN 0.288154 NaN 0.292243 0.295905 0.329597 NaN 0.301154 NaN 0.311408 0.00001 0.326057 NaN 0.000010 0.00001 0.00001
    2022-09-16 NaN 0.469244 NaN 0.458502 NaN 0.449590 0.440191 0.431890 0.423712 0.419195 0.421637 0.411017 0.407355 0.40296 0.399664 0.396124 0.392462 0.389166 0.385626 0.378302 0.372077 0.366034 0.360846 0.353156 0.348273 NaN 0.344123 NaN 0.339179 NaN 0.333503 NaN 0.329719 NaN 0.326301 NaN 0.322028 NaN 0.319190 NaN 0.315467 0.312965 NaN NaN NaN 0.307227 0.302619 0.298927 0.295143 0.292854 0.289741 0.289009 0.287635 0.286659 0.285438 0.284156 0.285743 0.284217 0.284309 0.286628 NaN 0.287239 NaN 0.290107 0.292365 0.295051 NaN 0.299018 NaN 0.305061 NaN 0.310432 NaN 0.000010 0.00001 0.00001
    2023-01-20 NaN NaN NaN 0.427618 NaN NaN 0.411505 NaN 0.396612 NaN NaN NaN 0.383063 NaN NaN NaN 0.370185 NaN NaN 0.359503 NaN 0.349982 NaN 0.339545 NaN NaN 0.330756 NaN NaN NaN 0.324073 NaN NaN NaN 0.316779 NaN NaN NaN 0.311652 NaN NaN 0.306373 NaN NaN NaN 0.301704 0.298255 0.295082 0.291419 0.290168 0.289161 0.287147 0.286293 0.285316 0.284416 0.283699 0.282569 0.281898 0.281135 0.281257 NaN 0.281562 NaN 0.282356 NaN 0.283943 NaN NaN NaN 0.285163 NaN 0.288276 NaN NaN NaN NaN
    2023-03-17 NaN NaN NaN 0.418463 NaN NaN 0.404303 NaN 0.391730 NaN NaN NaN 0.378180 NaN NaN NaN 0.366767 NaN NaN 0.357001 NaN 0.346198 NaN 0.338630 NaN NaN 0.330970 NaN NaN NaN 0.324073 NaN NaN NaN 0.317695 NaN NaN NaN 0.313270 NaN NaN 0.308753 NaN NaN NaN 0.304847 0.301429 0.299140 0.296516 0.295600 0.293769 0.292457 0.291053 0.290382 0.289192 0.494207 0.287727 0.286323 0.336219 0.285682 NaN 0.286018 NaN NaN NaN NaN NaN 0.288032 NaN NaN NaN 0.289436 NaN NaN NaN NaN
    2023-06-16 NaN NaN NaN 0.408087 NaN NaN 0.394537 NaN 0.382086 NaN NaN NaN 0.370490 NaN NaN NaN 0.360175 NaN NaN 0.351386 NaN 0.343055 NaN 0.334968 NaN NaN 0.327949 NaN NaN NaN 0.322578 NaN NaN NaN 0.317542 NaN NaN NaN 0.313483 NaN NaN 0.309394 NaN NaN NaN 0.306022 0.303199 0.300910 0.299110 0.296577 0.295875 0.294532 0.293891 0.292838 0.292030 0.290992 0.289741 0.289741 0.289131 0.288642 NaN 0.287757 NaN 0.288002 NaN 0.287727 NaN 0.287635 NaN 0.288459 NaN 0.288642 NaN 0.290107 NaN NaN
    2023-09-15 NaN NaN NaN NaN NaN NaN NaN NaN 0.375128 NaN NaN NaN NaN NaN NaN NaN 0.390875 NaN NaN 0.350715 NaN 0.378424 NaN 0.334418 NaN NaN 0.354163 NaN NaN NaN 0.323798 NaN NaN NaN 0.348609 NaN NaN NaN 0.337317 NaN NaN 0.337348 NaN NaN NaN 0.331306 0.322486 0.326469 0.323661 0.304603 0.316352 0.314277 NaN NaN NaN NaN 0.311362 NaN NaN 0.311256 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

    2) Obtain quoted volatility surface for most traded options

    In [110]:
    put_vols.columns.max(), put_vols.columns.min()
    
    Out[110]:
    (290.0, 61.25)
    In [111]:
    sel_K
    
    Out[111]:
    array([ 65,  70,  75,  80,  85,  90,  95, 100, 105, 110, 115, 120, 125,
           130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190,
           195, 200, 205, 210, 215, 220, 225, 230])
    In [112]:
    put_vols = put_vols[sel_K]
    put_vols
    
    Out[112]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    2021-12-17 0.610355 0.580082 0.549321 0.519536 0.494146 0.466314 0.439215 0.413580 0.389532 0.366461 0.345832 0.328010 0.311591 0.298591 0.287727 0.280281 0.273018 0.267891 0.265815 0.263191 0.262336 0.261726 0.265266 0.272163 0.275764 0.284919 NaN 0.295417 NaN NaN NaN NaN NaN NaN
    2022-01-21 0.578129 0.548833 0.519780 0.495611 0.468999 0.443609 0.418463 0.394537 0.374274 0.353400 0.335334 0.319404 0.306648 0.294258 0.284309 0.276558 0.270973 0.266823 0.262703 0.261390 0.258491 0.260383 0.261665 0.263435 0.265266 0.268501 0.275276 0.278816 0.285652 0.294197 0.301032 0.306037 0.308845 0.327888
    2022-03-18 NaN NaN 0.485357 0.461431 0.438116 0.416998 0.396857 0.378424 0.360724 0.345221 0.332282 0.319953 0.309333 0.299262 0.292579 0.285835 0.280830 0.276069 0.274665 0.271522 0.271309 0.270271 0.270088 0.270271 0.276374 0.274238 0.278572 0.279914 NaN NaN NaN NaN NaN NaN
    2022-06-17 0.489507 0.467046 0.446539 0.427008 0.409430 0.392950 0.377814 0.363410 0.349860 0.339301 0.328315 0.319251 0.311256 0.304817 0.298286 0.293403 0.289741 0.286323 0.284004 0.282081 0.280677 0.281135 0.278999 0.279487 0.278999 0.281318 0.281013 0.282081 NaN 0.288154 NaN 0.292243 0.295905 0.329597
    2022-09-16 0.458502 0.440191 0.423712 0.407355 0.392462 0.378302 0.366034 0.353156 0.344123 0.333503 0.326301 0.319190 0.312965 0.307227 0.302619 0.298927 0.295143 0.292854 0.289741 0.289009 0.287635 0.286659 0.285438 0.284156 0.285743 0.284217 0.284309 0.286628 NaN 0.287239 NaN 0.290107 0.292365 0.295051
    2023-01-20 0.427618 0.411505 0.396612 0.383063 0.370185 0.359503 0.349982 0.339545 0.330756 0.324073 0.316779 0.311652 0.306373 0.301704 0.298255 0.295082 0.291419 0.290168 0.289161 0.287147 0.286293 0.285316 0.284416 0.283699 0.282569 0.281898 0.281135 0.281257 NaN 0.281562 NaN 0.282356 NaN 0.283943
    2023-03-17 0.418463 0.404303 0.391730 0.378180 0.366767 0.357001 0.346198 0.338630 0.330970 0.324073 0.317695 0.313270 0.308753 0.304847 0.301429 0.299140 0.296516 0.295600 0.293769 0.292457 0.291053 0.290382 0.289192 0.494207 0.287727 0.286323 0.336219 0.285682 NaN 0.286018 NaN NaN NaN NaN
    2023-06-16 0.408087 0.394537 0.382086 0.370490 0.360175 0.351386 0.343055 0.334968 0.327949 0.322578 0.317542 0.313483 0.309394 0.306022 0.303199 0.300910 0.299110 0.296577 0.295875 0.294532 0.293891 0.292838 0.292030 0.290992 0.289741 0.289741 0.289131 0.288642 NaN 0.287757 NaN 0.288002 NaN 0.287727
    2023-09-15 NaN NaN 0.375128 NaN 0.390875 0.350715 0.378424 0.334418 0.354163 0.323798 0.348609 0.337317 0.337348 0.331306 0.322486 0.326469 0.323661 0.304603 0.316352 0.314277 NaN NaN NaN NaN 0.311362 NaN NaN 0.311256 NaN NaN NaN NaN NaN NaN
    In [113]:
    # Interpolate filtered vols
    
    # First: Linear interpolation by strike (horizontally)
    put_vols = put_vols.interpolate(method='linear',axis=1)
    # Second: Time interpolation by expiration (vertically)
    put_vols = put_vols.interpolate(method='time',axis=0)
    put_vols
    
    #put_vols = put_vols.dropna("columns")
    #put_vols
    
    Out[113]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    2021-12-17 0.610355 0.580082 0.549321 0.519536 0.494146 0.466314 0.439215 0.413580 0.389532 0.366461 0.345832 0.328010 0.311591 0.298591 0.287727 0.280281 0.273018 0.267891 0.265815 0.263191 0.262336 0.261726 0.265266 0.272163 0.275764 0.284919 0.290168 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417
    2022-01-21 0.578129 0.548833 0.519780 0.495611 0.468999 0.443609 0.418463 0.394537 0.374274 0.353400 0.335334 0.319404 0.306648 0.294258 0.284309 0.276558 0.270973 0.266823 0.262703 0.261390 0.258491 0.260383 0.261665 0.263435 0.265266 0.268501 0.275276 0.278816 0.285652 0.294197 0.301032 0.306037 0.308845 0.327888
    2022-03-18 0.544368 0.517676 0.485357 0.461431 0.438116 0.416998 0.396857 0.378424 0.360724 0.345221 0.332282 0.319953 0.309333 0.299262 0.292579 0.285835 0.280830 0.276069 0.274665 0.271522 0.271309 0.270271 0.270088 0.270271 0.276374 0.274238 0.278572 0.279914 0.279914 0.279914 0.279914 0.279914 0.279914 0.279914
    2022-06-17 0.489507 0.467046 0.446539 0.427008 0.409430 0.392950 0.377814 0.363410 0.349860 0.339301 0.328315 0.319251 0.311256 0.304817 0.298286 0.293403 0.289741 0.286323 0.284004 0.282081 0.280677 0.281135 0.278999 0.279487 0.278999 0.281318 0.281013 0.282081 0.285118 0.288154 0.290199 0.292243 0.295905 0.329597
    2022-09-16 0.458502 0.440191 0.423712 0.407355 0.392462 0.378302 0.366034 0.353156 0.344123 0.333503 0.326301 0.319190 0.312965 0.307227 0.302619 0.298927 0.295143 0.292854 0.289741 0.289009 0.287635 0.286659 0.285438 0.284156 0.285743 0.284217 0.284309 0.286628 0.286933 0.287239 0.288673 0.290107 0.292365 0.295051
    2023-01-20 0.427618 0.411505 0.396612 0.383063 0.370185 0.359503 0.349982 0.339545 0.330756 0.324073 0.316779 0.311652 0.306373 0.301704 0.298255 0.295082 0.291419 0.290168 0.289161 0.287147 0.286293 0.285316 0.284416 0.283699 0.282569 0.281898 0.281135 0.281257 0.281410 0.281562 0.281959 0.282356 0.283149 0.283943
    2023-03-17 0.418463 0.404303 0.391730 0.378180 0.366767 0.357001 0.346198 0.338630 0.330970 0.324073 0.317695 0.313270 0.308753 0.304847 0.301429 0.299140 0.296516 0.295600 0.293769 0.292457 0.291053 0.290382 0.289192 0.494207 0.287727 0.286323 0.336219 0.285682 0.285850 0.286018 0.286018 0.286018 0.286018 0.286018
    2023-06-16 0.408087 0.394537 0.382086 0.370490 0.360175 0.351386 0.343055 0.334968 0.327949 0.322578 0.317542 0.313483 0.309394 0.306022 0.303199 0.300910 0.299110 0.296577 0.295875 0.294532 0.293891 0.292838 0.292030 0.290992 0.289741 0.289741 0.289131 0.288642 0.288200 0.287757 0.287879 0.288002 0.287864 0.287727
    2023-09-15 0.408087 0.394537 0.375128 0.383002 0.390875 0.350715 0.378424 0.334418 0.354163 0.323798 0.348609 0.337317 0.337348 0.331306 0.322486 0.326469 0.323661 0.304603 0.316352 0.314277 0.313694 0.313111 0.312528 0.311945 0.311362 0.311327 0.311291 0.311256 0.311256 0.311256 0.311256 0.311256 0.311256 0.311256

    3) Compute continuous volatility surface with QuantLib

    In [114]:
    # Quantlib datetime format 
    put_vols.index = [quantDate_obj]
    put_vols
    
    Out[114]:
    strike 65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    December 17th, 2021 0.610355 0.580082 0.549321 0.519536 0.494146 0.466314 0.439215 0.413580 0.389532 0.366461 0.345832 0.328010 0.311591 0.298591 0.287727 0.280281 0.273018 0.267891 0.265815 0.263191 0.262336 0.261726 0.265266 0.272163 0.275764 0.284919 0.290168 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417
    January 21st, 2022 0.578129 0.548833 0.519780 0.495611 0.468999 0.443609 0.418463 0.394537 0.374274 0.353400 0.335334 0.319404 0.306648 0.294258 0.284309 0.276558 0.270973 0.266823 0.262703 0.261390 0.258491 0.260383 0.261665 0.263435 0.265266 0.268501 0.275276 0.278816 0.285652 0.294197 0.301032 0.306037 0.308845 0.327888
    March 18th, 2022 0.544368 0.517676 0.485357 0.461431 0.438116 0.416998 0.396857 0.378424 0.360724 0.345221 0.332282 0.319953 0.309333 0.299262 0.292579 0.285835 0.280830 0.276069 0.274665 0.271522 0.271309 0.270271 0.270088 0.270271 0.276374 0.274238 0.278572 0.279914 0.279914 0.279914 0.279914 0.279914 0.279914 0.279914
    June 17th, 2022 0.489507 0.467046 0.446539 0.427008 0.409430 0.392950 0.377814 0.363410 0.349860 0.339301 0.328315 0.319251 0.311256 0.304817 0.298286 0.293403 0.289741 0.286323 0.284004 0.282081 0.280677 0.281135 0.278999 0.279487 0.278999 0.281318 0.281013 0.282081 0.285118 0.288154 0.290199 0.292243 0.295905 0.329597
    September 16th, 2022 0.458502 0.440191 0.423712 0.407355 0.392462 0.378302 0.366034 0.353156 0.344123 0.333503 0.326301 0.319190 0.312965 0.307227 0.302619 0.298927 0.295143 0.292854 0.289741 0.289009 0.287635 0.286659 0.285438 0.284156 0.285743 0.284217 0.284309 0.286628 0.286933 0.287239 0.288673 0.290107 0.292365 0.295051
    January 20th, 2023 0.427618 0.411505 0.396612 0.383063 0.370185 0.359503 0.349982 0.339545 0.330756 0.324073 0.316779 0.311652 0.306373 0.301704 0.298255 0.295082 0.291419 0.290168 0.289161 0.287147 0.286293 0.285316 0.284416 0.283699 0.282569 0.281898 0.281135 0.281257 0.281410 0.281562 0.281959 0.282356 0.283149 0.283943
    March 17th, 2023 0.418463 0.404303 0.391730 0.378180 0.366767 0.357001 0.346198 0.338630 0.330970 0.324073 0.317695 0.313270 0.308753 0.304847 0.301429 0.299140 0.296516 0.295600 0.293769 0.292457 0.291053 0.290382 0.289192 0.494207 0.287727 0.286323 0.336219 0.285682 0.285850 0.286018 0.286018 0.286018 0.286018 0.286018
    June 16th, 2023 0.408087 0.394537 0.382086 0.370490 0.360175 0.351386 0.343055 0.334968 0.327949 0.322578 0.317542 0.313483 0.309394 0.306022 0.303199 0.300910 0.299110 0.296577 0.295875 0.294532 0.293891 0.292838 0.292030 0.290992 0.289741 0.289741 0.289131 0.288642 0.288200 0.287757 0.287879 0.288002 0.287864 0.287727
    September 15th, 2023 0.408087 0.394537 0.375128 0.383002 0.390875 0.350715 0.378424 0.334418 0.354163 0.323798 0.348609 0.337317 0.337348 0.331306 0.322486 0.326469 0.323661 0.304603 0.316352 0.314277 0.313694 0.313111 0.312528 0.311945 0.311362 0.311327 0.311291 0.311256 0.311256 0.311256 0.311256 0.311256 0.311256 0.311256
    In [115]:
    # Collect implied vols in list of lists
    row_data_p = put_vols.values.tolist()
    
    In [116]:
    # Collect strikes in list and convert to float
    strikes_p = put_vols.columns.values.tolist()
    strikes_p = [float(i) for i in strikes_p]
    len(strikes_p)
    
    Out[116]:
    34
    In [117]:
    # Error Checking: 
    put_expiries_after_dropna = put_vols.index.values.tolist()
    len(put_expiries_after_dropna) == len(quantDate_obj)
    
    Out[117]:
    True
    In [118]:
    # Rearrange quoted vol matrix for input to black_var_surface
    implied_vols_p = ql.Matrix(len(strikes_p), len(quantDate_obj))
    for i in range(implied_vols_p.rows()):
        for j in range(implied_vols_p.columns()):
            implied_vols_p[i][j] = row_data_p[j][i]
    
    In [119]:
    # Create continously interpolated black variance surface
    black_var_surface_p = ql.BlackVarianceSurface(calculation_date, 
                                                  calendar, quantDate_obj, strikes_p, implied_vols_p, day_count)
    

    9) Compute ATM Volatility

    In [120]:
    # Obtain ATM implied volatility from surface with black_var_surface taking 2 arguments (time to expiry and strike)
    ATMstrike = spot
    ATMexpiry = 0.5# years
    ATMvol_p = black_var_surface_p.blackVol(ATMexpiry, ATMstrike)
    ATMvol_p
    
    Out[120]:
    0.26754307656069193
    In [121]:
    ATMvol - ATMvol_p
    
    Out[121]:
    0.001631802159518192
    In [122]:
    # As Dataframe
    df_vol_surf_strike_p = matrix_vol_surface_strike(vol_surface = black_var_surface_p, 
                                     plot_years=np.arange(0.25, 2, 0.1), 
                                     plot_strikes=strikes_grid)
    df_vol_surf_strike_p
    
    Out[122]:
    65.000000 66.666667 68.333333 70.000000 71.666667 73.333333 75.000000 76.666667 78.333333 80.000000 81.666667 83.333333 85.000000 86.666667 88.333333 90.000000 91.666667 93.333333 95.000000 96.666667 98.333333 100.000000 101.666667 103.333333 105.000000 106.666667 108.333333 110.000000 111.666667 113.333333 115.000000 116.666667 118.333333 120.000000 121.666667 123.333333 125.000000 126.666667 128.333333 130.000000 131.666667 133.333333 135.000000 136.666667 138.333333 140.000000 141.666667 143.333333 145.000000 146.666667 148.333333 150.000000 151.666667 153.333333 155.000000 156.666667 158.333333 160.000000 161.666667 163.333333 165.000000 166.666667 168.333333 170.000000 171.666667 173.333333 175.000000 176.666667 178.333333 180.000000 181.666667 183.333333 185.000000 186.666667 188.333333 190.000000 191.666667 193.333333 195.000000 196.666667 198.333333 200.000000 201.666667 203.333333 205.000000 206.666667 208.333333 210.000000 211.666667 213.333333 215.000000 216.666667 218.333333 220.000000 221.666667 223.333333 225.000000 226.666667 228.333333 230.000000
    0.25 0.610355 0.600434 0.590346 0.580082 0.570013 0.559763 0.549321 0.539575 0.529650 0.519536 0.511213 0.502752 0.494146 0.485046 0.475772 0.466314 0.457459 0.448430 0.439215 0.430839 0.422298 0.413580 0.405723 0.397710 0.389532 0.381997 0.374310 0.366461 0.359716 0.352842 0.345832 0.339995 0.334056 0.328010 0.322630 0.317159 0.311591 0.307319 0.302986 0.298591 0.295014 0.291393 0.287727 0.285266 0.282784 0.280281 0.277881 0.275460 0.273018 0.271319 0.269610 0.267891 0.267201 0.266509 0.265815 0.264944 0.264069 0.263191 0.262906 0.262622 0.262336 0.262133 0.261930 0.261726 0.262911 0.264091 0.265266 0.267585 0.269884 0.272163 0.273369 0.274569 0.275764 0.278849 0.281901 0.284919 0.286680 0.288429 0.290168 0.291928 0.293678 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417
    0.35 0.610355 0.600434 0.590346 0.580082 0.570013 0.559763 0.549321 0.539575 0.529650 0.519536 0.511213 0.502752 0.494146 0.485046 0.475772 0.466314 0.457459 0.448430 0.439215 0.430839 0.422298 0.413580 0.405723 0.397710 0.389532 0.381997 0.374310 0.366461 0.359716 0.352842 0.345832 0.339995 0.334056 0.328010 0.322630 0.317159 0.311591 0.307319 0.302986 0.298591 0.295014 0.291393 0.287727 0.285266 0.282784 0.280281 0.277881 0.275460 0.273018 0.271319 0.269610 0.267891 0.267201 0.266509 0.265815 0.264944 0.264069 0.263191 0.262906 0.262622 0.262336 0.262133 0.261930 0.261726 0.262911 0.264091 0.265266 0.267585 0.269884 0.272163 0.273369 0.274569 0.275764 0.278849 0.281901 0.284919 0.286680 0.288429 0.290168 0.291928 0.293678 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417 0.295417
    0.45 0.592973 0.583228 0.573317 0.563231 0.553463 0.543520 0.533391 0.524616 0.515692 0.506610 0.498083 0.489408 0.480576 0.471901 0.463064 0.454055 0.445541 0.436862 0.428006 0.419930 0.411695 0.403292 0.396089 0.388753 0.381275 0.374121 0.366828 0.359387 0.353088 0.346674 0.340139 0.334632 0.329034 0.323338 0.318599 0.313787 0.308901 0.304737 0.300515 0.296232 0.292817 0.289362 0.285865 0.283351 0.280814 0.278254 0.276153 0.274036 0.271903 0.270380 0.268848 0.267308 0.266249 0.265187 0.264120 0.263485 0.262848 0.262209 0.261556 0.260900 0.260244 0.260494 0.260744 0.260994 0.261767 0.262537 0.263306 0.264688 0.266064 0.267432 0.268318 0.269201 0.270082 0.272094 0.274091 0.276074 0.278108 0.280128 0.282133 0.283586 0.285032 0.286470 0.287694 0.288912 0.290125 0.291675 0.293217 0.294751 0.296005 0.297254 0.298497 0.299422 0.300344 0.301263 0.301785 0.302305 0.302824 0.306447 0.310027 0.313567
    0.55 0.564969 0.555700 0.546274 0.536682 0.526780 0.516688 0.506396 0.498504 0.490485 0.482333 0.474036 0.465591 0.456990 0.449213 0.441300 0.433242 0.425643 0.417906 0.410023 0.402887 0.395622 0.388221 0.381907 0.375487 0.368955 0.362803 0.356546 0.350176 0.344909 0.339560 0.334126 0.329362 0.324529 0.319622 0.315703 0.311735 0.307716 0.303944 0.300124 0.296254 0.293405 0.290527 0.287620 0.285193 0.282746 0.280277 0.278505 0.276722 0.274928 0.273470 0.272004 0.270531 0.269529 0.268524 0.267515 0.266831 0.266146 0.265459 0.264858 0.264257 0.263654 0.263887 0.264120 0.264353 0.264582 0.264811 0.265041 0.265417 0.265794 0.266170 0.267362 0.268549 0.269730 0.270085 0.270439 0.270793 0.272739 0.274670 0.276589 0.277480 0.278367 0.279252 0.280638 0.282016 0.283388 0.285140 0.286881 0.288612 0.290025 0.291432 0.292832 0.293873 0.294910 0.295944 0.296530 0.297114 0.297698 0.301764 0.305775 0.309735
    0.65 0.544726 0.535967 0.527063 0.518006 0.507473 0.496717 0.485723 0.477880 0.469906 0.461795 0.454145 0.446364 0.438444 0.431505 0.424452 0.417280 0.410659 0.403929 0.397085 0.391018 0.384856 0.378593 0.372778 0.366870 0.360866 0.355755 0.350570 0.345306 0.341030 0.336700 0.332314 0.328243 0.324122 0.319947 0.316440 0.312893 0.309306 0.305978 0.302614 0.299211 0.296990 0.294751 0.292495 0.290261 0.288010 0.285741 0.284081 0.282410 0.280730 0.279155 0.277570 0.275976 0.275500 0.275023 0.274545 0.273507 0.272466 0.271420 0.271340 0.271260 0.271180 0.270844 0.270508 0.270171 0.270115 0.270059 0.270003 0.270069 0.270135 0.270202 0.272237 0.274257 0.276263 0.275570 0.274876 0.274180 0.275640 0.277093 0.278538 0.278994 0.279449 0.279903 0.279927 0.279950 0.279974 0.280004 0.280035 0.280065 0.280090 0.280115 0.280139 0.280158 0.280177 0.280195 0.280206 0.280216 0.280227 0.280301 0.280375 0.280449
    0.75 0.519189 0.511065 0.502810 0.494418 0.485589 0.476596 0.467431 0.460240 0.452935 0.445510 0.438719 0.431822 0.424813 0.418579 0.412249 0.405822 0.399964 0.394018 0.387982 0.382538 0.377016 0.371411 0.366228 0.360971 0.355636 0.351292 0.346894 0.342439 0.338478 0.334471 0.330415 0.326857 0.323260 0.319622 0.316526 0.313400 0.310242 0.307486 0.304704 0.301897 0.299710 0.297507 0.295287 0.293349 0.291397 0.289433 0.287987 0.286533 0.285072 0.283707 0.282335 0.280957 0.280344 0.279729 0.279113 0.278264 0.277412 0.276558 0.276296 0.276034 0.275772 0.275666 0.275560 0.275454 0.275080 0.274706 0.274331 0.274441 0.274551 0.274661 0.275650 0.276635 0.277617 0.277612 0.277608 0.277604 0.278313 0.279021 0.279727 0.280132 0.280536 0.280940 0.281422 0.281903 0.282383 0.282868 0.283352 0.283835 0.284162 0.284490 0.284816 0.285145 0.285474 0.285802 0.286394 0.286985 0.287575 0.293285 0.298886 0.304384
    0.85 0.498580 0.490978 0.483255 0.475408 0.468024 0.460522 0.452896 0.446243 0.439490 0.432631 0.426542 0.420365 0.414096 0.408428 0.402680 0.396848 0.391600 0.386281 0.380887 0.375933 0.370913 0.365824 0.361145 0.356404 0.351600 0.347855 0.344071 0.340244 0.336520 0.332754 0.328945 0.325782 0.322588 0.319362 0.316584 0.313781 0.310953 0.308635 0.306299 0.303945 0.301776 0.299592 0.297391 0.295677 0.293954 0.292220 0.290936 0.289646 0.288351 0.287148 0.285940 0.284727 0.284003 0.283276 0.282548 0.281847 0.281144 0.280439 0.280032 0.279625 0.279217 0.279294 0.279370 0.279446 0.278835 0.278223 0.277609 0.277757 0.277904 0.278051 0.278229 0.278408 0.278586 0.279129 0.279670 0.280211 0.280350 0.280489 0.280629 0.280999 0.281370 0.281740 0.282596 0.283450 0.284302 0.285159 0.286015 0.286867 0.287445 0.288022 0.288598 0.289176 0.289753 0.290330 0.291369 0.292404 0.293436 0.303346 0.312943 0.322254
    0.95 0.482362 0.475297 0.468126 0.460843 0.454406 0.447878 0.441252 0.435074 0.428807 0.422447 0.416870 0.411217 0.405485 0.400240 0.394926 0.389539 0.384775 0.379950 0.375064 0.370440 0.365757 0.361013 0.356895 0.352729 0.348513 0.345025 0.341501 0.337940 0.334607 0.331241 0.327840 0.324998 0.322130 0.319237 0.316731 0.314206 0.311660 0.309584 0.307493 0.305388 0.303377 0.301353 0.299315 0.297791 0.296258 0.294717 0.293492 0.292262 0.291026 0.289981 0.288932 0.287879 0.287045 0.286209 0.285370 0.284825 0.284280 0.283733 0.283268 0.282803 0.282337 0.282374 0.282412 0.282450 0.281813 0.281174 0.280534 0.280555 0.280576 0.280597 0.280601 0.280604 0.280607 0.281074 0.281540 0.282006 0.281936 0.281865 0.281795 0.282252 0.282707 0.283162 0.283959 0.284755 0.285548 0.286347 0.287143 0.287938 0.288573 0.289207 0.289839 0.290474 0.291108 0.291740 0.292855 0.293966 0.295073 0.304232 0.313124 0.321770
    1.05 0.469482 0.462977 0.456378 0.449683 0.443790 0.437817 0.431762 0.426012 0.420184 0.414274 0.409059 0.403778 0.398426 0.393496 0.388502 0.383444 0.379068 0.374641 0.370160 0.365743 0.361272 0.356744 0.353239 0.349699 0.346123 0.342626 0.339094 0.335524 0.332707 0.329866 0.327000 0.324425 0.321829 0.319211 0.316948 0.314669 0.312373 0.310393 0.308400 0.306394 0.304647 0.302891 0.301124 0.299763 0.298397 0.297023 0.295781 0.294534 0.293281 0.292392 0.291501 0.290606 0.289662 0.288715 0.287765 0.287386 0.287006 0.286626 0.286165 0.285704 0.285242 0.285080 0.284918 0.284756 0.284246 0.283734 0.283222 0.282997 0.282772 0.282546 0.282839 0.283131 0.283423 0.283354 0.283285 0.283216 0.283201 0.283186 0.283171 0.283802 0.284432 0.285060 0.285476 0.285891 0.286305 0.286723 0.287140 0.287556 0.288106 0.288655 0.289203 0.289753 0.290302 0.290850 0.291768 0.292684 0.293598 0.298292 0.302915 0.307467
    1.15 0.458571 0.452546 0.446441 0.440251 0.434824 0.429329 0.423762 0.418379 0.412925 0.407398 0.402493 0.397528 0.392499 0.387835 0.383114 0.378334 0.374288 0.370197 0.366060 0.361817 0.357524 0.353178 0.350190 0.347176 0.344135 0.340632 0.337092 0.333515 0.331129 0.328726 0.326305 0.323951 0.321579 0.319190 0.317127 0.315051 0.312961 0.311060 0.309147 0.307222 0.305693 0.304155 0.302610 0.301383 0.300152 0.298915 0.297659 0.296398 0.295131 0.294369 0.293606 0.292840 0.291807 0.290769 0.289729 0.289484 0.289239 0.288994 0.288537 0.288079 0.287620 0.287296 0.286972 0.286647 0.286240 0.285832 0.285424 0.284999 0.284573 0.284146 0.284675 0.285202 0.285729 0.285224 0.284718 0.284211 0.284241 0.284272 0.284302 0.285076 0.285848 0.286619 0.286722 0.286826 0.286930 0.287033 0.287137 0.287241 0.287720 0.288198 0.288676 0.289156 0.289634 0.290112 0.290868 0.291621 0.292373 0.293295 0.294214 0.295130
    1.25 0.448109 0.442327 0.436469 0.430531 0.425281 0.419965 0.414582 0.409505 0.404365 0.399158 0.394475 0.389735 0.384938 0.380654 0.376321 0.371937 0.368193 0.364410 0.360588 0.356614 0.352595 0.348530 0.345572 0.342589 0.339580 0.336511 0.333413 0.330287 0.327893 0.325483 0.323054 0.320922 0.318776 0.316615 0.314659 0.312691 0.310711 0.308930 0.307139 0.305337 0.303939 0.302535 0.301124 0.299957 0.298785 0.297608 0.296366 0.295119 0.293866 0.293223 0.292578 0.291932 0.291137 0.290340 0.289541 0.289151 0.288760 0.288369 0.287971 0.287573 0.287174 0.286849 0.286523 0.286197 0.285827 0.285457 0.285086 0.284724 0.284362 0.283999 0.284218 0.284436 0.284655 0.284244 0.283833 0.283421 0.283354 0.283287 0.283220 0.283745 0.284268 0.284790 0.284875 0.284959 0.285044 0.285128 0.285213 0.285297 0.285658 0.286019 0.286379 0.286741 0.287102 0.287462 0.288051 0.288639 0.289226 0.289910 0.290593 0.291274
    1.35 0.438993 0.433423 0.427781 0.422063 0.416967 0.411807 0.406582 0.401776 0.396912 0.391987 0.387498 0.382957 0.378361 0.374412 0.370421 0.366386 0.362908 0.359395 0.355848 0.352109 0.348330 0.344510 0.341576 0.338618 0.335632 0.332944 0.330234 0.327501 0.325099 0.322680 0.320242 0.318303 0.316352 0.314388 0.312525 0.310650 0.308764 0.307087 0.305401 0.303705 0.302420 0.301130 0.299835 0.298718 0.297597 0.296472 0.295242 0.294007 0.292766 0.292225 0.291682 0.291138 0.290550 0.289961 0.289370 0.288854 0.288337 0.287819 0.287472 0.287125 0.286777 0.286452 0.286126 0.285800 0.285462 0.285123 0.284784 0.284478 0.284171 0.283864 0.283815 0.283766 0.283717 0.283390 0.283063 0.282736 0.282585 0.282434 0.282283 0.282590 0.282897 0.283204 0.283273 0.283343 0.283412 0.283481 0.283551 0.283620 0.283879 0.284137 0.284396 0.284655 0.284914 0.285172 0.285617 0.286060 0.286503 0.287001 0.287498 0.287994
    1.45 0.430981 0.425598 0.420147 0.414625 0.409664 0.404643 0.399558 0.394992 0.390372 0.385698 0.381381 0.377015 0.372598 0.368946 0.365258 0.361533 0.358288 0.355015 0.351710 0.348179 0.344611 0.341007 0.338094 0.335156 0.332192 0.329838 0.327468 0.325081 0.322671 0.320243 0.317797 0.316027 0.314247 0.312456 0.310673 0.308879 0.307075 0.305489 0.303894 0.302291 0.301105 0.299914 0.298719 0.297646 0.296570 0.295490 0.294270 0.293045 0.291814 0.291361 0.290907 0.290453 0.290043 0.289633 0.289222 0.288598 0.287971 0.287344 0.287041 0.286738 0.286434 0.286109 0.285784 0.285458 0.285147 0.284835 0.284524 0.284265 0.284006 0.283747 0.283467 0.283187 0.282906 0.282652 0.282398 0.282144 0.281920 0.281696 0.281472 0.281591 0.281710 0.281829 0.281885 0.281941 0.281998 0.282054 0.282110 0.282167 0.282336 0.282506 0.282675 0.282845 0.283014 0.283184 0.283501 0.283819 0.284136 0.284469 0.284802 0.285135
    1.55 0.424205 0.419139 0.414010 0.408817 0.404195 0.399519 0.394787 0.390323 0.385807 0.381238 0.377172 0.373062 0.368906 0.365492 0.362046 0.358566 0.355264 0.351932 0.348567 0.345473 0.342352 0.339202 0.336437 0.333648 0.330836 0.328597 0.326343 0.324073 0.321773 0.319457 0.317123 0.315511 0.313890 0.312260 0.310605 0.308941 0.307268 0.305815 0.304355 0.302887 0.301746 0.300600 0.299450 0.298507 0.297561 0.296611 0.295526 0.294436 0.293342 0.292968 0.292594 0.292219 0.291779 0.291340 0.290899 0.290318 0.289735 0.289151 0.288797 0.288443 0.288088 0.287802 0.287515 0.287228 0.286891 0.286554 0.286217 0.319259 0.349188 0.376747 0.348724 0.318244 0.284516 0.284200 0.283883 0.283567 0.290183 0.296652 0.302983 0.296448 0.289766 0.282926 0.282979 0.283031 0.283084 0.283137 0.283190 0.283243 0.283325 0.283407 0.283489 0.283571 0.283653 0.283736 0.283900 0.284065 0.284229 0.284394 0.284559 0.284723
    1.65 0.418430 0.413765 0.409046 0.404272 0.400125 0.395935 0.391700 0.387238 0.382724 0.378156 0.374391 0.370588 0.366746 0.363521 0.360267 0.356983 0.353422 0.349824 0.346188 0.343683 0.341160 0.338618 0.336085 0.333532 0.330960 0.328679 0.326382 0.324068 0.321958 0.319833 0.317694 0.316227 0.314752 0.313270 0.311773 0.310268 0.308755 0.307459 0.306158 0.304851 0.303716 0.302578 0.301435 0.300674 0.299911 0.299146 0.298275 0.297401 0.296524 0.296217 0.295911 0.295603 0.294996 0.294386 0.293776 0.293339 0.292902 0.292464 0.291997 0.291530 0.291062 0.290838 0.290614 0.290390 0.289994 0.289598 0.289201 0.370140 0.436314 0.493696 0.435990 0.369376 0.287733 0.287268 0.286801 0.286334 0.303822 0.320358 0.336080 0.320166 0.303419 0.285692 0.285747 0.285802 0.285857 0.285913 0.285968 0.286023 0.286024 0.286024 0.286024 0.286024 0.286024 0.286024 0.286024 0.286024 0.286024 0.286024 0.286023 0.286023
    1.75 0.413949 0.409369 0.404738 0.400053 0.395924 0.391752 0.387534 0.383346 0.379112 0.374830 0.371220 0.367575 0.363893 0.360806 0.357693 0.354552 0.351339 0.348097 0.344824 0.342246 0.339648 0.337030 0.334588 0.332128 0.329650 0.327586 0.325509 0.323419 0.321500 0.319570 0.317628 0.316213 0.314791 0.313363 0.311927 0.310484 0.309034 0.307815 0.306591 0.305362 0.304314 0.303262 0.302206 0.301445 0.300682 0.299917 0.299165 0.298411 0.297655 0.297114 0.296572 0.296029 0.295584 0.295139 0.294694 0.294252 0.293811 0.293368 0.293013 0.292657 0.292300 0.292021 0.291741 0.291461 0.291120 0.290780 0.290439 0.338150 0.379917 0.417526 0.379452 0.337106 0.288611 0.288349 0.288088 0.287826 0.297673 0.307206 0.316451 0.306943 0.297131 0.286983 0.286949 0.286916 0.286882 0.286848 0.286815 0.286781 0.286799 0.286817 0.286835 0.286853 0.286871 0.286889 0.286869 0.286848 0.286828 0.286808 0.286788 0.286768
    1.85 0.409910 0.405409 0.400857 0.396253 0.392139 0.387983 0.383781 0.379842 0.375861 0.371838 0.368369 0.364866 0.361330 0.358368 0.355381 0.352369 0.349471 0.346550 0.343603 0.340959 0.338294 0.335607 0.333247 0.330870 0.328476 0.326608 0.324728 0.322838 0.321091 0.319335 0.317569 0.316201 0.314826 0.313446 0.312065 0.310677 0.309283 0.308132 0.306977 0.305818 0.304846 0.303870 0.302892 0.302131 0.301368 0.300603 0.299957 0.299309 0.298660 0.297911 0.297160 0.296407 0.296108 0.295809 0.295510 0.295065 0.294619 0.294172 0.293915 0.293658 0.293400 0.293071 0.292742 0.292413 0.292122 0.291830 0.291538 0.306819 0.321373 0.335297 0.320726 0.305461 0.289392 0.289311 0.289230 0.289150 0.292080 0.294981 0.297854 0.294648 0.291407 0.288130 0.288017 0.287905 0.287793 0.287680 0.287568 0.287456 0.287489 0.287523 0.287557 0.287590 0.287624 0.287658 0.287620 0.287582 0.287544 0.287506 0.287468 0.287430
    1.95 0.408087 0.403621 0.399105 0.394537 0.389920 0.385248 0.380518 0.378149 0.375765 0.373366 0.371377 0.369378 0.367368 0.362070 0.356693 0.351234 0.351287 0.351341 0.351395 0.345965 0.340450 0.334843 0.334588 0.334334 0.334079 0.330380 0.326639 0.322855 0.323523 0.324189 0.324854 0.322930 0.320996 0.319049 0.318021 0.316990 0.315956 0.314623 0.313284 0.311940 0.310527 0.309108 0.307683 0.307421 0.307160 0.306898 0.306218 0.305538 0.304855 0.302725 0.300579 0.298417 0.299162 0.299904 0.300645 0.300140 0.299634 0.299128 0.298919 0.298710 0.298501 0.298188 0.297874 0.297561 0.297309 0.297058 0.296806 0.296497 0.296188 0.295878 0.295515 0.295151 0.294787 0.294784 0.294782 0.294779 0.294621 0.294464 0.294307 0.294180 0.294054 0.293927 0.293815 0.293703 0.293591 0.293480 0.293368 0.293256 0.293287 0.293317 0.293348 0.293379 0.293410 0.293441 0.293406 0.293372 0.293337 0.293302 0.293267 0.293233
    In [123]:
    # Call and put vol surfaces must have same index and column values.
    IV_spread = df_vol_surf_strike - df_vol_surf_strike_p
    IV_spread = pd.DataFrame(IV_spread)
    IV_spread.index = IV_spread.index.values.round(2)
    IV_spread
    
    Out[123]:
    65.000000 66.666667 68.333333 70.000000 71.666667 73.333333 75.000000 76.666667 78.333333 80.000000 81.666667 83.333333 85.000000 86.666667 88.333333 90.000000 91.666667 93.333333 95.000000 96.666667 98.333333 100.000000 101.666667 103.333333 105.000000 106.666667 108.333333 110.000000 111.666667 113.333333 115.000000 116.666667 118.333333 120.000000 121.666667 123.333333 125.000000 126.666667 128.333333 130.000000 131.666667 133.333333 135.000000 136.666667 138.333333 140.000000 141.666667 143.333333 145.000000 146.666667 148.333333 150.000000 151.666667 153.333333 155.000000 156.666667 158.333333 160.000000 161.666667 163.333333 165.000000 166.666667 168.333333 170.000000 171.666667 173.333333 175.000000 176.666667 178.333333 180.000000 181.666667 183.333333 185.000000 186.666667 188.333333 190.000000 191.666667 193.333333 195.000000 196.666667 198.333333 200.000000 201.666667 203.333333 205.000000 206.666667 208.333333 210.000000 211.666667 213.333333 215.000000 216.666667 218.333333 220.000000 221.666667 223.333333 225.000000 226.666667 228.333333 230.000000
    0.25 -0.012207 -0.006327 -0.000307 0.005859 0.002429 -0.001147 -0.004883 -0.007994 -0.011244 -0.014648 -0.007705 -0.000627 0.006592 0.006198 0.005792 0.005371 0.004277 0.003140 0.001953 0.001874 0.001793 0.001709 0.001865 0.002027 0.002197 0.002314 2.435414e-03 0.002563 0.002837 0.003121 0.003418 0.003064 0.002698 0.002319 0.002044 0.001759 0.001465 0.001267 0.001064 0.000854 0.000280 -0.000310 -0.000916 -0.000415 0.000094 0.000610 0.000590 0.000570 0.000549 0.000529 0.000509 0.000488 -0.000409 -0.001315 -0.002228 -0.001793 -0.001355 -0.000916 -0.000956 -0.000997 -0.001038 -0.000508 0.000021 0.000549 -0.000228 -0.001002 -0.001770 -0.002924 -0.004063 -0.005188 -0.004797 -0.004411 -0.004028 -0.005229 -0.006409 -0.007568 -0.006990 -0.006421 -0.005859 -0.005199 -0.004548 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906
    0.35 -0.012207 -0.006327 -0.000307 0.005859 0.002429 -0.001147 -0.004883 -0.007994 -0.011244 -0.014648 -0.007705 -0.000627 0.006592 0.006198 0.005792 0.005371 0.004277 0.003140 0.001953 0.001874 0.001793 0.001709 0.001865 0.002027 0.002197 0.002314 2.435414e-03 0.002563 0.002837 0.003121 0.003418 0.003064 0.002698 0.002319 0.002044 0.001759 0.001465 0.001267 0.001064 0.000854 0.000280 -0.000310 -0.000916 -0.000415 0.000094 0.000610 0.000590 0.000570 0.000549 0.000529 0.000509 0.000488 -0.000409 -0.001315 -0.002228 -0.001793 -0.001355 -0.000916 -0.000956 -0.000997 -0.001038 -0.000508 0.000021 0.000549 -0.000228 -0.001002 -0.001770 -0.002924 -0.004063 -0.005188 -0.004797 -0.004411 -0.004028 -0.005229 -0.006409 -0.007568 -0.006990 -0.006421 -0.005859 -0.005199 -0.004548 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906 -0.003906
    0.45 -0.008815 -0.005572 -0.002236 0.001197 -0.000375 -0.002009 -0.003711 -0.002594 -0.001443 -0.000254 0.002339 0.005009 0.007762 0.007704 0.007646 0.007589 0.006872 0.006127 0.005354 0.005338 0.005323 0.005310 0.004458 0.003574 0.002655 0.003130 3.624508e-03 0.004140 0.004033 0.003924 0.003812 0.003915 0.004022 0.004135 0.003475 0.002796 0.002094 0.002230 0.002370 0.002514 0.001918 0.001307 0.000679 0.001103 0.001534 0.001971 0.001707 0.001438 0.001164 0.001145 0.001125 0.001104 0.000702 0.000295 -0.000115 -0.000144 -0.000172 -0.000200 0.000261 0.000724 0.001189 0.000953 0.000718 0.000483 0.000129 -0.000224 -0.000575 -0.001161 -0.001742 -0.002318 -0.001853 -0.001392 -0.000935 -0.001353 -0.001765 -0.002171 -0.002246 -0.002319 -0.002392 -0.001736 -0.001088 -0.000448 -0.000635 -0.000821 -0.001005 -0.001263 -0.001518 -0.001771 -0.001763 -0.001756 -0.001748 -0.001376 -0.001007 -0.000641 0.000055 0.000747 0.001434 -0.000798 -0.002993 -0.005155
    0.55 -0.006603 -0.005093 -0.003536 -0.001930 -0.001243 -0.000531 0.000209 0.003333 0.006538 0.009829 0.009059 0.008263 0.007439 0.007076 0.006702 0.006317 0.006216 0.006113 0.006009 0.005682 0.005345 0.004996 0.004255 0.003489 0.002696 0.003359 4.043899e-03 0.004753 0.003886 0.002990 0.002064 0.002593 0.003137 0.003697 0.002892 0.002066 0.001217 0.001814 0.002425 0.003051 0.002500 0.001937 0.001362 0.001572 0.001786 0.002004 0.001714 0.001420 0.001122 0.001388 0.001656 0.001926 0.001645 0.001362 0.001076 0.000974 0.000872 0.000769 0.001147 0.001527 0.001908 0.001538 0.001169 0.000799 0.000815 0.000831 0.000847 0.000993 0.001138 0.001282 0.001018 0.000756 0.000496 0.001127 0.001755 0.002380 0.001873 0.001373 0.000879 0.001568 0.002251 0.002928 0.003230 0.003529 0.003825 0.004109 0.004389 0.004666 0.005256 0.005839 0.006416 0.007566 0.008704 0.009830 0.010565 0.011296 0.012022 0.009464 0.006954 0.004488
    0.65 -0.007791 -0.005382 -0.002905 -0.000354 0.001297 0.003013 0.004800 0.005318 0.005854 0.006410 0.006047 0.005673 0.005288 0.003970 0.002606 0.001191 0.001576 0.001973 0.002384 0.001490 0.000565 -0.000392 0.000429 0.001274 0.002146 0.002624 3.114885e-03 0.003621 0.002093 0.000519 -0.001104 -0.000509 0.000100 0.000723 0.000202 -0.000332 -0.000879 -0.000006 0.000884 0.001792 0.001340 0.000880 0.000413 0.000407 0.000400 0.000394 0.000369 0.000344 0.000319 0.000997 0.001682 0.002373 0.001672 0.000967 0.000258 0.000604 0.000953 0.001303 0.000960 0.000616 0.000272 0.000625 0.000979 0.001334 0.001374 0.001414 0.001455 0.001958 0.002461 0.002962 0.001544 0.000139 -0.001252 -0.000102 0.001049 0.002200 0.001889 0.001580 0.001275 0.002011 0.002742 0.003470 0.004781 0.006085 0.007384 0.008876 0.010360 0.011836 0.013373 0.014902 0.016423 0.018374 0.020313 0.022238 0.022250 0.022263 0.022275 0.022227 0.022180 0.022132
    0.75 -0.009210 -0.005708 -0.002117 0.001567 0.002792 0.004060 0.005375 0.004904 0.004419 0.003919 0.004204 0.004497 0.004802 0.003535 0.002228 0.000875 0.000925 0.000977 0.001030 0.000612 0.000181 -0.000263 0.000304 0.000886 0.001485 0.001469 1.454065e-03 0.001439 0.001070 0.000692 0.000304 0.000386 0.000470 0.000556 0.000269 -0.000023 -0.000322 0.000135 0.000600 0.001073 0.000984 0.000895 0.000805 0.000636 0.000466 0.000293 0.000283 0.000272 0.000261 0.000753 0.001249 0.001749 0.001382 0.001012 0.000640 0.000814 0.000989 0.001165 0.000986 0.000806 0.000626 0.000664 0.000702 0.000740 0.001079 0.001418 0.001758 0.001871 0.001984 0.002098 0.001535 0.000975 0.000418 0.000856 0.001294 0.001730 0.001998 0.002265 0.002530 0.002959 0.003386 0.003812 0.004435 0.005054 0.005670 0.006388 0.007102 0.007812 0.008797 0.009776 0.010750 0.011953 0.013147 0.014335 0.014240 0.014146 0.014052 0.008919 0.003895 -0.001027
    0.85 -0.010427 -0.005993 -0.001460 0.003178 0.004022 0.004893 0.005793 0.004532 0.003231 0.001886 0.002712 0.003561 0.004434 0.003225 0.001978 0.000691 0.000459 0.000221 -0.000024 -0.000042 -0.000061 -0.000080 0.000258 0.000605 0.000960 0.000553 1.368194e-04 -0.000289 0.000281 0.000863 0.001458 0.001134 0.000803 0.000466 0.000359 0.000250 0.000139 0.000269 0.000400 0.000534 0.000726 0.000921 0.001118 0.000828 0.000535 0.000237 0.000234 0.000231 0.000228 0.000572 0.000920 0.001270 0.001161 0.001051 0.000942 0.000979 0.001016 0.001054 0.001008 0.000962 0.000916 0.000705 0.000494 0.000283 0.000848 0.001414 0.001981 0.001793 0.001604 0.001416 0.001521 0.001627 0.001732 0.001611 0.001492 0.001372 0.002082 0.002790 0.003496 0.003688 0.003880 0.004071 0.004145 0.004219 0.004293 0.004395 0.004497 0.004599 0.005143 0.005685 0.006223 0.006833 0.007440 0.008042 0.007895 0.007749 0.007604 -0.001275 -0.009844 -0.018131
    0.95 -0.010103 -0.005944 -0.001697 0.002644 0.003189 0.003751 0.004329 0.002951 0.001530 0.000062 0.000961 0.001883 0.002829 0.001759 0.000658 -0.000477 -0.000708 -0.000946 -0.001190 -0.000913 -0.000629 -0.000338 -0.000257 -0.000175 -0.000090 -0.000306 -5.270001e-04 -0.000753 -0.000096 0.000572 0.001252 0.000959 0.000660 0.000355 0.000252 0.000146 0.000039 0.000108 0.000177 0.000247 0.000484 0.000724 0.000967 0.000704 0.000439 0.000170 0.000233 0.000296 0.000360 0.000575 0.000792 0.001009 0.001068 0.001126 0.001185 0.001107 0.001028 0.000948 0.000958 0.000969 0.000979 0.000803 0.000627 0.000451 0.001023 0.001596 0.002170 0.002007 0.001842 0.001678 0.001838 0.001997 0.002157 0.002046 0.001936 0.001826 0.002606 0.003384 0.004161 0.004082 0.004004 0.003926 0.003918 0.003911 0.003904 0.003895 0.003886 0.003877 0.004183 0.004487 0.004789 0.005095 0.005399 0.005701 0.005585 0.005470 0.005355 -0.002670 -0.010431 -0.017952
    1.05 -0.008486 -0.005615 -0.002680 0.000321 0.000638 0.000963 0.001297 0.000358 -0.000609 -0.001607 -0.001020 -0.000418 0.000198 -0.000661 -0.001543 -0.002451 -0.002453 -0.002455 -0.002457 -0.001965 -0.001462 -0.000947 -0.001148 -0.001353 -0.001563 -0.001132 -6.923747e-04 -0.000244 -0.000162 -0.000079 0.000006 0.000079 0.000153 0.000228 -0.000005 -0.000242 -0.000483 -0.000276 -0.000067 0.000145 0.000252 0.000361 0.000470 0.000346 0.000220 0.000093 0.000266 0.000440 0.000615 0.000712 0.000809 0.000907 0.001067 0.001228 0.001389 0.001209 0.001028 0.000846 0.000856 0.000867 0.000877 0.000943 0.001009 0.001076 0.001495 0.001915 0.002336 0.002436 0.002535 0.002635 0.002399 0.002163 0.001927 0.002246 0.002565 0.002884 0.003459 0.004034 0.004607 0.004230 0.003854 0.003480 0.003737 0.003993 0.004248 0.004503 0.004757 0.005010 0.005224 0.005436 0.005648 0.005862 0.006075 0.006287 0.006256 0.006225 0.006195 0.002472 -0.001182 -0.004769
    1.15 -0.007089 -0.005332 -0.003535 -0.001696 -0.001576 -0.001452 -0.001326 -0.001882 -0.002454 -0.003043 -0.002723 -0.002396 -0.002061 -0.002735 -0.003428 -0.004140 -0.003941 -0.003739 -0.003533 -0.002857 -0.002167 -0.001462 -0.001899 -0.002345 -0.002800 -0.001824 -8.308675e-04 0.000180 -0.000217 -0.000621 -0.001031 -0.000652 -0.000267 0.000123 -0.000218 -0.000563 -0.000913 -0.000592 -0.000267 0.000062 0.000062 0.000063 0.000064 0.000053 0.000042 0.000031 0.000293 0.000557 0.000823 0.000823 0.000824 0.000824 0.001067 0.001310 0.001555 0.001292 0.001028 0.000763 0.000774 0.000784 0.000794 0.001057 0.001320 0.001584 0.001879 0.002175 0.002471 0.002784 0.003098 0.003413 0.002855 0.002297 0.001741 0.002409 0.003078 0.003748 0.004157 0.004565 0.004972 0.004351 0.003732 0.003115 0.003588 0.004059 0.004530 0.005003 0.005474 0.005945 0.006083 0.006221 0.006358 0.006497 0.006634 0.006772 0.006812 0.006852 0.006893 0.006808 0.006724 0.006641
    1.25 -0.008076 -0.006821 -0.005537 -0.004222 -0.003741 -0.003248 -0.002744 -0.003137 -0.003540 -0.003955 -0.003572 -0.003181 -0.002780 -0.002947 -0.003118 -0.003294 -0.003307 -0.003321 -0.003335 -0.002698 -0.002047 -0.001383 -0.001395 -0.001407 -0.001420 -0.000908 -3.876077e-04 0.000142 -0.000238 -0.000624 -0.001017 -0.000758 -0.000495 -0.000229 -0.000257 -0.000285 -0.000313 0.000074 0.000466 0.000862 0.000932 0.001003 0.001074 0.001054 0.001033 0.001013 0.001418 0.001826 0.002237 0.002144 0.002050 0.001956 0.002033 0.002110 0.002187 0.002089 0.001991 0.001892 0.001901 0.001910 0.001918 0.002179 0.002440 0.002701 0.002901 0.003102 0.003302 0.003523 0.003744 0.003965 0.003698 0.003431 0.003165 0.003633 0.004102 0.004571 0.005055 0.005538 0.006021 0.005653 0.005285 0.004919 0.005277 0.005635 0.005991 0.006350 0.006707 0.007065 0.007275 0.007484 0.007693 0.007903 0.008112 0.008321 0.008355 0.008388 0.008422 0.008388 0.008354 0.008320
    1.35 -0.008974 -0.008164 -0.007335 -0.006487 -0.005677 -0.004849 -0.004003 -0.004250 -0.004504 -0.004765 -0.004323 -0.003871 -0.003410 -0.003125 -0.002834 -0.002538 -0.002739 -0.002945 -0.003155 -0.002552 -0.001938 -0.001311 -0.000948 -0.000579 -0.000204 -0.000102 9.497146e-07 0.000106 -0.000256 -0.000625 -0.000999 -0.000846 -0.000692 -0.000536 -0.000290 -0.000042 0.000209 0.000654 0.001103 0.001557 0.001686 0.001817 0.001948 0.001919 0.001890 0.001860 0.002388 0.002920 0.003454 0.003280 0.003105 0.002930 0.002863 0.002796 0.002728 0.002772 0.002817 0.002861 0.002869 0.002876 0.002883 0.003141 0.003399 0.003657 0.003776 0.003895 0.004015 0.004154 0.004294 0.004434 0.004419 0.004405 0.004391 0.004685 0.004980 0.005274 0.005824 0.006374 0.006923 0.006774 0.006626 0.006479 0.006737 0.006996 0.007253 0.007512 0.007771 0.008029 0.008302 0.008575 0.008847 0.009120 0.009392 0.009664 0.009693 0.009722 0.009751 0.009743 0.009734 0.009726
    1.45 -0.009785 -0.009375 -0.008956 -0.008528 -0.007420 -0.006289 -0.005133 -0.005249 -0.005367 -0.005490 -0.004995 -0.004489 -0.003973 -0.003284 -0.002583 -0.001869 -0.002237 -0.002613 -0.002996 -0.002424 -0.001841 -0.001247 -0.000554 0.000150 0.000864 0.000605 3.416183e-04 0.000074 -0.000273 -0.000625 -0.000983 -0.000924 -0.000864 -0.000804 -0.000319 0.000170 0.000665 0.001159 0.001657 0.002160 0.002341 0.002523 0.002706 0.002669 0.002632 0.002594 0.003228 0.003865 0.004507 0.004263 0.004017 0.003771 0.003579 0.003387 0.003194 0.003361 0.003529 0.003697 0.003703 0.003709 0.003715 0.003970 0.004225 0.004481 0.004530 0.004579 0.004629 0.004698 0.004768 0.004838 0.005042 0.005246 0.005450 0.005594 0.005738 0.005882 0.006489 0.007096 0.007702 0.007745 0.007788 0.007830 0.008003 0.008176 0.008349 0.008522 0.008695 0.008867 0.009196 0.009524 0.009851 0.010179 0.010507 0.010834 0.010860 0.010887 0.010913 0.010928 0.010943 0.010958
    1.55 -0.010041 -0.009417 -0.008779 -0.008127 -0.008030 -0.007931 -0.007831 -0.007412 -0.006984 -0.006547 -0.005872 -0.005185 -0.004484 -0.004108 -0.003725 -0.003335 -0.003173 -0.003007 -0.002839 -0.002301 -0.001754 -0.001198 -0.000666 -0.000127 0.000421 0.000427 4.329803e-04 0.000439 0.000326 0.000210 0.000093 -0.000019 -0.000133 -0.000248 0.000207 0.000667 0.001131 0.001440 0.001751 0.002065 0.002363 0.002662 0.002964 0.002833 0.002702 0.002570 0.003066 0.003565 0.004066 0.003888 0.003709 0.003530 0.003458 0.003385 0.003312 0.003442 0.003572 0.003702 0.003790 0.003878 0.003966 0.004043 0.004120 0.004198 0.004383 0.004569 0.004755 -0.028340 -0.058323 -0.085935 -0.058100 -0.027807 0.005733 0.005986 0.006239 0.006493 0.000141 -0.006064 -0.012130 -0.005440 0.001397 0.008392 0.008522 0.008651 0.008781 0.008911 0.009040 0.009170 0.009496 0.009822 0.010148 0.010474 0.010800 0.011126 0.011295 0.011465 0.011634 0.011804 0.011974 0.012143
    1.65 -0.009911 -0.008633 -0.007330 -0.006002 -0.007817 -0.009682 -0.011597 -0.010360 -0.009099 -0.007813 -0.006874 -0.005920 -0.004948 -0.005386 -0.005833 -0.006289 -0.005110 -0.003912 -0.002693 -0.002191 -0.001682 -0.001167 -0.001135 -0.001103 -0.001071 -0.000369 3.404713e-04 0.001059 0.001320 0.001585 0.001853 0.001523 0.001189 0.000852 0.001094 0.001338 0.001584 0.001554 0.001525 0.001495 0.001949 0.002406 0.002866 0.002588 0.002309 0.002029 0.002226 0.002424 0.002624 0.002604 0.002585 0.002565 0.002773 0.002981 0.003190 0.003176 0.003161 0.003146 0.003368 0.003591 0.003814 0.003602 0.003390 0.003177 0.003633 0.004090 0.004548 -0.076209 -0.142201 -0.199401 -0.142055 -0.075801 0.005482 0.006014 0.006547 0.007080 -0.010327 -0.026781 -0.042422 -0.026367 -0.009478 0.008391 0.008509 0.008627 0.008745 0.008862 0.008980 0.009098 0.009382 0.009667 0.009951 0.010235 0.010520 0.010804 0.011211 0.011618 0.012025 0.012432 0.012840 0.013246
    1.75 -0.013241 -0.011810 -0.010352 -0.008867 -0.009788 -0.010732 -0.011701 -0.010446 -0.009167 -0.007865 -0.007098 -0.006319 -0.005525 -0.005878 -0.006237 -0.006604 -0.005645 -0.004671 -0.003681 -0.003166 -0.002644 -0.002115 -0.001906 -0.001695 -0.001480 -0.001061 -6.374803e-04 -0.000209 0.000078 0.000368 0.000661 0.000593 0.000523 0.000454 0.000695 0.000940 0.001186 0.001264 0.001342 0.001422 0.001768 0.002116 0.002467 0.002297 0.002127 0.001956 0.002149 0.002342 0.002537 0.002608 0.002680 0.002751 0.002948 0.003146 0.003343 0.003420 0.003496 0.003573 0.003609 0.003644 0.003680 0.003650 0.003621 0.003591 0.003945 0.004300 0.004655 -0.043009 -0.084726 -0.122287 -0.084393 -0.042226 0.006089 0.006410 0.006732 0.007053 -0.002713 -0.012164 -0.021328 -0.011776 -0.001920 0.008271 0.008489 0.008707 0.008925 0.009142 0.009360 0.009578 0.009795 0.010013 0.010231 0.010448 0.010666 0.010884 0.011313 0.011743 0.012171 0.012601 0.013030 0.013459
    1.85 -0.016300 -0.014727 -0.013125 -0.011494 -0.011592 -0.011692 -0.011797 -0.010524 -0.009230 -0.007913 -0.007302 -0.006681 -0.006049 -0.006324 -0.006604 -0.006890 -0.006128 -0.005356 -0.004572 -0.004046 -0.003512 -0.002970 -0.002601 -0.002227 -0.001848 -0.001683 -1.516670e-03 -0.001348 -0.001037 -0.000723 -0.000405 -0.000239 -0.000071 0.000098 0.000340 0.000584 0.000831 0.001005 0.001180 0.001357 0.001607 0.001859 0.002112 0.002038 0.001965 0.001891 0.002080 0.002270 0.002460 0.002612 0.002764 0.002917 0.003104 0.003292 0.003479 0.003636 0.003794 0.003951 0.003822 0.003692 0.003562 0.003693 0.003825 0.003957 0.004220 0.004485 0.004749 -0.010601 -0.025225 -0.039218 -0.024667 -0.009422 0.006627 0.006761 0.006895 0.007029 0.004180 0.001361 -0.001431 0.001732 0.004930 0.008165 0.008471 0.008778 0.009084 0.009390 0.009697 0.010003 0.010162 0.010320 0.010479 0.010637 0.010796 0.010955 0.011404 0.011853 0.012301 0.012751 0.013200 0.013648
    1.95 -0.017700 -0.016061 -0.014393 -0.012695 -0.008430 -0.004110 0.000267 -0.000302 -0.000880 -0.001466 -0.002649 -0.003849 -0.005067 -0.002775 -0.000429 0.001973 -0.000265 -0.002517 -0.004783 -0.004076 -0.003349 -0.002600 -0.003173 -0.003748 -0.004325 -0.001906 5.496046e-04 0.003043 0.000691 -0.001669 -0.004036 -0.004365 -0.004698 -0.005036 -0.004672 -0.004305 -0.003937 -0.003798 -0.003659 -0.003518 -0.002510 -0.001497 -0.000478 -0.000687 -0.000896 -0.001105 -0.001492 -0.001880 -0.002271 -0.000184 0.001918 0.004035 0.002541 0.001047 -0.000447 -0.000116 0.000216 0.000548 0.000477 0.000406 0.000335 0.001182 0.002029 0.002875 0.003745 0.004614 0.005482 0.005478 0.005474 0.005470 0.005232 0.004992 0.004752 0.006049 0.007340 0.008626 0.008892 0.009158 0.009424 0.007936 0.006440 0.004935 0.005662 0.006387 0.007111 0.006917 0.006723 0.006528 0.007410 0.008290 0.009167 0.010066 0.010963 0.011857 0.012497 0.013136 0.013774 0.014415 0.015055 0.015694
    In [124]:
    IV_spread.mean(axis=1).plot( kind = 'bar')
    plt.ylabel('IV Spread')
    plt.xlabel('Time to Expiry')
    plt.title("Call-put implied volatility spread");
    


    K) Equity Skew

    1) View max and min moneyness to inform dimensions of vol surface

    In [125]:
    max_strike = strikes[-1]
    max_moneyness = strikes[-1]/spot
    min_strike = strikes[0]
    min_moneyness = strikes[0]/spot
    
    (max_strike, max_moneyness), (min_strike, min_moneyness), 
    
    Out[125]:
    ((230.0, 1.5408320493066254), (65.0, 0.43545253567361153))

    2) Create Dataframe for OTM CALL Moneyness

    In [126]:
    list_tte = [0.5,1, 1.5]
    
    
    OTM_call_moneyness = np.arange(1,1.4, 0.02)
    OTM_call_strikes = OTM_call_moneyness * spot
    
    
    OTM_call_vol = vol_smile(vol_surface = black_var_surface, 
                                     plot_years=list_tte, 
                                     plot_strikes=OTM_call_strikes)
    OTM_call_vol.columns = OTM_call_strikes / spot
    OTM_call_vol
    
    Out[126]:
    1.00 1.02 1.04 1.06 1.08 1.10 1.12 1.14 1.16 1.18 1.20 1.22 1.24 1.26 1.28 1.30 1.32 1.34 1.36 1.38
    0.5 0.269175 0.266725 0.264353 0.262829 0.261856 0.261703 0.261347 0.260982 0.261738 0.262539 0.263416 0.265111 0.267147 0.269540 0.272111 0.275016 0.278174 0.281428 0.284855 0.288533
    1.0 0.290627 0.289213 0.287837 0.286780 0.285821 0.285010 0.284639 0.284435 0.284294 0.284115 0.283875 0.283940 0.284142 0.284679 0.285428 0.286556 0.287296 0.287860 0.289164 0.290464
    1.5 0.294702 0.293627 0.292614 0.291804 0.291119 0.290620 0.290378 0.290212 0.289729 0.289303 0.288968 0.288756 0.288592 0.288349 0.288427 0.289085 0.289492 0.289783 0.290120 0.290456

    3) Create Dataframe for comparable OTM PUT moneyness

    In [127]:
    list_tte = [0.5,1, 1.5]
    
    # Ensure comparable moneyness of call and puts
    Init_OTM_put_moneyness = 1- (OTM_call_moneyness[-1]-1)
    
    OTM_put_moneyness = np.arange(Init_OTM_put_moneyness,1, 0.02)
    OTM_put_strikes = OTM_put_moneyness * spot
    
    OTM_put_vol = vol_smile(vol_surface = black_var_surface_p, 
                                     plot_years=list_tte, 
                                     plot_strikes=OTM_put_strikes)
    
    OTM_put_vol.columns = OTM_put_strikes / spot
    
    OTM_put_vol
    
    Out[127]:
    0.62 0.64 0.66 0.68 0.70 0.72 0.74 0.76 0.78 0.80 0.82 0.84 0.86 0.88 0.90 0.92 0.94 0.96 0.98 1.00
    0.5 0.430708 0.415740 0.401574 0.388379 0.376237 0.363968 0.351688 0.341000 0.330830 0.321306 0.313360 0.305740 0.298397 0.291659 0.285728 0.280829 0.276321 0.273000 0.270028 0.267543
    1.0 0.379356 0.371060 0.362898 0.355354 0.348455 0.342062 0.335832 0.330311 0.325081 0.320189 0.315791 0.311566 0.307922 0.304393 0.301015 0.298255 0.295698 0.293482 0.291470 0.289739
    1.5 0.354592 0.348783 0.342619 0.336909 0.331668 0.327469 0.323411 0.319084 0.315354 0.312301 0.309187 0.306086 0.303317 0.300865 0.298805 0.296888 0.294975 0.292811 0.291252 0.290511
    In [128]:
    len(OTM_call_vol) == len(OTM_put_vol)
    
    Out[128]:
    True

    4) Visualize skew as line graph

    In [129]:
    OTM_line = pd.concat([OTM_put_vol.iloc[:,0:-1], OTM_call_vol], axis=1)
    OTM_line
    
    Out[129]:
    0.62 0.64 0.66 0.68 0.70 0.72 0.74 0.76 0.78 0.80 0.82 0.84 0.86 0.88 0.90 0.92 0.94 0.96 0.98 1.00 1.02 1.04 1.06 1.08 1.10 1.12 1.14 1.16 1.18 1.20 1.22 1.24 1.26 1.28 1.30 1.32 1.34 1.36 1.38
    0.5 0.430708 0.415740 0.401574 0.388379 0.376237 0.363968 0.351688 0.341000 0.330830 0.321306 0.313360 0.305740 0.298397 0.291659 0.285728 0.280829 0.276321 0.273000 0.270028 0.269175 0.266725 0.264353 0.262829 0.261856 0.261703 0.261347 0.260982 0.261738 0.262539 0.263416 0.265111 0.267147 0.269540 0.272111 0.275016 0.278174 0.281428 0.284855 0.288533
    1.0 0.379356 0.371060 0.362898 0.355354 0.348455 0.342062 0.335832 0.330311 0.325081 0.320189 0.315791 0.311566 0.307922 0.304393 0.301015 0.298255 0.295698 0.293482 0.291470 0.290627 0.289213 0.287837 0.286780 0.285821 0.285010 0.284639 0.284435 0.284294 0.284115 0.283875 0.283940 0.284142 0.284679 0.285428 0.286556 0.287296 0.287860 0.289164 0.290464
    1.5 0.354592 0.348783 0.342619 0.336909 0.331668 0.327469 0.323411 0.319084 0.315354 0.312301 0.309187 0.306086 0.303317 0.300865 0.298805 0.296888 0.294975 0.292811 0.291252 0.294702 0.293627 0.292614 0.291804 0.291119 0.290620 0.290378 0.290212 0.289729 0.289303 0.288968 0.288756 0.288592 0.288349 0.288427 0.289085 0.289492 0.289783 0.290120 0.290456
    In [130]:
    OTM_line.T.plot(kind = 'line')
    plt.axvline(x=1, color='black',label='ATM',linestyle='dashed',linewidth=4);
    plt.ylabel('Equity Skew')
    plt.xlabel('Moneyness');
    

    5) Reverse values in OTM Call vols for calculation

    In [131]:
    rev_OTM_call_vol = OTM_call_vol.iloc[:, ::-1]
    np.array(rev_OTM_call_vol)
    
    Out[131]:
    array([[0.28853267, 0.28485471, 0.28142797, 0.27817352, 0.27501583,
            0.27211147, 0.26953988, 0.26714737, 0.26511143, 0.26341566,
            0.26253942, 0.26173775, 0.26098155, 0.26134654, 0.26170303,
            0.2618563 , 0.26282863, 0.26435339, 0.26672477, 0.26917488],
           [0.29046414, 0.28916354, 0.28786045, 0.28729598, 0.28655637,
            0.28542826, 0.28467929, 0.28414161, 0.28393975, 0.28387521,
            0.28411491, 0.28429384, 0.28443534, 0.28463918, 0.28500951,
            0.28582117, 0.28678001, 0.28783738, 0.28921333, 0.29062684],
           [0.29045582, 0.29011959, 0.28978319, 0.2894925 , 0.28908532,
            0.28842699, 0.28834856, 0.2885924 , 0.28875585, 0.28896776,
            0.28930262, 0.28972921, 0.29021185, 0.29037783, 0.29061979,
            0.29111902, 0.29180388, 0.29261415, 0.29362674, 0.29470202]])

    6) Calculate skew

    In [132]:
    equity_skew_val = pd.DataFrame(np.array(OTM_put_vol) - np.array(rev_OTM_call_vol))
    
    equity_skew_val.columns = (rev_OTM_call_vol.columns.values -1)
    
    equity_skew_val = equity_skew_val.iloc[:, ::-1]
    
    equity_skew_val.index = OTM_put_vol.index.values
    
    equity_skew_val
    
    Out[132]:
    0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14 0.16 0.18 0.20 0.22 0.24 0.26 0.28 0.30 0.32 0.34 0.36 0.38
    0.5 -0.001632 0.003303 0.008646 0.013492 0.018973 0.024025 0.030312 0.037416 0.044003 0.050821 0.057891 0.065719 0.073853 0.082149 0.091856 0.101221 0.110206 0.120146 0.130885 0.142175
    1.0 -0.000888 0.002257 0.005645 0.008918 0.012434 0.016005 0.019753 0.023486 0.027272 0.031676 0.036314 0.041141 0.046170 0.051152 0.056634 0.061898 0.068058 0.075038 0.081896 0.088892
    1.5 -0.004191 -0.002374 0.000197 0.003171 0.005769 0.008185 0.010487 0.013105 0.016357 0.019884 0.023333 0.026598 0.030491 0.035063 0.039042 0.042583 0.047417 0.052836 0.058663 0.064136
    In [133]:
    # set width of bar
    barWidth = 0.25
    fig = plt.subplots()
     
    # Set position of bar on X axis
    br1 = np.arange(len(equity_skew_val.iloc[0]))
    br2 = [x + barWidth for x in br1]
    br3 = [x + barWidth for x in br2]
     
    # Make the plot
    plt.bar(br1, equity_skew_val.iloc[0], color ='r', width = barWidth,
            edgecolor ='grey', label ='0.5 (years)')
    plt.bar(br2, equity_skew_val.iloc[1], color ='g', width = barWidth,
            edgecolor ='grey', label ='1 (year)')
    plt.bar(br3, equity_skew_val.iloc[2], color ='b', width = barWidth,
            edgecolor ='grey', label ='1.5 (years)')
     
    # Adding Xticks
    plt.xlabel('Out-of-Moneyness', fontweight ='bold', fontsize = 15)
    plt.ylabel('Equity Skew', fontweight ='bold', fontsize = 15)
    
    N = len(equity_skew_val.columns)
    ind = np.arange(N)
    
    xtick_moneyness = equity_skew_val.columns.values.tolist()
    xtick_moneyness = [float(i) for i in xtick_moneyness]
    xtick_moneyness = [round(num, 2) for num in xtick_moneyness ]
    
    plt.xticks(ind, xtick_moneyness)
    
    plt.legend()
    
    plt.title("Equity skew for 3 tenors and multiple out-of-moneyness levels",fontweight ='bold', fontsize = 15)
    plt.show()
    


    L) Butterfly Spread

    1) Compute spread for different tenors

    In [134]:
    bf0 = ((np.array(OTM_put_vol.iloc[0]) + np.array(rev_OTM_call_vol.iloc[0]))/2) - rev_OTM_call_vol.loc[:,1.0].iloc[0]
    bf1 =((np.array(OTM_put_vol.iloc[1]) + np.array(rev_OTM_call_vol.iloc[1]))/2) - rev_OTM_call_vol.loc[:,1.0].iloc[1]
    bf2 =((np.array(OTM_put_vol.iloc[2]) + np.array(rev_OTM_call_vol.iloc[2]))/2) - rev_OTM_call_vol.loc[:,1.0].iloc[2]
    

    2) Collect in dataframe

    In [135]:
    bf_spread_val = pd.DataFrame(np.stack((bf0, bf1,bf2), axis=0))
    bf_spread_val.columns = (rev_OTM_call_vol.columns.values -1)
    bf_spread_val = bf_spread_val.iloc[:, ::-1]
    bf_spread_val.index = OTM_call_vol.index.values
    bf_spread_val
    
    Out[135]:
    0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14 0.16 0.18 0.20 0.22 0.24 0.26 0.28 0.30 0.32 0.34 0.36 0.38
    0.5 -0.000816 -0.000799 -0.000498 0.000400 0.002168 0.004541 0.007328 0.010515 0.014564 0.018775 0.023186 0.028796 0.034899 0.041439 0.048865 0.056451 0.064102 0.072326 0.081122 0.090445
    1.0 -0.000444 -0.000285 0.000033 0.000612 0.001411 0.002385 0.003889 0.005552 0.007303 0.009326 0.011405 0.013884 0.016600 0.019629 0.023118 0.026879 0.030698 0.034753 0.039485 0.044283
    1.5 -0.002095 -0.002262 -0.001989 -0.001312 -0.000699 0.000010 0.000919 0.002063 0.003206 0.004543 0.005932 0.007353 0.009136 0.011178 0.013246 0.015675 0.018499 0.021499 0.024749 0.027822

    3) Visualize Butterfly Spread

    In [136]:
    # set width of bar
    barWidth = 0.25
    fig = plt.subplots()
     
    # set height of bar
    
     
    # Set position of bar on X axis
    br1 = np.arange(len(bf_spread_val.iloc[0]))
    br2 = [x + barWidth for x in br1]
    br3 = [x + barWidth for x in br2]
     
    # Make the plot
    plt.bar(br1, bf_spread_val.iloc[0], color ='r', width = barWidth,
            edgecolor ='grey', label ='0.5 (years)')
    plt.bar(br2, bf_spread_val.iloc[1], color ='g', width = barWidth,
            edgecolor ='grey', label ='1 (year)')
    plt.bar(br3, bf_spread_val.iloc[2], color ='b', width = barWidth,
            edgecolor ='grey', label ='1.5 (years)')
     
    # Adding Xticks
    plt.xlabel('Out-of-Moneyness', fontweight ='bold', fontsize = 15)
    plt.ylabel('Butterfly Spread', fontweight ='bold', fontsize = 15)
    
    N = len(bf_spread_val.columns)
    ind = np.arange(N)
    
    xtick_moneyness = bf_spread_val.columns.values.tolist()
    xtick_moneyness = [float(i) for i in xtick_moneyness]
    xtick_moneyness = [round(num, 2) for num in xtick_moneyness ]
    
    plt.xticks(ind, xtick_moneyness)
    
    plt.legend()
    
    plt.title("Butterfly Spread for 3 tenors and multiple out-of-moneyness levels",fontweight ='bold', fontsize = 15)
    plt.show()
    


    M) Distribution of stock prices implied by volatility smile

    1) Retrieve Strikes, Implied Volatilities

    In [137]:
    list_tte = [0.5,1, 1.5]
    
    spot = spot
    r = r
    
    
    call_strikes = list(call_vols.columns.values)
    
    
    call_vol = vol_smile(vol_surface = black_var_surface, 
                                     plot_years=list_tte, 
                                     plot_strikes=call_strikes)
    call_vol
    
    Out[137]:
    65.0 70.0 75.0 80.0 85.0 90.0 95.0 100.0 105.0 110.0 115.0 120.0 125.0 130.0 135.0 140.0 145.0 150.0 155.0 160.0 165.0 170.0 175.0 180.0 185.0 190.0 195.0 200.0 205.0 210.0 215.0 220.0 225.0 230.0
    0.5 0.571867 0.545579 0.516790 0.507128 0.477402 0.452738 0.426453 0.402678 0.377160 0.358777 0.339388 0.325028 0.309262 0.298197 0.286401 0.279747 0.272752 0.268559 0.264476 0.261918 0.261662 0.260939 0.262204 0.263672 0.267071 0.271067 0.275933 0.281403 0.287119 0.294170 0.300980 0.307926 0.314322 0.321565
    1.0 0.466380 0.456458 0.439052 0.417371 0.403256 0.384847 0.370648 0.358123 0.346400 0.336187 0.327999 0.319512 0.311800 0.306110 0.300972 0.296060 0.292707 0.290270 0.287922 0.286150 0.284791 0.284443 0.284206 0.283805 0.284125 0.285025 0.286914 0.287851 0.290030 0.292210 0.294745 0.297281 0.300095 0.303231
    1.5 0.417227 0.401995 0.390682 0.377063 0.365852 0.357707 0.346949 0.338299 0.332005 0.324164 0.315914 0.310837 0.307320 0.304180 0.301366 0.298075 0.296445 0.294403 0.292679 0.291321 0.290485 0.290239 0.289430 0.288869 0.288600 0.288192 0.289294 0.289781 0.290344 0.290907 0.292339 0.293771 0.294651 0.295531
    In [138]:
    # Select desired tenor of smile
    T = OTM_line.index.values[1]
    
    X_prices = call_vols.columns.values
    O_vols = call_vol.iloc[1,:].values
    
    X_prices , O_vols,T
    
    Out[138]:
    (array([ 65.,  70.,  75.,  80.,  85.,  90.,  95., 100., 105., 110., 115.,
            120., 125., 130., 135., 140., 145., 150., 155., 160., 165., 170.,
            175., 180., 185., 190., 195., 200., 205., 210., 215., 220., 225.,
            230.]),
     array([0.46638008, 0.45645802, 0.4390517 , 0.41737073, 0.40325589,
            0.38484687, 0.37064767, 0.35812273, 0.34640009, 0.33618739,
            0.32799912, 0.31951178, 0.31179982, 0.30610993, 0.30097198,
            0.29605972, 0.29270664, 0.29026978, 0.2879225 , 0.28614992,
            0.2847908 , 0.2844433 , 0.28420627, 0.28380484, 0.28412452,
            0.28502452, 0.28691404, 0.28785092, 0.2900301 , 0.29220992,
            0.29474491, 0.29728126, 0.30009461, 0.30323077]),
     1.0)

    3) Collect Strikes and Vols in dataframe

    In [139]:
    columns = ["Strike", "ImpVol"]
    df_c_opt = pd.DataFrame(np.stack((X_prices, O_vols), axis=1),columns = columns )
    df_c_opt
    
    Out[139]:
    Strike ImpVol
    0 65.0 0.466380
    1 70.0 0.456458
    2 75.0 0.439052
    3 80.0 0.417371
    4 85.0 0.403256
    5 90.0 0.384847
    6 95.0 0.370648
    7 100.0 0.358123
    8 105.0 0.346400
    9 110.0 0.336187
    10 115.0 0.327999
    11 120.0 0.319512
    12 125.0 0.311800
    13 130.0 0.306110
    14 135.0 0.300972
    15 140.0 0.296060
    16 145.0 0.292707
    17 150.0 0.290270
    18 155.0 0.287922
    19 160.0 0.286150
    20 165.0 0.284791
    21 170.0 0.284443
    22 175.0 0.284206
    23 180.0 0.283805
    24 185.0 0.284125
    25 190.0 0.285025
    26 195.0 0.286914
    27 200.0 0.287851
    28 205.0 0.290030
    29 210.0 0.292210
    30 215.0 0.294745
    31 220.0 0.297281
    32 225.0 0.300095
    33 230.0 0.303231

    3) Impute European Call prices from strikes and vols

    In [140]:
    def call_value(S, K, sigma, t=0, r=0):
        # use np.multiply and divide to handle divide-by-zero
        with np.errstate(divide='ignore'):
            d1 = np.divide(1, sigma * np.sqrt(t)) * (np.log(S/K) + (r+sigma**2 / 2) * t)
            d2 = d1 - sigma * np.sqrt(t)
        return np.multiply(norm.cdf(d1),S) - np.multiply(norm.cdf(d2), K * np.exp(-r * t))
    
    
    
    df_c_opt['OptPrice'] = df_c_opt.apply(lambda row: call_value(spot, row.Strike, row.ImpVol, 
                                                                 t=T,r=r), axis=1)
    df_c_opt
    
    Out[140]:
    Strike ImpVol OptPrice
    0 65.0 0.466380 84.983784
    1 70.0 0.456458 80.239476
    2 75.0 0.439052 75.462316
    3 80.0 0.417371 70.653053
    4 85.0 0.403256 65.977475
    5 90.0 0.384847 61.267939
    6 95.0 0.370648 56.679438
    7 100.0 0.358123 52.191227
    8 105.0 0.346400 47.802654
    9 110.0 0.336187 43.555433
    10 115.0 0.327999 39.497859
    11 120.0 0.319512 35.570439
    12 125.0 0.311800 31.831119
    13 130.0 0.306110 28.363319
    14 135.0 0.300972 25.121952
    15 140.0 0.296060 22.101968
    16 145.0 0.292707 19.387619
    17 150.0 0.290270 16.950427
    18 155.0 0.287922 14.740046
    19 160.0 0.286150 12.778272
    20 165.0 0.284791 11.045007
    21 170.0 0.284443 9.560492
    22 175.0 0.284206 8.255757
    23 180.0 0.283805 7.100491
    24 185.0 0.284125 6.124778
    25 190.0 0.285025 5.301366
    26 195.0 0.286914 4.629066
    27 200.0 0.287851 4.007051
    28 205.0 0.290030 3.515200
    29 210.0 0.292210 3.089056
    30 215.0 0.294745 2.731336
    31 220.0 0.297281 2.420931
    32 225.0 0.300095 2.159077
    33 230.0 0.303231 1.939657

    4) Calculate Implied Probability distribution

    In [141]:
    data = []
    
    for (_, left) ,(_,centre), (_, right) in zip (df_c_opt.iterrows(), df_c_opt.iloc[1:].iterrows(), df_c_opt.iloc[2:].iterrows()):
        butterfly_price = left.OptPrice - 2* centre.OptPrice + right.OptPrice
        max_profit = centre.Strike - left.Strike
        data.append([left.Strike,centre.Strike,right.Strike, butterfly_price, max_profit])
        
    bflys = pd.DataFrame(data, columns=["low_strike","strike","up_strike", "price", "max_profit"])
    bflys["prob"] = math.exp(r*T)*(bflys.price / bflys.max_profit**2)
    
    bflys
    
    Out[141]:
    low_strike strike up_strike price max_profit prob
    0 65.0 70.0 75.0 -0.032852 5.0 -0.001315
    1 70.0 75.0 80.0 -0.032103 5.0 -0.001285
    2 75.0 80.0 85.0 0.133685 5.0 0.005351
    3 80.0 85.0 90.0 -0.033958 5.0 -0.001359
    4 85.0 90.0 95.0 0.121034 5.0 0.004845
    5 90.0 95.0 100.0 0.100291 5.0 0.004014
    6 95.0 100.0 105.0 0.099638 5.0 0.003988
    7 100.0 105.0 110.0 0.141352 5.0 0.005658
    8 105.0 110.0 115.0 0.189648 5.0 0.007591
    9 110.0 115.0 120.0 0.130154 5.0 0.005210
    10 115.0 120.0 125.0 0.188100 5.0 0.007529
    11 120.0 125.0 130.0 0.271520 5.0 0.010868
    12 125.0 130.0 135.0 0.226433 5.0 0.009064
    13 130.0 135.0 140.0 0.221382 5.0 0.008861
    14 135.0 140.0 145.0 0.305636 5.0 0.012234
    15 140.0 145.0 150.0 0.277156 5.0 0.011094
    16 145.0 150.0 155.0 0.226812 5.0 0.009079
    17 150.0 155.0 160.0 0.248606 5.0 0.009951
    18 155.0 160.0 165.0 0.228509 5.0 0.009147
    19 160.0 165.0 170.0 0.248751 5.0 0.009957
    20 165.0 170.0 175.0 0.179780 5.0 0.007196
    21 170.0 175.0 180.0 0.149468 5.0 0.005983
    22 175.0 180.0 185.0 0.179554 5.0 0.007187
    23 180.0 185.0 190.0 0.152301 5.0 0.006096
    24 185.0 190.0 195.0 0.151112 5.0 0.006049
    25 190.0 195.0 200.0 0.050285 5.0 0.002013
    26 195.0 200.0 205.0 0.130163 5.0 0.005210
    27 200.0 205.0 210.0 0.065708 5.0 0.002630
    28 205.0 210.0 215.0 0.068424 5.0 0.002739
    29 210.0 215.0 220.0 0.047314 5.0 0.001894
    30 215.0 220.0 225.0 0.048551 5.0 0.001943
    31 220.0 225.0 230.0 0.042434 5.0 0.001699

    6) View Raw Probabilities

    In [142]:
    fig, axis_3 = plt.subplots()
    
    axis_3.plot(bflys.strike, bflys.prob,"o",color = 'green', label='pdf', )
    
    
    
    plt.title('Distribution of Stock Prices Implied by Volatility Smile')
    plt.grid(True)
    
    axis_3.set_xlabel("Stock Price", color='black')
    axis_3.set_ylabel("Raw Probability", color='green');
    

    7) Obtain smoothed probability density function

    In [143]:
    from scipy.ndimage import gaussian_filter1d
    
    smoothed_prob = gaussian_filter1d(bflys.prob, 2)
    
    plt.plot(bflys.strike, bflys.prob, "go", bflys.strike, smoothed_prob, "r-")
    plt.legend(["Raw prob", "Smoothed prob"], loc="best")
    plt.xlabel("Strike")
    plt.ylabel("Probability")
    plt.show()
    

    8) Obtain interpolated pdf

    In [144]:
    import scipy as scipy
    pdf = scipy.interpolate.interp1d(bflys.strike, smoothed_prob, kind="cubic",
                                     fill_value="extrapolate")
    x_new = np.linspace(bflys.strike.min(), bflys.strike.max(), 10000)
    plt.plot(bflys.strike, smoothed_prob, "ko", x_new, pdf(x_new), "r-");
    plt.legend(["smoothed prob", "fitted PDF"], loc="best")
    plt.xlabel("K")
    plt.ylabel("f(K)")
    plt.tight_layout()
    plt.show()
    

    9) Compute cumulative density function

    In [145]:
    prob_density = pd.DataFrame(x_new[0:-1], columns = ["Spot"])
    prob_density['pdf'] = pdf(x_new)[0:-1]
    
    cum_prob = scipy.integrate.cumtrapz(y=pdf(x_new), x=x_new)
    prob_density['cdf'] = cum_prob
    prob_density.head()
    
    Out[145]:
    Spot pdf cdf
    0 70.000000 0.000225 0.000003
    1 70.015502 0.000226 0.000007
    2 70.031003 0.000226 0.000011
    3 70.046505 0.000227 0.000014
    4 70.062006 0.000228 0.000018

    10) Compute VaR at desired confidence level

    In [146]:
    # Important that unity is obtaned with CDF
    var = 0.05
    s = prob_density.set_index('Spot').sub(var).abs().idxmin()
    s['cdf']
    
    Out[146]:
    95.77907790779078

    11) Visualize Distribution of Stock Prices Implied by Volatility Smile

    In [147]:
    figure, axis_3 = plt.subplots()
    
    axis_3.plot(x_new[0:-1], pdf(x_new)[0:-1] ,color='green', label='pdf')
    
    plt.title('Distribution of Stock Prices Implied by Volatility Smile')
    plt.grid(False);
    
    axis_4 = axis_3.twinx()
    axis_4.plot(x_new[0:-1], cum_prob ,color='blue', label='cdf')
    
    axis_3.set_xlabel("Stock Price", color='black')
    axis_3.set_ylabel("Probability Density Function", color='green')
    axis_4.set_ylabel("Cumulative Density Function", color='blue');
    
    var = 0.05
    
    s = prob_density.set_index('Spot').sub(var).abs().idxmin()
    s['cdf']
    
    imp_loss = s['cdf']/spot -1
    
    
    plt.axvline(x=s['cdf'], color='red',label='5% VaR',linestyle='dashed',linewidth=4);
    
    
    
    print(f"- There is a probability of 5 percent that the stock price will fall to {s['cdf']:.2f} or below in the next 12 months")
    print(f"- This equates to a loss equal to or greater than {imp_loss:.2f}")
    
    - There is a probability of 5 percent that the stock price will fall to 95.78 or below in the next 12 months
    - This equates to a loss equal to or greater than -0.36
    
    In [148]:
    # Find area under curve
    smoothed_total_prob = scipy.integrate.trapz(y=pdf(x_new), x=x_new)
    print(f"Smoothed total probability: {smoothed_total_prob}")
    
    Smoothed total probability: 0.9002273923361412
    
    In [149]:
    # Find area under curve
    raw_total_prob = scipy.integrate.trapz(y=bflys.prob, x=bflys.strike)
    print(f"Raw total probability: {raw_total_prob}")
    
    Raw total probability: 0.9046524182398522
    


    N) Generating simulated values for the volatility level (and stock price) with the Heston Process

    1) Simulate asset path and volatility path.

    In [150]:
    from mpl_toolkits.mplot3d import Axes3D
    import numpy as np
    import matplotlib.pyplot as plt
    from math import exp, log, sqrt, pi
    import time
    start_time = time.time()
    
    # simulate the asset paths under Heston model 
    def EulerMilsteinSim(scheme, negvar, numPaths, numSteps, rho, S_0, V_0, T, kappa, theta, sigma, r, q ):
        N = numSteps
        dt = T/N 
        num_time = int(T/dt) 
        S = np.zeros((num_time+1, numPaths)) 
        S[0,:] = S_0
        V = np.zeros((num_time+1, numPaths))
        V[0,:] = V_0
        Vcount0 = 0
        for i in range(numPaths):
            for t_step in range(1, num_time+1):
    # the 2 stochastic drivers for variance V and asset price S and correlated             
                Zv = np.random.randn(1)
                Zs = rho*Zv + sqrt(1-rho**2)*np.random.randn(1)
    # users can choose either Euler or Milstein scheme
                if scheme == 'Euler':
                    V[t_step,i] =  V[t_step-1,i] + kappa*(theta-V[t_step-1,i])*dt+ sigma* sqrt(V[t_step-1,i]) * sqrt(dt)*Zv 
                elif scheme == 'Milstein':
                    V[t_step,i] = V[t_step-1,i] + kappa*(theta-V[t_step-1,i])*dt + sigma* sqrt(V[t_step-1,i]) * sqrt(dt)*Zv \
                    + 1/4 *sigma**2*dt*(Zv**2 -1)
    
                if V[t_step,i] <= 0:
                    Vcount0 = Vcount0+1
                    if negvar == 'Reflect':
                        V[t_step,i] = abs(V[t_step,i])
                    elif negvar == 'Trunca':
                        V[t_step,i] = max( V[t_step,i] , 0 )
                        
                ################         simluations for asset price S              ########
                S[t_step,i] = S[t_step-1,i] * np.exp( (r-q-V[t_step-1,i]/2)*dt + sqrt(V[t_step-1,i])*sqrt(dt)* Zs)
        return S, V, Vcount0
    

    2) Specify input paramenters for function

    In [151]:
    S0= 2.7 # Initial Spot
    K = 2   # Strike
    
    r = 0.03 #rfr
    q = 0.0  # div yield
    
    V0 = 0.20**2 # instantaneous variance
    theta = 0.15**2 # long run variance
    
    kappa = 0.0621 # rate reversion
    
    rho = -0.8  # correlation
    sigma = 0.50 # vol of vol
    
    time_maturity = 3 # tenor
    
    num_simulations = 10000
    n = 250 # numbers of division of the time 
    
    
    
    
    sim_output = EulerMilsteinSim(scheme='Euler', 
                                 negvar='Trunca', 
                                 numPaths=num_simulations,
                                 numSteps=n,
                                 rho=rho, 
                                 S_0=S0, 
                                 V_0=V0, 
                                 T=time_maturity,
                                 kappa=kappa, theta=theta, sigma=sigma, r=r, q=q)
    

    3) Retrieve stock evolution and vol evolution outputs

    In [152]:
    stock_evolution = sim_output[0]
    var_evolution = sim_output[1]
    vol_evolution = np.sqrt(var_evolution)
    
    In [153]:
    fig = plt.figure(1)
    plt.plot(stock_evolution)
    plt.ylabel('Simulated asset paths from Heston model')
    plt.xlabel('time step')
    
    fig = plt.figure(2)
    plt.plot(vol_evolution)
    plt.ylabel('Stochastic volatility from Heston model')
    plt.xlabel('time step')
    
    Out[153]:
    Text(0.5, 0, 'time step')

    4) Retrieve Terminal values for stock and vol processes

    In [154]:
    stock_term_val = stock_evolution[-1]
    vol_term_val = vol_evolution[-1]
    
    In [155]:
    fig, (ax1, ax2) = plt.subplots(1, 2)
    ax1.hist(stock_term_val, bins=50)
    ax1.set_xlabel('index level')
    ax1.set_ylabel('frequency')
    ax2.hist(vol_term_val , bins=50)
    ax2.set_xlabel('volatility');
    

    5) Compute vol forecast from Heston process

    In [156]:
    Hest_vol_forecast = np.mean(vol_term_val)
    Hest_vol_forecast
    
    Out[156]:
    0.11764554288706608

    6) Compute Call Price from Heston Process

    In [157]:
    S_T = stock_term_val
    T = time_maturity
    # Terminal stock prices
    payoff = np.maximum(S_T - K, 0)
    ave_payoff = np.mean(payoff)
    
    # Option Price
    Hest_call_px = np.exp(-r* T)* ave_payoff
    Hest_call_px
    
    Out[157]:
    0.9620363862570981

    7) Compute multiple call and put prices from multiple strikes and maturities

    In [158]:
    # simulate the asset paths under Heston model 
    def EulerMilsteinSim(scheme, negvar, numPaths, rho, S_0, V_0, T, kappa, theta, sigma, r, q ):
        N=250 # hardcoded number of steps
        dt = T/N
        num_time = int(T/dt) 
        S = np.zeros((num_time+1, numPaths)) 
        S[0,:] = S_0
        V = np.zeros((num_time+1, numPaths))
        V[0,:] = V_0
        Vcount0 = 0
        for i in range(numPaths):
            for t_step in range(1, num_time+1):
    # the 2 stochastic drivers for variance V and asset price S and correlated             
                Zv = np.random.randn(1)
                Zs = rho*Zv + sqrt(1-rho**2)*np.random.randn(1)
    # users can choose either Euler or Milstein scheme
                if scheme == 'Euler':
                    V[t_step,i] =  V[t_step-1,i] + kappa*(theta-V[t_step-1,i])*dt+ sigma* sqrt(V[t_step-1,i]) * sqrt(dt)*Zv 
                elif scheme == 'Milstein':
                    V[t_step,i] = V[t_step-1,i] + kappa*(theta-V[t_step-1,i])*dt + sigma* sqrt(V[t_step-1,i]) * sqrt(dt)*Zv \
                    + 1/4 *sigma**2*dt*(Zv**2 -1)
    
                if V[t_step,i] <= 0:
                    Vcount0 = Vcount0+1
                    if negvar == 'Reflect':
                        V[t_step,i] = abs(V[t_step,i])
                    elif negvar == 'Trunca':
                        V[t_step,i] = max( V[t_step,i] , 0 )
                        
                ################         simluations for asset price S              ########
                S[t_step,i] = S[t_step-1,i] * np.exp( (r-q-V[t_step-1,i]/2)*dt + sqrt(V[t_step-1,i])*sqrt(dt)* Zs)
        return S, V, Vcount0
    
    
    # calculate the put option price
    def EulerMilsteinCallPrice(scheme, negvar, numPaths, rho, S_0 , V_0, Tmax, kappa, theta, sigma, r, q, MaturityList, ExercList):
        OptionPriceMatrix = np.zeros((len(ExercList),len(MaturityList)))
        stdErrTable = np.zeros((len(ExercList),len(MaturityList)))
        # Obtain the simulated stock price S and simulated variance V
        S, V, Vcount0 = EulerMilsteinSim(scheme, negvar, numPaths, rho, S_0 , V_0, Tmax, kappa, theta, sigma, r, q)
        N= 250 # hardcoded number of steps
        dt = Tmax/N
        for i in range(len(MaturityList)):
            T_temp = MaturityList[i]
            T_row = int( T_temp / dt)
            S_T = S[ T_row, :  ]# Terminal stock prices at different time T
            
            for j in range(len(ExercList)):
                KK = ExercList[j]
                # Payoff vectors
                Payoff = [max( x - KK ,0) for x in S_T]
                SimPrice = np.exp(-r* T_temp )* np.mean(Payoff)
                OptionPriceMatrix[j][i] = SimPrice
                stdDev = np.std(Payoff, dtype=np.float64)
                stdErr = stdDev/sqrt(numPaths)
                stdErrTable[j][i] = stdErr 
        return S, V, Vcount0, OptionPriceMatrix, stdErrTable, Payoff
    
    # calculate the put option price
    def EulerMilsteinPutPrice(scheme, negvar, numPaths, rho, S_0 , V_0, Tmax, kappa, theta, sigma, r, q, MaturityList, ExercList):
        OptionPriceMatrix = np.zeros((len(ExercList),len(MaturityList)))
        stdErrTable = np.zeros((len(ExercList),len(MaturityList)))
        # Obtain the simulated stock price S and simulated variance V
        S, V, Vcount0 = EulerMilsteinSim(scheme, negvar, numPaths, rho, S_0 , V_0, Tmax, kappa, theta, sigma, r, q)
        N= 250 # hardcoded number of steps
        dt = Tmax/N
        for i in range(len(MaturityList)):
            T_temp = MaturityList[i]
            T_row = int( T_temp / dt)
            S_T = S[ T_row, :  ]# Terminal stock prices at different time T
            
            for j in range(len(ExercList)):
                KK = ExercList[j]
                # Payoff vectors
                Payoff = [max( KK - x ,0) for x in S_T]
                SimPrice = np.exp(-r* T_temp )* np.mean(Payoff)
                OptionPriceMatrix[j][i] = SimPrice
                stdDev = np.std(Payoff, dtype=np.float64)
                stdErr = stdDev/sqrt(numPaths)
                stdErrTable[j][i] = stdErr 
        return S, V, Vcount0, OptionPriceMatrix, stdErrTable, Payoff
    
    In [159]:
    ExercList = [2, 2.5]
    MaturityList = [2,3]
    
    In [160]:
    Hest_Calls = EulerMilsteinCallPrice('Euler', 
                           'Trunca', 
                           num_simulations, 
                           rho, 
                           S0 , 
                           V0, 
                           time_maturity, 
                           kappa, 
                           theta, 
                           sigma, 
                           r, 
                           q, 
                           MaturityList, 
                           ExercList)
    
    In [161]:
    pd.DataFrame(Hest_Calls[3], index=ExercList, columns=MaturityList)
    
    Out[161]:
    2 3
    2.0 0.883733 0.960314
    2.5 0.477674 0.567808
    In [162]:
    Hest_Puts = EulerMilsteinPutPrice('Euler', 
                           'Trunca', 
                           num_simulations, 
                           rho, 
                           S0 , 
                           V0, 
                           time_maturity, 
                           kappa, 
                           theta, 
                           sigma, 
                           r, 
                           q, 
                           MaturityList, 
                           ExercList)
    
    In [163]:
    pd.DataFrame(Hest_Puts[3], index=ExercList, columns=MaturityList)
    
    Out[163]:
    2 3
    2.0 0.064136 0.083817
    2.5 0.128522 0.149418