All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.finmath.functions.AnalyticFormulas Maven / Gradle / Ivy

Go to download

finmath lib is a Mathematical Finance Library in Java. It provides algorithms and methodologies related to mathematical finance.

There is a newer version: 6.0.19
Show newest version
/*
 * Created on 23.01.2004.
 *
 * (c) Copyright Christian P. Fries, Germany. Contact: [email protected].
 */
package net.finmath.functions;

import java.util.Calendar;

import net.finmath.rootfinder.NewtonsMethod;
import net.finmath.stochastic.RandomVariable;
import net.finmath.stochastic.Scalar;

/**
 * This class implements some functions as static class methods.
 *
 * It provides functions like
 * 
    *
  • the Black-Scholes formula, *
  • the inverse of the Back-Scholes formula with respect to (implied) volatility, *
  • the Bachelier formula, *
  • the inverse of the Bachelier formula with respect to (implied) volatility, *
  • the corresponding functions (versions) for caplets and swaptions, *
  • analytic approximation for European options under the SABR model, *
  • some convexity adjustments. *
* * @author Christian Fries * @version 1.10 * @date 27.04.2012 */ public class AnalyticFormulas { /** * Preventing instantiation of this class. */ private AnalyticFormulas() { // This constructor will never be invoked } /** * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a log-normal process with constant log-volatility. * * The method also handles cases where the forward and/or option strike is negative * and some limit cases where the forward and/or the option strike is zero. * * @param forward The forward of the underlying. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @param payoffUnit The payoff unit (e.g., the discount factor) * @return Returns the value of a European call option under the Black-Scholes model. */ public static double blackScholesGeneralizedOptionValue( final double forward, final double volatility, final double optionMaturity, final double optionStrike, final double payoffUnit) { if(optionMaturity < 0) { return 0; } else if(forward < 0) { // We use max(X,0) = X + max(-X,0) return (forward - optionStrike) * payoffUnit + blackScholesGeneralizedOptionValue(-forward, volatility, optionMaturity, -optionStrike, payoffUnit); } else if((forward == 0) || (optionStrike <= 0.0) || (volatility <= 0.0) || (optionMaturity <= 0.0)) { // Limit case (where dPlus = +/- infty) return Math.max(forward - optionStrike,0) * payoffUnit; } else { // Calculate analytic value final double dPlus = (Math.log(forward / optionStrike) + 0.5 * volatility * volatility * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double dMinus = dPlus - volatility * Math.sqrt(optionMaturity); final double valueAnalytic = (forward * NormalDistribution.cumulativeDistribution(dPlus) - optionStrike * NormalDistribution.cumulativeDistribution(dMinus)) * payoffUnit; return valueAnalytic; } } /** * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a log-normal process with constant log-volatility. * * The model specific quantities are considered to be random variable, i.e., * the function may calculate an per-path valuation in a single call. * * @param forward The forward of the underlying. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @param payoffUnit The payoff unit (e.g., the discount factor) * @return Returns the value of a European call option under the Black-Scholes model. */ public static RandomVariable blackScholesGeneralizedOptionValue( final RandomVariable forward, final RandomVariable volatility, final double optionMaturity, final double optionStrike, final RandomVariable payoffUnit) { if(optionMaturity < 0) { return forward.mult(0.0); } else { final RandomVariable dPlus = forward.div(optionStrike).log().add(volatility.squared().mult(0.5 * optionMaturity)).div(volatility).div(Math.sqrt(optionMaturity)); final RandomVariable dMinus = dPlus.sub(volatility.mult(Math.sqrt(optionMaturity))); final RandomVariable valueAnalytic = dPlus.apply(NormalDistribution::cumulativeDistribution).mult(forward).sub(dMinus.apply(NormalDistribution::cumulativeDistribution).mult(optionStrike)).mult(payoffUnit); return valueAnalytic; } } /** * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0), where S follows a log-normal process with constant log-volatility. * * @param initialStockValue The spot value of the underlying. * @param riskFreeRate The risk free rate r (df = exp(-r T)). * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @return Returns the value of a European call option under the Black-Scholes model. */ public static double blackScholesOptionValue( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { return blackScholesGeneralizedOptionValue( initialStockValue * Math.exp(riskFreeRate * optionMaturity), // forward volatility, optionMaturity, optionStrike, Math.exp(-riskFreeRate * optionMaturity) // payoff unit ); } /** * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0), where S follows a log-normal process with constant log-volatility. * * @param initialStockValue The spot value of the underlying. * @param riskFreeRate The risk free rate r (df = exp(-r T)). * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @return Returns the value of a European call option under the Black-Scholes model. */ public static RandomVariable blackScholesOptionValue( final RandomVariable initialStockValue, final RandomVariable riskFreeRate, final RandomVariable volatility, final double optionMaturity, final double optionStrike) { return blackScholesGeneralizedOptionValue( initialStockValue.mult(riskFreeRate.mult(optionMaturity).exp()), // forward volatility, optionMaturity - 0.0, optionStrike, riskFreeRate.mult(-optionMaturity).exp() // payoff unit ); } /** * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0), where S follows a log-normal process with constant log-volatility. * * @param initialStockValue The spot value of the underlying. * @param riskFreeRate The risk free rate r (df = exp(-r T)). * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @return Returns the value of a European call option under the Black-Scholes model. */ public static RandomVariable blackScholesOptionValue( final RandomVariable initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { return blackScholesGeneralizedOptionValue( initialStockValue.mult(Math.exp(riskFreeRate*optionMaturity)), // forward new Scalar(volatility), optionMaturity - 0.0, optionStrike, new Scalar(Math.exp(-riskFreeRate*optionMaturity)) // payoff unit ); } /** * Calculates the Black-Scholes option value of a call, i.e., the payoff max(S(T)-K,0), or a put, i.e., the payoff max(K-S(T),0), where S follows a log-normal process with constant log-volatility. * * @param initialStockValue The spot value of the underlying. * @param riskFreeRate The risk free rate r (df = exp(-r T)). * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T for the call and zero for the put. * @param isCall If true, the value of a call is calculated, if false, the value of a put is calculated. * @return Returns the value of a European call/put option under the Black-Scholes model. */ public static double blackScholesOptionValue( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike, final boolean isCall) { final double callValue = blackScholesOptionValue(initialStockValue, riskFreeRate, volatility, optionMaturity, optionStrike); if(isCall) { return callValue; } else { final double putValue = callValue - (initialStockValue-optionStrike * Math.exp(-riskFreeRate * optionMaturity)); return putValue; } } /** * Calculates the Black-Scholes option value of an atm call option. * * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param forward The forward, i.e., the expectation of the index under the measure associated with payoff unit. * @param payoffUnit The payoff unit, i.e., the discount factor or the anuity associated with the payoff. * @return Returns the value of a European at-the-money call option under the Black-Scholes model */ public static double blackScholesATMOptionValue( final double volatility, final double optionMaturity, final double forward, final double payoffUnit) { if(optionMaturity < 0) { return 0.0; } // Calculate analytic value final double dPlus = 0.5 * volatility * Math.sqrt(optionMaturity); final double dMinus = -dPlus; final double valueAnalytic = (NormalDistribution.cumulativeDistribution(dPlus) - NormalDistribution.cumulativeDistribution(dMinus)) * forward * payoffUnit; return valueAnalytic; } /** * Calculates the delta of a call option under a Black-Scholes model * * The method also handles cases where the forward and/or option strike is negative * and some limit cases where the forward or the option strike is zero. * In the case forward = option strike = 0 the method returns 1.0. * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The delta of the option */ public static double blackScholesOptionDelta( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionMaturity < 0) { return 0; } else if(initialStockValue < 0) { // We use Indicator(S>K) = 1 - Indicator(-S>-K) return 1 - blackScholesOptionDelta(-initialStockValue, riskFreeRate, volatility, optionMaturity, -optionStrike); } else if(initialStockValue == 0) { // Limit case (where dPlus = +/- infty) if(optionStrike < 0) { return 1.0; // dPlus = +infinity } else if(optionStrike > 0) { return 0.0; // dPlus = -infinity } else { return 1.0; // Matter of definition of continuity of the payoff function } } else if((optionStrike <= 0.0) || (volatility <= 0.0) || (optionMaturity <= 0.0)) // (and initialStockValue > 0) { // The Black-Scholes model does not consider it being an option return 1.0; } else { // Calculate delta final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double delta = NormalDistribution.cumulativeDistribution(dPlus); return delta; } } /** * Calculates the delta of a call option under a Black-Scholes model * * The method also handles cases where the forward and/or option strike is negative * and some limit cases where the forward or the option strike is zero. * In the case forward = option strike = 0 the method returns 1.0. * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The delta of the option */ public static RandomVariable blackScholesOptionDelta( final RandomVariable initialStockValue, final RandomVariable riskFreeRate, final RandomVariable volatility, final double optionMaturity, final double optionStrike) { if(optionMaturity < 0) { return initialStockValue.mult(0.0); } else { // Calculate delta final RandomVariable dPlus = initialStockValue.div(optionStrike).log().add(volatility.squared().mult(0.5).add(riskFreeRate).mult(optionMaturity)).div(volatility).div(Math.sqrt(optionMaturity)); final RandomVariable delta = dPlus.apply(NormalDistribution::cumulativeDistribution); return delta; } } /** * Calculates the delta of a call option under a Black-Scholes model * * The method also handles cases where the forward and/or option strike is negative * and some limit cases where the forward or the option strike is zero. * In the case forward = option strike = 0 the method returns 1.0. * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The delta of the option */ public static RandomVariable blackScholesOptionDelta( final RandomVariable initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { return blackScholesOptionDelta(initialStockValue, new Scalar(riskFreeRate), new Scalar(volatility), optionMaturity, optionStrike); } /** * Calculates the delta of a call option under a Black-Scholes model * * The method also handles cases where the forward and/or option strike is negative * and some limit cases where the forward or the option strike is zero. * In the case forward = option strike = 0 the method returns 1.0. * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The delta of the option */ public static RandomVariable blackScholesOptionDelta( final RandomVariable initialStockValue, final RandomVariable riskFreeRate, final RandomVariable volatility, final double optionMaturity, final RandomVariable optionStrike) { if(optionMaturity < 0) { return initialStockValue.mult(0.0); } else { // Calculate delta final RandomVariable dPlus = initialStockValue.div(optionStrike).log().add(volatility.squared().mult(0.5).add(riskFreeRate).mult(optionMaturity)).div(volatility).div(Math.sqrt(optionMaturity)); final RandomVariable delta = dPlus.apply(NormalDistribution::cumulativeDistribution); return delta; } } /** * This static method calculated the gamma of a call option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The gamma of the option */ public static double blackScholesOptionGamma( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate gamma final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double gamma = Math.exp(-0.5*dPlus*dPlus) / (Math.sqrt(2.0 * Math.PI * optionMaturity) * initialStockValue * volatility); return gamma; } } /** * This static method calculated the gamma of a call option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The gamma of the option */ public static RandomVariable blackScholesOptionGamma( final RandomVariable initialStockValue, final RandomVariable riskFreeRate, final RandomVariable volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return initialStockValue.mult(0.0); } else { // Calculate gamma final RandomVariable dPlus = initialStockValue.div(optionStrike).log().add(volatility.squared().mult(0.5).add(riskFreeRate).mult(optionMaturity)).div(volatility).div(Math.sqrt(optionMaturity)); final RandomVariable gamma = dPlus.squared().mult(-0.5).exp().div(initialStockValue.mult(volatility).mult(Math.sqrt(2.0 * Math.PI * optionMaturity))); return gamma; } } /** * This static method calculated the gamma of a call option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The gamma of the option */ public static RandomVariable blackScholesOptionGamma( final RandomVariable initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { return blackScholesOptionGamma(initialStockValue, new Scalar(riskFreeRate), new Scalar(volatility), optionMaturity, optionStrike); } /** * Calculates the vega of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a * normal process with constant volatility, i.e., a Black-Scholes model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma S(t)\mathrm{d}W(t) * \] * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The vega of the option */ public static double blackScholesOptionVega( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate vega final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double vega = Math.exp(-0.5*dPlus*dPlus) / Math.sqrt(2.0 * Math.PI) * initialStockValue * Math.sqrt(optionMaturity); return vega; } } /** * Calculates the vega of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a * normal process with constant volatility, i.e., a Black-Scholes model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma S(t)\mathrm{d}W(t) * \] * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The vega of the option */ public static RandomVariable blackScholesOptionVega( final RandomVariable initialStockValue, final RandomVariable riskFreeRate, final RandomVariable volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return initialStockValue.mult(0.0); } else { // Calculate vega final RandomVariable dPlus = initialStockValue.div(optionStrike).log().add(volatility.squared().mult(0.5).add(riskFreeRate).mult(optionMaturity)).div(volatility).div(Math.sqrt(optionMaturity)); final RandomVariable vega = dPlus.squared().mult(-0.5).exp().div(Math.sqrt(2.0 * Math.PI * optionMaturity)).mult(initialStockValue).mult(volatility); return vega; } } /** * Calculates the vega of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a * normal process with constant volatility, i.e., a Black-Scholes model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma S(t)\mathrm{d}W(t) * \] * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The vega of the option */ public static RandomVariable blackScholesOptionVega( final RandomVariable initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { return blackScholesOptionVega(initialStockValue, new Scalar(riskFreeRate), new Scalar(volatility), optionMaturity, optionStrike); } /** * This static method calculated the vega of a call option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The vega of the option */ public static double blackScholesOptionTheta( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate theta final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double dMinus = dPlus - volatility * Math.sqrt(optionMaturity); final double theta = volatility * Math.exp(-0.5*dPlus*dPlus) / Math.sqrt(2.0 * Math.PI) / Math.sqrt(optionMaturity) / 2 * initialStockValue + riskFreeRate * optionStrike * Math.exp(-riskFreeRate * optionMaturity) * NormalDistribution.cumulativeDistribution(dMinus); return theta; } } /** * This static method calculated the rho of a call option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The rho of the option */ public static double blackScholesOptionRho( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate rho final double dMinus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate - 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double rho = optionStrike * optionMaturity * Math.exp(-riskFreeRate * optionMaturity) * NormalDistribution.cumulativeDistribution(dMinus); return rho; } } /** * Calculates the Black-Scholes option implied volatility of a call, i.e., the payoff *

max(S(T)-K,0)

, where S follows a log-normal process with constant log-volatility. * The admissible values for optionValue are between forward * payoffUnit - optionStrike (the inner value) and forward * payoffUnit. * * @param forward The forward of the underlying (which is equal to S(0) / payoffUnit, given the spot value S(0)). * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @param payoffUnit The payoff unit (e.g., the discount factor), (which is equal to exp(-maturity * r), given the interest rate r). * @param optionValue The option value. The admissible values for optionValue are between forward * payoffUnit - optionStrike (the inner value) and forward * payoffUnit. * @return Returns the implied volatility of a European call option under the Black-Scholes model. */ public static double blackScholesOptionImpliedVolatility( final double forward, final double optionMaturity, final double optionStrike, final double payoffUnit, final double optionValue) { // Limit the maximum number of iterations, to ensure this calculation returns fast, e.g. in cases when there is no such thing as an implied vol // TODO An exception should be thrown, when there is no implied volatility for the given value. final int maxIterations = 500; final double maxAccuracy = 1E-15; if(optionStrike <= 0.0) { // Actually it is not an option return 0.0; } else { // Calculate an lower and upper bound for the volatility final double p = NormalDistribution.inverseCumulativeDistribution((optionValue/payoffUnit+optionStrike)/(forward+optionStrike)) / Math.sqrt(optionMaturity); final double q = 2.0 * Math.abs(Math.log(forward/optionStrike)) / optionMaturity; final double volatilityLowerBound = p + Math.sqrt(Math.max(p * p - q, 0.0)); final double volatilityUpperBound = p + Math.sqrt( p * p + q ); // If strike is close to forward the two bounds are close to the analytic solution if(Math.abs(volatilityLowerBound - volatilityUpperBound) < maxAccuracy) { return (volatilityLowerBound+volatilityUpperBound) / 2.0; } // Solve for implied volatility final NewtonsMethod solver = new NewtonsMethod(0.5*(volatilityLowerBound+volatilityUpperBound) /* guess */); while(solver.getAccuracy() > maxAccuracy && !solver.isDone() && solver.getNumberOfIterations() < maxIterations) { final double volatility = solver.getNextPoint(); // Calculate analytic value final double dPlus = (Math.log(forward / optionStrike) + 0.5 * volatility * volatility * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double dMinus = dPlus - volatility * Math.sqrt(optionMaturity); final double valueAnalytic = (forward * NormalDistribution.cumulativeDistribution(dPlus) - optionStrike * NormalDistribution.cumulativeDistribution(dMinus)) * payoffUnit; final double derivativeAnalytic = forward * Math.sqrt(optionMaturity) * Math.exp(-0.5*dPlus*dPlus) / Math.sqrt(2.0*Math.PI) * payoffUnit; final double error = valueAnalytic - optionValue; solver.setValueAndDerivative(error,derivativeAnalytic); } return solver.getBestPoint(); } } /** * Calculates the Black-Scholes option value of a digital call option. * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return Returns the value of a European call option under the Black-Scholes model */ public static double blackScholesDigitalOptionValue( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0) { // The Black-Scholes model does not consider it being an option return 1.0; } else { // Calculate analytic value final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double dMinus = dPlus - volatility * Math.sqrt(optionMaturity); final double valueAnalytic = Math.exp(- riskFreeRate * optionMaturity) * NormalDistribution.cumulativeDistribution(dMinus); return valueAnalytic; } } /** * Calculates the delta of a digital option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The delta of the digital option */ public static double blackScholesDigitalOptionDelta( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate delta final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double dMinus = dPlus - volatility * Math.sqrt(optionMaturity); final double delta = Math.exp(-riskFreeRate * optionMaturity) * Math.exp(-0.5*dMinus*dMinus) / (Math.sqrt(2.0 * Math.PI * optionMaturity) * initialStockValue * volatility); return delta; } } /** * Calculates the vega of a digital option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The vega of the digital option */ public static double blackScholesDigitalOptionVega( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate vega final double dPlus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate + 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double dMinus = dPlus - volatility * Math.sqrt(optionMaturity); final double vega = - Math.exp(-riskFreeRate * optionMaturity) * Math.exp(-0.5*dMinus*dMinus) / Math.sqrt(2.0 * Math.PI) * dPlus / volatility; return vega; } } /** * Calculates the rho of a digital option under a Black-Scholes model * * @param initialStockValue The initial value of the underlying, i.e., the spot. * @param riskFreeRate The risk free rate of the bank account numerarie. * @param volatility The Black-Scholes volatility. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @return The rho of the digital option */ public static double blackScholesDigitalOptionRho( final double initialStockValue, final double riskFreeRate, final double volatility, final double optionMaturity, final double optionStrike) { if(optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else if(optionStrike <= 0.0) { final double rho = - optionMaturity * Math.exp(-riskFreeRate * optionMaturity); return rho; } else { // Calculate rho final double dMinus = (Math.log(initialStockValue / optionStrike) + (riskFreeRate - 0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double rho = - optionMaturity * Math.exp(-riskFreeRate * optionMaturity) * NormalDistribution.cumulativeDistribution(dMinus) + Math.sqrt(optionMaturity)/volatility * Math.exp(-riskFreeRate * optionMaturity) * Math.exp(-0.5*dMinus*dMinus) / Math.sqrt(2.0 * Math.PI); return rho; } } /** * Calculate the value of a caplet assuming the Black'76 model. * * @param forward The forward (spot). * @param volatility The Black'76 volatility. * @param optionMaturity The option maturity * @param optionStrike The option strike. * @param periodLength The period length of the underlying forward rate. * @param discountFactor The discount factor corresponding to the payment date (option maturity + period length). * @return Returns the value of a caplet under the Black'76 model */ public static double blackModelCapletValue( final double forward, final double volatility, final double optionMaturity, final double optionStrike, final double periodLength, final double discountFactor) { // May be interpreted as a special version of the Black-Scholes Formula return AnalyticFormulas.blackScholesGeneralizedOptionValue(forward, volatility, optionMaturity, optionStrike, periodLength * discountFactor); } /** * Calculate the implied volatility of a caplet assuming the Black'76 model. * * @param forward The forward (spot). * @param optionMaturity The option maturity * @param optionStrike The option strike. * @param periodLength The period length of the underlying forward rate. * @param discountFactor The discount factor corresponding to the payment date (option maturity + period length). * @param value The value of the caplet. * @return Returns the value of a caplet under the Black'76 model */ public static double blackModelCapletImpliedVolatility( final double forward, final double optionMaturity, final double optionStrike, final double periodLength, final double discountFactor, final double value) { // May be interpreted as a special version of the Black-Scholes Formula return AnalyticFormulas.blackScholesOptionImpliedVolatility(forward, optionMaturity, optionStrike, periodLength * discountFactor, value); } /** * Calculate the value of a digital caplet assuming the Black'76 model. * * @param forward The forward (spot). * @param volatility The Black'76 volatility. * @param periodLength The period length of the underlying forward rate. * @param discountFactor The discount factor corresponding to the payment date (option maturity + period length). * @param optionMaturity The option maturity * @param optionStrike The option strike. * @return Returns the price of a digital caplet under the Black'76 model */ public static double blackModelDigitalCapletValue( final double forward, final double volatility, final double periodLength, final double discountFactor, final double optionMaturity, final double optionStrike) { // May be interpreted as a special version of the Black-Scholes Formula return AnalyticFormulas.blackScholesDigitalOptionValue(forward, 0.0, volatility, optionMaturity, optionStrike) * periodLength * discountFactor; } /** * Calculate the delta of a digital caplet assuming the Black'76 model. * * @param forward The forward (spot). * @param volatility The Black'76 volatility. * @param periodLength The period length of the underlying forward rate. * @param discountFactor The discount factor corresponding to the payment date (option maturity + period length). * @param optionMaturity The option maturity * @param optionStrike The option strike. * @return Returns the price of a digital caplet under the Black'76 model */ public static double blackModelDigitalCapletDelta( final double forward, final double volatility, final double periodLength, final double discountFactor, final double optionMaturity, final double optionStrike) { // May be interpreted as a special version of the Black-Scholes Formula return AnalyticFormulas.blackScholesDigitalOptionDelta(forward, 0.0, volatility, optionMaturity, optionStrike) * periodLength * discountFactor; } /** * Calculate the value of a digital caplet assuming the Black'76 model. * * This method exists for backward compatibility due to a typo in an earlier version. * * @param forward The forward (spot). * @param volatility The Black'76 volatility. * @param periodLength The period length of the underlying forward rate. * @param discountFactor The discount factor corresponding to the payment date (option maturity + period length). * @param optionMaturity The option maturity * @param optionStrike The option strike. * @return Returns the price of a digital caplet under the Black'76 model */ public static double blackModelDgitialCapletValue( final double forward, final double volatility, final double periodLength, final double discountFactor, final double optionMaturity, final double optionStrike) { return AnalyticFormulas.blackModelDigitalCapletValue(forward, volatility, periodLength, discountFactor, optionMaturity, optionStrike); } /** * Calculate the value of a swaption assuming the Black'76 model. * * @param forwardSwaprate The forward (spot) * @param volatility The Black'76 volatility. * @param optionMaturity The option maturity. * @param optionStrike The option strike. * @param swapAnnuity The swap annuity corresponding to the underlying swap. * @return Returns the value of a Swaption under the Black'76 model */ public static double blackModelSwaptionValue( final double forwardSwaprate, final double volatility, final double optionMaturity, final double optionStrike, final double swapAnnuity) { // May be interpreted as a special version of the Black-Scholes Formula return AnalyticFormulas.blackScholesGeneralizedOptionValue(forwardSwaprate, volatility, optionMaturity, optionStrike, swapAnnuity); } /** * Calculates the value of an Exchange option under a generalized Black-Scholes model, i.e., the payoff \( max(S_{1}(T)-S_{2}(T),0) \), * where \( S_{1} \) and \( S_{2} \) follow a log-normal process with constant log-volatility and constant instantaneous correlation. * * The method also handles cases where the forward and/or option strike is negative * and some limit cases where the forward and/or the option strike is zero. * * @param spot1 Value of \( S_{1}(0) \) * @param spot2 Value of \( S_{2}(0) \) * @param volatility1 Volatility of \( \log(S_{1}(t)) \) * @param volatility2 Volatility of \( \log(S_{2}(t)) \) * @param correlation Instantaneous correlation of \( \log(S_{1}(t)) \) and \( \log(S_{2}(t)) \) * @param optionMaturity The option maturity \( T \). * @return Returns the value of a European exchange option under the Black-Scholes model. */ public static double margrabeExchangeOptionValue( final double spot1, final double spot2, final double volatility1, final double volatility2, final double correlation, final double optionMaturity) { final double volatility = Math.sqrt(volatility1*volatility1 + volatility2*volatility2 - 2.0 * volatility1*volatility2*correlation); return blackScholesGeneralizedOptionValue(spot1, volatility, optionMaturity, spot2, 1.0); } /* * Some functions for the Bachelier model. For more see BachelierModel class. */ /** * Calculates the option value of a call, i.e., the payoff max(S(T)-K,0), where S follows a * normal process with numeraire scaled volatility, i.e., a homogeneous Bachelier model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma exp(-r (T-t)) \mathrm{d}W(t) * \] * * Considering the numeraire \( N(t) = exp(-r (T-t)) \), this implies that \( F(t) = S(t)/N(t) \) follows * \[ * \mathrm{d} F(t) = \sigma \mathrm{d}W(t) \text{.} * \] * * @param forward The forward of the underlying \( F(0) = S(0)/N(0) = S(0) \exp(r T) \). * @param volatility The Bachelier volatility \( \sigma \) of the forward process. * @param optionMaturity The option maturity T. * @param optionStrike The option strike K. * @param payoffUnit The payoff unit (e.g., the discount factor \( N(0)/N(T) = exp(-r T) \)) * @return Returns the value of a European call option under the Bachelier model. */ public static double bachelierOptionValue( final double forward, final double volatility, final double optionMaturity, final double optionStrike, final double payoffUnit) { return BachelierModel.bachelierOptionValue(forward, volatility, optionMaturity, optionStrike, payoffUnit); } /** * Calculates the option value of a call, i.e., the payoff max(S(T)-K,0), where S follows a * normal process with numeraire scaled volatility, i.e., a homogeneous Bachelier model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma exp(-r (T-t)) \mathrm{d}W(t) * \] * Considering the numeraire \( N(t) = exp(-r (T-t)) \), this implies that \( F(t) = S(t)/N(t) \) follows * \[ * \mathrm{d} F(t) = \sigma \mathrm{d}W(t) \text{.} * \] * * @param forward The forward of the underlying \( F(0) = S(0)/N(0) = S(0) \exp(r T) \). * @param volatility The Bachelier volatility \( \sigma \) of the forward process. * @param optionMaturity The option maturity T. * @param optionStrike The option strike K. * @param payoffUnit The payoff unit (e.g., the discount factor \( N(0)/N(T) = exp(-r T) \)) * @return Returns the value of a European call option under the Bachelier model. */ public static RandomVariable bachelierOptionValue( final RandomVariable forward, final RandomVariable volatility, final double optionMaturity, final double optionStrike, final RandomVariable payoffUnit) { return BachelierModel.bachelierOptionValue(forward, volatility, optionMaturity, optionStrike, payoffUnit); } /** * Calculates the Bachelier option implied volatility of a call, i.e., the payoff max(S(T)-K,0), where S follows a * normal process with numeraire scaled volatility, i.e., a homogeneous Bachelier model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma exp(-r (T-t)) \mathrm{d}W(t) * \] * Considering the numeraire \( N(t) = exp(-r (T-t)) \), this implies that \( F(t) = S(t)/N(t) \) follows * \[ * \mathrm{d} F(t) = \sigma \mathrm{d}W(t) \text{.} * \] * * @param forward The forward of the underlying. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. If the option strike is ≤ 0.0 the method returns the value of the forward contract paying S(T)-K in T. * @param payoffUnit The payoff unit (e.g., the discount factor) * @param optionValue The option value. * @return Returns the implied volatility of a European call option under the Bachelier model. */ public static double bachelierOptionImpliedVolatility( final double forward, final double optionMaturity, final double optionStrike, final double payoffUnit, final double optionValue) { return BachelierModel.bachelierOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, optionValue); } /** * Calculates the option delta dV(0)/dS(0) of a call option, i.e., the payoff V(T)=max(S(T)-K,0), where S follows a * normal process with numeraire scaled volatility, i.e., a homogeneous Bachelier model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma exp(-r (T-t)) \mathrm{d}W(t) * \] * Considering the numeraire \( N(t) = exp(-r (T-t)) \), this implies that \( F(t) = S(t)/N(t) \) follows * \[ * \mathrm{d} F(t) = \sigma \mathrm{d}W(t) \text{.} * \] * * @param forward The forward of the underlying \( F(0) = S(0)/N(0) = S(0) \exp(r T) \). * @param volatility The Bachelier volatility \( \sigma \) of the forward process. * @param optionMaturity The option maturity T. * @param optionStrike The option strike K. * @param payoffUnit The payoff unit (e.g., the discount factor \( N(0)/N(T) = exp(-r T) \)) * @return Returns the value of the option delta (dV/dS(0)) of a European call option under the Bachelier model. */ public static double bachelierOptionDelta( final double forward, final double volatility, final double optionMaturity, final double optionStrike, final double payoffUnit) { return BachelierModel.bachelierOptionDelta(forward, volatility, optionMaturity, optionStrike, payoffUnit); } /* * CMS Options */ /** * Calculate the value of a CMS option using the Black-Scholes model for the swap rate together with * the Hunt-Kennedy convexity adjustment. * * @param forwardSwaprate The forward swap rate * @param volatility Volatility of the log of the swap rate * @param swapAnnuity The swap annuity * @param optionMaturity The option maturity * @param swapMaturity The swap maturity * @param payoffUnit The payoff unit, e.g., the discount factor corresponding to the payment date * @param optionStrike The option strike * @return Value of the CMS option */ public static double huntKennedyCMSOptionValue( final double forwardSwaprate, final double volatility, final double swapAnnuity, final double optionMaturity, final double swapMaturity, final double payoffUnit, final double optionStrike) { final double a = 1.0/swapMaturity; final double b = (payoffUnit / swapAnnuity - a) / forwardSwaprate; final double convexityAdjustment = Math.exp(volatility*volatility*optionMaturity); final double valueUnadjusted = blackModelSwaptionValue(forwardSwaprate, volatility, optionMaturity, optionStrike, swapAnnuity); final double valueAdjusted = blackModelSwaptionValue(forwardSwaprate * convexityAdjustment, volatility, optionMaturity, optionStrike, swapAnnuity); return a * valueUnadjusted + b * forwardSwaprate * valueAdjusted; } /** * Calculate the value of a CMS strike using the Black-Scholes model for the swap rate together with * the Hunt-Kennedy convexity adjustment. * * @param forwardSwaprate The forward swap rate * @param volatility Volatility of the log of the swap rate * @param swapAnnuity The swap annuity * @param optionMaturity The option maturity * @param swapMaturity The swap maturity * @param payoffUnit The payoff unit, e.g., the discount factor corresponding to the payment date * @param optionStrike The option strike * @return Value of the CMS strike */ public static double huntKennedyCMSFloorValue( final double forwardSwaprate, final double volatility, final double swapAnnuity, final double optionMaturity, final double swapMaturity, final double payoffUnit, final double optionStrike) { final double huntKennedyCMSOptionValue = huntKennedyCMSOptionValue(forwardSwaprate, volatility, swapAnnuity, optionMaturity, swapMaturity, payoffUnit, optionStrike); // A floor is an option plus the strike (max(X,K) = max(X-K,0) + K) return huntKennedyCMSOptionValue + optionStrike * payoffUnit; } /** * Calculate the adjusted forward swaprate corresponding to a change of payoff unit from the given swapAnnuity to the given payoffUnit * using the Black-Scholes model for the swap rate together with the Hunt-Kennedy convexity adjustment. * * @param forwardSwaprate The forward swap rate * @param volatility Volatility of the log of the swap rate * @param swapAnnuity The swap annuity * @param optionMaturity The option maturity * @param swapMaturity The swap maturity * @param payoffUnit The payoff unit, e.g., the discount factor corresponding to the payment date * @return Convexity adjusted forward rate */ public static double huntKennedyCMSAdjustedRate( final double forwardSwaprate, final double volatility, final double swapAnnuity, final double optionMaturity, final double swapMaturity, final double payoffUnit) { final double a = 1.0/swapMaturity; final double b = (payoffUnit / swapAnnuity - a) / forwardSwaprate; final double convexityAdjustment = Math.exp(volatility*volatility*optionMaturity); final double rateUnadjusted = forwardSwaprate; final double rateAdjusted = forwardSwaprate * convexityAdjustment; return (a * rateUnadjusted + b * forwardSwaprate * rateAdjusted) * swapAnnuity / payoffUnit; } /** * Calculated the approximation to the lognormal Black volatility using the * standard SABR model and the standard Hagan approximation. * * @param alpha initial value of the stochastic volatility process of the SABR model. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param underlying Underlying (spot) value. * @param strike Strike. * @param maturity Maturity. * @return Implied lognormal Black volatility. */ public static double sabrHaganLognormalBlackVolatilityApproximation(final double alpha, final double beta, final double rho, final double nu, final double underlying, final double strike, final double maturity) { return sabrHaganLognormalBlackVolatilityApproximation(alpha, beta, rho, nu, 0.0, underlying, strike, maturity); } /** * Calculated the approximation to the lognormal Black volatility using the * standard SABR model and the standard Hagan approximation. * * @param alpha initial value of the stochastic volatility process of the SABR model. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param displacement The displacement parameter d. * @param underlying Underlying (spot) value. * @param strike Strike. * @param maturity Maturity. * @return Implied lognormal Black volatility. */ public static double sabrHaganLognormalBlackVolatilityApproximation(final double alpha, final double beta, final double rho, final double nu, final double displacement, double underlying, double strike, final double maturity) { if(alpha <= 0) { throw new IllegalArgumentException("α must be greater than 0."); } if(rho > 1 || rho < -1) { throw new IllegalArgumentException("ρ must be between -1 and 1."); } if(nu <= 0) { throw new IllegalArgumentException("ν must be greater than 0."); } if(underlying <= 0) { throw new IllegalArgumentException("Approximation not definied for non-positive underlyings."); } // Apply displacement. Displaced model is just a shift on underlying and strike. underlying += displacement; strike += displacement; if(Math.abs(underlying - strike) < 0.0001 * (1+Math.abs(underlying))) { /* * ATM case - we assume underlying = strike */ final double term1 = alpha / (Math.pow(underlying,1-beta)); final double term2 = Math.pow(1-beta,2)/24 * Math.pow(alpha,2)/Math.pow(underlying,2*(1-beta)) + rho*beta*alpha*nu/(4*Math.pow(underlying,1-beta)) + (2-3*rho*rho)*nu*nu/24; return term1 * (1+ term2 * maturity); } else{ /* * General non-ATM case no prob with log(F/K) */ final double forwardTimesStrike = underlying * strike; final double z = nu/alpha * Math.pow(forwardTimesStrike, (1-beta)/2) * Math.log(underlying / strike); final double x = Math.log((Math.sqrt(1- 2*rho * z + z*z) + z - rho)/(1 - rho)); final double term1 = alpha / Math.pow(forwardTimesStrike,(1-beta)/2) / (1 + Math.pow(1-beta,2)/24*Math.pow(Math.log(underlying/strike),2) + Math.pow(1-beta,4)/1920 * Math.pow(Math.log(underlying/strike),4)); final double term2 = (Math.abs(x-z) < 1E-10) ? 1 : z / x; final double term3 = 1 + (Math.pow(1 - beta,2)/24 *Math.pow(alpha, 2)/Math.pow(forwardTimesStrike, 1-beta) + rho*beta*nu*alpha / 4 / Math.pow(forwardTimesStrike, (1-beta)/2) + (2-3*rho*rho)/24 * nu*nu) *maturity; return term1 * term2 * term3; } } /** * Return the implied normal volatility (Bachelier volatility) under a SABR model using the * approximation of Berestycki. * * @param alpha initial value of the stochastic volatility process of the SABR model. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param displacement The displacement parameter d. * @param underlying Underlying (spot) value. * @param strike Strike. * @param maturity Maturity. * @return The implied normal volatility (Bachelier volatility) */ public static double sabrBerestyckiNormalVolatilityApproximation(final double alpha, final double beta, final double rho, final double nu, final double displacement, double underlying, double strike, final double maturity) { // Apply displacement. Displaced model is just a shift on underlying and strike. underlying += displacement; strike += displacement; final double forwardStrikeAverage = (underlying+strike) / 2.0; // Original paper uses a geometric average here double z; if(beta < 1.0) { z = nu / alpha * (Math.pow(underlying, 1.0-beta) - Math.pow(strike, 1.0-beta)) / (1.0-beta); } else { z = nu / alpha * Math.log(underlying/strike); } final double x = Math.log((Math.sqrt(1.0 - 2.0*rho*z + z*z) + z - rho) / (1.0-rho)); double term1; if(Math.abs(underlying - strike) < 1E-10 * (1+Math.abs(underlying))) { // ATM case - we assume underlying = strike term1 = alpha * Math.pow(underlying, beta); } else if(x == 0) { // x ~ z for z small if(beta < 1.0) { term1 = (underlying-strike) * alpha / (Math.pow(underlying, 1.0-beta) - Math.pow(strike, 1.0-beta)) / (1.0-beta); } else { term1 = (underlying-strike) * alpha / Math.log(underlying/strike); } } else { term1 = nu * (underlying-strike) / x; } final double sigma = term1 * (1.0 + maturity * ((-beta*(2-beta)*alpha*alpha)/(24*Math.pow(forwardStrikeAverage,2.0*(1.0-beta))) + beta*alpha*rho*nu / (4*Math.pow(forwardStrikeAverage,(1.0-beta))) + (2.0 -3.0*rho*rho)*nu*nu/24)); return Math.max(sigma, 0.0); } /** * Return the implied normal volatility (Bachelier volatility) under a SABR model using the * approximation of Hagan. * * @param alpha initial value of the stochastic volatility process of the SABR model. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param displacement The displacement parameter d. * @param underlying Underlying (spot) value. * @param strike Strike. * @param maturity Maturity. * @return The implied normal volatility (Bachelier volatility) */ public static double sabrNormalVolatilityApproximation(final double alpha, final double beta, final double rho, final double nu, final double displacement, double underlying, double strike, final double maturity) { // Apply displacement. Displaced model is just a shift on underlying and strike. underlying += displacement; strike += displacement; final double forwardStrikeAverage = (underlying+strike) / 2.0; final double z = nu / alpha * (underlying-strike) / Math.pow(forwardStrikeAverage, beta); final double x = Math.log((Math.sqrt(1.0 - 2.0*rho*z + z*z) + z - rho) / (1.0-rho)); double term1; if(Math.abs(underlying - strike) < 1E-8 * (1+Math.abs(underlying))) { // ATM case - we assume underlying = strike term1 = alpha * Math.pow(underlying, beta); } else { final double z2 = (1.0 - beta) / (Math.pow(underlying, 1.0-beta) - Math.pow(strike, 1.0-beta)); term1 = alpha * z2 * z * (underlying-strike) / x; } final double sigma = term1 * (1.0 + maturity * ((-beta*(2-beta)*alpha*alpha)/(24*Math.pow(forwardStrikeAverage,2.0*(1.0-beta))) + beta*alpha*rho*nu / (4*Math.pow(forwardStrikeAverage,(1.0-beta))) + (2.0 -3.0*rho*rho)*nu*nu/24)); return Math.max(sigma, 0.0); } /** * Return the parameter alpha (initial value of the stochastic vol process) of a SABR model using the * to match the given at-the-money volatility. * * @param normalVolatility ATM volatility to match. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param displacement The displacement parameter d. * @param underlying Underlying (spot) value. * @param maturity Maturity. * @return The implied normal volatility (Bachelier volatility) */ public static double sabrAlphaApproximation(final double normalVolatility, final double beta, final double rho, final double nu, final double displacement, double underlying, final double maturity) { // Apply displacement. Displaced model is just a shift on underlying and strike. underlying += displacement; // ATM case. final double forwardStrikeAverage = underlying; final double guess = normalVolatility/Math.pow(underlying, beta); final NewtonsMethod search = new NewtonsMethod(guess); while(!search.isDone() && search.getAccuracy() > 1E-16 && search.getNumberOfIterations() < 100) { final double alpha = search.getNextPoint(); final double term1 = alpha * Math.pow(underlying, beta); final double term2 = (1.0 + maturity * ((-beta*(2-beta)*alpha*alpha)/(24*Math.pow(forwardStrikeAverage,2.0*(1.0-beta))) + beta*alpha*rho*nu / (4*Math.pow(forwardStrikeAverage,(1.0-beta))) + (2.0 -3.0*rho*rho)*nu*nu/24)); final double derivativeTerm1 = Math.pow(underlying, beta); final double derivativeTerm2 = maturity * (2*(-beta*(2-beta)*alpha)/(24*Math.pow(forwardStrikeAverage,2.0*(1.0-beta))) + beta*rho*nu / (4*Math.pow(forwardStrikeAverage,(1.0-beta)))); final double sigma = term1 * term2; final double derivative = derivativeTerm1 * term2 + term1 * derivativeTerm2; search.setValueAndDerivative(sigma-normalVolatility, derivative); } return search.getBestPoint(); } /** * Return the skew of the implied normal volatility (Bachelier volatility) under a SABR model using the * approximation of Berestycki. The skew is the first derivative of the implied vol w.r.t. the strike, * evaluated at the money. * * @param alpha initial value of the stochastic volatility process of the SABR model. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param displacement The displacement parameter d. * @param underlying Underlying (spot) value. * @param maturity Maturity. * @return The skew of the implied normal volatility (Bachelier volatility) */ public static double sabrNormalVolatilitySkewApproximation(final double alpha, final double beta, final double rho, final double nu, final double displacement, double underlying, final double maturity) { final double sigma = sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying, maturity); // Apply displacement. Displaced model is just a shift on underlying and strike. underlying += displacement; final double a = alpha/Math.pow(underlying, 1-beta); final double c = 1.0/24*Math.pow(a, 3)*beta*(1.0-beta); final double skew = + (rho*nu/a + beta) * (1.0/2.0*sigma/underlying) - maturity*c*(3.0*rho*nu/a + beta - 2.0); // Some alternative representations // double term1dterm21 = (beta*(2-beta)*alpha*alpha*alpha)/24*Math.pow(underlying,-3.0*(1.0-beta)) * (1.0-beta); // double term1dterm22 = beta*alpha*alpha*rho*nu / 4 * Math.pow(underlying,-2.0*(1.0-beta)) * -(1.0-beta) * 0.5; // skew = + 1.0/2.0*sigma/underlying*(rho*nu/alpha * Math.pow(underlying, 1-beta) + beta) + maturity * (term1dterm21+term1dterm22); // skew = + (rho*nu/a + beta) * (1.0/2.0*sigma/underlying - maturity*3.0*c) + maturity*2.0*c*(1+beta); // skew = + (rho*nu/a + beta) * (1.0/2.0*sigma/underlying - maturity*c) - maturity*c*(2.0*rho*nu/a - 2.0); // The follwoing may be used as approximations (for beta=0 the approximation is exact). // double approximation = (rho*nu/a + beta) * (1.0/2.0*sigma/underlying); // double residual = skew - approximation; return skew; } /** * Return the curvature of the implied normal volatility (Bachelier volatility) under a SABR model using the * approximation of Berestycki. The curvatures is the second derivative of the implied vol w.r.t. the strike, * evaluated at the money. * * @param alpha initial value of the stochastic volatility process of the SABR model. * @param beta CEV parameter of the SABR model. * @param rho Correlation (leverages) of the stochastic volatility. * @param nu Volatility of the stochastic volatility (vol-of-vol). * @param displacement The displacement parameter d. * @param underlying Underlying (spot) value. * @param maturity Maturity. * @return The curvature of the implied normal volatility (Bachelier volatility) */ public static double sabrNormalVolatilityCurvatureApproximation(final double alpha, final double beta, final double rho, final double nu, final double displacement, double underlying, final double maturity) { final double sigma = sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying, maturity); // Apply displacement. Displaced model is just a shift on underlying and strike. underlying += displacement; /* double d1xdz1 = 1.0; double d2xdz2 = rho; double d3xdz3 = 3.0*rho*rho-1.0; double d1zdK1 = -nu/alpha * Math.pow(underlying, -beta); double d2zdK2 = + nu/alpha * beta * Math.pow(underlying, -beta-1.0); double d3zdK3 = - nu/alpha * beta * (1.0+beta) * Math.pow(underlying, -beta-2.0); double d1xdK1 = d1xdz1*d1zdK1; double d2xdK2 = d2xdz2*d1zdK1*d1zdK1 + d1xdz1*d2zdK2; double d3xdK3 = d3xdz3*d1zdK1*d1zdK1*d1zdK1 + 3.0*d2xdz2*d2zdK2*d1zdK1 + d1xdz1*d3zdK3; double term1 = alpha * Math.pow(underlying, beta) / nu; */ final double d2Part1dK2 = nu * ((1.0/3.0 - 1.0/2.0 * rho * rho) * nu/alpha * Math.pow(underlying, -beta) + (1.0/6.0 * beta*beta - 2.0/6.0 * beta) * alpha/nu*Math.pow(underlying, beta-2)); final double d0BdK0 = (-1.0/24.0 *beta*(2-beta)*alpha*alpha*Math.pow(underlying, 2*beta-2) + 1.0/4.0 * beta*alpha*rho*nu*Math.pow(underlying, beta-1.0) + (2.0 -3.0*rho*rho)*nu*nu/24); final double d1BdK1 = (-1.0/48.0 *beta*(2-beta)*(2*beta-2)*alpha*alpha*Math.pow(underlying, 2*beta-3) + 1.0/8.0 * beta*(beta-1.0)*alpha*rho*nu*Math.pow(underlying, beta-2)); final double d2BdK2 = (-1.0/96.0 *beta*(2-beta)*(2*beta-2)*(2*beta-3)*alpha*alpha*Math.pow(underlying, 2*beta-4) + 1.0/16.0 * beta*(beta-1)*(beta-2)*alpha*rho*nu*Math.pow(underlying, beta-3)); final double curvatureApproximation = nu/alpha * ((1.0/3.0 - 1.0/2.0 * rho * rho) * sigma*nu/alpha * Math.pow(underlying, -2*beta)); final double curvaturePart1 = nu/alpha * ((1.0/3.0 - 1.0/2.0 * rho * rho) * sigma*nu/alpha * Math.pow(underlying, -2*beta) + (1.0/6.0 * beta*beta - 2.0/6.0 * beta) * sigma*alpha/nu*Math.pow(underlying, -2)); final double curvatureMaturityPart = (rho*nu + alpha*beta*Math.pow(underlying, beta-1))*d1BdK1 + alpha*Math.pow(underlying, beta)*d2BdK2; return (curvaturePart1 + maturity * curvatureMaturityPart); } /** * Exact conversion of displaced lognormal ATM volatility to normal ATM volatility. * * @param forward The forward * @param displacement The displacement (considering a displaced lognormal model, otherwise 0. * @param optionMaturity The maturity * @param lognormalVolatility The (implied) lognormal volatility. * @return The (implied) normal volatility. * @see Dimitroff, Fries, Lichtner and Rodi: Lognormal vs Normal Volatilities and Sensitivities in Practice */ public static double volatilityConversionLognormalATMtoNormalATM(final double forward, final double displacement, final double optionMaturity, final double lognormalVolatility) { final double x = lognormalVolatility * Math.sqrt(optionMaturity / 8); final double y = org.apache.commons.math3.special.Erf.erf(x); final double normalVol = Math.sqrt(2*Math.PI / optionMaturity) * (forward+displacement) * y; return normalVol; } /** * Numerical conversion of displaced lognormal volatility to normal volatility. * * @param forward The forward. * @param displacement The displacement (considering a displaced lognormal model, otherwise 0. * @param optionMaturity The maturity. * @param optionStrike The strike. * @param lognormalVolatility The (implied) lognormal volatility. * @return The (implied) normal volatility. */ public static double volatilityConversionLognormalToNormal(final double forward, final double displacement, final double optionMaturity, final double optionStrike, final double lognormalVolatility) { final double payoffUnit = 1.0; // does not matter in this conversion final double optionValue = blackScholesGeneralizedOptionValue(forward+displacement, lognormalVolatility, optionMaturity, optionStrike+displacement, payoffUnit); final double impliedNormalVolatility = bachelierOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, optionValue); return impliedNormalVolatility; } /** * Re-implementation of the Excel PRICE function (a rather primitive bond price formula). * The re-implementation is not exact, because this function does not consider daycount conventions. * * @param settlementDate Valuation date. * @param maturityDate Maturity date of the bond. * @param coupon Coupon payment. * @param yield Yield (discount factor, using frequency: 1/(1 + yield/frequency). * @param redemption Redemption (notional repayment). * @param frequency Frequency (1,2,4). * @return price Clean price. */ public static double price( final java.util.Date settlementDate, final java.util.Date maturityDate, final double coupon, final double yield, final double redemption, final int frequency) { double price = 0.0; if(maturityDate.after(settlementDate)) { price += redemption; } final Calendar paymentDate = Calendar.getInstance(); paymentDate.setTime(maturityDate); while(paymentDate.after(settlementDate)) { price += coupon; // Discount back price /= 1.0 + yield / frequency; paymentDate.add(Calendar.MONTH, -12/frequency); } final Calendar periodEndDate = (Calendar)paymentDate.clone(); periodEndDate.add(Calendar.MONTH, +12/frequency); // Accrue running period final double accrualPeriod = (paymentDate.getTimeInMillis() - settlementDate.getTime()) / (periodEndDate.getTimeInMillis() - paymentDate.getTimeInMillis()); price *= Math.pow(1.0 + yield / frequency, accrualPeriod); price -= coupon * accrualPeriod; return price; } /** * Re-implementation of the Excel PRICE function (a rather primitive bond price formula). * The re-implementation is not exact, because this function does not consider daycount conventions. * We assume we have (int)timeToMaturity/frequency future periods and the running period has * an accrual period of timeToMaturity - frequency * ((int)timeToMaturity/frequency). * * @param timeToMaturity The time to maturity. * @param coupon Coupon payment. * @param yield Yield (discount factor, using frequency: 1/(1 + yield/frequency). * @param redemption Redemption (notional repayment). * @param frequency Frequency (1,2,4). * @return price Clean price. */ public static double price( final double timeToMaturity, final double coupon, final double yield, final double redemption, final int frequency) { double price = 0.0; if(timeToMaturity > 0) { price += redemption; } double paymentTime = timeToMaturity; while(paymentTime > 0) { price += coupon; // Discount back price = price / (1.0 + yield / frequency); paymentTime -= 1.0 / frequency; } // Accrue running period final double accrualPeriod = 0.0- paymentTime; price *= Math.pow(1.0 + yield / frequency, accrualPeriod); price -= coupon * accrualPeriod; return price; } /** * Calculates the vega of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a * normal process with constant volatility, i.e., a Bachelier model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma \mathrm{d}W(t) * \] * * @param forward The forward of the underlying \( F = S(0) \exp(r T) \). * @param volatility The Bachelier volatility \( \sigma \). * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @param payoffUnit The payoff unit (e.g., the discount factor) * @return Returns the vega of a European call option under the Bachelier model. */ public static double bachelierGeneralizedOptionVega( final double forward, final double volatility, final double optionMaturity, final double optionStrike, final double payoffUnit) { if(optionMaturity < 0) { return 0; } else if(forward == optionStrike) { return Math.sqrt(optionMaturity / (Math.PI * 2.0)) * payoffUnit; } else { // Calculate analytic value final double dPlus = (forward - optionStrike) / (volatility * Math.sqrt(optionMaturity)); final double vegaAnalytic = Math.sqrt(optionMaturity) * NormalDistribution.density(dPlus) * payoffUnit; return vegaAnalytic; } } /** * Calculates the vega of a call, i.e., the payoff max(S(T)-K,0) P, where S follows a * normal process with constant volatility, i.e., a Black-Scholes model * \[ * \mathrm{d} S(t) = r S(t) \mathrm{d} t + \sigma S(t)\mathrm{d}W(t) * \] * * @param forward The forward of the underlying \( F = S(0) \exp(r T) \). * @param volatility The Black-Scholes volatility \( \sigma \). * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @param payoffUnit The payoff unit (e.g., the discount factor) * @return Returns the vega of a European call option under the Black-Scholes model. */ public static double blackScholesGeneralizedOptionVega( final double forward, final double volatility, final double optionMaturity, final double optionStrike, final double payoffUnit) { if(optionStrike <= 0.0 || optionMaturity <= 0.0) { // The Black-Scholes model does not consider it being an option return 0.0; } else { // Calculate vega final double dPlus = (Math.log(forward / optionStrike) + (0.5 * volatility * volatility) * optionMaturity) / (volatility * Math.sqrt(optionMaturity)); final double vega = payoffUnit * NormalDistribution.density(dPlus) * forward * Math.sqrt(optionMaturity); return vega; } } /** * Calculates the CEV option value of a call, i.e., the payoff max(S(T)-K,0), where S follows a CEV process. * Formula is from 2007 - Hsu Lin Lee - 'Constant Elasticity of Variance Option Pricing Model'. * CEV exponent must be between 0 and 1. * * @author Ralph Rudd * * @param initialStockValue The spot value of the underlying. * @param riskFreeRate The risk free rate r (df = exp(-r T)). * @param volatility The CEV volatility (NOT the log-normal volatility). * @param exponent The exponent of S in the diffusion term. * @param optionMaturity The option maturity T. * @param optionStrike The option strike. * @param isCall If true, the value of a call is calculated, if false, the value of a put is calculated. * @return Returns the value of a European call option under the CEV model. */ public static double constantElasticityOfVarianceOptionValue( double initialStockValue, double riskFreeRate, double volatility, double exponent, double optionMaturity, double optionStrike, boolean isCall) { final double kappa = 2 * riskFreeRate / (Math.pow(volatility, 2) * (1 - exponent) * (Math.exp(2 * riskFreeRate * (1 - exponent) * optionMaturity) - 1)); final double z = 2 + 1 / (1 - exponent); final double y = kappa * Math.pow(optionStrike, 2 * (1 - exponent)); final double x = kappa * Math.pow(initialStockValue, 2 * (1 - exponent)) * Math.exp(2 * riskFreeRate * (1 - exponent) * optionMaturity); final NonCentralChiSquaredDistribution P1 = new NonCentralChiSquaredDistribution(z, x); final NonCentralChiSquaredDistribution P2 = new NonCentralChiSquaredDistribution(z - 2, y); if (isCall) { return initialStockValue * (1 - P1.cumulativeDistribution(y)) - optionStrike * Math.exp(-riskFreeRate * optionMaturity) * P2.cumulativeDistribution(x); } else { return -initialStockValue * P1.cumulativeDistribution(y) + optionStrike * Math.exp(-riskFreeRate * optionMaturity) * (1 - P2.cumulativeDistribution(x)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy