Shift dates of a pipeline query back by `shift` days.

    load_adjusted_array is called with dates on which the user's algo
    will be shown data, which means we need to return the data that would
    be known at the start of each date.  This is often labeled with a
    previous date in the underlying data (e.g. at the start of today, we
    have the data as of yesterday). In this case, we can shift the query
    dates back to query the appropriate values.

    Parameters
    ----------
    dates : DatetimeIndex
        All known dates.
    start_date : pd.Timestamp
        Start date of the pipeline query.
    end_date : pd.Timestamp
        End date of the pipeline query.
    shift : int
        The number of days to shift back the query dates.

def shift_dates(dates, start_date, end_date, shift):
    """
    Shift dates of a pipeline query back by `shift` days.

    load_adjusted_array is called with dates on which the user's algo
    will be shown data, which means we need to return the data that would
    be known at the start of each date.  This is often labeled with a
    previous date in the underlying data (e.g. at the start of today, we
    have the data as of yesterday). In this case, we can shift the query
    dates back to query the appropriate values.

    Parameters
    ----------
    dates : DatetimeIndex
        All known dates.
    start_date : pd.Timestamp
        Start date of the pipeline query.
    end_date : pd.Timestamp
        End date of the pipeline query.
    shift : int
        The number of days to shift back the query dates.
    """
    try:
        start = dates.get_loc(start_date)
    except KeyError:
        if start_date < dates[0]:
            raise NoFurtherDataError(
                msg=(
                    "Pipeline Query requested data starting on {query_start}, "
                    "but first known date is {calendar_start}"
                ).format(
                    query_start=str(start_date),
                    calendar_start=str(dates[0]),
                )
            )
        else:
            raise ValueError("Query start %s not in calendar" % start_date)

    # Make sure that shifting doesn't push us out of the calendar.
    if start < shift:
        raise NoFurtherDataError(
            msg=(
                "Pipeline Query requested data from {shift}"
                " days before {query_start}, but first known date is only "
                "{start} days earlier."
            ).format(shift=shift, query_start=start_date, start=start),
        )

    try:
        end = dates.get_loc(end_date)
    except KeyError:
        if end_date > dates[-1]:
            raise NoFurtherDataError(
                msg=(
                    "Pipeline Query requesting data up to {query_end}, "
                    "but last known date is {calendar_end}"
                ).format(
                    query_end=end_date,
                    calendar_end=dates[-1],
                )
            )
        else:
            raise ValueError("Query end %s not in calendar" % end_date)
    return dates[start - shift], dates[end - shift]