markowitz
Markowitz Portfolio Theory (MPT), introduced by Harry Markowitz in his 1952 paper Portfolio Selection, established a formal mathematical approach to portfolio construction by shifting the focus from individual securities to the risk-return characteristics of the portfolio as a whole. Its core insight is that diversification reduces risk when asset returns are not perfectly correlated, allowing investors to achieve either higher expected returns for a given level of risk or lower risk for a given expected return, a contribution later recognized with the 1990 Nobel Prize in Economics.
MaxMean
class MaxMean(reg=None, strength=1)
Maximum Mean return optimization.
Maximum mean represents the limiting case of Markowitz's framework where the investor is risk-neutral and cares only about expected return. This portfolio allocates capital entirely to the asset(s) with the highest expected return, ignoring risk considerations. Without short-sale constraints, this strategy places 100% weight in the single asset with maximum expected return. While theoretically simple, this approach is rarely used in practice as it completely ignores diversification benefits and risk management.
Args
reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Methods
clean_weights
def clean_weights(threshold=1e-08)
Cleans the portfolio weights by setting very small positions to zero.
Any weight whose absolute value is below the specified threshold is replaced with zero.
This helps remove negligible allocations while keeping the array structure intact. This method
requires portfolio optimization (optimize() method) to take place for self.weights to be
defined other than None.
Warning:
This method modifies the existing portfolio weights in place. After cleaning, re-optimization is required to recover the original weights.
Args
threshold(float, optional): Float specifying the minimum absolute weight to retain. Defaults to1e-8.
Returns:
numpy.ndarray: Cleaned and re-normalized portfolio weight vector.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- Weights are cleaned using absolute values, making this method compatible with long-short portfolios.
- Re-normalization ensures the portfolio remains properly scaled after cleaning.
- Increasing threshold promotes sparsity but may materially alter the portfolio composition.
optimize
def optimize(
data=None,
weight_bounds=(0, 1),
w=None,
custom_mean=None
)
Solves the Maximum Mean objective:
Args
data(pd.DataFrame): Ticker price data in either multi-index or single-index formats. Examples are given below:# Single-Index Example Ticker TSLA NVDA GME PFE AAPL ... Date 2015-01-02 14.620667 0.483011 6.288958 18.688917 24.237551 ... 2015-01-05 14.006000 0.474853 6.460137 18.587513 23.554741 ... 2015-01-06 14.085333 0.460456 6.268492 18.742599 23.556952 ... 2015-01-07 14.063333 0.459257 6.195926 18.999102 23.887287 ... 2015-01-08 14.041333 0.476533 6.268492 19.386841 24.805082 ... ... # Multi-Index Example Structure (OHLCV) Columns: + Ticker (e.g. GME, PFE, AAPL, ...) - Open - High - Low - Close - Volumeweight_bounds(tuple, optional): Boundary constraints for asset weights. Values must be of the format(lesser, greater)with0 <= |lesser|, |greater| <= 1. Defaults to(0,1).w(None or np.ndarray, optional): Initial weight vector for warm starts. Mainly used for backtesting and not recommended for user input. Defaults toNone.custom_mean(None or np.ndarray, optional): Custom mean vector. Can be used to inject externally generated mean vectors (eg. Black-Litterman). Defaults toNone.
Returns:
np.ndarray: Vector of optimized portfolio weights.
Raises
DataError: For any data mismatch during integrity check.PortfolioError: For any invalid portfolio variable inputs during integrity check.OptimizationError: IfSLSQPsolver fails to solve.
Example:
# Importing the maximum mean module
from opes.objectives import MaxMean
# Let this be your ticker data
training_data = some_data()
# Let this be your custom mean vector
# Can be Black-Litterman, Bayesian, Fama-French etc.
mean_v = customMean()
# Initialize with custom regularization
maxmean = MaxMean(reg='entropy', strength=0.02)
# Optimize portfolio with custom weight bounds and custom mean vector
weights = maxmean.optimize(data=training_data, weight_bounds=(0.05, 0.75), custom_mean=mean_v)
set_regularizer
def set_regularizer(reg=None, strength=1)
Updates the regularization function and its penalty strength.
This method updates both the regularization function and its associated penalty strength. Useful for changing the behaviour of the optimizer without initiating a new one.
Args
reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Example:
# Import the MaxMean class
from opes.objectives import MaxMean
# Set with 'entropy' regularization
optimizer = MaxMean(reg='entropy', strength=0.01)
# --- Do Something with `optimizer` ---
optimizer.optimize(data=some_data())
# Change regularizer using `set_regularizer`
optimizer.set_regularizer(reg='l1', strength=0.02)
# --- Do something else with new `optimizer` ---
optimizer.optimize(data=some_data())
stats
def stats()
Calculates and returns portfolio concentration and diversification statistics.
These statistics help users to inspect portfolio's overall concentration in
allocation. For the method to work, the optimizer must have been initialized, i.e.
the optimize() method should have been called at least once for self.weights
to be defined other than None.
Returns:
- A
dictcontaining the following keys:'tickers'(list): A list of tickers used for optimization.'weights'(np.ndarry): Portfolio weights, output from optimization.'portfolio_entropy'(float): Shannon entropy computed on portfolio weights.'herfindahl_index'(float): Herfindahl Index value, computed on portfolio weights.'gini_coefficient'(float): Gini Coefficient value, computed on portfolio weights.'absolute_max_weight'(float): Absolute maximum allocation for an asset.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- All statistics are computed on absolute normalized weights (within the simplex), ensuring compatibility with long-short portfolios.
- This method is diagnostic only and does not modify portfolio weights.
- For meaningful interpretation, use these metrics in conjunction with risk and performance measures.
MaxSharpe
class MaxSharpe(risk_free=0.01, reg=None, strength=1)
Maximum Sharpe Optimization.
The maximum Sharpe ratio portfolio, formalized by William F. Sharpe, follows directly from Markowitz's framework. Also known as the tangency portfolio, this portfolio provides the highest risk-adjusted return as measured by excess return per unit of risk. When a risk-free asset is available, this portfolio represents the optimal risky portfolio for all mean-variance investors regardless of their risk aversion.
Args
risk_free(float, optional): Risk-free rate. A non-zero value induces the tangency portfolio. Defaults to0.01.reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Methods
clean_weights
def clean_weights(threshold=1e-08)
Cleans the portfolio weights by setting very small positions to zero.
Any weight whose absolute value is below the specified threshold is replaced with zero.
This helps remove negligible allocations while keeping the array structure intact. This method
requires portfolio optimization (optimize() method) to take place for self.weights to be
defined other than None.
Warning:
This method modifies the existing portfolio weights in place. After cleaning, re-optimization is required to recover the original weights.
Args
threshold(float, optional): Float specifying the minimum absolute weight to retain. Defaults to1e-8.
Returns:
numpy.ndarray: Cleaned and re-normalized portfolio weight vector.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- Weights are cleaned using absolute values, making this method compatible with long-short portfolios.
- Re-normalization ensures the portfolio remains properly scaled after cleaning.
- Increasing threshold promotes sparsity but may materially alter the portfolio composition.
optimize
def optimize(
data=None,
custom_cov=None,
custom_mean=None,
seed=100,
**kwargs
)
Solves the Maximum Sharpe objective:
Warning
Since the maximum Sharpe objective is generally non-convex, SciPy's differential_evolution optimizer is used to obtain more robust solutions.
This approach incurs significantly higher computational cost and should be used with care.
Note
Asset weight bounds are defaulted to (0,1).
Args
data(pd.DataFrame): Ticker price data in either multi-index or single-index formats. Examples are given below:# Single-Index Example Ticker TSLA NVDA GME PFE AAPL ... Date 2015-01-02 14.620667 0.483011 6.288958 18.688917 24.237551 ... 2015-01-05 14.006000 0.474853 6.460137 18.587513 23.554741 ... 2015-01-06 14.085333 0.460456 6.268492 18.742599 23.556952 ... 2015-01-07 14.063333 0.459257 6.195926 18.999102 23.887287 ... 2015-01-08 14.041333 0.476533 6.268492 19.386841 24.805082 ... ... # Multi-Index Example Structure (OHLCV) Columns: + Ticker (e.g. GME, PFE, AAPL, ...) - Open - High - Low - Close - Volumecustom_mean(None or np.ndarray, optional): Custom mean vector. Can be used to inject externally generated mean vectors (eg. Black-Litterman). Defaults toNone.custom_cov(None or array-like of shape (n_assets, n_assets), optional): Custom covariance matrix. Can be used to inject externally generated covariance matrices (eg. Ledoit-Wolf). Defaults toNone.seed(int or None, optional): Seed for differential evolution solver. Defaults to100to preserve deterministic outputs.- kwargs (optional): Included for interface consistency, allowing the backtesting engine to pass additional or optimizer-specific arguments that may be safely ignored by this optimizer.
Returns:
np.ndarray: Vector of optimized portfolio weights.
Raises
DataError: For any data mismatch during integrity check.PortfolioError: For any invalid portfolio variable inputs during integrity check.OptimizationError: Ifdifferential_evolutionsolver fails to solve.
Example:
# Importing the maximum sharpe module
from opes.objectives import MaxSharpe
# Let this be your ticker data
training_data = some_data()
# Let these be your custom mean and covariance
mean_v = customMean()
cov_m = customCov()
# Initialize with risk free rate and custom regularization
maxsharpe = MaxSharpe(risk_free=0.02, reg='entropy', strength=0.01)
# Optimize portfolio with custom mean vector and covariance matrix
# We also set the seed to 46
weights = maxsharpe.optimize(data=training_data, custom_mean=mean_v, custom_cov=cov_m, seed=46)
set_regularizer
def set_regularizer(reg=None, strength=1)
Updates the regularization function and its penalty strength.
This method updates both the regularization function and its associated penalty strength. Useful for changing the behaviour of the optimizer without initiating a new one.
Args
reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Example:
# Import the MaxSharpe class
from opes.objectives import MaxSharpe
# Set with 'entropy' regularization
optimizer = MaxSharpe(reg='entropy', strength=0.01)
# --- Do Something with `optimizer` ---
optimizer.optimize(data=some_data())
# Change regularizer using `set_regularizer`
optimizer.set_regularizer(reg='l1', strength=0.02)
# --- Do something else with new `optimizer` ---
optimizer.optimize(data=some_data())
stats
def stats()
Calculates and returns portfolio concentration and diversification statistics.
These statistics help users to inspect portfolio's overall concentration in
allocation. For the method to work, the optimizer must have been initialized, i.e.
the optimize() method should have been called at least once for self.weights
to be defined other than None.
Returns:
- A
dictcontaining the following keys:'tickers'(list): A list of tickers used for optimization.'weights'(np.ndarry): Portfolio weights, output from optimization.'portfolio_entropy'(float): Shannon entropy computed on portfolio weights.'herfindahl_index'(float): Herfindahl Index value, computed on portfolio weights.'gini_coefficient'(float): Gini Coefficient value, computed on portfolio weights.'absolute_max_weight'(float): Absolute maximum allocation for an asset.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- All statistics are computed on absolute normalized weights (within the simplex), ensuring compatibility with long-short portfolios.
- This method is diagnostic only and does not modify portfolio weights.
- For meaningful interpretation, use these metrics in conjunction with risk and performance measures.
MeanVariance
class MeanVariance(risk_aversion=0.5, reg=None, strength=1)
Mean-Variance Optimization.
Mean-variance optimization trades off expected return and risk via a risk-aversion parameter, where higher values produce more conservative portfolios closer to the global minimum variance solution and lower values lead to more aggressive, return-seeking allocations.
Args
risk_aversion(float, optional): Risk-aversion coefficient. Higher values emphasize risk minimization, while lower values favor return seeking. Usually greater than0. Defaults to0.5.reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Methods
clean_weights
def clean_weights(threshold=1e-08)
Cleans the portfolio weights by setting very small positions to zero.
Any weight whose absolute value is below the specified threshold is replaced with zero.
This helps remove negligible allocations while keeping the array structure intact. This method
requires portfolio optimization (optimize() method) to take place for self.weights to be
defined other than None.
Warning:
This method modifies the existing portfolio weights in place. After cleaning, re-optimization is required to recover the original weights.
Args
threshold(float, optional): Float specifying the minimum absolute weight to retain. Defaults to1e-8.
Returns:
numpy.ndarray: Cleaned and re-normalized portfolio weight vector.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- Weights are cleaned using absolute values, making this method compatible with long-short portfolios.
- Re-normalization ensures the portfolio remains properly scaled after cleaning.
- Increasing threshold promotes sparsity but may materially alter the portfolio composition.
optimize
def optimize(
data=None,
weight_bounds=(0, 1),
w=None,
custom_cov=None,
custom_mean=None
)
Solves the Mean-Variance Optimization objective:
Args
data(pd.DataFrame): Ticker price data in either multi-index or single-index formats. Examples are given below:# Single-Index Example Ticker TSLA NVDA GME PFE AAPL ... Date 2015-01-02 14.620667 0.483011 6.288958 18.688917 24.237551 ... 2015-01-05 14.006000 0.474853 6.460137 18.587513 23.554741 ... 2015-01-06 14.085333 0.460456 6.268492 18.742599 23.556952 ... 2015-01-07 14.063333 0.459257 6.195926 18.999102 23.887287 ... 2015-01-08 14.041333 0.476533 6.268492 19.386841 24.805082 ... ... # Multi-Index Example Structure (OHLCV) Columns: + Ticker (e.g. GME, PFE, AAPL, ...) - Open - High - Low - Close - Volumeweight_bounds(tuple, optional): Boundary constraints for asset weights. Values must be of the format(lesser, greater)with0 <= |lesser|, |greater| <= 1. Defaults to(0,1).w(None or np.ndarray, optional): Initial weight vector for warm starts. Mainly used for backtesting and not recommended for user input. Defaults toNone.custom_mean(None or np.ndarray, optional): Custom mean vector. Can be used to inject externally generated mean vectors (eg. Black-Litterman). Defaults toNone.custom_cov(None or array-like of shape (n_assets, n_assets), optional): Custom covariance matrix. Can be used to inject externally generated covariance matrices (eg. Ledoit-Wolf). Defaults toNone.
Returns:
np.ndarray: Vector of optimized portfolio weights.
Raises
DataError: For any data mismatch during integrity check.PortfolioError: For any invalid portfolio variable inputs during integrity check.OptimizationError: IfSLSQPsolver fails to solve.
Example:
# Importing the mean variance module
from opes.objectives import MeanVariance
# Let this be your ticker data
training_data = some_data()
# Let these be your custom mean and covariance
mean_v = customMean()
cov_m = customCov()
# Initialize with risk aversion and custom regularization
mean_variance = MeanVariance(risk_aversion=0.33, reg='entropy', strength=0.01)
# Optimize portfolio with custom weight bounds, mean vector and covariance matrix
weights = mean_variance.optimize(data=training_data, weight_bounds=(0.05, 0.75), custom_mean=mean_v, custom_cov=cov_m)
set_regularizer
def set_regularizer(reg=None, strength=1)
Updates the regularization function and its penalty strength.
This method updates both the regularization function and its associated penalty strength. Useful for changing the behaviour of the optimizer without initiating a new one.
Args
reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Example:
# Import the MeanVariance class
from opes.objectives import MeanVariance
# Set with 'entropy' regularization
optimizer = MeanVariance(reg='entropy', strength=0.01)
# --- Do Something with `optimizer` ---
optimizer.optimize(data=some_data())
# Change regularizer using `set_regularizer`
optimizer.set_regularizer(reg='l1', strength=0.02)
# --- Do something else with new `optimizer` ---
optimizer.optimize(data=some_data())
stats
def stats()
Calculates and returns portfolio concentration and diversification statistics.
These statistics help users to inspect portfolio's overall concentration in
allocation. For the method to work, the optimizer must have been initialized, i.e.
the optimize() method should have been called at least once for self.weights
to be defined other than None.
Returns:
- A
dictcontaining the following keys:'tickers'(list): A list of tickers used for optimization.'weights'(np.ndarry): Portfolio weights, output from optimization.'portfolio_entropy'(float): Shannon entropy computed on portfolio weights.'herfindahl_index'(float): Herfindahl Index value, computed on portfolio weights.'gini_coefficient'(float): Gini Coefficient value, computed on portfolio weights.'absolute_max_weight'(float): Absolute maximum allocation for an asset.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- All statistics are computed on absolute normalized weights (within the simplex), ensuring compatibility with long-short portfolios.
- This method is diagnostic only and does not modify portfolio weights.
- For meaningful interpretation, use these metrics in conjunction with risk and performance measures.
MinVariance
class MinVariance(reg=None, strength=1)
Minimum Variance optimization.
The Global Minimum Variance portfolio represents the lowest-risk portfolio achievable through diversification, regardless of expected returns. This portfolio lies at the leftmost point of the efficient frontier and has the important property that it does not require estimates of expected returns. This makes GMV portfolios more robust to estimation error than other mean-variance strategies, as expected returns are notoriously difficult to estimate accurately, even more so than covariance matrix. The GMV portfolio is particularly popular among practitioners who are skeptical of return forecasts or who seek a purely defensive allocation.
Args
reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Methods
clean_weights
def clean_weights(threshold=1e-08)
Cleans the portfolio weights by setting very small positions to zero.
Any weight whose absolute value is below the specified threshold is replaced with zero.
This helps remove negligible allocations while keeping the array structure intact. This method
requires portfolio optimization (optimize() method) to take place for self.weights to be
defined other than None.
Warning:
This method modifies the existing portfolio weights in place. After cleaning, re-optimization is required to recover the original weights.
Args
threshold(float, optional): Float specifying the minimum absolute weight to retain. Defaults to1e-8.
Returns:
numpy.ndarray: Cleaned and re-normalized portfolio weight vector.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- Weights are cleaned using absolute values, making this method compatible with long-short portfolios.
- Re-normalization ensures the portfolio remains properly scaled after cleaning.
- Increasing threshold promotes sparsity but may materially alter the portfolio composition.
optimize
def optimize(
data=None,
weight_bounds=(0, 1),
w=None,
custom_cov=None
)
Solves the Minimum Variance objective:
Args
data(pd.DataFrame): Ticker price data in either multi-index or single-index formats. Examples are given below:# Single-Index Example Ticker TSLA NVDA GME PFE AAPL ... Date 2015-01-02 14.620667 0.483011 6.288958 18.688917 24.237551 ... 2015-01-05 14.006000 0.474853 6.460137 18.587513 23.554741 ... 2015-01-06 14.085333 0.460456 6.268492 18.742599 23.556952 ... 2015-01-07 14.063333 0.459257 6.195926 18.999102 23.887287 ... 2015-01-08 14.041333 0.476533 6.268492 19.386841 24.805082 ... ... # Multi-Index Example Structure (OHLCV) Columns: + Ticker (e.g. GME, PFE, AAPL, ...) - Open - High - Low - Close - Volumeweight_bounds(tuple, optional): Boundary constraints for asset weights. Values must be of the format(lesser, greater)with0 <= |lesser|, |greater| <= 1. Defaults to(0,1).w(None or np.ndarray, optional): Initial weight vector for warm starts. Mainly used for backtesting and not recommended for user input. Defaults toNone.custom_cov(None or array-like of shape (n_assets, n_assets), optional): Custom covariance matrix. Can be used to inject externally generated covariance matrices (eg. Ledoit-Wolf). Defaults toNone.
Returns:
np.ndarray: Vector of optimized portfolio weights.
Raises
DataError: For any data mismatch during integrity check.PortfolioError: For any invalid portfolio variable inputs during integrity check.OptimizationError: IfSLSQPsolver fails to solve.
Example:
# Importing the Global Minimum Variance (GMV) module
from opes.objectives import MinVariance
# Let this be your ticker data
training_data = some_data()
# Let this be your custom covariance matrix
# Can be Ledoit-Wolf, OAS, BARRA etc.
cov_m = customCov()
# Initialize GMV optimizer with custom regularizer
gmv = MinVariance(reg='entropy', strength=0.05)
# Optimize portfolio with custom weight bounds and covariance matrix
weights_ftrl = gmv.optimize(data=training_data, weight_bounds=(0.05, 0.8), custom_cov=cov_m)
set_regularizer
def set_regularizer(reg=None, strength=1)
Updates the regularization function and its penalty strength.
This method updates both the regularization function and its associated penalty strength. Useful for changing the behaviour of the optimizer without initiating a new one.
Args
reg(str or None, optional): Type of regularization to be used. Setting toNoneimplies no regularizer. Defaults toNone.strength(float, optional): Strength of the regularization. Defaults to1.
Example:
# Import the MinVariance class
from opes.objectives import MinVariance
# Set with 'entropy' regularization
optimizer = MinVariance(reg='entropy', strength=0.01)
# --- Do Something with `optimizer` ---
optimizer.optimize(data=some_data())
# Change regularizer using `set_regularizer`
optimizer.set_regularizer(reg='l1', strength=0.02)
# --- Do something else with new `optimizer` ---
optimizer.optimize(data=some_data())
stats
def stats()
Calculates and returns portfolio concentration and diversification statistics.
These statistics help users to inspect portfolio's overall concentration in
allocation. For the method to work, the optimizer must have been initialized, i.e.
the optimize() method should have been called at least once for self.weights
to be defined other than None.
Returns:
- A
dictcontaining the following keys:'tickers'(list): A list of tickers used for optimization.'weights'(np.ndarry): Portfolio weights, output from optimization.'portfolio_entropy'(float): Shannon entropy computed on portfolio weights.'herfindahl_index'(float): Herfindahl Index value, computed on portfolio weights.'gini_coefficient'(float): Gini Coefficient value, computed on portfolio weights.'absolute_max_weight'(float): Absolute maximum allocation for an asset.
Raises
PortfolioError: If weights have not been calculated via optimization.
Notes:
- All statistics are computed on absolute normalized weights (within the simplex), ensuring compatibility with long-short portfolios.
- This method is diagnostic only and does not modify portfolio weights.
- For meaningful interpretation, use these metrics in conjunction with risk and performance measures.