com.opengamma.bbg.BloombergHistoricalTimeSeriesSource.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.bbg.BloombergHistoricalTimeSeriesSource.java

Source

/**
 * 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.BLOOMBERG_DATA_SOURCE_NAME;
import static com.opengamma.bbg.BloombergConstants.BLOOMBERG_FIELDS_REQUEST;
import static com.opengamma.bbg.BloombergConstants.BLOOMBERG_HISTORICAL_DATA_REQUEST;
import static com.opengamma.bbg.BloombergConstants.BLOOMBERG_SECURITIES_REQUEST;
import static com.opengamma.bbg.BloombergConstants.ERROR_INFO;
import static com.opengamma.bbg.BloombergConstants.FIELD_DATA;
import static com.opengamma.bbg.BloombergConstants.FIELD_EXCEPTIONS;
import static com.opengamma.bbg.BloombergConstants.FIELD_ID;
import static com.opengamma.bbg.BloombergConstants.RESPONSE_ERROR;
import static com.opengamma.bbg.BloombergConstants.SECURITY_DATA;
import static com.opengamma.bbg.BloombergConstants.SECURITY_ERROR;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;

import javax.time.calendar.LocalDate;
import javax.time.calendar.MonthOfYear;
import javax.time.calendar.format.DateTimeFormatters;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bloomberglp.blpapi.CorrelationID;
import com.bloomberglp.blpapi.Datetime;
import com.bloomberglp.blpapi.Element;
import com.bloomberglp.blpapi.Request;
import com.bloomberglp.blpapi.Service;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.bbg.referencedata.statistics.BloombergReferenceDataStatistics;
import com.opengamma.bbg.util.BloombergDomainIdentifierResolver;
import com.opengamma.core.change.BasicChangeManager;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeries;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeriesSource;
import com.opengamma.core.historicaltimeseries.impl.SimpleHistoricalTimeSeries;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.UniqueId;
import com.opengamma.id.UniqueIdSupplier;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.DateUtils;
import com.opengamma.util.timeseries.localdate.ArrayLocalDateDoubleTimeSeries;
import com.opengamma.util.timeseries.localdate.LocalDateDoubleTimeSeries;
import com.opengamma.util.timeseries.localdate.MapLocalDateDoubleTimeSeries;
import com.opengamma.util.tuple.Pair;

/**
 * Loads time-series from Bloomberg.
 */
public class BloombergHistoricalTimeSeriesSource extends AbstractBloombergStaticDataProvider
        implements HistoricalTimeSeriesSource {

    /**
     * Default start date for loading time-series
     */
    public static final LocalDate DEFAULT_START_DATE = LocalDate.of(1900, MonthOfYear.JANUARY, 01);

    /** Logger. */
    private static final Logger s_logger = LoggerFactory.getLogger(BloombergHistoricalTimeSeriesSource.class);
    /**
     * The format of error messages.
     */
    private static final String ERROR_MESSAGE_FORMAT = "{0}:{1}/{2} - {3}";
    /**
     * The Bloomberg service.
     */
    private static final UniqueIdSupplier UID_SUPPLIER = new UniqueIdSupplier("BbgHTS");

    /**
     * The Bloomberg service.
     */
    private Service _refDataService;
    /**
     * The statistics for Bloomberg access.
     */
    private final BloombergReferenceDataStatistics _statistics;
    /**
     * The local change manager.
     */
    private final ChangeManager _changeManager;

    @Override
    public ChangeManager changeManager() {
        return _changeManager;
    }

    /**
     * Creates an instance.
     * <p>
     * This will use the statistics tool in the connector.
     * 
     * @param bloombergConnector  the Bloomberg connector, not null
     */
    public BloombergHistoricalTimeSeriesSource(BloombergConnector bloombergConnector) {
        this(bloombergConnector, bloombergConnector.getReferenceDataStatistics());
    }

    /**
     * Creates an instance.
     * <p>
     * This will use the statistics tool in the connector.
     * 
     * @param bloombergConnector  the Bloomberg connector, not null
     * @param changeManager  the change manager, not null
     */
    public BloombergHistoricalTimeSeriesSource(BloombergConnector bloombergConnector, ChangeManager changeManager) {
        this(bloombergConnector, bloombergConnector.getReferenceDataStatistics(), changeManager);
    }

    /**
     * Creates an instance with statistics gathering.
     * 
     * @param bloombergConnector  the Bloomberg connector, not null
     * @param statistics  the statistics to collect, not null
     */
    public BloombergHistoricalTimeSeriesSource(BloombergConnector bloombergConnector,
            BloombergReferenceDataStatistics statistics) {
        this(bloombergConnector, statistics, new BasicChangeManager());
    }

    /**
     * Creates an instance with statistics gathering.
     * 
     * @param bloombergConnector  the Bloomberg connector, not null
     * @param statistics  the statistics to collect, not null
     * @param changeManager  the change manager, not null
     */
    public BloombergHistoricalTimeSeriesSource(BloombergConnector bloombergConnector,
            BloombergReferenceDataStatistics statistics, ChangeManager changeManager) {
        super(bloombergConnector);
        ArgumentChecker.notNull(statistics, "statistics");
        ArgumentChecker.notNull(changeManager, "changeManager");
        _statistics = statistics;
        _changeManager = changeManager;
    }

    //-------------------------------------------------------------------------
    @Override
    protected Logger getLogger() {
        return s_logger;
    }

    @Override
    protected void openServices() {
        _refDataService = openService(BloombergConstants.REF_DATA_SVC_NAME);
    }

    /**
     * Gets the Bloomberg reference data service.
     * 
     * @return the service, not null once started
     */
    protected Service getRefDataService() {
        return _refDataService;
    }

    //-------------------------------------------------------------------------
    private Request composeRequest(final String identifier, final String dataSource, final String dataProvider,
            final String field, LocalDate startDate, LocalDate endDate, Integer maxPoints) {
        ArgumentChecker.notNull(identifier, "identifier must not be null or empty");

        Request request = getRefDataService().createRequest(BLOOMBERG_HISTORICAL_DATA_REQUEST);
        Element securitiesElem = request.getElement(BLOOMBERG_SECURITIES_REQUEST);
        securitiesElem.appendValue(identifier);

        Element fieldElem = request.getElement(BLOOMBERG_FIELDS_REQUEST);

        fieldElem.appendValue(field == null ? "PX_LAST" : field);

        request.set("periodicityAdjustment", "ACTUAL");
        request.set("periodicitySelection", "DAILY");
        request.set("startDate", DateUtils.printYYYYMMDD(startDate));
        request.set("endDate", DateUtils.printYYYYMMDD(endDate));
        request.set("returnEids", true);

        if (maxPoints != null && maxPoints <= 0) {
            request.set("maxDataPoints", -maxPoints);
        }

        return request;
    }

    private HistoricalTimeSeries doGetHistoricalTimeSeries(ExternalIdBundle identifiers, String dataSource,
            String dataProvider, String field, LocalDate startDate, LocalDate endDate, Integer maxPoints) {
        ArgumentChecker.notNull(identifiers, "identifiers");
        ArgumentChecker.notNull(field, "field");
        ArgumentChecker.notNull(startDate, "startDate");
        ArgumentChecker.notNull(endDate, "endDate");
        Validate.isTrue(ObjectUtils.equals(dataSource, BLOOMBERG_DATA_SOURCE_NAME),
                getClass().getName() + "cannot support " + dataSource);

        if (maxPoints != null && maxPoints > 0) {
            throw new UnsupportedOperationException(
                    "Fetching a bounded number of points from the start of a Bloomberg time-series is unsupported");
        }

        s_logger.info("Getting HistoricalTimeSeries for security {}", identifiers);

        ensureStarted();

        if (endDate.isBefore(startDate)) {
            throw new IllegalArgumentException("endDate must be after startDate");
        }
        ExternalId dsid = BloombergDomainIdentifierResolver.resolvePreferredIdentifier(identifiers);
        String bbgKey = BloombergDomainIdentifierResolver.toBloombergKeyWithDataProvider(dsid, dataProvider);
        Request request = composeRequest(bbgKey, dataSource, dataProvider, field, startDate, endDate, maxPoints);
        _statistics.recordStatistics(Collections.singleton(bbgKey), Collections.singleton(field));
        LocalDateDoubleTimeSeries timeSeries = processRequest(bbgKey, request, field);
        return new SimpleHistoricalTimeSeries(UID_SUPPLIER.get(), timeSeries);
    }

    private LocalDateDoubleTimeSeries processRequest(String identifier, Request request, String field) {
        CorrelationID cid = submitBloombergRequest(request);
        BlockingQueue<Element> resultElements = getResultElement(cid);

        if (resultElements == null || resultElements.isEmpty()) {
            s_logger.info("Unable to get HistoricalTimeSeries for {}", identifier);
            return null;
        }
        List<LocalDate> dates = Lists.newArrayList();
        List<Double> values = Lists.newArrayList();
        for (Element resultElem : resultElements) {
            if (resultElem.hasElement(RESPONSE_ERROR)) {
                s_logger.warn("Response error");
                processError(resultElem.getElement(RESPONSE_ERROR));
            }
            Element securityElem = resultElem.getElement(SECURITY_DATA);
            if (securityElem.hasElement(SECURITY_ERROR)) {
                processError(securityElem.getElement(SECURITY_ERROR));
                return null;
            }
            if (securityElem.hasElement(FIELD_EXCEPTIONS)) {
                Element fieldExceptions = securityElem.getElement(FIELD_EXCEPTIONS);

                for (int i = 0; i < fieldExceptions.numValues(); i++) {
                    Element fieldException = fieldExceptions.getValueAsElement(i);
                    String fieldId = fieldException.getElementAsString(FIELD_ID);
                    s_logger.warn("Field error on {}", fieldId);
                    Element errorInfo = fieldException.getElement(ERROR_INFO);
                    processError(errorInfo);
                }
            }
            if (securityElem.hasElement(FIELD_DATA)) {
                processFieldData(securityElem.getElement(FIELD_DATA), field, dates, values);
            }
        }
        return new ArrayLocalDateDoubleTimeSeries(dates, values);
    }

    private void processFieldData(Element securityElem, String field, Map<String, ExternalIdBundle> bbgSecDomainMap,
            Map<ExternalIdBundle, HistoricalTimeSeries> result) {
        String secDes = securityElem.getElementAsString(BloombergConstants.SECURITY);
        ExternalIdBundle identifiers = bbgSecDomainMap.get(secDes);
        if (identifiers == null) {
            String message = "Found time series data for unrecognized security" + secDes + " " + bbgSecDomainMap;
            throw new OpenGammaRuntimeException(message);
        }
        HistoricalTimeSeries hts = result.get(identifiers);
        MapLocalDateDoubleTimeSeries timeSeries;
        if (hts == null) {
            timeSeries = new MapLocalDateDoubleTimeSeries();
            hts = new SimpleHistoricalTimeSeries(UID_SUPPLIER.get(), timeSeries);
            result.put(identifiers, hts);
        } else {
            timeSeries = (MapLocalDateDoubleTimeSeries) hts.getTimeSeries();
        }
        Element fieldDataArray = securityElem.getElement(FIELD_DATA);
        int numValues = fieldDataArray.numValues();
        for (int i = 0; i < numValues; i++) {
            Element fieldData = fieldDataArray.getValueAsElement(i);
            Datetime date = fieldData.getElementAsDate("date");
            double lastPrice = fieldData.getElementAsFloat64(field);
            int year = date.year();
            int month = date.month();
            int day = date.dayOfMonth();
            timeSeries.putDataPoint(LocalDate.of(year, month, day), lastPrice);
        }
    }

    private String printYYYYMMDD(LocalDate localDate) {
        String formatted = DateTimeFormatters.isoLocalDate().print(localDate);
        return StringUtils.remove(formatted, '-');
    }

    protected void processFieldData(final Element fieldDataArray, String field, final List<LocalDate> dates,
            final List<Double> values) {
        int numValues = fieldDataArray.numValues();
        for (int i = 0; i < numValues; i++) {
            Element fieldData = fieldDataArray.getValueAsElement(i);
            Datetime date = fieldData.getElementAsDate("date");
            double fieldValue = fieldData.getElementAsFloat64(field);
            dates.add(LocalDate.of(date.year(), date.month(), date.dayOfMonth()));
            values.add(fieldValue);
        }
    }

    protected void processError(Element element) {
        int code = element.getElementAsInt32("code");
        String category = element.getElementAsString("category");
        String subcategory = element.getElementAsString("subcategory");
        String message = element.getElementAsString("message");

        String errorMessage = MessageFormat.format(ERROR_MESSAGE_FORMAT, code, category, subcategory, message);
        s_logger.warn(errorMessage);
    }

    //-------------------------------------------------------------------------
    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(UniqueId uniqueId) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using unique identifier");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(UniqueId uniqueId, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using unique identifier");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(UniqueId uniqueId, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd, int maxPoints) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using unique identifier");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(UniqueId uniqueId) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using unique identifier");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(UniqueId uniqueId, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using unique identifier");
    }

    //-------------------------------------------------------------------------
    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(final ExternalIdBundle identifiers, final String dataSource,
            final String dataProvider, final String dataField) {
        LocalDate yesterday = DateUtils.previousWeekDay();
        return doGetHistoricalTimeSeries(identifiers, dataSource, dataProvider, dataField, DEFAULT_START_DATE,
                yesterday, null);
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(final ExternalIdBundle identifiers, final String dataSource,
            final String dataProvider, final String dataField, final LocalDate start, boolean includeStart,
            final LocalDate end, boolean includeEnd) {
        LocalDate resolvedStart = null;
        if (!includeStart && start != null) {
            resolvedStart = start.plusDays(1);
        } else {
            resolvedStart = start;
        }
        LocalDate resolvedEnd = null;
        if (!includeEnd && end != null) {
            resolvedEnd = end.minusDays(1);
        } else {
            resolvedEnd = end;
        }
        return doGetHistoricalTimeSeries(identifiers, dataSource, dataProvider, dataField, resolvedStart,
                resolvedEnd, null);
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(ExternalIdBundle identifiers, String dataSource,
            String dataProvider, String dataField, LocalDate start, boolean includeStart, LocalDate end,
            boolean includeEnd, int maxPoints) {
        LocalDate resolvedStart = null;
        if (!includeStart && start != null) {
            resolvedStart = start.plusDays(1);
        } else {
            resolvedStart = start;
        }
        LocalDate resolvedEnd = null;
        if (!includeEnd && end != null) {
            resolvedEnd = end.minusDays(1);
        } else {
            resolvedEnd = end;
        }
        return doGetHistoricalTimeSeries(identifiers, dataSource, dataProvider, dataField, resolvedStart,
                resolvedEnd, maxPoints);
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(ExternalIdBundle identifiers,
            LocalDate identifierValidityDate, String dataSource, String dataProvider, String dataField) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(ExternalIdBundle identifiers,
            LocalDate identifierValidityDate, String dataSource, String dataProvider, String dataField,
            LocalDate start, boolean includeStart, LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(ExternalIdBundle identifierBundle,
            LocalDate identifierValidityDate, String dataSource, String dataProvider, String dataField,
            LocalDate start, boolean includeStart, LocalDate end, boolean includeEnd, int maxPoints) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(ExternalIdBundle identifierBundle,
            LocalDate identifierValidityDate, String dataSource, String dataProvider, String dataField) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(ExternalIdBundle identifierBundle,
            LocalDate identifierValidityDate, String dataSource, String dataProvider, String dataField,
            LocalDate start, boolean includeStart, LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(ExternalIdBundle identifierBundle, String dataSource,
            String dataProvider, String dataField) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(ExternalIdBundle identifierBundle, String dataSource,
            String dataProvider, String dataField, LocalDate start, boolean includeStart, LocalDate end,
            boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using identifier validity date");
    }

    //-------------------------------------------------------------------------
    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(String dataField, ExternalIdBundle identifiers,
            String resolutionKey) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(String dataField, ExternalIdBundle identifiers,
            String resolutionKey, LocalDate start, boolean includeStart, LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(String dataField, ExternalIdBundle identifiers,
            LocalDate identifierValidityDate, String resolutionKey) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(String dataField, ExternalIdBundle identifiers,
            LocalDate identifierValidityDate, String resolutionKey, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(String dataField, ExternalIdBundle identifierBundle,
            String resolutionKey, LocalDate start, boolean includeStart, LocalDate end, boolean includeEnd,
            int maxPoints) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public HistoricalTimeSeries getHistoricalTimeSeries(String dataField, ExternalIdBundle identifierBundle,
            LocalDate identifierValidityDate, String resolutionKey, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd, int maxPoints) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(String dataField, ExternalIdBundle identifierBundle,
            String resolutionKey) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(String dataField, ExternalIdBundle identifierBundle,
            String resolutionKey, LocalDate start, boolean includeStart, LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(String dataField, ExternalIdBundle identifierBundle,
            LocalDate identifierValidityDate, String resolutionKey) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    @Override
    public Pair<LocalDate, Double> getLatestDataPoint(String dataField, ExternalIdBundle identifierBundle,
            LocalDate identifierValidityDate, String resolutionKey, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

    //-------------------------------------------------------------------------
    @Override
    public Map<ExternalIdBundle, HistoricalTimeSeries> getHistoricalTimeSeries(Set<ExternalIdBundle> identifierSet,
            String dataSource, String dataProvider, String dataField, LocalDate start, boolean includeStart,
            LocalDate end, boolean includeEnd) {
        ArgumentChecker.notNull(identifierSet, "identifierSet");
        ArgumentChecker.notNull(dataField, "dataField");
        ArgumentChecker.notNull(start, "start");
        ArgumentChecker.notNull(end, "end");
        Validate.isTrue(ObjectUtils.equals(dataSource, BLOOMBERG_DATA_SOURCE_NAME),
                getClass().getName() + "cannot support " + dataSource);

        if (end.isBefore(start)) {
            throw new IllegalArgumentException("end must be after start");
        }

        ensureStarted();
        s_logger.debug("Getting historical data for {}", identifierSet);

        if (identifierSet.isEmpty()) {
            s_logger.info("Historical data request for empty identifier set");
            return Collections.emptyMap();
        }
        Map<String, ExternalIdBundle> bbgSecDomainMap = Maps.newHashMap();
        Request request = getRefDataService().createRequest(BLOOMBERG_HISTORICAL_DATA_REQUEST);
        Element securitiesElem = request.getElement(BLOOMBERG_SECURITIES_REQUEST);
        for (ExternalIdBundle identifiers : identifierSet) {
            ExternalId preferredIdentifier = BloombergDomainIdentifierResolver
                    .resolvePreferredIdentifier(identifiers);
            s_logger.debug("Resolved preferred identifier {} from identifier bundle {}", preferredIdentifier,
                    identifiers);
            String bbgKey = BloombergDomainIdentifierResolver.toBloombergKeyWithDataProvider(preferredIdentifier,
                    dataProvider);
            securitiesElem.appendValue(bbgKey);
            bbgSecDomainMap.put(bbgKey, identifiers);
        }

        Element fieldElem = request.getElement(BLOOMBERG_FIELDS_REQUEST);
        fieldElem.appendValue(dataField);

        // TODO: inclusive start / exclusive end
        request.set("periodicityAdjustment", "ACTUAL");
        request.set("periodicitySelection", "DAILY");
        request.set("startDate", printYYYYMMDD(start));
        request.set("endDate", printYYYYMMDD(end));
        request.set("adjustmentSplit", true);

        _statistics.recordStatistics(bbgSecDomainMap.keySet(), Collections.singleton(dataField));
        CorrelationID cid = submitBloombergRequest(request);
        BlockingQueue<Element> resultElements = getResultElement(cid);
        if (resultElements == null || resultElements.isEmpty()) {
            s_logger.warn("Unable to get historical data for {}", identifierSet);
            return null;
        }

        //REVIEW simon 2011/11/01: should this be deduped with the single case? 
        Map<ExternalIdBundle, HistoricalTimeSeries> result = Maps.newHashMap();
        for (Element resultElem : resultElements) {
            if (resultElem.hasElement(RESPONSE_ERROR)) {
                s_logger.warn("Response error");
                processError(resultElem.getElement(RESPONSE_ERROR));
                continue;
            }
            Element securityElem = resultElem.getElement(SECURITY_DATA);
            if (securityElem.hasElement(SECURITY_ERROR)) {
                processError(securityElem.getElement(SECURITY_ERROR));
            }
            if (securityElem.hasElement(FIELD_EXCEPTIONS)) {
                Element fieldExceptions = securityElem.getElement(FIELD_EXCEPTIONS);

                for (int i = 0; i < fieldExceptions.numValues(); i++) {
                    Element fieldException = fieldExceptions.getValueAsElement(i);
                    String fieldId = fieldException.getElementAsString(FIELD_ID);
                    s_logger.warn("Field error on {}", fieldId);
                    Element errorInfo = fieldException.getElement(ERROR_INFO);
                    processError(errorInfo);
                }
            }
            if (securityElem.hasElement(FIELD_DATA)) {
                processFieldData(securityElem, dataField, bbgSecDomainMap, result);
            }
        }
        if (identifierSet.size() != result.size()) {
            s_logger.warn("Failed to get time series results for ({}/{}) {}",
                    new Object[] { identifierSet.size() - result.size(), identifierSet.size(),
                            Sets.difference(identifierSet, result.keySet()) });
        }
        return result;
    }

    @Override
    public ExternalIdBundle getExternalIdBundle(UniqueId uniqueId) {
        throw new UnsupportedOperationException(
                "Unable to retrieve historical time-series from Bloomberg using config");
    }

}