Hello all, 

I was interested in reading about any strategies you have in scaling the size of new positions based on the remaining buying power. This can be useful when your alpha opens an unknown number of positions e.g. invest 0.05 in all securities that meet some filtering criteria - if more than 20 securities meet this criteria, buying power is exceeded. 

Ideally, the strategies 1) work with any amount of leverage 2) guarantee that buying power won't be exceeded 3) work with any number of new positions.

I have 2 strategies right now, but they each have their issues.

1)

class WeightedConstructionModel(PortfolioConstructionModel):
    def CreateTargets(self, algorithm, insights):
        targets = []
        buying_power = algorithm.Portfolio.MarginRemaining/algorithm.Portfolio.TotalPortfolioValue
        for insight in insights:
            coeff = 1.00
            if buying_power < 0.10:
                coeff = np.power(abs(buying_power), 0.90)
                algorithm.Debug("{} Scaling positions by factor {}".format(algorithm.Time, coeff))
                
            targets.append(PortfolioTarget.Percent(algorithm, insight.Symbol, coeff * insight.Weight))
            buying_power = buying_power - (coeff * insight.Weight)
            
        return targets

Essentially, this strategy does no scaling until buying power is less than 0.10. Then it scales pretty aggressively. Typically, the updated weights are 1/10 of the original weight i.e. and input weight of 0.05 is scaled to 0.005. It also doesn't mathematically guarantee that buying power isn't exceeded. There are other issues too, such as how the buying power is decreased during the loop, which assigns arbitrary weights to insights.

This leads me to my updated strategy for scaling:

2)

class WeightedConstructionModel(PortfolioConstructionModel):
    def CreateTargets(self, algorithm, insights):
        
        remaining_bp = (algorithm.Portfolio.MarginRemaining/algorithm.Portfolio.TotalPortfolioValue) - BUFFER_BUYING_POWER
        opening_bp = sum(abs(insight.Weight) for insight in insights) 
        
        to_open = list(filter(lambda x: x.Weight != 0, insights))
        
        coeff = 1.0
        if remaining_bp <= 0:
            coeff = 0.0
            algorithm.Debug("{} Skipping {} new positions".format(algorithm.Time, len(to_open)))
            
        elif opening_bp != 0 and remaining_bp - opening_bp < 0:
            coeff = min(1.0, remaining_bp/opening_bp)
            algorithm.Debug("{} Scaling {} new positions by {}".format(algorithm.Time, len(to_open), coeff))
            
        targets = []
        for insight in insights:
            targets.append(PortfolioTarget.Percent(algorithm, insight.Symbol, coeff * insight.Weight))
            
        return targets

This strategy calculates the remaining buying power and the buying power that will be used, then scales by the excess buying power. For example, if 0.3 is remaining, and new positions will use 0.5, those positions will be scaled by a factor of 0.6 (0.3/0.5). There is some buffer used. This solves some problems with the previous strategy, but I'm interested in reading about anything new or better.