Skip to content

Commit

Permalink
Make SignalExportManager skip non-tradeable securities (#8453)
Browse files Browse the repository at this point in the history
* First approach

* Fix bug

* Address requests

* Address requests
  • Loading branch information
Marinovsky authored Dec 11, 2024
1 parent f1c50e4 commit 2a62f16
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;
using System;
using System.Linq;

namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports
{
Expand Down Expand Up @@ -87,38 +89,15 @@ public bool SetTargetPortfolioFromPortfolio()
/// <returns>True if TotalPortfolioValue was bigger than zero, false otherwise</returns>
protected bool GetPortfolioTargets(out PortfolioTarget[] targets)
{
var portfolio = _algorithm.Portfolio;
targets = new PortfolioTarget[portfolio.Values.Count];
var index = 0;

var totalPortfolioValue = portfolio.TotalPortfolioValue;
var totalPortfolioValue = _algorithm.Portfolio.TotalPortfolioValue;
if (totalPortfolioValue <= 0)
{
_algorithm.Error("Total portfolio value was less than or equal to 0");
targets = Array.Empty<PortfolioTarget>();
return false;
}

foreach (var holding in portfolio.Values)
{
var security = _algorithm.Securities[holding.Symbol];
var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity);
var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue;
// See PortfolioTarget.Percent:
// we normalize the target buying power by the leverage so we work in the land of margin
var holdingPercent = adjustedPercent * security.BuyingPowerModel.GetLeverage(security);

// FreePortfolioValue is used for orders not to be rejected due to volatility when using SetHoldings and CalculateOrderQuantity
// Then, we need to substract its value from the TotalPortfolioValue and obtain again the holding percentage for our holding
var adjustedHoldingPercent = (holdingPercent * totalPortfolioValue) / _algorithm.Portfolio.TotalPortfolioValueLessFreeBuffer;
if (holding.Quantity < 0)
{
adjustedHoldingPercent *= -1;
}

targets[index] = new PortfolioTarget(holding.Symbol, adjustedHoldingPercent);
++index;
}

targets = GetPortfolioTargets(totalPortfolioValue).ToArray();
return true;
}

Expand Down Expand Up @@ -162,5 +141,37 @@ public bool SetTargetPortfolio(params PortfolioTarget[] portfolioTargets)

return result;
}

private IEnumerable<PortfolioTarget> GetPortfolioTargets(decimal totalPortfolioValue)
{
foreach (var holding in _algorithm.Portfolio.Values)
{
var security = _algorithm.Securities[holding.Symbol];

// Skip non-tradeable securities except canonical futures as some signal providers
// like Collective2 accept them.
// See https://collective2.com/api-docs/latest#Basic_submitsignal_format
if (!security.IsTradable && !security.Symbol.IsCanonical())
{
continue;
}

var marginParameters = MaintenanceMarginParameters.ForQuantityAtCurrentPrice(security, holding.Quantity);
var adjustedPercent = security.BuyingPowerModel.GetMaintenanceMargin(marginParameters) / totalPortfolioValue;
// See PortfolioTarget.Percent:
// we normalize the target buying power by the leverage so we work in the land of margin
var holdingPercent = adjustedPercent * security.BuyingPowerModel.GetLeverage(security);

// FreePortfolioValue is used for orders not to be rejected due to volatility when using SetHoldings and CalculateOrderQuantity
// Then, we need to substract its value from the TotalPortfolioValue and obtain again the holding percentage for our holding
var adjustedHoldingPercent = (holdingPercent * totalPortfolioValue) / _algorithm.Portfolio.TotalPortfolioValueLessFreeBuffer;
if (holding.Quantity < 0)
{
adjustedHoldingPercent *= -1;
}

yield return new PortfolioTarget(holding.Symbol, adjustedHoldingPercent);
}
}
}
}
27 changes: 27 additions & 0 deletions Tests/Algorithm/Framework/Portfolio/SignalExportTargetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,27 @@ public void SignalExportManagerGetsCorrectPortfolioTargetArray(SecurityType secu
Assert.AreEqual(quantity, targetQuantity, 1);
}

[TestCaseSource(nameof(SignalExportManagerSkipsNonTradeableFuturesTestCase))]
public void SignalExportManagerSkipsNonTradeableFutures(IEnumerable<Symbol> symbols, int expectedNumberOfTargets)
{
var algorithm = new AlgorithmStub(true);
algorithm.SetFinishedWarmingUp();
algorithm.SetCash(100000);

foreach (var symbol in symbols)
{
var security = algorithm.AddSecurity(symbol);
security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m));
security.Holdings.SetHoldings(144.81m, 100);
}

var signalExportManagerHandler = new SignalExportManagerHandler(algorithm);
var result = signalExportManagerHandler.GetPortfolioTargets(out PortfolioTarget[] portfolioTargets);

Assert.IsTrue(result);
Assert.AreEqual(expectedNumberOfTargets, portfolioTargets.Length);
}

[Test]
public void SignalExportManagerReturnsFalseWhenNegativeTotalPortfolioValue()
{
Expand Down Expand Up @@ -410,5 +431,11 @@ public string GetMessageSent(SignalExportTargetParameters parameters)
return message;
}
}

private static object[] SignalExportManagerSkipsNonTradeableFuturesTestCase =
{
new object[] { new List<Symbol>() { Symbols.AAPL, Symbols.SPY, Symbols.SPX }, 2 },
new object[] { new List<Symbol>() { Symbols.AAPL, Symbols.SPY, Symbols.NFLX }, 3 },
};
}
}

0 comments on commit 2a62f16

Please sign in to comment.