Pyfolio Single Stock Example correction + Quandl API

Single stock analysis example in pyfolio + Quandl

This post describes how I corrected the error when running Single Stock Example from PyFolio.

Yahoo and Google no longer support the API as used by pandas_datareader 0.6.0, so I updated the code to use Quandl API.

To get this to work, make sure that QUANDL_API_KEY key is set up in your environment. Go to Quandl site, register as a user, then in your user settings, get the API key. In Ubuntu Linux, update ~/.bashrc to include the following key initialization:

export QUANDL_API_KEY=[your key]

Here's a simple example where we produce a set of plots, called a tear sheet, for a single stock.

Import pyfolio and matplotlib

In [1]:
%matplotlib inline

# silence warnings
import warnings
warnings.filterwarnings('ignore')
import pyfolio as pf
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
<ipython-input-1-23dd6c005ff5> in <module>()
      4 import warnings
      5 warnings.filterwarnings('ignore')
----> 6 import pyfolio as pf

ModuleNotFoundError: No module named 'pyfolio'
In [2]:
import pandas_datareader as pdr

import pandas_datareader.data as web
symbol = 'FB'  # or 'AAPL.US'
df = web.DataReader(symbol, 'quandl', '2012-05-22', '2014-05-16')
df.tail()
Out[2]:
Open High Low Close Volume ExDividend SplitRatio AdjOpen AdjHigh AdjLow AdjClose AdjVolume
Date
2012-05-29 31.48 31.69 28.65 28.84 78063400.0 0.0 1.0 31.48 31.69 28.65 28.84 78063400.0
2012-05-25 32.90 32.95 31.11 31.91 37149800.0 0.0 1.0 32.90 32.95 31.11 31.91 37149800.0
2012-05-24 32.95 33.21 31.77 33.03 50237200.0 0.0 1.0 32.95 33.21 31.77 33.03 50237200.0
2012-05-23 31.37 32.50 31.36 32.00 73600000.0 0.0 1.0 31.37 32.50 31.36 32.00 73600000.0
2012-05-22 32.61 33.59 30.94 31.00 101786600.0 0.0 1.0 32.61 33.59 30.94 31.00 101786600.0

Fetch the daily returns for a stock

In [3]:
from empyrical.utils import get_utc_timestamp, get_returns_cached, _1_bday_ago, data_path
from pandas_datareader import data as web
from datetime import datetime
import pandas as pd

def get_symbol_returns_from_quandl(symbol, start=None, end=None):
    """
    Wrapper for pandas.io.data.get_data_quandl().
    Retrieves prices for symbol from quandl and computes returns
    based on adjusted closing prices.

    Parameters
    ----------
    symbol : str
        Symbol name to load, e.g. 'SPY'
    start : pandas.Timestamp compatible, optional
        Start date of time period to retrieve
    end : pandas.Timestamp compatible, optional
        End date of time period to retrieve

    Returns
    -------
    pandas.DataFrame
        Returns of symbol in requested period.
    """

    px = web.get_data_quandl(symbol, start=start, end=end)
    rets = px[['AdjClose']]
    rets = rets.shift(-1)
    rets.iloc[-1]['AdjClose'] = px.tail(1)['AdjOpen']
    rets = rets.shift(1) / rets - 1
    rets = rets.dropna()
    rets.index = rets.index.to_datetime()
    rets.index = rets.index.tz_localize("UTC")
    rets.columns = [symbol]
    return rets

def returns_func(symbol, start=None, end=None):
    """
    Gets returns for a symbol.
    Queries Quandl Finance. Attempts to cache SPY.

    Parameters
    ----------
    symbol : str
        Ticker symbol, e.g. APPL.
    start : date, optional
        Earliest date to fetch data for.
        Defaults to earliest date available.
    end : date, optional
        Latest date to fetch data for.
        Defaults to latest date available.

    Returns
    -------
    pd.Series
        Daily returns for the symbol.
         - See full explanation in tears.create_full_tear_sheet (returns).
    """

    if start is None:
        start = '1/1/1970'
    if end is None:
        end = _1_bday_ago()

    start = get_utc_timestamp(start)
    end = get_utc_timestamp(end)

    if symbol == 'SPY':
        filepath = data_path('spy.csv')
        rets = get_returns_cached(filepath,
                                  get_symbol_returns_from_quandl,
                                  end,
                                  symbol='SPY',
                                  start=start,
                                  end=datetime.now())
        
        try:
            rets = rets[rets.index.isin(pd.bdate_range(start, end))]
        except Exception as e:
            rets = rets[start:end]
    else:
        rets = get_symbol_returns_from_quandl(symbol, start=start, end=end)
    rets.sort_index(inplace=True)
    return rets[symbol]

pf.utils.register_return_func(returns_func)
In [4]:
stock_rets = pf.utils.get_symbol_rets('FB', start='2012-05-22', end='2014-05-16')
In [5]:
stock_rets.head()
Out[5]:
Date
2012-05-22 00:00:00+00:00   -0.049371
2012-05-23 00:00:00+00:00    0.032258
2012-05-24 00:00:00+00:00    0.032188
2012-05-25 00:00:00+00:00   -0.033909
2012-05-29 00:00:00+00:00   -0.096208
Name: FB, dtype: float64
In [6]:
benchmark_rets = pf.utils.get_symbol_rets('SPY', start='2012-05-22', end='2014-05-16')
benchmark_rets.head()
Out[6]:
date
2012-05-22 00:00:00+00:00   -0.000529
2012-05-23 00:00:00+00:00   -0.001962
2012-05-24 00:00:00+00:00    0.003255
2012-05-25 00:00:00+00:00   -0.011967
2012-05-29 00:00:00+00:00    0.014724
Name: SPY, dtype: float64

Create a returns tear sheet for the single stock

This will show charts and analysis about returns of the single stock.

In [7]:
pf.create_returns_tear_sheet(stock_rets, benchmark_rets=benchmark_rets)
Start date2012-05-23
End date2014-05-15
Total months23
Backtest
Annual return 37.3%
Cumulative returns 86.8%
Annual volatility 52.0%
Sharpe ratio 0.86
Calmar ratio 0.80
Stability 0.73
Max drawdown -46.4%
Omega ratio 1.17
Sortino ratio 1.44
Skew 1.89
Kurtosis 15.55
Tail ratio 1.13
Daily value at risk -6.4%
Alpha 0.46
Beta 0.09
Worst drawdown periods Net drawdown in % Peak date Valley date Recovery date Duration
0 46.44 2012-06-26 2012-09-04 2013-07-25 283
1 22.06 2014-03-10 2014-04-28 NaT NaN
2 21.68 2012-05-24 2012-06-05 2012-06-22 22
3 17.34 2013-10-18 2013-11-25 2013-12-17 43
4 8.72 2013-09-27 2013-10-09 2013-10-17 15