Portfolio Construction

Key Concepts

Introduction

The Portfolio Construction model receives Insight objects from the Alpha model and creates PortfolioTarget objects for the Risk Management model. A PortfolioTarget provides the number of units of an asset to hold.

Set Models

To set a Portfolio Construction model, in the Initializeinitialize method, call the SetPortfolioConstruction method.

SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())

To view all the pre-built Portfolio Construction models, see Supported Models.

Model Structure

Portfolio Construction models should extend the PortfolioConstructionModel class or one of the supported models. Extensions of the PortfolioConstructionModel class should implement the CreateTargets method, which receives an array of Insight objects from the Alpha model at every time step and returns an array of PortfolioTarget objects. The Portfolio Construction model seeks to answer the question, "how many units should I buy based on the insight predictions I've been presented?".

If you don't override the CreateTargets method, the base class implementation calls the model's IsRebalanceDue, DetermineTargetPercent, and GetTargetInsights helper methods. The GetTargetInsights method, in turn, calls the model's ShouldCreateTargetForInsight method. You can override any of these helper methods. If you don't override the CreateTargets method from the PortfolioConstructionModel class, your class must at least override the DetermineTargetPercent method.

// Portfolio construction scaffolding class; basic method args.
class MyPortfolioConstructionModel : PortfolioConstructionModel
{
    // Create list of PortfolioTarget objects from Insights
    public override List<PortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
    {
        return base.CreateTargets(algorithm, insights);
    }
    
    // Determines if the portfolio should rebalance based on the provided rebalancing func
    protected virtual bool IsRebalanceDue(Insight[] insights, DateTime algorithmUtc) 
    {
        return base.IsRebalanceDue(insights, algorithmUtc);
    }
    
    // Determines the target percent for each insight
    protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
    {
        return new Dictionary<Insight, double>();
    }

    // Gets the target insights to calculate a portfolio target percent for, they will be piped to DetermineTargetPercent()
    protected override List<Insight> GetTargetInsights()
    {
        return base.GetTargetInsights();
    }

    // Determine if the portfolio construction model should create a target for this insight
    protected override bool ShouldCreateTargetForInsight(Insight insight)
    {
        return base.ShouldCreateTargetForInsight(insight);
    }

    // Security change details
    public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
    {
        return base.OnSecuritiesChanged(algorithm, changes);
    }
}
 # Portfolio construction scaffolding class; basic method args.
class MyPortfolioConstructionModel(PortfolioConstructionModel):
    # Create list of PortfolioTarget objects from Insights
    def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]) -> List[PortfolioTarget]:
        return super().create_targets(algorithm, insights)
	
    # Determines if the portfolio should rebalance based on the provided rebalancing func
    def is_rebalance_due(self, insights: List[Insight], algorithmUtc: datetime) -> bool:
        return super().is_rebalance_due(insights, algorithmUtc)

    # Determines the target percent for each insight
    def determine_target_percent(self, activeInsights: List[Insight]) -> Dict[Insight, float]:
        return {}

    # Gets the target insights to calculate a portfolio target percent for, they will be piped to DetermineTargetPercent()
    def get_target_insights(self) -> List[Insight]:
        return super().get_target_insights()

    # Determine if the portfolio construction model should create a target for this insight
    def should_create_target_for_insight(self, insight: Insight) -> bool:
        return super().should_create_target_for_insight(insight)

    # Security change details
    def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None:
        super().on_securities_changed(algorithm, changes)

The Portfolio Construction model should remove expired insights from the Insight Manager. The CreateTargets definition of the base PortfolioConstructionModel class already removes them during each rebalance. Therefore, if you override the CreateTargets method and don't call the CreateTargets definition of the base class, your new method definition should remove expired insights from the Insight Manager.

The model should also remove all a security's insights from the Insight Manager when the security is removed from the universe. The OnSecuritiesChangedon_securities_changed definition of the base PortfolioConstructionModel class already does this. Therefore, if you override the OnSecuritiesChangedon_securities_changed method and don't call the OnSecuritiesChangedon_securities_changed definition of the base class, your new method definition should remove the security's insights from the Insight Manager.

The algorithm argument that the methods receive is an instance of the base QCAlgorithm class, not your subclass of it.

You may use the PortfolioBias enumeration in the definition of Portfolio Construction model methods. The PortfolioBias enumeration has the following members:

To view a full example of a PortfolioConstructionModel subclass, see the EqualWeightingPortfolioConstructionModelEqualWeightingPortfolioConstructionModel in the LEAN GitHub repository.

Multi-Alpha Algorithms

If you add multiple Alpha models, each Alpha model receives the current slice in the order that you add the Alphas. The combined stream of Insight objects is passed to the Portfolio Construction model.

Each Portfolio Construction model has a unique method to combine Insight objects. The base PortfolioConstructionModel that most PCM's inherit from doesn't combine information from Insight objects with the same Symbol - but just gets the most recent active insight. To combine the active insights differently, override the GetTargetInsights, and return all active insights. The DetermineTargetPercent method implements the combination criteria and determines the target for each Symbol.

public class MultipleAlphaPortfolioConstructionModel : PortfolioConstructionModel
{
    protected override List<Insight> GetTargetInsights()
    {
        return InsightCollection.GetActiveInsights(Algorithm.UtcTime).ToList();
    }

    protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
    {
        return new Dictionary<Insight, double>();
    }
}
class MultipleAlphaPortfolioConstructionModel(PortfolioConstructionModel):
    def get_target_insights(self) -> List[Insight]:
        return list(self.insight_collection.get_active_insights(self.algorithm.utc_time))

    def determine_target_percent(self, activeInsights: List[Insight]) -> Dict[Insight, float]:
        return {}

Portfolio Targets

The Portfolio Construction model returns PortfolioTarget objects, which are passed to the Risk Management model.

To create a PortfolioTarget object based on a quantity, pass the Symbol and quantity to the PortfolioTarget constructor.

// Create a new portfolio target for 1200 IBM shares.
var target = new PortfolioTarget("IBM", 1200);
# Create a new portfolio target for 1200 IBM shares.
target = PortfolioTarget("IBM", 1200)

To create a PortfolioTarget object based on a portfolio weight, call the Percent method. This method is only available for margin accounts.

// Calculate target equivalent to 10% of portfolio value
var target = PortfolioTarget.Percent(algorithm, "IBM", 0.1);
# Calculate target equivalent to 10% of portfolio value
target = PortfolioTarget.percent(algorithm, "IBM", 0.1)

To include more information in the PortfolioTarget object, pass a tag argument to the constructor or the Percent method.

The CreateTargets method of your Portfolio Construction model must return an array of PortfolioTarget objects.

return new PortfolioTarget[] {  new PortfolioTarget("IBM", 1200)  };
return [ PortfolioTarget("IBM", 1200) ]

Portfolio Target Collection

The PortfolioTargetCollection class is a helper class to manage PortfolioTarget objects. The class manages an internal dictionary that has the security Symbol as the key and a PortfolioTarget as the value.

Add Portfolio Targets

To add a PortfolioTarget to the PortfolioTargetCollection, call the Add method.

_targetsCollection.Add(portfolioTarget);
self.targets_collection.add(portfolio_target)

To add a list of PortfolioTarget objects, call the AddRange method.

_targetsCollection.AddRange(portfolioTargets);
self.targets_collection.add_range(portfolio_targets)

Check Membership

To check if a PortfolioTarget exists in the PortfolioTargetCollection, call the Contains method.

var targetInCollection = _targetsCollection.Contains(portfolioTarget);
target_in_collection = self.targets_collection.contains(portfolio_target)

To check if a Symbol exists in the PortfolioTargetCollection, call the ContainsKeycontains_key method.

var symbolInCollection = _targetsCollection.ContainsKey(symbol);
symbol_in_collection = self.targets_collection.contains_key(symbol)

To get all the Symbol objects, use the Keys property.

var symbols = _targetsCollection.Keys;
symbols = self.targets_collection.keys

Access Portfolio Targets

To access the PortfolioTarget objects for a Symbol, index the PortfolioTargetCollection with the Symbol.

var portfolioTarget = _targetsCollection[symbol];
portfolio_target = self.targets_collection[symbol]

To iterate through the PortfolioTargetCollection, call the GetEnumerator method.

var enumerator = _targetsCollection.GetEnumerator();
enumerator = self.targets_collection.get_enumerator()

To get all the PortfolioTarget objects, use the Values property

var portfolioTargets = _targetsCollection.Values;
portfolio_targets = self.targets_collection.values

Order Portfolio Targets by Margin Impact

To get an enumerable where position reducing orders are executed first and the remaining orders are executed in decreasing order value, call the OrderByMarginImpact method.

foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
{
    // Place order
}
for target in self.targets_collection.order_by_margin_impact(algorithm):
    # Place order

This method won't return targets for securities that have no data yet. This method also won't return targets for which the sum of the current holdings and open orders quantity equals the target quantity.

Remove Portfolio Targets

To remove a PortfolioTarget from the PortfolioTargetCollection, call the Removeremove method.

removeSuccessful = _targetsCollection.Remove(symbol);
remove_successful = self.targets_collection.remove(symbol)

To remove all the PortfolioTarget objects, call the Clear method.

_targetsCollection.Clear();
self.targets_collection.clear()

To remove all the PortfolioTarget objects that have been fulfilled, call the ClearFulfilled method.

_targetsCollection.ClearFulfilled(algorithm);
self.targets_collection.clear_fulfilled(algorithm)

Rebalance Frequency

If you use a Portfolio Construction model that is a subclass of the PortfolioConstructionModel class, you can set the rebalancing frequency of the model with a function. The rebalancing function receives the Coordinated Universal Time (UTC) of the algorithm and should return the next rebalance UTC time or Nonenull. If the function returns Nonenull, the model doesn't rebalance unless the rebalance settings trigger a rebalance. For a full example of a custom rebalance function, see the PortfolioRebalanceOnCustomFuncRegressionAlgorithmPortfolioRebalanceOnCustomFuncRegressionAlgorithm.

If you use a Portfolio Construction model with the following characteristics, you can also set the rebalancing frequency of the model with a timedeltaTimeSpan, Resolution, or DateRules:

  • The model is a subclass of the EqualWeightingPortfolioConstructionModel class.
  • The model constructor calls the EqualWeightingPortfolioConstructionModel constructor.
  • The model doesn't override the CreateTargets method.

To check which of the pre-built Portfolio Construction models support this functionality, see Supported Models.

Rebalance Settings

By default, portfolio construction models create PortfolioTarget objects to rebalance the portfolio when any of the following events occur:

  • The model's rebalance function signals it's time to rebalance
  • The Alpha model emits new insights
  • The universe changes

To disable rebalances when the Alpha model emits insights or when insights expire, set RebalancePortfolioOnInsightChangesrebalance_portfolio_on_insight_changes to false.

// In Initialize
Settings.RebalancePortfolioOnInsightChanges = false;
# In Initialize
self.settings.rebalance_portfolio_on_insight_changes = False

To disable rebalances when security changes occur, set RebalancePortfolioOnSecurityChangesrebalance_portfolio_on_security_changes to false.

// In Initialize
Settings.RebalancePortfolioOnSecurityChanges = false;
# In Initialize
self.settings.rebalance_portfolio_on_security_changes = False

Portfolio Optimizer Structure

Some portfolio construction models contain an optimizer that accepts the historical returns of each security and returns a list of optimized portfolio weights. Portfolio optimizer models must implement the IPortfolioOptimizer interface, which has an Optimize method.

public class MyPortfolioOptimizer : IPortfolioOptimizer
{
    public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
    {
        // Create weights
        //  For example, equal-weighting:
        int numAssets = historicalReturns.GetLength(1);
        var weights = Enumerable.Repeat(1.0 / numAssets, numAssets).ToArray();

        return weights;
    }
}
class MyPortfolioOptimizer:

    def optimize(self, historicalReturns: pd.DataFrame, expectedReturns: pd.Series = None, covariance: pd.DataFrame = None) -> pd.Series:
        # Create weights
        #  For example, equal-weighting:
        num_assets = historicalReturns.shape[1]
        weights = [1/num_assets] * num_assets

        return weights

The following table describes the arguments the Optimize method accepts:

ArgumentData TypeDescriptionDefault Value
historicalReturnsdouble[,]DataFrameMatrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N)
expectedReturnsdouble[]SeriesArray of double with the portfolio annualized expected returns (size: K x 1)nullNone
covariancedouble[,]DataFrameMulti-dimensional array of double with the portfolio covariance of annualized returns (size: K x K)nullNone

The method should return a K x 1 array of double objects that represent the portfolio weights.

To view all the pre-built portfolio optimization algorithms, see Supported Optimizers.

To view a full example of an IPortfolioOptimizer implementation, see the MaximumSharpeRatioPortfolioOptimizerMaximumSharpeRatioPortfolioOptimizer in the LEAN GitHub repository.

If you define a custom optimizer and want to use it as the optimizer argument for one of the pre-built Portfolio Construction models, import the Python version of the Portfolio Construction model into your project file. For example, to pair your optimizer with the Black Litterman Optimization Model, add the following line:

from Portfolio.black_litterman_optimization_portfolio_construction_model import BlackLittermanOptimizationPortfolioConstructionModel

Track Security Changes

The Universe Selection model may select a dynamic universe of assets, so you should not assume a fixed set of assets in the Portfolio Construction model. When the Universe Selection model adds and removes assets from the universe, it triggers an OnSecuritiesChangedon_securities_changed event. In the OnSecuritiesChangedon_securities_changed event handler, you can initialize the security-specific state or load any history required for your Portfolio Construction model. If you need to save data for individual securities, add custom members to the respective Security objectcast the Security object to a dynamic object and then save custom members to it.

class MyPortfolioConstructionModel : PortfolioConstructionModel{
    private List<Security> _securities = new List<Security>();

    public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
    {
        base.OnSecuritiesChanged(algorithm, changes);
        foreach (var security in changes.AddedSecurities)
        {               
            // Store and manage Symbol-specific data
            var dynamicSecurity = security as dynamic;
            dynamicSecurity.Sma = SMA(security.Symbol, 20);

            _securities.Add(security);
        }

        foreach (var security in changes.RemovedSecurities)
        {
            if (_securities.Contains(security))
            {
                algorithm.DeregisterIndicator((security as dynamic).Sma);

                _securities.Remove(security);
            }
        }
    }
}
class MyPortfolioConstructionModel(PortfolioConstructionModel):
    _securities = []

    def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None:
        super().on_securities_changed(algorithm, changes)
        for security in changes.added_securities::
            # Store and manage Symbol-specific data
            security.indicator = algorithm.sma(security.symbol, 20)
            algorithm.warm_up_indicator(security.symbol, security.indicator)

            self._securities.append(security)

        for security in changes.removed_securities:
            if security in self.securities:
                algorithm.deregister_indicator(security.indicator)
                self._securities.remove(security)

Universe Timing Considerations

If the Portfolio Construction model manages some indicators or consolidators for securities in the universe and the universe selection runs during the indicator sampling period or the consolidator aggregation period, the indicators and consolidators might be missing some data. For example, take the following scenario:

  • The security resolution is minute
  • You have a consolidator that aggregates the security data into daily bars to update the indicator
  • The universe selection runs at noon

In this scenario, you create and warm-up the indicator at noon. Since it runs at noon, the history request that gathers daily data to warm up the indicator won't contain any data from the current day and the consolidator that updates the indicator also won't aggregate any data from before noon. This process doesn't cause issues if the indicator only uses the close price to calculate the indicator value (like the simple moving average indicator) because the first consolidated bar that updates the indicator will have the correct close price. However, if the indicator uses more than just the close price to calculate its value (like the True Range indicator), the open, high, and low values of the first consolidated bar may be incorrect, causing the initial indicator values to be incorrect.

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: