protected double blackPrice( final double spot, final double strike, final double expiry, final double rate, final double carry, final double vol, final boolean isCall) { final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall); final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE.getBlackScholes(rate, rate - carry, vol); double sMin = 0.0; double sMax = spot * Math.exp(_z * Math.sqrt(expiry)); BoundaryCondition lower; BoundaryCondition upper; if (isCall) { lower = new DirichletBoundaryCondition(0.0, sMin); Function1D<Double, Double> upperValue = new Function1D<Double, Double>() { @Override public Double evaluate(Double tau) { return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike); } }; upper = new DirichletBoundaryCondition(upperValue, sMax); } else { Function1D<Double, Double> lowerValue = new Function1D<Double, Double>() { @Override public Double evaluate(Double tau) { return Math.exp(-rate * tau) * strike; } }; lower = new DirichletBoundaryCondition(lowerValue, sMin); upper = new DirichletBoundaryCondition(0.0, sMax); } final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda); final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching); final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh); PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, intCon, lower, upper, grid); PDEResults1D res = SOLVER.solve(pdeData); // for now just do linear interpolation on price. TODO replace this with something more robust final double[] xNodes = grid.getSpaceNodes(); final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot); final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]); return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1); }
private double logContactPriceFromPureSpot(final LocalVolatilitySurfaceMoneyness lv) { final double fT = DIV_CURVES.getF(EXPIRY); final double dT = DIV_CURVES.getD(EXPIRY); final double dStar = dT / (fT - dT); final ConvectionDiffusionPDE1DCoefficients pde = PDE_PROVIDER.getLogBackwardsLocalVol(EXPIRY, lv); final double theta = 0.5; final double yL = -0.5; final double yH = 0.5; final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false); final BoundaryCondition lower = new NeumannBoundaryCondition(1 / (1 + dStar * Math.exp(-yL)), yL, true); final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, yH, false); final MeshingFunction timeMesh = new ExponentialMeshing(0.0, EXPIRY, 100, 0.0); final MeshingFunction spaceMesh = new ExponentialMeshing(yL, yH, 101, 0.0); final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh); final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<>(pde, PURE_LOG_PAY_OFF, lower, upper, grid); final PDEResults1D res = solver.solve(db); final int n = res.getNumberSpaceNodes(); final double val = res.getFunctionValue(n / 2); return val; }
public void testMixedLogNormalVolSurface() { final double[] weights = new double[] {0.9, 0.1}; final double[] sigmas = new double[] {0.2, 0.8}; final MultiHorizonMixedLogNormalModelData data = new MultiHorizonMixedLogNormalModelData(weights, sigmas); final LocalVolatilitySurfaceStrike lv = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(FORWARD_CURVE, data); final LocalVolatilitySurfaceMoneyness lvm = LocalVolatilitySurfaceConverter.toMoneynessSurface(lv, FORWARD_CURVE); final double expected = Math.sqrt(weights[0] * sigmas[0] * sigmas[0] + weights[1] * sigmas[1] * sigmas[1]); final double ft = FORWARD_CURVE.getForward(EXPIRY); final double theta = 0.5; // Review the accuracy is very dependent on these numbers final double fL = Math.log(ft / 30); final double fH = Math.log(30 * ft); final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false); final BoundaryCondition lower = new NeumannBoundaryCondition(1.0, fL, true); final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, fH, false); final MeshingFunction timeMesh = new ExponentialMeshing(0, EXPIRY, 50, 0.0); final MeshingFunction spaceMesh = new HyperbolicMeshing(fL, fH, (fL + fH) / 2, 101, 0.4); final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE_DATA_PROVIDER.getLogBackwardsLocalVol(EXPIRY, lvm); final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh); final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>( pde, INITIAL_COND, lower, upper, grid); final PDEResults1D res = solver.solve(db); final int n = res.getNumberSpaceNodes(); final double[] values = new double[n]; for (int i = 0; i < n; i++) { values[i] = res.getFunctionValue(i); } final Interpolator1DDataBundle idb = INTERPOLATOR.getDataBundle(grid.getSpaceNodes(), values); final double elogS = INTERPOLATOR.interpolate(idb, Math.log(ft)); final double kVol = Math.sqrt(-2 * (elogS - Math.log(ft)) / EXPIRY); assertEquals( expected, kVol, 1e-3); // TODO Improve on 10bps error - local surface is (by construction) very smooth. // NOTE: this has got worse since we improved the T -> 0 // behaviour of the mixed log-normal local volatility surface }
public void testFlatSurface() { final double theta = 0.5; final double ft = FORWARD_CURVE.getForward(EXPIRY); final double fL = Math.log(ft / 5.0); final double fH = Math.log(5.0 * ft); final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false); final BoundaryCondition lower = new NeumannBoundaryCondition(1.0, fL, true); final BoundaryCondition upper = new NeumannBoundaryCondition(1.0, fH, false); final MeshingFunction timeMesh = new ExponentialMeshing(0, EXPIRY, 100, 0.0); final MeshingFunction spaceMesh = new ExponentialMeshing(fL, fH, 101, 0.0); final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh); final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<>(PDE, INITIAL_COND, lower, upper, grid); final PDEResults1D res = solver.solve(db); final int n = res.getNumberSpaceNodes(); final double kVol = Math.sqrt(-2 * (res.getFunctionValue(n / 2) - Math.log(ft)) / EXPIRY); assertEquals(FLAT_VOL, kVol, 1e-6); final YieldAndDiscountCurve yieldCurve = new YieldCurve("test", ConstantDoublesCurve.from(DRIFT)); final AffineDividends ad = AffineDividends.noDividends(); final EquityVarianceSwapBackwardsPurePDE backSolver = new EquityVarianceSwapBackwardsPurePDE(); final PureLocalVolatilitySurface plv = new PureLocalVolatilitySurface(ConstantDoublesSurface.from(FLAT_VOL)); final double[] res2 = backSolver.expectedVariance(SPOT, yieldCurve, ad, EXPIRY, plv); final double kVol2 = Math.sqrt(res2[0] / EXPIRY); assertEquals(FLAT_VOL, kVol2, 1e-6); }
/** * Computes the price of a one-touch out barrier option in the Black-Scholes world by solving the * BS PDE on a finite difference grid. If a barrier is hit at any time before expiry, the option * is cancelled (knocked-out) and a rebate (which is often zero) is paid <b>immediately</b>. If * the barrier is not hit, then a normal European option payment is made. * * <p>If the barrier is above the spot it is assumed to be an up-and-out barrier (otherwise it * would expire immediately) otherwise it is a down-and-out barrier As there are exact formulae * for this case (see {@link BlackBarrierPriceFunction}), this is purely for testing purposes. * * @param spot The current (i.e. option price time) of the underlying * @param barrierLevel The barrier level * @param strike The strike of the European option * @param expiry The expiry of the option * @param rate The interest rate. * @param carry The cost-of-carry (i.e. the forward = spot*exp(costOfCarry*T) ) * @param vol The Black volatility. * @param isCall true for call * @param rebate The rebate amount. * @return The price. */ public double outBarrier( final double spot, final double barrierLevel, final double strike, final double expiry, final double rate, final double carry, final double vol, final boolean isCall, final double rebate) { final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall); final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE.getBlackScholes(rate, rate - carry, vol); final boolean isUp = barrierLevel > spot; final double adj = 0.0; // _lambda == 0 ? ZETA * vol * Math.sqrt(expiry / (_nTNodes - 1)) : 0.0; // System.out.println("adjustment: " + adj); double sMin; double sMax; BoundaryCondition lower; BoundaryCondition upper; if (isUp) { sMin = 0.0; sMax = barrierLevel * Math.exp(-adj); // bring the barrier DOWN slightly to adjust for discrete monitoring if (isCall) { lower = new DirichletBoundaryCondition(0.0, sMin); } else { Function1D<Double, Double> lowerValue = new Function1D<Double, Double>() { @Override public Double evaluate(Double tau) { return Math.exp(-rate * tau) * strike; } }; lower = new DirichletBoundaryCondition(lowerValue, sMin); } upper = new DirichletBoundaryCondition(rebate, sMax); } else { sMin = barrierLevel * Math.exp(adj); // bring the barrier UP slightly to adjust for discrete monitoring sMax = spot * Math.exp(_z * Math.sqrt(expiry)); lower = new DirichletBoundaryCondition(rebate, sMin); if (isCall) { Function1D<Double, Double> upperValue = new Function1D<Double, Double>() { @Override public Double evaluate(Double tau) { return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike); } }; upper = new DirichletBoundaryCondition(upperValue, sMax); } else { upper = new DirichletBoundaryCondition(0.0, sMax); } } final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda); final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching); final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh); PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, intCon, lower, upper, grid); PDEResults1D res = SOLVER.solve(pdeData); // for now just do linear interpolation on price. TODO replace this with something more robust final double[] xNodes = grid.getSpaceNodes(); final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot); final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]); return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1); }