Ejemplo n.º 1
0
    def _data_array_to_dataframe(self, prices_data_array: QFDataArray,
                                 frequency: Frequency):
        """
        Converts a QFDataArray into a DataFrame by removing the "Price Field" axis.

        Every index (e.g. 15:00) denotes the close price of the time range beginning at this time (15:00 - 15:01)
        The only exception is the time range 1 minute before market open (e.g. 9:29 - 9:30 if market opens 9:30). The
        price for this time range, denotes the OPEN price of 9:30 - 9:31.
        """
        original_dates = list(prices_data_array.dates.to_index())
        dates = prices_data_array.resample(dates='1D').first().dates.to_index()
        market_open_datetimes = [
            price_datetime + MarketOpenEvent.trigger_time()
            for price_datetime in dates if price_datetime +
            MarketOpenEvent.trigger_time() in original_dates
        ]
        shifted_open_datetimes = [
            price_datetime - frequency.time_delta()
            for price_datetime in market_open_datetimes
        ]

        new_dates = list(set(original_dates + shifted_open_datetimes))
        new_dates = sorted(new_dates)
        prices_df = PricesDataFrame(index=new_dates,
                                    columns=prices_data_array.tickers)

        prices_df.loc[shifted_open_datetimes, :] = \
            prices_data_array.loc[market_open_datetimes, :, PriceField.Open].values
        prices_df.loc[original_dates, :] = prices_data_array.loc[
            original_dates, :, PriceField.Close].values

        return prices_df
Ejemplo n.º 2
0
    def get_current_bar(self, tickers: Union[Ticker, Sequence[Ticker]], frequency: Frequency = None) \
            -> Union[QFSeries, QFDataFrame]:
        """
        Gets the current bar(s) for given Ticker(s). If the bar is not available yet, None is returned.

        If the request for single Ticker was made, then the result is a QFSeries indexed with PriceFields (OHLCV).
        If the request for multiple Tickers was made, then the result has Tickers as an index and PriceFields
        as columns.

        In case of N minutes frequency, the current bar can be returned in the time between (inclusive) N minutes
        after MarketOpenEvent and the MarketCloseEvent).

        E.g. for 1 minute frequency, at 13:00 (if the market opens before 13:00), the 12:59 - 13:00 bar will be
        returned. In case of 15 minutes frequency, when the market opened less then 15 minutes ago, Nones will be
        returned. If current time ("now") contains non-zero seconds or microseconds, Nones will be returned.

        Parameters
        -----------
        tickers: Ticker, Sequence[Ticker]
            tickers of the securities which prices should be downloaded
        frequency: Frequency
            frequency of the data

        Returns
        -------
        QFSeries, QFDataFrame
            current bar

        """
        if not tickers:
            return QFSeries()

        frequency = frequency or self.fixed_data_provider_frequency or Frequency.MIN_1

        tickers, was_single_ticker_provided = convert_to_list(tickers, Ticker)

        current_datetime = self.timer.now()

        start_date = current_datetime - frequency.time_delta()
        prices_data_array = self.get_price(tickers=tickers,
                                           fields=PriceField.ohlcv(),
                                           start_date=start_date,
                                           end_date=current_datetime,
                                           frequency=frequency)
        try:
            last_available_bars = cast_data_array_to_proper_type(
                prices_data_array.loc[start_date])
        except KeyError:
            return QFDataFrame(index=tickers, columns=PriceField.ohlcv())

        if was_single_ticker_provided:
            last_available_bars = last_available_bars.iloc[0, :]

        return last_available_bars
Ejemplo n.º 3
0
    def get_history(self, tickers: Union[Ticker, Sequence[Ticker]],
                    fields: Union[str, Sequence[str]],
                    start_date: datetime, end_date: datetime = None, frequency: Frequency = Frequency.DAILY, **kwargs
                    ) -> Union[QFSeries, QFDataFrame, QFDataArray]:

        # Verify whether the passed frequency parameter is correct and can be used with the preset data
        assert frequency == self._frequency, "Currently, for the get history does not support data sampling"

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

        if frequency > Frequency.DAILY:
            # In case of high frequency - the data array should not include the end_date. The data range is
            # labeled with the beginning index time and excludes the end of the time range, therefore a new
            # end date is computed.
            end_date = end_date - Frequency.MIN_1.time_delta()

        # In order to be able to return data for FutureTickers create a mapping between tickers and corresponding
        # specific tickers (in case of non FutureTickers it will be an identity mapping)
        tickers, got_single_ticker = convert_to_list(tickers, Ticker)
        tickers_mapping = {
            (t.get_current_specific_ticker() if isinstance(t, FutureTicker) else t): t for t in tickers
        }
        specific_tickers = list(tickers_mapping.keys())

        fields, got_single_field = convert_to_list(fields, str)
        got_single_date = start_date is not None and (
            (start_date == end_date) if frequency <= Frequency.DAILY else
            (start_date + frequency.time_delta() >= end_date)
        )

        self._check_if_cached_data_available(specific_tickers, fields, start_date, end_date)

        data_array = self._data_bundle.loc[start_date:end_date, specific_tickers, fields]

        normalized_result = normalize_data_array(data_array, specific_tickers, fields, got_single_date,
                                                 got_single_ticker,
                                                 got_single_field, use_prices_types=False)

        # Map the specific tickers onto the tickers given by the tickers_mapping array
        if isinstance(normalized_result, QFDataArray):
            normalized_result = normalized_result.assign_coords(
                tickers=[tickers_mapping[t] for t in normalized_result.tickers.values])
        elif isinstance(normalized_result, PricesDataFrame):
            normalized_result = normalized_result.rename(columns=tickers_mapping)
        elif isinstance(normalized_result, PricesSeries):
            # Name of the PricesSeries can only contain strings
            ticker = tickers[0]
            normalized_result = normalized_result.rename(ticker.name)

        return normalized_result
Ejemplo n.º 4
0
    def get_current_price(
            self,
            tickers: Union[Ticker, Sequence[Ticker]],
            frequency: Frequency = None) -> Union[float, QFSeries]:
        """
        Works just like get_last_available_price() but it can return NaNs if data is not available at the current
        moment (e.g. it returns NaN on a non-trading day).

        The frequency parameter is always casted into 1 minute frequency, to represent the most recent price.

        If the frequency parameter is an intraday frequency, the CLOSE price of the currently available bar will be
        returned.
        E.g. for 1 minute frequency, at 13:00 (if the market opens before 13:00), the CLOSE price of the
        12:59 - 13:00 bar will be returned.
        If "now" contains non-zero seconds or microseconds, None will be returned.

        Parameters
        -----------
        tickers: Ticker, Sequence[Ticker]
            tickers of the securities which prices should be downloaded
        frequency: Frequency
            frequency of the data

        Returns
        -------
        float, QFSeries
            current_prices series where:
            - current_prices.name contains a date of current prices,
            - current_prices.index contains tickers
            - current_prices.data contains latest available prices for given tickers
        """
        frequency = frequency or self.fixed_data_provider_frequency or Frequency.MIN_1

        if frequency <= Frequency.DAILY:
            raise ValueError(
                "The Intraday Data Handler can be used only with the Intraday Frequency"
            )

        tickers, was_single_ticker_provided = convert_to_list(tickers, Ticker)
        # if an empty tickers list was supplied then return an empty result
        if not tickers:
            return QFSeries()

        current_datetime = self.timer.now()

        # Check if the current time is at the market open, if so - take the Open price of the time range, starting
        # at current datetime
        if current_datetime + MarketOpenEvent.trigger_time(
        ) == current_datetime:
            time_range_start = current_datetime
            field = PriceField.Open
        else:
            time_range_start = current_datetime - frequency.time_delta()
            field = PriceField.Close

        prices_data_array = self.data_provider.get_price(
            tickers, field, time_range_start,
            time_range_start + frequency.time_delta(), frequency)
        try:
            # Below, the loc[time_range_start] is used instead of iloc[0], in order to return the price exactly from the
            # time_range_start, and not from the range between time_range_start and time_range_start +
            # frequency.time_delta()
            prices_series = prices_data_array.loc[time_range_start]
        except KeyError:
            prices_series = QFSeries(index=tickers)

        prices_series.name = "Current asset prices"

        prices_series = cast_series(prices_series, QFSeries)
        if was_single_ticker_provided:
            return prices_series[0]
        else:
            return prices_series
Ejemplo n.º 5
0
    def get_last_available_price(
            self,
            tickers: Union[Ticker, Sequence[Ticker]],
            frequency: Frequency = None) -> Union[float, QFSeries]:
        """
        Gets the latest available price for given assets, even if the full bar is not yet available.

        The frequency parameter is always casted into 1 minute frequency, to represent the most recent price.

        It returns the CLOSE price of the last available bar. If "now" is after the market OPEN, and before the market
        CLOSE, the last available price is equal to the current price (CLOSE price of the bar, which right bound is
        equal to "now"). If the market did not open yet, the last available CLOSE price will be returned.
        Non-zero seconds or microseconds values are omitted (e.g. 13:40:01 is always treated as 13:40:00).

        Parameters
        -----------
        tickers: Ticker, Sequence[Ticker]
            tickers of the securities which prices should be downloaded
        frequency: Frequency
            frequency of the data

        Returns
        -------
        float, QFSeries
            last_prices series where:
            - last_prices.name contains a date of current prices,
            - last_prices.index contains tickers
            - last_prices.data contains latest available prices for given tickers
        """
        frequency = frequency or self.fixed_data_provider_frequency or Frequency.MIN_1

        if frequency <= Frequency.DAILY:
            raise ValueError(
                "The Intraday Data Handler can be used only with the Intraday Frequency"
            )

        tickers, was_single_ticker_provided = convert_to_list(tickers, Ticker)

        # if an empty tickers list was supplied then return an empty result
        if not tickers:
            return QFSeries()

        current_datetime = self.timer.now()

        # If the current_datetime represents the time after Market Close and before Market Open, shift it to the
        # Market Close of the day before
        if current_datetime + MarketOpenEvent.trigger_time(
        ) > current_datetime:
            current_datetime = current_datetime - RelativeDelta(days=1)
            current_datetime = current_datetime + MarketCloseEvent.trigger_time(
            )
        elif current_datetime + MarketCloseEvent.trigger_time(
        ) < current_datetime:
            current_datetime = current_datetime + MarketCloseEvent.trigger_time(
            )

        # If the current_datetime represents Saturday or Sunday, shift it to last Friday
        if current_datetime.weekday() in (5, 6):
            current_datetime = current_datetime - RelativeDelta(weekday=4,
                                                                weeks=1)

        # The time range denotes the current_datetime +- time delta related to the given frequency. The current price is
        # represented as the close price of (time_range_start, current_datetime) range, labeled using the time_range_
        # start value in most of the cases.
        #
        # The only exception is the price at the market open - in this case we do not have the bar directly
        # leading up to market open time. Thus, the open price from the time range (current_datetime, time_range_end)
        # is used to denote the price.

        time_range_start = current_datetime - frequency.time_delta()
        time_range_end = current_datetime + frequency.time_delta()

        # The start date is used to download older data, in case if there is no price available currently and we are
        # interested in the last available one. Therefore, at first we look one hour in the past. If this amount of data
        # would not be sufficient, we would look up to a few days in the past.

        download_start_date = current_datetime - Frequency.MIN_60.time_delta()

        def download_prices(start_time, end_time, multiple_days=False):
            # Function which downloads prices for the given tickers. In case if the time range spans over multiple days
            # and thus contains at least one Market Open Event, combine the Open price for the first bar after the
            # market open with the Close prices for all other bars from this day.
            if multiple_days:
                price_fields = [PriceField.Open, PriceField.Close]
                prices = self.data_provider.get_price(tickers, price_fields,
                                                      start_time, end_time,
                                                      frequency)
                return self._data_array_to_dataframe(prices, frequency)
            else:
                return self.data_provider.get_price(tickers, PriceField.Close,
                                                    start_time, end_time,
                                                    frequency)

        # If the data contains the Market Open Price, merge the prices
        if download_start_date <= MarketOpenEvent.trigger_time(
        ) + time_range_end <= time_range_end:
            contains_market_open = True
        elif download_start_date <= MarketOpenEvent.trigger_time(
        ) + download_start_date <= time_range_end:
            contains_market_open = True
        elif (time_range_end - download_start_date) > timedelta(days=1):
            contains_market_open = True
        else:
            contains_market_open = False

        prices_data_array = download_prices(download_start_date,
                                            time_range_end,
                                            contains_market_open)

        # Access the price bar starting at time_range_start and ending at current_datetime
        try:
            prices_series = prices_data_array.asof(time_range_start)
            prices_series.name = "Last available asset prices"

            if prices_series.isnull().values.any():
                # If any of the values is null, download more data, using a longer period of time
                raise IndexError

        except IndexError:
            # Download data using a longer period of time. In case of Monday or Tuesday, we download data from last 4
            # days in order to handle situations, were there was no price on Monday or Friday (and during the weekend).
            # In all other cases, we download data from the last 2 days.
            number_of_days_to_go_back = 2 if download_start_date.weekday(
            ) not in (0, 1) else 4
            prices_data_array = download_prices(
                download_start_date -
                RelativeDelta(days=number_of_days_to_go_back),
                time_range_end,
                multiple_days=True)

            prices_series = prices_data_array.asof(time_range_start)
            prices_series.name = "Last available asset prices"

        prices_series = cast_series(prices_series, QFSeries)
        if was_single_ticker_provided:
            return prices_series[0]
        else:
            return prices_series