Files
quantconnect--lean/Tests/Common/OrderTargetsByMarginImpactTests.cs
2026-07-13 13:02:50 +08:00

231 lines
8.6 KiB
C#

/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Tests.Common.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
namespace QuantConnect.Tests.Common
{
public class OrderTargetsByMarginImpactTests
{
[TestCase(false)]
[TestCase(true)]
public void LessThanLotSizeIsIgnored_NoHoldings(bool targetIsDelta)
{
var algorithm = GetAlgorithm();
var collection = new[] {new PortfolioTarget(Symbols.AAPL, 0.9m)};
var result = collection.OrderTargetsByMarginImpact(algorithm, targetIsDelta).ToList();
Assert.AreEqual(0, result.Count);
}
[TestCase(false)]
[TestCase(true)]
public void LessThanLotSizeIsIgnored_WithHoldings(bool targetIsDelta)
{
var algorithm = GetAlgorithm(holdings:1m);
var collection = new[] { new PortfolioTarget(Symbols.AAPL, 1.9m) };
var result = collection.OrderTargetsByMarginImpact(algorithm, targetIsDelta).ToList();
if (targetIsDelta)
{
Assert.AreEqual(1, result.Count);
Assert.AreEqual(1.9m, result[0].Quantity);
}
else
{
Assert.AreEqual(0, result.Count);
}
}
[Test]
public void SecurityWithNoDataIsIgnored()
{
var algorithm = GetAlgorithm();
// SPY won't have any data and should be ignored
algorithm.AddEquity(Symbols.SPY.Value);
var collection = new[] {new PortfolioTarget(Symbols.SPY, 5000m),
new PortfolioTarget(Symbols.AAPL, 1m)};
var result = collection.OrderTargetsByMarginImpact(algorithm).ToList();
Assert.AreEqual(1, result.Count);
Assert.AreEqual(1m, result[0].Quantity);
}
[Test]
public void NoExistingHoldings()
{
var algorithm = GetAlgorithm();
var spy = algorithm.AddEquity(Symbols.SPY.Value);
Update(spy, 1);
var collection = new[] {new PortfolioTarget(Symbols.SPY, 5m),
new PortfolioTarget(Symbols.AAPL, 1m)};
var result = collection.OrderTargetsByMarginImpact(algorithm).ToList();
Assert.AreEqual(2, result.Count);
// highest order value first
Assert.AreEqual(5m, result[0].Quantity);
Assert.AreEqual(1m, result[1].Quantity);
}
[TestCase(OrderDirection.Buy, false)]
[TestCase(OrderDirection.Sell, false)]
[TestCase(OrderDirection.Buy, true)]
[TestCase(OrderDirection.Sell, true)]
public void ReducingPosition(OrderDirection direction, bool targetIsDelta)
{
var algorithm = GetAlgorithm(direction == OrderDirection.Sell ? 2 : -2);
var spy = algorithm.AddEquity(Symbols.SPY.Value);
Update(spy, 1);
var target = direction == OrderDirection.Sell ? -1 : 1;
var collection = new[] {new PortfolioTarget(Symbols.SPY, 5m),
new PortfolioTarget(Symbols.AAPL, target)};
var result = collection.OrderTargetsByMarginImpact(algorithm, targetIsDelta).ToList();
Assert.AreEqual(2, result.Count);
// target reducing the position first
Assert.AreEqual(target, result[0].Quantity);
Assert.AreEqual(5m, result[1].Quantity);
}
[TestCase(OrderDirection.Buy, false)]
[TestCase(OrderDirection.Sell, false)]
[TestCase(OrderDirection.Buy, true)]
[TestCase(OrderDirection.Sell, true)]
public void ReducingPositionDeltaEffect(OrderDirection direction, bool targetIsDelta)
{
var algorithm = GetAlgorithm(direction == OrderDirection.Sell ? 2 : -2);
var spy = algorithm.AddEquity(Symbols.SPY.Value);
Update(spy, 1);
var target = direction == OrderDirection.Sell ? -2.5m : 2.5m;
var collection = new[] {new PortfolioTarget(Symbols.SPY, 5m),
new PortfolioTarget(Symbols.AAPL, target)};
var result = collection.OrderTargetsByMarginImpact(algorithm, targetIsDelta).ToList();
Assert.AreEqual(2, result.Count);
if (targetIsDelta)
{
// target reducing the position first
Assert.AreEqual(target, result[0].Quantity);
Assert.AreEqual(5m, result[1].Quantity);
}
else
{
Assert.AreEqual(5m, result[0].Quantity);
Assert.AreEqual(target, result[1].Quantity);
}
}
[TestCase(OrderDirection.Buy, false)]
[TestCase(OrderDirection.Sell, false)]
[TestCase(OrderDirection.Buy, true)]
[TestCase(OrderDirection.Sell, true)]
public void IncreasePosition(OrderDirection direction, bool targetIsDelta)
{
var value = direction == OrderDirection.Sell ? -1 : 1;
var algorithm = GetAlgorithm(value);
var spy = algorithm.AddEquity(Symbols.SPY.Value);
Update(spy, 1);
var collection = new[] {new PortfolioTarget(Symbols.SPY, 20m),
new PortfolioTarget(Symbols.AAPL, value * 21m)};
var result = collection.OrderTargetsByMarginImpact(algorithm, targetIsDelta).ToList();
Assert.AreEqual(2, result.Count);
if (targetIsDelta)
{
// AAPL is increasing the position by 21
Assert.AreEqual(Symbols.AAPL, result[0].Symbol);
Assert.AreEqual(Symbols.SPY, result[1].Symbol);
}
else
{
Assert.AreEqual(Symbols.SPY, result[0].Symbol);
// target with the least order value, AAPL is increasing the position by 11
Assert.AreEqual(Symbols.AAPL, result[1].Symbol);
}
}
[TestCase(OrderDirection.Buy, false)]
[TestCase(OrderDirection.Sell, false)]
[TestCase(OrderDirection.Buy, true)]
[TestCase(OrderDirection.Sell, true)]
public void RoundQuantityInOrderTargets(OrderDirection direction, bool targetIsDelta)
{
var value = direction == OrderDirection.Sell ? -1 : 1;
var algorithm = GetAlgorithm(value);
var spy = algorithm.AddEquity(Symbols.SPY.Value);
Update(spy, 1);
var collection = new[] {new PortfolioTarget(Symbols.SPY, 20m),
new PortfolioTarget(Symbols.AAPL, value * 20.1m)};
var result = collection.OrderTargetsByMarginImpact(algorithm, targetIsDelta).ToList();
Assert.AreEqual(2, result.Count);
// Since the order value is the same SPY comes first because of its the first in collection.
Assert.AreEqual(Symbols.SPY, result[0].Symbol);
Assert.AreEqual(Symbols.AAPL, result[1].Symbol);
}
private static void Update(Security security, decimal close)
{
security.SetMarketPrice(new TradeBar
{
Time = DateTime.Now,
Symbol = security.Symbol,
Open = close,
High = close,
Low = close,
Close = close
});
}
private QCAlgorithm GetAlgorithm(decimal? holdings = null)
{
var algorithm = new AlgorithmStub();
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
var aapl = algorithm.AddEquity(Symbols.AAPL.Value);
Update(aapl, 1);
if (holdings != null)
{
aapl.Holdings.SetHoldings(10, holdings.Value);
}
algorithm.SetFinishedWarmingUp();
return algorithm;
}
}
}