-
Notifications
You must be signed in to change notification settings - Fork 284
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add measure/calculation API for bond futures
See #1331
- Loading branch information
1 parent
f61f13a
commit 2b0439e
Showing
5 changed files
with
855 additions
and
0 deletions.
There are no files selected for viewing
185 changes: 185 additions & 0 deletions
185
...easure/src/main/java/com/opengamma/strata/measure/bond/BondFutureMeasureCalculations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
} |
170 changes: 170 additions & 0 deletions
170
...e/src/main/java/com/opengamma/strata/measure/bond/BondFutureTradeCalculationFunction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |
Oops, something went wrong.