/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.finitedifference.applications; import org.apache.commons.lang.Validate; import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDE1DFullCoefficients; import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDE1DStandardCoefficients; import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve; import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceConverter; import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceMoneyness; import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceStrike; import com.opengamma.analytics.math.curve.Curve; import com.opengamma.analytics.math.function.Function; import com.opengamma.analytics.math.surface.ConstantDoublesSurface; import com.opengamma.analytics.math.surface.FunctionalDoublesSurface; import com.opengamma.analytics.math.surface.Surface; /** * */ public class PDE1DCoefficientsProvider { private static final Surface<Double, Double, Double> ZERO_SURFACE = ConstantDoublesSurface.from(0.0); private static final Surface<Double, Double, Double> UNITY_SURFACE = ConstantDoublesSurface.from(1.0); // ****************************************************************************************************************************** // Backwards PDEs - the initial condition is the option payoff at expiry, T, and the PDE is solved backwards in time, t. // In this setup, the time coordinate is time-to-expiry tau = T-t, so this initial condition is at tau = 0 // ****************************************************************************************************************************** /** * Sets up a standard Black-Scholes PDE * $$\frac{\partial V}{\partial \tau} - \frac{\sigma^2 s^2}{2} \frac{\partial^2 V}{\partial s^2} -(r-y)s \frac{\partial V}{\partial s} + rV = 0$$ * where the 'time' term $\tau$ is time to maturity * @param rate The rate, $r$ * @param yield The yield $y$ <b>Note</b> this is NOT the cost-of-carry $b$, they are related by $b=r-y$ * @param vol The volatility * @return a ConvectionDiffusionPDE1DStandardCofficients */ public ConvectionDiffusionPDE1DStandardCoefficients getBlackScholes(final double rate, final double yield, final double vol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double s = ts[1]; final double temp = s * vol; return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double s = ts[1]; return -s * (rate - yield); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ConstantDoublesSurface.from(rate)); } /** * Sets up a Black-Scholes PDE with term structure of rates, yield and volatility * $$\frac{\partial V}{\partial \tau} - \frac{\sigma^2 s^2}{2} \frac{\partial^2 V}{\partial s^2} -(r-y)s \frac{\partial V}{\partial s} + rV = 0$$ * where the 'time' term $\tau$ is time to maturity * @param rate The rate, $r$ * @param yield The yield (or cost-of-carry), $y$ * @param vol The volatility * @return a ConvectionDiffusionPDE1DStandardCofficients */ public ConvectionDiffusionPDE1DStandardCoefficients getBlackScholes(final Curve<Double, Double> rate, final Curve<Double, Double> yield, final Curve<Double, Double> vol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; final double temp = s * vol.getYValue(t); return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; return -s * (rate.getYValue(t) - yield.getYValue(t)); } }; final Function<Double, Double> c = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; return rate.getYValue(t); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), FunctionalDoublesSurface.from(c)); } /** * Set up a Black-Scholes PDE where the space variable is the log of the spot $x=\ln(s)$ * $$\frac{\partial V}{\partial \tau} - \frac{\sigma^2}{2} \frac{\partial^2 V}{\partial x^2} -(\frac{\sigma^2}{2}+r-y) \frac{\partial V}{\partial x} + rV = 0$$ * @param rate The rate, $r$ * @param yield The yield (or cost-of-carry), $y$ * @param vol The volatility * @return a ConvectionDiffusionPDE1DStandardCofficients */ public ConvectionDiffusionPDE1DStandardCoefficients getLogBlackScholes(final double rate, final double yield, final double vol) { final double a = -vol * vol / 2; final double b = -a - (rate - yield); return new ConvectionDiffusionPDE1DStandardCoefficients(ConstantDoublesSurface.from(a), ConstantDoublesSurface.from(b), ConstantDoublesSurface.from(rate)); } /** * This models the CEV process - the forward follows the SDE $df = \sigma f^\beta dW$ where $f(t,T) = \mathbb{E^T}[s_T|\mathcal{F}_t]$ and * is a Martingale. The corresponding PDE for the option price is * $$\frac{\partial V}{\partial \tau} - \frac{(\sigma^*)^2 f^{2\beta}}{2} \frac{\partial^2 V}{\partial f^2} + rV = 0$$ * The term $r$ is yield to maturity - it can be set to zero and a discount factor applied to the option price instead. * @param zeroRate The zero rate * @param beta The beta * @param vol The volatility * @return A ConvectionDiffusionPDE1DStandardCofficients */ public ConvectionDiffusionPDE1DStandardCoefficients getCEV(final double zeroRate, final double beta, final double vol) { Validate.isTrue(beta >= 0.0, "Need beta >=0"); final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double s = ts[1]; final double temp = vol * Math.pow(s, beta); return -0.5 * temp * temp; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), ZERO_SURFACE, ConstantDoublesSurface.from(zeroRate)); } /** * Classic (i.e. formulated in terms of constant instantaneous short rates) backwards PDE setup for option price under * a local volatility. The state variables are time-to-maturity and (spot) value of the underlying<p> * <b>Note</b> The local volatility is a function of calendar time, t, and spot, but since the time variable in this PDE is * $\tau = T-t$ where $T$ is the expiry, we must specify $T$ is get the correct local volatility at a given $\tau$. * @param rate The risk free rate (domestic risk free rate in FX case) * @param yield The dividend yield (for equity) or foreign risk free (for FX) * @param maturity the expiry (time-to-maturity) * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and * the value of the underlying at that time * @return The ConvectionDiffusionPDE1DStandardCofficients to run through a PDE solver that will give the time-zero option price as a * function of the time-zero value of the underlying */ public ConvectionDiffusionPDE1DStandardCoefficients getBackwardsLocalVol(final double rate, final double yield, final double maturity, final LocalVolatilitySurfaceStrike localVol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double tau = ts[0]; final double s = ts[1]; final double t = maturity - tau; final double temp = s * localVol.getVolatility(t, s); return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double s = ts[1]; return -s * (rate - yield); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ConstantDoublesSurface.from(rate)); } /** * Backwards PDE setup for option price under a local volatility. The state variables are time-to-maturity and * forward value of the underlying. <b>Note</b> the option price will be the forward (non-discounted) price. The PDE is * $$ * \frac{\partial V}{\partial \tau} - \frac{1}{2}\sigma\left(t,f_{\tau}\frac{f(0,t)}{f(0,T)}\right)^2f(t,T)^2\frac{\partial^2 V}{\partial f_{\tau}^2}=0\\ * \text{where} \quad f_{\tau} \equiv f(T-\tau,T) * $$ * @param forwardCurve the time-zero forward curve, F(0,T), for the underlying * @param maturity the time-to-maturity * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and * the value of the underlying at that time<p> * <b> Note </b> Even though the PDE is expressed in terms of the forward rate, the local volatility MUST be specified in terms of * the spot, hence the term $f_{\tau}\frac{f(0,t)}{f(0,T)} = s_{tau}$ in the local volatility arguments * @return The data to run through a PDE solver that will give the time-zero forward option price as a function of the time-zero * value of the underlying */ public ConvectionDiffusionPDE1DStandardCoefficients getBackwardsLocalVol(final ForwardCurve forwardCurve, final double maturity, final LocalVolatilitySurfaceStrike localVol) { final double fT = forwardCurve.getForward(maturity); final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tf) { Validate.isTrue(tf.length == 2); final double tau = tf[0]; final double f = tf[1]; final double t = maturity - tau; final double ft = forwardCurve.getForward(t); final double temp = f * localVol.getVolatility(t, f * ft / fT); // NOTE: f * ft / fT = s, the spot return -0.5 * temp * temp; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), ZERO_SURFACE, ZERO_SURFACE); } /** * Backwards PDE setup for option price under a local volatility. The state variables are time-to-maturity and * value of the underlying. The PDE is * @param instRiskFreeRate the instantaneous risk free rate, r_t * @param instCostOfCarry the instantaneous cost-of-carry, b_t = r_t - q_t, where q_t is the (instantaneous) yield * @param maturity the time-to-maturity * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and * the value of the underlying at that time * @return The data to run through a PDE solver that will give the time-zero option price as a function of the time-zero * value of the underlying */ public ConvectionDiffusionPDE1DStandardCoefficients getBackwardsLocalVol(final Curve<Double, Double> instRiskFreeRate, final Curve<Double, Double> instCostOfCarry, final double maturity, final LocalVolatilitySurfaceStrike localVol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double tau = ts[0]; final double s = ts[1]; final double t = maturity - tau; final double temp = s * localVol.getVolatility(t, s); return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double tau = ts[0]; final double s = ts[1]; final double t = maturity - tau; return -s * instCostOfCarry.getYValue(t); } }; final Function<Double, Double> c = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double tau = ts[0]; final double t = maturity - tau; return instRiskFreeRate.getYValue(t); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), FunctionalDoublesSurface.from(c)); } /** * Backwards PDE setup for option price under a local volatility parameterised by moneyness. The state variables are time-to-maturity and * forward value of the underlying. <b>Note</b> the option price will be the forward (non-discounted) price. The PDE is * $$ * \frac{\partial V}{\partial \tau} - \frac{1}{2}\sigma\left(t,\frac{f_{\tau}}{f(0,T)}\right)^2f(t,T)^2\frac{\partial^2 V}{\partial f_{\tau}^2}=0\\ * \text{where} \quad f_{\tau} \equiv f(T-\tau,T) * $$ * @param maturity the time-to-maturity * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and * the moneyness at that time * @return The data to run through a PDE solver that will give the time-zero forward option price as a function of the time-zero * value of the underlying */ public ConvectionDiffusionPDE1DStandardCoefficients getBackwardsLocalVol(final double maturity, final LocalVolatilitySurfaceMoneyness localVol) { final ForwardCurve fc = localVol.getForwardCurve(); final double f0 = fc.getForward(maturity); final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tf) { Validate.isTrue(tf.length == 2); final double tau = tf[0]; final double f = tf[1]; final double t = maturity - tau; final double x = f / f0; final double temp = f * localVol.getVolatilityForMoneyness(t, x); return -0.5 * temp * temp; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), ZERO_SURFACE, ZERO_SURFACE); } /** * Backwards PDE setup for option price under a local volatility parameterised by spot. The state variables are time-to-maturity, $\tau$, and * log-spot , $x = \log(s)$. <b>Note</b> the option price will be the forward (non-discounted) price. <P>The PDE is * $$ * \frac{\partial V}{\partial \tau} - \frac{1}{2}\sigma\left(T-\tau,e^x\right)^2\frac{\partial^2 V}{\partial x^2} + * \left(\frac{1}{2}\sigma\left(T-\tau,e^x\right)^2 -r + y \right)\frac{\partial V}{\partial x} + rV=0\\ * \text{where} \quad x \equiv \log S * $$ * @param rate The risk free rate (domestic risk free rate in FX case) * @param yield The dividend yield (for equity) or foreign risk free (for FX) * @param maturity the time-to-maturity * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and spot * @return The data to run through a PDE solver that will give the time-zero option price as a function of the time-zero * value of the log-spot */ public ConvectionDiffusionPDE1DStandardCoefficients getLogBackwardsLocalVol(final double rate, final double yield, final double maturity, final LocalVolatilitySurfaceStrike localVol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { Validate.isTrue(tx.length == 2); final double tau = tx[0]; final double x = tx[1]; final double s = Math.exp(x); final double t = maturity - tau; final double temp = localVol.getVolatility(t, s); return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { Validate.isTrue(tx.length == 2); final double tau = tx[0]; final double x = tx[1]; final double t = maturity - tau; final double s = Math.exp(x); final double temp = localVol.getVolatility(t, s); return 0.5 * temp * temp - (rate - yield); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ConstantDoublesSurface.from(rate)); } /** * Backwards PDE setup for option price under a local volatility parameterised by moneyness. The state variables are time-to-maturity, $\tau$, and * log-forward value of the underlying, $x$. <b>Note</b> the option price will be the forward (non-discounted) price. <P>The PDE is * $$ * \frac{\partial V}{\partial \tau} - \frac{1}{2}\sigma\left(t,\frac{e^x}{f(0,T)}\right)^2\frac{\partial^2 V}{\partial x^2} + * \frac{1}{2}\sigma\left(t,\frac{e^x}{f(0,T)}\right)^2\frac{\partial V}{\partial x}=0\\ * \text{where} \quad x \equiv \log f(T-\tau,T) * $$ * @param maturity the time-to-maturity * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and moneyness * @return The data to run through a PDE solver that will give the time-zero <b>forward</b> option price as a function of the time-zero * value of the log-forward */ public ConvectionDiffusionPDE1DStandardCoefficients getLogBackwardsLocalVol(final double maturity, final LocalVolatilitySurfaceMoneyness localVol) { final ForwardCurve fc = localVol.getForwardCurve(); final double f0T = fc.getForward(maturity); final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { Validate.isTrue(tx.length == 2); final double tau = tx[0]; final double x = tx[1]; final double t = maturity - tau; final double ftT = Math.exp(x); final double temp = localVol.getVolatilityForMoneyness(t, ftT / f0T); return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { Validate.isTrue(tx.length == 2); final double tau = tx[0]; final double x = tx[1]; final double t = maturity - tau; final double ftT = Math.exp(x); final double temp = localVol.getVolatilityForMoneyness(t, ftT / f0T); return 0.5 * temp * temp; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ZERO_SURFACE); } /** * Backwards PDE setup for option price under a local volatility parameterised by spot. The state variables are time-to-maturity, $\tau$, and * log-forward value of the underlying, $x$. <b>Note</b> the option price will be the forward (non-discounted) price. <P>The PDE is * $$ * \frac{\partial V}{\partial \tau} - \frac{1}{2}\sigma\left(t,\frac{e^xf(0,T-\tau)}{f(0,T)}\right)^2\frac{\partial^2 V}{\partial x^2} + * \frac{1}{2}\sigma\left(t,\frac{e^xf(0,T-\tau)}{f(0,T)}\right)^2\frac{\partial V}{\partial x}=0\\ * \text{where} \quad x \equiv \log f(T-\tau,T) * $$ * @param forwardCurve Forward Curve * @param maturity the time-to-maturity * @param localVol A local volatility surface - gives the instantaneous (log-normal) volatility as a function of time and spot * @return The data to run through a PDE solver that will give the time-zero <b>forward</b> option price as a function of the time-zero * value of the log of the forward */ public ConvectionDiffusionPDE1DStandardCoefficients getLogBackwardsLocalVol(final ForwardCurve forwardCurve, final double maturity, final LocalVolatilitySurfaceStrike localVol) { final double f0T = forwardCurve.getForward(maturity); final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { Validate.isTrue(tx.length == 2); final double tau = tx[0]; final double x = tx[1]; final double ftT = Math.exp(x); final double t = maturity - tau; final double f0t = forwardCurve.getForward(t); final double temp = localVol.getVolatility(t, ftT * f0t / f0T); // NOTE: f * ft / fT = s, the spot return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { Validate.isTrue(tx.length == 2); final double tau = tx[0]; final double x = tx[1]; final double t = maturity - tau; final double ftT = Math.exp(x); final double f0t = forwardCurve.getForward(t); final double temp = localVol.getVolatility(t, ftT * f0t / f0T); return 0.5 * temp * temp; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ZERO_SURFACE); } // ****************************************************************************************************************************** // Forward PDEs - the initial condition is at expiry, T = 0, and the PDE is solved forward in expiry time, T (calendar time t remains zero). // In this setup the coordinates are T and strike, k, so solving the PDE gives a set rather that a single option price // (NOTE: only option prices with European call and put payoffs can be transformed into a forward PDE) // ****************************************************************************************************************************** /** * Classic (i.e. formulated in terms of constant instantaneous short rates) forward PDE setup for option price under a BlackSholes framework. * The state variables are time-to-maturity, $T$ and strike, $k$. The PDE is * $$ * \frac{\partial V(T,k)}{\partial T} - \frac{1}{2}\sigma^2 k^2 \frac{\partial^2 V(T,k)}{\partial k^2} + k(r-y) \frac{\partial V(T,k)}{\partial k} +yV(T,k)=0 * $$ * @param rate $r$ The risk free rate (domestic risk free rate in FX case) * @param yield $y$ The dividend yield (for equity) or foreign risk free (for FX) * @param vol $\sigma$ The volatility * @return The data to run through a PDE solver that will give the option price as a function of strike and expiry */ public ConvectionDiffusionPDE1DStandardCoefficients getForwardBlackSholes(final double rate, final double yield, final double vol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tk) { Validate.isTrue(tk.length == 2); final double k = tk[1]; final double temp = k * vol; return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... tk) { Validate.isTrue(tk.length == 2); final double k = tk[1]; return k * (rate - yield); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ConstantDoublesSurface.from(yield)); } /** * Classic (i.e. formulated in terms of constant instantaneous short rates) forwards PDE setup for option price under a local volatility. * The state variables are time-to-maturity, $T$ and strike, $k$. The PDE is * $$ * \frac{\partial V(T,k)}{\partial T} - \frac{1}{2}\sigma(T,k)^2 k^2 \frac{\partial^2 V(T,k)}{\partial k^2} + k(r-y) \frac{\partial V(T,k)}{\partial k} +yV(T,k)=0 * $$ * @param rate $r$ The risk free rate (domestic risk free rate in FX case) * @param yield $y$ The dividend yield (for equity) or foreign risk free (for FX) * @param localVol $\sigma(T,k)$ The local volatility * @return The data to run through a PDE solver that will give the option price as a function of strike and expiry */ public ConvectionDiffusionPDE1DStandardCoefficients getForwardLocalVolatility(final double rate, final double yield, final LocalVolatilitySurfaceStrike localVol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tk) { Validate.isTrue(tk.length == 2); final double t = tk[0]; final double k = tk[1]; final double temp = k * localVol.getVolatility(t, k); return -0.5 * temp * temp; } }; final Function<Double, Double> b = new Function<Double, Double>() { @Override public Double evaluate(final Double... tk) { Validate.isTrue(tk.length == 2); final double k = tk[1]; return k * (rate - yield); } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), ConstantDoublesSurface.from(yield)); } /** * Set up for running forward PDE with local volatility parameterised by moneyness (i.e. m = strike/forward). The option * prices will be in a normalised form, and will have to be multiplied by the discount factor AND the forward to recover * the actual option price. The PDE is * $$ * \frac{\partial V(T,m)}{\partial T} - \frac{1}{2}\sigma\left(T,m\right)^2 m^2 \frac{\partial^2 V(T,m)}{\partial m^2}=0\\ * \text{where}\quad m \equiv \frac{k}{f(0,T)} * $$ * @param localVol A local volatility surface parametrised by expiry and moneyness (=strike/forward) * @return The data to run through a PDE solver that will give the modified option price (the true option price is this * multiplied by the discount factor AND the forward) as a function of expiry and <b>moneyness</b> */ public ConvectionDiffusionPDE1DStandardCoefficients getForwardLocalVol(final LocalVolatilitySurfaceMoneyness localVol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... tm) { Validate.isTrue(tm.length == 2); final double t = tm[0]; final double m = tm[1]; final double vol = localVol.getVolatilityForMoneyness(t, m); final double temp = m * vol; return -0.5 * temp * temp; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), ZERO_SURFACE, ZERO_SURFACE); } /** * Set up for running forward PDE with local volatility parameterised by strike. The option * prices will be in a normalised form, and will have to be multiplied by the discount factor AND the forward to recover * the actual option price. <P> The PDE is * $$ * \frac{\partial V(T,m)}{\partial T} - \frac{1}{2}\sigma\left(T,mf(0,T)\right)^2 m^2 \frac{\partial^2 V(T,m)}{\partial m^2}=0\\ * \text{where}\quad m \equiv \frac{k}{f(0,T)} * $$ <p> * <b>Note</b> The coordinates used in the PDE are expiry (T) and <b>moneyness</b> (m), hence the solution will be in these coordinates * @param forwardCurve the forward curve * @param localVol A local volatility surface * @return The data to run through a PDE solver that will give the modified option price (the true option price is this * multiplied by the discount factor AND the forward) as a function of expiry and <b>moneyness</b> */ public ConvectionDiffusionPDE1DStandardCoefficients getForwardLocalVol(final ForwardCurve forwardCurve, final LocalVolatilitySurfaceStrike localVol) { final LocalVolatilitySurfaceMoneyness lvm = LocalVolatilitySurfaceConverter.toMoneynessSurface(localVol, forwardCurve); return getForwardLocalVol(lvm); } // ****************************************************************************************************************************** // Forward PDEs for the transition density $P(t,S_t,T,S_T)$ // ****************************************************************************************************************************** /** * The Fokker-Plank equation with a deterministic (time dependent) short-rate and a local volatility (i.e. a instantaneous volatility that is * a function of time and spot). If the SDE for the underlying is $\frac{dS_t}{S_t}=r_t dt + \sigma(t,S_t)^2dW_t$, then the PDE for the transition * density $P(t,S_t;T,S_T)$ is * $$ * \frac{\partial P}{\partial T} - \frac{1}{2}\frac{\partial^2[ \sigma(T,S)^2S^2P]}{\partial S^2} + * r_T \frac{\partial[ S^2 P]}{\partial S} * $$ * @param shortRate Deterministic (time dependent) short-rate * @param localVol Local Volatility * @return Description of Fokker-Plank PDE */ public ConvectionDiffusionPDE1DFullCoefficients getFokkerPlank(final Curve<Double, Double> shortRate, final LocalVolatilitySurfaceStrike localVol) { final Function<Double, Double> alpha = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; final double temp = localVol.getVolatility(t, s) * s; return -0.5 * temp * temp; } }; final Function<Double, Double> beta = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; return shortRate.getYValue(t) * s; } }; return new ConvectionDiffusionPDE1DFullCoefficients(UNITY_SURFACE, UNITY_SURFACE, ZERO_SURFACE, FunctionalDoublesSurface.from(alpha), FunctionalDoublesSurface.from(beta)); } /** * The Fokker-Plank equation with a deterministic (time dependent) short-rate and a local volatility (i.e. a instantaneous volatility that is * a function of time and spot), but written in the form.<P> * $$ * \frac{\partial P}{\partial t}+ a(t,x)\frac{\partial^2 P}{\partial x^2}+b(t,x)\frac{\partial P}{\partial x2}+c(t,x)V=0 * $$ * @param shortRate Deterministic (time dependent) short-rate * @param localVol Local Volatility * @return Description of Fokker-Plank PDE */ public ConvectionDiffusionPDE1DStandardCoefficients getFokkerPlankInStandardCoefficients(final Curve<Double, Double> shortRate, final LocalVolatilitySurfaceStrike localVol) { final Function<Double, Double> a = new Function<Double, Double>() { @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; final double sigma = localVol.getVolatility(t, s) * s; return -0.5 * sigma * sigma; } }; final Function<Double, Double> b = new Function<Double, Double>() { @SuppressWarnings("synthetic-access") @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; final double lvDiv = getLocalVolFirstDiv(localVol, t, s); final double lv = localVol.getVolatility(t, s); return s * (shortRate.getYValue(t) - 2 * lv * (s * lvDiv + lv)); } }; final Function<Double, Double> c = new Function<Double, Double>() { @SuppressWarnings("synthetic-access") @Override public Double evaluate(final Double... ts) { Validate.isTrue(ts.length == 2); final double t = ts[0]; final double s = ts[1]; final double lv1Div = getLocalVolFirstDiv(localVol, t, s); final double lv2Div = getLocalVolSecondDiv(localVol, t, s); final double lv = localVol.getVolatility(t, s); final double temp1 = (lv + s * lv1Div); final double temp2 = lv * s * (s * lv2Div + 2 * lv1Div); return shortRate.getYValue(t) - temp1 * temp1 - temp2; } }; return new ConvectionDiffusionPDE1DStandardCoefficients(FunctionalDoublesSurface.from(a), FunctionalDoublesSurface.from(b), FunctionalDoublesSurface.from(c)); } // TODO handle with a central calculator private static double getLocalVolFirstDiv(final LocalVolatilitySurfaceStrike localVol, final double t, final double s) { final double eps = 1e-4; final double up = localVol.getVolatility(t, s + eps); final double down = localVol.getVolatility(t, s - eps); return (up - down) / 2 / eps; } private static double getLocalVolSecondDiv(final LocalVolatilitySurfaceStrike localVol, final double t, final double s) { final double eps = 1e-4; final double up = localVol.getVolatility(t, s + eps); final double mid = localVol.getVolatility(t, s); final double down = localVol.getVolatility(t, s - eps); return (up + down - 2 * mid) / eps / eps; } }