/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.examples.finance;
import static com.opengamma.strata.basics.date.BusinessDayConventions.MODIFIED_FOLLOWING;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static com.opengamma.strata.measure.StandardComponents.marketDataFactory;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import com.google.common.collect.ImmutableList;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.StandardId;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.DaysAdjustment;
import com.opengamma.strata.basics.date.HolidayCalendarIds;
import com.opengamma.strata.basics.index.IborIndices;
import com.opengamma.strata.basics.schedule.Frequency;
import com.opengamma.strata.basics.schedule.PeriodicSchedule;
import com.opengamma.strata.calc.CalculationRules;
import com.opengamma.strata.calc.CalculationRunner;
import com.opengamma.strata.calc.Column;
import com.opengamma.strata.calc.Results;
import com.opengamma.strata.calc.marketdata.MarketDataConfig;
import com.opengamma.strata.calc.marketdata.MarketDataFilter;
import com.opengamma.strata.calc.marketdata.MarketDataRequirements;
import com.opengamma.strata.calc.marketdata.PerturbationMapping;
import com.opengamma.strata.calc.marketdata.ScenarioDefinition;
import com.opengamma.strata.calc.runner.CalculationFunctions;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.data.MarketData;
import com.opengamma.strata.data.scenario.ScenarioArray;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
import com.opengamma.strata.examples.marketdata.ExampleMarketDataBuilder;
import com.opengamma.strata.market.ShiftType;
import com.opengamma.strata.market.curve.Curve;
import com.opengamma.strata.market.curve.CurveGroup;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.CurvePointShifts;
import com.opengamma.strata.market.curve.CurvePointShiftsBuilder;
import com.opengamma.strata.measure.Measures;
import com.opengamma.strata.measure.StandardComponents;
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.product.TradeAttributeType;
import com.opengamma.strata.product.TradeInfo;
import com.opengamma.strata.product.common.PayReceive;
import com.opengamma.strata.product.swap.IborRateCalculation;
import com.opengamma.strata.product.swap.NotionalSchedule;
import com.opengamma.strata.product.swap.PaymentSchedule;
import com.opengamma.strata.product.swap.RateCalculationSwapLeg;
import com.opengamma.strata.product.swap.Swap;
import com.opengamma.strata.product.swap.SwapLeg;
import com.opengamma.strata.product.swap.SwapTrade;
/**
* Example to illustrate using the engine to run a set of historical scenarios on a single swap
* to produce a P&L series. This P&L series could then be used to calculate historical VaR.
* <p>
* In this example we are provided with market data containing:
* <li>a complete snapshot to value the swap on the valuation date (curves only as the swap is forward-starting)
* <li>a series of historical curves for every date leading up to the valuation date
* <p>
* The differences between the zero rates in consecutive historical curves (dates d-1 and d)
* are used to generate a scenario, later attributed to date d, containing these relative curve
* shifts. The swap is then valued on the valuation date, applying each scenario to the base
* snapshot from the valuation date, to produce a PV series. A P&L series is then generated from
* this.
* <p>
* Instead of generating the perturbations on-the-fly from real data as in this example, the
* scenario could be pre-generated and stored, or generated in any other way.
*/
public class HistoricalScenarioExample {
private static final String MARKET_DATA_RESOURCE_ROOT = "example-historicalscenario-marketdata";
public static void main(String[] args) {
// setup calculation runner component, which needs life-cycle management
// a typical application might use dependency injection to obtain the instance
try (CalculationRunner runner = CalculationRunner.ofMultiThreaded()) {
calculate(runner);
}
}
// obtains the data and calculates the grid of results
private static void calculate(CalculationRunner runner) {
// the trades for which to calculate a P&L series
List<Trade> trades = ImmutableList.of(createTrade());
// the columns, specifying the measures to be calculated
List<Column> columns = ImmutableList.of(
Column.of(Measures.PRESENT_VALUE));
// use the built-in example historical scenario market data
ExampleMarketDataBuilder marketDataBuilder = ExampleMarketDataBuilder.ofResource(MARKET_DATA_RESOURCE_ROOT);
// the complete set of rules for calculating measures
CalculationFunctions functions = StandardComponents.calculationFunctions();
CalculationRules rules = CalculationRules.of(functions, marketDataBuilder.ratesLookup(LocalDate.of(2015, 4, 23)));
// load the historical calibrated curves from which we will build our scenarios
// these curves are provided in the example data environment
SortedMap<LocalDate, CurveGroup> historicalCurves = marketDataBuilder.loadAllRatesCurves();
// sorted list of dates for the available series of curves
// the entries in the P&L vector we produce will correspond to these dates
List<LocalDate> scenarioDates = new ArrayList<>(historicalCurves.keySet());
// build the historical scenarios
ScenarioDefinition historicalScenarios = buildHistoricalScenarios(historicalCurves, scenarioDates);
// build a market data snapshot for the valuation date
// this is the base snapshot which will be perturbed by the scenarios
LocalDate valuationDate = LocalDate.of(2015, 4, 23);
MarketData marketData = marketDataBuilder.buildSnapshot(valuationDate);
// the reference data, such as holidays and securities
ReferenceData refData = ReferenceData.standard();
// calculate the results
MarketDataRequirements reqs = MarketDataRequirements.of(rules, trades, columns, refData);
ScenarioMarketData scenarioMarketData =
marketDataFactory().createMultiScenario(reqs, MarketDataConfig.empty(), marketData, refData, historicalScenarios);
Results results = runner.calculateMultiScenario(rules, trades, columns, scenarioMarketData, refData);
// the results contain the one measure requested (Present Value) for each scenario
ScenarioArray<?> scenarioValuations = (ScenarioArray<?>) results.get(0, 0).getValue();
outputPnl(scenarioDates, scenarioValuations);
}
private static ScenarioDefinition buildHistoricalScenarios(
Map<LocalDate, CurveGroup> historicalCurves,
List<LocalDate> scenarioDates) {
// extract the curves to perturb
List<Curve> usdDiscountCurves = scenarioDates.stream()
.map(date -> historicalCurves.get(date))
.map(group -> group.findDiscountCurve(Currency.USD).get())
.collect(toImmutableList());
List<Curve> libor3mCurves = scenarioDates.stream()
.map(date -> historicalCurves.get(date))
.map(group -> group.findForwardCurve(IborIndices.USD_LIBOR_3M).get())
.collect(toImmutableList());
List<Curve> libor6mCurves = scenarioDates.stream()
.map(date -> historicalCurves.get(date))
.map(group -> group.findForwardCurve(IborIndices.USD_LIBOR_6M).get())
.collect(toImmutableList());
// create mappings which will cause the point shift perturbations generated above
// to be applied to the correct curves
PerturbationMapping<Curve> discountCurveMappings = PerturbationMapping.of(
Curve.class,
MarketDataFilter.ofName(CurveName.of("USD-Disc")),
buildShifts(usdDiscountCurves));
PerturbationMapping<Curve> libor3mMappings = PerturbationMapping.of(
Curve.class,
MarketDataFilter.ofName(CurveName.of("USD-3ML")),
buildShifts(libor3mCurves));
PerturbationMapping<Curve> libor6mMappings = PerturbationMapping.of(
Curve.class,
MarketDataFilter.ofName(CurveName.of("USD-6ML")),
buildShifts(libor6mCurves));
// create a scenario definition from these mappings
return ScenarioDefinition.ofMappings(
discountCurveMappings,
libor3mMappings,
libor6mMappings);
}
private static CurvePointShifts buildShifts(List<Curve> historicalCurves) {
CurvePointShiftsBuilder builder = CurvePointShifts.builder(ShiftType.ABSOLUTE);
for (int scenarioIndex = 1; scenarioIndex < historicalCurves.size(); scenarioIndex++) {
Curve previousCurve = historicalCurves.get(scenarioIndex - 1);
Curve curve = historicalCurves.get(scenarioIndex);
// build up the shifts to apply to each node
// these are calculated as the actual change in the zero rate at that node between the two scenario dates
for (int curveNodeIdx = 0; curveNodeIdx < curve.getParameterCount(); curveNodeIdx++) {
double zeroRate = curve.getParameter(curveNodeIdx);
double previousZeroRate = previousCurve.getParameter(curveNodeIdx);
double shift = (zeroRate - previousZeroRate);
// the parameter metadata is used to identify a node to apply a perturbation to
builder.addShift(scenarioIndex, curve.getParameterMetadata(curveNodeIdx).getIdentifier(), shift);
}
}
return builder.build();
}
private static void outputPnl(List<LocalDate> scenarioDates, ScenarioArray<?> scenarioValuations) {
NumberFormat numberFormat = new DecimalFormat("0.00", new DecimalFormatSymbols(Locale.ENGLISH));
double basePv = ((CurrencyAmount) scenarioValuations.get(0)).getAmount();
System.out.println("Base PV (USD): " + numberFormat.format(basePv));
System.out.println();
System.out.println("P&L series (USD):");
for (int i = 1; i < scenarioValuations.getScenarioCount(); i++) {
double scenarioPv = ((CurrencyAmount) scenarioValuations.get(i)).getAmount();
double pnl = scenarioPv - basePv;
LocalDate scenarioDate = scenarioDates.get(i);
System.out.println(Messages.format("{} = {}", scenarioDate, numberFormat.format(pnl)));
}
}
//-------------------------------------------------------------------------
// create a libor 3m vs libor 6m swap
private static Trade createTrade() {
NotionalSchedule notional = NotionalSchedule.of(Currency.USD, 1_000_000);
SwapLeg payLeg = RateCalculationSwapLeg.builder()
.payReceive(PayReceive.PAY)
.accrualSchedule(PeriodicSchedule.builder()
.startDate(LocalDate.of(2015, 9, 11))
.endDate(LocalDate.of(2021, 9, 11))
.frequency(Frequency.P3M)
.businessDayAdjustment(BusinessDayAdjustment.of(MODIFIED_FOLLOWING, HolidayCalendarIds.USNY))
.build())
.paymentSchedule(PaymentSchedule.builder()
.paymentFrequency(Frequency.P3M)
.paymentDateOffset(DaysAdjustment.NONE)
.build())
.notionalSchedule(notional)
.calculation(IborRateCalculation.of(IborIndices.USD_LIBOR_3M))
.build();
SwapLeg receiveLeg = RateCalculationSwapLeg.builder()
.payReceive(PayReceive.RECEIVE)
.accrualSchedule(PeriodicSchedule.builder()
.startDate(LocalDate.of(2015, 9, 11))
.endDate(LocalDate.of(2021, 9, 11))
.frequency(Frequency.P6M)
.businessDayAdjustment(BusinessDayAdjustment.of(MODIFIED_FOLLOWING, HolidayCalendarIds.USNY))
.build())
.paymentSchedule(PaymentSchedule.builder()
.paymentFrequency(Frequency.P6M)
.paymentDateOffset(DaysAdjustment.NONE)
.build())
.notionalSchedule(notional)
.calculation(IborRateCalculation.of(IborIndices.USD_LIBOR_6M))
.build();
return SwapTrade.builder()
.product(Swap.of(payLeg, receiveLeg))
.info(TradeInfo.builder()
.id(StandardId.of("example", "1"))
.addAttribute(TradeAttributeType.DESCRIPTION, "Libor 3m vs Libor 6m")
.counterparty(StandardId.of("example", "A"))
.settlementDate(LocalDate.of(2015, 9, 11))
.build())
.build();
}
}