Skip to content

Commit

Permalink
Merge pull request #289 from quantopian/pandas_round
Browse files Browse the repository at this point in the history
Better table output
  • Loading branch information
twiecki committed Mar 31, 2016
2 parents ab89c06 + c218c8e commit ef0cb59
Show file tree
Hide file tree
Showing 12 changed files with 1,008 additions and 233 deletions.
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)

0 comments on commit ef0cb59

Please sign in to comment.