Source code for qf_lib.portfolio_construction.portfolio_models.max_sharpe_ratio_portfolio

#     Copyright 2016-present CERN – European Organization for Nuclear Research
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from typing import Union, Sequence

import numpy as np
import scipy.optimize

from qf_lib.common.utils.logging.qf_parent_logger import qf_logger
from qf_lib.containers.dataframe.qf_dataframe import QFDataFrame
from qf_lib.containers.series.qf_series import QFSeries
from qf_lib.portfolio_construction.optimizers.quadratic_optimizer import QuadraticOptimizer
from qf_lib.portfolio_construction.portfolio_models.portfolio import Portfolio


[docs]class MaxSharpeRatioPortfolio(Portfolio): """ Class used for constructing a Max Sharpe Ratio portfolio. See: http://people.stat.sc.edu/sshen/events/backtesting/reference/maximizing%20the%20sharpe%20ratio.pdf """ def __init__(self, cov_matrix: QFDataFrame, mean_returns: QFSeries, upper_constraint: Union[float, Sequence[float]] = None, risk_free_rate: float = 0.0, max_iter: int = 10000): self.cov_matrix = cov_matrix self.mean_returns = mean_returns self.upper_constraint = upper_constraint self.risk_free_rate = risk_free_rate self.max_iter = max_iter self.logger = qf_logger.getChild(self.__class__.__name__)
[docs] def get_weights(self) -> QFSeries: cov_matrix = self.cov_matrix.values mean_returns = self.mean_returns.values def minimised_func(k: np.ndarray) -> float: k = k.squeeze() scaled_mean_returns = -k * mean_returns # type: np.ndarray weights = QuadraticOptimizer.get_optimal_weights(cov_matrix, scaled_mean_returns, self.upper_constraint) weights = weights.reshape((-1, 1)) # make it vertical portfolio_excess_ret = (mean_returns - self.risk_free_rate).dot(weights) portfolio_vol = np.sqrt((weights.T.dot(cov_matrix)).dot(weights)) # sqrt(w'*E*w) portfolio_excess_ret = portfolio_excess_ret.squeeze() portfolio_vol = portfolio_vol.squeeze() sr = portfolio_excess_ret / portfolio_vol return -sr k = self._find_scaling_factor_for_returns(minimised_func) scaled_mean_returns = -k * mean_returns weights = QuadraticOptimizer.get_optimal_weights(cov_matrix, scaled_mean_returns, self.upper_constraint) weights_series = QFSeries(data=weights, index=self.cov_matrix.columns) return weights_series
def _find_scaling_factor_for_returns(self, minimised_func): options = { 'maxiter': self.max_iter, 'disp': False } optimization_result = scipy.optimize.minimize( fun=minimised_func, x0=np.array([0]), method='L-BFGS-B', bounds=[(0, None)], options=options) if not optimization_result.success: self.logger.warning("Unsuccessful optimization: " + optimization_result.message) k = optimization_result.x k = k.squeeze() return k