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 __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__)
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.")
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.")