PortfolioOptimizer

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 case value adjusts the relative weighting of the constraint.

Some requirements accept dict or pd.Series for benchmark weights keyed by asset name. If you wish to use this optimization in a SignalStrategy allocation then this data must also be time aware. Hence, these arguments can also be passed as a pd.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 and base, 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 outside SignalStrategy.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 a SignalStrategy 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 and base, 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 and max_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 and max_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 and max_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 outside SignalStrategy.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 outside SignalStrategy.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 and max_value.

require_signal_tolerance(tolerance=0.1) PortfolioOptimizer

If using this PortfolioOptimizer in a SignalStrategy allocation function, this method ensures the asset weights track the SignalStrategy 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. if tolerance=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 to benchmark_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 and max_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 and max_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 the allocation_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.