Merge pull request #1322 from quantopian/move_risk_calculations

Move risk calculations
This commit is contained in:
Ana Ruelas
2016-08-23 18:39:22 -04:00
committed by GitHub
77 changed files with 624 additions and 1430 deletions
+8
View File
@@ -0,0 +1,8 @@
"%PYTHON%" setup.py install
if errorlevel 1 exit 1
:: Add more build steps here, if they are necessary.
:: See
:: http://docs.continuum.io/conda/build.html
:: for a list of environment variables that are set during the build process.
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
$PYTHON setup.py install
# Add more build steps here, if they are necessary.
# See
# http://docs.continuum.io/conda/build.html
# for a list of environment variables that are set during the build process.
+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
+21
View File
@@ -36,6 +36,10 @@ Enhancements
produced a True on N or more days in the previous ``window_length``
days (:issue:`1367`).
- Use external library empyrical for risk calculations. Empyrical unifies risk
metric calculations between pyfolio and zipline. Empyrical adds custom
annualization options for returns of custom frequencies. (:issue:`855`)
Bug Fixes
~~~~~~~~~
@@ -44,6 +48,23 @@ Bug Fixes
simply discarded before averaging, giving the remaining values too much
weight (:issue:`1309`).
- Remove risk-free rate from sharpe ratio calculation. The ratio is now the
average of risk adjusted returns over violatility of adjusted
returns. (:issue:`853`)
- Sortino ratio will return calculation instead of np.nan when required returns
are equal to zero. The ratio now returns the average of risk adjusted returns
over downside risk. Fixed mislabeled API by converting `mar` to
`downside_risk`. (:issue:`747`)
- Downside risk now returns the square root of the mean of downside
difference squares. (:issue:`747`)
- Information ratio updated to return mean of risk adjusted returns over
standard deviation of risk adjusted returns. (:issue:`1322`)
- Alpha and sharpe ratio are now annualized. (:issue:`1322`)
Documentation
~~~~~~~~~~~~~
+3
View File
@@ -62,3 +62,6 @@ sortedcontainers==1.4.4
intervaltree==2.1.0
cachetools==1.1.5
# For financial risk calculations
empyrical==0.1.9
Binary file not shown.
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
-87
View File
@@ -1,87 +0,0 @@
{
"metadata": {
"name": "AnswerKeyAnnotations"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": false,
"input": [
"#\n",
"# Copyright 2013 Quantopian, Inc.\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# http://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%load_ext autoreload\n",
"%autoreload 2"
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import datetime\n",
"import pandas as pd\n",
"from IPython.display import HTML\n",
"\n",
"import answer_key\n",
"ANSWER_KEY = answer_key.ANSWER_KEY"
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print 'Period Returns Index'\n",
"print ANSWER_KEY.RETURNS"
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"HTML(answer_key.RETURNS_DATA.to_html())"
],
"language": "python",
"outputs": []
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"ANSWER_KEY.ALGORITHM_CUMULATIVE_SHARPE"
],
"language": "python",
"outputs": []
}
]
}
]
}
-62
View File
@@ -1,62 +0,0 @@
{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": true,
"input": [
"#\n",
"# Copyright 2014 Quantopian, Inc.\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# http://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License.\n",
"\n",
"from annotation_utils import Markdown\n",
"import answer_key"
],
"language": "python",
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"Markdown(\"\"\"\n",
"Download link for latest answer key: [{latest_answer_key_url}]({latest_answer_key_url})\n",
"\"\"\".format(latest_answer_key_url=answer_key.LATEST_ANSWER_KEY_URL))"
],
"language": "python",
"outputs": [
{
"html": [
"<p>Download link for latest answer key: <a href=\"https://s3.amazonaws.com/zipline-test-data/risk/79d117cd4849745bf72ee1fd7442ef89/risk-answer-key.xlsx\">https://s3.amazonaws.com/zipline-test-data/risk/79d117cd4849745bf72ee1fd7442ef89/risk-answer-key.xlsx</a></p>"
],
"output_type": "pyout",
"prompt_number": 2,
"text": [
"'\\nDownload link for latest answer key: [https://s3.amazonaws.com/zipline-test-data/risk/79d117cd4849745bf72ee1fd7442ef89/risk-answer-key.xlsx](https://s3.amazonaws.com/zipline-test-data/risk/79d117cd4849745bf72ee1fd7442ef89/risk-answer-key.xlsx)\\n'"
]
}
],
"prompt_number": 2
}
]
}
]
}
-341
View File
@@ -1,341 +0,0 @@
#
# Copyright 2014 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import hashlib
import os
import numpy as np
import pandas as pd
import pytz
import xlrd
import requests
from six.moves import map
def col_letter_to_index(col_letter):
# Only supports single letter,
# but answer key doesn't need multi-letter, yet.
index = 0
for i, char in enumerate(reversed(col_letter)):
index += ((ord(char) - 65) + 1) * pow(26, i)
return index
DIR = os.path.dirname(os.path.realpath(__file__))
ANSWER_KEY_CHECKSUMS_PATH = os.path.join(DIR, 'risk-answer-key-checksums')
ANSWER_KEY_CHECKSUMS = open(ANSWER_KEY_CHECKSUMS_PATH, 'r').read().splitlines()
ANSWER_KEY_FILENAME = 'risk-answer-key.xlsx'
ANSWER_KEY_PATH = os.path.join(DIR, ANSWER_KEY_FILENAME)
ANSWER_KEY_BUCKET_NAME = 'zipline-test_data'
ANSWER_KEY_DL_TEMPLATE = """
https://s3.amazonaws.com/zipline-test-data/risk/{md5}/risk-answer-key.xlsx
""".strip()
LATEST_ANSWER_KEY_URL = ANSWER_KEY_DL_TEMPLATE.format(
md5=ANSWER_KEY_CHECKSUMS[-1])
def answer_key_signature():
with open(ANSWER_KEY_PATH, 'rb') as f:
md5 = hashlib.md5()
buf = f.read(1024)
md5.update(buf)
while buf != b"":
buf = f.read(1024)
md5.update(buf)
return md5.hexdigest()
def ensure_latest_answer_key():
"""
Get the latest answer key from a publically available location.
Logic for determining what and when to download is as such:
- If there is no local spreadsheet file, then get the lastest answer key,
as defined by the last row in the checksum file.
- If there is a local spreadsheet file:
-- If the spreadsheet's checksum is in the checksum file:
--- If the spreadsheet's checksum does not match the latest, then grab the
the latest checksum and replace the local checksum file.
--- If the spreadsheet's checksum matches the latest, then skip download,
and use the local spreadsheet as a cached copy.
-- If the spreadsheet's checksum is not in the checksum file, then leave
the local file alone, assuming that the local xls's md5 is not in the list
due to local modifications during development.
It is possible that md5's could collide, if that is ever case, we should
then find an alternative naming scheme.
The spreadsheet answer sheet is not kept in SCM, as every edit would
increase the repo size by the file size, since it is treated as a binary.
"""
answer_key_dl_checksum = None
local_answer_key_exists = os.path.exists(ANSWER_KEY_PATH)
if local_answer_key_exists:
local_hash = answer_key_signature()
if local_hash in ANSWER_KEY_CHECKSUMS:
# Assume previously downloaded version.
# Check for latest.
if local_hash != ANSWER_KEY_CHECKSUMS[-1]:
# More recent checksum, download
answer_key_dl_checksum = ANSWER_KEY_CHECKSUMS[-1]
else:
# Assume local copy that is being developed on
answer_key_dl_checksum = None
else:
answer_key_dl_checksum = ANSWER_KEY_CHECKSUMS[-1]
if answer_key_dl_checksum:
res = requests.get(
ANSWER_KEY_DL_TEMPLATE.format(md5=answer_key_dl_checksum))
with open(ANSWER_KEY_PATH, 'wb') as f:
f.write(res.content)
# Get latest answer key on load.
ensure_latest_answer_key()
class DataIndex(object):
"""
Coordinates for the spreadsheet, using the values as seen in the notebook.
The python-excel libraries use 0 index, while the spreadsheet in a GUI
uses a 1 index.
"""
def __init__(self, sheet_name, col, row_start, row_end,
value_type='float'):
self.sheet_name = sheet_name
self.col = col
self.row_start = row_start
self.row_end = row_end
self.value_type = value_type
@property
def col_index(self):
return col_letter_to_index(self.col) - 1
@property
def row_start_index(self):
return self.row_start - 1
@property
def row_end_index(self):
return self.row_end - 1
def __str__(self):
return "'{sheet_name}'!{col}{row_start}:{col}{row_end}".format(
sheet_name=self.sheet_name,
col=self.col,
row_start=self.row_start,
row_end=self.row_end
)
class AnswerKey(object):
INDEXES = {
'RETURNS': DataIndex('Sim Period', 'D', 4, 255),
'BENCHMARK': {
'Dates': DataIndex('s_p', 'A', 4, 254, value_type='date'),
'Returns': DataIndex('s_p', 'H', 4, 254)
},
# Below matches the inconsistent capitalization in spreadsheet
'BENCHMARK_PERIOD_RETURNS': {
'Monthly': DataIndex('s_p', 'R', 8, 19),
'3-Month': DataIndex('s_p', 'S', 10, 19),
'6-month': DataIndex('s_p', 'T', 13, 19),
'year': DataIndex('s_p', 'U', 19, 19),
},
'BENCHMARK_PERIOD_VOLATILITY': {
'Monthly': DataIndex('s_p', 'V', 8, 19),
'3-Month': DataIndex('s_p', 'W', 10, 19),
'6-month': DataIndex('s_p', 'X', 13, 19),
'year': DataIndex('s_p', 'Y', 19, 19),
},
'ALGORITHM_PERIOD_RETURNS': {
'Monthly': DataIndex('Sim Period', 'Z', 23, 34),
'3-Month': DataIndex('Sim Period', 'AA', 25, 34),
'6-month': DataIndex('Sim Period', 'AB', 28, 34),
'year': DataIndex('Sim Period', 'AC', 34, 34),
},
'ALGORITHM_PERIOD_VOLATILITY': {
'Monthly': DataIndex('Sim Period', 'AH', 23, 34),
'3-Month': DataIndex('Sim Period', 'AI', 25, 34),
'6-month': DataIndex('Sim Period', 'AJ', 28, 34),
'year': DataIndex('Sim Period', 'AK', 34, 34),
},
'ALGORITHM_PERIOD_SHARPE': {
'Monthly': DataIndex('Sim Period', 'AL', 23, 34),
'3-Month': DataIndex('Sim Period', 'AM', 25, 34),
'6-month': DataIndex('Sim Period', 'AN', 28, 34),
'year': DataIndex('Sim Period', 'AO', 34, 34),
},
'ALGORITHM_PERIOD_BETA': {
'Monthly': DataIndex('Sim Period', 'AP', 23, 34),
'3-Month': DataIndex('Sim Period', 'AQ', 25, 34),
'6-month': DataIndex('Sim Period', 'AR', 28, 34),
'year': DataIndex('Sim Period', 'AS', 34, 34),
},
'ALGORITHM_PERIOD_ALPHA': {
'Monthly': DataIndex('Sim Period', 'AT', 23, 34),
'3-Month': DataIndex('Sim Period', 'AU', 25, 34),
'6-month': DataIndex('Sim Period', 'AV', 28, 34),
'year': DataIndex('Sim Period', 'AW', 34, 34),
},
'ALGORITHM_PERIOD_BENCHMARK_VARIANCE': {
'Monthly': DataIndex('Sim Period', 'BJ', 23, 34),
'3-Month': DataIndex('Sim Period', 'BK', 25, 34),
'6-month': DataIndex('Sim Period', 'BL', 28, 34),
'year': DataIndex('Sim Period', 'BM', 34, 34),
},
'ALGORITHM_PERIOD_COVARIANCE': {
'Monthly': DataIndex('Sim Period', 'BF', 23, 34),
'3-Month': DataIndex('Sim Period', 'BG', 25, 34),
'6-month': DataIndex('Sim Period', 'BH', 28, 34),
'year': DataIndex('Sim Period', 'BI', 34, 34),
},
'ALGORITHM_PERIOD_DOWNSIDE_RISK': {
'Monthly': DataIndex('Sim Period', 'BN', 23, 34),
'3-Month': DataIndex('Sim Period', 'BO', 25, 34),
'6-month': DataIndex('Sim Period', 'BP', 28, 34),
'year': DataIndex('Sim Period', 'BQ', 34, 34),
},
'ALGORITHM_PERIOD_SORTINO': {
'Monthly': DataIndex('Sim Period', 'BR', 23, 34),
'3-Month': DataIndex('Sim Period', 'BS', 25, 34),
'6-month': DataIndex('Sim Period', 'BT', 28, 34),
'year': DataIndex('Sim Period', 'BU', 34, 34),
},
'ALGORITHM_RETURN_VALUES': DataIndex(
'Sim Cumulative', 'D', 4, 254),
'ALGORITHM_CUMULATIVE_VOLATILITY': DataIndex(
'Sim Cumulative', 'P', 4, 254),
'ALGORITHM_CUMULATIVE_SHARPE': DataIndex(
'Sim Cumulative', 'R', 4, 254),
'CUMULATIVE_DOWNSIDE_RISK': DataIndex(
'Sim Cumulative', 'U', 4, 254),
'CUMULATIVE_SORTINO': DataIndex(
'Sim Cumulative', 'V', 4, 254),
'CUMULATIVE_INFORMATION': DataIndex(
'Sim Cumulative', 'AA', 4, 254),
'CUMULATIVE_BETA': DataIndex(
'Sim Cumulative', 'AD', 4, 254),
'CUMULATIVE_ALPHA': DataIndex(
'Sim Cumulative', 'AE', 4, 254),
'CUMULATIVE_MAX_DRAWDOWN': DataIndex(
'Sim Cumulative', 'AH', 4, 254),
}
def __init__(self):
self.workbook = xlrd.open_workbook(ANSWER_KEY_PATH)
self.sheets = {}
self.sheets['Sim Period'] = self.workbook.sheet_by_name('Sim Period')
self.sheets['Sim Cumulative'] = self.workbook.sheet_by_name(
'Sim Cumulative')
self.sheets['s_p'] = self.workbook.sheet_by_name('s_p')
for name, index in self.INDEXES.items():
if isinstance(index, dict):
subvalues = {}
for subkey, subindex in index.items():
subvalues[subkey] = self.get_values(subindex)
setattr(self, name, subvalues)
else:
setattr(self, name, self.get_values(index))
def parse_date_value(self, value):
return xlrd.xldate_as_tuple(value, 0)
def parse_float_value(self, value):
return value if value != '' else np.nan
def get_raw_values(self, data_index):
return self.sheets[data_index.sheet_name].col_values(
data_index.col_index,
data_index.row_start_index,
data_index.row_end_index + 1)
@property
def value_type_to_value_func(self):
return {
'float': self.parse_float_value,
'date': self.parse_date_value,
}
def get_values(self, data_index):
value_parser = self.value_type_to_value_func[data_index.value_type]
return [value for value in
map(value_parser, self.get_raw_values(data_index))]
ANSWER_KEY = AnswerKey()
BENCHMARK_DATES = ANSWER_KEY.BENCHMARK['Dates']
BENCHMARK_RETURNS = ANSWER_KEY.BENCHMARK['Returns']
DATES = [datetime.datetime(*x, tzinfo=pytz.UTC) for x in BENCHMARK_DATES]
BENCHMARK = pd.Series(dict(zip(DATES, BENCHMARK_RETURNS)))
ALGORITHM_RETURNS = pd.Series(
dict(zip(DATES, ANSWER_KEY.ALGORITHM_RETURN_VALUES)))
RETURNS_DATA = pd.DataFrame({'Benchmark Returns': BENCHMARK,
'Algorithm Returns': ALGORITHM_RETURNS})
RISK_CUMULATIVE = pd.DataFrame({
'volatility': pd.Series(dict(zip(
DATES, ANSWER_KEY.ALGORITHM_CUMULATIVE_VOLATILITY))),
'sharpe': pd.Series(dict(zip(
DATES, ANSWER_KEY.ALGORITHM_CUMULATIVE_SHARPE))),
'downside_risk': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_DOWNSIDE_RISK))),
'sortino': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_SORTINO))),
'information': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_INFORMATION))),
'alpha': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_ALPHA))),
'beta': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_BETA))),
'max_drawdown': pd.Series(dict(zip(
DATES, ANSWER_KEY.CUMULATIVE_MAX_DRAWDOWN))),
})
-16
View File
@@ -1,16 +0,0 @@
3ac0773c4be4e9e5bacd9c6fa0e03e15
3a5fae958c8bac684f1773fa8dff7810
19d580890e211a122e9e746f07c80cbc
70cfe3677a0ff401c801b8628e125d8f
99b3855ef1b8963163c3cb8f7e05cb70
97dfb557c3501179504926e4079e6446
cc507b6fca18aabadac69657181edd4e
5b48e6a70181d73ecb7f07df5a3092e2
3343940379161143630503413627a53a
820235c4157a3c55474836438019ef2e
75c1b1441efbc2431215835a5079ccc6
37e3ea4a1788f1aa6f3ee0986bc625ae
651e611e723e2a58b1ded91d0cd39b66
d62fce39ec78f032165d8f356bba5c2c
97632f6f64dfc4a2de09882419a79421
79d117cd4849745bf72ee1fd7442ef89
+93 -70
View File
@@ -21,8 +21,13 @@ from zipline.utils import factory
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
BENCHMARK_BASE = 0.005
BENCHMARK = [BENCHMARK_BASE] * 251
DECIMAL_PLACES = 8
class TestRisk(WithTradingEnvironment, ZiplineTestCase):
@@ -38,86 +43,104 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
end_session=end_session,
trading_calendar=self.trading_calendar,
)
self.algo_returns_06 = factory.create_returns_from_list(
answer_key.ALGORITHM_RETURNS.values,
self.algo_returns = factory.create_returns_from_list(
RETURNS,
self.sim_params
)
self.cumulative_metrics_06 = risk.RiskMetricsCumulative(
self.cumulative_metrics = risk.RiskMetricsCumulative(
self.sim_params,
treasury_curves=self.env.treasury_curves,
trading_calendar=self.trading_calendar,
)
for dt, returns in self.algo_returns.iteritems():
self.cumulative_metrics.update(
dt,
returns,
BENCHMARK_BASE,
0.0
)
for dt, returns in answer_key.RETURNS_DATA.iterrows():
self.cumulative_metrics_06.update(dt,
returns['Algorithm Returns'],
returns['Benchmark Returns'],
0.0)
def test_algorithm_volatility(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.algorithm_volatility)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.algorithm_volatility),
True
)
def test_algorithm_volatility_06(self):
algo_vol_answers = answer_key.RISK_CUMULATIVE.volatility
for dt, value in algo_vol_answers.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
self.cumulative_metrics_06.algorithm_volatility[dt_loc],
value,
err_msg="Mismatch at %s" % (dt,))
def test_sharpe(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.sharpe)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.sharpe),
True)
def test_sharpe_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.sharpe.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
self.cumulative_metrics_06.sharpe[dt_loc],
value,
err_msg="Mismatch at %s" % (dt,))
def test_downside_risk(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.downside_risk)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.downside_risk),
True)
def test_downside_risk_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.downside_risk.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
value,
self.cumulative_metrics_06.downside_risk[dt_loc],
err_msg="Mismatch at %s" % (dt,))
def test_sortino(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.sortino)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.sortino),
True)
def test_sortino_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.sortino.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
self.cumulative_metrics_06.sortino[dt_loc],
value,
decimal=4,
err_msg="Mismatch at %s" % (dt,))
def test_information(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.information)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.information),
True)
def test_information_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.information.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
value,
self.cumulative_metrics_06.information[dt_loc],
err_msg="Mismatch at %s" % (dt,))
def test_alpha(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.alpha)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.alpha),
True)
def test_alpha_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.alpha.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
self.cumulative_metrics_06.alpha[dt_loc],
value,
err_msg="Mismatch at %s" % (dt,))
def test_beta(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.beta)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.beta),
True)
def test_beta_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.beta.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
value,
self.cumulative_metrics_06.beta[dt_loc],
err_msg="Mismatch at %s" % (dt,))
def test_max_drawdown(self):
np.testing.assert_equal(
len(self.algo_returns),
len(self.cumulative_metrics.max_drawdowns)
)
np.testing.assert_equal(
all(isinstance(x, float)
for x in self.cumulative_metrics.max_drawdowns),
True)
def test_max_drawdown_06(self):
for dt, value in answer_key.RISK_CUMULATIVE.max_drawdown.iteritems():
dt_loc = self.cumulative_metrics_06.cont_index.get_loc(dt)
np.testing.assert_almost_equal(
self.cumulative_metrics_06.max_drawdowns[dt_loc],
value,
err_msg="Mismatch at %s" % (dt,))
def test_representation(self):
assert all([metric in self.cumulative_metrics.__repr__() for metric in
self.cumulative_metrics.METRIC_NAMES])
+337 -395
View File
@@ -17,444 +17,302 @@ import datetime
import calendar
import pandas as pd
import numpy as np
import pytz
from itertools import chain
from six import itervalues
import zipline.finance.risk as risk
from zipline.utils import factory
from zipline.finance.trading import SimulationParameters
from zipline.testing.fixtures import WithTradingEnvironment, ZiplineTestCase
from . import answer_key
from . answer_key import AnswerKey
ANSWER_KEY = AnswerKey()
from zipline.finance.risk.period import RiskMetricsPeriod
RETURNS = ANSWER_KEY.RETURNS
RETURNS_BASE = 0.01
RETURNS = [RETURNS_BASE] * 251
BENCHMARK_BASE = 0.005
BENCHMARK = [BENCHMARK_BASE] * 251
DECIMAL_PLACES = 8
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_06 = factory.create_returns_from_list(
self.algo_returns = factory.create_returns_from_list(
RETURNS,
self.sim_params
)
self.benchmark_returns_06 = \
answer_key.RETURNS_DATA['Benchmark Returns']
self.metrics_06 = risk.RiskReport(
self.algo_returns_06,
self.benchmark_returns = factory.create_returns_from_list(
BENCHMARK,
self.sim_params
)
self.metrics = risk.RiskReport(
self.algo_returns,
self.sim_params,
benchmark_returns=self.benchmark_returns_06,
benchmark_returns=self.benchmark_returns,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
self.sim_params08 = SimulationParameters(
start_session=pd.Timestamp("2008-01-01", tz='UTC'),
end_session=pd.Timestamp("2008-12-31", tz='UTC'),
trading_calendar=self.trading_calendar,
)
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] <=
datetime.datetime(
year=2006, month=12, day=31, tzinfo=pytz.utc))
pd.Timestamp('2006-12-31', tz='UTC'))
def test_drawdown(self):
returns = factory.create_returns_from_list(
[1.0, -0.5, 0.8, .17, 1.0, -0.1, -0.45], self.sim_params)
# 200, 100, 180, 210.6, 421.2, 379.8, 208.494
metrics = risk.RiskMetricsPeriod(
returns.index[0],
returns.index[-1],
returns,
trading_calendar=self.trading_calendar,
benchmark_returns=self.env.benchmark_returns,
treasury_curves=self.env.treasury_curves,
)
self.assertEqual(metrics.max_drawdown, 0.505)
np.testing.assert_equal(
all(x.max_drawdown == 0 for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(x.max_drawdown == 0 for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(x.max_drawdown == 0 for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(x.max_drawdown == 0 for x in self.metrics.year_periods),
True)
def test_benchmark_returns_06(self):
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics.month_periods],
[(1 + BENCHMARK_BASE) ** len(x.benchmark_returns) - 1
for x in self.metrics.month_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics.three_month_periods],
[(1 + BENCHMARK_BASE) ** len(x.benchmark_returns) - 1
for x in self.metrics.three_month_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics.six_month_periods],
[(1 + BENCHMARK_BASE) ** len(x.benchmark_returns) - 1
for x in self.metrics.six_month_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics.year_periods],
[(1 + BENCHMARK_BASE) ** len(x.benchmark_returns) - 1
for x in self.metrics.year_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics_06.month_periods],
ANSWER_KEY.BENCHMARK_PERIOD_RETURNS['Monthly'])
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics_06.three_month_periods],
ANSWER_KEY.BENCHMARK_PERIOD_RETURNS['3-Month'])
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics_06.six_month_periods],
ANSWER_KEY.BENCHMARK_PERIOD_RETURNS['6-month'])
np.testing.assert_almost_equal(
[x.benchmark_period_returns
for x in self.metrics_06.year_periods],
ANSWER_KEY.BENCHMARK_PERIOD_RETURNS['year'])
def test_trading_days_06(self):
returns = factory.create_returns_from_range(self.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.assertEqual([x.num_trading_days for x in metrics.year_periods],
def test_trading_days(self):
self.assertEqual([x.num_trading_days
for x in self.metrics.year_periods],
[251])
self.assertEqual([x.num_trading_days for x in metrics.month_periods],
self.assertEqual([x.num_trading_days
for x in self.metrics.month_periods],
[20, 19, 23, 19, 22, 22, 20, 23, 20, 22, 21, 20])
def test_benchmark_volatility_06(self):
def test_benchmark_volatility(self):
# 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(
all(isinstance(x.benchmark_volatility, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.benchmark_volatility, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.benchmark_volatility, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.benchmark_volatility, float)
for x in self.metrics.year_periods),
True)
np.testing.assert_almost_equal(
[x.benchmark_volatility
for x in self.metrics_06.month_periods],
ANSWER_KEY.BENCHMARK_PERIOD_VOLATILITY['Monthly'])
np.testing.assert_almost_equal(
[x.benchmark_volatility
for x in self.metrics_06.three_month_periods],
ANSWER_KEY.BENCHMARK_PERIOD_VOLATILITY['3-Month'])
np.testing.assert_almost_equal(
[x.benchmark_volatility
for x in self.metrics_06.six_month_periods],
ANSWER_KEY.BENCHMARK_PERIOD_VOLATILITY['6-month'])
np.testing.assert_almost_equal(
[x.benchmark_volatility
for x in self.metrics_06.year_periods],
ANSWER_KEY.BENCHMARK_PERIOD_VOLATILITY['year'])
def test_algorithm_returns_06(self):
def test_algorithm_returns(self):
np.testing.assert_almost_equal(
[x.algorithm_period_returns
for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_RETURNS['Monthly'])
for x in self.metrics.month_periods],
[(1 + RETURNS_BASE) ** len(x.algorithm_returns) - 1
for x in self.metrics.month_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.algorithm_period_returns
for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_RETURNS['3-Month'])
for x in self.metrics.three_month_periods],
[(1 + RETURNS_BASE) ** len(x.algorithm_returns) - 1
for x in self.metrics.three_month_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.algorithm_period_returns
for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_RETURNS['6-month'])
for x in self.metrics.six_month_periods],
[(1 + RETURNS_BASE) ** len(x.algorithm_returns) - 1
for x in self.metrics.six_month_periods],
DECIMAL_PLACES)
np.testing.assert_almost_equal(
[x.algorithm_period_returns
for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_RETURNS['year'])
for x in self.metrics.year_periods],
[(1 + RETURNS_BASE) ** len(x.algorithm_returns) - 1
for x in self.metrics.year_periods],
DECIMAL_PLACES)
def test_algorithm_volatility_06(self):
np.testing.assert_almost_equal(
[x.algorithm_volatility
for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_VOLATILITY['Monthly'])
np.testing.assert_almost_equal(
[x.algorithm_volatility
for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_VOLATILITY['3-Month'])
np.testing.assert_almost_equal(
[x.algorithm_volatility
for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_VOLATILITY['6-month'])
np.testing.assert_almost_equal(
[x.algorithm_volatility
for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_VOLATILITY['year'])
def test_algorithm_volatility(self):
# 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(
all(isinstance(x.algorithm_volatility, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.algorithm_volatility, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.algorithm_volatility, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.algorithm_volatility, float)
for x in self.metrics.year_periods),
True)
def test_algorithm_sharpe_06(self):
np.testing.assert_almost_equal(
[x.sharpe for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SHARPE['Monthly'])
np.testing.assert_almost_equal(
[x.sharpe for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SHARPE['3-Month'])
np.testing.assert_almost_equal(
[x.sharpe for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SHARPE['6-month'])
np.testing.assert_almost_equal(
[x.sharpe for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SHARPE['year'])
def test_algorithm_sharpe(self):
# 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(
all(isinstance(x.sharpe, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.sharpe, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.sharpe, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.sharpe, float)
for x in self.metrics.year_periods),
True)
def test_algorithm_downside_risk_06(self):
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['Monthly'],
decimal=4)
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['3-Month'],
decimal=4)
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['6-month'],
decimal=4)
np.testing.assert_almost_equal(
[x.downside_risk for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_DOWNSIDE_RISK['year'],
decimal=4)
def test_algorithm_downside_risk(self):
# 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(
all(isinstance(x.downside_risk, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.downside_risk, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.downside_risk, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.downside_risk, float)
for x in self.metrics.year_periods),
True)
def test_algorithm_sortino_06(self):
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['Monthly'],
decimal=3)
def test_algorithm_sortino(self):
# 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(
all(isinstance(x.sortino, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.sortino, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.sortino, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.sortino, float)
for x in self.metrics.year_periods),
True)
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['3-Month'],
decimal=3)
def test_algorithm_information(self):
# 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),
True)
np.testing.assert_equal(
all(isinstance(x.information, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.information, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.information, float)
for x in self.metrics.year_periods),
True)
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['6-month'],
decimal=3)
def test_algorithm_beta(self):
# 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(
all(isinstance(x.beta, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.beta, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.beta, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.beta, float)
for x in self.metrics.year_periods),
True)
np.testing.assert_almost_equal(
[x.sortino for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_SORTINO['year'],
decimal=3)
def test_algorithm_alpha(self):
# 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(
all(isinstance(x.alpha, float)
for x in self.metrics.month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.alpha, float)
for x in self.metrics.three_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.alpha, float)
for x in self.metrics.six_month_periods),
True)
np.testing.assert_equal(
all(isinstance(x.alpha, float)
for x in self.metrics.year_periods),
True)
def test_algorithm_information_06(self):
self.assertEqual([round(x.information, 3)
for x in self.metrics_06.month_periods],
[0.131,
-0.11,
-0.067,
0.136,
0.301,
-0.387,
0.107,
-0.032,
-0.058,
0.069,
0.095,
-0.123])
self.assertEqual([round(x.information, 3)
for x in self.metrics_06.three_month_periods],
[-0.013,
-0.009,
0.111,
-0.014,
-0.017,
-0.108,
0.011,
-0.004,
0.032,
0.011])
self.assertEqual([round(x.information, 3)
for x in self.metrics_06.six_month_periods],
[-0.013,
-0.014,
-0.003,
-0.002,
-0.011,
-0.041,
0.011])
self.assertEqual([round(x.information, 3)
for x in self.metrics_06.year_periods],
[-0.001])
def test_algorithm_beta_06(self):
np.testing.assert_almost_equal(
[x.beta for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BETA['Monthly'])
np.testing.assert_almost_equal(
[x.beta for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BETA['3-Month'])
np.testing.assert_almost_equal(
[x.beta for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BETA['6-month'])
np.testing.assert_almost_equal(
[x.beta for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BETA['year'])
def test_algorithm_alpha_06(self):
np.testing.assert_almost_equal(
[x.alpha for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_ALPHA['Monthly'])
np.testing.assert_almost_equal(
[x.alpha for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_ALPHA['3-Month'])
np.testing.assert_almost_equal(
[x.alpha for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_ALPHA['6-month'])
np.testing.assert_almost_equal(
[x.alpha for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_ALPHA['year'])
# FIXME: Covariance is not matching excel precisely enough to run the test.
# Month 4 seems to be the problem. Variance is disabled
# just to avoid distraction - it is much closer than covariance
# and can probably pass with 6 significant digits instead of 7.
# re-enable variance, alpha, and beta tests once this is resolved
def test_algorithm_covariance_06(self):
np.testing.assert_almost_equal(
[x.algorithm_covariance for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_COVARIANCE['Monthly'])
np.testing.assert_almost_equal(
[x.algorithm_covariance
for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_COVARIANCE['3-Month'])
np.testing.assert_almost_equal(
[x.algorithm_covariance
for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_COVARIANCE['6-month'])
np.testing.assert_almost_equal(
[x.algorithm_covariance
for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_COVARIANCE['year'])
def test_benchmark_variance_06(self):
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics_06.month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BENCHMARK_VARIANCE['Monthly'])
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics_06.three_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BENCHMARK_VARIANCE['3-Month'])
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics_06.six_month_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BENCHMARK_VARIANCE['6-month'])
np.testing.assert_almost_equal(
[x.benchmark_variance
for x in self.metrics_06.year_periods],
ANSWER_KEY.ALGORITHM_PERIOD_BENCHMARK_VARIANCE['year'])
def test_benchmark_returns_08(self):
returns = factory.create_returns_from_range(self.sim_params08)
metrics = risk.RiskReport(returns, self.sim_params08,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.assertEqual([round(x.benchmark_period_returns, 3)
for x in metrics.month_periods],
[-0.061,
-0.035,
-0.006,
0.048,
0.011,
-0.086,
-0.01,
0.012,
-0.091,
-0.169,
-0.075,
0.008])
self.assertEqual([round(x.benchmark_period_returns, 3)
for x in metrics.three_month_periods],
[-0.099,
0.005,
0.052,
-0.032,
-0.085,
-0.084,
-0.089,
-0.236,
-0.301,
-0.226])
self.assertEqual([round(x.benchmark_period_returns, 3)
for x in metrics.six_month_periods],
[-0.128,
-0.081,
-0.036,
-0.118,
-0.301,
-0.36,
-0.294])
self.assertEqual([round(x.benchmark_period_returns, 3)
for x in metrics.year_periods],
[-0.385])
def test_trading_days_08(self):
returns = factory.create_returns_from_range(self.sim_params08)
metrics = risk.RiskReport(returns, self.sim_params08,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.assertEqual([x.num_trading_days for x in metrics.year_periods],
[253])
self.assertEqual([x.num_trading_days for x in metrics.month_periods],
[21, 20, 20, 22, 21, 21, 22, 21, 21, 23, 19, 22])
def test_benchmark_volatility_08(self):
returns = factory.create_returns_from_range(self.sim_params08)
metrics = risk.RiskReport(returns, self.sim_params08,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.assertEqual([round(x.benchmark_volatility, 3)
for x in metrics.month_periods],
[0.07,
0.058,
0.082,
0.054,
0.041,
0.057,
0.068,
0.06,
0.157,
0.244,
0.195,
0.145])
self.assertEqual([round(x.benchmark_volatility, 3)
for x in metrics.three_month_periods],
[0.12,
0.113,
0.105,
0.09,
0.098,
0.107,
0.179,
0.293,
0.344,
0.34])
self.assertEqual([round(x.benchmark_volatility, 3)
for x in metrics.six_month_periods],
[0.15,
0.149,
0.15,
0.2,
0.308,
0.36,
0.383])
# TODO: ugly, but I can't get the rounded float to match.
# maybe we need a different test that checks the
# difference between the numbers
self.assertEqual([round(x.benchmark_volatility, 3)
for x in metrics.year_periods],
[0.411])
def test_treasury_returns_06(self):
def test_treasury_returns(self):
returns = factory.create_returns_from_range(self.sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
trading_calendar=self.trading_calendar,
@@ -523,10 +381,6 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
benchmark_returns=self.env.benchmark_returns)
self.check_metrics(metrics, 24, start_session)
# self.check_year_range(
# datetime.datetime(
# year=2008, month=1, day=1, tzinfo=pytz.utc),
# 2)
def test_partial_month(self):
@@ -626,17 +480,105 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
self.assert_month(start_date.month, col[-1]._end_session.month)
self.assert_last_day(col[-1]._end_session)
def test_sparse_benchmark(self):
benchmark_returns = self.benchmark_returns_06.copy()
# Set every other day to nan.
benchmark_returns.iloc[::2] = np.nan
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])
report = risk.RiskReport(
self.algo_returns_06,
self.sim_params,
benchmark_returns=benchmark_returns,
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,
)
for risk_period in chain.from_iterable(itervalues(report.to_dict())):
self.assertIsNone(risk_period['beta'])
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])
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2013 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Utility script for maintainer use to upload current version of the answer key
spreadsheet to S3.
"""
import hashlib
import boto
from . import answer_key
BUCKET_NAME = 'zipline-test-data'
def main():
with open(answer_key.ANSWER_KEY_PATH, 'r') as f:
md5 = hashlib.md5()
while True:
buf = f.read(1024)
if not buf:
break
md5.update(buf)
local_hash = md5.hexdigest()
s3_conn = boto.connect_s3()
bucket = s3_conn.get_bucket(BUCKET_NAME)
key = boto.s3.key.Key(bucket)
key.key = "risk/{local_hash}/risk-answer-key.xlsx".format(
local_hash=local_hash)
key.set_contents_from_filename(answer_key.ANSWER_KEY_PATH)
key.set_acl('public-read')
download_link = "http://s3.amazonaws.com/{bucket_name}/{key}".format(
bucket_name=BUCKET_NAME,
key=key.key)
print("Uploaded to key: {key}".format(key=key.key))
print("Download link: {download_link}".format(download_link=download_link))
# Now update checksum file with the recently added answer key.
# checksum file update will be then need to be commited via git.
with open(answer_key.ANSWER_KEY_CHECKSUMS_PATH, 'a') as checksum_file:
checksum_file.write(local_hash)
checksum_file.write("\n")
if __name__ == "__main__":
main()
+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
+52 -146
View File
@@ -15,23 +15,28 @@
import functools
import logbook
import math
import numpy as np
import zipline.utils.math_utils as zp_math
import pandas as pd
from pandas.tseries.tools import normalize_date
from six import iteritems
from . risk import (
alpha,
check_entry,
choose_treasury,
choose_treasury
)
from empyrical import (
alpha,
annual_volatility,
beta,
cum_returns,
downside_risk,
information_ratio,
max_drawdown,
sharpe_ratio,
sortino_ratio,
sortino_ratio
)
log = logbook.Logger('Risk Cumulative')
@@ -41,33 +46,6 @@ choose_treasury = functools.partial(choose_treasury, lambda *args: '10year',
compound=False)
def information_ratio(algo_volatility, algorithm_return, benchmark_return):
"""
http://en.wikipedia.org/wiki/Information_ratio
Args:
algorithm_returns (np.array-like):
All returns during algorithm lifetime.
benchmark_returns (np.array-like):
All benchmark returns during algo lifetime.
Returns:
float. Information ratio.
"""
if zp_math.tolerant_equals(algo_volatility, 0):
return np.nan
# The square of the annualization factor is in the volatility,
# because the volatility is also annualized,
# i.e. the sqrt(annual factor) is in the volatility's numerator.
# So to have the the correct annualization factor for the
# Sharpe value's numerator, which should be the sqrt(annual factor).
# The square of the sqrt of the annual factor, i.e. the annual factor
# itself, is needed in the numerator to factor out the division by
# its square root.
return (algorithm_return - benchmark_return) / algo_volatility
class RiskMetricsCumulative(object):
"""
:Usage:
@@ -174,6 +152,7 @@ class RiskMetricsCumulative(object):
self.algorithm_returns_cont[dt_loc] = algorithm_returns
self.algorithm_returns = self.algorithm_returns_cont[:dt_loc + 1]
algorithm_returns_series = pd.Series(self.algorithm_returns)
self.num_trading_days = len(self.algorithm_returns)
@@ -181,8 +160,9 @@ class RiskMetricsCumulative(object):
if len(self.algorithm_returns) == 1:
self.algorithm_returns = np.append(0.0, self.algorithm_returns)
self.algorithm_cumulative_returns[dt_loc] = \
self.calculate_cumulative_returns(self.algorithm_returns)
self.algorithm_cumulative_returns[dt_loc] = cum_returns(
algorithm_returns_series
).iloc[-1]
algo_cumulative_returns_to_date = \
self.algorithm_cumulative_returns[:dt_loc + 1]
@@ -206,13 +186,14 @@ class RiskMetricsCumulative(object):
self.benchmark_returns_cont[dt_loc] = benchmark_returns
self.benchmark_returns = self.benchmark_returns_cont[:dt_loc + 1]
benchmark_returns_series = pd.Series(self.benchmark_returns)
if self.create_first_day_stats:
if len(self.benchmark_returns) == 1:
self.benchmark_returns = np.append(0.0, self.benchmark_returns)
self.benchmark_cumulative_returns[dt_loc] = \
self.calculate_cumulative_returns(self.benchmark_returns)
self.benchmark_cumulative_returns[dt_loc] = cum_returns(
benchmark_returns_series
).iloc[-1]
benchmark_cumulative_returns_to_date = \
self.benchmark_cumulative_returns[:dt_loc + 1]
@@ -252,10 +233,12 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
raise Exception(message)
self.update_current_max()
self.benchmark_volatility[dt_loc] = \
self.calculate_volatility(self.benchmark_returns)
self.algorithm_volatility[dt_loc] = \
self.calculate_volatility(self.algorithm_returns)
self.benchmark_volatility[dt_loc] = annual_volatility(
benchmark_returns_series
)
self.algorithm_volatility[dt_loc] = annual_volatility(
algorithm_returns_series
)
# caching the treasury rates for the minutely case is a
# big speedup, because it avoids searching the treasury
@@ -274,14 +257,33 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
self.excess_returns[dt_loc] = (
self.algorithm_cumulative_returns[dt_loc] -
self.treasury_period_return)
self.beta[dt_loc] = self.calculate_beta()
self.alpha[dt_loc] = self.calculate_alpha()
self.sharpe[dt_loc] = self.calculate_sharpe()
self.downside_risk[dt_loc] = \
self.calculate_downside_risk()
self.sortino[dt_loc] = self.calculate_sortino()
self.information[dt_loc] = self.calculate_information()
self.max_drawdown = self.calculate_max_drawdown()
self.beta[dt_loc] = beta(
algorithm_returns_series,
benchmark_returns_series
)
self.alpha[dt_loc] = alpha(
algorithm_returns_series,
benchmark_returns_series
)
self.sharpe[dt_loc] = sharpe_ratio(
algorithm_returns_series,
benchmark_returns_series
)
self.downside_risk[dt_loc] = downside_risk(
algorithm_returns_series,
benchmark_returns_series
)
self.sortino[dt_loc] = sortino_ratio(
algorithm_returns_series,
benchmark_returns_series
)
self.information[dt_loc] = information_ratio(
algorithm_returns_series,
benchmark_returns_series
)
self.max_drawdown = max_drawdown(
algorithm_returns_series
)
self.max_drawdowns[dt_loc] = self.max_drawdown
self.max_leverage = self.calculate_max_leverage()
self.max_leverages[dt_loc] = self.max_leverage
@@ -336,9 +338,6 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
return '\n'.join(statements)
def calculate_cumulative_returns(self, returns):
return (1. + returns).prod() - 1
def update_current_max(self):
if len(self.algorithm_cumulative_returns) == 0:
return
@@ -347,29 +346,6 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
if self.current_max < current_cumulative_return:
self.current_max = current_cumulative_return
def calculate_max_drawdown(self):
if len(self.algorithm_cumulative_returns) == 0:
return self.max_drawdown
# The drawdown is defined as: (high - low) / high
# The above factors out to: 1.0 - (low / high)
#
# Instead of explicitly always using the low, use the current total
# return value, and test that against the max drawdown, which will
# exceed the previous max_drawdown iff the current return is lower than
# the previous low in the current drawdown window.
cur_drawdown = 1.0 - (
(1.0 + self.algorithm_cumulative_returns[self.latest_dt_loc])
/
(1.0 + self.current_max))
self.drawdowns[self.latest_dt_loc] = cur_drawdown
if self.max_drawdown < cur_drawdown:
return cur_drawdown
else:
return self.max_drawdown
def calculate_max_leverage(self):
# The leverage is defined as: the gross_exposure/net_liquidation
# gross_exposure = long_exposure + abs(short_exposure)
@@ -378,73 +354,3 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
self.latest_dt_loc]
return max(cur_leverage, self.max_leverage)
def calculate_sharpe(self):
"""
http://en.wikipedia.org/wiki/Sharpe_ratio
"""
return sharpe_ratio(
self.algorithm_volatility[self.latest_dt_loc],
self.annualized_mean_returns_cont[self.latest_dt_loc],
self.daily_treasury[self.latest_dt.date()])
def calculate_sortino(self):
"""
http://en.wikipedia.org/wiki/Sortino_ratio
"""
return sortino_ratio(
self.annualized_mean_returns_cont[self.latest_dt_loc],
self.daily_treasury[self.latest_dt.date()],
self.downside_risk[self.latest_dt_loc])
def calculate_information(self):
"""
http://en.wikipedia.org/wiki/Information_ratio
"""
return information_ratio(
self.algorithm_volatility[self.latest_dt_loc],
self.annualized_mean_returns_cont[self.latest_dt_loc],
self.annualized_mean_benchmark_returns_cont[self.latest_dt_loc])
def calculate_alpha(self):
"""
http://en.wikipedia.org/wiki/Alpha_(investment)
"""
return alpha(
self.annualized_mean_returns_cont[self.latest_dt_loc],
self.treasury_period_return,
self.annualized_mean_benchmark_returns_cont[self.latest_dt_loc],
self.beta[self.latest_dt_loc])
def calculate_volatility(self, daily_returns):
if len(daily_returns) <= 1:
return 0.0
return np.std(daily_returns, ddof=1) * math.sqrt(252)
def calculate_downside_risk(self):
return downside_risk(self.algorithm_returns,
self.mean_returns,
252)
def calculate_beta(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
# values, so return none.
if len(self.algorithm_returns) < 2:
return 0.0
returns_matrix = np.vstack([self.algorithm_returns,
self.benchmark_returns])
C = np.cov(returns_matrix, ddof=1)
algorithm_covariance = C[0][1]
benchmark_variance = C[1][1]
beta = algorithm_covariance / benchmark_variance
return beta
+37 -135
View File
@@ -16,22 +16,24 @@
import functools
import logbook
import math
import numpy as np
import numpy.linalg as la
from six import iteritems
import pandas as pd
from . import risk
from . risk import (
from . risk import check_entry
from empyrical import (
alpha,
check_entry,
annual_volatility,
beta,
cum_returns,
downside_risk,
information_ratio,
max_drawdown,
sharpe_ratio,
sortino_ratio,
sortino_ratio
)
log = logbook.Logger('Risk Period')
@@ -43,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))
@@ -76,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
@@ -101,17 +102,18 @@ class RiskMetricsPeriod(object):
self.mean_algorithm_returns = \
self.algorithm_returns.cumsum() / self.trading_day_counts
self.benchmark_volatility = self.calculate_volatility(
self.benchmark_returns)
self.algorithm_volatility = self.calculate_volatility(
self.algorithm_returns)
self.benchmark_volatility = annual_volatility(self.benchmark_returns)
self.algorithm_volatility = annual_volatility(self.algorithm_returns)
self.treasury_period_return = choose_treasury(
self.treasury_curves,
self._start_session,
self._end_session,
self.trading_calendar,
)
self.sharpe = self.calculate_sharpe()
self.sharpe = sharpe_ratio(
self.algorithm_returns,
)
# The consumer currently expects a 0.0 value for sharpe in period,
# this differs from cumulative which was np.nan.
# When factoring out the sharpe_ratio, the different return types
@@ -121,14 +123,29 @@ class RiskMetricsPeriod(object):
# In the meantime, convert nan values to 0.0
if pd.isnull(self.sharpe):
self.sharpe = 0.0
self.sortino = self.calculate_sortino()
self.information = self.calculate_information()
self.beta, self.algorithm_covariance, self.benchmark_variance, \
self.condition_number, self.eigen_values = self.calculate_beta()
self.alpha = self.calculate_alpha()
self.downside_risk = downside_risk(
self.algorithm_returns,
self.benchmark_returns
)
self.sortino = sortino_ratio(
self.algorithm_returns,
self.benchmark_returns
)
self.information = information_ratio(
self.algorithm_returns,
self.benchmark_returns
)
self.beta = beta(
self.algorithm_returns,
self.benchmark_returns
)
self.alpha = alpha(
self.algorithm_returns,
self.benchmark_returns
)
self.excess_return = self.algorithm_period_returns - \
self.treasury_period_return
self.max_drawdown = self.calculate_max_drawdown()
self.max_drawdown = max_drawdown(self.algorithm_returns)
self.max_leverage = self.calculate_max_leverage()
def to_dict(self):
@@ -170,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:
@@ -203,117 +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_volatility(self, daily_returns):
return np.std(daily_returns, ddof=1) * math.sqrt(self.num_trading_days)
def calculate_sharpe(self):
"""
http://en.wikipedia.org/wiki/Sharpe_ratio
"""
return sharpe_ratio(self.algorithm_volatility,
self.algorithm_period_returns,
self.treasury_period_return)
def calculate_sortino(self):
"""
http://en.wikipedia.org/wiki/Sortino_ratio
"""
mar = downside_risk(self.algorithm_returns,
self.mean_algorithm_returns,
self.num_trading_days)
# Hold on to downside risk for debugging purposes.
self.downside_risk = mar
return sortino_ratio(self.algorithm_period_returns,
self.treasury_period_return,
mar)
def calculate_information(self):
"""
http://en.wikipedia.org/wiki/Information_ratio
"""
return information_ratio(self.algorithm_returns,
self.benchmark_returns)
def calculate_beta(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, 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, 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]
beta = algorithm_covariance / benchmark_variance
return (
beta,
algorithm_covariance,
benchmark_variance,
condition_number,
eigen_values
)
def calculate_alpha(self):
"""
http://en.wikipedia.org/wiki/Alpha_(investment)
"""
return alpha(self.algorithm_period_returns,
self.treasury_period_return,
self.benchmark_period_returns,
self.beta)
def calculate_max_drawdown(self):
compounded_returns = []
cur_return = 0.0
for r in self.algorithm_returns:
try:
cur_return += math.log(1.0 + r)
# this is a guard for a single day returning -100%, if returns are
# greater than -1.0 it will throw an error because you cannot take
# the log of a negative number
except ValueError:
log.debug("{cur} return, zeroing the returns".format(
cur=cur_return))
cur_return = 0.0
compounded_returns.append(cur_return)
cur_max = None
max_drawdown = None
for cur in compounded_returns:
if cur_max is None or cur > cur_max:
cur_max = cur
drawdown = (cur - cur_max)
if max_drawdown is None or drawdown < max_drawdown:
max_drawdown = drawdown
if max_drawdown is None:
return 0.0
return 1.0 - math.exp(max_drawdown)
def calculate_max_leverage(self):
if self.algorithm_leverages is None:
return 0.0
+1 -1
View File
@@ -113,7 +113,7 @@ class RiskReport(object):
- 6_month
- 12_month
The return value of this funciton is a dictionary keyed by the above
The return value of this function is a dictionary keyed by the above
list of durations. The value of each entry is a list of RiskMetric
dicts of the same duration as denoted by the top_level key.
-107
View File
@@ -59,11 +59,8 @@ Risk Report
"""
import logbook
import math
import numpy as np
import zipline.utils.math_utils as zp_math
log = logbook.Logger('Risk')
@@ -83,110 +80,6 @@ def check_entry(key, value):
return False
############################
# Risk Metric Calculations #
############################
def sharpe_ratio(algorithm_volatility, algorithm_return, treasury_return):
"""
http://en.wikipedia.org/wiki/Sharpe_ratio
Args:
algorithm_volatility (float): Algorithm volatility.
algorithm_return (float): Algorithm return percentage.
treasury_return (float): Treasury return percentage.
Returns:
float. The Sharpe ratio.
"""
if zp_math.tolerant_equals(algorithm_volatility, 0):
return np.nan
return (algorithm_return - treasury_return) / algorithm_volatility
def downside_risk(algorithm_returns, mean_returns, normalization_factor):
rets = algorithm_returns.round(8)
mar = mean_returns.round(8)
mask = rets < mar
downside_diff = rets[mask] - mar[mask]
if len(downside_diff) <= 1:
return 0.0
return np.std(downside_diff, ddof=1) * math.sqrt(normalization_factor)
def sortino_ratio(algorithm_period_return, treasury_period_return, mar):
"""
http://en.wikipedia.org/wiki/Sortino_ratio
Args:
algorithm_returns (np.array-like):
Returns from algorithm lifetime.
algorithm_period_return (float):
Algorithm return percentage from latest period.
mar (float): Minimum acceptable return.
Returns:
float. The Sortino ratio.
"""
if zp_math.tolerant_equals(mar, 0):
return 0.0
return (algorithm_period_return - treasury_period_return) / mar
def information_ratio(algorithm_returns, benchmark_returns):
"""
http://en.wikipedia.org/wiki/Information_ratio
Args:
algorithm_returns (np.array-like):
All returns during algorithm lifetime.
benchmark_returns (np.array-like):
All benchmark returns during algo lifetime.
Returns:
float. Information ratio.
"""
relative_returns = algorithm_returns - benchmark_returns
relative_deviation = relative_returns.std(ddof=1)
if zp_math.tolerant_equals(relative_deviation, 0) or \
np.isnan(relative_deviation):
return 0.0
return np.mean(relative_returns) / relative_deviation
def alpha(algorithm_period_return, treasury_period_return,
benchmark_period_returns, beta):
"""
http://en.wikipedia.org/wiki/Alpha_(investment)
Args:
algorithm_period_return (float):
Return percentage from algorithm period.
treasury_period_return (float):
Return percentage for treasury period.
benchmark_period_return (float):
Return percentage for benchmark period.
beta (float):
beta value for the same period as all other values
Returns:
float. The alpha of the algorithm.
"""
return algorithm_period_return - \
(treasury_period_return + beta *
(benchmark_period_returns - treasury_period_return))
###########################
# End Risk Metric Section #
###########################
def get_treasury_rate(treasury_curves, treasury_duration, day):
rate = None