/* * Copyright (C) 2012 - present by Yann Le Tallec. * Please see distribution for license. */ package com.assylias.jbloomberg; import com.bloomberglp.blpapi.Request; import com.google.common.base.Preconditions; import java.time.LocalDate; import java.util.Arrays; import java.util.Collection; import java.util.Currency; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * This class enables to build a HistoricalData request while ensuring argument safety. Typically, instead of passing * strings arguments (and typos) as with the standard Bloomberg API, the possible options used to override the behaviour * of the query have been wrapped in enums or relevant primitive types. * <p> * All methods, including the constructors, throw NullPointerException when null arguments are passed in. * <p> * Once the request has been built, the HistoricalRequestBuilder can be submitted to a BloombergSession. * <p> * <b>This class is not thread safe.</b> */ public final class HistoricalRequestBuilder extends AbstractRequestBuilder<HistoricalData> { //Required parameters private final Set<String> tickers = new HashSet<>(); private final Set<String> fields = new HashSet<>(); private final LocalDate startDate; private final LocalDate endDate; //Optional parameters private PeriodicityAdjustment periodicityAdjustment = PeriodicityAdjustment.ACTUAL; private Period period = Period.DAILY; private Currency currency = null; private Days days = Days.ACTIVE_DAYS_ONLY; private Fill fill = Fill.NIL_VALUE; private int points = 0; private boolean adjNormal = false; private boolean adjAbnormal = false; private boolean adjSplit = false; private boolean adjDefault = false; private final Map<String, String> overrides = new HashMap<>(); /** * Equivalent to calling * <code> new HistoricalRequestBuilder(Arrays.asList(ticker), Arrays.asList(field), startDate, endDate); * </code> */ public HistoricalRequestBuilder(String ticker, String field, LocalDate startDate, LocalDate endDate) { this(Arrays.asList(ticker), Arrays.asList(field), startDate, endDate); } /** * Equivalent to calling * <code> new HistoricalRequestBuilder(Arrays.asList(ticker), fields, startDate, endDate); * </code> */ public HistoricalRequestBuilder(String ticker, Collection<String> fields, LocalDate startDate, LocalDate endDate) { this(Arrays.asList(ticker), fields, startDate, endDate); } /** * Equivalent to calling * <code> new HistoricalRequestBuilder(tickers, Arrays.asList(field), startDate, endDate); * </code> */ public HistoricalRequestBuilder(Collection<String> tickers, String field, LocalDate startDate, LocalDate endDate) { this(tickers, Arrays.asList(field), startDate, endDate); } /** * Creates a HistoricalRequestBuilder with standard options. The Builder can be further customised with the provided * methods. * <p> * @param tickers a collection of tickers for which data needs to be retrieved - tickers must be valid Bloomberg * symbols (for example: IBM US Equity) * @param fields a collection of Bloomberg fields to retrieve for each ticker * @param startDate the start of the date range (inclusive) for which to retrieve data * @param endDate the end of the date range (inclusive) for which to retrieve data * <p> * @throws NullPointerException if any of the parameters is null or if the collections contain null items * @throws IllegalArgumentException if <ul> <li> any of the collections is empty or contains empty strings <li> the * start date is strictly after the end date </ul> */ public HistoricalRequestBuilder(Collection<String> tickers, Collection<String> fields, LocalDate startDate, LocalDate endDate) { this.startDate = Preconditions.checkNotNull(startDate, "The start date must not be null"); this.endDate = Preconditions.checkNotNull(endDate, "The end date must not be null"); Preconditions.checkArgument(!startDate.isAfter(endDate), "The start date (%s) must not be after the end date (%s)", startDate, endDate); Preconditions.checkArgument(!tickers.isEmpty(), "The list of tickers must not be empty"); Preconditions.checkArgument(!fields.isEmpty(), "The list of fields must not be empty"); Preconditions.checkArgument(!tickers.contains(""), "The list of tickers must not contain empty strings"); Preconditions.checkArgument(!fields.contains(""), "The list of fields must not contain empty strings"); this.tickers.addAll(tickers); this.fields.addAll(fields); } /** * Sets the period and calendar type of the output. To be used in conjunction with Period Selection. */ public HistoricalRequestBuilder periodicityAdjustment(PeriodicityAdjustment periodicityAdjustment) { this.periodicityAdjustment = Preconditions.checkNotNull(periodicityAdjustment); return this; } /** * @param period Sets the period of the output. To be used in conjunction with Period Adjustment. */ public HistoricalRequestBuilder period(Period period) { this.period = Preconditions.checkNotNull(period); return this; } /** * @param currency Sets the currency in which the values are returned */ public HistoricalRequestBuilder currency(Currency currency) { this.currency = Preconditions.checkNotNull(currency); return this; } /** * Sets to include/exclude non trading days where no data was generated. */ public HistoricalRequestBuilder days(Days days) { this.days = Preconditions.checkNotNull(days); return this; } /** * If data is to be displayed for non trading days what is the data to be returned. */ public HistoricalRequestBuilder fill(Fill fill) { this.fill = Preconditions.checkNotNull(fill); return this; } /** * @param maxPoints Sets the maximum number of data points to return. * <p> * @throws IllegalArgumentException if the number of points is negative */ public HistoricalRequestBuilder maxPoints(int maxPoints) { if (maxPoints <= 0) { throw new IllegalArgumentException("Maximum number of points must be positive: " + maxPoints); } this.points = maxPoints; return this; } /** * Adjust historical pricing based on the DPDF<GO> BLOOMBERG PROFESSIONAL service function. */ public HistoricalRequestBuilder adjustDefault() { this.adjDefault = true; return this; } /** * Adjust historical pricing to reflect: Special Cash, Liquidation, Capital Gains, Long-Term Capital Gains, * Short-Term Capital Gains, Memorial, Return of Capital, Rights Redemption, Miscellaneous, Return Premium, * Preferred Rights Redemption, Proceeds/Rights, Proceeds/Shares, Proceeds/ Warrants. */ public HistoricalRequestBuilder adjustAbnormalDistributions() { this.adjAbnormal = true; return this; } /** * Adjust historical pricing to reflect: Regular Cash, Interim, 1st Interim, 2nd Interim, 3rd Interim, 4th Interim, * 5th Interim, Income, Estimated, Partnership Distribution, Final, Interest on Capital, Distribution, Prorated. */ public HistoricalRequestBuilder adjustNormalDistributions() { this.adjNormal = true; return this; } /** * Adjust historical pricing and/or volume to reflect: Spin-Offs, Stock Splits/Consolidations, Stock * Dividend/Bonus, Rights Offerings/ Entitlement. */ public HistoricalRequestBuilder adjustSplits() { this.adjSplit = true; return this; } /** * Add a custom override. */ public HistoricalRequestBuilder addOverride(String field, String value) { Preconditions.checkNotNull(field, "Field cannot be null when adding overrides"); Preconditions.checkNotNull(value, "Value cannot be null when adding overrides"); Preconditions.checkArgument(!field.isEmpty(), "Field cannot be empty when adding overrides"); Preconditions.checkArgument(!value.isEmpty(), "Value cannot be empty when adding overrides"); overrides.put(field, value); return this; } @Override public String toString() { return "HistoricalQueryBuilder{" + "tickers=" + tickers + ", fields=" + fields + ", startDate=" + startDate + ", endDate=" + endDate + ", periodicityAdjustment=" + periodicityAdjustment + ", period=" + period + ", currency=" + currency + ", days=" + days + ", fill=" + fill + ", points=" + points + ", adjNormal=" + adjNormal + ", adjAbnormal=" + adjAbnormal + ", adjSplit=" + adjSplit + ", adjDefault=" + adjDefault + '}'; } @Override public BloombergServiceType getServiceType() { return BloombergServiceType.REFERENCE_DATA; } @Override public BloombergRequestType getRequestType() { return BloombergRequestType.HISTORICAL_DATA; } @Override protected void buildRequest(Request request) { addCollectionToElement(request, tickers, "securities"); addCollectionToElement(request, fields, "fields"); addOverrides(request, overrides); request.set("periodicityAdjustment", periodicityAdjustment.toString()); request.set("periodicitySelection", period.toString()); if (currency != null) { request.set("currency", currency.getCurrencyCode()); } request.set("nonTradingDayFillOption", days.toString()); request.set("nonTradingDayFillMethod", fill.toString()); if (points != 0) { request.set("maxDataPoints", points); } request.set("adjustmentNormal", adjNormal); request.set("adjustmentAbnormal", adjAbnormal); request.set("adjustmentSplit", adjSplit); request.set("adjustmentFollowDPDF", adjDefault); request.set("startDate", startDate.format(BB_REQUEST_DATE_FORMATTER)); request.set("endDate", endDate.format(BB_REQUEST_DATE_FORMATTER)); } @Override public ResultParser<HistoricalData> getResultParser() { return new HistoricalResultParser(); } /** * Defines the periodicity adjustment. */ public static enum PeriodicityAdjustment { /** * These revert to the actual date from today (if the end date is left blank) or from the End Date */ ACTUAL, /** * For pricing fields, these revert to the last business day of the specified calendar period. Calendar * Quarterly (CQ), Calendar Semi-Annually (CS) or Calendar Yearly (CY). */ CALENDAR, /** * These periods revert to the fiscal period end for the company - Fiscal Quarterly (FQ), Fiscal Semi- Annually * (FS) and Fiscal Yearly (FY) only */ FISCAL; } /** * Defines the period used for historical data requests (daily, weekly etc.). */ public static enum Period { DAILY, WEEKLY, MONTHLY, QUARTERLY, SEMI_ANNUALLY, YEARLY; } /** * Defines what days are returned in historical requests (weekdays only, all days etc.). */ public static enum Days { /** * Include all weekdays (Monday to Friday) in the data set * <p> */ NON_TRADING_WEEKDAYS, //TODO: Confirm if other days are returned in some countries (Russia, Korea, Taiwan, Israel...) /** * Include all days of the calendar in the data set returned */ ALL_CALENDAR_DAYS, /** * Include only active days (days where the instrument and field pair updated) in the data set returned */ ACTIVE_DAYS_ONLY; } /** * Defines what values are returned when a field has no value on a specific date. */ public static enum Fill { /** * Search back and retrieve the previous value available for this security field pair. The search back period is * up to one month. */ PREVIOUS_VALUE, /** * Returns blank for the "value" value within the data element for this field. */ NIL_VALUE; } }