diff --git a/Common/Securities/Future/FutureMarginModel.cs b/Common/Securities/Future/FutureMarginModel.cs
index d6b662585405..44170124a979 100644
--- a/Common/Securities/Future/FutureMarginModel.cs
+++ b/Common/Securities/Future/FutureMarginModel.cs
@@ -111,7 +111,8 @@ public override void SetLeverage(Security security, decimal leverage)
public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(
GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
{
- if (Math.Abs(parameters.TargetBuyingPower) > 1)
+ if (Math.Abs(parameters.TargetBuyingPower) > 1
+ && !IsReducingExistingExposure(parameters))
{
throw new InvalidOperationException(
"Futures do not allow specifying a leveraged target, since they are traded using margin which already is leveraged. " +
@@ -120,6 +121,31 @@ public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBu
return base.GetMaximumOrderQuantityForTargetBuyingPower(parameters);
}
+ ///
+ /// Futures can temporarily exceed 100% target buying power during margin call liquidation requests.
+ /// In these cases we allow leveraged targets only when they reduce existing exposure.
+ ///
+ private bool IsReducingExistingExposure(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
+ {
+ var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue;
+ if (totalPortfolioValue == 0)
+ {
+ return false;
+ }
+
+ var currentMargin = this.GetInitialMarginRequirement(
+ parameters.Security, parameters.Security.Holdings.Quantity
+ );
+ if (currentMargin == 0)
+ {
+ return false;
+ }
+
+ var targetMargin = parameters.TargetBuyingPower * totalPortfolioValue;
+ return Math.Sign(targetMargin) == Math.Sign(currentMargin)
+ && Math.Abs(targetMargin) <= Math.Abs(currentMargin);
+ }
+
///
/// Gets the total margin required to execute the specified order in units of the account currency including fees
///
diff --git a/Tests/Common/Securities/FutureMarginBuyingPowerModelTests.cs b/Tests/Common/Securities/FutureMarginBuyingPowerModelTests.cs
index 67ae203500e8..19694de12d0f 100644
--- a/Tests/Common/Securities/FutureMarginBuyingPowerModelTests.cs
+++ b/Tests/Common/Securities/FutureMarginBuyingPowerModelTests.cs
@@ -396,6 +396,43 @@ public void GetMaximumOrderQuantityForTargetBuyingPower_ThrowsForInvalidTarget(d
0)));
}
+ [Test]
+ public void GetMaximumOrderQuantityForDeltaBuyingPower_AllowsReducingLeveragedTarget()
+ {
+ var algorithm = new QCAlgorithm();
+ algorithm.SetFinishedWarmingUp();
+ algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
+
+ var ticker = QuantConnect.Securities.Futures.Financials.EuroDollar;
+ var futureSecurity = algorithm.AddFuture(ticker);
+ Update(futureSecurity, 100, algorithm);
+
+ algorithm.Portfolio.SetCash(1000);
+ futureSecurity.Holdings.SetHoldings(100, 10);
+ algorithm.Portfolio.InvalidateTotalPortfolioValue();
+
+ var usedBuyingPower = futureSecurity.BuyingPowerModel.GetReservedBuyingPowerForPosition(
+ new ReservedBuyingPowerForPositionParameters(futureSecurity)).AbsoluteUsedBuyingPower;
+ var totalPortfolioValue = algorithm.Portfolio.TotalPortfolioValue;
+ var targetBuyingPower = 1.1m * totalPortfolioValue;
+ var deltaBuyingPower = targetBuyingPower - usedBuyingPower;
+
+ Assert.Greater(Math.Abs(usedBuyingPower / totalPortfolioValue), 1m);
+ Assert.Less(deltaBuyingPower, 0m);
+
+ Assert.DoesNotThrow(() =>
+ {
+ var result = futureSecurity.BuyingPowerModel.GetMaximumOrderQuantityForDeltaBuyingPower(
+ new GetMaximumOrderQuantityForDeltaBuyingPowerParameters(
+ algorithm.Portfolio,
+ futureSecurity,
+ deltaBuyingPower,
+ 0
+ ));
+ Assert.Less(result.Quantity, 0m);
+ });
+ }
+
[TestCase(1)]
[TestCase(0.5)]
[TestCase(-1)]