Skip to content

Commit

Permalink
DEP: Deprecate all data reading functionality via pandas-datareader (#97
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
eigenfoo authored and twiecki committed Jun 14, 2018
1 parent 7d39c4a commit 30a5c4c
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 3 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ roll_up_capture(returns, window=60)

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

### Deprecated: Data Reading via `pandas-datareader`

As of early 2018, Yahoo Finance has suffered major API breaks with no stable
replacement, and the Google Finance API has not been stable since late 2017
[(source)](https://github.com/pydata/pandas-datareader/blob/da18fbd7621d473828d7fa81dfa5e0f9516b6793/README.rst).
In recent months it has become a greater and greater strain on the `empyrical`
development team to maintain support for fetching data through
`pandas-datareader` and other third-party libraries, as these APIs are known to
be unstable.

As a result, all `empyrical` support for data reading functionality has been
deprecated and will be removed in a future version.

Users should beware that the following functions are now deprecated:

- `empyrical.utils.cache_dir`
- `empyrical.utils.data_path`
- `empyrical.utils.ensure_directory`
- `empyrical.utils.get_fama_french`
- `empyrical.utils.load_portfolio_risk_factors`
- `empyrical.utils.default_returns_func`
- `empyrical.utils.get_symbol_returns_from_yahoo`

Users should expect regular failures from the following functions, pending
patches to the Yahoo or Google Finance API:

- `empyrical.utils.default_returns_func`
- `empyrical.utils.get_symbol_returns_from_yahoo`

## Contributing

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/).
Expand Down
1 change: 1 addition & 0 deletions empyrical/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
roll_up_capture,
roll_up_down_capture,
sharpe_ratio,
simple_returns,
sortino_ratio,
stability_of_timeseries,
tail_ratio,
Expand Down
45 changes: 45 additions & 0 deletions empyrical/deprecate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Utilities for marking deprecated functions."""
# Copyright 2018 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import warnings
from functools import wraps


def deprecated(msg=None, stacklevel=2):
"""
Used to mark a function as deprecated.
Parameters
----------
msg : str
The message to display in the deprecation warning.
stacklevel : int
How far up the stack the warning needs to go, before
showing the relevant calling lines.
Usage
-----
@deprecated(msg='function_a is deprecated! Use function_b instead.')
def function_a(*args, **kwargs):
"""
def deprecated_dec(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
warnings.warn(
msg or "Function %s is deprecated." % fn.__name__,
category=DeprecationWarning,
stacklevel=stacklevel
)
return fn(*args, **kwargs)
return wrapper
return deprecated_dec
26 changes: 26 additions & 0 deletions empyrical/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,32 @@ def annualization_factor(period, annualization):
return factor


def simple_returns(prices):
"""
Compute simple returns from a timeseries of prices.
Parameters
----------
prices : pd.Series, pd.DataFrame or np.ndarray
Prices of assets in wide-format, with assets as columns,
and indexed by datetimes.
Returns
-------
returns : array-like
Returns of assets in wide-format, with assets as columns,
and index coerced to be tz-aware.
"""
if isinstance(prices, (pd.DataFrame, pd.Series)):
out = prices.pct_change().iloc[1:]
else:
# Assume np.ndarray
out = np.diff(prices, axis=0)
np.divide(out, prices[:-1], out=out)

return out


def cum_returns(returns, starting_value=0, out=None):
"""
Compute cumulative returns from simple returns.
Expand Down
11 changes: 11 additions & 0 deletions empyrical/tests/test_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ class TestStats(BaseTestCase):
'one': pd.Series(one, index=df_index_month),
'two': pd.Series(two, index=df_index_month)})

@parameterized.expand([
# Constant price implies zero returns,
# and linearly increasing prices imples returns like 1/n
(flat_line_1, [0.0] * (flat_line_1.shape[0] - 1)),
(pos_line, [np.inf] + [1/n for n in range(1, 999)])
])
def test_simple_returns(self, prices, expected):
simple_returns = self.empyrical.simple_returns(prices)
assert_almost_equal(np.array(simple_returns), expected, 4)
self.assert_indexes_match(simple_returns, prices.iloc[1:])

@parameterized.expand([
(empty_returns, 0, []),
(mixed_returns, 0, [0.0, 0.01, 0.111, 0.066559, 0.08789, 0.12052,
Expand Down
31 changes: 28 additions & 3 deletions empyrical/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2016 Quantopian, Inc.
# Copyright 2018 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -23,8 +23,22 @@
from numpy.lib.stride_tricks import as_strided
import pandas as pd
from pandas.tseries.offsets import BDay
from pandas_datareader import data as web

try:
from pandas_datareader import data as web
except ImportError as e:
msg = ("Unable to import pandas_datareader. Suppressing import error and "
"continuing. All data reading functionality will raise errors; but "
"has been deprecated and will be removed in a later version.")
warnings.warn(msg)
from .deprecate import deprecated

DATAREADER_DEPRECATION_WARNING = \
("Yahoo and Google Finance have suffered large API breaks with no "
"stable replacement. As a result, any data reading functionality "
"in empyrical has been deprecated and will be removed in a future "
"version. See README.md for more details: "
"\n\n"
"\thttps://github.com/quantopian/pyfolio/blob/master/README.md")
try:
# fast versions
import bottleneck as bn
Expand Down Expand Up @@ -175,6 +189,7 @@ def _roll_pandas(func, window, *args, **kwargs):
return pd.Series(data, index=type(args[0].index)(index_values))


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def cache_dir(environ=environ):
try:
return environ['EMPYRICAL_CACHE_DIR']
Expand All @@ -189,10 +204,12 @@ def cache_dir(environ=environ):
)


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def data_path(name):
return join(cache_dir(), name)


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def ensure_directory(path):
"""
Ensure that a directory named "path" exists.
Expand All @@ -209,10 +226,12 @@ def get_utc_timestamp(dt):
"""
Returns the Timestamp/DatetimeIndex
with either localized or converted to UTC.
Parameters
----------
dt : Timestamp/DatetimeIndex
the date(s) to be converted
Returns
-------
same type as input
Expand All @@ -234,6 +253,7 @@ def _1_bday_ago():
return pd.Timestamp.now().normalize() - _1_bday


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def get_fama_french():
"""
Retrieve Fama-French factors via pandas-datareader
Expand All @@ -257,6 +277,7 @@ def get_fama_french():
return five_factors


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


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


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def get_treasury_yield(start=None, end=None, period='3MO'):
"""
Load treasury yields from FRED.
Expand Down Expand Up @@ -386,6 +409,7 @@ def get_treasury_yield(start=None, end=None, period='3MO'):
return treasury


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def get_symbol_returns_from_yahoo(symbol, start=None, end=None):
"""
Wrapper for pandas.io.data.get_data_yahoo().
Expand Down Expand Up @@ -424,6 +448,7 @@ def get_symbol_returns_from_yahoo(symbol, start=None, end=None):
return rets


@deprecated(msg=DATAREADER_DEPRECATION_WARNING)
def default_returns_func(symbol, start=None, end=None):
"""
Gets returns for a symbol.
Expand Down

0 comments on commit 30a5c4c

Please sign in to comment.