Skip to content

Commit

Permalink
Add measure/calculation API for bond futures
Browse files Browse the repository at this point in the history
See #1331
  • Loading branch information
jodastephen committed Oct 4, 2016
1 parent f61f13a commit 2b0439e
Show file tree
Hide file tree
Showing 5 changed files with 855 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.measure.bond;

import com.opengamma.strata.basics.StandardId;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.data.FieldName;
import com.opengamma.strata.data.scenario.CurrencyScenarioArray;
import com.opengamma.strata.data.scenario.DoubleScenarioArray;
import com.opengamma.strata.data.scenario.MultiCurrencyScenarioArray;
import com.opengamma.strata.data.scenario.ScenarioArray;
import com.opengamma.strata.market.observable.QuoteId;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.market.sensitivity.PointSensitivities;
import com.opengamma.strata.pricer.bond.DiscountingBondFutureTradePricer;
import com.opengamma.strata.pricer.bond.LegalEntityDiscountingProvider;
import com.opengamma.strata.product.bond.ResolvedBondFutureTrade;

/**
* Multi-scenario measure calculations for Bond Future trades.
* <p>
* Each method corresponds to a measure, typically calculated by one or more calls to the pricer.
*/
final class BondFutureMeasureCalculations {

/**
* Default implementation.
*/
public static final BondFutureMeasureCalculations DEFAULT = new BondFutureMeasureCalculations(
DiscountingBondFutureTradePricer.DEFAULT);
/**
* One basis point, expressed as a {@code double}.
*/
private static final double ONE_BASIS_POINT = 1e-4;

/**
* Pricer for {@link ResolvedBondFutureTrade}.
*/
private final DiscountingBondFutureTradePricer tradePricer;

/**
* Creates an instance.
*
* @param tradePricer the pricer for {@link ResolvedBondFutureTrade}
*/
BondFutureMeasureCalculations(
DiscountingBondFutureTradePricer tradePricer) {
this.tradePricer = ArgChecker.notNull(tradePricer, "tradePricer");
}

//-------------------------------------------------------------------------
// calculates present value for all scenarios
CurrencyScenarioArray presentValue(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

return CurrencyScenarioArray.of(
marketData.getScenarioCount(),
i -> presentValue(trade, marketData.scenario(i).discountingProvider()));
}

// present value for one scenario
CurrencyAmount presentValue(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingProvider discountingProvider) {

// mark to model
double settlementPrice = settlementPrice(trade, discountingProvider);
return tradePricer.presentValue(trade, discountingProvider, settlementPrice);
}

//-------------------------------------------------------------------------
// calculates calibrated sum PV01 for all scenarios
MultiCurrencyScenarioArray pv01CalibratedSum(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

return MultiCurrencyScenarioArray.of(
marketData.getScenarioCount(),
i -> pv01CalibratedSum(trade, marketData.scenario(i).discountingProvider()));
}

// calibrated sum PV01 for one scenario
MultiCurrencyAmount pv01CalibratedSum(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingProvider discountingProvider) {

PointSensitivities pointSensitivity = tradePricer.presentValueSensitivity(trade, discountingProvider);
return discountingProvider.parameterSensitivity(pointSensitivity).total().multipliedBy(ONE_BASIS_POINT);
}

//-------------------------------------------------------------------------
// calculates calibrated bucketed PV01 for all scenarios
ScenarioArray<CurrencyParameterSensitivities> pv01CalibratedBucketed(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

return ScenarioArray.of(
marketData.getScenarioCount(),
i -> pv01CalibratedBucketed(trade, marketData.scenario(i).discountingProvider()));
}

// calibrated bucketed PV01 for one scenario
CurrencyParameterSensitivities pv01CalibratedBucketed(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingProvider discountingProvider) {

PointSensitivities pointSensitivity = tradePricer.presentValueSensitivity(trade, discountingProvider);
return discountingProvider.parameterSensitivity(pointSensitivity).multipliedBy(ONE_BASIS_POINT);
}

//-------------------------------------------------------------------------
// calculates par spread for all scenarios
DoubleScenarioArray parSpread(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

return DoubleScenarioArray.of(
marketData.getScenarioCount(),
i -> parSpread(trade, marketData.scenario(i).discountingProvider()));
}

// par spread for one scenario
double parSpread(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingProvider discountingProvider) {

double settlementPrice = settlementPrice(trade, discountingProvider);
return tradePricer.parSpread(trade, discountingProvider, settlementPrice);
}

//-------------------------------------------------------------------------
// calculates unit price for all scenarios
DoubleScenarioArray unitPrice(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

return DoubleScenarioArray.of(
marketData.getScenarioCount(),
i -> unitPrice(trade, marketData.scenario(i).discountingProvider()));
}

// unit price for one scenario
double unitPrice(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingProvider discountingProvider) {

// mark to model
return tradePricer.price(trade, discountingProvider);
}

//-------------------------------------------------------------------------
// calculates currency exposure for all scenarios
MultiCurrencyScenarioArray currencyExposure(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

return MultiCurrencyScenarioArray.of(
marketData.getScenarioCount(),
i -> currencyExposure(trade, marketData.scenario(i).discountingProvider()));
}

// currency exposure for one scenario
MultiCurrencyAmount currencyExposure(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingProvider discountingProvider) {

double settlementPrice = settlementPrice(trade, discountingProvider);
return tradePricer.currencyExposure(trade, discountingProvider, settlementPrice);
}

//-------------------------------------------------------------------------
// gets the settlement price
private double settlementPrice(ResolvedBondFutureTrade trade, LegalEntityDiscountingProvider discountingProvider) {
StandardId standardId = trade.getProduct().getSecurityId().getStandardId();
QuoteId id = QuoteId.of(standardId, FieldName.SETTLEMENT_PRICE);
return discountingProvider.data(id) / 100; // convert market quote to value needed
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.measure.bond;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.calc.Measure;
import com.opengamma.strata.calc.runner.CalculationFunction;
import com.opengamma.strata.calc.runner.CalculationParameters;
import com.opengamma.strata.calc.runner.FunctionRequirements;
import com.opengamma.strata.collect.result.FailureReason;
import com.opengamma.strata.collect.result.Result;
import com.opengamma.strata.data.FieldName;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
import com.opengamma.strata.market.observable.QuoteId;
import com.opengamma.strata.measure.Measures;
import com.opengamma.strata.measure.rate.RatesMarketDataLookup;
import com.opengamma.strata.product.bond.BondFuture;
import com.opengamma.strata.product.bond.BondFutureTrade;
import com.opengamma.strata.product.bond.FixedCouponBond;
import com.opengamma.strata.product.bond.ResolvedBondFutureTrade;

/**
* Perform calculations on a single {@code BondFutureTrade} for each of a set of scenarios.
* <p>
* This uses the standard discounting calculation method.
* An instance of {@link RatesMarketDataLookup} must be specified.
* The supported built-in measures are:
* <ul>
* <li>{@linkplain Measures#PRESENT_VALUE Present value}
* <li>{@linkplain Measures#PV01_CALIBRATED_SUM PV01 calibrated sum}
* <li>{@linkplain Measures#PV01_CALIBRATED_BUCKETED PV01 calibrated bucketed}
* <li>{@linkplain Measures#UNIT_PRICE Unit price}
* <li>{@linkplain Measures#PAR_SPREAD Par spread}
* <li>{@linkplain Measures#CURRENCY_EXPOSURE Currency exposure}
* <li>{@linkplain Measures#RESOLVED_TARGET Resolved trade}
* </ul>
*
* <h4>Price</h4>
* Strata uses <i>decimal prices</i> for bond futures in the trade model, pricers and market data.
* This is coherent with the pricing of {@link FixedCouponBond}. The bond futures delivery is a bond
* for an amount computed from the bond future price, a conversion factor and the accrued interest.
*/
public class BondFutureTradeCalculationFunction
implements CalculationFunction<BondFutureTrade> {

/**
* The calculations by measure.
*/
private static final ImmutableMap<Measure, SingleMeasureCalculation> CALCULATORS =
ImmutableMap.<Measure, SingleMeasureCalculation>builder()
.put(Measures.PRESENT_VALUE, BondFutureMeasureCalculations.DEFAULT::presentValue)
.put(Measures.PV01_CALIBRATED_SUM, BondFutureMeasureCalculations.DEFAULT::pv01CalibratedSum)
.put(Measures.PV01_CALIBRATED_BUCKETED, BondFutureMeasureCalculations.DEFAULT::pv01CalibratedBucketed)
.put(Measures.UNIT_PRICE, BondFutureMeasureCalculations.DEFAULT::unitPrice)
.put(Measures.PAR_SPREAD, BondFutureMeasureCalculations.DEFAULT::parSpread)
.put(Measures.CURRENCY_EXPOSURE, BondFutureMeasureCalculations.DEFAULT::currencyExposure)
.put(Measures.RESOLVED_TARGET, (rt, smd) -> rt)
.build();

private static final ImmutableSet<Measure> MEASURES = CALCULATORS.keySet();

/**
* Creates an instance.
*/
public BondFutureTradeCalculationFunction() {
}

//-------------------------------------------------------------------------
@Override
public Class<BondFutureTrade> targetType() {
return BondFutureTrade.class;
}

@Override
public Set<Measure> supportedMeasures() {
return MEASURES;
}

@Override
public Optional<String> identifier(BondFutureTrade target) {
return target.getInfo().getId().map(id -> id.toString());
}

@Override
public Currency naturalCurrency(BondFutureTrade trade, ReferenceData refData) {
return trade.getProduct().getCurrency();
}

//-------------------------------------------------------------------------
@Override
public FunctionRequirements requirements(
BondFutureTrade trade,
Set<Measure> measures,
CalculationParameters parameters,
ReferenceData refData) {

// extract data from product
BondFuture product = trade.getProduct();
QuoteId quoteId = QuoteId.of(product.getSecurityId().getStandardId(), FieldName.SETTLEMENT_PRICE);
Currency currency = product.getCurrency();

// use lookup to build requirements
FunctionRequirements freqs = FunctionRequirements.builder()
.valueRequirements(quoteId)
.outputCurrencies(currency)
.build();
LegalEntityDiscountingMarketDataLookup ledLookup = parameters.getParameter(LegalEntityDiscountingMarketDataLookup.class);
for (FixedCouponBond bond : product.getDeliveryBasket()) {
freqs = freqs.combinedWith(ledLookup.requirements(bond.getSecurityId(), bond.getLegalEntityId(), bond.getCurrency()));
}
return freqs;
}

//-------------------------------------------------------------------------
@Override
public Map<Measure, Result<?>> calculate(
BondFutureTrade trade,
Set<Measure> measures,
CalculationParameters parameters,
ScenarioMarketData scenarioMarketData,
ReferenceData refData) {

// resolve the trade once for all measures and all scenarios
ResolvedBondFutureTrade resolved = trade.resolve(refData);

// use lookup to query market data
LegalEntityDiscountingMarketDataLookup ledLookup = parameters.getParameter(LegalEntityDiscountingMarketDataLookup.class);
LegalEntityDiscountingScenarioMarketData marketData = ledLookup.marketDataView(scenarioMarketData);

// loop around measures, calculating all scenarios for one measure
Map<Measure, Result<?>> results = new HashMap<>();
for (Measure measure : measures) {
results.put(measure, calculate(measure, resolved, marketData));
}
return results;
}

// calculate one measure
private Result<?> calculate(
Measure measure,
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData) {

SingleMeasureCalculation calculator = CALCULATORS.get(measure);
if (calculator == null) {
return Result.failure(FailureReason.UNSUPPORTED, "Unsupported measure for BondFutureTrade: {}", measure);
}
return Result.of(() -> calculator.calculate(trade, marketData));
}

//-------------------------------------------------------------------------
@FunctionalInterface
interface SingleMeasureCalculation {
public abstract Object calculate(
ResolvedBondFutureTrade trade,
LegalEntityDiscountingScenarioMarketData marketData);
}

}
Loading

0 comments on commit 2b0439e

Please sign in to comment.