Skip to content

Commit fddf51a

Browse files
authored
Merge pull request #334 from luca-s/master
BUG: utils.compute_forward_returns can raise out of bounds exception
2 parents 4325560 + c885a12 commit fddf51a

File tree

2 files changed

+30
-11
lines changed

2 files changed

+30
-11
lines changed

alphalens/tests/test_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,27 @@ def test_compute_forward_returns(self):
8181

8282
assert_frame_equal(fp, expected)
8383

84+
def test_compute_forward_returns_index_out_of_bound(self):
85+
dr = date_range(start='2014-12-29', end='2015-1-3')
86+
prices = DataFrame(index=dr, columns=['A', 'B'],
87+
data=[[nan, nan], [nan, nan], [nan, nan],
88+
[1, 1], [1, 2], [2, 1]])
89+
90+
dr = date_range(start='2015-1-1', end='2015-1-3')
91+
factor = DataFrame(index=dr, columns=['A', 'B'],
92+
data=[[1, 1], [1, 2], [2, 1]])
93+
factor = factor.stack()
94+
95+
fp = compute_forward_returns(factor, prices, periods=[1, 2])
96+
97+
ix = MultiIndex.from_product([dr, ['A', 'B']],
98+
names=['date', 'asset'])
99+
expected = DataFrame(index=ix, columns=['1D', '2D'])
100+
expected['1D'] = [0., 1., 1., -0.5, nan, nan]
101+
expected['2D'] = [1., 0., nan, nan, nan, nan]
102+
103+
assert_frame_equal(fp, expected)
104+
84105
def test_compute_forward_returns_non_cum(self):
85106
dr = date_range(start='2015-1-1', end='2015-1-3')
86107
prices = DataFrame(index=dr, columns=['A', 'B'],

alphalens/utils.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -299,19 +299,17 @@ def compute_forward_returns(factor,
299299

300300
#
301301
# Find the period length, which will be the column name. We'll test
302-
# several entries in order to find out the correct period length as
303-
# there could be non-trading days which would make the computation
304-
# wrong if made only one test
302+
# several entries in order to find out the most likely period length
303+
# (in case the user passed inconsinstent data)
305304
#
306-
entries_to_test = min(
307-
30,
308-
len(forward_returns.index),
309-
len(prices.index) - period
310-
)
311-
312305
days_diffs = []
313-
for i in range(entries_to_test):
306+
for i in range(30):
307+
if i >= len(forward_returns.index):
308+
break
314309
p_idx = prices.index.get_loc(forward_returns.index[i])
310+
if p_idx is None or p_idx < 0 or (
311+
p_idx + period) >= len(prices.index):
312+
continue
315313
start = prices.index[p_idx]
316314
end = prices.index[p_idx + period]
317315
period_len = diff_custom_calendar_timedeltas(start, end, freq)
@@ -619,7 +617,7 @@ def get_clean_factor(factor,
619617
print("Dropped %.1f%% entries from factor data: %.1f%% in forward "
620618
"returns computation and %.1f%% in binning phase "
621619
"(set max_loss=0 to see potentially suppressed Exceptions)." %
622-
(tot_loss * 100, fwdret_loss * 100, bin_loss * 100))
620+
(tot_loss * 100, fwdret_loss * 100, bin_loss * 100))
623621

624622
if tot_loss > max_loss:
625623
message = ("max_loss (%.1f%%) exceeded %.1f%%, consider increasing it."

0 commit comments

Comments
 (0)