/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.volatility.surface; import java.util.Map; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.analytics.financial.model.option.definition.OptionDefinition; import com.opengamma.analytics.financial.model.option.definition.StandardOptionDataBundle; import com.opengamma.analytics.financial.model.option.pricing.OptionPricingException; import com.opengamma.analytics.financial.model.option.pricing.analytic.AnalyticOptionModel; import com.opengamma.analytics.financial.model.option.pricing.analytic.BlackScholesMertonModel; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.rootfinding.SingleRootFinder; import com.opengamma.analytics.math.surface.ConstantDoublesSurface; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.DoublesPair; /** * */ public class BlackScholesMertonImpliedVolatilitySurfaceModel implements VolatilitySurfaceModel<Map<OptionDefinition, Double>, StandardOptionDataBundle> { private static final Logger s_logger = LoggerFactory.getLogger(BlackScholesMertonImpliedVolatilitySurfaceModel.class); private final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> _bsm = new BlackScholesMertonModel(); private SingleRootFinder<StandardOptionDataBundle, Double> _rootFinder; @Override public VolatilitySurface getSurface(final Map<OptionDefinition, Double> optionPrices, final StandardOptionDataBundle optionDataBundle) { Validate.notNull(optionPrices); ArgumentChecker.notEmpty(optionPrices, "option prices"); Validate.notNull(optionDataBundle); if (optionPrices.size() > 1) { s_logger.info("Option price map had more than one entry: using the first pair to imply volatility"); } final Map.Entry<OptionDefinition, Double> entry = optionPrices.entrySet().iterator().next(); final Double price = entry.getValue(); final Function1D<StandardOptionDataBundle, Double> pricingFunction = _bsm.getPricingFunction(entry.getKey()); _rootFinder = new MyBisectionSingleRootFinder(optionDataBundle, price); return _rootFinder.getRoot(pricingFunction, optionDataBundle.withVolatilitySurface(new VolatilitySurface(ConstantDoublesSurface.from(0))), optionDataBundle.withVolatilitySurface(new VolatilitySurface(ConstantDoublesSurface.from(10)))).getVolatilitySurface(); } private static class MyBisectionSingleRootFinder implements SingleRootFinder<StandardOptionDataBundle, Double> { private final StandardOptionDataBundle _data; private final double _price; private final DoublesPair _origin = DoublesPair.of(0., 0.); private static final double ACCURACY = 1e-12; private static final int MAX_ATTEMPTS = 10000; public MyBisectionSingleRootFinder(final StandardOptionDataBundle data, final double price) { _data = data; _price = price; } @Override public StandardOptionDataBundle getRoot(final Function1D<StandardOptionDataBundle, Double> function, final StandardOptionDataBundle... volData) { final StandardOptionDataBundle lowVolData = volData[0]; final StandardOptionDataBundle highVolData = volData[1]; final Double lowPrice = function.evaluate(lowVolData) - _price; if (Math.abs(lowPrice) < ACCURACY) { return lowVolData; } Double highPrice = function.evaluate(highVolData) - _price; if (Math.abs(highPrice) < ACCURACY) { return highVolData; } final double highVol = highVolData.getVolatilitySurface().getVolatility(_origin); final double lowVol = lowVolData.getVolatilitySurface().getVolatility(_origin); double dVol, midVol, rootVol; if (lowPrice < 0) { dVol = highVol - lowVol; rootVol = lowVol; } else { dVol = lowVol - highVol; rootVol = highVol; } StandardOptionDataBundle midVolData; for (int i = 0; i < MAX_ATTEMPTS; i++) { dVol *= 0.5; midVol = rootVol + dVol; midVolData = _data.withVolatilitySurface(new VolatilitySurface(ConstantDoublesSurface.from((midVol)))); highPrice = function.evaluate(midVolData) - _price; if (highPrice <= 0) { rootVol = midVol; } if (Math.abs(dVol) < ACCURACY) { return _data.withVolatilitySurface(new VolatilitySurface(ConstantDoublesSurface.from((midVol)))); } } throw new OptionPricingException("Could not find volatility in " + MAX_ATTEMPTS + " attempts"); } } }