QuantConnect / QuantConnect/Lean

Actual portfolio weight exceeds target percentage when using `PortfolioTarget.Percent`

Open
#6,360 5 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug impact-low
Dominant language
C#
Stars
21.7k
Forks
5.3k
Avg merge
2d 22h
Merged PRs (30d)
34

Description

Expected Behavior

The portfolio weight is less or equal to the target percentage at the time of order fill.

Actual Behavior

The portfolio weight far exceeds the target percentage when using PortfolioTarget.Percent.

image

Potential Solution

I think this happens since LEAN uses security.Price to determine the initial margin requirements in line 230 of the buying power model which affects the result of PortfolioTarget.Percent via line 269 in the position group buying power model.

Instead we should use security.AskPrice if target.Quantity > security.Holdings.Quantity (i.e. delta > 0) and otherwise security.BidPrice; at least if quote bars are available for the given security and we therefore can assume to get a fill at the ask/bid price. It also explains why the discrepancy is proportional to the bid-ask spread.

Reproducing the Problem

Run this backtest and view the order history and log file.
It is intended to buy 10% of a QQQ option contract, but instead we get 14% which is a huge discrepancy.

from AlgorithmImports import *


class DemoAlgoPortfolioTargetPercentBug(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 3, 15)  
        self.SetEndDate(2020, 4, 1)
        self.SetCash(1_000_000)  
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw 
        self.Settings.FreePortfolioValuePercentage = 0 
        self.targetPercent = 0.1 
        self.AddEquity("QQQ", Resolution.Minute)
        self.symbol = Symbol.CreateOption("QQQ", Market.USA, OptionStyle.American, OptionRight.Call, 174, datetime(2020, 4, 17))
        self.AddOptionContract(self.symbol, Resolution.Minute) 


    def OnData(self, data: Slice):
        if not self.Portfolio.Invested:
            if self.symbol in data.QuoteBars:
                target = PortfolioTarget.Percent(self, self.symbol, self.targetPercent) 
                self.MarketOrder(self.symbol, target.Quantity)
    

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            portfolioWeight = self.Portfolio[orderEvent.Symbol].HoldingsCost / self.Portfolio.TotalPortfolioValue 
            self.Debug(f"\nActual portfolio weight of {orderEvent.Symbol.Value} after order fill: {portfolioWeight:.2%}. Target percent was {self.targetPercent:.2%}.\n")


System Information

QC Cloud

Checklist
  • I have completely filled out this template
  • I have confirmed that this issue exists on the current master branch
  • I have confirmed that this is not a duplicate issue by searching issues
  • I have provided detailed steps to reproduce the issue

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Common/Securities/BuyingPowerModel.cs at line 230 and Common/Securities/Positions/PositionGroupBuyingPowerModel.cs at line 269. Run the linked QQQ option backtest or the supplied Python algorithm, then compare the target percentage with the portfolio weight after the order fill. Done means the reproduced order no longer produces the reported discrepancy and the relevant behavior is covered by an appropriate test.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, python
Domain
backend, fintech-quant
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.