migrated efficient frontier #77

This commit is contained in:
robertmartin8
2020-03-16 10:40:15 +00:00
parent 546acebbfb
commit eaae099efc
6 changed files with 376 additions and 216 deletions

View File

@@ -1,7 +1,7 @@
"""
The ``base_optimizer`` module houses the parent classes ``BaseOptimizer`` and
``BaseConvexOptimizer``, from which all optimisers will inherit. The later is for
optimisers that use the scipy solver.
The ``base_optimizer`` module houses the parent classes ``BaseOptimizer`` from which all
optimisers will inherit. ``BaseConvexOptimizer`` is thebase class for all ``cvxpy`` (and ``scipy``)
optimisation.
Additionally, we define a general utility function ``portfolio_performance`` to
evaluate return and risk for a given set of portfolio weights.
@@ -11,7 +11,9 @@ import json
import numpy as np
import pandas as pd
import cvxpy as cp
import scipy.optimize as sco
from . import objective_functions
from . import exceptions
class BaseOptimizer:
@@ -100,16 +102,24 @@ class BaseOptimizer:
class BaseConvexOptimizer(BaseOptimizer):
"""
The BaseConvexOptimizer contains many private variables for use by
``cvxpy``. For example, the immutable optimisation variable for weights
is stored as self._w. Interacting directly with these variables is highly
discouraged.
Instance variables:
- ``n_assets`` - int
- ``tickers`` - str list
- ``weights`` - np.ndarray
- ``bounds`` - float tuple OR (float tuple) list
- ``constraints`` - dict list
Public methods:
- ``add_objective()`` adds a (convex) objective to the optimisation problem
- ``add_constraint()`` adds a (linear) constraint to the optimisation problem
- ``convex_objective()`` solves for a generic convex objective with linear constraints
- ``nonconvex_objective()`` solves for a generic nonconvex objective using the scipy backend.
This is prone to getting stuck in local minima and is generally *not* recommended.
- ``set_weights()`` creates self.weights (np.ndarray) from a weights dict
- ``clean_weights()`` rounds the weights and clips near-zeros.
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
@@ -174,12 +184,164 @@ class BaseConvexOptimizer(BaseOptimizer):
self._constraints.append(self._w >= self._lower_bounds)
self._constraints.append(self._w <= self._upper_bounds)
@staticmethod
def _make_scipy_bounds():
def _solve_cvxpy_opt_problem(self):
"""
Convert the current cvxpy bounds to scipy bounds
Helper method to solve the cvxpy problem and check output,
once objectives and constraints have been defined
:raises exceptions.OptimizationError: if problem is not solvable by cvxpy
"""
raise NotImplementedError
try:
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
opt.solve()
except (TypeError, cp.DCPError):
raise exceptions.OptimizationError
if opt.status != "optimal":
raise exceptions.OptimizationError
self.weights = self._w.value.round(16) + 0.0 # +0.0 removes signed zero
def add_objective(self, new_objective, **kwargs):
"""
Add a new term into the objective function. This term must be convex,
and built from cvxpy atomic functions.
Example:
def L1_norm(w, k=1):
return k * cp.norm(w, 1)
ef.add_objective(L1_norm, k=2)
:param new_objective: the objective to be added
:type new_objective: cp.Expression (i.e function of cp.Variable)
"""
self._additional_objectives.append(new_objective(self._w, **kwargs))
def add_constraint(self, new_constraint):
"""
Add a new constraint to the optimisation problem. This constraint must be linear and
must be either an equality or simple inequality.
Examples:
ef.add_constraint(lambda x : x[0] == 0.02)
ef.add_constraint(lambda x : x >= 0.01)
ef.add_constraint(lambda x: x <= np.array([0.01, 0.08, ..., 0.5]))
:param new_constraint: the constraint to be added
:type constraintfunc: lambda function
"""
if not callable(new_constraint):
raise TypeError("New constraint must be provided as a lambda function")
# Save raw constraint (needed for e.g max_sharpe)
self._additional_constraints_raw.append(new_constraint)
# Add constraint
self._constraints.append(new_constraint(self._w))
def convex_objective(self, custom_objective, weights_sum_to_one=True, **kwargs):
"""
Optimise a custom convex objective function. Constraints should be added with
``ef.add_constraint()``. Optimiser arguments *must* be passed as keyword-args. Example:
# Could define as a lambda function instead
def logarithmic_barrier(w, cov_matrix, k=0.1):
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
return cp.quad_form(w, cov_matrix) - k * cp.sum(cp.log(w))
w = ef.convex_objective(logarithmic_barrier, cov_matrix=ef.cov_matrix)
:param custom_objective: an objective function to be MINIMISED. This should be written using
cvxpy atoms Should map (w, **kwargs) -> float.
:type custom_objective: function with signature (cp.Variable, **kwargs) -> cp.Expression
:param weights_sum_to_one: whether to add the default objective, defaults to True
:type weights_sum_to_one: bool, optional
:raises OptimizationError: if the objective is nonconvex or constraints nonlinear.
:return: asset weights for the efficient risk portfolio
:rtype: dict
"""
# custom_objective must have the right signature (w, **kwargs)
self._objective = custom_objective(self._w, **kwargs)
for obj in self._additional_objectives:
self._objective += obj
if weights_sum_to_one:
self._constraints.append(cp.sum(self._w) == 1)
self._solve_cvxpy_opt_problem()
return dict(zip(self.tickers, self.weights))
def nonconvex_objective(
self,
custom_objective,
objective_args=None,
weights_sum_to_one=True,
constraints=None,
solver="SLSQP",
):
"""
Optimise some objective function using the scipy backend. This can
support nonconvex objectives and nonlinear constraints, but often gets stuck
at local minima. This method is not recommended caveat emptor. Example:
# Market-neutral efficient risk
constraints = [
{"type": "eq", "fun": lambda w: np.sum(w)}, # weights sum to zero
{
"type": "eq",
"fun": lambda w: target_risk ** 2 - np.dot(w.T, np.dot(ef.cov_matrix, w)),
}, # risk = target_risk
]
ef.nonconvex_objective(
lambda w, mu: -w.T.dot(mu), # min negative return (i.e maximise return)
objective_args=(ef.expected_returns,),
weights_sum_to_one=False,
constraints=constraints,
)
:param objective_function: an objective function to be MINIMISED. This function
should map (weight, args) -> cost
:type objective_function: function with signature (np.ndarray, args) -> float
:param objective_args: arguments for the objective function (excluding weight)
:type objective_args: tuple of np.ndarrays
:param weights_sum_to_one: whether to add the default objective, defaults to True
:type weights_sum_to_one: bool, optional
:param constraints: list of constraints in the scipy format (i.e dicts)
:type constraints: dict list
:param solver: which SCIPY solver to use, e.g "SLSQP", "COBYLA", "BFGS".
User beware: different optimisers require different inputs.
:type solver: string
:return: asset weights that optimise the custom objective
:rtype: dict
"""
# Sanitise inputs
if not isinstance(objective_args, tuple):
objective_args = (objective_args,)
# Make scipy bounds
bound_array = np.vstack((self._lower_bounds, self._upper_bounds)).T
bounds = list(map(tuple, bound_array))
initial_guess = np.array([1 / self.n_assets] * self.n_assets)
# Construct constraints
final_constraints = []
if weights_sum_to_one:
final_constraints.append({"type": "eq", "fun": lambda x: np.sum(x) - 1})
if constraints is not None:
final_constraints += constraints
result = sco.minimize(
custom_objective,
x0=initial_guess,
args=objective_args,
method=solver,
bounds=bounds,
constraints=final_constraints,
)
self.weights = result["x"]
return dict(zip(self.tickers, self.weights))
def portfolio_performance(

View File

@@ -148,7 +148,8 @@ class DiscreteAllocation:
# Construct long-only discrete allocations for each
short_val = self.total_portfolio_value * self.short_ratio
print("\nAllocating long sub-portfolio:")
if verbose:
print("\nAllocating long sub-portfolio...")
da1 = DiscreteAllocation(
longs,
self.latest_prices[longs.keys()],
@@ -156,7 +157,8 @@ class DiscreteAllocation:
)
long_alloc, long_leftover = da1.greedy_portfolio()
print("\nAllocating short sub-portfolio:")
if verbose:
print("\nAllocating short sub-portfolio...")
da2 = DiscreteAllocation(
shorts,
self.latest_prices[shorts.keys()],
@@ -263,7 +265,8 @@ class DiscreteAllocation:
# Construct long-only discrete allocations for each
short_val = self.total_portfolio_value * self.short_ratio
print("\nAllocating long sub-portfolio:")
if verbose:
print("\nAllocating long sub-portfolio:")
da1 = DiscreteAllocation(
longs,
self.latest_prices[longs.keys()],
@@ -271,7 +274,8 @@ class DiscreteAllocation:
)
long_alloc, long_leftover = da1.lp_portfolio()
print("\nAllocating short sub-portfolio:")
if verbose:
print("\nAllocating short sub-portfolio:")
da2 = DiscreteAllocation(
shorts,
self.latest_prices[shorts.keys()],

View File

@@ -7,8 +7,7 @@ import warnings
import numpy as np
import pandas as pd
import cvxpy as cp
import scipy.optimize as sco
from . import exceptions
from . import objective_functions, base_optimizer
@@ -30,11 +29,6 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
- ``cov_matrix`` - np.ndarray
- ``expected_returns`` - np.ndarray
- Optimisation parameters:
- ``initial_guess`` - np.ndarray
- ``constraints`` - dict list
- ``opt_method`` - the optimisation algorithm to use. Defaults to SLSQP.
- Output: ``weights`` - np.ndarray
@@ -42,7 +36,8 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
- ``max_sharpe()`` optimises for maximal Sharpe ratio (a.k.a the tangency portfolio)
- ``min_volatility()`` optimises for minimum volatility
- ``custom_objective()`` optimises for some custom objective function
- ``max_quadratic_utility()`` maximises the quadratic utility, giiven some risk aversion.
- ``efficient_risk()`` maximises Sharpe for a given target risk
- ``efficient_return()`` minimises risk for a given target return
- ``portfolio_performance()`` calculates the expected return, volatility and Sharpe ratio for
@@ -128,97 +123,6 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
del self._constraints[0]
del self._constraints[0]
def _solve_cvxpy_opt_problem(self):
"""
Helper method to solve the cvxpy problem and check output,
once objectives and constraints have been defined
:raises exceptions.OptimizationError: if problem is not solvable by cvxpy
"""
try:
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
opt.solve()
except (TypeError, cp.DCPError):
raise exceptions.OptimizationError
if opt.status != "optimal":
raise exceptions.OptimizationError
self.weights = self._w.value.round(16) + 0.0 # +0.0 removes signed zero
def add_objective(self, new_objective, **kwargs):
"""
Add a new term into the objective function. This term must be convex,
and built from cvxpy atomic functions.
Example:
def L1_norm(w, k=1):
return k * cp.norm(w, 1)
ef.add_objective(L1_norm, k=2)
:param new_objective: the objective to be added
:type new_objective: cp.Expression (i.e function of cp.Variable)
"""
self._additional_objectives.append(new_objective(self._w, **kwargs))
def add_constraint(self, new_constraint):
"""
Add a new constraint to the optimisation problem. This constraint must be linear and
must be either an equality or simple inequality.
Examples:
ef.add_constraint(lambda x : x[0] == 0.02)
ef.add_constraint(lambda x : x >= 0.01)
ef.add_constraint(lambda x: x <= np.array([0.01, 0.08, ..., 0.5]))
:param new_constraint: the constraint to be added
:type constraintfunc: lambda function
"""
if not callable(new_constraint):
raise TypeError("New constraint must be provided as a lambda function")
# Save raw constraint (needed for e.g max_sharpe)
self._additional_constraints_raw.append(new_constraint)
# Add constraint
self._constraints.append(new_constraint(self._w))
def convex_optimize(self, custom_objective, constraints):
# TODO: fix
# genera convex optimistion
pass
def nonconvex_optimize(self, custom_objective=None, constraints=None):
#  TODO: fix
# opt using scipy
args = (self.cov_matrix,)
initial_guess = np.array([1 / self.n_assets] * self.n_assets)
result = sco.minimize(
objective_functions.volatility,
x0=initial_guess,
args=args,
method="SLSQP",
bounds=[(0, 1)] * 20,
constraints=[{"type": "eq", "fun": lambda x: np.sum(x) - 1}],
)
self.weights = result["x"]
#  max sharpe
# args = (self.expected_returns, self.cov_matrix, self.gamma, risk_free_rate)
# result = sco.minimize(
# objective_functions.negative_sharpe,
# x0=self.initial_guess,
# args=args,
# method=self.opt_method,
# bounds=self.bounds,
# constraints=self.constraints,
# )
# self.weights = result["x"]
return dict(zip(self.tickers, self.weights))
def min_volatility(self):
"""
Minimise volatility.
@@ -325,29 +229,6 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
self._solve_cvxpy_opt_problem()
return dict(zip(self.tickers, self.weights))
# TODO: roll custom_objective into nonconvex_optimizer
def custom_objective(self, objective_function, *args):
"""
Optimise some objective function. While an implicit requirement is that the function
can be optimised via a quadratic optimiser, this is not enforced. Thus there is a
decent chance of silent failure.
:param objective_function: function which maps (weight, args) -> cost
:type objective_function: function with signature (np.ndarray, args) -> float
:return: asset weights that optimise the custom objective
:rtype: dict
"""
result = sco.minimize(
objective_function,
x0=self.initial_guess,
args=args,
method=self.opt_method,
bounds=self.bounds,
constraints=self.constraints,
)
self.weights = result["x"]
return dict(zip(self.tickers, self.weights))
def efficient_risk(self, target_volatility, market_neutral=False):
"""
Maximise return for a target risk.

View File

@@ -9,14 +9,14 @@ from tests.utilities_for_tests import get_data, setup_efficient_frontier
def test_custom_bounds():
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(0.02, 0.10)
*setup_efficient_frontier(data_only=True), weight_bounds=(0.02, 0.13)
)
ef.min_volatility()
np.testing.assert_allclose(ef._lower_bounds, np.array([0.02] * ef.n_assets))
np.testing.assert_allclose(ef._lower_bounds, np.array([0.10] * ef.n_assets))
np.testing.assert_allclose(ef._upper_bounds, np.array([0.13] * ef.n_assets))
assert ef.weights.min() >= 0.02
assert ef.weights.max() <= 0.10
assert ef.weights.max() <= 0.13
np.testing.assert_almost_equal(ef.weights.sum(), 1)
@@ -62,6 +62,7 @@ def test_none_bounds():
w2 = ef.weights
np.testing.assert_array_almost_equal(w1, w2)
def test_bound_input_types():
bounds = [0.01, 0.13]
ef = EfficientFrontier(

View File

@@ -1,45 +1,116 @@
import numpy as np
from tests.utilities_for_tests import setup_efficient_frontier
import cvxpy as cp
import pytest
from pypfopt import EfficientFrontier
from pypfopt import objective_functions
from pypfopt import exceptions
from tests.utilities_for_tests import setup_efficient_frontier
def test_custom_objective_equal_weights():
def test_custom_convex_equal_weights():
ef = setup_efficient_frontier()
def new_objective(weights):
return (weights ** 2).sum()
def new_objective(w):
return cp.sum(w ** 2)
ef.custom_objective(new_objective)
ef.convex_objective(new_objective)
np.testing.assert_allclose(ef.weights, np.array([1 / 20] * 20))
def test_custom_objective_min_var():
def test_custom_convex_min_var():
ef = setup_efficient_frontier()
ef.min_volatility()
built_in = ef.weights
# With custom objective
ef = setup_efficient_frontier()
ef.custom_objective(objective_functions.volatility, ef.cov_matrix, 0)
ef.convex_objective(
objective_functions.portfolio_variance, cov_matrix=ef.cov_matrix
)
custom = ef.weights
np.testing.assert_allclose(built_in, custom, atol=1e-7)
def test_custom_objective_sharpe_L2():
ef = setup_efficient_frontier()
ef.gamma = 2
ef.max_sharpe()
def test_custom_convex_objective_market_neutral_efficient_risk():
target_risk = 0.19
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)
)
ef.efficient_risk(target_risk, market_neutral=True)
built_in = ef.weights
# Recreate the market-neutral efficient_risk optimiser using this API
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)
)
ef.add_constraint(lambda x: cp.sum(x) == 0)
ef.add_constraint(lambda x: cp.quad_form(x, ef.cov_matrix) <= target_risk ** 2)
ef.convex_objective(lambda x: -x @ ef.expected_returns, weights_sum_to_one=False)
custom = ef.weights
np.testing.assert_allclose(built_in, custom, atol=1e-7)
def test_convex_sharpe_raises_error():
# With custom objective
with pytest.raises(exceptions.OptimizationError):
ef = setup_efficient_frontier()
ef.convex_objective(
objective_functions.sharpe_ratio,
expected_returns=ef.expected_returns,
cov_matrix=ef.cov_matrix,
)
def test_custom_convex_logarithmic_barrier():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
ef = setup_efficient_frontier()
def logarithmic_barrier(w, cov_matrix, k=0.1):
log_sum = cp.sum(cp.log(w))
var = cp.quad_form(w, cov_matrix)
return var - k * log_sum
w = ef.convex_objective(logarithmic_barrier, cov_matrix=ef.cov_matrix)
assert isinstance(w, dict)
assert set(w.keys()) == set(ef.tickers)
np.testing.assert_almost_equal(ef.weights.sum(), 1)
np.testing.assert_allclose(
ef.portfolio_performance(),
(0.23978400459553223, 0.21100848889958182, 1.041588448605623),
)
def test_custom_convex_deviation_risk_parity_error():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
ef = setup_efficient_frontier()
def deviation_risk_parity(w, cov_matrix):
n = cov_matrix.shape[0]
rp = (w * (cov_matrix @ w)) / cp.quad_form(w, cov_matrix)
return cp.sum_squares(rp - 1 / n)
with pytest.raises(exceptions.OptimizationError):
ef.convex_objective(deviation_risk_parity, cov_matrix=ef.cov_matrix)
def test_custom_nonconvex_min_var():
ef = setup_efficient_frontier()
ef.min_volatility()
original_vol = ef.portfolio_performance()[1]
# With custom objective
ef = setup_efficient_frontier()
ef.custom_objective(objective_functions.negative_sharpe,
ef.expected_returns, ef.cov_matrix, 2)
custom = ef.weights
np.testing.assert_allclose(built_in, custom, atol=1e-7)
ef.nonconvex_objective(
objective_functions.portfolio_variance, objective_args=ef.cov_matrix
)
custom_vol = ef.portfolio_performance()[1]
# Scipy should be close but not as good for this simple objective
np.testing.assert_almost_equal(custom_vol, original_vol, decimal=5)
assert original_vol < custom_vol
def test_custom_logarithmic_barrier():
def test_custom_nonconvex_logarithmic_barrier():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
ef = setup_efficient_frontier()
@@ -48,43 +119,85 @@ def test_custom_logarithmic_barrier():
portfolio_volatility = np.dot(weights.T, np.dot(cov_matrix, weights))
return portfolio_volatility - k * log_sum
w = ef.custom_objective(logarithmic_barrier, ef.cov_matrix, 0.1)
w = ef.nonconvex_objective(logarithmic_barrier, objective_args=(ef.cov_matrix, 0.2))
assert isinstance(w, dict)
assert set(w.keys()) == set(ef.tickers)
assert set(w.keys()) == set(ef.expected_returns.index)
np.testing.assert_almost_equal(ef.weights.sum(), 1)
def test_custom_deviation_risk_parity():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
def test_custom_nonconvex_deviation_risk_parity_1():
# 60 Years of Portfolio Optimisation, Kolm et al (2014) - first definition
ef = setup_efficient_frontier()
def deviation_risk_parity(w, cov_matrix):
diff = w * np.dot(cov_matrix, w) - \
(w * np.dot(cov_matrix, w)).reshape(-1, 1)
diff = w * np.dot(cov_matrix, w) - (w * np.dot(cov_matrix, w)).reshape(-1, 1)
return (diff ** 2).sum().sum()
w = ef.custom_objective(deviation_risk_parity, ef.cov_matrix)
w = ef.nonconvex_objective(deviation_risk_parity, ef.cov_matrix)
assert isinstance(w, dict)
assert set(w.keys()) == set(ef.tickers)
assert set(w.keys()) == set(ef.expected_returns.index)
np.testing.assert_almost_equal(ef.weights.sum(), 1)
def test_custom_utility_objective():
def test_custom_nonconvex_deviation_risk_parity_2():
# 60 Years of Portfolio Optimisation, Kolm et al (2014) - second definition
ef = setup_efficient_frontier()
def deviation_risk_parity(w, cov_matrix):
n = cov_matrix.shape[0]
rp = (w * (cov_matrix @ w)) / cp.quad_form(w, cov_matrix)
return cp.sum_squares(rp - 1 / n).value
w = ef.nonconvex_objective(deviation_risk_parity, ef.cov_matrix)
assert isinstance(w, dict)
assert set(w.keys()) == set(ef.tickers)
np.testing.assert_almost_equal(ef.weights.sum(), 1)
def test_custom_nonconvex_utility_objective():
ef = setup_efficient_frontier()
def utility_obj(weights, mu, cov_matrix, k=1):
return -weights.dot(mu) + k * np.dot(weights.T, np.dot(cov_matrix, weights))
w = ef.custom_objective(utility_obj, ef.expected_returns, ef.cov_matrix, 1)
w = ef.nonconvex_objective(
utility_obj, objective_args=(ef.expected_returns, ef.cov_matrix, 1)
)
assert isinstance(w, dict)
assert set(w.keys()) == set(ef.tickers)
assert set(w.keys()) == set(ef.expected_returns.index)
np.testing.assert_almost_equal(ef.weights.sum(), 1)
vol1 = ef.portfolio_performance()[1]
# If we increase k, volatility should decrease
ef.custom_objective(utility_obj, ef.expected_returns, ef.cov_matrix, 2)
w = ef.nonconvex_objective(
utility_obj, objective_args=(ef.expected_returns, ef.cov_matrix, 3)
)
vol2 = ef.portfolio_performance()[1]
assert vol2 < vol1
def test_custom_nonconvex_objective_market_neutral_efficient_risk():
# Recreate the market-neutral efficient_risk optimiser using this API
target_risk = 0.19
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)
)
weight_constr = {"type": "eq", "fun": lambda w: np.sum(w)}
risk_constr = {
"type": "eq",
"fun": lambda w: target_risk ** 2 - np.dot(w.T, np.dot(ef.cov_matrix, w)),
}
constraints = [weight_constr, risk_constr]
ef.nonconvex_objective(
lambda w, mu: -w.T.dot(mu),
objective_args=(ef.expected_returns),
weights_sum_to_one=False,
constraints=constraints,
)
np.testing.assert_allclose(
ef.portfolio_performance(),
(0.2309497754562942, target_risk, 1.1102600451243954),
atol=1e-6,
)

View File

@@ -40,7 +40,7 @@ def test_greedy_portfolio_allocation():
da = DiscreteAllocation(w, latest_prices)
allocation, leftover = da.greedy_portfolio()
assert allocation == {
assert {
"MA": 14,
"FB": 12,
"PFE": 51,
@@ -49,7 +49,6 @@ def test_greedy_portfolio_allocation():
"BBY": 9,
"SBUX": 6,
"GOOG": 1,
"AMD": 1,
}
total = 0
@@ -68,7 +67,7 @@ def test_greedy_allocation_rmse_error():
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices)
da.greedy_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.0257368)
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.025762032436733803)
def test_greedy_portfolio_allocation_short():
@@ -124,7 +123,7 @@ def test_greedy_allocation_rmse_error_short():
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices)
da.greedy_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.03306318)
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.033070015016740284)
def test_greedy_portfolio_allocation_short_different_params():
@@ -154,13 +153,13 @@ def test_greedy_portfolio_allocation_short_different_params():
"XOM": 11,
"BAC": -271,
"GM": -133,
"GE": -355,
"SHLD": -923,
"AMD": -284,
"JPM": -6,
"T": -13,
"UAA": -7,
"RRC": -2,
"GE": -356,
"SHLD": -922,
"AMD": -285,
"JPM": -5,
"T": -14,
"UAA": -8,
"RRC": -3,
}
long_total = 0
short_total = 0
@@ -184,14 +183,14 @@ def test_lp_portfolio_allocation():
allocation, leftover = da.lp_portfolio()
assert da.allocation == {
"AAPL": 5.0,
"FB": 11.0,
"BABA": 5.0,
"AMZN": 1.0,
"BBY": 7.0,
"MA": 14.0,
"PFE": 50.0,
"SBUX": 5.0,
"AAPL": 5,
"FB": 11,
"BABA": 5,
"AMZN": 1,
"BBY": 7,
"MA": 14,
"PFE": 50,
"SBUX": 5,
}
total = 0
for ticker, num in allocation.items():
@@ -209,7 +208,7 @@ def test_lp_allocation_rmse_error():
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices)
da.lp_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(verbose=False), 0.0170634)
np.testing.assert_almost_equal(da._allocation_rmse_error(verbose=False), 0.017070218149194846)
def test_lp_portfolio_allocation_short():
@@ -224,24 +223,24 @@ def test_lp_portfolio_allocation_short():
allocation, leftover = da.lp_portfolio()
assert da.allocation == {
"GOOG": 1.0,
"AAPL": 5.0,
"FB": 8.0,
"BABA": 5.0,
"WMT": 2.0,
"XOM": 2.0,
"BBY": 9.0,
"MA": 16.0,
"PFE": 46.0,
"SBUX": 9.0,
"GE": -43.0,
"AMD": -34.0,
"BAC": -32.0,
"GM": -16.0,
"T": -1.0,
"UAA": -1.0,
"SHLD": -110.0,
"JPM": -1.0,
"GOOG": 1,
"AAPL": 5,
"FB": 8,
"BABA": 5,
"WMT": 2,
"XOM": 2,
"BBY": 9,
"MA": 16,
"PFE": 46,
"SBUX": 9,
"GE": -43,
"AMD": -34,
"BAC": -32,
"GM": -16,
"T": -1,
"UAA": -1,
"SHLD": -110,
"JPM": -1,
}
long_total = 0
short_total = 0
@@ -265,7 +264,7 @@ def test_lp_allocation_rmse_error_short():
latest_prices = get_latest_prices(df)
da = DiscreteAllocation(w, latest_prices)
da.lp_portfolio()
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.02699558)
np.testing.assert_almost_equal(da._allocation_rmse_error(), 0.027018566693989568)
def test_lp_portfolio_allocation_different_params():
@@ -282,17 +281,17 @@ def test_lp_portfolio_allocation_different_params():
allocation, leftover = da.lp_portfolio()
assert da.allocation == {
"GOOG": 1.0,
"AAPL": 43.0,
"FB": 95.0,
"BABA": 44.0,
"AMZN": 4.0,
"AMD": 1.0,
"SHLD": 3.0,
"BBY": 69.0,
"MA": 114.0,
"PFE": 412.0,
"SBUX": 51.0,
"GOOG": 1,
"AAPL": 43,
"FB": 95,
"BABA": 44,
"AMZN": 4,
"AMD": 1,
"SHLD": 3,
"BBY": 69,
"MA": 114,
"PFE": 412,
"SBUX": 51,
}
total = 0
for ticker, num in allocation.items():