/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.bbg;
import static com.opengamma.bbg.BloombergConstants.FIELD_FUT_FIRST_TRADE_DT;
import static com.opengamma.bbg.BloombergConstants.FIELD_FUT_LAST_TRADE_DT;
import static com.opengamma.bbg.BloombergConstants.FIELD_ID_BBG_UNIQUE;
import static com.opengamma.bbg.BloombergConstants.FIELD_ID_CUSIP;
import static com.opengamma.bbg.BloombergConstants.FIELD_ID_ISIN;
import static com.opengamma.bbg.BloombergConstants.FIELD_ID_SEDOL1;
import static com.opengamma.bbg.BloombergConstants.FIELD_MARKET_SECTOR_DES;
import static com.opengamma.bbg.BloombergConstants.FIELD_OPT_EXPIRE_DT;
import static com.opengamma.bbg.BloombergConstants.FIELD_PARSEKYABLE_DES;
import static com.opengamma.bbg.BloombergConstants.FIELD_SECURITY_DES;
import static com.opengamma.bbg.BloombergConstants.FIELD_SECURITY_TYPE;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.fudgemsg.FudgeMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.LocalDate;
import org.threeten.bp.format.DateTimeParseException;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.bbg.referencedata.ReferenceData;
import com.opengamma.bbg.referencedata.ReferenceDataError;
import com.opengamma.bbg.referencedata.ReferenceDataProvider;
import com.opengamma.bbg.referencedata.ReferenceDataProviderGetRequest;
import com.opengamma.bbg.referencedata.ReferenceDataProviderGetResult;
import com.opengamma.bbg.util.BloombergDataUtils;
import com.opengamma.bbg.util.BloombergDomainIdentifierResolver;
import com.opengamma.core.id.ExternalSchemes;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundleWithDates;
import com.opengamma.id.ExternalIdWithDates;
import com.opengamma.master.historicaltimeseries.ExternalIdResolver;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Pair;
/**
* Loads identifier bundle with dates for a given identifier.
*/
public class BloombergIdentifierProvider implements ExternalIdResolver {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(BloombergIdentifierProvider.class);
/**
* The Bloomberg data.
*/
private final ReferenceDataProvider _bbgRefDataProvider;
/**
* The requested Bloomberg fields.
*/
private final Set<String> _bbgFields = Sets.newHashSet(
FIELD_ID_BBG_UNIQUE,
FIELD_ID_CUSIP,
FIELD_ID_ISIN,
FIELD_ID_SEDOL1,
FIELD_PARSEKYABLE_DES,
FIELD_FUT_FIRST_TRADE_DT,
FIELD_FUT_LAST_TRADE_DT,
FIELD_SECURITY_TYPE,
FIELD_SECURITY_DES,
FIELD_MARKET_SECTOR_DES,
FIELD_OPT_EXPIRE_DT);
/**
* Creates a new instance.
*
* @param refDataProvider the Bloomberg data, not null
*/
public BloombergIdentifierProvider(ReferenceDataProvider refDataProvider) {
ArgumentChecker.notNull(refDataProvider, "refDataProvider");
_bbgRefDataProvider = refDataProvider;
}
//-------------------------------------------------------------------------
@Override
public Map<ExternalId, ExternalIdBundleWithDates> getExternalIds(Set<ExternalId> unresolvedIdentifiers) {
Map<ExternalId, ExternalIdBundleWithDates> result = new HashMap<ExternalId, ExternalIdBundleWithDates>(unresolvedIdentifiers.size());
Map<String, ExternalId> bloombergKeys = new HashMap<String, ExternalId>(unresolvedIdentifiers.size());
for (ExternalId identifier : unresolvedIdentifiers) {
String bloombergKey = BloombergDomainIdentifierResolver.toBloombergKey(identifier);
if (bloombergKey != null) {
bloombergKeys.put(bloombergKey, identifier);
} else {
s_logger.warn("cannot resolve {} to bloomberg identifier", bloombergKey);
}
}
ReferenceDataProviderGetRequest refDataRequest = ReferenceDataProviderGetRequest.createGet(bloombergKeys.keySet(), _bbgFields, true);
ReferenceDataProviderGetResult refDataResult = _bbgRefDataProvider.getReferenceData(refDataRequest);
for (ReferenceData refData : refDataResult.getReferenceData()) {
String securityDes = refData.getIdentifier();
// check no exceptions
if (refData.isIdentifierError()) {
List<ReferenceDataError> errors = refData.getErrors();
if (errors != null && errors.size() > 0) {
for (ReferenceDataError error : errors) {
s_logger.warn("Exception looking up {}/{} - {}",
new Object[] {securityDes, _bbgFields, error });
}
continue;
}
}
// check same security was returned
String refSec = refData.getIdentifier();
if (!securityDes.equals(refSec)) {
s_logger.warn("Returned security {} not the same as searched security {}", refSec, securityDes);
continue;
}
// get field data
FudgeMsg fieldData = refData.getFieldValues();
final String securityType = fieldData.getString(FIELD_SECURITY_TYPE);
if (securityType != null && securityType.toUpperCase().contains(" FUTURE")) {
final String firstTradeStr = fieldData.getString(FIELD_FUT_FIRST_TRADE_DT);
final LocalDate firstTradeDate = parseDate(firstTradeStr);
final String lastTradeStr = fieldData.getString(FIELD_FUT_LAST_TRADE_DT);
final LocalDate lastTradeDate = parseDate(lastTradeStr);
if (lastTradeDate.isBefore(firstTradeDate)) {
s_logger.warn("Reference data for {} indicates last trade date ({}) before first trade date ({}) - ignoring", new Object[] {securityDes, lastTradeDate, firstTradeDate});
continue;
}
}
try {
ExternalIdBundleWithDates bundleWithDates = parseIdentifiers(fieldData);
if (bundleWithDates != null) {
result.put(bloombergKeys.get(securityDes), bundleWithDates);
}
} catch (Exception e) {
s_logger.error("Error parsing identifiers for security " + securityDes, e);
throw new OpenGammaRuntimeException("Error parsing identifiers for security " + securityDes, e);
}
}
return result;
}
//-------------------------------------------------------------------------
/**
* Parses the response to a bundle.
*
* @param fieldData the Bloomberg field data, not null
* @return the identifier bundle, null if not available
*/
private ExternalIdBundleWithDates parseIdentifiers(FudgeMsg fieldData) {
ArgumentChecker.notNull(fieldData, "fieldData");
final Set<ExternalIdWithDates> identifiers = new HashSet<ExternalIdWithDates>();
ExternalIdWithDates buid = makeBuidIdentifier(fieldData);
if (buid != null) {
identifiers.add(buid);
}
ExternalIdWithDates cusip = makeCusipIdentifier(fieldData);
if (cusip != null) {
identifiers.add(cusip);
}
ExternalIdWithDates isin = makeIsinIdentifier(fieldData);
if (isin != null) {
identifiers.add(isin);
}
ExternalIdWithDates sedol1 = makeSedol1Identifier(fieldData);
if (sedol1 != null) {
identifiers.add(sedol1);
}
ExternalIdWithDates ticker = makeTickerIdentifier(fieldData);
if (ticker != null) {
identifiers.add(ticker);
}
return identifiers.isEmpty() ? null : new ExternalIdBundleWithDates(identifiers);
}
//-------------------------------------------------------------------------
/**
* Extract and build a ticker identifier.
*
* @param fieldData the Bloomberg field data, not null
* @return the identifier, null if not found
*/
private ExternalIdWithDates makeTickerIdentifier(FudgeMsg fieldData) {
ExternalIdWithDates result = null;
if (fieldData != null) {
final String securityType = fieldData.getString(FIELD_SECURITY_TYPE);
if (securityType != null) {
if (securityType.contains("GOVERNMENT")) {
return result;
}
if (securityType.contains("Option")) {
final String securityDes = fieldData.getString(FIELD_SECURITY_DES);
final String marketSector = fieldData.getString(FIELD_MARKET_SECTOR_DES);
final String expiry = fieldData.getString(FIELD_OPT_EXPIRE_DT);
LocalDate validTo = parseDate(expiry);
LocalDate validFrom = null;
if (BloombergDataUtils.isValidField(securityDes)) {
if (BloombergDataUtils.isValidField(marketSector)) {
ExternalId tickerId = ExternalSchemes.bloombergTickerSecurityId(securityDes + " " + marketSector);
result = ExternalIdWithDates.of(tickerId, validFrom, validTo);
}
}
} else {
final String securityIdentifier = fieldData.getString(FIELD_PARSEKYABLE_DES);
final Pair<LocalDate, LocalDate> futureValidFromTo = getFutureValidFromTo(fieldData);
if (BloombergDataUtils.isValidField(securityIdentifier)) {
ExternalId tickerId = ExternalSchemes.bloombergTickerSecurityId(BloombergDataUtils.removeDuplicateWhiteSpace(securityIdentifier, " "));
result = ExternalIdWithDates.of(tickerId, futureValidFromTo.getFirst(), futureValidFromTo.getSecond());
}
}
}
}
return result;
}
//-------------------------------------------------------------------------
/**
* Extract and build a SEDOL1 identifier.
*
* @param fieldData the Bloomberg field data, not null
* @return the identifier, null if not found
*/
private ExternalIdWithDates makeSedol1Identifier(FudgeMsg fieldData) {
final String sedol1 = fieldData.getString(FIELD_ID_SEDOL1);
if (BloombergDataUtils.isValidField(sedol1) == false) {
return null;
}
ExternalId sedol1Id = ExternalSchemes.sedol1SecurityId(sedol1);
return ExternalIdWithDates.of(sedol1Id, null, null);
}
/**
* Extract and build a ISIN identifier.
*
* @param fieldData the Bloomberg field data, not null
* @return the identifier, null if not found
*/
private ExternalIdWithDates makeIsinIdentifier(FudgeMsg fieldData) {
final String isin = fieldData.getString(FIELD_ID_ISIN);
if (BloombergDataUtils.isValidField(isin) == false) {
return null;
}
ExternalId isinId = ExternalSchemes.isinSecurityId(isin);
return ExternalIdWithDates.of(isinId, null, null);
}
/**
* Extract and build a CUSIP identifier.
*
* @param fieldData the Bloomberg field data, not null
* @return the identifier, null if not found
*/
private ExternalIdWithDates makeCusipIdentifier(FudgeMsg fieldData) {
final String cusip = fieldData.getString(FIELD_ID_CUSIP);
if (BloombergDataUtils.isValidField(cusip) == false) {
return null;
}
final Pair<LocalDate, LocalDate> validFromTo = getFutureValidFromTo(fieldData);
final ExternalId cusipId = ExternalSchemes.cusipSecurityId(cusip);
return ExternalIdWithDates.of(cusipId, validFromTo.getFirst(), validFromTo.getSecond());
}
/**
* Extract and build a BIUD identifier.
*
* @param fieldData the Bloomberg field data, not null
* @return the identifier, null if not found
*/
private ExternalIdWithDates makeBuidIdentifier(FudgeMsg fieldData) {
final String bbgUnique = fieldData.getString(FIELD_ID_BBG_UNIQUE);
if (BloombergDataUtils.isValidField(bbgUnique) == false) {
return null;
}
ExternalId buid = ExternalSchemes.bloombergBuidSecurityId(bbgUnique);
return ExternalIdWithDates.of(buid, null, null);
}
//-------------------------------------------------------------------------
/**
* Parses a Bloomberg formatted date.
*
* @param dateStr the date, not null
* @return the parsed date, null if not available
*/
private LocalDate parseDate(final String dateStr) {
if (BloombergDataUtils.isValidField(dateStr) == false) {
return null;
}
try {
return LocalDate.parse(dateStr);
} catch (DateTimeParseException ex) {
s_logger.warn("valid from date not in yyyy-mm-dd format - {}", dateStr);
return null;
}
}
private Pair<LocalDate, LocalDate> getFutureValidFromTo(FudgeMsg fieldData) {
final String validFromStr = fieldData.getString(FIELD_FUT_FIRST_TRADE_DT);
LocalDate validFrom = parseDate(validFromStr);
final String validToStr = fieldData.getString(FIELD_FUT_LAST_TRADE_DT);
LocalDate validTo = parseDate(validToStr);
if (validFrom != null && validTo != null && validFrom.isAfter(validTo)) {
validFrom = null;
}
return Pair.of(validFrom, validTo);
}
}