/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.examples.marketdata;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.io.CharSource;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.FxRate;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.io.ResourceLocator;
import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeries;
import com.opengamma.strata.data.FxRateId;
import com.opengamma.strata.data.ImmutableMarketData;
import com.opengamma.strata.data.ImmutableMarketDataBuilder;
import com.opengamma.strata.data.ObservableId;
import com.opengamma.strata.examples.marketdata.credit.markit.MarkitIndexCreditCurveDataParser;
import com.opengamma.strata.examples.marketdata.credit.markit.MarkitSingleNameCreditCurveDataParser;
import com.opengamma.strata.examples.marketdata.credit.markit.MarkitYieldCurveDataParser;
import com.opengamma.strata.loader.csv.FixingSeriesCsvLoader;
import com.opengamma.strata.loader.csv.QuotesCsvLoader;
import com.opengamma.strata.loader.csv.RatesCurvesCsvLoader;
import com.opengamma.strata.market.curve.CurveGroup;
import com.opengamma.strata.market.curve.CurveId;
import com.opengamma.strata.market.observable.QuoteId;
import com.opengamma.strata.measure.rate.RatesMarketDataLookup;
import com.opengamma.strata.pricer.credit.IsdaYieldCurveInputs;
import com.opengamma.strata.pricer.credit.IsdaYieldCurveInputsId;
/**
* Builds a market data snapshot from user-editable files in a prescribed directory structure.
* <p>
* Descendants of this class provide the ability to source this directory structure from any
* location.
* <p>
* The directory structure must look like:
* <ul>
* <li>root
* <ul>
* <li>curves
* <ul>
* <li>groups.csv
* <li>settings.csv
* <li>one or more curve CSV files
* </ul>
* <li>historical-fixings
* <ul>
* <li>one or more time-series CSV files
* </ul>
* </ul>
* </ul>
*/
public abstract class ExampleMarketDataBuilder {
private static final Logger log = LoggerFactory.getLogger(ExampleMarketDataBuilder.class);
/** The name of the subdirectory containing historical fixings. */
private static final String HISTORICAL_FIXINGS_DIR = "historical-fixings";
/** The name of the subdirectory containing calibrated rates curves. */
private static final String CURVES_DIR = "curves";
/** The name of the curve groups file. */
private static final String CURVES_GROUPS_FILE = "groups.csv";
/** The name of the curve settings file. */
private static final String CURVES_SETTINGS_FILE = "settings.csv";
/** The name of the directory containing CDS ISDA yield curve, credit curve and static data. */
private static final String CREDIT_DIR = "credit";
private static final String CDS_YIELD_CURVES_FILE = "cds.yieldCurves.csv";
private static final String SINGLE_NAME_CREDIT_CURVES_FILE = "singleName.creditCurves.csv";
private static final String SINGLE_NAME_STATIC_DATA_FILE = "singleName.staticData.csv";
private static final String INDEX_CREDIT_CURVES_FILE = "index.creditCurves.csv";
private static final String INDEX_STATIC_DATA_FILE = "index.staticData.csv";
/** The name of the subdirectory containing simple market quotes. */
private static final String QUOTES_DIR = "quotes";
/** The name of the quotes file. */
private static final String QUOTES_FILE = "quotes.csv";
//-------------------------------------------------------------------------
/**
* Creates an instance from a given classpath resource root location using the class loader
* which created this class.
* <p>
* This is designed to handle resource roots which may physically correspond to a directory on
* disk, or be located within a jar file.
*
* @param resourceRoot the resource root path
* @return the market data builder
*/
public static ExampleMarketDataBuilder ofResource(String resourceRoot) {
return ofResource(resourceRoot, ExampleMarketDataBuilder.class.getClassLoader());
}
/**
* Creates an instance from a given classpath resource root location, using the given class loader
* to find the resource.
* <p>
* This is designed to handle resource roots which may physically correspond to a directory on
* disk, or be located within a jar file.
*
* @param resourceRoot the resource root path
* @param classLoader the class loader with which to find the resource
* @return the market data builder
*/
public static ExampleMarketDataBuilder ofResource(String resourceRoot, ClassLoader classLoader) {
// classpath resources are forward-slash separated
String qualifiedRoot = resourceRoot;
qualifiedRoot = qualifiedRoot.startsWith("/") ? qualifiedRoot.substring(1) : qualifiedRoot;
qualifiedRoot = qualifiedRoot.startsWith("\\") ? qualifiedRoot.substring(1) : qualifiedRoot;
qualifiedRoot = qualifiedRoot.endsWith("/") ? qualifiedRoot : qualifiedRoot + "/";
URL url = classLoader.getResource(qualifiedRoot);
if (url == null) {
throw new IllegalArgumentException(Messages.format("Classpath resource not found: {}", qualifiedRoot));
}
if (url.getProtocol() != null && "jar".equals(url.getProtocol().toLowerCase(Locale.ENGLISH))) {
// Inside a JAR
int classSeparatorIdx = url.getFile().indexOf("!");
if (classSeparatorIdx == -1) {
throw new IllegalArgumentException(Messages.format("Unexpected JAR file URL: {}", url));
}
String jarPath = url.getFile().substring("file:".length(), classSeparatorIdx);
File jarFile;
try {
jarFile = new File(jarPath);
} catch (Exception e) {
throw new IllegalArgumentException(Messages.format("Unable to create file for JAR: {}", jarPath), e);
}
return new JarMarketDataBuilder(jarFile, resourceRoot);
} else {
// Resource is on disk
File file;
try {
file = new File(url.toURI());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(Messages.format("Unexpected file location: {}", url), e);
}
return new DirectoryMarketDataBuilder(file.toPath());
}
}
/**
* Creates an instance from a given directory root.
*
* @param rootPath the root directory
* @return the market data builder
*/
public static ExampleMarketDataBuilder ofPath(Path rootPath) {
return new DirectoryMarketDataBuilder(rootPath);
}
//-------------------------------------------------------------------------
/**
* Builds a market data snapshot from this environment.
*
* @param marketDataDate the date of the market data
* @return the snapshot
*/
public ImmutableMarketData buildSnapshot(LocalDate marketDataDate) {
ImmutableMarketDataBuilder builder = ImmutableMarketData.builder(marketDataDate);
loadFixingSeries(builder);
loadRatesCurves(builder, marketDataDate);
loadQuotes(builder, marketDataDate);
loadFxRates(builder);
loadCreditMarketData(builder, marketDataDate);
return builder.build();
}
/**
* Gets the rates market lookup to use with this environment.
*
* @param marketDataDate the date of the market data
* @return the rates lookup
*/
public RatesMarketDataLookup ratesLookup(LocalDate marketDataDate) {
SortedMap<LocalDate, CurveGroup> curves = loadAllRatesCurves();
return RatesMarketDataLookup.of(curves.get(marketDataDate));
}
/**
* Gets all rates curves.
*
* @return the map of all rates curves
*/
public SortedMap<LocalDate, CurveGroup> loadAllRatesCurves() {
if (!subdirectoryExists(CURVES_DIR)) {
throw new IllegalArgumentException("No rates curves directory found");
}
ResourceLocator curveGroupsResource = getResource(CURVES_DIR, CURVES_GROUPS_FILE);
if (curveGroupsResource == null) {
throw new IllegalArgumentException(Messages.format(
"Unable to load rates curves: curve groups file not found at {}/{}", CURVES_DIR, CURVES_GROUPS_FILE));
}
ResourceLocator curveSettingsResource = getResource(CURVES_DIR, CURVES_SETTINGS_FILE);
if (curveSettingsResource == null) {
throw new IllegalArgumentException(Messages.format(
"Unable to load rates curves: curve settings file not found at {}/{}", CURVES_DIR, CURVES_SETTINGS_FILE));
}
ListMultimap<LocalDate, CurveGroup> curveGroups =
RatesCurvesCsvLoader.loadAllDates(curveGroupsResource, curveSettingsResource, getRatesCurvesResources());
// There is only one curve group in the market data file so this will always succeed
Map<LocalDate, CurveGroup> curveGroupMap = Maps.transformValues(curveGroups.asMap(), groups -> groups.iterator().next());
return new TreeMap<>(curveGroupMap);
}
//-------------------------------------------------------------------------
private void loadFixingSeries(ImmutableMarketDataBuilder builder) {
if (!subdirectoryExists(HISTORICAL_FIXINGS_DIR)) {
log.debug("No historical fixings directory found");
return;
}
try {
Collection<ResourceLocator> fixingSeriesResources = getAllResources(HISTORICAL_FIXINGS_DIR);
Map<ObservableId, LocalDateDoubleTimeSeries> fixingSeries = FixingSeriesCsvLoader.load(fixingSeriesResources);
builder.addTimeSeriesMap(fixingSeries);
} catch (Exception e) {
log.error("Error loading fixing series", e);
}
}
private void loadRatesCurves(ImmutableMarketDataBuilder builder, LocalDate marketDataDate) {
if (!subdirectoryExists(CURVES_DIR)) {
log.debug("No rates curves directory found");
return;
}
ResourceLocator curveGroupsResource = getResource(CURVES_DIR, CURVES_GROUPS_FILE);
if (curveGroupsResource == null) {
log.error("Unable to load rates curves: curve groups file not found at {}/{}", CURVES_DIR, CURVES_GROUPS_FILE);
return;
}
ResourceLocator curveSettingsResource = getResource(CURVES_DIR, CURVES_SETTINGS_FILE);
if (curveSettingsResource == null) {
log.error("Unable to load rates curves: curve settings file not found at {}/{}", CURVES_DIR, CURVES_SETTINGS_FILE);
return;
}
try {
Collection<ResourceLocator> curvesResources = getRatesCurvesResources();
List<CurveGroup> ratesCurves =
RatesCurvesCsvLoader.load(marketDataDate, curveGroupsResource, curveSettingsResource, curvesResources);
for (CurveGroup group : ratesCurves) {
// add entry for higher level discount curve name
group.getDiscountCurves().forEach(
(ccy, curve) -> builder.addValue(CurveId.of(group.getName(), curve.getName()), curve));
// add entry for higher level forward curve name
group.getForwardCurves().forEach(
(idx, curve) -> builder.addValue(CurveId.of(group.getName(), curve.getName()), curve));
}
} catch (Exception e) {
log.error("Error loading rates curves", e);
}
}
// load quotes
private void loadQuotes(ImmutableMarketDataBuilder builder, LocalDate marketDataDate) {
if (!subdirectoryExists(QUOTES_DIR)) {
log.debug("No quotes directory found");
return;
}
ResourceLocator quotesResource = getResource(QUOTES_DIR, QUOTES_FILE);
if (quotesResource == null) {
log.error("Unable to load quotes: quotes file not found at {}/{}", QUOTES_DIR, QUOTES_FILE);
return;
}
try {
Map<QuoteId, Double> quotes = QuotesCsvLoader.load(marketDataDate, quotesResource);
builder.addValueMap(quotes);
} catch (Exception ex) {
log.error("Error loading quotes", ex);
}
}
private void loadFxRates(ImmutableMarketDataBuilder builder) {
// TODO - load from CSV file - format to be defined
builder.addValue(FxRateId.of(Currency.GBP, Currency.USD), FxRate.of(Currency.GBP, Currency.USD, 1.61));
}
//-------------------------------------------------------------------------
private Collection<ResourceLocator> getRatesCurvesResources() {
return getAllResources(CURVES_DIR).stream()
.filter(res -> !res.getLocator().endsWith(CURVES_GROUPS_FILE))
.filter(res -> !res.getLocator().endsWith(CURVES_SETTINGS_FILE))
.collect(toImmutableList());
}
private void loadCreditMarketData(ImmutableMarketDataBuilder builder, LocalDate marketDataDate) {
if (!subdirectoryExists(CREDIT_DIR)) {
log.debug("No credit curves directory found");
return;
}
String creditMarketDataDateDirectory = String.format(
Locale.ENGLISH,
"%s/%s",
CREDIT_DIR,
marketDataDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
if (!subdirectoryExists(creditMarketDataDateDirectory)) {
log.debug("Unable to load market data: directory not found at {}", creditMarketDataDateDirectory);
return;
}
loadCdsYieldCurves(builder, creditMarketDataDateDirectory);
loadCdsSingleNameSpreadCurves(builder, creditMarketDataDateDirectory);
loadCdsIndexSpreadCurves(builder, creditMarketDataDateDirectory);
}
private void loadCdsYieldCurves(ImmutableMarketDataBuilder builder, String creditMarketDataDateDirectory) {
ResourceLocator cdsYieldCurvesResource = getResource(creditMarketDataDateDirectory, CDS_YIELD_CURVES_FILE);
if (cdsYieldCurvesResource == null) {
log.debug("Unable to load cds yield curves: file not found at {}/{}", creditMarketDataDateDirectory,
CDS_YIELD_CURVES_FILE);
return;
}
CharSource inputSource = cdsYieldCurvesResource.getCharSource();
Map<IsdaYieldCurveInputsId, IsdaYieldCurveInputs> yieldCuves = MarkitYieldCurveDataParser.parse(inputSource);
for (IsdaYieldCurveInputsId id : yieldCuves.keySet()) {
IsdaYieldCurveInputs curveInputs = yieldCuves.get(id);
builder.addValue(id, curveInputs);
}
}
private void loadCdsSingleNameSpreadCurves(ImmutableMarketDataBuilder builder, String creditMarketDataDateDirectory) {
ResourceLocator singleNameCurvesResource = getResource(creditMarketDataDateDirectory, SINGLE_NAME_CREDIT_CURVES_FILE);
if (singleNameCurvesResource == null) {
log.debug("Unable to load single name spread curves: file not found at {}/{}", creditMarketDataDateDirectory,
SINGLE_NAME_CREDIT_CURVES_FILE);
return;
}
ResourceLocator singleNameStaticDataResource = getResource(creditMarketDataDateDirectory, SINGLE_NAME_STATIC_DATA_FILE);
if (singleNameStaticDataResource == null) {
log.debug("Unable to load single name static data: file not found at {}/{}", creditMarketDataDateDirectory,
SINGLE_NAME_STATIC_DATA_FILE);
return;
}
try {
CharSource inputCreditCurvesSource = singleNameCurvesResource.getCharSource();
CharSource inputStaticDataSource = singleNameStaticDataResource.getCharSource();
MarkitSingleNameCreditCurveDataParser.parse(builder, inputCreditCurvesSource, inputStaticDataSource);
} catch (Exception ex) {
throw new RuntimeException(String.format(
Locale.ENGLISH,
"Unable to read single name spread curves: exception at %s/%s",
creditMarketDataDateDirectory, SINGLE_NAME_CREDIT_CURVES_FILE), ex);
}
}
private void loadCdsIndexSpreadCurves(ImmutableMarketDataBuilder builder, String creditMarketDataDateDirectory) {
ResourceLocator inputCurvesResource = getResource(creditMarketDataDateDirectory, INDEX_CREDIT_CURVES_FILE);
if (inputCurvesResource == null) {
log.debug("Unable to load single name spread curves: file not found at {}/{}", creditMarketDataDateDirectory,
INDEX_CREDIT_CURVES_FILE);
return;
}
ResourceLocator inputStaticDataResource = getResource(creditMarketDataDateDirectory, INDEX_STATIC_DATA_FILE);
if (inputStaticDataResource == null) {
log.debug("Unable to load index static data: file not found at {}/{}", creditMarketDataDateDirectory,
INDEX_STATIC_DATA_FILE);
return;
}
CharSource indexCreditCurvesSource = inputCurvesResource.getCharSource();
CharSource indexStaticDataSource = inputStaticDataResource.getCharSource();
MarkitIndexCreditCurveDataParser.parse(builder, indexCreditCurvesSource, indexStaticDataSource);
}
//-------------------------------------------------------------------------
/**
* Gets all available resources from a given subdirectory.
*
* @param subdirectoryName the name of the subdirectory
* @return a collection of locators for the resources in the subdirectory
*/
protected abstract Collection<ResourceLocator> getAllResources(String subdirectoryName);
/**
* Gets a specific resource from a given subdirectory.
*
* @param subdirectoryName the name of the subdirectory
* @param resourceName the name of the resource
* @return a locator for the requested resource
*/
protected abstract ResourceLocator getResource(String subdirectoryName, String resourceName);
/**
* Checks whether a specific subdirectory exists.
*
* @param subdirectoryName the name of the subdirectory
* @return whether the subdirectory exists
*/
protected abstract boolean subdirectoryExists(String subdirectoryName);
}