mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-28 17:16:55 +08:00
Merge pull request #1322 from quantopian/move_risk_calculations
Move risk calculations
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
{"names": ["open", "high", "low", "close", "volume", "day", "id"]}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
{"nbytes": 98584, "shape": [24646], "cbytes": 131072}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"chunklen": 32768, "dtype": "uint32", "expectedlen": 24646, "dflt": 0, "cparams": {"shuffle": true, "clevel": 5}}
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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))),
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
3ac0773c4be4e9e5bacd9c6fa0e03e15
|
||||
3a5fae958c8bac684f1773fa8dff7810
|
||||
19d580890e211a122e9e746f07c80cbc
|
||||
70cfe3677a0ff401c801b8628e125d8f
|
||||
99b3855ef1b8963163c3cb8f7e05cb70
|
||||
97dfb557c3501179504926e4079e6446
|
||||
cc507b6fca18aabadac69657181edd4e
|
||||
5b48e6a70181d73ecb7f07df5a3092e2
|
||||
3343940379161143630503413627a53a
|
||||
820235c4157a3c55474836438019ef2e
|
||||
75c1b1441efbc2431215835a5079ccc6
|
||||
37e3ea4a1788f1aa6f3ee0986bc625ae
|
||||
651e611e723e2a58b1ded91d0cd39b66
|
||||
d62fce39ec78f032165d8f356bba5c2c
|
||||
97632f6f64dfc4a2de09882419a79421
|
||||
79d117cd4849745bf72ee1fd7442ef89
|
||||
@@ -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
@@ -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])
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user