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

Better table output #289

Merged
merged 7 commits into from
Mar 31, 2016
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ install:
- pip install nose_parameterized contextlib2 logbook==0.10.1
#- pip install --no-deps git+https://github.com/quantopian/zipline
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then conda install --yes mock enum34; fi
- pip install --no-deps git+https://github.com/Theano/Theano.git@557fe6fab49262706241f0e774f3269a2ec0275b
- pip install --no-deps git+https://github.com/Theano/Theano.git@rel-0.8.1
- pip install --no-deps git+https://github.com/pymc-devs/pymc3.git
- python setup.py build_ext --inplace

Expand Down
2 changes: 1 addition & 1 deletion pyfolio/bayesian.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down
592 changes: 487 additions & 105 deletions pyfolio/examples/single_stock_example.ipynb

Large diffs are not rendered by default.

517 changes: 436 additions & 81 deletions pyfolio/examples/slippage_example.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyfolio/interesting_periods.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down
67 changes: 32 additions & 35 deletions pyfolio/plotting.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down Expand Up @@ -191,8 +191,7 @@ def plot_monthly_returns_heatmap(returns, ax=None, **kwargs):

monthly_ret_table = timeseries.aggregate_returns(returns,
'monthly')
monthly_ret_table = monthly_ret_table.unstack()
monthly_ret_table = np.round(monthly_ret_table, 3)
monthly_ret_table = monthly_ret_table.unstack().round(3)

sns.heatmap(
monthly_ret_table.fillna(0) *
Expand Down Expand Up @@ -534,13 +533,13 @@ def show_perf_stats(returns, factor_returns, live_start_date=None,
returns_backtest = returns[returns.index < live_start_date]
returns_live = returns[returns.index > live_start_date]

perf_stats_live = np.round(perf_func(
perf_stats_live = perf_func(
returns_live,
factor_returns=factor_returns).values, 2)
factor_returns=factor_returns)

perf_stats_all = np.round(perf_func(
perf_stats_all = perf_func(
returns,
factor_returns=factor_returns).values, 2)
factor_returns=factor_returns)

print('Out-of-Sample Months: ' +
str(int(len(returns_live) / APPROX_BDAYS_PER_MONTH)))
Expand All @@ -550,18 +549,21 @@ def show_perf_stats(returns, factor_returns, live_start_date=None,
print('Backtest Months: ' +
str(int(len(returns_backtest) / APPROX_BDAYS_PER_MONTH)))

perf_stats = np.round(perf_func(
perf_stats = perf_func(
returns_backtest,
factor_returns=factor_returns).values, 2)
factor_returns=factor_returns)

if live_start_date is not None:
perf_stats = pd.concat(OrderedDict([
('Backtest', perf_stats),
('Out of sample', perf_stats_live),
('All history', perf_stats_all),
]), axis=1)
else:
perf_stats = pd.DataFrame(perf_stats, columns=['Backtest'])

print(perf_stats)
utils.print_table(perf_stats, name='Performance statistics',
fmt='{0:.2f}')


def plot_rolling_returns(returns,
Expand Down Expand Up @@ -907,28 +909,23 @@ def show_and_plot_top_positions(returns, positions_alloc,
positions_alloc)

if show_and_plot == 1 or show_and_plot == 2:
print("\n")
print('Top 10 long positions of all time (and max%)')
print(pd.DataFrame(df_top_long).index.values)
print(np.round(pd.DataFrame(df_top_long)[0].values, 3))
print("\n")

print('Top 10 short positions of all time (and max%)')
print(pd.DataFrame(df_top_short).index.values)
print(np.round(pd.DataFrame(df_top_short)[0].values, 3))
print("\n")

print('Top 10 positions of all time (and max%)')
print(pd.DataFrame(df_top_abs).index.values)
print(np.round(pd.DataFrame(df_top_abs)[0].values, 3))
print("\n")
utils.print_table(pd.DataFrame(df_top_long * 100, columns=['max']),
fmt='{0:.2f}%',
name='Top 10 long positions of all time')

utils.print_table(pd.DataFrame(df_top_short * 100, columns=['max']),
fmt='{0:.2f}%',
name='Top 10 short positions of all time')

utils.print_table(pd.DataFrame(df_top_abs * 100, columns=['max']),
fmt='{0:.2f}%',
name='Top 10 positions of all time')

_, _, df_top_abs_all = pos.get_top_long_short_abs(
positions_alloc, top=9999)
print('All positions ever held')
print(pd.DataFrame(df_top_abs_all).index.values)
print(np.round(pd.DataFrame(df_top_abs_all)[0].values, 3))
print("\n")
utils.print_table(pd.DataFrame(df_top_abs_all * 100, columns=['max']),
fmt='{0:.2f}%',
name='All positions ever held')

if show_and_plot == 0 or show_and_plot == 2:

Expand Down Expand Up @@ -1392,11 +1389,9 @@ def show_worst_drawdown_periods(returns, top=5):

"""

print('\nWorst Drawdown Periods')
drawdown_df = timeseries.gen_drawdown_table(returns, top=top)
drawdown_df['net drawdown in %'] = list(
map(utils.round_two_dec_places, drawdown_df['net drawdown in %']))
print(drawdown_df.sort('net drawdown in %', ascending=False))
utils.print_table(drawdown_df.sort('net drawdown in %', ascending=False),
name='Worst Drawdown Periods', fmt='{0:.2f}')


def plot_monthly_returns_timeseries(returns, ax=None, **kwargs):
Expand Down Expand Up @@ -1516,8 +1511,10 @@ def show_profit_attribution(round_trips):
pct_profit_attribution = round_trips.groupby(
'symbol')['pnl'].sum() / total_pnl

print('\nProfitability (PnL / PnL total) per name:')
print(pct_profit_attribution.sort(inplace=False, ascending=False))
utils.print_table(pct_profit_attribution.sort(inplace=False,
ascending=False),
name='Profitability (PnL / PnL total) per name',
fmt='{0:.2f}%')


def plot_prob_profit_trade(round_trips, ax=None):
Expand Down
2 changes: 1 addition & 1 deletion pyfolio/pos.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down
2 changes: 1 addition & 1 deletion pyfolio/round_trips.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down
10 changes: 6 additions & 4 deletions pyfolio/tears.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down Expand Up @@ -628,9 +628,11 @@ def create_interesting_times_tear_sheet(
'interesting times.', UserWarning)
return

print('\nStress Events')
print(np.round(pd.DataFrame(rets_interesting).describe().transpose().loc[
:, ['mean', 'min', 'max']], 3))
utils.print_table(pd.DataFrame(rets_interesting)
.describe().transpose()
.loc[:, ['mean', 'min', 'max']] * 100,
name='Stress Events',
fmt='{0:.2f}%')

if benchmark_rets is None:
benchmark_rets = utils.get_symbol_rets('SPY')
Expand Down
2 changes: 1 addition & 1 deletion pyfolio/timeseries.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down
2 changes: 1 addition & 1 deletion pyfolio/txn.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down
41 changes: 40 additions & 1 deletion pyfolio/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 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 Down Expand Up @@ -470,3 +470,42 @@ def get_symbol_rets(symbol, start=None, end=None):
return SETTINGS['returns_func'](symbol,
start=start,
end=end)


def print_table(table, name=None, fmt=None):
"""Pretty print a pandas DataFrame.

Uses HTML output if running inside Jupyter Notebook, otherwise
formatted text output.

Parameters
----------
table : pandas.Series or pandas.DataFrame
Table to pretty-print.
name : str, optional
Table name to display in upper left corner.
fmt : str, optional
Formatter to use for displaying table elements.
E.g. '{0:.2f}%' for displaying 100 as '100.00%'.
Restores original setting after displaying.

"""
if isinstance(table, pd.Series):
table = pd.DataFrame(table)

if fmt is not None:
prev_option = pd.get_option('display.float_format')
pd.set_option('display.float_format', lambda x: fmt.format(x))

if name is not None:
table.columns.name = name

try:
get_ipython() # noqa
from IPython.display import display, HTML
display(HTML(table.to_html()))
except:
print(table)

if fmt is not None:
pd.set_option('display.float_format', prev_option)