/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.credit.tool;
import static com.opengamma.sesame.config.ConfigBuilder.argument;
import static com.opengamma.sesame.config.ConfigBuilder.arguments;
import static com.opengamma.sesame.config.ConfigBuilder.config;
import static com.opengamma.sesame.config.ConfigBuilder.function;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZonedDateTime;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Collections2;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantCreditCurve;
import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantYieldCurve;
import com.opengamma.component.tool.AbstractTool;
import com.opengamma.component.tool.ToolUtils;
import com.opengamma.core.holiday.HolidaySource;
import com.opengamma.core.holiday.impl.CachedHolidaySource;
import com.opengamma.core.link.SnapshotLink;
import com.opengamma.core.marketdatasnapshot.MarketDataSnapshotSource;
import com.opengamma.financial.analytics.isda.credit.CreditCurveDataKey;
import com.opengamma.financial.analytics.isda.credit.CreditCurveDataSnapshot;
import com.opengamma.financial.analytics.isda.credit.YieldCurveData;
import com.opengamma.financial.analytics.isda.credit.YieldCurveDataSnapshot;
import com.opengamma.financial.tool.ToolContext;
import com.opengamma.id.VersionCorrection;
import com.opengamma.service.ServiceContext;
import com.opengamma.service.ThreadLocalServiceContext;
import com.opengamma.service.VersionCorrectionProvider;
import com.opengamma.sesame.Environment;
import com.opengamma.sesame.SimpleEnvironment;
import com.opengamma.sesame.cache.CachingProxyDecorator;
import com.opengamma.sesame.config.FunctionModelConfig;
import com.opengamma.sesame.credit.DefaultIsdaCompliantYieldCurveFn;
import com.opengamma.sesame.credit.IsdaCompliantCreditCurveFn;
import com.opengamma.sesame.credit.IsdaCompliantYieldCurveFn;
import com.opengamma.sesame.credit.IsdaCreditCurve;
import com.opengamma.sesame.credit.IsdaYieldCurve;
import com.opengamma.sesame.credit.StandardIsdaCompliantCreditCurveFn;
import com.opengamma.sesame.credit.snapshot.SnapshotCreditCurveDataProviderFn;
import com.opengamma.sesame.credit.snapshot.SnapshotYieldCurveDataProviderFn;
import com.opengamma.sesame.engine.ComponentMap;
import com.opengamma.sesame.engine.DefaultCacheProvider;
import com.opengamma.sesame.engine.FixedInstantVersionCorrectionProvider;
import com.opengamma.sesame.function.AvailableImplementations;
import com.opengamma.sesame.function.AvailableImplementationsImpl;
import com.opengamma.sesame.function.AvailableOutputs;
import com.opengamma.sesame.function.AvailableOutputsImpl;
import com.opengamma.sesame.graph.FunctionModel;
import com.opengamma.sesame.marketdata.MarketDataBundle;
import com.opengamma.sesame.marketdata.MarketDataId;
import com.opengamma.sesame.proxy.ExceptionWrappingProxy;
import com.opengamma.timeseries.date.DateTimeSeries;
import com.opengamma.util.money.Currency;
import com.opengamma.util.result.FailureStatus;
import com.opengamma.util.result.Result;
import com.opengamma.util.time.LocalDateRange;
import com.opengamma.util.time.Tenor;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Tests ISDA curve data by calibrating each curve in turn and dumping the result
* out to the console.
*/
public class IsdaCurveSnapshotCalibrationTool extends AbstractTool<ToolContext> {
private static final Logger s_logger = LoggerFactory.getLogger(IsdaCurveSnapshotCalibrationTool.class);
private static final MarketDataBundle s_noOpMarketDataSource = new MarketDataBundle() {
@Override
public <T, I extends MarketDataId<T>> Result<T> get(I id, Class<T> dataType) {
return Result.failure(FailureStatus.ERROR, "Not implemented");
}
@Override
public <T, I extends MarketDataId<T>> Result<DateTimeSeries<LocalDate, T>> get(
I id,
Class<T> dataType,
LocalDateRange dateRange) {
return Result.failure(FailureStatus.ERROR, "Not implemented");
}
@Override
public MarketDataBundle withTime(ZonedDateTime time) {
return this;
}
@Override
public MarketDataBundle withDate(LocalDate date) {
return this;
}
};
/**
* @param args command line args, run with -h for details
*/
public static void main(String[] args) {
new IsdaCurveSnapshotCalibrationTool().invokeAndTerminate(args);
}
@Override
protected void doRun() throws Exception {
CommandLine cmdLine = getCommandLine();
String creditSnapshot = cmdLine.getOptionValue("cs");
String yieldCurveSnapshot = cmdLine.getOptionValue("ys");
//load snapshots
MarketDataSnapshotSource source = getToolContext().getMarketDataSnapshotSource();
YieldCurveDataSnapshot ycSnapshot = source.getSingle(YieldCurveDataSnapshot.class,
yieldCurveSnapshot,
VersionCorrection.LATEST);
CreditCurveDataSnapshot ccSnapshot = source.getSingle(CreditCurveDataSnapshot.class,
creditSnapshot,
VersionCorrection.LATEST);
ComponentMap componentMap = getComponentMap();
FunctionModelConfig functionModelConfig = initGraph(ccSnapshot, ycSnapshot, componentMap);
Cache<Object, Object> cache = buildCache();
CachingProxyDecorator cachingDecorator = new CachingProxyDecorator(new DefaultCacheProvider(cache));
IsdaCompliantYieldCurveFn ycFn = FunctionModel.build(DefaultIsdaCompliantYieldCurveFn.class,
functionModelConfig,
componentMap,
ExceptionWrappingProxy.INSTANCE,
cachingDecorator);
IsdaCompliantCreditCurveFn ccFn = FunctionModel.build(StandardIsdaCompliantCreditCurveFn.class,
functionModelConfig,
componentMap,
ExceptionWrappingProxy.INSTANCE,
cachingDecorator);
Environment env = new SimpleEnvironment(ZonedDateTime.now(), s_noOpMarketDataSource);
calibrateYieldCurves(env, ycSnapshot, ycFn);
calibrateCreditCurves(env, ccSnapshot, ccFn);
}
/**
* Builds the component map from the tool context, overriding the holiday source
* with a caching wrapper.
*/
private ComponentMap getComponentMap() {
ComponentMap componentMap = ComponentMap.loadComponents(getToolContext());
HolidaySource holidaySource = componentMap.getComponent(HolidaySource.class);
return componentMap.with(HolidaySource.class, new CachedHolidaySource(holidaySource));
}
private void calibrateCreditCurves(Environment env,
CreditCurveDataSnapshot ccSnapshot,
IsdaCompliantCreditCurveFn ccFn) {
Map<CreditCurveDataKey, IsdaCreditCurve> creditCurves = Maps.newHashMap();
Map<CreditCurveDataKey, Result<IsdaCreditCurve>> creditCurveFailures = Maps.newHashMap();
int i = 1;
for (CreditCurveDataKey key : ccSnapshot.getCreditCurves().keySet()) {
Result<IsdaCreditCurve> curve;
try {
curve = ccFn.buildIsdaCompliantCreditCurve(env, key);
} catch (Exception e) {
curve = Result.failure(e);
}
if (curve.isSuccess()) {
creditCurves.put(key, curve.getValue());
} else {
creditCurveFailures.put(key, curve);
}
if (i % 100 == 0) {
s_logger.info("Calibrated {} credit curves", i);
}
i++;
}
s_logger.info("Calibrated {} credit curves in total", i);
renderCreditCurves(creditCurves, creditCurveFailures);
}
private void calibrateYieldCurves(Environment env,
YieldCurveDataSnapshot ycSnapshot,
IsdaCompliantYieldCurveFn ycFn) {
List<IsdaYieldCurve> yieldCurves = Lists.newArrayList();
Map<Currency, Result<IsdaYieldCurve>> yieldCurveFailures = Maps.newTreeMap();
for (Currency ccy : ycSnapshot.getYieldCurves().keySet()) {
Result<IsdaYieldCurve> curve = ycFn.buildIsdaCompliantCurve(env, ccy);
if (curve.isSuccess()) {
yieldCurves.add(curve.getValue());
} else {
yieldCurveFailures.put(ccy, curve);
}
}
renderYieldCurves(yieldCurves, yieldCurveFailures);
}
/**
* Builds a basic cache.
*/
private Cache<Object, Object> buildCache() {
int concurrencyLevel = Runtime.getRuntime().availableProcessors() + 2;
return CacheBuilder.newBuilder()
.maximumSize(50000)
.softValues()
.concurrencyLevel(concurrencyLevel)
.build();
}
/**
* Dumps hazard rates for credit curves in a tabular CSV format. Failures are appended at the end.
*/
private void renderCreditCurves(Map<CreditCurveDataKey, IsdaCreditCurve> creditCurves,
Map<CreditCurveDataKey, Result<IsdaCreditCurve>> creditCurveFailures) {
List<String> headers = Lists.newArrayList("Curve name", "Currency", "Restructuring", "Seniority", "Yield curve");
Set<Tenor> allTenors = Sets.newTreeSet();
for (IsdaCreditCurve curve : creditCurves.values()) {
allTenors.addAll(curve.getCurveData().getCdsQuotes().keySet());
}
headers.addAll(Collections2.transform(allTenors, Functions.toStringFunction()));
Map<CreditCurveDataKey, List<String>> rows = Maps.newTreeMap(new Comparator<CreditCurveDataKey>() {
@Override
public int compare(CreditCurveDataKey o1, CreditCurveDataKey o2) {
return ComparisonChain.start()
.compare(o1.getCurveName(), o2.getCurveName())
.compare(o1.getCurrency(), o2.getCurrency())
.compare(o1.getRestructuring().toString(), o2.getRestructuring().toString())
.compare(o1.getSeniority().toString(), o2.getSeniority().toString())
.result();
}
});
for (Entry<CreditCurveDataKey, IsdaCreditCurve> entry : creditCurves.entrySet()) {
CreditCurveDataKey key = entry.getKey();
IsdaCreditCurve curve = entry.getValue();
Currency yieldCurveCcy = curve.getYieldCurve().getCurveData().getCurrency();
List<Object> rowObjs = Lists.<Object>newArrayList(key.getCurveName(),
key.getCurrency(),
key.getRestructuring(),
key.getSeniority(),
yieldCurveCcy);
int i = 0;
for (Tenor tenor : allTenors) {
if (curve.getCurveData().getCdsQuotes().containsKey(tenor)) {
ISDACompliantCreditCurve calibratedCurve = curve.getCalibratedCurve();
double timeAtI = calibratedCurve.getTimeAtIndex(i);
double hazardRate = calibratedCurve.getHazardRate(timeAtI);
rowObjs.add(hazardRate);
i++;
} else {
rowObjs.add("");
}
}
rows.put(key, Lists.transform(rowObjs, Functions.toStringFunction()));
}
renderTable("Credit curve hazard rates", headers, rows.values());
System.out.println("Credit curve failures:");
for (Entry<CreditCurveDataKey, Result<IsdaCreditCurve>> entry : creditCurveFailures.entrySet()) {
System.out.format("%s: %s\n", entry.getKey(), entry.getValue());
}
}
/**
* Renders zero rates for yield curves in a tabular CSV format. Failures are appended to the end.
*/
private void renderYieldCurves(List<IsdaYieldCurve> yieldCurves,
Map<Currency, Result<IsdaYieldCurve>> yieldCurveFailures) {
Pair<SortedSet<Tenor>, SortedSet<Tenor>> tenors = getTenors(yieldCurves);
Set<Tenor> cashTenors = tenors.getFirst();
Set<Tenor> swapTenors = tenors.getSecond();
Set<Tenor> allTenors = Sets.newLinkedHashSet(Iterables.concat(cashTenors, swapTenors));
List<String> headers = Lists.newArrayList("Curve");
headers.addAll(Collections2.transform(cashTenors, new Function<Object, String>() {
@Override
public String apply(Object input) {
return "C: " + input.toString();
}
}));
headers.addAll(Collections2.transform(swapTenors, new Function<Object, String>() {
@Override
public String apply(Object input) {
return "S: " + input.toString();
}
}));
Map<Currency, List<String>> inputRows = Maps.newTreeMap();
Map<Currency, List<String>> rows = Maps.newTreeMap();
for (IsdaYieldCurve curve : yieldCurves) {
Currency ccy = curve.getCurveData().getCurrency();
List<Object> row = Lists.<Object>newArrayList(ccy);
List<Object> inputRow = Lists.<Object>newArrayList(ccy);
SortedSet<Tenor> cashTerms = curve.getCurveData().getCashData().keySet();
SortedSet<Tenor> swapTerms = curve.getCurveData().getSwapData().keySet();
ISDACompliantYieldCurve calibratedCurve = curve.getCalibratedCurve();
Set<Tenor> curveTerms = Sets.newLinkedHashSet(Iterables.concat(cashTerms, swapTerms));
int i = 0;
for (Tenor tenor : allTenors) {
if (curveTerms.contains(tenor)) {
double forwardRate = calibratedCurve.getZeroRateAtIndex(i);
row.add(Double.toString(forwardRate));
i++;
YieldCurveData curveData = curve.getCurveData();
if (cashTerms.contains(tenor)) {
inputRow.add(curveData.getCashData().get(tenor).toString());
} else {
inputRow.add(curveData.getSwapData().get(tenor).toString());
}
} else {
row.add("");
inputRow.add("");
}
}
rows.put(ccy, Lists.transform(row, Functions.toStringFunction()));
inputRows.put(ccy, Lists.transform(inputRow, Functions.toStringFunction()));
}
renderTable("Yield curve market quotes", headers, inputRows.values());
renderTable("Yield curve zero rates", headers, rows.values());
for (Map.Entry<Currency, Result<IsdaYieldCurve>> failure : yieldCurveFailures.entrySet()) {
System.out.println(failure.getKey() + ": " + failure.getValue());
}
}
/**
* Renders as a CSV table with a title.
*/
private void renderTable(String title, List<String> headers, Iterable<List<String>> rows) {
Joiner joiner = Joiner.on(",");
System.out.println(title);
System.out.println(joiner.join(headers));
for (List<String> row : rows) {
System.out.println(joiner.join(row));
}
}
/**
* Pulls all cash and swap tenors from all yield curves, returning the result as a pair.
*/
private Pair<SortedSet<Tenor>, SortedSet<Tenor>> getTenors(List<IsdaYieldCurve> yieldCurves) {
SortedSet<Tenor> cashTenors = Sets.newTreeSet();
SortedSet<Tenor> swapTenors = Sets.newTreeSet();
for (IsdaYieldCurve curve : yieldCurves) {
cashTenors.addAll(curve.getCurveData().getCashData().keySet());
swapTenors.addAll(curve.getCurveData().getSwapData().keySet());
}
return Pairs.of(cashTenors, swapTenors);
}
/**
* Initializes a {@link FunctionModelConfig} instance using the passed snapshot names and component map.
*/
private FunctionModelConfig initGraph(CreditCurveDataSnapshot ccSnapshot,
YieldCurveDataSnapshot ycSnapshot,
ComponentMap componentMap) {
ThreadLocalServiceContext.init(
ServiceContext.of(componentMap.getComponents())
.with(VersionCorrectionProvider.class, new FixedInstantVersionCorrectionProvider(Instant.now())));
AvailableOutputs availableOutputs = new AvailableOutputsImpl();
availableOutputs.register(IsdaCompliantYieldCurveFn.class,
IsdaCompliantCreditCurveFn.class);
AvailableImplementations availableImplementations = new AvailableImplementationsImpl();
availableImplementations.register(DefaultIsdaCompliantYieldCurveFn.class,
SnapshotYieldCurveDataProviderFn.class,
SnapshotCreditCurveDataProviderFn.class,
StandardIsdaCompliantCreditCurveFn.class);
FunctionModelConfig provider = new FunctionModelConfig(availableImplementations.getDefaultImplementations());
FunctionModelConfig config =
config(
arguments(
function(
SnapshotYieldCurveDataProviderFn.class,
argument("snapshotLink", SnapshotLink.resolved(ycSnapshot))),
function(
SnapshotCreditCurveDataProviderFn.class,
argument("snapshotLink", SnapshotLink.resolved(ccSnapshot)))));
return config.mergedWith(provider);
}
@Override
protected Options createOptions(boolean mandatoryConfigResource) {
Options options = super.createOptions(mandatoryConfigResource);
ToolUtils.option(options, "cs", "credit-snapshot", true, "The credit snapshot to use");
ToolUtils.option(options, "ys", "yieldcurve-snapshot", true, "The yield curve snapshot to use");
return options;
}
}