WorstOfOption.java
package net.finmath.finitedifference.assetderivativevaluation.products;
import net.finmath.finitedifference.assetderivativevaluation.models.FiniteDifferenceEquityModel;
import net.finmath.finitedifference.solvers.FDMSolver;
import net.finmath.finitedifference.solvers.FDMSolverFactory;
import net.finmath.modelling.EuropeanExercise;
import net.finmath.modelling.Exercise;
import net.finmath.modelling.products.CallOrPut;
/**
* Finite-difference valuation of a European option on the worst of two assets.
*
* <p>
* Let {@code S1} and {@code S2} denote the two underlying assets and let
* {@code T} be the maturity. The worst-of underlying is
* </p>
*
* <p>
* <i>
* W(T) = min(S_1(T), S_2(T)).
* </i>
* </p>
*
* <p>
* The payoff is
* </p>
*
* <p>
* <i>
* \left( \omega \left( W(T) - K \right) \right)^{+},
* </i>
* </p>
*
* <p>
* where {@code \omega = +1} for a call and {@code \omega = -1} for a put. Hence
* the product pays
* </p>
*
* <p>
* <i>
* \left( min(S_1(T), S_2(T)) - K \right)^{+}
* </i>
* </p>
*
* <p>
* for a call, and
* </p>
*
* <p>
* <i>
* \left( K - min(S_1(T), S_2(T)) \right)^{+}
* </i>
* </p>
*
* <p>
* for a put.
* </p>
*
* <p>
* This product is genuinely non-linear in the two assets and cannot be reduced
* to the linear-combination basket class. The current implementation requires a
* two-dimensional model state and supports European exercise only.
* </p>
*
* @author Alessandro Gnoatto
*/
public class WorstOfOption implements FiniteDifferenceEquityProduct {
/**
* The first underlying name.
*/
private final String firstUnderlyingName;
/**
* The second underlying name.
*/
private final String secondUnderlyingName;
/**
* The maturity.
*/
private final double maturity;
/**
* The strike.
*/
private final double strike;
/**
* The call or put.
*/
private final CallOrPut callOrPut;
/**
* The exercise.
*/
private final Exercise exercise;
/**
* Creates a European worst-of option for two named underlyings.
*
* @param firstUnderlyingName Name of the first underlying.
* @param secondUnderlyingName Name of the second underlying.
* @param maturity Maturity {@code T}.
* @param strike Strike {@code K}.
* @param callOrPut Option type.
*/
public WorstOfOption(
final String firstUnderlyingName,
final String secondUnderlyingName,
final double maturity,
final double strike,
final CallOrPut callOrPut) {
if (callOrPut == null) {
throw new IllegalArgumentException("callOrPut must not be null.");
}
if (maturity < 0.0) {
throw new IllegalArgumentException("maturity must be non-negative.");
}
this.firstUnderlyingName = firstUnderlyingName;
this.secondUnderlyingName = secondUnderlyingName;
this.maturity = maturity;
this.strike = strike;
this.callOrPut = callOrPut;
this.exercise = new EuropeanExercise(maturity);
}
/**
* Creates a European worst-of option for two named underlyings.
*
* @param firstUnderlyingName Name of the first underlying.
* @param secondUnderlyingName Name of the second underlying.
* @param maturity Maturity {@code T}.
* @param strike Strike {@code K}.
* @param callOrPutSign Payoff sign, where {@code 1.0} corresponds to a call
* and {@code -1.0} corresponds to a put.
*/
public WorstOfOption(
final String firstUnderlyingName,
final String secondUnderlyingName,
final double maturity,
final double strike,
final double callOrPutSign) {
this(
firstUnderlyingName,
secondUnderlyingName,
maturity,
strike,
mapCallOrPut(callOrPutSign)
);
}
/**
* Creates a European worst-of option with unnamed underlyings.
*
* @param maturity Maturity {@code T}.
* @param strike Strike {@code K}.
* @param callOrPut Option type.
*/
public WorstOfOption(
final double maturity,
final double strike,
final CallOrPut callOrPut) {
this(null, null, maturity, strike, callOrPut);
}
/**
* Creates a European worst-of option with unnamed underlyings.
*
* @param maturity Maturity {@code T}.
* @param strike Strike {@code K}.
* @param callOrPutSign Payoff sign, where {@code 1.0} corresponds to a call
* and {@code -1.0} corresponds to a put.
*/
public WorstOfOption(
final double maturity,
final double strike,
final double callOrPutSign) {
this(null, null, maturity, strike, mapCallOrPut(callOrPutSign));
}
@Override
public double[] getValue(final double evaluationTime, final FiniteDifferenceEquityModel model) {
validateModel(model);
final FDMSolver solver = FDMSolverFactory.createSolver(model, this, exercise);
return solver.getValue(
evaluationTime,
maturity,
this::terminalPayoff
);
}
@Override
public double[][] getValues(final FiniteDifferenceEquityModel model) {
validateModel(model);
final FDMSolver solver = FDMSolverFactory.createSolver(model, this, exercise);
return solver.getValues(
maturity,
this::terminalPayoff
);
}
/**
* Returns the terminal payoff.
*
* @param firstAssetValue Value of the first asset.
* @param secondAssetValue Value of the second asset.
* @return The terminal payoff.
*/
private double terminalPayoff(final double firstAssetValue, final double secondAssetValue) {
final double worstOfValue = Math.min(firstAssetValue, secondAssetValue);
return Math.max(callOrPut.toInteger() * (worstOfValue - strike), 0.0);
}
/**
* Validates model compatibility.
*
* @param model The finite-difference model.
*/
private void validateModel(final FiniteDifferenceEquityModel model) {
if (model == null) {
throw new IllegalArgumentException("model must not be null.");
}
if (model.getSpaceTimeDiscretization().getNumberOfSpaceGrids() != 2) {
throw new IllegalArgumentException(
"WorstOfOption currently requires a two-dimensional space discretization.");
}
if (model.getInitialValue() == null || model.getInitialValue().length != 2) {
throw new IllegalArgumentException(
"WorstOfOption currently requires a two-dimensional model state.");
}
}
/**
* Maps the sign convention to the option type.
*
* @param sign Option sign.
* @return The corresponding option type.
*/
private static CallOrPut mapCallOrPut(final double sign) {
if (sign == 1.0) {
return CallOrPut.CALL;
}
if (sign == -1.0) {
return CallOrPut.PUT;
}
throw new IllegalArgumentException("Unknown option type.");
}
/**
* Returns the name of the first underlying.
*
* @return The name of the first underlying, or {@code null} if unspecified.
*/
public String getFirstUnderlyingName() {
return firstUnderlyingName;
}
/**
* Returns the name of the second underlying.
*
* @return The name of the second underlying, or {@code null} if
* unspecified.
*/
public String getSecondUnderlyingName() {
return secondUnderlyingName;
}
/**
* Returns the maturity.
*
* @return The maturity.
*/
public double getMaturity() {
return maturity;
}
/**
* Returns the strike.
*
* @return The strike.
*/
public double getStrike() {
return strike;
}
/**
* Returns the option type.
*
* @return The option type.
*/
public CallOrPut getCallOrPut() {
return callOrPut;
}
/**
* Returns the exercise specification.
*
* @return The exercise specification.
*/
public Exercise getExercise() {
return exercise;
}
}