@@ -65,20 +65,23 @@ def test_scalar_lookup(self):
6565 reader = self .reader
6666
6767 rates = self .FX_RATES_RATE_NAMES
68- currencies = self .FX_RATES_CURRENCIES
69- dates = pd .date_range (self .FX_RATES_START_DATE , self .FX_RATES_END_DATE )
70-
71- cases = itertools .product (rates , currencies , currencies , dates )
68+ quotes = self .FX_RATES_CURRENCIES
69+ bases = self .FX_RATES_CURRENCIES + [None ]
70+ dates = pd .date_range (
71+ self .FX_RATES_START_DATE - pd .Timedelta ('1 day' ),
72+ self .FX_RATES_END_DATE ,
73+ )
74+ cases = itertools .product (rates , quotes , bases , dates )
7275
7376 for rate , quote , base , dt in cases :
7477 dts = pd .DatetimeIndex ([dt ], tz = 'UTC' )
75- bases = np .array ([base ])
78+ bases = np .array ([base ], dtype = object )
7679
7780 result = reader .get_rates (rate , quote , bases , dts )
7881 assert_equal (result .shape , (1 , 1 ))
7982
8083 result_scalar = result [0 , 0 ]
81- if quote == base :
84+ if dt >= self . FX_RATES_START_DATE and quote == base :
8285 assert_equal (result_scalar , 1.0 )
8386
8487 expected = self .get_expected_fx_rate_scalar (rate , quote , base , dt )
@@ -93,12 +96,16 @@ def test_scalar_lookup(self):
9396 def test_2d_lookup (self ):
9497 rand = np .random .RandomState (42 )
9598
96- dates = pd .date_range (self .FX_RATES_START_DATE , self .FX_RATES_END_DATE )
99+ dates = pd .date_range (
100+ self .FX_RATES_START_DATE - pd .Timedelta ('2 days' ),
101+ self .FX_RATES_END_DATE
102+ )
97103 rates = self .FX_RATES_RATE_NAMES + [DEFAULT_FX_RATE ]
98- currencies = self .FX_RATES_CURRENCIES
104+ possible_quotes = self .FX_RATES_CURRENCIES
105+ possible_bases = self .FX_RATES_CURRENCIES + [None ]
99106
100107 # For every combination of rate name and quote currency...
101- for rate , quote in itertools .product (rates , currencies ):
108+ for rate , quote in itertools .product (rates , possible_quotes ):
102109
103110 # Choose N random distinct days...
104111 for ndays in 1 , 2 , 7 , 20 :
@@ -107,7 +114,10 @@ def test_2d_lookup(self):
107114
108115 # Choose M random possibly-non-distinct currencies...
109116 for nbases in 1 , 2 , 10 , 200 :
110- bases = rand .choice (currencies , nbases , replace = True )
117+ bases = (
118+ rand .choice (possible_bases , nbases , replace = True )
119+ .astype (object )
120+ )
111121
112122 # ...And check that we get the expected result when querying
113123 # for those dates/currencies.
@@ -119,18 +129,25 @@ def test_2d_lookup(self):
119129 def test_columnar_lookup (self ):
120130 rand = np .random .RandomState (42 )
121131
122- dates = pd .date_range (self .FX_RATES_START_DATE , self .FX_RATES_END_DATE )
132+ dates = pd .date_range (
133+ self .FX_RATES_START_DATE - pd .Timedelta ('2 days' ),
134+ self .FX_RATES_END_DATE ,
135+ )
123136 rates = self .FX_RATES_RATE_NAMES + [DEFAULT_FX_RATE ]
124- currencies = self .FX_RATES_CURRENCIES
137+ possible_quotes = self .FX_RATES_CURRENCIES
138+ possible_bases = self .FX_RATES_CURRENCIES + [None ]
125139 reader = self .reader
126140
127141 # For every combination of rate name and quote currency...
128- for rate , quote in itertools .product (rates , currencies ):
142+ for rate , quote in itertools .product (rates , possible_quotes ):
129143 for N in 1 , 2 , 10 , 200 :
130144 # Choose N (date, base) pairs randomly with replacement.
131145 dts_raw = rand .choice (dates , N , replace = True )
132- dts = pd .DatetimeIndex (dts_raw , tz = 'utc' ).sort_values ()
133- bases = rand .choice (currencies , N , replace = True )
146+ dts = pd .DatetimeIndex (dts_raw , tz = 'utc' )
147+ bases = (
148+ rand .choice (possible_bases , N , replace = True )
149+ .astype (object )
150+ )
134151
135152 # ... And check that we get the expected result when querying
136153 # for those dates/currencies.
@@ -175,27 +192,50 @@ def test_load_everything(self):
175192 assert_equal (london_result , london_rates .values )
176193
177194 def test_read_before_start_date (self ):
195+ # Reads from before the start of our data should emit NaN. We do this
196+ # because, for some Pipeline loaders, it's hard to put a lower bound on
197+ # input asof dates, so we end up making queries for asof_dates that
198+ # might be before the start of FX data. When that happens, we want to
199+ # emit NaN, but we don't want to fail.
178200 for bad_date in (self .FX_RATES_START_DATE - pd .Timedelta ('1 day' ),
179201 self .FX_RATES_START_DATE - pd .Timedelta ('1000 days' )):
180202
181203 for rate in self .FX_RATES_RATE_NAMES :
182204 quote = 'USD'
183205 bases = np .array (['CAD' ], dtype = object )
184206 dts = pd .DatetimeIndex ([bad_date ])
185- with self .assertRaises (ValueError ):
186- self .reader .get_rates (rate , quote , bases , dts )
207+ result = self .reader .get_rates (rate , quote , bases , dts )
208+ assert_equal (result .shape , (1 , 1 ))
209+ assert_equal (np .nan , result [0 , 0 ])
187210
188211 def test_read_after_end_date (self ):
212+ # Reads from **after** the end of our data, on the other hand, should
213+ # fail. We can always upper bound the relevant asofs that we're
214+ # interested in, and having fx rates forward-fill past the end of data
215+ # is confusing and takes a while to debug.
189216 for bad_date in (self .FX_RATES_END_DATE + pd .Timedelta ('1 day' ),
190217 self .FX_RATES_END_DATE + pd .Timedelta ('1000 days' )):
191218
192219 for rate in self .FX_RATES_RATE_NAMES :
193220 quote = 'USD'
194221 bases = np .array (['CAD' ], dtype = object )
195222 dts = pd .DatetimeIndex ([bad_date ])
223+
196224 with self .assertRaises (ValueError ):
197225 self .reader .get_rates (rate , quote , bases , dts )
198226
227+ with self .assertRaises (ValueError ):
228+ self .reader .get_rates_columnar (rate , quote , bases , dts )
229+
230+ def test_read_unknown_base (self ):
231+ for rate in self .FX_RATES_RATE_NAMES :
232+ quote = 'USD'
233+ for unknown_base in 'XXX' , None :
234+ bases = np .array ([unknown_base ], dtype = object )
235+ dts = pd .DatetimeIndex ([self .FX_RATES_START_DATE ])
236+ result = self .reader .get_rates (rate , quote , bases , dts )[0 , 0 ]
237+ assert_equal (result , np .nan )
238+
199239
200240class InMemoryFXReaderTestCase (_FXReaderTestCase ):
201241
0 commit comments