Skip to content

Commit 1be90ed

Browse files
authored
Remove statsmodels, scikit-learn, and other dependencies; add gross leverage to summary stats (#347)
Remove statsmodels, scikit-learn, and other dependencies; add gross leverage to performance stats summary
1 parent 1d0e92c commit 1be90ed

File tree

8 files changed

+56
-91
lines changed

8 files changed

+56
-91
lines changed

.travis.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@ before_install:
2424
- cp pyfolio/tests/matplotlibrc .
2525

2626
install:
27-
- conda create -q -n testenv --yes python=$TRAVIS_PYTHON_VERSION ipython pyzmq numpy scipy nose matplotlib pandas Cython patsy statsmodels flake8 scikit-learn seaborn runipy pytables networkx pandas-datareader matplotlib-tests joblib
27+
- conda create -q -n testenv --yes python=$TRAVIS_PYTHON_VERSION ipython pyzmq numpy scipy nose matplotlib pandas Cython patsy flake8 seaborn runipy pytables networkx pandas-datareader matplotlib-tests joblib
2828
- source activate testenv
2929
- pip install nose_parameterized
3030
#- pip install --no-deps git+https://github.com/quantopian/zipline
31-
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then conda install --yes mock enum34; fi
3231
- pip install -e .[bayesian]
3332

3433
before_script:

conda/meta.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,12 @@ requirements:
1717

1818
run:
1919
- python
20-
- funcsigs >=0.4
2120
- matplotlib >=1.4.0
22-
- mock >=1.1.2
2321
- numpy >=1.9.1
2422
- pandas >=0.18.0
25-
- pyparsing >=2.0.3
26-
- python-dateutil >=2.4.2
2723
- pytz >=2014.10
28-
- scikit-learn >=0.15.0
2924
- scipy >=0.14.0
3025
- seaborn >=0.6.0
31-
- statsmodels >=0.5.0
3226
- pandas-datareader >=0.2
3327
- ipython
3428
- empyrical >=0.2.1

pyfolio/plotting.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
from matplotlib import figure
2727
from matplotlib.backends.backend_agg import FigureCanvasAgg
2828

29-
from sklearn import preprocessing
30-
3129
from . import utils
3230
from . import timeseries
3331
from . import pos
@@ -499,8 +497,8 @@ def plot_perf_stats(returns, factor_returns, ax=None):
499497
return ax
500498

501499

502-
def show_perf_stats(returns, factor_returns, live_start_date=None,
503-
bootstrap=False):
500+
def show_perf_stats(returns, factor_returns, gross_lev=None,
501+
live_start_date=None, bootstrap=False):
504502
"""Prints some performance metrics of the strategy.
505503
506504
- Shows amount of time the strategy has been run in backtest and
@@ -537,25 +535,35 @@ def show_perf_stats(returns, factor_returns, live_start_date=None,
537535
returns_backtest = returns[returns.index < live_start_date]
538536
returns_live = returns[returns.index > live_start_date]
539537

538+
gross_lev_backtest = None
539+
gross_lev_live = None
540+
if gross_lev is not None:
541+
gross_lev_backtest = gross_lev[gross_lev.index < live_start_date]
542+
gross_lev_live = gross_lev[gross_lev.index > live_start_date]
543+
540544
perf_stats_live = perf_func(
541545
returns_live,
542-
factor_returns=factor_returns)
546+
factor_returns=factor_returns,
547+
gross_lev=gross_lev_live)
543548

544549
perf_stats_all = perf_func(
545550
returns,
546-
factor_returns=factor_returns)
551+
factor_returns=factor_returns,
552+
gross_lev=gross_lev)
547553

548554
print('Out-of-Sample Months: ' +
549555
str(int(len(returns_live) / APPROX_BDAYS_PER_MONTH)))
550556
else:
551557
returns_backtest = returns
558+
gross_lev_backtest = gross_lev
552559

553560
print('Backtest Months: ' +
554561
str(int(len(returns_backtest) / APPROX_BDAYS_PER_MONTH)))
555562

556563
perf_stats = perf_func(
557564
returns_backtest,
558-
factor_returns=factor_returns)
565+
factor_returns=factor_returns,
566+
gross_lev=gross_lev_backtest)
559567

560568
if live_start_date is not None:
561569
perf_stats = pd.concat(OrderedDict([
@@ -1425,8 +1433,7 @@ def plot_daily_volume(returns, transactions, ax=None, **kwargs):
14251433

14261434

14271435
def plot_daily_returns_similarity(returns_backtest, returns_live,
1428-
title='', scale_kws=None, ax=None,
1429-
**kwargs):
1436+
title='', ax=None, **kwargs):
14301437
"""Plots overlapping distributions of in-sample (backtest) returns
14311438
and out-of-sample (live trading) returns.
14321439
@@ -1438,8 +1445,6 @@ def plot_daily_returns_similarity(returns_backtest, returns_live,
14381445
Daily returns of the strategy's live trading, noncumulative.
14391446
title : str, optional
14401447
The title to use for the plot.
1441-
scale_kws : dict, optional
1442-
Additional arguments passed to preprocessing.scale.
14431448
ax : matplotlib.Axes, optional
14441449
Axes upon which to plot.
14451450
**kwargs, optional
@@ -1454,13 +1459,11 @@ def plot_daily_returns_similarity(returns_backtest, returns_live,
14541459

14551460
if ax is None:
14561461
ax = plt.gca()
1457-
if scale_kws is None:
1458-
scale_kws = {}
14591462

1460-
sns.kdeplot(preprocessing.scale(returns_backtest, **scale_kws),
1463+
sns.kdeplot(utils.standardize_data(returns_backtest),
14611464
bw='scott', shade=True, label='backtest',
14621465
color='forestgreen', ax=ax, **kwargs)
1463-
sns.kdeplot(preprocessing.scale(returns_live, **scale_kws),
1466+
sns.kdeplot(utils.standardize_data(returns_live),
14641467
bw='scott', shade=True, label='out-of-sample',
14651468
color='red', ax=ax, **kwargs)
14661469
ax.set_title(title)

pyfolio/tears.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def create_full_tear_sheet(returns,
163163
create_returns_tear_sheet(
164164
returns,
165165
live_start_date=live_start_date,
166+
gross_lev=gross_lev,
166167
cone_std=cone_std,
167168
benchmark_rets=benchmark_rets,
168169
bootstrap=bootstrap,
@@ -204,6 +205,7 @@ def create_full_tear_sheet(returns,
204205

205206
@plotting_context
206207
def create_returns_tear_sheet(returns, live_start_date=None,
208+
gross_lev=None,
207209
cone_std=(1.0, 1.5, 2.0),
208210
benchmark_rets=None,
209211
bootstrap=False,
@@ -256,6 +258,7 @@ def create_returns_tear_sheet(returns, live_start_date=None,
256258
print('\n')
257259

258260
plotting.show_perf_stats(returns, benchmark_rets,
261+
gross_lev=gross_lev,
259262
bootstrap=bootstrap,
260263
live_start_date=live_start_date)
261264

pyfolio/tests/test_timeseries.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -276,33 +276,6 @@ def test_beta(self, returns, benchmark_rets, rolling_window, expected):
276276
expected)
277277

278278

279-
class TestMultifactor(TestCase):
280-
simple_rets = pd.Series(
281-
[0.1] * 3 + [0] * 497,
282-
pd.date_range(
283-
'2000-1-1',
284-
periods=500,
285-
freq='D'))
286-
simple_benchmark_rets = pd.DataFrame(
287-
pd.Series(
288-
[0.03] * 4 + [0] * 496,
289-
pd.date_range(
290-
'2000-1-1',
291-
periods=500,
292-
freq='D')),
293-
columns=['bm'])
294-
295-
@parameterized.expand([
296-
(simple_rets[:4], simple_benchmark_rets[:4], [2.5000000000000004])
297-
])
298-
def test_calc_multifactor(self, returns, factors, expected):
299-
self.assertEqual(
300-
timeseries.calc_multifactor(
301-
returns,
302-
factors).values.tolist(),
303-
expected)
304-
305-
306279
class TestCone(TestCase):
307280
def test_bootstrap_cone_against_linear_cone_normal_returns(self):
308281
random_seed = 100

pyfolio/timeseries.py

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ def common_sense_ratio(returns):
474474
stats.skew,
475475
stats.kurtosis,
476476
tail_ratio,
477-
common_sense_ratio,
477+
common_sense_ratio
478478
]
479479

480480
FACTOR_STAT_FUNCS = [
@@ -554,34 +554,6 @@ def aggregate_returns(returns, convert_to):
554554
return empyrical.aggregate_returns(returns, convert_to=convert_to)
555555

556556

557-
def calc_multifactor(returns, factors):
558-
"""Computes multiple ordinary least squares linear fits, and returns
559-
fit parameters.
560-
561-
Parameters
562-
----------
563-
returns : pd.Series
564-
Daily returns of the strategy, noncumulative.
565-
- See full explanation in tears.create_full_tear_sheet.
566-
factors : pd.Series
567-
Secondary sets to fit.
568-
569-
Returns
570-
-------
571-
pd.DataFrame
572-
Fit parameters.
573-
574-
"""
575-
576-
import statsmodels.api as sm
577-
factors = factors.loc[returns.index]
578-
factors = sm.add_constant(factors)
579-
factors = factors.dropna(axis=0)
580-
results = sm.OLS(returns[factors.index], factors).fit()
581-
582-
return results.params
583-
584-
585557
def rolling_beta(returns, factor_returns,
586558
rolling_window=APPROX_BDAYS_PER_MONTH * 6):
587559
"""Determines the rolling beta of a strategy.
@@ -658,7 +630,7 @@ def rolling_fama_french(returns, factor_returns=None,
658630
rolling_window=rolling_window)
659631

660632

661-
def perf_stats(returns, factor_returns=None):
633+
def perf_stats(returns, factor_returns=None, gross_lev=None):
662634
"""Calculates various performance metrics of a strategy, for use in
663635
plotting.show_perf_stats.
664636
@@ -671,6 +643,8 @@ def perf_stats(returns, factor_returns=None):
671643
Daily noncumulative returns of the benchmark.
672644
- This is in the same style as returns.
673645
If None, do not compute alpha, beta, and information ratio.
646+
gross_lev : pd.Series (optional)
647+
Daily gross leverage of the strategy.
674648
675649
Returns
676650
-------
@@ -680,10 +654,12 @@ def perf_stats(returns, factor_returns=None):
680654
"""
681655

682656
stats = pd.Series()
683-
684657
for stat_func in SIMPLE_STAT_FUNCS:
685658
stats[stat_func.__name__] = stat_func(returns)
686659

660+
if gross_lev is not None:
661+
stats['mean_gross_leverage'] = gross_lev.mean()
662+
687663
if factor_returns is not None:
688664
for stat_func in FACTOR_STAT_FUNCS:
689665
stats[stat_func.__name__] = stat_func(returns,
@@ -692,7 +668,8 @@ def perf_stats(returns, factor_returns=None):
692668
return stats
693669

694670

695-
def perf_stats_bootstrap(returns, factor_returns=None, return_stats=True):
671+
def perf_stats_bootstrap(returns, factor_returns=None, gross_lev=None,
672+
return_stats=True):
696673
"""Calculates various bootstrapped performance metrics of a strategy.
697674
698675
Parameters
@@ -726,6 +703,10 @@ def perf_stats_bootstrap(returns, factor_returns=None, return_stats=True):
726703
bootstrap_values[stat_name] = calc_bootstrap(stat_func,
727704
returns)
728705

706+
if gross_lev is not None:
707+
bootstrap_values['mean_gross_leverage'] = calc_bootstrap(np.mean,
708+
gross_lev)
709+
729710
if factor_returns is not None:
730711
for stat_func in FACTOR_STAT_FUNCS:
731712
stat_name = stat_func.__name__

pyfolio/utils.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from os.path import expanduser, join, getmtime, isdir
2121
import warnings
2222

23+
import numpy as np
2324
from IPython.display import display
2425
import pandas as pd
2526
from pandas.tseries.offsets import BDay
@@ -475,7 +476,8 @@ def get_symbol_rets(symbol, start=None, end=None):
475476

476477

477478
def print_table(table, name=None, fmt=None):
478-
"""Pretty print a pandas DataFrame.
479+
"""
480+
Pretty print a pandas DataFrame.
479481
480482
Uses HTML output if running inside Jupyter Notebook, otherwise
481483
formatted text output.
@@ -506,3 +508,20 @@ def print_table(table, name=None, fmt=None):
506508

507509
if fmt is not None:
508510
pd.set_option('display.float_format', prev_option)
511+
512+
513+
def standardize_data(x):
514+
"""
515+
Standardize an array with mean and standard deviation.
516+
517+
Parameters
518+
----------
519+
x : np.array
520+
Array to standardize.
521+
522+
Returns
523+
-------
524+
np.array
525+
Standardized array.
526+
"""
527+
return (x - np.mean(x)) / np.std(x)

setup.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,15 @@
3838
'Operating System :: OS Independent']
3939

4040
install_reqs = [
41-
'funcsigs>=0.4',
4241
'ipython>=3.2.3',
4342
'matplotlib>=1.4.0',
44-
'mock>=1.1.2',
4543
'numpy>=1.9.1',
4644
'pandas>=0.18.0',
47-
'pyparsing>=2.0.3',
48-
'python-dateutil>=2.4.2',
4945
'pytz>=2014.10',
5046
'scipy>=0.14.0',
5147
'seaborn>=0.7.1',
5248
'pandas-datareader>=0.2',
53-
'scikit-learn>=0.17',
54-
'empyrical>=0.2.1',
55-
'statsmodels>=0.6.1',
56-
'jsonschema>=2.5.1',
49+
'empyrical>=0.2.1'
5750
]
5851

5952
test_reqs = ['nose>=1.3.7', 'nose-parameterized>=0.5.0', 'runipy>=0.1.3']

0 commit comments

Comments
 (0)