Example #1
0
    def __init__(self, settings: Settings):
        self.settings = settings

        self.host = settings.bloomberg.host
        self.port = settings.bloomberg.port
        self.logger = qf_logger.getChild(self.__class__.__name__)

        if is_blpapi_installed:
            session_options = blpapi.SessionOptions()
            session_options.setServerHost(self.host)
            session_options.setServerPort(self.port)
            session_options.setAutoRestartOnDisconnection(True)
            self.session = blpapi.Session(session_options)

            self._historical_data_provider = HistoricalDataProvider(
                self.session)
            self._reference_data_provider = ReferenceDataProvider(self.session)
            self._tabular_data_provider = TabularDataProvider(self.session)
            self._futures_data_provider = FuturesDataProvider(self.session)
        else:
            self.session = None
            self._historical_data_provider = None
            self._reference_data_provider = None
            self._tabular_data_provider = None
            self._futures_data_provider = None

            self.logger.warning(
                "Couldn't import the Bloomberg API. Check if the necessary dependencies are installed."
            )

        self.connected = False
Example #2
0
    def __init__(self, settings: Settings):
        self.settings = settings
        session_options = blpapi.SessionOptions()
        self.host = settings.bloomberg.host
        self.port = settings.bloomberg.port

        session_options.setServerHost(self.host)
        session_options.setServerPort(self.port)
        session_options.setAutoRestartOnDisconnection(True)
        self.session = blpapi.Session(session_options)

        self._historical_data_provider = HistoricalDataProvider(self.session)
        self._reference_data_provider = ReferenceDataProvider(self.session)
        self._tabular_data_provider = TabularDataProvider(self.session)
        self.connected = False
        self.logger = qf_logger.getChild(self.__class__.__name__)
Example #3
0
class BloombergDataProvider(AbstractPriceDataProvider,
                            TickersUniverseProvider):
    """
    Data Provider which provides financial data from Bloomberg.
    """
    def __init__(self, settings: Settings):
        self.settings = settings

        self.host = settings.bloomberg.host
        self.port = settings.bloomberg.port
        self.logger = qf_logger.getChild(self.__class__.__name__)

        if is_blpapi_installed:
            session_options = blpapi.SessionOptions()
            session_options.setServerHost(self.host)
            session_options.setServerPort(self.port)
            session_options.setAutoRestartOnDisconnection(True)
            self.session = blpapi.Session(session_options)

            self._historical_data_provider = HistoricalDataProvider(
                self.session)
            self._reference_data_provider = ReferenceDataProvider(self.session)
            self._tabular_data_provider = TabularDataProvider(self.session)
            self._futures_data_provider = FuturesDataProvider(self.session)
        else:
            self.session = None
            self._historical_data_provider = None
            self._reference_data_provider = None
            self._tabular_data_provider = None
            self._futures_data_provider = None

            self.logger.warning(
                "Couldn't import the Bloomberg API. Check if the necessary dependencies are installed."
            )

        self.connected = False

    def connect(self):
        """
        Connects to Bloomberg data service and holds a connection.
        Connecting might take about 10-15 seconds
        """
        self.connected = False
        if not is_blpapi_installed:
            self.logger.error(
                "Couldn't import the Bloomberg API. Check if the necessary dependencies are installed."
            )
            return

        if not self.session.start():
            self.logger.error("Failed to start session with host: " +
                              str(self.host) + " on port: " + str(self.port))
            return

        if not self.session.openService(REF_DATA_SERVICE_URI):
            self.logger.error("Failed to open service: " +
                              REF_DATA_SERVICE_URI)
            return

        self.connected = True

    def _get_futures_chain_dict(
        self, tickers: Union[BloombergFutureTicker,
                             Sequence[BloombergFutureTicker]],
        expiration_date_fields: Union[str, Sequence[str]]
    ) -> Dict[BloombergFutureTicker, QFDataFrame]:
        """
        Returns tickers of futures contracts, which belong to the same futures contract chain as the provided ticker
        (tickers), along with their expiration dates.

        Parameters
        ----------
        tickers: BloombergFutureTicker, Sequence[BloombergFutureTicker]
            future tickers for which future chains should be retrieved
        expiration_date_fields: ExpirationDateField, Sequence[ExpirationDateField]
            field that should be downloaded as the expiration date field

        Returns
        -------
        Dict[BloombergFutureTicker, QFDataFrame]
            Dictionary mapping each BloombergFutureTicker to a QFDataFrame, containing specific future
            contracts tickers (BloombergTickers), indexed by these tickers

        Raises
        -------
        BloombergError
            When couldn't get the data from Bloomberg Service
        """
        self._connect_if_needed()
        self._assert_is_connected()

        tickers, got_single_ticker = convert_to_list(tickers,
                                                     BloombergFutureTicker)
        expiration_date_fields, _ = convert_to_list(expiration_date_fields,
                                                    ExpirationDateField)

        # Create a dictionary, which is mapping BloombergFutureTickers to lists of tickers related to specific future
        # contracts belonging to the chain, e.g. it will map Cotton Bloomberg future ticker into:
        # [BloombergTicker("CTH7 Comdty"), BloombergTicker("CTK7 Comdty"), BloombergTicker("CTN7 Comdty"),
        # BloombergTicker("CTV7 Comdty"), BloombergTicker("CTZ7 Comdty") ... ]
        future_ticker_to_chain_tickers_list = self._futures_data_provider.get_list_of_tickers_in_the_future_chain(
            tickers
        )  # type: Dict[BloombergFutureTicker, List[BloombergTicker]]

        def get_futures_expiration_dates(specific_tickers: Union[BloombergTicker, Sequence[BloombergTicker]]) -> \
                Union[QFSeries, QFDataFrame]:
            """
            For a ticker (tickers) it returns a QFDataFrame consisting of the expiration dates fields of Future
            Contracts, that are defined by the given tickers.

            Parameters
            ----------
            specific_tickers
                tickers for which the expiration dates of future contracts should be retrieved

            Returns
            -------
            QFDataFrame
                Container containing all the dates, indexed by tickers
            """
            exp_dates = self.get_current_values(
                specific_tickers, expiration_date_fields)  # type: QFDataFrame

            # Drop these futures, for which no expiration date was found and cast all string dates into datetime
            return exp_dates.dropna(how="all").astype('datetime64[ns]')

        all_specific_tickers = [
            ticker for specific_tickers_list in
            future_ticker_to_chain_tickers_list.values()
            for ticker in specific_tickers_list
        ]
        futures_expiration_dates = get_futures_expiration_dates(
            all_specific_tickers)

        def specific_futures_index(future_ticker) -> pd.Index:
            """
            Returns an Index of specific tickers for the given future ticker, which appeared in the futures
            expiration dates dataframe / series.
            """
            specific_tickers_list = future_ticker_to_chain_tickers_list[
                future_ticker]
            return futures_expiration_dates.index.intersection(
                specific_tickers_list)

        ticker_to_future_expiration_dates = {
            future_ticker:
            futures_expiration_dates.loc[specific_futures_index(future_ticker)]
            for future_ticker in tickers
        }

        return ticker_to_future_expiration_dates

    def get_current_values(
        self, tickers: Union[BloombergTicker, Sequence[BloombergTicker]],
        fields: Union[str, Sequence[str]]
    ) -> Union[None, float, QFSeries, QFDataFrame]:
        """
        Gets the current values of fields for given tickers.

        Parameters
        ----------
        tickers: BloombergTicker, Sequence[BloombergTicker]
            tickers for securities which should be retrieved
        fields: str, Sequence[str]
            fields of securities which should be retrieved

        Returns
        -------
        QFDataFrame/QFSeries
            Either QFDataFrame with 2 dimensions: ticker, field or QFSeries with 1 dimensions: ticker of field
            (depending if many tickers or fields was provided) is returned.

        Raises
        -------
        BloombergError
            When couldn't get the data from Bloomberg Service
        """
        self._connect_if_needed()
        self._assert_is_connected()

        tickers, got_single_ticker = convert_to_list(tickers, BloombergTicker)
        fields, got_single_field = convert_to_list(fields, (PriceField, str))

        data_frame = self._reference_data_provider.get(tickers, fields)

        # to keep the order of tickers and fields we reindex the data frame
        data_frame = data_frame.reindex(index=tickers, columns=fields)

        # squeeze unused dimensions
        tickers_indices = 0 if got_single_ticker else slice(None)
        fields_indices = 0 if got_single_field else slice(None)
        squeezed_result = data_frame.iloc[tickers_indices, fields_indices]

        casted_result = cast_dataframe_to_proper_type(squeezed_result) if tickers_indices != 0 or fields_indices != 0 \
            else squeezed_result

        return casted_result

    def get_history(self, tickers: Union[BloombergTicker, Sequence[BloombergTicker]], fields: Union[str, Sequence[str]],
                    start_date: datetime, end_date: datetime = None, frequency: Frequency = Frequency.DAILY,
                    currency: str = None, override_name: str = None, override_value: str = None) \
            -> Union[QFSeries, QFDataFrame, QFDataArray]:
        """
        Gets historical data from Bloomberg from the (start_date - end_date) time range. In case of frequency, which is
        higher than daily frequency (intraday data), the data is indexed by the start_date. E.g. Time range: 8:00 - 8:01,
        frequency: 1 minute - indexed with the 8:00 timestamp

        Parameters
        ----------
        tickers: Ticker, Sequence[Ticker]
            tickers for securities which should be retrieved
        fields: None, str, Sequence[str]
            fields of securities which should be retrieved. If None, all available fields will be returned
            (only supported by few DataProviders)
        start_date: datetime
            date representing the beginning of historical period from which data should be retrieved
        end_date: datetime
            date representing the end of historical period from which data should be retrieved;
            if no end_date was provided, by default the current date will be used
        frequency: Frequency
            frequency of the data
        currency: str
        override_name: str
        override_value: str

        Returns
        -------
        QFSeries, QFDataFrame, QFDataArray
            If possible the result will be squeezed, so that instead of returning QFDataArray, data of lower
            dimensionality will be returned. The results will be either an QFDataArray (with 3 dimensions: date, ticker,
            field), a QFDataFrame (with 2 dimensions: date, ticker or field; it is also possible to get 2 dimensions
            ticker and field if single date was provided) or QFSeries (with 1 dimensions: date).
            If no data is available in the database or an non existing ticker was provided an empty structure
            (QFSeries, QFDataFrame or QFDataArray) will be returned returned.
        """
        if fields is None:
            raise ValueError("Fields being None is not supported by {}".format(
                self.__class__.__name__))

        self._connect_if_needed()
        self._assert_is_connected()

        if end_date is None:
            end_date = datetime.now()

        got_single_date = start_date is not None and (
            (start_date.date() == end_date.date())
            if frequency == Frequency.DAILY else False)

        tickers, got_single_ticker = convert_to_list(tickers, BloombergTicker)
        fields, got_single_field = convert_to_list(fields, (PriceField, str))

        def current_ticker(t: BloombergTicker):
            return t.get_current_specific_ticker() if isinstance(
                t, BloombergFutureTicker) else t

        tickers_mapping = {current_ticker(t): t for t in tickers}
        data_array = self._historical_data_provider.get(
            tickers, fields, start_date, end_date, frequency, currency,
            override_name, override_value)
        data_array = data_array.assign_coords(tickers=[
            tickers_mapping.get(t, t) for t in data_array.tickers.values
        ])

        normalized_result = normalize_data_array(data_array, tickers, fields,
                                                 got_single_date,
                                                 got_single_ticker,
                                                 got_single_field)

        return normalized_result

    def supported_ticker_types(self):
        return {BloombergTicker, BloombergFutureTicker}

    def expiration_date_field_str_map(
            self,
            ticker: BloombergTicker = None) -> Dict[ExpirationDateField, str]:
        expiration_date_field_dict = {
            ExpirationDateField.FirstNotice: "FUT_NOTICE_FIRST",
            ExpirationDateField.LastTradeableDate: "LAST_TRADEABLE_DT"
        }
        return expiration_date_field_dict

    def price_field_to_str_map(self,
                               ticker: BloombergTicker = None
                               ) -> Dict[PriceField, str]:
        price_field_dict = {
            PriceField.Open: 'PX_OPEN',
            PriceField.High: 'PX_HIGH',
            PriceField.Low: 'PX_LOW',
            PriceField.Close: 'PX_LAST',
            PriceField.Volume: 'PX_VOLUME'
        }
        return price_field_dict

    def get_tickers_universe(self,
                             universe_ticker: BloombergTicker,
                             date: datetime = None) -> List[BloombergTicker]:
        if date and date.date() != datetime.today().date():
            raise ValueError(
                "BloombergDataProvider does not provide historical tickers_universe data"
            )
        field = 'INDX_MEMBERS'
        ticker_data = self.get_tabular_data(universe_ticker, field)
        tickers = [
            BloombergTicker(fields['Member Ticker and Exchange Code'] +
                            " Equity") for fields in ticker_data
        ]
        return tickers

    def get_unique_tickers(self, universe_ticker: Ticker) -> List[Ticker]:
        raise ValueError(
            "BloombergDataProvider does not provide historical tickers_universe data"
        )

    def get_tabular_data(
            self,
            ticker: BloombergTicker,
            field: str,
            override_names: Optional[Union[str, Sequence[str]]] = None,
            override_values: Optional[Union[str,
                                            Sequence[str]]] = None) -> List:
        """
        Provides current tabular data from Bloomberg.

        Was tested on 'INDX_MEMBERS' and 'MERGERS_AND_ACQUISITIONS' requests. There is no guarantee that
        all other request will be handled, as returned data structures might vary.

        Parameters
        -----------
        ticker: BloombergTicker
            ticker for security that should be retrieved
        field: str
            field of security that should be retrieved
        override_names: str
        override_values: str

        Returns
        -------
        List
            tabular data for the given ticker and field
        """
        if field is None:
            raise ValueError("Field being None is not supported by {}".format(
                self.__class__.__name__))

        self._connect_if_needed()
        self._assert_is_connected()

        if override_names is not None:
            override_names, _ = convert_to_list(override_names, str)
        if override_values is not None:
            override_values, _ = convert_to_list(override_values, str)

        tickers, got_single_ticker = convert_to_list(ticker, BloombergTicker)
        fields, got_single_field = convert_to_list(field, (PriceField, str))

        tickers_str = [t.as_string() for t in tickers]
        result = self._tabular_data_provider.get(tickers_str, fields,
                                                 override_names,
                                                 override_values)

        return result

    def _connect_if_needed(self):
        if not self.connected:
            self.connect()

    def _assert_is_connected(self):
        if not self.connected:
            raise BloombergError("Connection to Bloomberg was not successful.")
Example #4
0
class BloombergDataProvider(AbstractPriceDataProvider, TickersUniverseProvider):
    """
    Provides financial data from Bloomberg.
    """

    def __init__(self, settings: Settings):
        self.settings = settings
        session_options = blpapi.SessionOptions()
        self.host = settings.bloomberg.host
        self.port = settings.bloomberg.port

        session_options.setServerHost(self.host)
        session_options.setServerPort(self.port)
        session_options.setAutoRestartOnDisconnection(True)
        self.session = blpapi.Session(session_options)

        self._historical_data_provider = HistoricalDataProvider(self.session)
        self._reference_data_provider = ReferenceDataProvider(self.session)
        self._tabular_data_provider = TabularDataProvider(self.session)
        self.connected = False
        self.logger = qf_logger.getChild(self.__class__.__name__)

    def connect(self):
        """
        Connects to Bloomberg data service and holds a connection.
        Connecting might take about 10-15 seconds
        """
        self.connected = False
        if not self.session.start():
            self.logger.warning("Failed to start session with host: " + str(self.host) + " on port: " + str(self.port))
            return

        if not self.session.openService(REF_DATA_SERVICE_URI):
            self.logger.warning("Failed to open service: " + REF_DATA_SERVICE_URI)
            return

        self.connected = True

    def get_current_values(
            self, tickers: Union[BloombergTicker, Sequence[BloombergTicker]], fields: Union[str, Sequence[str]]) \
            -> Union[None, float, QFSeries, QFDataFrame]:
        """
        Gets the current values of fields for given tickers.

        Parameters
        ----------
        tickers
            tickers for securities which should be retrieved
        fields
            fields of securities which should be retrieved

        Returns
        -------
        historical_data: QFDataFrame/QFSeries

            QFDataFrame  with 2 dimensions: ticker, field
            QFSeries     with 1 dimensions: ticker of field (depending if many tickers or fields was provided)

        Raises
        -------
        BloombergError
            When couldn't get the data from Bloomberg Service
        """
        self._connect_if_needed()
        self._assert_is_connected()

        tickers, got_single_ticker = convert_to_list(tickers, BloombergTicker)
        fields, got_single_field = convert_to_list(fields, (PriceField, str))

        tickers_str = tickers_as_strings(tickers)
        data_frame = self._reference_data_provider.get(tickers_str, fields)

        # to keep the order of tickers and fields we reindex the data frame
        data_frame = data_frame.reindex(index=tickers, columns=fields)

        # squeeze unused dimensions
        tickers_indices = 0 if got_single_ticker else slice(None)
        fields_indices = 0 if got_single_field else slice(None)
        squeezed_result = data_frame.iloc[tickers_indices, fields_indices]

        casted_result = cast_dataframe_to_proper_type(squeezed_result)

        return casted_result

    def get_history(
            self, tickers: Union[BloombergTicker, Sequence[BloombergTicker]], fields: Union[str, Sequence[str]],
            start_date: datetime, end_date: datetime = None, frequency: Frequency = Frequency.DAILY,
            currency: str = None,
            override_name: str = None, override_value: str = None) \
            -> Union[QFSeries, QFDataFrame, QFDataArray]:
        if fields is None:
            raise ValueError("Fields being None is not supported by {}".format(self.__class__.__name__))

        self._connect_if_needed()
        self._assert_is_connected()

        got_single_date = start_date is not None and (start_date == end_date)

        tickers, got_single_ticker = convert_to_list(tickers, BloombergTicker)
        fields, got_single_field = convert_to_list(fields, (PriceField, str))

        if end_date is None:
            end_date = datetime.now()

        tickers_str = tickers_as_strings(tickers)
        data_array = self._historical_data_provider.get(
            tickers_str, fields, start_date, end_date, frequency, currency, override_name, override_value)

        normalized_result = normalize_data_array(
            data_array, tickers, fields, got_single_date, got_single_ticker, got_single_field)

        return normalized_result

    def supported_ticker_types(self):
        return {BloombergTicker}

    def price_field_to_str_map(self, ticker: BloombergTicker = None) -> Dict[PriceField, str]:
        price_field_dict = {
            PriceField.Open: 'PX_OPEN',
            PriceField.High: 'PX_HIGH',
            PriceField.Low: 'PX_LOW',
            PriceField.Close: 'PX_LAST',
            PriceField.Volume: 'PX_VOLUME'
        }
        return price_field_dict

    def get_tickers_universe(self, universe_ticker: BloombergTicker, date: datetime = None) -> List[BloombergTicker]:
        if date and date.date() != datetime.today().date():
            raise ValueError("BloombergDataProvider does not provide historical tickers_universe data")
        field = 'INDX_MEMBERS'
        ticker_data = self.get_tabular_data(universe_ticker, field)
        tickers = [BloombergTicker(fields['Member Ticker and Exchange Code'] + " Equity") for fields in ticker_data]
        return tickers

    def get_tabular_data(self, ticker: BloombergTicker, field: str) -> List:
        """
        Provides current tabular data from Bloomberg.

        Was tested on 'INDX_MEMBERS' and 'MERGERS_AND_ACQUISITIONS' requests. There is no guarantee that
        all other request will be handled, as returned data structures might vary.
        """
        if field is None:
            raise ValueError("Field being None is not supported by {}".format(self.__class__.__name__))

        self._connect_if_needed()
        self._assert_is_connected()

        tickers, got_single_ticker = convert_to_list(ticker, BloombergTicker)
        fields, got_single_field = convert_to_list(field, (PriceField, str))

        tickers_str = tickers_as_strings(tickers)
        result = self._tabular_data_provider.get(tickers_str, fields)

        return result

    def _connect_if_needed(self):
        if not self.connected:
            self.connect()

    def _assert_is_connected(self):
        if not self.connected:
            raise BloombergError("Connection to Bloomberg was not successful.")