/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.option.definition; import java.util.Collections; import java.util.Set; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.Validate; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.greeks.Greek; import com.opengamma.analytics.financial.model.option.pricing.analytic.AnalyticOptionModel; import com.opengamma.analytics.financial.model.option.pricing.analytic.BlackScholesMertonModel; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.time.DateUtils; import com.opengamma.util.time.Expiry; /** * A complex chooser option gives the holder the right to choose whether the * option is to be a standard call or put after a certain time. The exercise * style of the option, once the choice has been made, is European. * <p> * The payoff from the option with strike <i>K</i> and spot <i>S</i> is * <i>max(c<sub>BSM</i>(S, K, T<sub>C</sub>), p<sub>BSM</sub>(S, K, * T<sub>P</sub>)</i>, where <i>C<sub>BSM</sub></i> is the Black-Scholes-Merton * call price, <i>P<sub>BSM</sub></i> is the Black-Scholes-Merton put price, * <i>T<sub>C</sub></i> is the time to maturity of the call and * <i>T<sub>P</sub></i> is the time to maturity of the put. * */ public class ComplexChooserOptionDefinition extends OptionDefinition { private final OptionPayoffFunction<StandardOptionDataBundle> _payoffFunction = new OptionPayoffFunction<StandardOptionDataBundle>() { @SuppressWarnings("synthetic-access") @Override public double getPayoff(final StandardOptionDataBundle data, final Double optionPrice) { final double callPrice = BSM.getGreeks(getCallDefinition(), data, GREEKS).get(Greek.FAIR_PRICE); final double putPrice = BSM.getGreeks(getPutDefinition(), data, GREEKS).get(Greek.FAIR_PRICE); return Math.max(callPrice, putPrice); } }; private final OptionExerciseFunction<StandardOptionDataBundle> _exerciseFunction = new EuropeanExerciseFunction<>(); private final double _callStrike; private final double _putStrike; private final Expiry _callExpiry; private final Expiry _putExpiry; private final OptionDefinition _callDefinition; private final OptionDefinition _putDefinition; private static final AnalyticOptionModel<OptionDefinition, StandardOptionDataBundle> BSM = new BlackScholesMertonModel(); private static final Set<Greek> GREEKS = Collections.singleton(Greek.FAIR_PRICE); /** * * @param callStrike The strike of the potential call option * @param putStrike The strike of the potential put option * @param chooseDate The choice date (expiry) of the chooser option * @param callExpiry The expiry date of the potential call option * @param putExpiry The expiry date of the potential put option */ public ComplexChooserOptionDefinition(final Expiry chooseDate, final double callStrike, final Expiry callExpiry, final double putStrike, final Expiry putExpiry) { super(null, chooseDate, null); Validate.notNull(callExpiry); Validate.notNull(putExpiry); ArgumentChecker.notNegative(callStrike, "call strike"); ArgumentChecker.notNegative(putStrike, "put strike"); if (callExpiry.getExpiry().isBefore(chooseDate.getExpiry())) { throw new IllegalArgumentException("Call expiry must be after the choose date"); } if (putExpiry.getExpiry().isBefore(chooseDate.getExpiry())) { throw new IllegalArgumentException("Put expiry must be after the choose date"); } _callStrike = callStrike; _putStrike = putStrike; _callExpiry = callExpiry; _putExpiry = putExpiry; _callDefinition = new EuropeanVanillaOptionDefinition(callStrike, callExpiry, true); _putDefinition = new EuropeanVanillaOptionDefinition(putStrike, putExpiry, false); } public double getCallStrike() { return _callStrike; } public double getPutStrike() { return _putStrike; } public Expiry getCallExpiry() { return _callExpiry; } public Expiry getPutExpiry() { return _putExpiry; } public double getTimeToCallExpiry(final ZonedDateTime date) { if (date.isAfter(getCallExpiry().getExpiry())) { throw new IllegalArgumentException("Date " + date + " is after call expiry " + getCallExpiry()); } return DateUtils.getDifferenceInYears(date, getCallExpiry().getExpiry()); } public double getTimeToPutExpiry(final ZonedDateTime date) { if (date.isAfter(getPutExpiry().getExpiry())) { throw new IllegalArgumentException("Date " + date + " is after put expiry " + getPutExpiry()); } return DateUtils.getDifferenceInYears(date, getPutExpiry().getExpiry()); } public OptionDefinition getCallDefinition() { return _callDefinition; } public OptionDefinition getPutDefinition() { return _putDefinition; } @Override public OptionExerciseFunction<StandardOptionDataBundle> getExerciseFunction() { return _exerciseFunction; } @Override public OptionPayoffFunction<StandardOptionDataBundle> getPayoffFunction() { return _payoffFunction; } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((_callExpiry == null) ? 0 : _callExpiry.hashCode()); long temp; temp = Double.doubleToLongBits(_callStrike); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + ((_putExpiry == null) ? 0 : _putExpiry.hashCode()); temp = Double.doubleToLongBits(_putStrike); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } final ComplexChooserOptionDefinition other = (ComplexChooserOptionDefinition) obj; if (Double.doubleToLongBits(_callStrike) != Double.doubleToLongBits(other._callStrike)) { return false; } if (Double.doubleToLongBits(_putStrike) != Double.doubleToLongBits(other._putStrike)) { return false; } return ObjectUtils.equals(_callExpiry, other._callExpiry) && ObjectUtils.equals(_putExpiry, other._putExpiry); } }