com.opengamma.analytics.financial.interestrate.future.method.BondFutureHullWhiteMethod.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.analytics.financial.interestrate.future.method.BondFutureHullWhiteMethod.java

Source

/**
 * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.analytics.financial.interestrate.future.method;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.Validate;

import com.opengamma.analytics.financial.interestrate.CashFlowEquivalentCalculator;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivative;
import com.opengamma.analytics.financial.interestrate.InterestRateCurveSensitivity;
import com.opengamma.analytics.financial.interestrate.YieldCurveBundle;
import com.opengamma.analytics.financial.interestrate.annuity.derivative.AnnuityPaymentFixed;
import com.opengamma.analytics.financial.interestrate.future.derivative.BondFuture;
import com.opengamma.analytics.financial.model.interestrate.HullWhiteOneFactorPiecewiseConstantInterestRateModel;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.definition.HullWhiteOneFactorPiecewiseConstantDataBundle;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.rootfinding.BracketRoot;
import com.opengamma.analytics.math.rootfinding.RidderSingleRootFinder;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
import com.opengamma.util.money.CurrencyAmount;
import com.opengamma.util.tuple.DoublesPair;

/**
 * Method to compute the price of bond future using the Hull-White one factor model to estimate the delivery option.
 * <P> Reference: Henrard, M. Bonds futures and their options: more than the cheapest-to-deliver; quality option and margining. Journal of Fixed Income, 2006, 16, 62-75
 * @deprecated Use {@link com.opengamma.analytics.financial.interestrate.future.provider.BondFutureHullWhiteMethod}
 */
@Deprecated
public final class BondFutureHullWhiteMethod extends BondFutureMethod {

    /**
     * The number of points used in the numerical integration process.
     */
    private static final int DEFAULT_NB_POINTS = 81;
    /**
     * The normal distribution implementation.
     */
    private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
    /**
     * The cash flow equivalent calculator used in computations.
     */
    private static final CashFlowEquivalentCalculator CFEC = CashFlowEquivalentCalculator.getInstance();
    /**
     * The model used in computations.
     */
    private static final HullWhiteOneFactorPiecewiseConstantInterestRateModel MODEL = new HullWhiteOneFactorPiecewiseConstantInterestRateModel();

    /**
     * Creates the method unique instance.
     */
    private static final BondFutureHullWhiteMethod INSTANCE = new BondFutureHullWhiteMethod();

    /**
     * Return the method unique instance.
     * @return The instance.
     */
    public static BondFutureHullWhiteMethod getInstance() {
        return INSTANCE;
    }

    /**
     * Constructor.
     */
    private BondFutureHullWhiteMethod() {
    }

    /**
     * Computes the future price from the curves used to price the underlying bonds and a Hull-White one factor model.
     * @param future The future security.
     * @param hwData The curve and Hull-White parameters.
     * @param nbPoint The number of point in the numerical cross estimation.
     * @return The future price.
     */
    public double price(final BondFuture future, final HullWhiteOneFactorPiecewiseConstantDataBundle hwData,
            final int nbPoint) {
        Validate.notNull(future, "Future");
        Validate.notNull(hwData, "Hull-White data bundle");
        final int nbBond = future.getDeliveryBasket().length;
        final YieldAndDiscountCurve bndCurve = hwData
                .getCurve(future.getDeliveryBasket()[0].getDiscountingCurveName());
        final double expiry = future.getNoticeLastTime();
        final double delivery = future.getDeliveryLastTime();
        final double dfdelivery = bndCurve.getDiscountFactor(delivery);
        // Constructing non-homogeneous point series for the numerical estimations.
        final int nbPtWing = ((int) Math.floor(nbPoint / 20.)); // Number of point on each wing.
        final int nbPtCenter = nbPoint - 2 * nbPtWing;
        final double prob = 1.0 / (2.0 * nbPtCenter);
        final double xStart = NORMAL.getInverseCDF(prob);
        final double[] x = new double[nbPoint];
        for (int loopwing = 0; loopwing < nbPtWing; loopwing++) {
            x[loopwing] = xStart * (1.0 + (nbPtWing - loopwing) / 2.0);
            x[nbPoint - 1 - loopwing] = -xStart * (1.0 + (nbPtWing - loopwing) / 2.0);
        }
        for (int loopcent = 0; loopcent < nbPtCenter; loopcent++) {
            x[nbPtWing + loopcent] = xStart + loopcent * (-2.0 * xStart) / (nbPtCenter - 1);
        }
        // Figures for each bond
        final double[][] cfTime = new double[nbBond][];
        final double[][] df = new double[nbBond][];
        final double[][] alpha = new double[nbBond][];
        final double[][] beta = new double[nbBond][];
        final double[][] cfaAdjusted = new double[nbBond][];
        final double[] e = new double[nbBond];
        final double[][] pv = new double[nbPoint][nbBond];
        final AnnuityPaymentFixed[] cf = new AnnuityPaymentFixed[nbBond];
        for (int loopbnd = 0; loopbnd < nbBond; loopbnd++) {
            cf[loopbnd] = future.getDeliveryBasket()[loopbnd].accept(CFEC, hwData);
            final int nbCf = cf[loopbnd].getNumberOfPayments();
            cfTime[loopbnd] = new double[nbCf];
            df[loopbnd] = new double[nbCf];
            alpha[loopbnd] = new double[nbCf];
            beta[loopbnd] = new double[nbCf];
            cfaAdjusted[loopbnd] = new double[nbCf];
            for (int loopcf = 0; loopcf < nbCf; loopcf++) {
                cfTime[loopbnd][loopcf] = cf[loopbnd].getNthPayment(loopcf).getPaymentTime();
                df[loopbnd][loopcf] = bndCurve.getDiscountFactor(cfTime[loopbnd][loopcf]);
                alpha[loopbnd][loopcf] = MODEL.alpha(hwData.getHullWhiteParameter(), 0.0, expiry, delivery,
                        cfTime[loopbnd][loopcf]);
                beta[loopbnd][loopcf] = MODEL.futuresConvexityFactor(hwData.getHullWhiteParameter(), expiry,
                        cfTime[loopbnd][loopcf], delivery);
                cfaAdjusted[loopbnd][loopcf] = df[loopbnd][loopcf] / dfdelivery * beta[loopbnd][loopcf]
                        * cf[loopbnd].getNthPayment(loopcf).getAmount() / future.getConversionFactor()[loopbnd];
                for (int looppt = 0; looppt < nbPoint; looppt++) {
                    pv[looppt][loopbnd] += cfaAdjusted[loopbnd][loopcf]
                            * Math.exp(-alpha[loopbnd][loopcf] * alpha[loopbnd][loopcf] / 2.0
                                    - alpha[loopbnd][loopcf] * x[looppt]);
                }
            }
            e[loopbnd] = future.getDeliveryBasket()[loopbnd].getAccruedInterest()
                    / future.getConversionFactor()[loopbnd];
            for (int looppt = 0; looppt < nbPoint; looppt++) {
                pv[looppt][loopbnd] -= e[loopbnd];
            }
        }
        // Minimum: create a list of index of the CTD in each interval and a first estimate of the crossing point (x[]).
        final double[] pvMin = new double[nbPoint];
        final int[] indMin = new int[nbPoint];
        for (int looppt = 0; looppt < nbPoint; looppt++) {
            pvMin[looppt] = Double.POSITIVE_INFINITY;
            for (int loopbnd = 0; loopbnd < nbBond; loopbnd++) {
                if (pv[looppt][loopbnd] < pvMin[looppt]) {
                    pvMin[looppt] = pv[looppt][loopbnd];
                    indMin[looppt] = loopbnd;
                }
            }
        }
        final ArrayList<Double> refx = new ArrayList<>();
        final ArrayList<Integer> ctd = new ArrayList<>();
        int lastInd = indMin[0];
        ctd.add(indMin[0]);
        for (int looppt = 1; looppt < nbPoint; looppt++) {
            if (indMin[looppt] != lastInd) {
                ctd.add(indMin[looppt]);
                lastInd = indMin[looppt];
                refx.add(x[looppt]);
            }
        }

        // Sum on each interval
        final int nbInt = ctd.size();
        final double[] kappa = new double[nbInt - 1];
        double price = 0.0;
        if (nbInt == 1) {
            for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(0)].length; loopcf++) {
                price += cfaAdjusted[ctd.get(0)][loopcf];
            }
            price -= e[ctd.get(0)];
        } else {
            // The intersections
            final BracketRoot bracketer = new BracketRoot();
            final double accuracy = 1.0E-8;
            final RidderSingleRootFinder rootFinder = new RidderSingleRootFinder(accuracy);
            for (int loopint = 1; loopint < nbInt; loopint++) {
                final BondDifference cross = new BondDifference(cfaAdjusted[ctd.get(loopint - 1)],
                        alpha[ctd.get(loopint - 1)], e[ctd.get(loopint - 1)], cfaAdjusted[ctd.get(loopint)],
                        alpha[ctd.get(loopint)], e[ctd.get(loopint)]);
                final double[] range = bracketer.getBracketedPoints(cross, refx.get(loopint - 1) - 0.01,
                        refx.get(loopint - 1) + 0.01);
                kappa[loopint - 1] = rootFinder.getRoot(cross, range[0], range[1]);
            }
            // From -infinity to first cross.
            for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(0)].length; loopcf++) {
                price += cfaAdjusted[ctd.get(0)][loopcf] * NORMAL.getCDF(kappa[0] + alpha[ctd.get(0)][loopcf]);
            }
            price -= e[ctd.get(0)] * NORMAL.getCDF(kappa[0]);
            // Between cross
            for (int loopint = 1; loopint < nbInt - 1; loopint++) {
                for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(loopint)].length; loopcf++) {
                    price += cfaAdjusted[ctd.get(loopint)][loopcf]
                            * (NORMAL.getCDF(kappa[loopint] + alpha[ctd.get(loopint)][loopcf])
                                    - NORMAL.getCDF(kappa[loopint - 1] + alpha[ctd.get(loopint)][loopcf]));
                }
                price -= e[ctd.get(loopint)] * (NORMAL.getCDF(kappa[loopint]) - NORMAL.getCDF(kappa[loopint - 1]));
            }
            // From last cross to +infinity
            for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(nbInt - 1)].length; loopcf++) {
                price += cfaAdjusted[ctd.get(nbInt - 1)][loopcf]
                        * (1.0 - NORMAL.getCDF(kappa[nbInt - 2] + alpha[ctd.get(nbInt - 1)][loopcf]));
            }
            price -= e[ctd.get(nbInt - 1)] * (1 - NORMAL.getCDF(kappa[nbInt - 2]));
        }

        return price;
    }

    /**
     * Computes the future price from the curves used to price the underlying bonds and a Hull-White one factor model. The default number of points is used for the numerical search.
     * @param future The future security.
     * @param hwData The curve and Hull-White parameters.
     * @return The future price.
     */
    public double price(final BondFuture future, final HullWhiteOneFactorPiecewiseConstantDataBundle hwData) {
        return price(future, hwData, DEFAULT_NB_POINTS);
    }

    /**
     * Computes the present value of future from the curves using the cheapest-to-deliver and computing the value as a forward.
     * @param future The future.
     * @param curves The yield curves. Should contain the credit and repo curves associated to the instrument.
     * @return The present value.
     */
    public CurrencyAmount presentValue(final BondFuture future,
            final HullWhiteOneFactorPiecewiseConstantDataBundle curves) {
        Validate.notNull(future, "Future");
        final double futurePrice = price(future, curves);
        return presentValueFromPrice(future, futurePrice);
    }

    /**
     * Computes the present value of future from the curves using the cheapest-to-deliver and computing the value as a forward.
     * @param instrument The future.
     * @param curves The yield curves. Should contain the credit and repo curves associated to the instrument.
     * @return The present value.
     */
    @Override
    public CurrencyAmount presentValue(final InstrumentDerivative instrument, final YieldCurveBundle curves) {
        Validate.isTrue(instrument instanceof BondFuture, "Bond future");
        Validate.isTrue(curves instanceof HullWhiteOneFactorPiecewiseConstantDataBundle,
                "Bundle should contain Hull-White data");
        return presentValue((BondFuture) instrument, (HullWhiteOneFactorPiecewiseConstantDataBundle) curves);
    }

    /**
     * Computes the future price curve sensitivity.
     * @param future The future security.
     * @param hwData The curve and Hull-White parameters.
     * @param nbPoint The number of point in the numerical cross estimation.
     * @return The curve sensitivity.
     */
    public InterestRateCurveSensitivity priceCurveSensitivity(final BondFuture future,
            final HullWhiteOneFactorPiecewiseConstantDataBundle hwData, final int nbPoint) {
        Validate.notNull(future, "Future");
        Validate.notNull(hwData, "Hull-White data bundle");
        final int nbBond = future.getDeliveryBasket().length;
        final YieldAndDiscountCurve bndCurve = hwData
                .getCurve(future.getDeliveryBasket()[0].getDiscountingCurveName());
        final double expiry = future.getNoticeLastTime();
        final double delivery = future.getDeliveryLastTime();
        final double dfdelivery = bndCurve.getDiscountFactor(delivery);
        // Constructing non-homogeneous point series for the numerical estimations.
        final int nbPtWing = ((int) Math.floor(nbPoint / 20.)); // Number of point on each wing.
        final int nbPtCenter = nbPoint - 2 * nbPtWing;
        final double prob = 1.0 / (2.0 * nbPtCenter);
        final double xStart = NORMAL.getInverseCDF(prob);
        final double[] x = new double[nbPoint];
        for (int loopwing = 0; loopwing < nbPtWing; loopwing++) {
            x[loopwing] = xStart * (1.0 + (nbPtWing - loopwing) / 2.0);
            x[nbPoint - 1 - loopwing] = -xStart * (1.0 + (nbPtWing - loopwing) / 2.0);
        }
        for (int loopcent = 0; loopcent < nbPtCenter; loopcent++) {
            x[nbPtWing + loopcent] = xStart + loopcent * (-2.0 * xStart) / (nbPtCenter - 1);
        }
        // Figures for each bond
        final double[][] cfTime = new double[nbBond][];
        final double[][] df = new double[nbBond][];
        final double[][] alpha = new double[nbBond][];
        final double[][] beta = new double[nbBond][];
        final double[][] cfaAdjusted = new double[nbBond][];
        final double[] e = new double[nbBond];
        final double[][] pv = new double[nbPoint][nbBond];
        final AnnuityPaymentFixed[] cf = new AnnuityPaymentFixed[nbBond];
        for (int loopbnd = 0; loopbnd < nbBond; loopbnd++) {
            cf[loopbnd] = future.getDeliveryBasket()[loopbnd].accept(CFEC, hwData);
            final int nbCf = cf[loopbnd].getNumberOfPayments();
            cfTime[loopbnd] = new double[nbCf];
            df[loopbnd] = new double[nbCf];
            alpha[loopbnd] = new double[nbCf];
            beta[loopbnd] = new double[nbCf];
            cfaAdjusted[loopbnd] = new double[nbCf];
            for (int loopcf = 0; loopcf < nbCf; loopcf++) {
                cfTime[loopbnd][loopcf] = cf[loopbnd].getNthPayment(loopcf).getPaymentTime();
                df[loopbnd][loopcf] = bndCurve.getDiscountFactor(cfTime[loopbnd][loopcf]);
                alpha[loopbnd][loopcf] = MODEL.alpha(hwData.getHullWhiteParameter(), 0.0, expiry, delivery,
                        cfTime[loopbnd][loopcf]);
                beta[loopbnd][loopcf] = MODEL.futuresConvexityFactor(hwData.getHullWhiteParameter(), expiry,
                        cfTime[loopbnd][loopcf], delivery);
                cfaAdjusted[loopbnd][loopcf] = df[loopbnd][loopcf] / dfdelivery * beta[loopbnd][loopcf]
                        * cf[loopbnd].getNthPayment(loopcf).getAmount() / future.getConversionFactor()[loopbnd];
                for (int looppt = 0; looppt < nbPoint; looppt++) {
                    pv[looppt][loopbnd] += cfaAdjusted[loopbnd][loopcf]
                            * Math.exp(-alpha[loopbnd][loopcf] * alpha[loopbnd][loopcf] / 2.0
                                    - alpha[loopbnd][loopcf] * x[looppt]);
                }
            }
            e[loopbnd] = future.getDeliveryBasket()[loopbnd].getAccruedInterest()
                    / future.getConversionFactor()[loopbnd];
            for (int looppt = 0; looppt < nbPoint; looppt++) {
                pv[looppt][loopbnd] -= e[loopbnd];
            }
        }
        // Minimum: create a list of index of the CTD in each interval and a first estimate of the crossing point (x[]).
        final double[] pvMin = new double[nbPoint];
        final int[] indMin = new int[nbPoint];
        for (int looppt = 0; looppt < nbPoint; looppt++) {
            pvMin[looppt] = Double.POSITIVE_INFINITY;
            for (int loopbnd = 0; loopbnd < nbBond; loopbnd++) {
                if (pv[looppt][loopbnd] < pvMin[looppt]) {
                    pvMin[looppt] = pv[looppt][loopbnd];
                    indMin[looppt] = loopbnd;
                }
            }
        }
        final ArrayList<Double> refx = new ArrayList<>();
        final ArrayList<Integer> ctd = new ArrayList<>();
        int lastInd = indMin[0];
        ctd.add(indMin[0]);
        for (int looppt = 1; looppt < nbPoint; looppt++) {
            if (indMin[looppt] != lastInd) {
                ctd.add(indMin[looppt]);
                lastInd = indMin[looppt];
                refx.add(x[looppt]);
            }
        }
        // Sum on each interval
        final int nbInt = ctd.size();
        final double[] kappa = new double[nbInt - 1];
        //    double price = 0.0;
        if (nbInt != 1) {
            // The intersections
            final BracketRoot bracketer = new BracketRoot();
            final double accuracy = 1.0E-8;
            final RidderSingleRootFinder rootFinder = new RidderSingleRootFinder(accuracy);
            for (int loopint = 1; loopint < nbInt; loopint++) {
                final BondDifference cross = new BondDifference(cfaAdjusted[ctd.get(loopint - 1)],
                        alpha[ctd.get(loopint - 1)], e[ctd.get(loopint - 1)], cfaAdjusted[ctd.get(loopint)],
                        alpha[ctd.get(loopint)], e[ctd.get(loopint)]);
                final double[] range = bracketer.getBracketedPoints(cross, refx.get(loopint - 1) - 0.01,
                        refx.get(loopint - 1) + 0.01);
                kappa[loopint - 1] = rootFinder.getRoot(cross, range[0], range[1]);
            }
        }

        // === Backward Sweep ===
        final double priceBar = 1.0;
        final double[][] cfaAdjustedBar = new double[nbBond][];
        final double[][] dfBar = new double[nbBond][];
        for (int loopbnd = 0; loopbnd < nbBond; loopbnd++) {
            final int nbCf = cf[loopbnd].getNumberOfPayments();
            cfaAdjustedBar[loopbnd] = new double[nbCf];
            dfBar[loopbnd] = new double[nbCf];
        }
        double dfdeliveryBar = 0.0;
        final Map<String, List<DoublesPair>> resultMap = new HashMap<>();
        final List<DoublesPair> listCredit = new ArrayList<>();
        if (nbInt == 1) {
            for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(0)].length; loopcf++) {
                cfaAdjustedBar[ctd.get(0)][loopcf] = priceBar;
                dfBar[ctd.get(0)][loopcf] = beta[ctd.get(0)][loopcf] / dfdelivery
                        * cf[ctd.get(0)].getNthPayment(loopcf).getAmount()
                        / future.getConversionFactor()[ctd.get(0)] * cfaAdjustedBar[ctd.get(0)][loopcf];
                listCredit.add(new DoublesPair(cfTime[ctd.get(0)][loopcf],
                        -cfTime[ctd.get(0)][loopcf] * df[ctd.get(0)][loopcf] * dfBar[ctd.get(0)][loopcf]));
                dfdeliveryBar += -cfaAdjusted[ctd.get(0)][loopcf] / dfdelivery * cfaAdjustedBar[ctd.get(0)][loopcf];
            }
            listCredit.add(new DoublesPair(delivery, -delivery * dfdelivery * dfdeliveryBar));
        } else {
            // From -infinity to first cross.
            for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(0)].length; loopcf++) {
                cfaAdjustedBar[ctd.get(0)][loopcf] = NORMAL.getCDF(kappa[0] + alpha[ctd.get(0)][loopcf]) * priceBar;
            }
            // Between cross
            for (int loopint = 1; loopint < nbInt - 1; loopint++) {
                for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(loopint)].length; loopcf++) {
                    cfaAdjustedBar[ctd.get(
                            loopint)][loopcf] = (NORMAL.getCDF(kappa[loopint] + alpha[ctd.get(loopint)][loopcf])
                                    - NORMAL.getCDF(kappa[loopint - 1] + alpha[ctd.get(loopint)][loopcf]))
                                    * priceBar;
                }
            }
            // From last cross to +infinity
            for (int loopcf = 0; loopcf < cfaAdjusted[ctd.get(nbInt - 1)].length; loopcf++) {
                cfaAdjustedBar[ctd.get(nbInt - 1)][loopcf] = (1.0
                        - NORMAL.getCDF(kappa[nbInt - 2] + alpha[ctd.get(nbInt - 1)][loopcf])) * priceBar;
            }
            for (int loopbnd = 0; loopbnd < nbBond; loopbnd++) { // Could be reduced to only the ctd intervals.
                for (int loopcf = 0; loopcf < cfaAdjusted[loopbnd].length; loopcf++) {
                    dfBar[loopbnd][loopcf] = beta[loopbnd][loopcf] / dfdelivery
                            * cf[loopbnd].getNthPayment(loopcf).getAmount() / future.getConversionFactor()[loopbnd]
                            * cfaAdjustedBar[loopbnd][loopcf];
                    listCredit.add(new DoublesPair(cfTime[loopbnd][loopcf],
                            -cfTime[loopbnd][loopcf] * df[loopbnd][loopcf] * dfBar[loopbnd][loopcf]));
                    dfdeliveryBar += -cfaAdjusted[loopbnd][loopcf] / dfdelivery * cfaAdjustedBar[loopbnd][loopcf];
                }
            }
            listCredit.add(new DoublesPair(delivery, -delivery * dfdelivery * dfdeliveryBar));
        }
        resultMap.put(future.getDeliveryBasket()[0].getDiscountingCurveName(), listCredit);
        final InterestRateCurveSensitivity result = new InterestRateCurveSensitivity(resultMap);
        return result;
    }

    /**
     * Computes the future price curve sensitivity. The default number of points is used for the numerical search.
     * @param future The future derivative.
     * @param hwData The curve and Hull-White parameters.
     * @return The curve sensitivity.
     */
    public InterestRateCurveSensitivity priceCurveSensitivity(final BondFuture future,
            final HullWhiteOneFactorPiecewiseConstantDataBundle hwData) {
        return priceCurveSensitivity(future, hwData, DEFAULT_NB_POINTS);
    }

    /**
     * Compute the present value sensitivity to rates of a bond future by discounting.
     * @param future The future.
     * @param curves The yield curves. Should contain the credit and repo curves associated.
     * @return The present value rate sensitivity.
     */
    public InterestRateCurveSensitivity presentValueCurveSensitivity(final BondFuture future,
            final HullWhiteOneFactorPiecewiseConstantDataBundle curves) {
        Validate.notNull(future, "Future");
        final InterestRateCurveSensitivity priceSensitivity = priceCurveSensitivity(future, curves);
        final InterestRateCurveSensitivity transactionSensitivity = priceSensitivity
                .multipliedBy(future.getNotional());
        return transactionSensitivity;
    }

    public InterestRateCurveSensitivity presentValueCurveSensitivity(final InstrumentDerivative instrument,
            final YieldCurveBundle curves) {
        Validate.isTrue(instrument instanceof BondFuture, "Bond future");
        Validate.isTrue(curves instanceof HullWhiteOneFactorPiecewiseConstantDataBundle,
                "Bundle should contain Hull-White data");
        return presentValueCurveSensitivity((BondFuture) instrument,
                (HullWhiteOneFactorPiecewiseConstantDataBundle) curves);
    }

    /**
     * Internal class to estimate the price difference between two bonds.
     */
    private static final class BondDifference extends Function1D<Double, Double> {

        private final double[] _cfa1;
        private final double[] _alpha1;
        private final double _e1;
        private final double[] _cfa2;
        private final double[] _alpha2;
        private final double _e2;

        public BondDifference(final double[] cfa1, final double[] alpha1, final double e1, final double[] cfa2,
                final double[] alpha2, final double e2) {
            _cfa1 = cfa1;
            _alpha1 = alpha1;
            _e1 = e1;
            _cfa2 = cfa2;
            _alpha2 = alpha2;
            _e2 = e2;
        }

        @Override
        public Double evaluate(final Double x) {
            double pv = 0.0;
            for (int loopcf = 0; loopcf < _cfa1.length; loopcf++) {
                pv += _cfa1[loopcf] * Math.exp(-_alpha1[loopcf] * _alpha1[loopcf] / 2.0 - _alpha1[loopcf] * x);
            }
            pv -= _e1;
            for (int loopcf = 0; loopcf < _cfa2.length; loopcf++) {
                pv -= _cfa2[loopcf] * Math.exp(-_alpha2[loopcf] * _alpha2[loopcf] / 2.0 - _alpha2[loopcf] * x);
            }
            pv += _e2;
            return pv;
        }

    }

}