PortfolioOptimizer#
-
class sigtech.framework.analytics.optimization.portfolio_optimizer.PortfolioOptimizer
Baseclasses:
Optimizer
Simplified wrapper around
Optimizer
to allow quick and easy portfolio optimization with standard constraints.value
args set the relative importance of optimization objectives.Constraint
value
args are only relevant in the case of multiple constraints being applied in which casevalue
adjusts the relative weighting of the constraint.Some requirements accept
dict
orpd.Series
for benchmark weights keyed by asset name. If you wish to use this optimization in aSignalStrategy
allocation then this data must also be time aware. Hence, these arguments can also be passed as apd.DataFrame
with index as datetimes and columns as asset names.Example usage:
import numpy as np import pandas as pd import sigtech.framework as sig np.random.seed(12) n_days = 252 * 10 stock_returns = pd.DataFrame({ 'Stock_A': np.random.normal(loc=+1.00e-3, scale=0.01, size=n_days), # Average performance 'Stock_B': np.random.normal(loc=+1.01e-3, scale=0.02, size=n_days), # Positive return but volatile 'Stock_C': np.random.normal(loc=-1.01e-3, scale=0.01, size=n_days) # Negative performance }) # Construct the optimizer and add a mean-variance requirement. base_opt = sig.PortfolioOptimizer() base_opt.prefer_mean_variance() base_opt.calculate_weights(stock_returns) ''' Stock_A 3.293694 Stock_B 1.327438 Stock_C -6.395387 dtype: float64 ''' # Copy the previous optimizer to retain the mean-variance setup but add long-only requirement. base_opt.copy().require_long_only().calculate_weights(stock_returns) ''' Stock_A 3.332055e+00 Stock_B 1.325735e+00 Stock_C 1.079088e-24 dtype: float64 ''' # Restrict the weights. The above long-only requirement was applied to a copy so is not applicable here. base_opt.copy().require_weight_limit(min_value=-1, max_value=0.5).calculate_weights(stock_returns) ''' Stock_A 0.5 Stock_B 0.5 Stock_C -1.0 dtype: float64 ''' # Apply an L2 penalty. ``value`` is the L2 lambda parameter. base_opt.copy().prefer_weights_l2_penalty(value=1e-3).calculate_weights(stock_returns) ''' Stock_A 0.297179 Stock_B 0.368629 Stock_C -0.579929 dtype: float64 ''' # CVaR optimization example base_opt = sig.PortfolioOptimizer() base_opt.prefer_lower_cvar(percentile=10) base_opt.require_fully_invested() base_opt.require_long_only() base_opt.calculate_weights(stock_returns) ''' Stock_A 0.489962 Stock_B 0.130832 Stock_C 0.379206 dtype: float64 ''' # Custom objective example import cvxpy base_opt = sig.PortfolioOptimizer() base_opt.prefer_maximum_return() base_opt.prefer_custom(lambda x, data: data['my_var'] * cvxpy.quad_form(x, data['covariance']), my_var=-2)
-
add_constraints(constraints: Union[Callable, list[collections.abc.Callable]]) PortfolioOptimizer
Apply user supplied constraints to the optimization problem.
- Parameters:
constraints – List of or single callable of
PortfolioOptimizer.require_...
methods.
-
calculate_weights(instrument_returns: DataFrame)
Perform portfolio optimization over the history in
instrument_returns
.- Parameters:
instrument_returns – History of instrument returns used in optimization
- Returns:
pd.Series of optimized weights indexed by columns of
instrument_returns
.
-
copy() PortfolioOptimizer
Return a copy of this optimizer.
-
get_additional_data() Optional[dict]
Format the additional data into the correct dict of dicts format for use in the SignalStrategy allocation optimization.
-
objective_function_values() DataFrame
The objective function values for each objective on each date. This allows analysis of relative objective importance over time to ensure all objectives are considered.
-
prefer_custom(fn, value: Union[float, int] = 1, label: Optional[str] = None, **kwargs) PortfolioOptimizer
Add a custom objective term defined in a python string or function, which can contain numpy or cvxpy terms.
The
fn
takes two inputs as args corresponding to the weights array and a dictionary of additional data.The additional data contains the
covariance
,correlation
,`avg_returns
andbase
, together with the extra kwargs entered.Please see the class docs for an example.
- Parameters:
fn – String of python code or callable method taking the weights x and a dictionary of data.
value – Weight modifier given to this objective (optional, defaults to 1).
label – string label for this objective (optional).
-
prefer_equal_risk_contribution(value: Union[float, int] = 1) PortfolioOptimizer
Target equal risk contribution from each asset.
- Parameters:
value – Weight modifier given to this constraint.
-
prefer_gross_leverage(gross_leverage: Union[float, int] = 1, value: Union[float, int] = 1) PortfolioOptimizer
Prefer the sum of the absolute-value weights to near
gross_leverage
.This method adds an objective that pulls the gross leverage towards the specified value but allows some deviation if other objectives or constraints will not allow an exact fit. This can be useful in turnover controlled fits where we wish to tend to a gross leverage value.
-
prefer_inverse_vol_weighting(value: Union[float, int] = 1) PortfolioOptimizer
Target weights proportional to inverse volatility weighting (ignoring inter asset correlations).
- Parameters:
value – Weight modifier given to this constraint.
-
prefer_lower_cvar(value: Union[float, int] = 1, percentile: Optional[Union[float, int]] = 5) PortfolioOptimizer
Prefer a lower conditional value-at-risk (CVaR).
This method adds an objective that minimizes the negative 5% CVaR. The penalty is specified with
value
, which should be a positive value.Please see the class docs for an example.
- Parameters:
value – Weight given to this objective.
percentile – Percentile for the CVaR (Optional, defaults to 5%).
-
prefer_lower_var(value: Union[float, int] = 1, percentile: Optional[Union[float, int]] = 5) PortfolioOptimizer
Prefer a lower value-at-risk (VaR).
This method adds an objective that minimizes the negative 5% VaR. The penalty is specified with
value
, which should be a positive value.- Parameters:
value – Weight given to this objective.
percentile – Percentile for the VaR (Optional, defaults to 5%).
-
prefer_maximum_diversification(value: Union[float, int] = 1) PortfolioOptimizer
Add an objective to the optimization to maximise the portfolio diversification by minimizing the sum of squared weights.
- Parameters:
value – Weight modifier given to this constraint.
-
prefer_maximum_return(value: Union[float, int] = 1, expected_returns: Optional[Series] = None) PortfolioOptimizer
Maximize the expected portfolio return from historical average returns.
- Parameters:
value – Weight modifier given to this constraint.
-
prefer_mean_variance(risk_aversion: Union[float, int] = 1) PortfolioOptimizer
Assign the mean-variance optimization requirements to this problem.
- Parameters:
risk_aversion – Relative strength of variance minimization to return maximization (larger value means less return).
-
prefer_minimum_turnover(initial_weights: Optional[Union[Series, dict]] = None, value: Union[float, int] = 1) PortfolioOptimizer
Add an objective to the optimization to minimize the portfolio turnover.
- Parameters:
initial_weights – Starting weights of portfolio instruments. If unset (
None
, default) we use the internal method to set the previous weights (initially starting from zero). If set, the user controls the starting weights to allow for use outsideSignalStrategy.allocation_function
.value – Weight modifier given to this constraint.
-
prefer_minimum_variance(value: Union[float, int] = 1) PortfolioOptimizer
Minimize the variance using the full, empirical covariance matrix.
- Parameters:
value – Weight modifier given to this constraint.
-
prefer_net_leverage(net_leverage: Union[float, int] = 1, value: Union[float, int] = 1) PortfolioOptimizer
Prefer the sum of the weights to be near
net_leverage
.This method adds an objective that pulls the net leverage towards the specified value but allows some deviation if other objectives or constraints will not allow an exact fit. This can be useful in turnover controlled fits where we wish to tend to a net leverage value.
-
prefer_signal(value=1) PortfolioOptimizer
Add an optimization objective to minimize the difference between the asset weights and the supplied
SignalStrategy
Signal
.- Parameters:
value – Weight modifier given to this constraint.
-
prefer_target_return(pct_return_target: Union[float, int] = 10, value: Union[float, int] = 1) PortfolioOptimizer
Set a target for the annualized portfolio percentage return (default=10%).
- Parameters:
pct_return_target – Annualised percentage portfolio return target.
value – Weight modifier for this objective.
-
prefer_weights_l1_penalty(value: Union[float, int] = 1) PortfolioOptimizer
Regularize the portfolio weights with an L1 penalty term.
- Parameters:
value – Weight modifier given to this constraint.
-
prefer_weights_l2_penalty(value: Union[float, int] = 1) PortfolioOptimizer
Regularize the portfolio weights with an L2 penalty term.
- Parameters:
value – Weight modifier given to this constraint.
-
require_active_share_upper_bound(upper_bound: Union[float, int], benchmark_weights: Union[DataFrame, Series, dict]) PortfolioOptimizer
Limit \(\frac{1}{2}\sum_{i | w_i - w_{BM} | \leq \texttt{upper_bound}\)
- Parameters:
upper_bound – Maximum active share.
benchmark_weights – Key-value pairs of instrument names to benchmark weights. A
pd.DataFrame
may also be passed if the optimization is being run through aSignalStrategy
allocation function.
-
require_custom(fn, min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None, label: Optional[str] = None, **kwargs) PortfolioOptimizer
Add a custom constraint term defined in a python string or function, which can contain numpy or cvxpy terms.
The
fn
takes two inputs as args corresponding to the weights array and a dictionary of additional data.The additional data contains the
covariance
,correlation
,`avg_returns
andbase
, together with the extra kwargs entered.- Parameters:
fn – String of python code or callable method taking the weights x and a dictionary of data.
min_value – Lower bound on term (optional).
max_value – Upper bound on term (optional).
label – string label for this objective (optional).
-
require_cvar(min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None, percentile: Optional[Union[float, int]] = 5) PortfolioOptimizer
Require the 5% conditional value-at-risk (CVaR) to be between
min_value
andmax_value
.Note: The bounds are in terms of the loss, for example to set a maximum CVaR corresponding to loss of 5, set the
max_value
to 5.- Parameters:
min_value – Lower bound on term (Optional).
max_value – Upper bound on term (Optional).
percentile – Percentile for the CVaR (Optional, defaults to 5%).
-
require_equal_risk_contribution(value: Union[float, int] = 1) PortfolioOptimizer deprecated
Target equal risk contribution from each asset.
- Parameters:
value – Weight modifier given to this constraint.
-
require_fully_invested() PortfolioOptimizer
Require the summed weights to equal 1.
-
require_gross_leverage(min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None) PortfolioOptimizer
Require the sum of the absolute-value weights to be between
min_value
andmax_value
.
-
require_inverse_vol_weighting(value: Union[float, int] = 1) PortfolioOptimizer deprecated
Target weights proportional to inverse volatility weighting (ignoring inter asset correlations).
- Parameters:
value – Weight modifier given to this constraint.
-
require_label_limits(min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None, label_identifiers: Optional[list] = None, gross_weight: Optional[bool] = False) PortfolioOptimizer
Require the summed weights in a label group to be between
min_value
andmax_value
.
-
require_limited_participation(aum: Union[float, int], initial_weights: Union[Series, dict], date: date, instrument_mapping: Optional[Union[Series, dict]] = None, max_participation_pct: Union[float, int] = 10) PortfolioOptimizer
Limit the weight change of the portfolio instruments to a percent of median daily volume.
This requirement cannot be used with the
SignalStrategy
allocation setup as we need to know the identity of all assets in the portfolio before we can access the MDVs. This requirement can only be used on a step-by -step basis.- Parameters:
aum – Portfolio AUM in USD.
initial_weights – Starting weights of portfolio instruments.
date – Date to extract the average daily volume data.
instrument_mapping – Mapping from
initial_weights
keys to SIGTech tickers with.history('Volume')
attribute.max_participation_pct – Maximum allowed participation percent to limit trades.
-
require_limited_weights_turnover(initial_weights: Optional[Union[Series, dict]] = None, max_weight_change: Union[float, int] = 0.05) PortfolioOptimizer
Limit the weight change of the portfolio instruments to a set value.
- Parameters:
initial_weights – Starting weights of portfolio instruments. If unset (
None
, default) we use the internal method to set the previous weights (initially starting from zero). If set, the user controls the starting weights to allow for use outsideSignalStrategy.allocation_function
.max_weight_change – Maximum allowed portfolio weight change.
-
require_long_only() PortfolioOptimizer
Require that all instrument weights are >= 0.
-
require_long_short_neutral() PortfolioOptimizer
Require the summed weights to equal 0.
-
require_maximum_diversification(value: Union[float, int] = 1) PortfolioOptimizer deprecated
Add an objective to the optimization to maximise the portfolio diversification by minimizing the sum of squared weights.
- Parameters:
value – Weight modifier given to this constraint.
-
require_maximum_return(value: Union[float, int] = 1, expected_returns: Optional[Series] = None) PortfolioOptimizer deprecated
Maximize the expected portfolio return from historical average returns.
- Parameters:
value – Weight modifier given to this constraint.
-
require_mean_variance(risk_aversion: Union[float, int] = 1) PortfolioOptimizer deprecated
Assign the mean-variance optimization requirements to this problem.
- Parameters:
risk_aversion – Relative strength of variance minimization to return maximization (larger value means less return).
-
require_minimum_turnover(initial_weights: Optional[Union[Series, dict]] = None, value: Union[float, int] = 1) PortfolioOptimizer deprecated
Add an objective to the optimization to minimize the portfolio turnover.
- Parameters:
initial_weights – Starting weights of portfolio instruments. If unset (
None
, default) we use the internal method to set the previous weights (initially starting from zero). If set, the user controls the starting weights to allow for use outsideSignalStrategy.allocation_function
.value – Weight modifier given to this constraint.
-
require_minimum_variance(value: Union[float, int] = 1) PortfolioOptimizer deprecated
Minimize the variance using the full, empirical covariance matrix.
- Parameters:
value – Weight modifier given to this constraint.
-
require_net_leverage(min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None) PortfolioOptimizer
Require the summed weights to be between
min_value
andmax_value
.
-
require_signal_tolerance(tolerance=0.1) PortfolioOptimizer
If using this
PortfolioOptimizer
in aSignalStrategy
allocation function, this method ensures the asset weights track theSignalStrategy
Signal
to within a tolerance expressed in terms of portfolio weight.- Parameters:
tolerance – Maximum allowed deviation from
SignalStrategy
Signal
in units of portfolio weight fraction. I.e. iftolerance=0.1
the maximum deviation from the supplied signal is 10% of the portfolio weight.
-
require_target_return(pct_return_target: Union[float, int] = 10, value: Union[float, int] = 1) PortfolioOptimizer deprecated
Set a target for the annualized portfolio percentage return (default=10%).
- Parameters:
pct_return_target – Annualised percentage portfolio return target.
value – Weight modifier for this objective.
-
require_target_volatility(pct_vol_target: Union[float, int] = 10) PortfolioOptimizer
Set a target for the annualized portfolio percentage volatility (default=10%).
- Parameters:
pct_vol_target – Annualised percentage portfolio volatility (std. dev.) target.
-
require_tracking_error_upper_bound(ann_te_pct: Union[float, int], benchmark_weights: Union[DataFrame, Series, dict]) PortfolioOptimizer
Add a constraint to
problem
to match the tracking error tobenchmark_weights
to <ann_te_pct
- Parameters:
ann_te_pct – Annualized tracking-error percentage.
benchmark_weights – Key-value pairs of instrument names to benchmark weights.
-
require_var(min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None, percentile: Optional[Union[float, int]] = 5) PortfolioOptimizer
Require the 5% value-at-risk (VaR) to be between
min_value
andmax_value
.Note: The bounds are in terms of the loss, for example to set a maximum VaR corresponding to loss of 5, set the
max_value
to 5.- Parameters:
min_value – Lower bound on term (Optional).
max_value – Upper bound on term (Optional).
percentile – Percentile for the VaR (Optional, defaults to 5%).
-
require_weight_limit(min_value: Optional[Union[float, int]] = None, max_value: Optional[Union[float, int]] = None, label_identifiers: Optional[tuple] = None) PortfolioOptimizer
Require all instrument weights are between
min_value
andmax_value
.
-
require_weights_l1_penalty(value: Union[float, int] = 1) PortfolioOptimizer deprecated
Regularize the portfolio weights with an L1 penalty term.
- Parameters:
value – Weight modifier given to this constraint.
-
require_weights_l2_penalty(value: Union[float, int] = 1) PortfolioOptimizer deprecated
Regularize the portfolio weights with an L2 penalty term.
- Parameters:
value – Weight modifier given to this constraint.
-
signal_strategy_allocation_kwargs() dict
Return the dict for this object to be used in a
SignalStrategy
constructor for theallocation_kwargs
argument.Example usage:
>>> po = PortfolioOptimizer().require_... >>> sig.SignalStrategy( >>> currency='USD', >>> start_date=dtm.date(2020,1,1), >>> rebalance_frequency='1BD', >>> signal_name=signal.name, >>> allocation_function=sig.signal_library_allocation.optimized_allocations, >>> allocation_kwargs=po.signal_strategy_allocation_kwargs() >>> )
-
solved_weights() DataFrame
The optimized weights from this set of solvers. If run through a SignalStrategy allocation optimization this method allows access to the optimized weights rather than the realised ones used in the strategy which evolve with price changes.
-
test_solvers(instrument_returns: DataFrame) DataFrame
Perform portfolio optimization using all available solvers and report back which were successful. This method can be used to choose the best optimizer config. for your problem before doing long loops.
- Parameters:
instrument_returns – History of instrument returns used in optimization
- Returns:
pd.DataFrame
of the fitting test results.