Merge branch 'v1.2.5' into master

This commit is contained in:
Robert Martin
2020-08-31 19:59:21 +08:00
committed by GitHub
26 changed files with 545 additions and 612 deletions

View File

@@ -203,7 +203,6 @@ As of v1.2.0:
- Introduced a new API, in which the function `expected_returns.return_model(method="...")` allows - Introduced a new API, in which the function `expected_returns.return_model(method="...")` allows
all the different return models to be called. This should make testing easier. all the different return models to be called. This should make testing easier.
- Added option to 'properly' compound returns. - Added option to 'properly' compound returns.
- James-Stein shrinkage estimator
- CAPM return model. - CAPM return model.
- `from pypfopt import plotting`: moved all plotting functionality into a new class and added - `from pypfopt import plotting`: moved all plotting functionality into a new class and added
new plots. All other plotting functions (scattered in different classes) have been retained, new plots. All other plotting functions (scattered in different classes) have been retained,
@@ -244,9 +243,6 @@ A far more comprehensive version of this can be found on [ReadTheDocs](https://p
- Exponentially weighted mean historical returns: - Exponentially weighted mean historical returns:
- similar to mean historical returns, except it gives exponentially more weight to recent prices - similar to mean historical returns, except it gives exponentially more weight to recent prices
- it is likely the case that an asset's most recent returns hold more weight than returns from 10 years ago when it comes to estimating future returns. - it is likely the case that an asset's most recent returns hold more weight than returns from 10 years ago when it comes to estimating future returns.
- James-Stein shrinkage:
- a slightly more robust estimate of future returns
- by shrinking mean returns to the grand average, we can reduce loss.
- Capital Asset Pricing Model (CAPM): - Capital Asset Pricing Model (CAPM):
- a simple model to predict returns based on the beta to the market - a simple model to predict returns based on the beta to the market
- this is used all over finance! - this is used all over finance!

File diff suppressed because one or more lines are too long

View File

@@ -1,88 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hierarchical Risk Parity Portfolio\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from pypfopt import HRPOpt, risk_models, plotting, DiscreteAllocation\n",
"\n",
"\n",
"df = pd.read_csv(\"tests/stock_prices.csv\", parse_dates=True, index_col=\"date\")\n",
"print(df.shape)\n",
"df.tail()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"returns = risk_models.returns_from_prices(df)\n",
"hrp = HRPOpt(returns)\n",
"weights = hrp.optimize()\n",
"hrp.portfolio_performance(verbose=True);"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plotting.plot_dendrogram(hrp);"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"da = DiscreteAllocation(weights, df.iloc[-1], total_portfolio_value=10000)\n",
"w, leftover = da.lp_portfolio()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plotting.plot_weights(weights);"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -46,20 +46,6 @@ superior models and feed them into the optimiser.
the mean historical return. However, if you plan on rebalancing much more frequently, the mean historical return. However, if you plan on rebalancing much more frequently,
there is a case to be made for lowering the span in order to capture recent trends. there is a case to be made for lowering the span in order to capture recent trends.
.. autofunction:: james_stein_shrinkage
A surprising result in statistics is that the MLE estimator for a 3+ dimensional
Normal distribution is an *inadmissible* estimator. That is, there exists an estimator
:math:`\hat{\mu}^{JS}` such that:
.. math::
E\{ \lVert \hat{\mu}^{JS} - \mu \rVert^2 \} < E \{ \lVert \bar{\mu} - \mu \rVert^2 \}
In essence, to reduce account for the fact that our sample may not be representative and
thus reduce loss, we shrink the sample means to the "grand average" (mean of means). For
a more detailed explanation, refer to Efron and Hastie (2010) [1]_
.. autofunction:: capm_return .. autofunction:: capm_return
.. autofunction:: returns_from_prices .. autofunction:: returns_from_prices
@@ -69,7 +55,5 @@ superior models and feed them into the optimiser.
.. autofunction:: prices_from_returns .. autofunction:: prices_from_returns
References .. References
========== .. ==========
.. [1] Efron and Hastie (2010) `Empirical Bayes and the JamesStein Estimator <http://statweb.stanford.edu/~ckirby/brad/LSI/chapter1.pdf>`_.

View File

@@ -38,7 +38,7 @@ Sharpe Ratio: 1.09
# Black-Litterman # Black-Litterman
spy_prices = pd.read_csv( spy_prices = pd.read_csv(
"tests/spy_prices.csv", parse_dates=True, index_col=0, squeeze=True "tests/resources/spy_prices.csv", parse_dates=True, index_col=0, squeeze=True
) )
delta = black_litterman.market_implied_risk_aversion(spy_prices) delta = black_litterman.market_implied_risk_aversion(spy_prices)

359
poetry.lock generated
View File

@@ -16,6 +16,23 @@ optional = false
python-versions = "*" python-versions = "*"
version = "0.1.0" version = "0.1.0"
[[package]]
category = "dev"
description = "The secure Argon2 password hashing algorithm."
name = "argon2-cffi"
optional = false
python-versions = "*"
version = "20.1.0"
[package.dependencies]
cffi = ">=1.0.0"
six = "*"
[package.extras]
dev = ["coverage (>=5.0.2)", "hypothesis", "pytest", "sphinx", "wheel", "pre-commit"]
docs = ["sphinx"]
tests = ["coverage (>=5.0.2)", "hypothesis", "pytest"]
[[package]] [[package]]
category = "dev" category = "dev"
description = "Atomic file writes." description = "Atomic file writes."
@@ -30,13 +47,12 @@ description = "Classes Without Boilerplate"
name = "attrs" name = "attrs"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0" version = "20.1.0"
[package.extras] [package.extras]
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
docs = ["sphinx", "zope.interface"] tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]] [[package]]
category = "dev" category = "dev"
@@ -44,7 +60,7 @@ description = "Specifications for callback functions passed in to an API"
name = "backcall" name = "backcall"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.1.0" version = "0.2.0"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -77,6 +93,17 @@ packaging = "*"
six = ">=1.9.0" six = ">=1.9.0"
webencodings = "*" webencodings = "*"
[[package]]
category = "dev"
description = "Foreign Function Interface for Python calling C code."
name = "cffi"
optional = false
python-versions = "*"
version = "1.14.2"
[package.dependencies]
pycparser = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
@@ -95,21 +122,28 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3" version = "0.4.3"
[[package]]
category = "main"
description = "Convex optimization package"
name = "cvxopt"
optional = false
python-versions = "*"
version = "1.2.5"
[[package]] [[package]]
category = "main" category = "main"
description = "A domain-specific language for modeling convex optimization problems in Python." description = "A domain-specific language for modeling convex optimization problems in Python."
name = "cvxpy" name = "cvxpy"
optional = false optional = false
python-versions = "*" python-versions = ">=3.5"
version = "1.0.31" version = "1.1.5"
[package.dependencies] [package.dependencies]
ecos = ">=2" ecos = ">=2"
multiprocess = "*"
numpy = ">=1.15" numpy = ">=1.15"
osqp = ">=0.4.1" osqp = ">=0.4.1"
scipy = ">=1.1.0" scipy = ">=1.1.0"
scs = ">=1.1.3" scs = ">=1.1.5"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -127,17 +161,6 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.6.0" version = "0.6.0"
[[package]]
category = "main"
description = "serialize all of python"
name = "dill"
optional = false
python-versions = ">=2.6, !=3.0.*"
version = "0.3.1.1"
[package.extras]
graph = ["objgraph (>=1.7.2)"]
[[package]] [[package]]
category = "main" category = "main"
description = "This is the Python package for ECOS: Embedded Cone Solver. See Github page for more information." description = "This is the Python package for ECOS: Embedded Cone Solver. See Github page for more information."
@@ -164,7 +187,7 @@ description = "the modular source code checker: pep8 pyflakes and co"
name = "flake8" name = "flake8"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "3.8.2" version = "3.8.3"
[package.dependencies] [package.dependencies]
mccabe = ">=0.6.0,<0.7.0" mccabe = ">=0.6.0,<0.7.0"
@@ -190,7 +213,7 @@ marker = "python_version < \"3.8\""
name = "importlib-metadata" name = "importlib-metadata"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.6.1" version = "1.7.0"
[package.dependencies] [package.dependencies]
zipp = ">=0.5" zipp = ">=0.5"
@@ -205,7 +228,7 @@ description = "IPython Kernel for Jupyter"
name = "ipykernel" name = "ipykernel"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "5.3.0" version = "5.3.4"
[package.dependencies] [package.dependencies]
appnope = "*" appnope = "*"
@@ -288,14 +311,14 @@ description = "An autocompletion tool for Python that can be used for text edito
name = "jedi" name = "jedi"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.17.0" version = "0.17.2"
[package.dependencies] [package.dependencies]
parso = ">=0.7.0" parso = ">=0.7.0,<0.8.0"
[package.extras] [package.extras]
qa = ["flake8 (3.7.9)"] qa = ["flake8 (3.7.9)"]
testing = ["colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"]
[[package]] [[package]]
category = "dev" category = "dev"
@@ -355,7 +378,7 @@ description = "Jupyter protocol implementation and client libraries"
name = "jupyter-client" name = "jupyter-client"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "6.1.3" version = "6.1.7"
[package.dependencies] [package.dependencies]
jupyter-core = ">=4.6.0" jupyter-core = ">=4.6.0"
@@ -365,7 +388,7 @@ tornado = ">=4.1"
traitlets = "*" traitlets = "*"
[package.extras] [package.extras]
test = ["ipykernel", "ipython", "mock", "pytest"] test = ["ipykernel", "ipython", "mock", "pytest", "pytest-asyncio", "async-generator", "pytest-timeout"]
[[package]] [[package]]
category = "dev" category = "dev"
@@ -428,18 +451,7 @@ marker = "python_version > \"2.7\""
name = "more-itertools" name = "more-itertools"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "8.3.0" version = "8.5.0"
[[package]]
category = "main"
description = "better multiprocessing and multithreading in python"
name = "multiprocess"
optional = false
python-versions = "*"
version = "0.70.9"
[package.dependencies]
dill = ">=0.3.1"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -475,7 +487,7 @@ description = "The Jupyter Notebook format"
name = "nbformat" name = "nbformat"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "5.0.6" version = "5.0.7"
[package.dependencies] [package.dependencies]
ipython-genutils = "*" ipython-genutils = "*"
@@ -484,7 +496,7 @@ jupyter-core = "*"
traitlets = ">=4.1" traitlets = ">=4.1"
[package.extras] [package.extras]
test = ["testpath", "pytest", "pytest-cov"] test = ["pytest", "pytest-cov", "testpath"]
[[package]] [[package]]
category = "dev" category = "dev"
@@ -492,10 +504,11 @@ description = "A web-based notebook environment for interactive computing"
name = "notebook" name = "notebook"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "6.0.3" version = "6.1.3"
[package.dependencies] [package.dependencies]
Send2Trash = "*" Send2Trash = "*"
argon2-cffi = "*"
ipykernel = "*" ipykernel = "*"
ipython-genutils = "*" ipython-genutils = "*"
jinja2 = "*" jinja2 = "*"
@@ -505,12 +518,13 @@ nbconvert = "*"
nbformat = "*" nbformat = "*"
prometheus-client = "*" prometheus-client = "*"
pyzmq = ">=17" pyzmq = ">=17"
terminado = ">=0.8.1" terminado = ">=0.8.3"
tornado = ">=5.0" tornado = ">=5.0"
traitlets = ">=4.2.1" traitlets = ">=4.2.1"
[package.extras] [package.extras]
test = ["nose", "coverage", "requests", "nose-warnings-filters", "nbval", "nose-exclude", "selenium", "pytest", "pytest-cov", "nose-exclude"] docs = ["sphinx", "nbsphinx", "sphinxcontrib-github-alt"]
test = ["nose", "coverage", "requests", "nose-warnings-filters", "nbval", "nose-exclude", "selenium", "pytest", "pytest-cov", "requests-unixsocket"]
[[package]] [[package]]
category = "main" category = "main"
@@ -572,7 +586,7 @@ description = "A Python Parser"
name = "parso" name = "parso"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.7.0" version = "0.7.1"
[package.extras] [package.extras]
testing = ["docopt", "pytest (>=3.0.7)"] testing = ["docopt", "pytest (>=3.0.7)"]
@@ -663,7 +677,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili
name = "py" name = "py"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.8.1" version = "1.9.0"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -673,6 +687,14 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.6.0" version = "2.6.0"
[[package]]
category = "dev"
description = "C parser in Python"
name = "pycparser"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.20"
[[package]] [[package]]
category = "dev" category = "dev"
description = "passive checker of Python programs" description = "passive checker of Python programs"
@@ -770,7 +792,7 @@ marker = "sys_platform == \"win32\""
name = "pywin32" name = "pywin32"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "227" version = "228"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -787,7 +809,7 @@ description = "Python bindings for 0MQ"
name = "pyzmq" name = "pyzmq"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
version = "19.0.1" version = "19.0.2"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -795,7 +817,7 @@ description = "Jupyter Qt console"
name = "qtconsole" name = "qtconsole"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "4.7.4" version = "4.7.6"
[package.dependencies] [package.dependencies]
ipykernel = ">=4.1" ipykernel = ">=4.1"
@@ -929,7 +951,7 @@ description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth" name = "wcwidth"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.2.3" version = "0.2.5"
[[package]] [[package]]
category = "dev" category = "dev"
@@ -977,7 +999,7 @@ matplotlib = []
scikit-learn = [] scikit-learn = []
[metadata] [metadata]
content-hash = "d25a25d8c927137e2fb52a061ae18663175879217a6a4a632b4c3e0b166acc55" content-hash = "0ec98fc57e383a229e29979c086e8fc1417f6654529c9441afa00209c19f012d"
python-versions = "^3.5" python-versions = "^3.5"
[metadata.files] [metadata.files]
@@ -989,17 +1011,35 @@ appnope = [
{file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
{file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
] ]
argon2-cffi = [
{file = "argon2-cffi-20.1.0.tar.gz", hash = "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d"},
{file = "argon2_cffi-20.1.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003"},
{file = "argon2_cffi-20.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf"},
{file = "argon2_cffi-20.1.0-cp27-cp27m-win32.whl", hash = "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5"},
{file = "argon2_cffi-20.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc"},
{file = "argon2_cffi-20.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe"},
{file = "argon2_cffi-20.1.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647"},
{file = "argon2_cffi-20.1.0-cp35-cp35m-win32.whl", hash = "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361"},
{file = "argon2_cffi-20.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b"},
{file = "argon2_cffi-20.1.0-cp36-cp36m-win32.whl", hash = "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496"},
{file = "argon2_cffi-20.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa"},
{file = "argon2_cffi-20.1.0-cp37-abi3-macosx_10_6_intel.whl", hash = "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b"},
{file = "argon2_cffi-20.1.0-cp37-cp37m-win32.whl", hash = "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5"},
{file = "argon2_cffi-20.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203"},
{file = "argon2_cffi-20.1.0-cp38-cp38-win32.whl", hash = "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78"},
{file = "argon2_cffi-20.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2"},
]
atomicwrites = [ atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
] ]
attrs = [ attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-20.1.0-py2.py3-none-any.whl", hash = "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, {file = "attrs-20.1.0.tar.gz", hash = "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a"},
] ]
backcall = [ backcall = [
{file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"}, {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
{file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
] ]
black = [ black = [
{file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"},
@@ -1009,6 +1049,36 @@ bleach = [
{file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"}, {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"},
{file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"}, {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"},
] ]
cffi = [
{file = "cffi-1.14.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82"},
{file = "cffi-1.14.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4"},
{file = "cffi-1.14.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e"},
{file = "cffi-1.14.2-cp27-cp27m-win32.whl", hash = "sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c"},
{file = "cffi-1.14.2-cp27-cp27m-win_amd64.whl", hash = "sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1"},
{file = "cffi-1.14.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7"},
{file = "cffi-1.14.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c"},
{file = "cffi-1.14.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731"},
{file = "cffi-1.14.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0"},
{file = "cffi-1.14.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e"},
{file = "cffi-1.14.2-cp35-cp35m-win32.whl", hash = "sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487"},
{file = "cffi-1.14.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad"},
{file = "cffi-1.14.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2"},
{file = "cffi-1.14.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123"},
{file = "cffi-1.14.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1"},
{file = "cffi-1.14.2-cp36-cp36m-win32.whl", hash = "sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281"},
{file = "cffi-1.14.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4"},
{file = "cffi-1.14.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798"},
{file = "cffi-1.14.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4"},
{file = "cffi-1.14.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f"},
{file = "cffi-1.14.2-cp37-cp37m-win32.whl", hash = "sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650"},
{file = "cffi-1.14.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15"},
{file = "cffi-1.14.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa"},
{file = "cffi-1.14.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c"},
{file = "cffi-1.14.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75"},
{file = "cffi-1.14.2-cp38-cp38-win32.whl", hash = "sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e"},
{file = "cffi-1.14.2-cp38-cp38-win_amd64.whl", hash = "sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c"},
{file = "cffi-1.14.2.tar.gz", hash = "sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b"},
]
click = [ click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
@@ -1017,11 +1087,38 @@ colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
] ]
cvxopt = [
{file = "cvxopt-1.2.5-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:78d0ea93f71f4b5b882ecd741ddc1e0cc8b06f4a2881f9d3fa55631d633273b6"},
{file = "cvxopt-1.2.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fcc447eb3a0465f9d6b78f3df9861cb6fdb18ff7ad008e17f614da580e1fdbee"},
{file = "cvxopt-1.2.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d74806ea13bbe5829903d048e19402051e784e4b34a86cdee2ba80d13f29a54f"},
{file = "cvxopt-1.2.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:50c464cbb43d1e350759294107abbef29576ea70605f5ac86db7c022002ab22f"},
{file = "cvxopt-1.2.5-cp27-cp27m-win_amd64.whl", hash = "sha256:60daaf297d195b33eab6a90c6fd9e8939541fdb209ed2972b70fba6dcbf82192"},
{file = "cvxopt-1.2.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ae3684ffabb135a059ba2c1947c0c1e00d79374967f40e807cb0fce65e514999"},
{file = "cvxopt-1.2.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6517bde92411c6b57d09a60c51ef066952400b0b21cf26694a0b0b0fc3faa065"},
{file = "cvxopt-1.2.5-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:01b579ef42258f129fc1f249d5bd2945b6183e4323ff9a45a61d1224d76ab778"},
{file = "cvxopt-1.2.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:4dcb001b1c6a40ec9c61103816aae0d32dbc11894e0f5f645668f524a051bcaa"},
{file = "cvxopt-1.2.5-cp35-cp35m-win_amd64.whl", hash = "sha256:8095c783278b3b3084b0ea431fdec072f5c17ba7b4249b3a71b3866f00fd0d9f"},
{file = "cvxopt-1.2.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7737e406e9d7ecfd86d93492c619238969023ea2b4556bf576b4a7be149aa9f0"},
{file = "cvxopt-1.2.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9ef3bade9effc568b4b077166d5dde9632c2ead8d71e3f6c930e08ae29c7bb64"},
{file = "cvxopt-1.2.5-cp36-cp36m-win_amd64.whl", hash = "sha256:02976910f5ff456246e0454d32babe61aa5c7e4c781a330840347fcf3c54f5fa"},
{file = "cvxopt-1.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d56655abfd7e0d3051d71bffe322ce77d71e5d683bcc6a359fe207dbff02a8b"},
{file = "cvxopt-1.2.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8bcb1e4b6a92ecbd6b1bea630b666d12cf253c2b902e5d039653e850defff887"},
{file = "cvxopt-1.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:dd2c97ace2739f31bceadd29e0e9535fe7457d756a6d9d207e1a654fd1ad1dc2"},
{file = "cvxopt-1.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37768ccefd3aec0dd9428d5eaa1a075d2db1d485e9b93c4d5d02464297a6530a"},
{file = "cvxopt-1.2.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3446b360c4123949511b4aef0b3d0bcdd3ce485b8e682c221a39c886945eacd3"},
{file = "cvxopt-1.2.5.tar.gz", hash = "sha256:94ec8c36bd6628a11de9014346692daeeef99b3b7bae28cef30c7490bbcb2d72"},
]
cvxpy = [ cvxpy = [
{file = "cvxpy-1.0.31-cp27-cp27m-macosx_10_7_x86_64.whl", hash = "sha256:e88982b2817252803c45c3c6bd4f8919c0684c4b6595da953249253e612e3729"}, {file = "cvxpy-1.1.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a21e6a5c49ff04b29c47ecd1c9d4e3dee070c1d1df4f71a310addc2e54f85c7e"},
{file = "cvxpy-1.0.31-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:513fe05931a0c93e25d4b4ccca84d7145a43b014d7a29a981d54c76a527bc0bf"}, {file = "cvxpy-1.1.5-cp35-cp35m-win_amd64.whl", hash = "sha256:20f3ae7b732b33475a44ec2c810fefc0717fed35e3c677611040049751f3c854"},
{file = "cvxpy-1.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4db9ec8593e8ef31cc8a22f5eb09e36460877724dcd9dc807b26e6768522e7a2"}, {file = "cvxpy-1.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9d4b7fad457875b90ba7ca93ca9797715dcfe8bc2044a13eed5ce3735ca4bdf6"},
{file = "cvxpy-1.0.31.tar.gz", hash = "sha256:b398754f9e9ceaa46b07806b5ae85f90fd8de748475db22a6b99c5943cebe69d"}, {file = "cvxpy-1.1.5-cp36-cp36m-win_amd64.whl", hash = "sha256:d12ce601b97264295f5c59d55a7fe1f479c074cbb064e7aa8818442c04c84a9d"},
{file = "cvxpy-1.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:51280efcc041855bb009bf304d0005d5b352e8027f09d40d60f69cc056e2afa2"},
{file = "cvxpy-1.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:7c10f6402fc1f075be7ac0ca0ad31340bac5a6ad4bbc01f94fa2232ec6f784d4"},
{file = "cvxpy-1.1.5-py3.5-win-amd64.egg", hash = "sha256:51e61497a1aaa43ee78430929e77f8acaf1bc9d2cb4187fee3a9a4f7f790230d"},
{file = "cvxpy-1.1.5-py3.6-win-amd64.egg", hash = "sha256:683fd7f56457a7f2375a47e2ed952adcdf47fffd290e56d213fee73ff115b315"},
{file = "cvxpy-1.1.5-py3.7-win-amd64.egg", hash = "sha256:382ea3dca38b4cf0004c1b9fa0fd2c97d07f2c1fbfd5da667e45a44ff622a297"},
{file = "cvxpy-1.1.5.tar.gz", hash = "sha256:7c826a874db2e4cefe54e63ebd3a3763d0d72e55a17c7d1cfec80008a87b8d81"},
] ]
decorator = [ decorator = [
{file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
@@ -1031,9 +1128,6 @@ defusedxml = [
{file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"}, {file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
{file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"}, {file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
] ]
dill = [
{file = "dill-0.3.1.1.tar.gz", hash = "sha256:42d8ef819367516592a825746a18073ced42ca169ab1f5f4044134703e7a049c"},
]
ecos = [ ecos = [
{file = "ecos-2.0.7.post1-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:dd9f01e28fe58894fb394931804884122606fb4e2a59d4514b803e9cd11b7d2b"}, {file = "ecos-2.0.7.post1-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:dd9f01e28fe58894fb394931804884122606fb4e2a59d4514b803e9cd11b7d2b"},
{file = "ecos-2.0.7.post1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:db7433051f6072d4821ebc582e9ff853d7d631ed98770550d248eae70b29dd26"}, {file = "ecos-2.0.7.post1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:db7433051f6072d4821ebc582e9ff853d7d631ed98770550d248eae70b29dd26"},
@@ -1063,19 +1157,19 @@ entrypoints = [
{file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
] ]
flake8 = [ flake8 = [
{file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"}, {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
{file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"}, {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
] ]
future = [ future = [
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
] ]
importlib-metadata = [ importlib-metadata = [
{file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
{file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
] ]
ipykernel = [ ipykernel = [
{file = "ipykernel-5.3.0-py3-none-any.whl", hash = "sha256:a8362e3ae365023ca458effe93b026b8cdadc0b73ff3031472128dd8a2cf0289"}, {file = "ipykernel-5.3.4-py3-none-any.whl", hash = "sha256:d6fbba26dba3cebd411382bc484f7bc2caa98427ae0ddb4ab37fe8bfeb5c7dd3"},
{file = "ipykernel-5.3.0.tar.gz", hash = "sha256:731adb3f2c4ebcaff52e10a855ddc87670359a89c9c784d711e62d66fccdafae"}, {file = "ipykernel-5.3.4.tar.gz", hash = "sha256:9b2652af1607986a1b231c62302d070bc0534f564c393a5d9d130db9abbbe89d"},
] ]
ipython = [ ipython = [
{file = "ipython-7.9.0-py3-none-any.whl", hash = "sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995"}, {file = "ipython-7.9.0-py3-none-any.whl", hash = "sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995"},
@@ -1090,8 +1184,8 @@ ipywidgets = [
{file = "ipywidgets-7.5.1.tar.gz", hash = "sha256:e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"}, {file = "ipywidgets-7.5.1.tar.gz", hash = "sha256:e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"},
] ]
jedi = [ jedi = [
{file = "jedi-0.17.0-py2.py3-none-any.whl", hash = "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798"}, {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"},
{file = "jedi-0.17.0.tar.gz", hash = "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030"}, {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"},
] ]
jinja2 = [ jinja2 = [
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
@@ -1107,8 +1201,8 @@ jupyter = [
{file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"},
] ]
jupyter-client = [ jupyter-client = [
{file = "jupyter_client-6.1.3-py3-none-any.whl", hash = "sha256:cde8e83aab3ec1c614f221ae54713a9a46d3bf28292609d2db1b439bef5a8c8e"}, {file = "jupyter_client-6.1.7-py3-none-any.whl", hash = "sha256:c958d24d6eacb975c1acebb68ac9077da61b5f5c040f22f6849928ad7393b950"},
{file = "jupyter_client-6.1.3.tar.gz", hash = "sha256:3a32fa4d0b16d1c626b30c3002a62dfd86d6863ed39eaba3f537fade197bb756"}, {file = "jupyter_client-6.1.7.tar.gz", hash = "sha256:49e390b36fe4b4226724704ea28d9fb903f1a3601b6882ce3105221cd09377a1"},
] ]
jupyter-console = [ jupyter-console = [
{file = "jupyter_console-6.1.0-py2.py3-none-any.whl", hash = "sha256:b392155112ec86a329df03b225749a0fa903aa80811e8eda55796a40b5e470d8"}, {file = "jupyter_console-6.1.0-py2.py3-none-any.whl", hash = "sha256:b392155112ec86a329df03b225749a0fa903aa80811e8eda55796a40b5e470d8"},
@@ -1157,25 +1251,20 @@ mistune = [
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
] ]
more-itertools = [ more-itertools = [
{file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"},
{file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"},
]
multiprocess = [
{file = "multiprocess-0.70.9-cp27-cp27m-win32.whl", hash = "sha256:0e4e65c2e74aa14fa0c9a1f838b5e9a5f8fe5b3a173925792260843c4a6157ec"},
{file = "multiprocess-0.70.9-cp27-cp27m-win_amd64.whl", hash = "sha256:1eb7dfe2d809d53be92e8a288ed1c01614fe5407bbc9d078ed451a749fb1bd34"},
{file = "multiprocess-0.70.9.tar.gz", hash = "sha256:9fd5bd990132da77e73dec6e9613408602a4612e1d73caf2e2b813d2b61508e5"},
] ]
nbconvert = [ nbconvert = [
{file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"}, {file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"},
{file = "nbconvert-5.6.1.tar.gz", hash = "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523"}, {file = "nbconvert-5.6.1.tar.gz", hash = "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523"},
] ]
nbformat = [ nbformat = [
{file = "nbformat-5.0.6-py3-none-any.whl", hash = "sha256:276343c78a9660ab2a63c28cc33da5f7c58c092b3f3a40b6017ae2ce6689320d"}, {file = "nbformat-5.0.7-py3-none-any.whl", hash = "sha256:ea55c9b817855e2dfcd3f66d74857342612a60b1f09653440f4a5845e6e3523f"},
{file = "nbformat-5.0.6.tar.gz", hash = "sha256:049af048ed76b95c3c44043620c17e56bc001329e07f83fec4f177f0e3d7b757"}, {file = "nbformat-5.0.7.tar.gz", hash = "sha256:54d4d6354835a936bad7e8182dcd003ca3dc0cedfee5a306090e04854343b340"},
] ]
notebook = [ notebook = [
{file = "notebook-6.0.3-py3-none-any.whl", hash = "sha256:3edc616c684214292994a3af05eaea4cc043f6b4247d830f3a2f209fa7639a80"}, {file = "notebook-6.1.3-py3-none-any.whl", hash = "sha256:964cc40cff68e473f3778aef9266e867f7703cb4aebdfd250f334efe02f64c86"},
{file = "notebook-6.0.3.tar.gz", hash = "sha256:47a9092975c9e7965ada00b9a20f0cf637d001db60d241d479f53c0be117ad48"}, {file = "notebook-6.1.3.tar.gz", hash = "sha256:9990d51b9931a31e681635899aeb198b4c4b41586a9e87fbfaaed1a71d0a05b6"},
] ]
numpy = [ numpy = [
{file = "numpy-1.18.5-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:e91d31b34fc7c2c8f756b4e902f901f856ae53a93399368d9a0dc7be17ed2ca0"}, {file = "numpy-1.18.5-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:e91d31b34fc7c2c8f756b4e902f901f856ae53a93399368d9a0dc7be17ed2ca0"},
@@ -1268,8 +1357,8 @@ pandocfilters = [
{file = "pandocfilters-1.4.2.tar.gz", hash = "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"}, {file = "pandocfilters-1.4.2.tar.gz", hash = "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"},
] ]
parso = [ parso = [
{file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"}, {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"},
{file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"}, {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"},
] ]
pathlib2 = [ pathlib2 = [
{file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"},
@@ -1301,13 +1390,17 @@ ptyprocess = [
{file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
] ]
py = [ py = [
{file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
{file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
] ]
pycodestyle = [ pycodestyle = [
{file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
{file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
] ]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
pyflakes = [ pyflakes = [
{file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
{file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
@@ -1336,18 +1429,18 @@ pytz = [
{file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
] ]
pywin32 = [ pywin32 = [
{file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, {file = "pywin32-228-cp27-cp27m-win32.whl", hash = "sha256:37dc9935f6a383cc744315ae0c2882ba1768d9b06700a70f35dc1ce73cd4ba9c"},
{file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, {file = "pywin32-228-cp27-cp27m-win_amd64.whl", hash = "sha256:11cb6610efc2f078c9e6d8f5d0f957620c333f4b23466931a247fb945ed35e89"},
{file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, {file = "pywin32-228-cp35-cp35m-win32.whl", hash = "sha256:1f45db18af5d36195447b2cffacd182fe2d296849ba0aecdab24d3852fbf3f80"},
{file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, {file = "pywin32-228-cp35-cp35m-win_amd64.whl", hash = "sha256:6e38c44097a834a4707c1b63efa9c2435f5a42afabff634a17f563bc478dfcc8"},
{file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, {file = "pywin32-228-cp36-cp36m-win32.whl", hash = "sha256:ec16d44b49b5f34e99eb97cf270806fdc560dff6f84d281eb2fcb89a014a56a9"},
{file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, {file = "pywin32-228-cp36-cp36m-win_amd64.whl", hash = "sha256:a60d795c6590a5b6baeacd16c583d91cce8038f959bd80c53bd9a68f40130f2d"},
{file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, {file = "pywin32-228-cp37-cp37m-win32.whl", hash = "sha256:af40887b6fc200eafe4d7742c48417529a8702dcc1a60bf89eee152d1d11209f"},
{file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, {file = "pywin32-228-cp37-cp37m-win_amd64.whl", hash = "sha256:00eaf43dbd05ba6a9b0080c77e161e0b7a601f9a3f660727a952e40140537de7"},
{file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, {file = "pywin32-228-cp38-cp38-win32.whl", hash = "sha256:fa6ba028909cfc64ce9e24bcf22f588b14871980d9787f1e2002c99af8f1850c"},
{file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, {file = "pywin32-228-cp38-cp38-win_amd64.whl", hash = "sha256:9b3466083f8271e1a5eb0329f4e0d61925d46b40b195a33413e0905dccb285e8"},
{file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, {file = "pywin32-228-cp39-cp39-win32.whl", hash = "sha256:ed74b72d8059a6606f64842e7917aeee99159ebd6b8d6261c518d002837be298"},
{file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, {file = "pywin32-228-cp39-cp39-win_amd64.whl", hash = "sha256:8319bafdcd90b7202c50d6014efdfe4fde9311b3ff15fd6f893a45c0868de203"},
] ]
pywinpty = [ pywinpty = [
{file = "pywinpty-0.5.7-cp27-cp27m-win32.whl", hash = "sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b"}, {file = "pywinpty-0.5.7-cp27-cp27m-win32.whl", hash = "sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b"},
@@ -1362,38 +1455,38 @@ pywinpty = [
{file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"}, {file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"},
] ]
pyzmq = [ pyzmq = [
{file = "pyzmq-19.0.1-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891"}, {file = "pyzmq-19.0.2-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:59f1e54627483dcf61c663941d94c4af9bf4163aec334171686cdaee67974fe5"},
{file = "pyzmq-19.0.1-cp27-cp27m-win32.whl", hash = "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7"}, {file = "pyzmq-19.0.2-cp27-cp27m-win32.whl", hash = "sha256:c36ffe1e5aa35a1af6a96640d723d0d211c5f48841735c2aa8d034204e87eb87"},
{file = "pyzmq-19.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a"}, {file = "pyzmq-19.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:0a422fc290d03958899743db091f8154958410fc76ce7ee0ceb66150f72c2c97"},
{file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f"}, {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c20dd60b9428f532bc59f2ef6d3b1029a28fc790d408af82f871a7db03e722ff"},
{file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087"}, {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d46fb17f5693244de83e434648b3dbb4f4b0fec88415d6cbab1c1452b6f2ae17"},
{file = "pyzmq-19.0.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421"}, {file = "pyzmq-19.0.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:f1a25a61495b6f7bb986accc5b597a3541d9bd3ef0016f50be16dbb32025b302"},
{file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230"}, {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ab0d01148d13854de716786ca73701012e07dff4dfbbd68c4e06d8888743526e"},
{file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea"}, {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:720d2b6083498a9281eaee3f2927486e9fe02cd16d13a844f2e95217f243efea"},
{file = "pyzmq-19.0.1-cp35-cp35m-win32.whl", hash = "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960"}, {file = "pyzmq-19.0.2-cp35-cp35m-win32.whl", hash = "sha256:29d51279060d0a70f551663bc592418bcad7f4be4eea7b324f6dd81de05cb4c1"},
{file = "pyzmq-19.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566"}, {file = "pyzmq-19.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:5120c64646e75f6db20cc16b9a94203926ead5d633de9feba4f137004241221d"},
{file = "pyzmq-19.0.1-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234"}, {file = "pyzmq-19.0.2-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:8a6ada5a3f719bf46a04ba38595073df8d6b067316c011180102ba2a1925f5b5"},
{file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448"}, {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fa411b1d8f371d3a49d31b0789eb6da2537dadbb2aef74a43aa99a78195c3f76"},
{file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217"}, {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:00dca814469436455399660247d74045172955459c0bd49b54a540ce4d652185"},
{file = "pyzmq-19.0.1-cp36-cp36m-win32.whl", hash = "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9"}, {file = "pyzmq-19.0.2-cp36-cp36m-win32.whl", hash = "sha256:046b92e860914e39612e84fa760fc3f16054d268c11e0e25dcb011fb1bc6a075"},
{file = "pyzmq-19.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd"}, {file = "pyzmq-19.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99cc0e339a731c6a34109e5c4072aaa06d8e32c0b93dc2c2d90345dd45fa196c"},
{file = "pyzmq-19.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c"}, {file = "pyzmq-19.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e36f12f503511d72d9bdfae11cadbadca22ff632ff67c1b5459f69756a029c19"},
{file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec"}, {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c40fbb2b9933369e994b837ee72193d6a4c35dfb9a7c573257ef7ff28961272c"},
{file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98"}, {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5d9fc809aa8d636e757e4ced2302569d6e60e9b9c26114a83f0d9d6519c40493"},
{file = "pyzmq-19.0.1-cp37-cp37m-win32.whl", hash = "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112"}, {file = "pyzmq-19.0.2-cp37-cp37m-win32.whl", hash = "sha256:3fa6debf4bf9412e59353defad1f8035a1e68b66095a94ead8f7a61ae90b2675"},
{file = "pyzmq-19.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e"}, {file = "pyzmq-19.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:73483a2caaa0264ac717af33d6fb3f143d8379e60a422730ee8d010526ce1913"},
{file = "pyzmq-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85"}, {file = "pyzmq-19.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36ab114021c0cab1a423fe6689355e8f813979f2c750968833b318c1fa10a0fd"},
{file = "pyzmq-19.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6"}, {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8b66b94fe6243d2d1d89bca336b2424399aac57932858b9a30309803ffc28112"},
{file = "pyzmq-19.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2"}, {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:654d3e06a4edc566b416c10293064732516cf8871a4522e0a2ba00cc2a2e600c"},
{file = "pyzmq-19.0.1-cp38-cp38-win32.whl", hash = "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec"}, {file = "pyzmq-19.0.2-cp38-cp38-win32.whl", hash = "sha256:276ad604bffd70992a386a84bea34883e696a6b22e7378053e5d3227321d9702"},
{file = "pyzmq-19.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6"}, {file = "pyzmq-19.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:09d24a80ccb8cbda1af6ed8eb26b005b6743e58e9290566d2a6841f4e31fa8e0"},
{file = "pyzmq-19.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054"}, {file = "pyzmq-19.0.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:c1a31cd42905b405530e92bdb70a8a56f048c8a371728b8acf9d746ecd4482c0"},
{file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"}, {file = "pyzmq-19.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a7e7f930039ee0c4c26e4dfee015f20bd6919cd8b97c9cd7afbde2923a5167b6"},
{file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"}, {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"},
] ]
qtconsole = [ qtconsole = [
{file = "qtconsole-4.7.4-py2.py3-none-any.whl", hash = "sha256:89442727940126c65c2f94a058f1b4693a0f5d4c4b192fd6518ba3b11f4791aa"}, {file = "qtconsole-4.7.6-py2.py3-none-any.whl", hash = "sha256:570b9e1dd4f9b727699b0ed04c6943d9d32d5a2085aa69d82d814e039bbcf74b"},
{file = "qtconsole-4.7.4.tar.gz", hash = "sha256:fd48bf1051d6e69cec1f9e2596cfaa94e3c726c70c5d848681ebce10c029f5fd"}, {file = "qtconsole-4.7.6.tar.gz", hash = "sha256:6c24397c19a49a5cf69582c931db4b0f6b00a78530a2bfd122936f2ebfae2fef"},
] ]
qtpy = [ qtpy = [
{file = "QtPy-1.9.0-py2.py3-none-any.whl", hash = "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea"}, {file = "QtPy-1.9.0-py2.py3-none-any.whl", hash = "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea"},
@@ -1466,8 +1559,8 @@ traitlets = [
{file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
] ]
wcwidth = [ wcwidth = [
{file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"}, {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
] ]
webencodings = [ webencodings = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},

View File

@@ -220,8 +220,8 @@ class BaseConvexOptimizer(BaseOptimizer):
opt.solve(solver=self._solver, verbose=self._verbose) opt.solve(solver=self._solver, verbose=self._verbose)
else: else:
opt.solve(verbose=self._verbose) opt.solve(verbose=self._verbose)
except (TypeError, cp.DCPError): except (TypeError, cp.DCPError) as e:
raise exceptions.OptimizationError raise exceptions.OptimizationError from e
if opt.status != "optimal": if opt.status != "optimal":
raise exceptions.OptimizationError raise exceptions.OptimizationError
@@ -341,6 +341,7 @@ class BaseConvexOptimizer(BaseOptimizer):
weights_sum_to_one=True, weights_sum_to_one=True,
constraints=None, constraints=None,
solver="SLSQP", solver="SLSQP",
initial_guess=None,
): ):
""" """
Optimise some objective function using the scipy backend. This can Optimise some objective function using the scipy backend. This can
@@ -374,6 +375,8 @@ class BaseConvexOptimizer(BaseOptimizer):
:param solver: which SCIPY solver to use, e.g "SLSQP", "COBYLA", "BFGS". :param solver: which SCIPY solver to use, e.g "SLSQP", "COBYLA", "BFGS".
User beware: different optimisers require different inputs. User beware: different optimisers require different inputs.
:type solver: string :type solver: string
:param initial_guess: the initial guess for the weights, shape (n,) or (n, 1)
:type initial_guess: np.ndarray
:return: asset weights that optimise the custom objective :return: asset weights that optimise the custom objective
:rtype: OrderedDict :rtype: OrderedDict
""" """
@@ -385,7 +388,8 @@ class BaseConvexOptimizer(BaseOptimizer):
bound_array = np.vstack((self._lower_bounds, self._upper_bounds)).T bound_array = np.vstack((self._lower_bounds, self._upper_bounds)).T
bounds = list(map(tuple, bound_array)) bounds = list(map(tuple, bound_array))
initial_guess = np.array([1 / self.n_assets] * self.n_assets) if initial_guess is None:
initial_guess = np.array([1 / self.n_assets] * self.n_assets)
# Construct constraints # Construct constraints
final_constraints = [] final_constraints = []

View File

@@ -375,7 +375,7 @@ class CLA(base_optimizer.BaseOptimizer):
""" """
Maximise the Sharpe ratio. Maximise the Sharpe ratio.
:return: asset weights for the volatility-minimising portfolio :return: asset weights for the max-sharpe portfolio
:rtype: OrderedDict :rtype: OrderedDict
""" """
if not self.w: if not self.w:

View File

@@ -297,19 +297,21 @@ class DiscreteAllocation:
# Integer allocation # Integer allocation
x = cp.Variable(n, integer=True) x = cp.Variable(n, integer=True)
# Remaining dollars # Remaining dollars
r = self.total_portfolio_value - p.T * x r = self.total_portfolio_value - p.T @ x
# Objective function is remaining dollars + sum of absolute deviations from ideality. # Set up linear program
objective = r + cp.norm(w * self.total_portfolio_value - cp.multiply(x, p), 1) eta = w * self.total_portfolio_value - cp.multiply(x, p)
constraints = [r + p.T * x == self.total_portfolio_value, x >= 0, r >= 0] u = cp.Variable(n)
constraints = [eta <= u, eta >= -u, x >= 0, r >= 0]
objective = cp.sum(u) + r
opt = cp.Problem(cp.Minimize(objective), constraints) opt = cp.Problem(cp.Minimize(objective), constraints)
opt.solve() opt.solve(solver="GLPK_MI")
if opt.status not in {"optimal", "optimal_inaccurate"}: if opt.status not in {"optimal", "optimal_inaccurate"}:
raise exceptions.OptimizationError("Please try greedy_portfolio") raise exceptions.OptimizationError("Please try greedy_portfolio")
vals = np.rint(x.value) vals = np.rint(x.value).astype(int)
self.allocation = self._remove_zero_positions( self.allocation = self._remove_zero_positions(
collections.OrderedDict(zip([i[0] for i in self.weights], vals)) collections.OrderedDict(zip([i[0] for i in self.weights], vals))
) )

View File

@@ -271,6 +271,15 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
if not isinstance(target_volatility, (float, int)) or target_volatility < 0: if not isinstance(target_volatility, (float, int)) or target_volatility < 0:
raise ValueError("target_volatility should be a positive float") raise ValueError("target_volatility should be a positive float")
global_min_volatility = np.sqrt(1 / np.sum(np.linalg.inv(self.cov_matrix)))
if target_volatility < global_min_volatility:
raise ValueError(
"The minimum volatility is {:.3f}. Please use a higher target_volatility".format(
global_min_volatility
)
)
self._objective = objective_functions.portfolio_return( self._objective = objective_functions.portfolio_return(
self._w, self.expected_returns self._w, self.expected_returns
) )

View File

@@ -3,7 +3,6 @@ The ``exceptions`` module houses custom exceptions. Currently implemented:
- OptimizationError - OptimizationError
""" """
import traceback
class OptimizationError(Exception): class OptimizationError(Exception):
@@ -16,9 +15,4 @@ class OptimizationError(Exception):
default_message = ( default_message = (
"Please check your objectives/constraints or use a different solver." "Please check your objectives/constraints or use a different solver."
) )
super().__init__(default_message, *args, **kwargs)
if not (args or kwargs):
args = (default_message,)
super().__init__(*args, **kwargs)
traceback.print_exc()

View File

@@ -15,7 +15,6 @@ Currently implemented:
- general return model function, allowing you to run any return model from one function. - general return model function, allowing you to run any return model from one function.
- mean historical return - mean historical return
- exponentially weighted mean historical return - exponentially weighted mean historical return
- James-Stein shrinkage
- CAPM estimate of returns - CAPM estimate of returns
Additionally, we provide utility functions to convert from returns to prices and vice-versa. Additionally, we provide utility functions to convert from returns to prices and vice-versa.
@@ -26,17 +25,22 @@ import pandas as pd
import numpy as np import numpy as np
def returns_from_prices(prices): def returns_from_prices(prices, log_returns=False):
""" """
Calculate the returns given prices. Calculate the returns given prices.
:param prices: adjusted (daily) closing prices of the asset, each row is a :param prices: adjusted (daily) closing prices of the asset, each row is a
date and each column is a ticker/id. date and each column is a ticker/id.
:type prices: pd.DataFrame :type prices: pd.DataFrame
:param log_returns: whether to compute using log returns
:type log_returns: bool, defaults to False
:return: (daily) returns :return: (daily) returns
:rtype: pd.DataFrame :rtype: pd.DataFrame
""" """
return prices.pct_change().dropna(how="all") if log_returns:
return np.log(1 + prices.pct_change()).dropna(how="all")
else:
return prices.pct_change().dropna(how="all")
def log_returns_from_prices(prices): def log_returns_from_prices(prices):
@@ -49,10 +53,13 @@ def log_returns_from_prices(prices):
:return: (daily) returns :return: (daily) returns
:rtype: pd.DataFrame :rtype: pd.DataFrame
""" """
warnings.warn(
"log_returns_from_prices is deprecated. Please use returns_from_prices(prices, log_returns=True)"
)
return np.log(1 + prices.pct_change()).dropna(how="all") return np.log(1 + prices.pct_change()).dropna(how="all")
def prices_from_returns(returns): def prices_from_returns(returns, log_returns=False):
""" """
Calculate the pseudo-prices given returns. These are not true prices because Calculate the pseudo-prices given returns. These are not true prices because
the initial prices are all set to 1, but it behaves as intended when passed the initial prices are all set to 1, but it behaves as intended when passed
@@ -60,9 +67,13 @@ def prices_from_returns(returns):
:param returns: (daily) percentage returns of the assets :param returns: (daily) percentage returns of the assets
:type returns: pd.DataFrame :type returns: pd.DataFrame
:param log_returns: whether to compute using log returns
:type log_returns: bool, defaults to False
:return: (daily) pseudo-prices. :return: (daily) pseudo-prices.
:rtype: pd.DataFrame :rtype: pd.DataFrame
""" """
if log_returns:
returns = np.exp(returns)
ret = 1 + returns ret = 1 + returns
ret.iloc[0] = 1 # set first day pseudo-price ret.iloc[0] = 1 # set first day pseudo-price
return ret.cumprod() return ret.cumprod()
@@ -81,7 +92,6 @@ def return_model(prices, method="mean_historical_return", **kwargs):
- ``mean_historical_return`` - ``mean_historical_return``
- ``ema_historical_return`` - ``ema_historical_return``
- ``james_stein_shrinkage``
- ``capm_return`` - ``capm_return``
:type method: str, optional :type method: str, optional
@@ -93,19 +103,16 @@ def return_model(prices, method="mean_historical_return", **kwargs):
return mean_historical_return(prices, **kwargs) return mean_historical_return(prices, **kwargs)
elif method == "ema_historical_return": elif method == "ema_historical_return":
return ema_historical_return(prices, **kwargs) return ema_historical_return(prices, **kwargs)
elif method == "james_stein_shrinkage":
return james_stein_shrinkage(prices, **kwargs)
elif method == "capm_return": elif method == "capm_return":
return capm_return(prices, **kwargs) return capm_return(prices, **kwargs)
else: else:
raise NotImplementedError("Return model {} not implemented".format(method)) raise NotImplementedError("Return model {} not implemented".format(method))
def mean_historical_return( def mean_historical_return(prices, returns_data=False, compounding=True, frequency=252):
prices, returns_data=False, compounding=False, frequency=252
):
""" """
Calculate annualised mean (daily) historical return from input (daily) asset prices. Calculate annualised mean (daily) historical return from input (daily) asset prices.
By default, this uses the arithmetic mean (correct if log_returns are used).
:param prices: adjusted closing prices of the asset, each row is a date :param prices: adjusted closing prices of the asset, each row is a date
and each column is a ticker/id. and each column is a ticker/id.
@@ -113,7 +120,7 @@ def mean_historical_return(
:param returns_data: if true, the first argument is returns instead of prices. :param returns_data: if true, the first argument is returns instead of prices.
:type returns_data: bool, defaults to False. :type returns_data: bool, defaults to False.
:param compounding: whether to properly compound the returns, optional. :param compounding: whether to properly compound the returns, optional.
:type compounding: bool, defaults to False :type compounding: bool, defaults to True
:param frequency: number of time periods in a year, defaults to 252 (the number :param frequency: number of time periods in a year, defaults to 252 (the number
of trading days in a year) of trading days in a year)
:type frequency: int, optional :type frequency: int, optional
@@ -128,13 +135,13 @@ def mean_historical_return(
else: else:
returns = returns_from_prices(prices) returns = returns_from_prices(prices)
if compounding: if compounding:
return (1 + returns.mean()) ** frequency - 1 return (1 + returns).prod() ** (frequency / returns.count()) - 1
else: else:
return returns.mean() * frequency return returns.mean() * frequency
def ema_historical_return( def ema_historical_return(
prices, returns_data=False, compounding=False, span=500, frequency=252 prices, returns_data=False, compounding=True, span=500, frequency=252
): ):
""" """
Calculate the exponentially-weighted mean of (daily) historical returns, giving Calculate the exponentially-weighted mean of (daily) historical returns, giving
@@ -146,7 +153,7 @@ def ema_historical_return(
:param returns_data: if true, the first argument is returns instead of prices. :param returns_data: if true, the first argument is returns instead of prices.
:type returns_data: bool, defaults to False. :type returns_data: bool, defaults to False.
:param compounding: whether to properly compound the returns, optional. :param compounding: whether to properly compound the returns, optional.
:type compounding: bool, defaults to False :type compounding: bool, defaults to True
:param frequency: number of time periods in a year, defaults to 252 (the number :param frequency: number of time periods in a year, defaults to 252 (the number
of trading days in a year) of trading days in a year)
:type frequency: int, optional :type frequency: int, optional
@@ -169,49 +176,10 @@ def ema_historical_return(
return returns.ewm(span=span).mean().iloc[-1] * frequency return returns.ewm(span=span).mean().iloc[-1] * frequency
def james_stein_shrinkage(prices, returns_data=False, compounding=False, frequency=252): def james_stein_shrinkage(prices, returns_data=False, compounding=True, frequency=252):
r""" raise NotImplementedError(
Compute the James-Stein shrinkage estimator, i.e "Deprecated because its implementation here was misguided."
)
.. math::
\hat{\mu}_i^{JS} = \hat{\kappa} \bar{\mu} + (1-\hat{\kappa}) \mu_i,
where :math:`\kappa` is the shrinkage parameter, :math:`\bar{\mu}` is the shrinkage
target (grand average), and :math:`\mu` is the vector of mean returns.
:param prices: adjusted closing prices of the asset, each row is a date
and each column is a ticker/id.
:type prices: pd.DataFrame
:param returns_data: if true, the first argument is returns instead of prices.
:type returns_data: bool, defaults to False.
:param compounded: whether to properly compound the returns, optional.
:type compounding: bool, defaults to False
:param frequency: number of time periods in a year, defaults to 252 (the number
of trading days in a year)
:type frequency: int, optional
:return: James-Stein estimate of annualised return
:rtype: pd.Series
"""
if not isinstance(prices, pd.DataFrame):
warnings.warn("prices are not in a dataframe", RuntimeWarning)
prices = pd.DataFrame(prices)
if returns_data:
returns = prices
else:
returns = returns_from_prices(prices)
T, n = returns.shape
mu = returns.mean(axis=0)
mu_bar = mu.mean()
sigma_squared = 1 / T * mu_bar * (1 - mu_bar) # binomial estimate
kappa = 1 - (n - 3) * sigma_squared / np.sum((mu - mu_bar) ** 2)
theta_js = (1 - kappa) * mu + kappa * mu_bar
if compounding:
return (1 + theta_js) ** frequency - 1
else:
return theta_js * frequency
def capm_return( def capm_return(
@@ -219,7 +187,7 @@ def capm_return(
market_prices=None, market_prices=None,
returns_data=False, returns_data=False,
risk_free_rate=0.02, risk_free_rate=0.02,
compounding=False, compounding=True,
frequency=252, frequency=252,
): ):
""" """
@@ -244,7 +212,7 @@ def capm_return(
to the frequency parameter. to the frequency parameter.
:type risk_free_rate: float, optional :type risk_free_rate: float, optional
:param compounding: whether to properly compound the returns, optional. :param compounding: whether to properly compound the returns, optional.
:type compounding: bool, defaults to False :type compounding: bool, defaults to True
:param frequency: number of time periods in a year, defaults to 252 (the number :param frequency: number of time periods in a year, defaults to 252 (the number
of trading days in a year) of trading days in a year)
:type frequency: int, optional :type frequency: int, optional
@@ -267,7 +235,6 @@ def capm_return(
if market_returns is None: if market_returns is None:
# Append market return to right and compute sample covariance matrix # Append market return to right and compute sample covariance matrix
returns["mkt"] = returns.mean(axis=1) returns["mkt"] = returns.mean(axis=1)
else: else:
market_returns.columns = ["mkt"] market_returns.columns = ["mkt"]
returns = returns.join(market_returns, how="left") returns = returns.join(market_returns, how="left")
@@ -279,7 +246,9 @@ def capm_return(
betas = betas.drop("mkt") betas = betas.drop("mkt")
# Find mean market return on a given time period # Find mean market return on a given time period
if compounding: if compounding:
mkt_mean_ret = (1 + returns["mkt"].mean()) ** frequency - 1 mkt_mean_ret = (1 + returns["mkt"]).prod() ** (
frequency / returns["mkt"].count()
) - 1
else: else:
mkt_mean_ret = returns["mkt"].mean() * frequency mkt_mean_ret = returns["mkt"].mean() * frequency

View File

@@ -13,7 +13,6 @@ Currently implemented:
""" """
import collections import collections
import warnings
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import scipy.cluster.hierarchy as sch import scipy.cluster.hierarchy as sch
@@ -139,13 +138,19 @@ class HRPOpt(base_optimizer.BaseOptimizer):
w[second_cluster] *= 1 - alpha # weight 2 w[second_cluster] *= 1 - alpha # weight 2
return w return w
def optimize(self): def optimize(self, linkage_method="single"):
""" """
Construct a hierarchical risk parity portfolio Construct a hierarchical risk parity portfolio, using Scipy hierarchical clustering
(see `here <https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html>`_)
:param linkage_method: which scipy linkage method to use
:type linkage_method: str
:return: weights for the HRP portfolio :return: weights for the HRP portfolio
:rtype: OrderedDict :rtype: OrderedDict
""" """
if linkage_method not in sch._LINKAGE_METHODS:
raise ValueError("linkage_method must be one recognised by scipy")
if self.returns is None: if self.returns is None:
cov = self.cov_matrix cov = self.cov_matrix
corr = risk_models.cov_to_corr(self.cov_matrix).round(6) corr = risk_models.cov_to_corr(self.cov_matrix).round(6)
@@ -159,7 +164,7 @@ class HRPOpt(base_optimizer.BaseOptimizer):
matrix = np.sqrt(np.clip((1.0 - corr) / 2.0, a_min=0.0, a_max=1.0)) matrix = np.sqrt(np.clip((1.0 - corr) / 2.0, a_min=0.0, a_max=1.0))
dist = ssd.squareform(matrix, checks=False) dist = ssd.squareform(matrix, checks=False)
self.clusters = sch.linkage(dist, "single") self.clusters = sch.linkage(dist, linkage_method)
sort_ix = HRPOpt._get_quasi_diag(self.clusters) sort_ix = HRPOpt._get_quasi_diag(self.clusters)
ordered_tickers = corr.index[sort_ix].tolist() ordered_tickers = corr.index[sort_ix].tolist()
hrp = HRPOpt._raw_hrp_allocation(cov, ordered_tickers) hrp = HRPOpt._raw_hrp_allocation(cov, ordered_tickers)

View File

@@ -117,6 +117,12 @@ def L2_reg(w, gamma=1):
r""" r"""
L2 regularisation, i.e :math:`\gamma ||w||^2`, to increase the number of nonzero weights. L2 regularisation, i.e :math:`\gamma ||w||^2`, to increase the number of nonzero weights.
Example::
ef = EfficientFrontier(mu, S)
ef.add_objective(objective_functions.L2_reg, gamma=2)
ef.min_volatility()
:param w: asset weights in the portfolio :param w: asset weights in the portfolio
:type w: np.ndarray OR cp.Variable :type w: np.ndarray OR cp.Variable
:param gamma: L2 regularisation parameter, defaults to 1. Increase if you want more :param gamma: L2 regularisation parameter, defaults to 1. Increase if you want more

View File

@@ -39,7 +39,8 @@ def _is_positive_semidefinite(matrix):
:rtype: bool :rtype: bool
""" """
try: try:
np.linalg.cholesky(matrix) # Significantly more efficient than checking eigenvalues (stackoverflow.com/questions/16266720)
np.linalg.cholesky(matrix + 1e-16 * np.eye(len(matrix)))
return True return True
except np.linalg.LinAlgError: except np.linalg.LinAlgError:
return False return False

View File

@@ -35,7 +35,8 @@ python = "^3.5"
numpy = "^1.12" numpy = "^1.12"
scipy = "^1.3" scipy = "^1.3"
pandas = ">=0.19" pandas = ">=0.19"
cvxpy = "~1.0" cvxpy = "^1.0"
cvxopt = "^1.2"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^4.6" pytest = "^4.6"

View File

@@ -3,4 +3,5 @@ numpy>=1.17
pandas>=0.19 pandas>=0.19
scikit-learn>=0.19 scikit-learn>=0.19
scipy>=1.3 scipy>=1.3
cvxpy==1.0.28 cvxpy>=1.0
cvxopt>=1.0

View File

@@ -143,26 +143,26 @@ def test_bl_returns_all_views():
posterior_rets, posterior_rets,
np.array( np.array(
[ [
0.11774473, 0.11168648,
0.1709139, 0.16782938,
0.12180833, 0.12516799,
0.21202423, 0.24067997,
0.28120945, 0.32848296,
-0.2787358, -0.22789895,
0.17274774, 0.16311297,
0.12714698, 0.11928542,
0.25492005, 0.25414308,
0.11229777, 0.11007738,
0.07182723, 0.06282615,
-0.01521839, -0.03140218,
-0.21235465, -0.16977172,
0.06399515, 0.05254821,
-0.11738365, -0.10463884,
0.28865661, 0.32173375,
0.23828607, 0.26399864,
0.12038049, 0.1118594,
0.2331218, 0.22999558,
0.10485376, 0.08977448,
] ]
), ),
) )

View File

@@ -28,7 +28,7 @@ def test_cla_max_sharpe_long_only():
np.testing.assert_allclose( np.testing.assert_allclose(
cla.portfolio_performance(), cla.portfolio_performance(),
(0.3253436663900292, 0.21333530089904357, 1.4312852355106793), (0.2994470912768992, 0.21764331657015668, 1.283968171780824),
) )
@@ -40,7 +40,7 @@ def test_cla_max_sharpe_short():
np.testing.assert_almost_equal(cla.weights.sum(), 1) np.testing.assert_almost_equal(cla.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
cla.portfolio_performance(), cla.portfolio_performance(),
(0.3799273115521356, 0.23115368271125736, 1.5570909679242886), (0.44859872371106785, 0.26762066559448255, 1.601515797589826),
) )
sharpe = cla.portfolio_performance()[2] sharpe = cla.portfolio_performance()[2]
@@ -72,7 +72,7 @@ def test_cla_min_volatility():
np.testing.assert_almost_equal(cla.weights.sum(), 1) np.testing.assert_almost_equal(cla.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
cla.portfolio_performance(), cla.portfolio_performance(),
(0.1793123248125915, 0.15915084514118688, 1.00101463282373), (0.1505682139948257, 0.15915084514118688, 0.8204054077060994),
) )
@@ -99,7 +99,7 @@ def test_cla_max_sharpe_semicovariance():
np.testing.assert_almost_equal(cla.weights.sum(), 1) np.testing.assert_almost_equal(cla.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
cla.portfolio_performance(), cla.portfolio_performance(),
(0.2936179968144084, 0.06362345488289835, 4.300583759841616), (0.2686858719299194, 0.06489248187610204, 3.8322755539652578),
) )
@@ -113,7 +113,7 @@ def test_cla_max_sharpe_exp_cov():
np.testing.assert_almost_equal(cla.weights.sum(), 1) np.testing.assert_almost_equal(cla.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
cla.portfolio_performance(), cla.portfolio_performance(),
(0.3619453128519127, 0.1724297730592084, 1.9830990135009723), (0.32971891062187103, 0.17670121760851704, 1.7527831149871063),
) )
@@ -127,7 +127,7 @@ def test_cla_min_volatility_exp_cov_short():
np.testing.assert_almost_equal(cla.weights.sum(), 1) np.testing.assert_almost_equal(cla.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
cla.portfolio_performance(), cla.portfolio_performance(),
(0.2634735528776959, 0.13259590618253303, 1.8362071642131053), (0.23215576461823062, 0.1325959061825329, 1.6000174569958052),
) )

View File

@@ -91,7 +91,7 @@ def test_custom_convex_logarithmic_barrier():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.23978400459553223, 0.21100848889958182, 1.041588448605623), (0.17261881638711316, 0.21100848889958182, 0.7232828270702603),
) )
@@ -101,7 +101,7 @@ def test_custom_convex_deviation_risk_parity_error():
def deviation_risk_parity(w, cov_matrix): def deviation_risk_parity(w, cov_matrix):
n = cov_matrix.shape[0] n = cov_matrix.shape[0]
rp = (w * (cov_matrix @ w)) / cp.quad_form(w, cov_matrix) rp = (w @ (cov_matrix @ w)) / cp.quad_form(w, cov_matrix)
return cp.sum_squares(rp - 1 / n) return cp.sum_squares(rp - 1 / n)
with pytest.raises(exceptions.OptimizationError): with pytest.raises(exceptions.OptimizationError):
@@ -287,6 +287,6 @@ def test_custom_nonconvex_objective_market_neutral_efficient_risk():
) )
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2309497754562942, target_risk, 1.1102600451243954), (0.2591296227818582, target_risk, 1.258574109251818),
atol=1e-6, atol=1e-6,
) )

View File

@@ -67,7 +67,9 @@ def test_greedy_allocation_rmse_error():
latest_prices = get_latest_prices(df) latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
da.greedy_portfolio() da.greedy_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.025762032436733803) np.testing.assert_almost_equal(
da._allocation_rmse_error(verbose=False), 0.017086185150415774
)
def test_greedy_portfolio_allocation_short(): def test_greedy_portfolio_allocation_short():
@@ -81,25 +83,25 @@ def test_greedy_portfolio_allocation_short():
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
allocation, leftover = da.greedy_portfolio() allocation, leftover = da.greedy_portfolio()
assert da.allocation == { assert allocation == {
"MA": 15, "MA": 19,
"PFE": 45, "PFE": 42,
"FB": 8, "FB": 7,
"GOOG": 1,
"BABA": 5, "BABA": 5,
"AAPL": 4, "AAPL": 4,
"BBY": 8, "SBUX": 8,
"AMZN": 1, "BBY": 6,
"SBUX": 9, "XOM": 4,
"WMT": 2, "WMT": 3,
"XOM": 2,
"BAC": -32, "BAC": -32,
"GM": -16, "AMD": -48,
"GE": -43, "SHLD": -132,
"SHLD": -110, "GM": -9,
"AMD": -34, "RRC": -19,
"JPM": -1, "GE": -14,
"T": -1, "T": -5,
"UAA": -1, "UAA": -8,
} }
long_total = 0 long_total = 0
short_total = 0 short_total = 0
@@ -123,7 +125,9 @@ def test_greedy_allocation_rmse_error_short():
latest_prices = get_latest_prices(df) latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
da.greedy_portfolio() da.greedy_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.033070015016740284) np.testing.assert_almost_equal(
da._allocation_rmse_error(verbose=False), 0.06063511265243106
)
def test_greedy_portfolio_allocation_short_different_params(): def test_greedy_portfolio_allocation_short_different_params():
@@ -139,27 +143,26 @@ def test_greedy_portfolio_allocation_short_different_params():
) )
allocation, leftover = da.greedy_portfolio() allocation, leftover = da.greedy_portfolio()
assert da.allocation == { assert allocation == {
"MA": 77, "MA": 96,
"PFE": 225, "PFE": 211,
"FB": 41, "FB": 34,
"BABA": 25, "GOOG": 4,
"AAPL": 23, "BABA": 22,
"BBY": 44, "AAPL": 17,
"SBUX": 38,
"AMZN": 2, "AMZN": 2,
"SBUX": 45, "BBY": 27,
"GOOG": 3, "XOM": 19,
"WMT": 11, "WMT": 10,
"XOM": 11, "BAC": -269,
"BAC": -271, "AMD": -399,
"GM": -133, "SHLD": -1099,
"GE": -356, "GM": -78,
"SHLD": -922, "RRC": -154,
"AMD": -285, "GE": -119,
"JPM": -5, "T": -41,
"T": -14, "UAA": -64,
"UAA": -8,
"RRC": -3,
} }
long_total = 0 long_total = 0
short_total = 0 short_total = 0
@@ -182,15 +185,15 @@ def test_lp_portfolio_allocation():
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
allocation, leftover = da.lp_portfolio() allocation, leftover = da.lp_portfolio()
assert da.allocation == { assert allocation == {
"AAPL": 5, "GOOG": 1,
"FB": 11, "AAPL": 4,
"BABA": 5, "FB": 12,
"AMZN": 1, "BABA": 4,
"BBY": 7, "BBY": 2,
"MA": 14, "MA": 20,
"PFE": 50, "PFE": 54,
"SBUX": 5, "SBUX": 1,
} }
total = 0 total = 0
for ticker, num in allocation.items(): for ticker, num in allocation.items():
@@ -208,7 +211,9 @@ def test_lp_allocation_rmse_error():
latest_prices = get_latest_prices(df) latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
da.lp_portfolio() da.lp_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(verbose=False), 0.017070218149194846) np.testing.assert_almost_equal(
da._allocation_rmse_error(verbose=False), 0.017082871441954087, decimal=5
)
def test_lp_portfolio_allocation_short(): def test_lp_portfolio_allocation_short():
@@ -222,25 +227,25 @@ def test_lp_portfolio_allocation_short():
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
allocation, leftover = da.lp_portfolio() allocation, leftover = da.lp_portfolio()
assert da.allocation == { assert allocation == {
"GOOG": 1, "GOOG": 1,
"AAPL": 5, "AAPL": 4,
"FB": 8, "FB": 7,
"BABA": 5, "BABA": 5,
"WMT": 2, "WMT": 3,
"XOM": 2, "XOM": 4,
"BBY": 9, "BBY": 6,
"MA": 16, "MA": 19,
"PFE": 46, "PFE": 42,
"SBUX": 9, "SBUX": 8,
"GE": -43, "GE": -14,
"AMD": -34, "AMD": -48,
"BAC": -32, "BAC": -32,
"GM": -16, "GM": -9,
"T": -1, "T": -5,
"UAA": -1, "UAA": -8,
"SHLD": -110, "SHLD": -132,
"JPM": -1, "RRC": -19,
} }
long_total = 0 long_total = 0
short_total = 0 short_total = 0
@@ -264,7 +269,9 @@ def test_lp_allocation_rmse_error_short():
latest_prices = get_latest_prices(df) latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices) da = DiscreteAllocation(w, latest_prices)
da.lp_portfolio() da.lp_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.027018566693989568) np.testing.assert_almost_equal(
da._allocation_rmse_error(verbose=False), 0.06063511265243109
)
def test_lp_portfolio_allocation_different_params(): def test_lp_portfolio_allocation_different_params():
@@ -280,19 +287,18 @@ def test_lp_portfolio_allocation_different_params():
) )
allocation, leftover = da.lp_portfolio() allocation, leftover = da.lp_portfolio()
assert da.allocation == { assert allocation == {
"GOOG": 1, "GOOG": 3,
"AAPL": 43, "AAPL": 32,
"FB": 95, "FB": 100,
"BABA": 44, "BABA": 34,
"AMZN": 4, "AMZN": 2,
"AMD": 1, "BBY": 15,
"SHLD": 3, "MA": 164,
"BBY": 69, "PFE": 438,
"MA": 114, "SBUX": 15,
"PFE": 412,
"SBUX": 51,
} }
total = 0 total = 0
for ticker, num in allocation.items(): for ticker, num in allocation.items():
total += num * latest_prices[ticker] total += num * latest_prices[ticker]

View File

@@ -61,7 +61,7 @@ def test_min_volatility():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.17931232481259154, 0.15915084514118694, 1.00101463282373), (0.15056821399482578, 0.15915084514118694, 0.8204054077060996),
) )
@@ -72,7 +72,7 @@ def test_min_volatility_different_solver():
assert set(w.keys()) == set(ef.tickers) assert set(w.keys()) == set(ef.tickers)
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
test_performance = (0.179312, 0.159151, 1.001015) test_performance = (0.150567, 0.159150, 0.820403)
np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-5) np.testing.assert_allclose(ef.portfolio_performance(), test_performance, atol=1e-5)
ef = setup_efficient_frontier(solver="OSQP") ef = setup_efficient_frontier(solver="OSQP")
@@ -124,7 +124,7 @@ def test_min_volatility_short():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.1721356467349655, 0.1555915367269669, 0.9777887019776287), (0.1516319319875544, 0.1555915367269669, 0.8460095886741129),
) )
# Shorting should reduce volatility # Shorting should reduce volatility
@@ -156,7 +156,7 @@ def test_min_volatility_L2_reg():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.23129890623344232, 0.1955254118258614, 1.080672349748733), (0.17356099329164965, 0.1955254118258614, 0.785376140408869),
) )
@@ -205,7 +205,7 @@ def test_min_volatility_tx_costs_L2_reg():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2316565265271545, 0.1959773703677164, 1.0800049318450338), (0.17363446634404042, 0.1959773703677164, 0.7839398296638683),
) )
@@ -335,7 +335,7 @@ def test_max_sharpe_long_only():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.33035037367760506, 0.21671276571944567, 1.4320816434015786), (0.3047768672819914, 0.22165566922402932, 1.2847714127003216),
) )
@@ -380,7 +380,7 @@ def test_max_sharpe_short():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.4072439477276246, 0.24823487545231313, 1.5599900981762558), (0.4937195216716211, 0.29516576454651955, 1.6049270564945908),
) )
sharpe = ef.portfolio_performance()[2] sharpe = ef.portfolio_performance()[2]
@@ -395,7 +395,7 @@ def test_max_sharpe_L2_reg():
ef = setup_efficient_frontier() ef = setup_efficient_frontier()
ef.add_objective(objective_functions.L2_reg, gamma=5) ef.add_objective(objective_functions.L2_reg, gamma=5)
with warnings.catch_warnings(record=True) as w: with pytest.warns(UserWarning) as w:
weights = ef.max_sharpe() weights = ef.max_sharpe()
assert len(w) == 1 assert len(w) == 1
@@ -405,7 +405,7 @@ def test_max_sharpe_L2_reg():
assert all([i >= 0 for i in weights.values()]) assert all([i >= 0 for i in weights.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2936875354933478, 0.22783545277575057, 1.2012508683744123), (0.2516854357026833, 0.22043282695478603, 1.051047790401043),
) )
ef2 = setup_efficient_frontier() ef2 = setup_efficient_frontier()
@@ -481,7 +481,7 @@ def test_max_sharpe_L2_reg_with_shorts():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.3076093180094401, 0.22415982749409985, 1.2830546901496447), (0.2995338981166366, 0.2234696161770517, 1.2508810052063901),
) )
new_number = sum(ef.weights > 0.01) new_number = sum(ef.weights > 0.01)
assert new_number >= initial_number assert new_number >= initial_number
@@ -733,7 +733,7 @@ def test_max_quadratic_utility():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.40064324249527605, 0.2917825266124642, 1.3045443362029479), (0.3677732711751504, 0.2921342197778279, 1.1904571516463793),
) )
ret1, var1, _ = ef.portfolio_performance() ret1, var1, _ = ef.portfolio_performance()
@@ -752,7 +752,7 @@ def test_max_quadratic_utility_with_shorts():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(1.3318330413711252, 1.0198436183533854, 1.2863080356272452), (1.4170505733098597, 1.0438577623242156, 1.3383533884915872),
) )
@@ -764,7 +764,7 @@ def test_max_quadratic_utility_market_neutral():
np.testing.assert_almost_equal(ef.weights.sum(), 0) np.testing.assert_almost_equal(ef.weights.sum(), 0)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(1.13434841843883, 0.9896404148973286, 1.1260134506071473), (1.248936321062371, 1.0219175004907117, 1.2025787996313317),
) )
@@ -789,7 +789,7 @@ def test_max_quadratic_utility_L2_reg():
assert all([i >= 0 for i in weights.values()]) assert all([i >= 0 for i in weights.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2602803268728476, 0.21603540587515674, 1.112226608872166), (0.19774277217586125, 0.2104822672707046, 0.8444548535162986),
) )
ef2 = setup_efficient_frontier() ef2 = setup_efficient_frontier()
@@ -820,7 +820,7 @@ def test_efficient_risk():
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.28577452556155075, 0.19, 1.3988132892376837), (0.2552422849133517, 0.1900000002876062, 1.2381172871434818),
atol=1e-6, atol=1e-6,
) )
@@ -834,7 +834,7 @@ def test_efficient_risk_error():
assert ef.efficient_risk(min_possible_vol + 0.01) assert ef.efficient_risk(min_possible_vol + 0.01)
ef = setup_efficient_frontier() ef = setup_efficient_frontier()
with pytest.raises(exceptions.OptimizationError): with pytest.raises(ValueError):
# This volatility is too low # This volatility is too low
ef.efficient_risk(min_possible_vol - 0.01) ef.efficient_risk(min_possible_vol - 0.01)
@@ -859,7 +859,7 @@ def test_efficient_risk_short():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.30468522897430295, 0.19, 1.4983424153337392), (0.30035471606347336, 0.1900000003049494, 1.4755511348079207),
atol=1e-6, atol=1e-6,
) )
sharpe = ef.portfolio_performance()[2] sharpe = ef.portfolio_performance()[2]
@@ -882,7 +882,7 @@ def test_efficient_risk_L2_reg():
assert all([i >= 0 for i in weights.values()]) assert all([i >= 0 for i in weights.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.24087463760460398, 0.19, 1.162498090632486), (0.1931352562313653, 0.18999999989010993, 0.9112381912184281),
atol=1e-6, atol=1e-6,
) )
@@ -908,7 +908,7 @@ def test_efficient_risk_market_neutral():
assert (ef.weights < 1).all() and (ef.weights > -1).all() assert (ef.weights < 1).all() and (ef.weights > -1).all()
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2552600197428133, 0.21, 1.1202858085349783), (0.28640632960825885, 0.20999999995100788, 1.2686015698590967),
atol=1e-6, atol=1e-6,
) )
sharpe = ef.portfolio_performance()[2] sharpe = ef.portfolio_performance()[2]
@@ -933,17 +933,16 @@ def test_efficient_risk_market_neutral_L2_reg():
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.10755645826336145, 0.11079556786108302, 0.7902523535340413), (0.12790320789339854, 0.1175336636355454, 0.9180621496492316),
atol=1e-6, atol=1e-6,
) )
def test_efficient_risk_market_neutral_warning(): def test_efficient_risk_market_neutral_warning():
ef = setup_efficient_frontier() ef = setup_efficient_frontier()
with warnings.catch_warnings(record=True) as w: with pytest.warns(RuntimeWarning) as w:
ef.efficient_risk(0.19, market_neutral=True) ef.efficient_risk(0.19, market_neutral=True)
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[0].category, RuntimeWarning)
assert ( assert (
str(w[0].message) str(w[0].message)
== "Market neutrality requires shorting - bounds have been amended" == "Market neutrality requires shorting - bounds have been amended"
@@ -959,7 +958,7 @@ def test_efficient_return():
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.25, 0.1738852429895079, 1.3227114391408021), (0.25, 0.18723269942026335, 1.2284179030274036),
atol=1e-6, atol=1e-6,
) )
@@ -994,7 +993,7 @@ def test_efficient_return_short():
assert set(w.keys()) == set(ef.tickers) assert set(w.keys()) == set(ef.tickers)
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), (0.25, 0.16826225873038014, 1.3669137793315087) ef.portfolio_performance(), (0.25, 0.17149595234895817, 1.3411395245760582)
) )
sharpe = ef.portfolio_performance()[2] sharpe = ef.portfolio_performance()[2]
@@ -1014,7 +1013,7 @@ def test_efficient_return_L2_reg():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), (0.25, 0.20033592447690426, 1.1480716731187948) ef.portfolio_performance(), (0.25, 0.20961660883459776, 1.0972412981906703)
) )
@@ -1028,22 +1027,21 @@ def test_efficient_return_market_neutral():
np.testing.assert_almost_equal(ef.weights.sum(), 0) np.testing.assert_almost_equal(ef.weights.sum(), 0)
assert (ef.weights < 1).all() and (ef.weights > -1).all() assert (ef.weights < 1).all() and (ef.weights > -1).all()
np.testing.assert_almost_equal( np.testing.assert_almost_equal(
ef.portfolio_performance(), (0.25, 0.20567263154580923, 1.1182819914898223) ef.portfolio_performance(), (0.25, 0.1833060046337015, 1.2547324920403273)
) )
sharpe = ef.portfolio_performance()[2] sharpe = ef.portfolio_performance()[2]
ef_long_only = setup_efficient_frontier() ef_long_only = setup_efficient_frontier()
ef_long_only.efficient_return(0.25) ef_long_only.efficient_return(0.25)
long_only_sharpe = ef_long_only.portfolio_performance()[2] long_only_sharpe = ef_long_only.portfolio_performance()[2]
assert long_only_sharpe > sharpe assert long_only_sharpe < sharpe
def test_efficient_return_market_neutral_warning(): def test_efficient_return_market_neutral_warning():
# This fails # This fails
ef = setup_efficient_frontier() ef = setup_efficient_frontier()
with warnings.catch_warnings(record=True) as w: with pytest.warns(RuntimeWarning) as w:
ef.efficient_return(0.25, market_neutral=True) ef.efficient_return(0.25, market_neutral=True)
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[0].category, RuntimeWarning)
assert ( assert (
str(w[0].message) str(w[0].message)
== "Market neutrality requires shorting - bounds have been amended" == "Market neutrality requires shorting - bounds have been amended"
@@ -1061,7 +1059,7 @@ def test_max_sharpe_semicovariance():
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2972184894480104, 0.06443145011260347, 4.302533762060766), (0.2732301946250426, 0.06603231922971581, 3.834943215368455),
) )
@@ -1077,7 +1075,7 @@ def test_max_sharpe_short_semicovariance():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.3564305116656491, 0.07201282488003401, 4.671813836300796), (0.3907992623559733, 0.0809285460933456, 4.581810501430255),
) )
@@ -1097,7 +1095,7 @@ def test_min_volatilty_shrunk_L2_reg():
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.23127396601517256, 0.19563960638632416, 1.0799140824173181), (0.17358178582309983, 0.19563960638632416, 0.7850239972361532),
) )
@@ -1113,7 +1111,7 @@ def test_efficient_return_shrunk():
np.testing.assert_almost_equal(ef.weights.sum(), 1) np.testing.assert_almost_equal(ef.weights.sum(), 1)
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), (0.22, 0.0849639369932322, 2.353939884117318) ef.portfolio_performance(), (0.22, 0.08892192396903059, 2.2491641101878916)
) )
@@ -1128,7 +1126,7 @@ def test_max_sharpe_exp_cov():
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.3678817256187322, 0.1753405505478982, 1.9840346373481956), (0.33700887443850647, 0.1807332515488447, 1.7540152225548384),
) )
@@ -1144,7 +1142,7 @@ def test_min_volatility_exp_cov_L2_reg():
assert all([i >= 0 for i in w.values()]) assert all([i >= 0 for i in w.values()])
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.2434082300792007, 0.17835412793427002, 1.2526103694192867), (0.1829496087575576, 0.17835412793427002, 0.9136295898775636),
) )
@@ -1161,6 +1159,6 @@ def test_efficient_risk_exp_cov_market_neutral():
assert (ef.weights < 1).all() and (ef.weights > -1).all() assert (ef.weights < 1).all() and (ef.weights > -1).all()
np.testing.assert_allclose( np.testing.assert_allclose(
ef.portfolio_performance(), ef.portfolio_performance(),
(0.3908928033782067, 0.18999999995323363, 1.9520673866815672), (0.3934093962620499, 0.18999999989011893, 1.9653126130421081),
atol=1e-6, atol=1e-6,
) )

View File

@@ -38,7 +38,7 @@ def test_returns_from_prices():
def test_log_returns_from_prices(): def test_log_returns_from_prices():
df = get_data() df = get_data()
old_nan = df.isnull().sum(axis=1).sum() old_nan = df.isnull().sum(axis=1).sum()
log_rets = expected_returns.log_returns_from_prices(df) log_rets = expected_returns.returns_from_prices(df, log_returns=True)
new_nan = log_rets.isnull().sum(axis=1).sum() new_nan = log_rets.isnull().sum(axis=1).sum()
assert new_nan == old_nan assert new_nan == old_nan
np.testing.assert_almost_equal(log_rets.iloc[-1, -1], 0.0001682740081102576) np.testing.assert_almost_equal(log_rets.iloc[-1, -1], 0.0001682740081102576)
@@ -55,17 +55,12 @@ def test_mean_historical_returns_dummy():
] ]
) )
mean = expected_returns.mean_historical_return(data, frequency=1) mean = expected_returns.mean_historical_return(data, frequency=1)
test_answer = pd.Series([0.00865598, 0.025, 0.01286968, -0.03632333]) test_answer = pd.Series([0.0061922, 0.0241137, 0.0122722, -0.0421775])
pd.testing.assert_series_equal(mean, test_answer) pd.testing.assert_series_equal(mean, test_answer, check_less_precise=4)
mean = expected_returns.mean_historical_return(data, compounding=True, frequency=1)
pd.testing.assert_series_equal(mean, test_answer)
mean = expected_returns.mean_historical_return(data, compounding=False, frequency=1)
def test_mean_historical_returns_compounding(): test_answer = pd.Series([0.0086560, 0.0250000, 0.0128697, -0.03632333])
df = get_data() pd.testing.assert_series_equal(mean, test_answer, check_less_precise=4)
mean = expected_returns.mean_historical_return(df)
mean2 = expected_returns.mean_historical_return(df, compounding=True)
assert (mean2 >= mean).all()
def test_mean_historical_returns(): def test_mean_historical_returns():
@@ -77,26 +72,26 @@ def test_mean_historical_returns():
assert mean.dtype == "float64" assert mean.dtype == "float64"
correct_mean = np.array( correct_mean = np.array(
[ [
0.26770284, 0.247967,
0.3637864, 0.294304,
0.31709032, 0.284037,
0.22616723, 0.1923164,
0.49982007, 0.371327,
0.16888704, 0.1360093,
0.22754479, 0.0328503,
0.14783539, 0.1200115,
0.19001915, 0.105540,
0.08150653, 0.0423457,
0.12826351, 0.1002559,
0.25797816, 0.1442237,
0.07580128, -0.0792602,
0.16087243, 0.1430506,
0.20510267, 0.0736356,
0.3511536, 0.238835,
0.38808003, 0.388665,
0.24635612, 0.226717,
0.21798433, 0.1561701,
0.28474973, 0.2318153,
] ]
) )
np.testing.assert_array_almost_equal(mean.values, correct_mean) np.testing.assert_array_almost_equal(mean.values, correct_mean)
@@ -106,10 +101,9 @@ def test_mean_historical_returns_type_warning():
df = get_data() df = get_data()
mean = expected_returns.mean_historical_return(df) mean = expected_returns.mean_historical_return(df)
with warnings.catch_warnings(record=True) as w: with pytest.warns(RuntimeWarning) as w:
mean_from_array = expected_returns.mean_historical_return(np.array(df)) mean_from_array = expected_returns.mean_historical_return(np.array(df))
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[0].category, RuntimeWarning)
assert str(w[0].message) == "prices are not in a dataframe" assert str(w[0].message) == "prices are not in a dataframe"
np.testing.assert_array_almost_equal(mean.values, mean_from_array.values, decimal=6) np.testing.assert_array_almost_equal(mean.values, mean_from_array.values, decimal=6)
@@ -117,8 +111,8 @@ def test_mean_historical_returns_type_warning():
def test_mean_historical_returns_frequency(): def test_mean_historical_returns_frequency():
df = get_data() df = get_data()
mean = expected_returns.mean_historical_return(df) mean = expected_returns.mean_historical_return(df, compounding=False)
mean2 = expected_returns.mean_historical_return(df, frequency=52) mean2 = expected_returns.mean_historical_return(df, compounding=False, frequency=52)
np.testing.assert_array_almost_equal(mean / 252, mean2 / 52) np.testing.assert_array_almost_equal(mean / 252, mean2 / 52)
@@ -133,56 +127,18 @@ def test_ema_historical_return():
def test_ema_historical_return_frequency(): def test_ema_historical_return_frequency():
df = get_data() df = get_data()
mean = expected_returns.ema_historical_return(df) mean = expected_returns.ema_historical_return(df, compounding=False)
mean2 = expected_returns.ema_historical_return(df, frequency=52) mean2 = expected_returns.ema_historical_return(df, compounding=False, frequency=52)
np.testing.assert_array_almost_equal(mean / 252, mean2 / 52) np.testing.assert_array_almost_equal(mean / 252, mean2 / 52)
mean3 = expected_returns.ema_historical_return(df, compounding=True)
assert (abs(mean3) > mean).all()
def test_ema_historical_return_limit(): def test_ema_historical_return_limit():
df = get_data() df = get_data()
sma = expected_returns.mean_historical_return(df) sma = expected_returns.mean_historical_return(df, compounding=False)
ema = expected_returns.ema_historical_return(df, span=1e10) ema = expected_returns.ema_historical_return(df, compounding=False, span=1e10)
np.testing.assert_array_almost_equal(ema.values, sma.values) np.testing.assert_array_almost_equal(ema.values, sma.values)
def test_james_stein():
df = get_data()
js = expected_returns.james_stein_shrinkage(df)
correct_mean = np.array(
[
0.25870218,
0.32318595,
0.29184719,
0.23082673,
0.41448111,
0.19238474,
0.23175124,
0.17825652,
0.20656697,
0.13374178,
0.16512141,
0.25217574,
0.12991287,
0.18700597,
0.21668984,
0.3147078,
0.33948993,
0.24437593,
0.225335,
0.27014272,
]
)
np.testing.assert_array_almost_equal(js.values, correct_mean)
# Test shrinkage
y = expected_returns.returns_from_prices(df).mean(axis=0) * 252
nu = y.mean()
assert (((js <= nu) & (js >= y)) | ((js >= nu) & (js <= y))).all()
def test_capm_no_benchmark(): def test_capm_no_benchmark():
df = get_data() df = get_data()
mu = expected_returns.capm_return(df) mu = expected_returns.capm_return(df)
@@ -192,26 +148,26 @@ def test_capm_no_benchmark():
assert mu.dtype == "float64" assert mu.dtype == "float64"
correct_mu = np.array( correct_mu = np.array(
[ [
0.21803135, 0.22148462799238577,
0.27902605, 0.2835429647498704,
0.14475533, 0.14693081977908462,
0.14668971, 0.1488989354304723,
0.40944875, 0.4162399750335195,
0.22361704, 0.22716772604184535,
0.39057166, 0.3970337136813829,
0.164807, 0.16733214988182069,
0.31280876, 0.31791477659742146,
0.17018046, 0.17279931642386534,
0.15044284, 0.15271750464365566,
0.34609161, 0.351778014382922,
0.3233097, 0.32859883451716376,
0.1479624, 0.1501938182844417,
0.26403991, 0.268295486802897,
0.31124465, 0.31632339201710874,
0.27312086, 0.27753479916328516,
0.16703193, 0.16959588523287855,
0.30396023, 0.3089119447773357,
0.25182927, 0.2558719211959501,
] ]
) )
np.testing.assert_array_almost_equal(mu.values, correct_mu) np.testing.assert_array_almost_equal(mu.values, correct_mu)
@@ -228,26 +184,26 @@ def test_capm_with_benchmark():
assert mu.dtype == "float64" assert mu.dtype == "float64"
correct_mu = np.array( correct_mu = np.array(
[ [
0.10903299, 0.09115799375654746,
0.11891232, 0.09905386632033128,
0.0659977, 0.05676282405265752,
0.07369941, 0.06291827346436336,
0.15948144, 0.13147799781014877,
0.12308759, 0.10239088012000815,
0.15907944, 0.1311567086884512,
0.08680978, 0.07339649698626659,
0.15778843, 0.1301248935078549,
0.0903294, 0.07620949056643983,
0.09043133, 0.07629095442513395,
0.14716681, 0.12163575425541985,
0.12510181, 0.10400070536161658,
0.0927869, 0.0781736030988492,
0.10990104, 0.09185177050469516,
0.12317033, 0.10245700691271296,
0.13596521, 0.11268307946677197,
0.09344662, 0.07870087187919145,
0.15457909, 0.1275598841214107,
0.11430041, 0.09536788741392595,
] ]
) )
np.testing.assert_array_almost_equal(mu.values, correct_mu) np.testing.assert_array_almost_equal(mu.values, correct_mu)
@@ -257,12 +213,7 @@ def test_risk_matrix_and_returns_data():
# Test the switcher method for simple calls # Test the switcher method for simple calls
df = get_data() df = get_data()
for method in { for method in {"mean_historical_return", "ema_historical_return", "capm_return"}:
"mean_historical_return",
"ema_historical_return",
"james_stein_shrinkage",
"capm_return",
}:
mu = expected_returns.return_model(df, method=method) mu = expected_returns.return_model(df, method=method)
assert isinstance(mu, pd.Series) assert isinstance(mu, pd.Series)

View File

@@ -10,7 +10,7 @@ def test_hrp_portfolio():
df = get_data() df = get_data()
returns = df.pct_change().dropna(how="all") returns = df.pct_change().dropna(how="all")
hrp = HRPOpt(returns) hrp = HRPOpt(returns)
w = hrp.optimize() w = hrp.optimize(linkage_method="single")
# uncomment this line if you want generating a new file # uncomment this line if you want generating a new file
# pd.Series(w).to_csv(resource("weights_hrp.csv")) # pd.Series(w).to_csv(resource("weights_hrp.csv"))
@@ -32,7 +32,7 @@ def test_portfolio_performance():
hrp = HRPOpt(returns) hrp = HRPOpt(returns)
with pytest.raises(ValueError): with pytest.raises(ValueError):
hrp.portfolio_performance() hrp.portfolio_performance()
hrp.optimize() hrp.optimize(linkage_method="single")
np.testing.assert_allclose( np.testing.assert_allclose(
hrp.portfolio_performance(), hrp.portfolio_performance(),
(0.21353402380950973, 0.17844159743748936, 1.084579081272277), (0.21353402380950973, 0.17844159743748936, 1.084579081272277),
@@ -43,7 +43,7 @@ def test_pass_cov_matrix():
df = get_data() df = get_data()
S = CovarianceShrinkage(df).ledoit_wolf() S = CovarianceShrinkage(df).ledoit_wolf()
hrp = HRPOpt(cov_matrix=S) hrp = HRPOpt(cov_matrix=S)
hrp.optimize() hrp.optimize(linkage_method="single")
perf = hrp.portfolio_performance() perf = hrp.portfolio_performance()
assert perf[0] is None and perf[2] is None assert perf[0] is None and perf[2] is None
np.testing.assert_almost_equal(perf[1], 0.10002783894982334) np.testing.assert_almost_equal(perf[1], 0.10002783894982334)
@@ -62,6 +62,6 @@ def test_quasi_dag():
df = get_data() df = get_data()
returns = df.pct_change().dropna(how="all") returns = df.pct_change().dropna(how="all")
hrp = HRPOpt(returns) hrp = HRPOpt(returns)
hrp.optimize() hrp.optimize(linkage_method="single")
clusters = hrp.clusters clusters = hrp.clusters
assert HRPOpt._get_quasi_diag(clusters)[:5] == [12, 6, 15, 14, 2] assert HRPOpt._get_quasi_diag(clusters)[:5] == [12, 6, 15, 14, 2]

View File

@@ -36,9 +36,9 @@ def test_ef_plot():
cla = CLA(rets, S) cla = CLA(rets, S)
ax = plotting.plot_efficient_frontier(cla, showfig=False) ax = plotting.plot_efficient_frontier(cla, showfig=False)
assert len(ax.findobj()) == 137 assert len(ax.findobj()) == 143
ax = plotting.plot_efficient_frontier(cla, show_assets=False, showfig=False) ax = plotting.plot_efficient_frontier(cla, show_assets=False, showfig=False)
assert len(ax.findobj()) == 149 assert len(ax.findobj()) == 161
def test_weight_plot(): def test_weight_plot():

View File

@@ -27,6 +27,11 @@ def test_sample_cov_dummy():
pd.testing.assert_frame_equal(S, test_answer) pd.testing.assert_frame_equal(S, test_answer)
def test_is_positive_semidefinite():
a = np.zeros((100, 100))
assert risk_models._is_positive_semidefinite(a)
def test_sample_cov_real_data(): def test_sample_cov_real_data():
df = get_data() df = get_data()
S = risk_models.sample_cov(df) S = risk_models.sample_cov(df)
@@ -42,11 +47,9 @@ def test_sample_cov_type_warning():
cov_from_df = risk_models.sample_cov(df) cov_from_df = risk_models.sample_cov(df)
returns_as_array = np.array(df) returns_as_array = np.array(df)
with warnings.catch_warnings(record=True) as w: with pytest.warns(RuntimeWarning) as w:
cov_from_array = risk_models.sample_cov(returns_as_array) cov_from_array = risk_models.sample_cov(returns_as_array)
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[0].category, RuntimeWarning)
assert str(w[0].message) == "data is not in a dataframe" assert str(w[0].message) == "data is not in a dataframe"
np.testing.assert_array_almost_equal( np.testing.assert_array_almost_equal(
@@ -59,11 +62,10 @@ def test_sample_cov_npd():
assert not risk_models._is_positive_semidefinite(S) assert not risk_models._is_positive_semidefinite(S)
for method in {"spectral", "diag"}: for method in {"spectral", "diag"}:
with warnings.catch_warnings(record=True) as w: with pytest.warns(UserWarning) as w:
S2 = risk_models.fix_nonpositive_semidefinite(S, fix_method=method) S2 = risk_models.fix_nonpositive_semidefinite(S, fix_method=method)
assert risk_models._is_positive_semidefinite(S2) assert risk_models._is_positive_semidefinite(S2)
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[0].category, UserWarning)
assert ( assert (
str(w[0].message) str(w[0].message)
== "The covariance matrix is non positive semidefinite. Amending eigenvalues." == "The covariance matrix is non positive semidefinite. Amending eigenvalues."
@@ -148,10 +150,9 @@ def test_cov_to_corr():
test_corr = risk_models.cov_to_corr(rets.cov()) test_corr = risk_models.cov_to_corr(rets.cov())
pd.testing.assert_frame_equal(test_corr, rets.corr()) pd.testing.assert_frame_equal(test_corr, rets.corr())
with warnings.catch_warnings(record=True) as w: with pytest.warns(RuntimeWarning) as w:
test_corr_numpy = risk_models.cov_to_corr(rets.cov().values) test_corr_numpy = risk_models.cov_to_corr(rets.cov().values)
assert len(w) == 1 assert len(w) == 1
assert issubclass(w[0].category, RuntimeWarning)
assert str(w[0].message) == "cov_matrix is not a dataframe" assert str(w[0].message) == "cov_matrix is not a dataframe"
np.testing.assert_array_almost_equal(test_corr_numpy, rets.corr().values) np.testing.assert_array_almost_equal(test_corr_numpy, rets.corr().values)