Skip to content

Commit

Permalink
Merge pull request #2644 from quantopian/ffill-past-fx-end
Browse files Browse the repository at this point in the history
MAINT: Forward-fill fx rates past file end.
  • Loading branch information
Scott Sanderson committed Feb 4, 2020
2 parents 7c654ef + 2546708 commit 8195f3a
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 25 deletions.
21 changes: 13 additions & 8 deletions tests/data/test_fx.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_scalar_lookup(self):
bases = self.FX_RATES_CURRENCIES + [None]
dates = pd.date_range(
self.FX_RATES_START_DATE - pd.Timedelta('1 day'),
self.FX_RATES_END_DATE,
self.FX_RATES_END_DATE + pd.Timedelta('1 day'),
)
cases = itertools.product(rates, quotes, bases, dates)

Expand Down Expand Up @@ -98,7 +98,7 @@ def test_2d_lookup(self):

dates = pd.date_range(
self.FX_RATES_START_DATE - pd.Timedelta('2 days'),
self.FX_RATES_END_DATE
self.FX_RATES_END_DATE + pd.Timedelta('2 days'),
)
rates = self.FX_RATES_RATE_NAMES + [DEFAULT_FX_RATE]
possible_quotes = self.FX_RATES_CURRENCIES
Expand Down Expand Up @@ -131,7 +131,7 @@ def test_columnar_lookup(self):

dates = pd.date_range(
self.FX_RATES_START_DATE - pd.Timedelta('2 days'),
self.FX_RATES_END_DATE,
self.FX_RATES_END_DATE + pd.Timedelta('2 days'),
)
rates = self.FX_RATES_RATE_NAMES + [DEFAULT_FX_RATE]
possible_quotes = self.FX_RATES_CURRENCIES
Expand Down Expand Up @@ -204,6 +204,7 @@ def test_read_before_start_date(self):
quote = 'USD'
bases = np.array(['CAD'], dtype=object)
dts = pd.DatetimeIndex([bad_date])

result = self.reader.get_rates(rate, quote, bases, dts)
assert_equal(result.shape, (1, 1))
assert_equal(np.nan, result[0, 0])
Expand All @@ -221,11 +222,15 @@ def test_read_after_end_date(self):
bases = np.array(['CAD'], dtype=object)
dts = pd.DatetimeIndex([bad_date])

with self.assertRaises(ValueError):
self.reader.get_rates(rate, quote, bases, dts)

with self.assertRaises(ValueError):
self.reader.get_rates_columnar(rate, quote, bases, dts)
result = self.reader.get_rates(rate, quote, bases, dts)
assert_equal(result.shape, (1, 1))
expected = self.get_expected_fx_rate_scalar(
rate,
quote,
'CAD',
self.FX_RATES_END_DATE,
)
assert_equal(expected, result[0, 0])

def test_read_unknown_base(self):
for rate in self.FX_RATES_RATE_NAMES:
Expand Down
2 changes: 1 addition & 1 deletion zipline/data/fx/hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def get_rates(self, rate, quote, bases, dts):
if rate == DEFAULT_FX_RATE:
rate = self._default_rate

check_dts(self.dts, dts)
check_dts(dts)

row_ixs = self.dts.searchsorted(dts, side='right') - 1
col_ixs = self.currencies.get_indexer(bases)
Expand Down
2 changes: 1 addition & 1 deletion zipline/data/fx/in_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get_rates(self, rate, quote, bases, dts):

df = self._data[rate][quote]

check_dts(df.index, dts)
check_dts(dts)

# Get raw values out of the frame.
#
Expand Down
15 changes: 2 additions & 13 deletions zipline/data/fx/utils.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import numpy as np


def check_dts(stored_dts, requested_dts):
def check_dts(requested_dts):
"""Validate that ``requested_dts`` are valid for querying from an FX reader.
"""
Validate that ``requested_dts`` are valid for querying from an FX reader
that has data for ``stored_dts``.
"""
request_end = requested_dts[-1]
data_end = stored_dts[-1]

if not is_sorted_ascending(requested_dts):
raise ValueError("Requested fx rates with non-ascending dts.")

if request_end > data_end:
raise ValueError(
"Requested fx rates ending at {}, but data ends at {}"
.format(request_end, data_end)
)


def is_sorted_ascending(array):
return (np.maximum.accumulate(array) <= array).all()
2 changes: 0 additions & 2 deletions zipline/testing/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2209,8 +2209,6 @@ def get_expected_fx_rate_scalar(cls, rate, quote, base, dt):
col = cls.fx_rates[rate][quote][base]
if dt < col.index[0]:
return np.nan
elif dt > col.index[-1]:
raise ValueError("dt={} > max dt={}".format(dt, col.index[-1]))

# PERF: We call this function a lot in some suites, and get_loc is
# surprisingly expensive, so optimizing it has a meaningful impact on
Expand Down

0 comments on commit 8195f3a

Please sign in to comment.