PiecewiseConstantVolatility.java
package net.finmath.marketdata.model.volatilities;
import java.util.Arrays;
import java.util.Objects;
import net.finmath.time.TimeDiscretization;
/**
* Piecewise constant local volatility function.
*
* <p>
* The volatility is a deterministic function of running time only. It does not
* depend on the asset value. Hence it represents a time-dependent
* Black-Scholes-type volatility as a {@link LocalVolatility}.
* </p>
*
* <p>
* The time discretization represents running times {@code t}, not time to
* maturity. The convention is:
* </p>
* <ul>
* <li>{@code volatilities[i]} applies from
* {@code timeDiscretization.getTime(i)} inclusive to
* {@code timeDiscretization.getTime(i+1)} exclusive;</li>
* <li>the last volatility applies from the last discretization time
* onward;</li>
* <li>before the first discretization time, the first volatility is used.</li>
* </ul>
*
* <p>
* The number of volatility values must equal
* {@code timeDiscretization.getNumberOfTimes()}.
* </p>
*
* @author Alessandro Gnoatto
*/
public class PiecewiseConstantVolatility implements LocalVolatility {
/**
* The time discretization.
*/
private final TimeDiscretization timeDiscretization;
/**
* The volatilities.
*/
private final double[] volatilities;
/**
* Creates a piecewise constant volatility from a time discretization and an
* array of volatility values.
*
* @param timeDiscretization The running-time discretization at which the
* volatility values become active.
* @param volatilities The volatility values.
*/
public PiecewiseConstantVolatility(
final TimeDiscretization timeDiscretization,
final double[] volatilities) {
this.timeDiscretization = Objects.requireNonNull(
timeDiscretization,
"timeDiscretization"
);
if (volatilities == null || volatilities.length == 0) {
throw new IllegalArgumentException("The volatility array must not be null or empty.");
}
if (volatilities.length != timeDiscretization.getNumberOfTimes()) {
throw new IllegalArgumentException(
"The number of volatility values must equal the number of "
+ "time discretization points. Received "
+ volatilities.length
+ " volatility values and "
+ timeDiscretization.getNumberOfTimes()
+ " time points."
);
}
this.volatilities = volatilities.clone();
for (final double volatility : this.volatilities) {
checkVolatility(volatility);
}
}
/**
* Returns the local volatility {@code sigma(t,S)}.
*
* <p>
* Since this implementation is piecewise constant in time and independent
* of
* the asset value, the argument {@code assetValue} is ignored.
* </p>
*
* @param time The running time.
* @param assetValue The asset value.
* @return The piecewise constant volatility at the given time.
*/
@Override
public double getValue(final double time, final double assetValue) {
if (Double.isNaN(time) || Double.isInfinite(time)) {
throw new IllegalArgumentException("The time must be finite.");
}
int timeIndex = timeDiscretization.getTimeIndexNearestLessOrEqual(time);
if (timeIndex < 0) {
timeIndex = 0;
}
if (timeIndex >= volatilities.length) {
timeIndex = volatilities.length - 1;
}
return volatilities[timeIndex];
}
/**
* Returns the volatility value associated with a time index.
*
* @param timeIndex The time index.
* @return The volatility value.
*/
public double getVolatility(final int timeIndex) {
if (timeIndex < 0 || timeIndex >= volatilities.length) {
throw new IllegalArgumentException(
"Time index out of bounds: " + timeIndex
);
}
return volatilities[timeIndex];
}
/**
* Returns the volatility value active at the given running time.
*
* @param time The running time.
* @return The volatility value.
*/
public double getVolatility(final double time) {
return getValue(time, 0.0);
}
/**
* Returns the running-time discretization.
*
* @return The running-time discretization.
*/
public TimeDiscretization getTimeDiscretization() {
return timeDiscretization;
}
/**
* Returns a defensive copy of the volatility array.
*
* @return The volatility array.
*/
public double[] getVolatilities() {
return volatilities.clone();
}
/**
* Returns the number of volatility values.
*
* @return The number of volatility values.
*/
public int getNumberOfVolatilities() {
return volatilities.length;
}
/**
* Creates a clone with modified volatility values and the same time
* discretization.
*
* @param newVolatilities The new volatility values.
* @return The modified volatility function.
*/
public PiecewiseConstantVolatility getCloneForModifiedVolatilities(
final double[] newVolatilities) {
return new PiecewiseConstantVolatility(
timeDiscretization,
newVolatilities
);
}
/**
* Creates a clone with a modified time discretization and the same
* volatility
* values.
*
* @param newTimeDiscretization The new time discretization.
* @return The modified volatility function.
*/
public PiecewiseConstantVolatility getCloneForModifiedTimeDiscretization(
final TimeDiscretization newTimeDiscretization) {
return new PiecewiseConstantVolatility(
newTimeDiscretization,
volatilities
);
}
private static void checkVolatility(final double volatility) {
if (Double.isNaN(volatility) || Double.isInfinite(volatility)) {
throw new IllegalArgumentException("Volatility must be finite.");
}
if (volatility < 0.0) {
throw new IllegalArgumentException("Volatility must be non-negative.");
}
}
@Override
public String toString() {
return "PiecewiseConstantVolatility [timeDiscretization="
+ timeDiscretization
+ ", volatilities="
+ Arrays.toString(volatilities)
+ "]";
}
}