Skip to content

Commit 30a5c4c

Browse files
eigenfootwiecki
authored andcommitted
DEP: Deprecate all data reading functionality via pandas-datareader (#97)
* DEP: Deprecate all functions using pandas-datareader * DOC: Update README with deprecation documentation * STY: Markdown style * STY: Markdown style again * REV: revert previous commit * STY: typo * STY: consistent naming convention * DEP: also deprecate any cacheing of data * DEP: forgot to deprecate additional funcs * REV: get_utc_timestamp should not be deprecated * ENH: add function to compute returns from prices * BUG: wrap import in try-except * MAINT: update deprecation warning * MAINT: move `simple_returns` func to `stats` module * MAINT: don't raise deprecation warning for _1_bday_ago * DOC: remove suggestions * TST: added test for simple_returns * MAINT: add simple_returns to init * TST: fixed simple_returns test * STY: use size, not shape * TST: tests passing * DOC: 1_bday_ago no longer deprecated
1 parent 7d39c4a commit 30a5c4c

File tree

6 files changed

+140
-3
lines changed

6 files changed

+140
-3
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,35 @@ roll_up_capture(returns, window=60)
6363

6464
Please [open an issue](https://github.com/quantopian/empyrical/issues/new) for support.
6565

66+
### Deprecated: Data Reading via `pandas-datareader`
67+
68+
As of early 2018, Yahoo Finance has suffered major API breaks with no stable
69+
replacement, and the Google Finance API has not been stable since late 2017
70+
[(source)](https://github.com/pydata/pandas-datareader/blob/da18fbd7621d473828d7fa81dfa5e0f9516b6793/README.rst).
71+
In recent months it has become a greater and greater strain on the `empyrical`
72+
development team to maintain support for fetching data through
73+
`pandas-datareader` and other third-party libraries, as these APIs are known to
74+
be unstable.
75+
76+
As a result, all `empyrical` support for data reading functionality has been
77+
deprecated and will be removed in a future version.
78+
79+
Users should beware that the following functions are now deprecated:
80+
81+
- `empyrical.utils.cache_dir`
82+
- `empyrical.utils.data_path`
83+
- `empyrical.utils.ensure_directory`
84+
- `empyrical.utils.get_fama_french`
85+
- `empyrical.utils.load_portfolio_risk_factors`
86+
- `empyrical.utils.default_returns_func`
87+
- `empyrical.utils.get_symbol_returns_from_yahoo`
88+
89+
Users should expect regular failures from the following functions, pending
90+
patches to the Yahoo or Google Finance API:
91+
92+
- `empyrical.utils.default_returns_func`
93+
- `empyrical.utils.get_symbol_returns_from_yahoo`
94+
6695
## Contributing
6796

6897
Please contribute using [Github Flow](https://guides.github.com/introduction/flow/). Create a branch, add commits, and [open a pull request](https://github.com/quantopian/empyrical/compare/).

empyrical/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
roll_up_capture,
5656
roll_up_down_capture,
5757
sharpe_ratio,
58+
simple_returns,
5859
sortino_ratio,
5960
stability_of_timeseries,
6061
tail_ratio,

empyrical/deprecate.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Utilities for marking deprecated functions."""
2+
# Copyright 2018 Quantopian, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import warnings
17+
from functools import wraps
18+
19+
20+
def deprecated(msg=None, stacklevel=2):
21+
"""
22+
Used to mark a function as deprecated.
23+
Parameters
24+
----------
25+
msg : str
26+
The message to display in the deprecation warning.
27+
stacklevel : int
28+
How far up the stack the warning needs to go, before
29+
showing the relevant calling lines.
30+
Usage
31+
-----
32+
@deprecated(msg='function_a is deprecated! Use function_b instead.')
33+
def function_a(*args, **kwargs):
34+
"""
35+
def deprecated_dec(fn):
36+
@wraps(fn)
37+
def wrapper(*args, **kwargs):
38+
warnings.warn(
39+
msg or "Function %s is deprecated." % fn.__name__,
40+
category=DeprecationWarning,
41+
stacklevel=stacklevel
42+
)
43+
return fn(*args, **kwargs)
44+
return wrapper
45+
return deprecated_dec

empyrical/stats.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,32 @@ def annualization_factor(period, annualization):
183183
return factor
184184

185185

186+
def simple_returns(prices):
187+
"""
188+
Compute simple returns from a timeseries of prices.
189+
190+
Parameters
191+
----------
192+
prices : pd.Series, pd.DataFrame or np.ndarray
193+
Prices of assets in wide-format, with assets as columns,
194+
and indexed by datetimes.
195+
196+
Returns
197+
-------
198+
returns : array-like
199+
Returns of assets in wide-format, with assets as columns,
200+
and index coerced to be tz-aware.
201+
"""
202+
if isinstance(prices, (pd.DataFrame, pd.Series)):
203+
out = prices.pct_change().iloc[1:]
204+
else:
205+
# Assume np.ndarray
206+
out = np.diff(prices, axis=0)
207+
np.divide(out, prices[:-1], out=out)
208+
209+
return out
210+
211+
186212
def cum_returns(returns, starting_value=0, out=None):
187213
"""
188214
Compute cumulative returns from simple returns.

empyrical/tests/test_stats.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@ class TestStats(BaseTestCase):
160160
'one': pd.Series(one, index=df_index_month),
161161
'two': pd.Series(two, index=df_index_month)})
162162

163+
@parameterized.expand([
164+
# Constant price implies zero returns,
165+
# and linearly increasing prices imples returns like 1/n
166+
(flat_line_1, [0.0] * (flat_line_1.shape[0] - 1)),
167+
(pos_line, [np.inf] + [1/n for n in range(1, 999)])
168+
])
169+
def test_simple_returns(self, prices, expected):
170+
simple_returns = self.empyrical.simple_returns(prices)
171+
assert_almost_equal(np.array(simple_returns), expected, 4)
172+
self.assert_indexes_match(simple_returns, prices.iloc[1:])
173+
163174
@parameterized.expand([
164175
(empty_returns, 0, []),
165176
(mixed_returns, 0, [0.0, 0.01, 0.111, 0.066559, 0.08789, 0.12052,

empyrical/utils.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright 2016 Quantopian, Inc.
2+
# Copyright 2018 Quantopian, Inc.
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -23,8 +23,22 @@
2323
from numpy.lib.stride_tricks import as_strided
2424
import pandas as pd
2525
from pandas.tseries.offsets import BDay
26-
from pandas_datareader import data as web
27-
26+
try:
27+
from pandas_datareader import data as web
28+
except ImportError as e:
29+
msg = ("Unable to import pandas_datareader. Suppressing import error and "
30+
"continuing. All data reading functionality will raise errors; but "
31+
"has been deprecated and will be removed in a later version.")
32+
warnings.warn(msg)
33+
from .deprecate import deprecated
34+
35+
DATAREADER_DEPRECATION_WARNING = \
36+
("Yahoo and Google Finance have suffered large API breaks with no "
37+
"stable replacement. As a result, any data reading functionality "
38+
"in empyrical has been deprecated and will be removed in a future "
39+
"version. See README.md for more details: "
40+
"\n\n"
41+
"\thttps://github.com/quantopian/pyfolio/blob/master/README.md")
2842
try:
2943
# fast versions
3044
import bottleneck as bn
@@ -175,6 +189,7 @@ def _roll_pandas(func, window, *args, **kwargs):
175189
return pd.Series(data, index=type(args[0].index)(index_values))
176190

177191

192+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
178193
def cache_dir(environ=environ):
179194
try:
180195
return environ['EMPYRICAL_CACHE_DIR']
@@ -189,10 +204,12 @@ def cache_dir(environ=environ):
189204
)
190205

191206

207+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
192208
def data_path(name):
193209
return join(cache_dir(), name)
194210

195211

212+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
196213
def ensure_directory(path):
197214
"""
198215
Ensure that a directory named "path" exists.
@@ -209,10 +226,12 @@ def get_utc_timestamp(dt):
209226
"""
210227
Returns the Timestamp/DatetimeIndex
211228
with either localized or converted to UTC.
229+
212230
Parameters
213231
----------
214232
dt : Timestamp/DatetimeIndex
215233
the date(s) to be converted
234+
216235
Returns
217236
-------
218237
same type as input
@@ -234,6 +253,7 @@ def _1_bday_ago():
234253
return pd.Timestamp.now().normalize() - _1_bday
235254

236255

256+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
237257
def get_fama_french():
238258
"""
239259
Retrieve Fama-French factors via pandas-datareader
@@ -257,6 +277,7 @@ def get_fama_french():
257277
return five_factors
258278

259279

280+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
260281
def get_returns_cached(filepath, update_func, latest_dt, **kwargs):
261282
"""
262283
Get returns from a cached file if the cache is recent enough,
@@ -324,6 +345,7 @@ def get_returns_cached(filepath, update_func, latest_dt, **kwargs):
324345
return returns
325346

326347

348+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
327349
def load_portfolio_risk_factors(filepath_prefix=None, start=None, end=None):
328350
"""
329351
Load risk factors Mkt-Rf, SMB, HML, Rf, and UMD.
@@ -353,6 +375,7 @@ def load_portfolio_risk_factors(filepath_prefix=None, start=None, end=None):
353375
return five_factors.loc[start:end]
354376

355377

378+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
356379
def get_treasury_yield(start=None, end=None, period='3MO'):
357380
"""
358381
Load treasury yields from FRED.
@@ -386,6 +409,7 @@ def get_treasury_yield(start=None, end=None, period='3MO'):
386409
return treasury
387410

388411

412+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
389413
def get_symbol_returns_from_yahoo(symbol, start=None, end=None):
390414
"""
391415
Wrapper for pandas.io.data.get_data_yahoo().
@@ -424,6 +448,7 @@ def get_symbol_returns_from_yahoo(symbol, start=None, end=None):
424448
return rets
425449

426450

451+
@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
427452
def default_returns_func(symbol, start=None, end=None):
428453
"""
429454
Gets returns for a symbol.

0 commit comments

Comments
 (0)