DiscreteTenorRollOver.java
/*
* (c) Copyright Christian P. Fries, Germany. Contact: email@christian-fries.de.
*
* Created on 20.05.2026
*/
package net.finmath.montecarlo.interestrate.products;
import net.finmath.exception.CalculationException;
import net.finmath.montecarlo.interestrate.TermStructureMonteCarloSimulationModel;
import net.finmath.stochastic.RandomVariable;
/**
* A diagnostic hedge instrument representing a discretely rolled unit bond.
*
* The product pays
*
* \[
* 1 + (T_{i+1}-T_{i}) L(T_{i},T_{i+1};T_{i})
* \]
*
* at payment time \( T_{i+1} \). Hence, in a single-curve model, the value at
* fixing time \( T_{i} \) is one. This is the instrument obtained if the cash
* from a matured \(T_{i}\)-bond is rolled from \( T_{i} \) to
* {\( T_{i+1} \) at the fixing Libor, rather than through the model's
* short-rate money-market numeraire.
*
* <p>
* The class is mainly meant as a diagnostic product for comparing LMM and
* short-rate/Hull-White roll-over conventions. For {@code t < T_i} its value is
* the adapted model-implied value of a {@code T_i}-bond, because the payoff is
* constructed to have value one at {@code T_i}. For {@code T_i <= t < T_{i+1}}
* the payoff amount is known and is valued as that amount times the adapted
* payment bond. At {@code T_{i+1}} the payoff amount is returned.
* </p>
*
* @author Christian Fries
*/
public class DiscreteTenorRollOver extends AbstractTermStructureMonteCarloProduct {
private final double fixingTime;
private final double paymentTime;
private final double periodLength;
/**
* Creates a discretely rolled unit bond.
*
* @param fixingTime The fixing time \( T_{i} \).
* @param paymentTime The payment time \( T_{i+1} \).
* @param periodLength The period length \( T_{i+1}-T_{i} \).
*/
public DiscreteTenorRollOver(
final double fixingTime,
final double paymentTime,
final double periodLength) {
super();
if(paymentTime <= fixingTime) {
throw new IllegalArgumentException("paymentTime must be greater than fixingTime.");
}
if(periodLength <= 0.0) {
throw new IllegalArgumentException("periodLength must be positive.");
}
this.fixingTime = fixingTime;
this.paymentTime = paymentTime;
this.periodLength = periodLength;
}
@Override
public RandomVariable getValue(
final double evaluationTime,
final TermStructureMonteCarloSimulationModel model) throws CalculationException {
final double tolerance = 1E-12;
if(evaluationTime < fixingTime - tolerance) {
return getAdaptedBondValue(evaluationTime, fixingTime, model);
}
final RandomVariable payoffAmount = model.getForwardRate(fixingTime, fixingTime, paymentTime).mult(periodLength).add(1.0);
if(evaluationTime < paymentTime - tolerance) {
return payoffAmount.mult(getAdaptedBondValue(evaluationTime, paymentTime, model));
}
if(evaluationTime <= paymentTime + tolerance) {
return payoffAmount;
}
/*
* Safe fallback for evaluation after payment: return the paid amount carried
* forward with the same numeraire/weight convention as standard products.
*/
return payoffAmount
.div(model.getNumeraire(paymentTime))
.mult(model.getMonteCarloWeights(paymentTime))
.mult(model.getNumeraire(evaluationTime))
.div(model.getMonteCarloWeights(evaluationTime));
}
private RandomVariable getAdaptedBondValue(
final double evaluationTime,
final double maturity,
final TermStructureMonteCarloSimulationModel model) throws CalculationException {
final double tolerance = 1E-12;
if(maturity <= evaluationTime + tolerance) {
if(maturity >= evaluationTime - tolerance) {
return model.getRandomVariableForConstant(1.0);
}
return model.getRandomVariableForConstant(1.0)
.div(model.getNumeraire(maturity))
.mult(model.getMonteCarloWeights(maturity))
.mult(model.getNumeraire(evaluationTime))
.div(model.getMonteCarloWeights(evaluationTime));
}
return model.getModel().getForwardDiscountBond(
model.getProcess(),
evaluationTime,
maturity);
}
public double getFixingTime() {
return fixingTime;
}
public double getPaymentTime() {
return paymentTime;
}
public double getPeriodLength() {
return periodLength;
}
@Override
public String toString() {
return super.toString()
+ "\nfixingTime: " + fixingTime
+ "\npaymentTime: " + paymentTime
+ "\nperiodLength: " + periodLength;
}
}