TST: Update to empyrical, increase test coverage

ENH: Resolve rebase conflict by using updated example_data.tar

TST: Increase test coverage for risk portion of zipline
This commit is contained in:
Ana Ruelas
2016-07-18 14:02:38 -04:00
parent 902865cf71
commit f0af856c13
69 changed files with 222 additions and 172 deletions
@@ -1,4 +1,4 @@
"%PYTHON%" setup.py install
"%PYTHON%" setup.py install
if errorlevel 1 exit 1
:: Add more build steps here, if they are necessary.
@@ -1,6 +1,6 @@
#!/bin/bash
$PYTHON setup.py install
$PYTHON setup.py install
# Add more build steps here, if they are necessary.
+33
View File
@@ -0,0 +1,33 @@
package:
name: empyrical
version: "0.1.9"
source:
fn: empyrical-0.1.9.tar.gz
url: https://pypi.python.org/packages/15/6b/de1d277d4342d2cecc8134f4935248853f32299cdd9b01728b0ec420c350/empyrical-0.1.9.tar.gz
md5: 2c8b928cae192fc9cb8b7104608eec56
requirements:
build:
- python
- setuptools
- numpy x.x
- pandas >=0.16.1
- scipy >=0.15.1
- bottleneck >=1.0.0
run:
- python
- numpy x.x
- pandas >=0.16.1
- scipy >=0.15.1
- bottleneck >=1.0.0
about:
home: https://github.com/quantopian/empyrical
license: Apache Software License
summary: 'empyrical is a Python library with performance and risk statistics\ncommonly used in quantitative finance'
# See
# http://docs.continuum.io/conda/build.html for
# more information about meta.yaml
-27
View File
@@ -1,27 +0,0 @@
package:
name: qrisk
version: "0.1.4"
source:
fn: qrisk-0.1.4.tar.gz
url: https://files.pythonhosted.org/packages/49/c9/81924d56e8173c4e92d60f5e59143dd345a0bf015508f85683ed9c2efd27/qrisk-0.1.4.tar.gz
md5: 24a76bcb960e82ade86bf115e87c5f53
requirements:
build:
- python
- setuptools
- numpy x.x
- pandas >=0.16.1
- scipy >=0.15.1
run:
- python
- numpy x.x
- pandas >=0.16.1
- scipy >=0.15.1
about:
home: https://github.com/quantopian/qrisk
license: Apache Software License
summary: 'qrisk is a Python library with performance and risk statistics\ncommonly used in quantitative finance'
+1 -1
View File
@@ -64,4 +64,4 @@ intervaltree==2.1.0
cachetools==1.1.5
# For financial risk calculations
qrisk==0.1.4
empyrical>=0.1.9
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"names": ["open", "high", "low", "close", "volume", "day", "id"]}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
@@ -0,0 +1 @@
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
@@ -0,0 +1 @@
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
File diff suppressed because one or more lines are too long
+4 -10
View File
@@ -17,15 +17,11 @@ import numpy as np
import pandas as pd
import zipline.finance.risk as risk
from zipline.utils import factory
import pandas as pd
from zipline.testing.fixtures import WithTradingEnvironment, ZiplineTestCase
from zipline.finance.trading import SimulationParameters
from . import answer_key
ANSWER_KEY = answer_key.ANSWER_KEY
RETURNS_BASE = 0.01
RETURNS = [RETURNS_BASE] * 251
@@ -38,16 +34,10 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def init_instance_fixtures(self):
super(TestRisk, self).init_instance_fixtures()
<<<<<<< 30f5a8fcfa4a194f05d58f50cf0a2b06dd8085cc
start_session = pd.Timestamp("2006-01-01", tz='UTC')
end_session = pd.Timestamp("2006-12-29", tz='UTC')
=======
start_date = pd.Timestamp('2006-01-01', tz=pytz.utc)
end_date = pd.Timestamp('2006-12-29', tz=pytz.utc)
>>>>>>> ENH: Change datetime.datetime to pd.Timestamp in tests
self.sim_params = SimulationParameters(
start_session=start_session,
end_session=end_session,
@@ -150,3 +140,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
all(isinstance(x, float)
for x in self.cumulative_metrics.max_drawdowns),
True)
def test_representation(self):
assert all([metric in self.cumulative_metrics.__repr__() for metric in
self.cumulative_metrics.METRIC_NAMES])
+147 -72
View File
@@ -17,8 +17,6 @@ import datetime
import calendar
import pandas as pd
import numpy as np
import pytz
import pandas as pd
import zipline.finance.risk as risk
from zipline.utils import factory
@@ -26,6 +24,8 @@ from zipline.utils import factory
from zipline.finance.trading import SimulationParameters
from zipline.testing.fixtures import WithTradingEnvironment, ZiplineTestCase
from zipline.finance.risk.period import RiskMetricsPeriod
RETURNS_BASE = 0.01
RETURNS = [RETURNS_BASE] * 251
@@ -38,17 +38,14 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def init_instance_fixtures(self):
super(TestRisk, self).init_instance_fixtures()
start_session = pd.Timestamp("2006-01-01", tz='UTC')
end_session = self.trading_calendar.minute_to_session_label(
self.start_session = pd.Timestamp("2006-01-01", tz='UTC')
self.end_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("2006-12-31", tz='UTC'),
direction="previous"
)
self.sim_params = SimulationParameters(
start_session=start_session,
end_session=end_session,
start_session=self.start_session,
end_session=self.end_session,
trading_calendar=self.trading_calendar,
)
self.algo_returns = factory.create_returns_from_list(
@@ -63,16 +60,15 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
self.algo_returns,
self.sim_params,
benchmark_returns=self.benchmark_returns,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
def test_factory(self):
returns = [0.1] * 100
r_objects = factory.create_returns_from_list(returns, self.sim_params)
self.assertTrue(r_objects.index[-1] <= self.end_date)
self.assertTrue(r_objects.index[0] >= self.start_date)
self.assertTrue(r_objects.sample().values[0] == 0.1)
self.assertTrue(r_objects.index[-1] <=
pd.Timestamp('2006-12-31', tz='UTC'))
def test_drawdown(self):
np.testing.assert_equal(
@@ -123,7 +119,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
[20, 19, 23, 19, 22, 22, 20, 23, 20, 22, 21, 20])
def test_benchmark_volatility(self):
# Volatility is calculated by a qrisk function so testing
# Volatility is calculated by a empyrical function so testing
# of period volatility will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -170,7 +166,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
DECIMAL_PLACES)
def test_algorithm_volatility(self):
# Volatility is calculated by a qrisk function so testing
# Volatility is calculated by a empyrical function so testing
# of period volatility will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -191,7 +187,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
True)
def test_algorithm_sharpe(self):
# The sharpe ratio is calculated by a qrisk function so testing
# The sharpe ratio is calculated by a empyrical function so testing
# of period sharpe ratios will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -212,7 +208,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
True)
def test_algorithm_downside_risk(self):
# Downside risk is calculated by a qrisk function so testing
# Downside risk is calculated by a empyrical function so testing
# of period downside risk will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -233,7 +229,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
True)
def test_algorithm_sortino(self):
# The sortino ratio is calculated by a qrisk function so testing
# The sortino ratio is calculated by a empyrical function so testing
# of period sortino ratios will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -254,9 +250,9 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
True)
def test_algorithm_information(self):
# The information ratio is calculated by a qrisk function so testing
# of period information ratio will be limited to determine if the value
# is numerical. This tests for its existence and format.
# The information ratio is calculated by a empyrical function
# testing of period information ratio will be limited to determine
# if the value is numerical. This tests for its existence and format.
np.testing.assert_equal(
all(isinstance(x.information, float)
for x in self.metrics.month_periods),
@@ -275,7 +271,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
True)
def test_algorithm_beta(self):
# Beta is calculated by a qrisk function so testing
# Beta is calculated by a empyrical function so testing
# of period beta will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -296,7 +292,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
True)
def test_algorithm_alpha(self):
# Alpha is calculated by a qrisk function so testing
# Alpha is calculated by a empyrical function so testing
# of period alpha will be limited to determine if the value is
# numerical. This tests for its existence and format.
np.testing.assert_equal(
@@ -316,49 +312,6 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
for x in self.metrics.year_periods),
True)
def test_algorithm_covariance(self):
np.testing.assert_almost_equal(
[x.algorithm_covariance for x in self.metrics.month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.algorithm_covariance
for x in self.metrics.three_month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.algorithm_covariance
for x in self.metrics.six_month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.algorithm_covariance
for x in self.metrics.year_periods],
[0.0],
DECIMAL_PLACES)
def test_benchmark_variance(self):
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics.month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics.three_month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics.six_month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics.year_periods],
[0.0],
DECIMAL_PLACES)
def test_treasury_returns(self):
returns = factory.create_returns_from_range(self.sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
@@ -407,13 +360,33 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
[0.0500])
def test_benchmarkrange(self):
self.check_year_range(
pd.Timestamp('2008-01-01', tz=pytz.utc),
2)
start_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("2008-01-01", tz='UTC')
)
end_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("2010-01-01", tz='UTC'), direction="previous"
)
sim_params = SimulationParameters(
start_session=start_session,
end_session=end_session,
trading_calendar=self.trading_calendar,
)
returns = factory.create_returns_from_range(sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.check_metrics(metrics, 24, start_session)
def test_partial_month(self):
start = pd.Timestamp('1991-01-01', tz=pytz.utc)
start_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("1991-01-01", tz='UTC')
)
# 1992 and 1996 were leap years
total_days = 365 * 5 + 2
@@ -504,6 +477,108 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
end=col[-1]._end_session,
actual=len(col))
)
self.assert_month(start_date.month, col[-1]._end_session.month)
self.assert_last_day(col[-1]._end_session)
self.assert_month(start_date.month, col[-1].end_date.month)
self.assert_last_day(col[-1].end_date)
def test_algorithm_leverages(self):
# Max leverage for an algorithm with 'None' as leverage is 0.
np.testing.assert_equal(
[x.max_leverage for x in self.metrics.month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
np.testing.assert_equal(
[x.max_leverage for x in self.metrics.three_month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
np.testing.assert_equal(
[x.max_leverage for x in self.metrics.six_month_periods],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
np.testing.assert_equal(
[x.max_leverage for x in self.metrics.year_periods],
[0.0])
def test_returns_beyond_treasury(self):
# The last treasury value is used when return dates go beyond
# treasury curve data
treasury_curves = self.env.treasury_curves
treasury = treasury_curves[treasury_curves.index < self.start_session]
test_period = RiskMetricsPeriod(
start_session=self.start_session,
end_session=self.end_session,
returns=self.algo_returns,
benchmark_returns=self.benchmark_returns,
trading_calendar=self.trading_calendar,
treasury_curves=treasury,
algorithm_leverages=[.01, .02, .03]
)
assert test_period.treasury_curves.equals(treasury[-1:])
# This return period has a list instead of None for algorithm_leverages
# Confirm that max_leverage is set to the max of those values
assert test_period.max_leverage == .03
def test_index_mismatch_exception(self):
# An exception is raised when returns and benchmark returns
# have indexes that do not match
bench_params = SimulationParameters(
start_session=pd.Timestamp("2006-02-01", tz='UTC'),
end_session=pd.Timestamp("2006-02-28", tz='UTC'),
trading_calendar=self.trading_calendar,
)
benchmark = factory.create_returns_from_list(
[BENCHMARK_BASE]*19,
bench_params
)
with np.testing.assert_raises(Exception):
RiskMetricsPeriod(
start_session=self.start_session,
end_session=self.end_session,
returns=self.algo_returns,
benchmark_returns=benchmark,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
def test_sharpe_value_when_null(self):
# Sharpe is displayed as '0.0' instead of np.nan
null_returns = factory.create_returns_from_list(
[0.0]*251,
self.sim_params
)
test_period = RiskMetricsPeriod(
start_session=self.start_session,
end_session=self.end_session,
returns=null_returns,
benchmark_returns=self.benchmark_returns,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
assert test_period.sharpe == 0.0
def test_representation(self):
test_period = RiskMetricsPeriod(
start_session=self.start_session,
end_session=self.end_session,
returns=self.algo_returns,
benchmark_returns=self.benchmark_returns,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
metrics = [
"algorithm_period_returns",
"benchmark_period_returns",
"excess_return",
"num_trading_days",
"benchmark_volatility",
"algorithm_volatility",
"sharpe",
"sortino",
"information",
"beta",
"alpha",
"max_drawdown",
"max_leverage",
"algorithm_returns",
"benchmark_returns",
]
representation = test_period.__repr__()
assert all([metric in representation for metric in metrics])
+4 -4
View File
@@ -873,10 +873,10 @@ def before_trading_start(context, data):
res2 = algo2.run(self.data_portal)
# FIXME I think we are getting Nans due to fixed benchmark,
# so dropping them for now.
res1 = res1.fillna(method='ffill')
res2 = res2.fillna(method='ffill')
# There are some np.NaN values in the first row because there is not
# enough data to calculate the metric, e.g. beta.
res1 = res1.fillna(value=0)
res2 = res2.fillna(value=0)
np.testing.assert_array_equal(res1, res2)
+2 -2
View File
@@ -1191,9 +1191,9 @@ class DataPortal(object):
if data_frequency == "minute":
freq_str = "1m"
calculated_bar_count = self._get_minute_count_for_transform(
calculated_bar_count = int(self._get_minute_count_for_transform(
dt, bars
)
))
else:
freq_str = "1d"
calculated_bar_count = bars
+1 -1
View File
@@ -27,7 +27,7 @@ from . risk import (
choose_treasury
)
from qrisk import (
from empyrical import (
alpha,
annual_volatility,
beta,
+4 -53
View File
@@ -16,8 +16,6 @@
import functools
import logbook
import numpy as np
import numpy.linalg as la
from six import iteritems
@@ -26,10 +24,11 @@ import pandas as pd
from . import risk
from . risk import check_entry
from qrisk import (
from empyrical import (
alpha,
annual_volatility,
beta,
cum_returns,
downside_risk,
information_ratio,
max_drawdown,
@@ -46,7 +45,6 @@ choose_treasury = functools.partial(risk.choose_treasury,
class RiskMetricsPeriod(object):
def __init__(self, start_session, end_session, returns, trading_calendar,
treasury_curves, benchmark_returns, algorithm_leverages=None):
if treasury_curves.index[-1] >= start_session:
mask = ((treasury_curves.index >= start_session) &
(treasury_curves.index <= end_session))
@@ -79,10 +77,10 @@ class RiskMetricsPeriod(object):
def calculate_metrics(self):
self.benchmark_period_returns = \
self.calculate_period_returns(self.benchmark_returns)
cum_returns(self.benchmark_returns).iloc[-1]
self.algorithm_period_returns = \
self.calculate_period_returns(self.algorithm_returns)
cum_returns(self.algorithm_returns).iloc[-1]
if not self.algorithm_returns.index.equals(
self.benchmark_returns.index
@@ -115,7 +113,6 @@ class RiskMetricsPeriod(object):
)
self.sharpe = sharpe_ratio(
self.algorithm_returns,
self.benchmark_returns
)
# The consumer currently expects a 0.0 value for sharpe in period,
# this differs from cumulative which was np.nan.
@@ -138,9 +135,6 @@ class RiskMetricsPeriod(object):
self.algorithm_returns,
self.benchmark_returns
)
self.algorithm_covariance, self.benchmark_variance, \
self.condition_number, self.eigen_values \
= self.calculate_covariance()
self.beta = beta(
self.algorithm_returns,
self.benchmark_returns
@@ -193,16 +187,12 @@ class RiskMetricsPeriod(object):
"sharpe",
"sortino",
"information",
"algorithm_covariance",
"benchmark_variance",
"beta",
"alpha",
"max_drawdown",
"max_leverage",
"algorithm_returns",
"benchmark_returns",
"condition_number",
"eigen_values"
]
for metric in metrics:
@@ -226,45 +216,6 @@ class RiskMetricsPeriod(object):
returns = returns[mask]
return returns
def calculate_period_returns(self, returns):
period_returns = (1. + returns).prod() - 1
return period_returns
def calculate_covariance(self):
"""
.. math::
\\beta_a = \\frac{\mathrm{Cov}(r_a,r_p)}{\mathrm{Var}(r_p)}
http://en.wikipedia.org/wiki/Beta_(finance)
"""
# it doesn't make much sense to calculate beta for less than two days,
# so return nan.
if len(self.algorithm_returns) < 2:
return np.nan, np.nan, np.nan, []
returns_matrix = np.vstack([self.algorithm_returns,
self.benchmark_returns])
C = np.cov(returns_matrix, ddof=1)
# If there are missing benchmark values, then we can't calculate the
# beta.
if not np.isfinite(C).all():
return np.nan, np.nan, np.nan, []
eigen_values = la.eigvals(C)
condition_number = max(eigen_values) / min(eigen_values)
algorithm_covariance = C[0][1]
benchmark_variance = C[1][1]
return (
algorithm_covariance,
benchmark_variance,
condition_number,
eigen_values
)
def calculate_max_leverage(self):
if self.algorithm_leverages is None:
return 0.0