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

258 lines
11 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 NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Algorithm;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
namespace QuantConnect.Tests.Algorithm
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class AlgorithmSetHoldingsTests
{
// Test class to enable calling protected methods
public class TestSecurityMarginModel : SecurityMarginModel
{
public TestSecurityMarginModel(decimal leverage) : base(leverage) { }
public new decimal GetInitialMarginRequiredForOrder(
InitialMarginRequiredForOrderParameters parameters)
{
return base.GetInitialMarginRequiredForOrder(parameters).Value;
}
public new decimal GetMarginRemaining(SecurityPortfolioManager portfolio, Security security, OrderDirection direction)
{
return base.GetMarginRemaining(portfolio, security, direction);
}
}
public enum Position { Zero = 0, Long = 1, Short = -1 }
public enum FeeType { None, Small, Large, InteractiveBrokers }
public enum PriceMovement { Static, RisingSmall, FallingSmall, RisingLarge, FallingLarge }
private readonly Dictionary<FeeType, IFeeModel> _feeModels = new Dictionary<FeeType, IFeeModel>
{
{ FeeType.None, new ConstantFeeModel(0) },
{ FeeType.Small, new ConstantFeeModel(1) },
{ FeeType.Large, new ConstantFeeModel(100) },
{ FeeType.InteractiveBrokers, new InteractiveBrokersFeeModel() }
};
private readonly Symbol _symbol = Symbols.SPY;
private const decimal Cash = 100000m;
private const decimal VeryLowPrice = 155m;
private const decimal LowPrice = 159m;
private const decimal BasePrice = 160m;
private const decimal HighPrice = 161m;
private const decimal VeryHighPrice = 165m;
public class Permuter<T>
{
private static void Permute(T[] row, int index, IReadOnlyList<List<T>> data, ICollection<T[]> result)
{
foreach (var dataRow in data[index])
{
row[index] = dataRow;
if (index >= data.Count - 1)
{
var rowCopy = new T[row.Length];
row.CopyTo(rowCopy, 0);
result.Add(rowCopy);
}
else
{
Permute(row, index + 1, data, result);
}
}
}
public static void Permute(List<List<T>> data, List<T[]> result)
{
if (data.Count == 0)
return;
Permute(new T[data.Count], 0, data, result);
}
}
private static IEnumerable<object[]> TestParameters
{
get
{
var initialPositions = Enum.GetValues(typeof(Position)).Cast<Position>().ToList();
var finalPositions = Enum.GetValues(typeof(Position)).Cast<Position>().ToList();
var feeTypes = Enum.GetValues(typeof(FeeType)).Cast<FeeType>().ToList();
var priceMovements = Enum.GetValues(typeof(PriceMovement)).Cast<PriceMovement>().ToList();
var leverages = new List<int> { 1, 100 };
var data = new List<List<object>>
{
initialPositions.Cast<object>().ToList(),
finalPositions.Cast<object>().ToList(),
feeTypes.Cast<object>().ToList(),
priceMovements.Cast<object>().ToList(),
leverages.Cast<object>().ToList()
};
var permutations = new List<object[]>();
Permuter<object>.Permute(data, permutations);
var ret = permutations
.Where(row => (Position)row[0] != (Position)row[1]); // initialPosition != finalPosition
return ret;
}
}
[Test, TestCaseSource(nameof(TestParameters))]
public void Run(object[] parameters)
{
Position initialPosition = (Position)parameters[0];
Position finalPosition = (Position)parameters[1];
FeeType feeType = (FeeType)parameters[2];
PriceMovement priceMovement = (PriceMovement)parameters[3];
int leverage = (int)parameters[4];
//Console.WriteLine("----------");
//Console.WriteLine("PARAMETERS");
//Console.WriteLine("Initial position: " + initialPosition);
//Console.WriteLine("Final position: " + finalPosition);
//Console.WriteLine("Fee type: " + feeType);
//Console.WriteLine("Price movement: " + priceMovement);
//Console.WriteLine("Leverage: " + leverage);
//Console.WriteLine("----------");
//Console.WriteLine();
var algorithm = new QCAlgorithm();
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
var security = algorithm.AddSecurity(_symbol.ID.SecurityType, _symbol.ID.Symbol);
security.FeeModel = _feeModels[feeType];
security.SetLeverage(leverage);
var buyingPowerModel = new TestSecurityMarginModel(leverage);
security.BuyingPowerModel = buyingPowerModel;
algorithm.SetCash(Cash);
Update(security, BasePrice);
decimal targetPercentage;
OrderDirection orderDirection;
MarketOrder order;
OrderFee orderFee;
OrderEvent fill;
decimal orderQuantity;
decimal freeMargin;
decimal requiredMargin;
if (initialPosition != Position.Zero)
{
targetPercentage = (decimal)initialPosition;
orderDirection = initialPosition == Position.Long ? OrderDirection.Buy : OrderDirection.Sell;
orderQuantity = algorithm.CalculateOrderQuantity(_symbol, targetPercentage);
order = new MarketOrder(_symbol, orderQuantity, DateTime.UtcNow);
freeMargin = buyingPowerModel.GetMarginRemaining(algorithm.Portfolio, security, orderDirection);
requiredMargin = buyingPowerModel.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(
new IdentityCurrencyConverter(algorithm.Portfolio.CashBook.AccountCurrency), security, order));
//Console.WriteLine("Current price: " + security.Price);
//Console.WriteLine("Target percentage: " + targetPercentage);
//Console.WriteLine("Order direction: " + orderDirection);
//Console.WriteLine("Order quantity: " + orderQuantity);
//Console.WriteLine("Free margin: " + freeMargin);
//Console.WriteLine("Required margin: " + requiredMargin);
//Console.WriteLine();
Assert.That(Math.Abs(requiredMargin) <= freeMargin);
orderFee = security.FeeModel.GetOrderFee(
new OrderFeeParameters(security, order));
fill = new OrderEvent(order, DateTime.UtcNow, orderFee) { FillPrice = security.Price, FillQuantity = orderQuantity };
algorithm.Portfolio.ProcessFills(new List<OrderEvent> { fill });
//Console.WriteLine("Portfolio.Cash: " + algorithm.Portfolio.Cash);
//Console.WriteLine("Portfolio.TotalPortfolioValue: " + algorithm.Portfolio.TotalPortfolioValue);
//Console.WriteLine();
if (priceMovement == PriceMovement.RisingSmall)
{
Update(security, HighPrice);
}
else if (priceMovement == PriceMovement.FallingSmall)
{
Update(security, LowPrice);
}
else if (priceMovement == PriceMovement.RisingLarge)
{
Update(security, VeryHighPrice);
}
else if (priceMovement == PriceMovement.FallingLarge)
{
Update(security, VeryLowPrice);
}
}
targetPercentage = (decimal)finalPosition;
orderDirection = finalPosition == Position.Long || (finalPosition == Position.Zero && initialPosition == Position.Short) ? OrderDirection.Buy : OrderDirection.Sell;
orderQuantity = algorithm.CalculateOrderQuantity(_symbol, targetPercentage);
order = new MarketOrder(_symbol, orderQuantity, DateTime.UtcNow);
freeMargin = buyingPowerModel.GetMarginRemaining(algorithm.Portfolio, security, orderDirection);
requiredMargin = buyingPowerModel.GetInitialMarginRequiredForOrder(
new InitialMarginRequiredForOrderParameters(
new IdentityCurrencyConverter(algorithm.Portfolio.CashBook.AccountCurrency), security, order));
//Console.WriteLine("Current price: " + security.Price);
//Console.WriteLine("Target percentage: " + targetPercentage);
//Console.WriteLine("Order direction: " + orderDirection);
//Console.WriteLine("Order quantity: " + orderQuantity);
//Console.WriteLine("Free margin: " + freeMargin);
//Console.WriteLine("Required margin: " + requiredMargin);
//Console.WriteLine();
Assert.That(Math.Abs(requiredMargin) <= freeMargin);
orderFee = security.FeeModel.GetOrderFee(
new OrderFeeParameters(security, order));
fill = new OrderEvent(order, DateTime.UtcNow, orderFee) { FillPrice = security.Price, FillQuantity = orderQuantity };
algorithm.Portfolio.ProcessFills(new List<OrderEvent> { fill });
//Console.WriteLine("Portfolio.Cash: " + algorithm.Portfolio.Cash);
//Console.WriteLine("Portfolio.TotalPortfolioValue: " + algorithm.Portfolio.TotalPortfolioValue);
//Console.WriteLine();
}
private static void Update(Security security, decimal price)
{
security.SetMarketPrice(new TradeBar
{
Time = DateTime.Now,
Symbol = security.Symbol,
Open = price,
High = price,
Low = price,
Close = price
});
}
}
}