Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH Pyfolio integration #227

Merged
merged 6 commits into from
Dec 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
890 changes: 890 additions & 0 deletions alphalens/examples/pyfolio_integration.ipynb

Large diffs are not rendered by default.

2,454 changes: 1,083 additions & 1,371 deletions alphalens/examples/tear_sheet_walk_through.ipynb

Large diffs are not rendered by default.

342 changes: 319 additions & 23 deletions alphalens/performance.py

Large diffs are not rendered by default.

44 changes: 14 additions & 30 deletions alphalens/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from functools import wraps

from . import utils
from . import performance as perf

DECIMAL_TO_BPS = 10000

Expand Down Expand Up @@ -706,7 +707,7 @@ def plot_monthly_ic_heatmap(mean_monthly_ic, ax=None):
return ax


def plot_cumulative_returns(factor_returns, period=1, overlap=True, ax=None):
def plot_cumulative_returns(factor_returns, period, ax=None):
"""
Plots the cumulative returns of the returns series passed in.

Expand All @@ -715,16 +716,10 @@ def plot_cumulative_returns(factor_returns, period=1, overlap=True, ax=None):
factor_returns : pd.Series
Period wise returns of dollar neutral portfolio weighted by factor
value.
period: int, optional
Period over which the daily returns are calculated
overlap: boolean, optional
Specify if subsequent returns overlaps.
If 'overlap' is True and 'period' N is greater than 1, the cumulative
returns plot is computed building and averaging the cumulative returns
of N interleaved portfolios (started at subsequent periods 1,2,3,...,N)
each one rebalancing every N periods. This results in trading the
factor at every value/signal computed by the factor and also the
cumulative returns don't dependent on a specific starting date.
period: pandas.Timedelta or string
Length of period for which the returns are computed (e.g. 1 day)
if 'period' is a string it must follow pandas.Timedelta constructor
format (e.g. '1 days', '1D', '30m', '3h', '1D1h', etc)
ax : matplotlib.Axes, optional
Axes upon which to plot.

Expand All @@ -736,9 +731,7 @@ def plot_cumulative_returns(factor_returns, period=1, overlap=True, ax=None):
if ax is None:
f, ax = plt.subplots(1, 1, figsize=(18, 6))

overlapping_period = period if overlap else 1
factor_returns = utils.cumulative_returns(factor_returns,
overlapping_period)
factor_returns = perf.cumulative_returns(factor_returns, period)

factor_returns.plot(ax=ax, lw=3, color='forestgreen', alpha=0.6)
ax.set(ylabel='Cumulative Returns',
Expand All @@ -751,26 +744,19 @@ def plot_cumulative_returns(factor_returns, period=1, overlap=True, ax=None):


def plot_cumulative_returns_by_quantile(quantile_returns,
period=1,
overlap=True,
period,
ax=None):
"""
Plots the cumulative returns of various factor quantiles.

Parameters
----------
quantile_returns : pd.DataFrame
Cumulative returns by factor quantile.
period: int, optional
Period over which the daily returns are calculated
overlap: boolean, optional
Specify if subsequent returns overlaps.
If 'overlap' is True and 'period' N is greater than 1, the cumulative
returns plot is computed building and averaging the cumulative returns
of N interleaved portfolios (started at subsequent periods 1,2,3,...,N)
each one rebalancing every N periods. This results in trading the
factor at every value/signal computed by the factor and also the
cumulative returns don't dependent on a specific starting date.
Returns by factor quantile
period: pandas.Timedelta or string
Length of period for which the returns are computed (e.g. 1 day)
if 'period' is a string it must follow pandas.Timedelta constructor
format (e.g. '1 days', '1D', '30m', '3h', '1D1h', etc)
ax : matplotlib.Axes, optional
Axes upon which to plot.

Expand All @@ -785,9 +771,7 @@ def plot_cumulative_returns_by_quantile(quantile_returns,
ret_wide = quantile_returns.reset_index()\
.pivot(index='date', columns='factor_quantile', values=period)

overlapping_period = period if overlap else 1
cum_ret = ret_wide.apply(utils.cumulative_returns,
args=(overlapping_period,))
cum_ret = ret_wide.apply(perf.cumulative_returns, period=period)
cum_ret = cum_ret.loc[:, ::-1]

cum_ret.plot(lw=2, ax=ax, cmap=cm.RdYlGn_r)
Expand Down
60 changes: 44 additions & 16 deletions alphalens/tears.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ def create_summary_tear_sheet(factor_data,
group_adjust=group_neutral)

mean_quant_rateret = \
mean_quant_ret.apply(utils.rate_of_return, axis=0)
mean_quant_ret.apply(utils.rate_of_return, axis=0,
base_period=mean_quant_ret.columns[0])

mean_quant_ret_bydate, std_quant_daily = \
perf.mean_return_by_quantile(factor_data,
Expand All @@ -214,10 +215,16 @@ def create_summary_tear_sheet(factor_data,
demeaned=long_short,
group_adjust=group_neutral)

mean_quant_rateret_bydate = \
mean_quant_ret_bydate.apply(utils.rate_of_return, axis=0)
mean_quant_rateret_bydate = mean_quant_ret_bydate.apply(
utils.rate_of_return,
axis=0,
base_period=mean_quant_ret_bydate.columns[0]
)

compstd_quant_daily = std_quant_daily.apply(utils.std_conversion, axis=0)
compstd_quant_daily = std_quant_daily.apply(
utils.std_conversion, axis=0,
base_period=std_quant_daily.columns[0]
)

alpha_beta = perf.factor_alpha_beta(factor_data,
long_short,
Expand Down Expand Up @@ -308,7 +315,8 @@ def create_returns_tear_sheet(factor_data,
group_adjust=group_neutral)

mean_quant_rateret = \
mean_quant_ret.apply(utils.rate_of_return, axis=0)
mean_quant_ret.apply(utils.rate_of_return, axis=0,
base_period=mean_quant_ret.columns[0])

mean_quant_ret_bydate, std_quant_daily = \
perf.mean_return_by_quantile(factor_data,
Expand All @@ -317,10 +325,14 @@ def create_returns_tear_sheet(factor_data,
demeaned=long_short,
group_adjust=group_neutral)

mean_quant_rateret_bydate = \
mean_quant_ret_bydate.apply(utils.rate_of_return, axis=0)
mean_quant_rateret_bydate = mean_quant_ret_bydate.apply(
utils.rate_of_return, axis=0,
base_period=mean_quant_ret_bydate.columns[0]
)

compstd_quant_daily = std_quant_daily.apply(utils.std_conversion, axis=0)
compstd_quant_daily = \
std_quant_daily.apply(utils.std_conversion, axis=0,
base_period=std_quant_daily.columns[0])

alpha_beta = perf.factor_alpha_beta(factor_data,
long_short,
Expand Down Expand Up @@ -375,8 +387,10 @@ def create_returns_tear_sheet(factor_data,
demeaned=long_short,
group_adjust=group_neutral)

mean_quant_rateret_group = \
mean_return_quantile_group.apply(utils.rate_of_return, axis=0)
mean_quant_rateret_group = mean_return_quantile_group.apply(
utils.rate_of_return, axis=0,
base_period=mean_return_quantile_group.columns[0]
)

num_groups = len(mean_quant_rateret_group.index
.get_level_values('group').unique())
Expand Down Expand Up @@ -452,7 +466,7 @@ def create_information_tear_sheet(factor_data,


@plotting.customize
def create_turnover_tear_sheet(factor_data):
def create_turnover_tear_sheet(factor_data, turnover_periods=None):
"""
Creates a tear sheet for analyzing the turnover properties of a factor.

Expand All @@ -464,9 +478,19 @@ def create_turnover_tear_sheet(factor_data):
each period, the factor quantile/bin that factor value belongs to, and
(optionally) the group the asset belongs to.
- See full explanation in utils.get_clean_factor_and_forward_returns
turnover_periods : sequence[string], optional
Periods to compute turnover analysis on. By default periods in
'factor_data' are used but custom periods can provided instead. This
can be useful when periods in 'factor_data' are not multiples of the
frequency at which factor values are computed i.e. the periods
are 2h and 4h and the factor is computed daily and so values like
['1D', '2D'] could be used instead
"""

turnover_periods = utils.get_forward_returns_columns(factor_data.columns)
if turnover_periods is None:
turnover_periods = utils.get_forward_returns_columns(
factor_data.columns)

quantile_factor = factor_data['factor_quantile']

quantile_turnover = \
Expand All @@ -487,7 +511,7 @@ def create_turnover_tear_sheet(factor_data):
vertical_sections = fr_cols + 3 * rows_when_wide + 2 * fr_cols
gf = GridFigure(rows=vertical_sections, cols=columns_wide)

for period in sorted(quantile_turnover.keys()):
for period in turnover_periods:
plotting.plot_top_bottom_quantile_turnover(quantile_turnover[period],
period=period,
ax=gf.next_row())
Expand Down Expand Up @@ -676,15 +700,19 @@ def create_event_study_tear_sheet(factor_data, prices, avgretplot=(5, 15)):
by_group=False,
demeaned=long_short)
mean_quant_ret = \
mean_quant_ret.apply(utils.rate_of_return, axis=0)
mean_quant_ret.apply(utils.rate_of_return, axis=0,
base_period=mean_quant_ret.columns[0])

mean_quant_ret_bydate, std_quant_daily = \
perf.mean_return_by_quantile(factor_data,
by_date=True,
by_group=False,
demeaned=long_short)
mean_quant_rateret_bydate = \
mean_quant_ret_bydate.apply(utils.rate_of_return, axis=0)

mean_quant_rateret_bydate = mean_quant_ret_bydate.apply(
utils.rate_of_return, axis=0,
base_period=mean_quant_ret_bydate.columns[0]
)

fr_cols = len(mean_quant_ret.columns)
vertical_sections = 2 + fr_cols * 3
Expand Down
Loading