Files
quantconnect--lean/Tests/Engine/Setup/BrokerageSetupHandlerTests.cs
T
2026-07-13 13:02:50 +08:00

1014 lines
49 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.RealTime;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Lean.Engine.Setup;
using QuantConnect.Lean.Engine.TransactionHandlers;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Securities.Option.StrategyMatcher;
using QuantConnect.Securities.Option;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util;
using Bitcoin = QuantConnect.Algorithm.CSharp.LiveTradingFeaturesAlgorithm.Bitcoin;
using System.Collections;
using QuantConnect.Configuration;
using NodaTime;
using QuantConnect.Data.Market;
using QuantConnect.Data;
namespace QuantConnect.Tests.Engine.Setup
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class BrokerageSetupHandlerTests
{
private IAlgorithm _algorithm;
private ITransactionHandler _transactionHandler;
private NonDequeingTestResultsHandler _resultHandler;
private IBrokerage _brokerage;
private DataManager _dataManager;
private TestableBrokerageSetupHandler _brokerageSetupHandler;
[OneTimeSetUp]
public void Setup()
{
_algorithm = new QCAlgorithm();
_dataManager = new DataManagerStub(_algorithm);
_algorithm.SubscriptionManager.SetDataManager(_dataManager);
_transactionHandler = new BrokerageTransactionHandler();
_resultHandler = new NonDequeingTestResultsHandler();
_brokerage = new TestBrokerage();
_brokerageSetupHandler = new TestableBrokerageSetupHandler();
_transactionHandler.Initialize(_algorithm, _brokerage, _resultHandler);
}
[OneTimeTearDown]
public void TearDown()
{
_dataManager.RemoveAllSubscriptions();
_brokerage.DisposeSafely();
_transactionHandler.Exit();
_resultHandler.Exit();
}
[Test]
public void CanGetOpenOrders()
{
_brokerageSetupHandler.PublicGetOpenOrders(_algorithm, _resultHandler, _transactionHandler, _brokerage);
Assert.AreEqual(_transactionHandler.Orders.Count, 4);
Assert.AreEqual(_transactionHandler.OrderTickets.Count, 4);
// Check Price Currency is not null
foreach (var order in _transactionHandler.Orders.Values)
{
Assert.IsFalse(string.IsNullOrEmpty(order.PriceCurrency));
Assert.AreEqual(OrderStatus.Submitted, order.Status);
}
// Warn the user about each open order
Assert.AreEqual(_resultHandler.PersistentMessages.Count, 4);
// Market order
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.Market).Value.Quantity, 100);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.Market).Value.SubmitRequest.LimitPrice, 1.2345m);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.Market).Value.SubmitRequest.StopPrice, 1.2345m);
// Limit Order
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.Limit).Value.Quantity, -100);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.Limit).Value.SubmitRequest.LimitPrice, 2.2345m);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.Limit).Value.SubmitRequest.StopPrice, 0m);
// Stop market order
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.StopMarket).Value.Quantity, 100);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.StopMarket).Value.SubmitRequest.LimitPrice, 0m);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.StopMarket).Value.SubmitRequest.StopPrice, 2.2345m);
// Stop Limit order
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.StopLimit).Value.Quantity, 100);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.StopLimit).Value.SubmitRequest.LimitPrice, 0.2345m);
Assert.AreEqual(_transactionHandler.OrderTickets.First(x => x.Value.OrderType == OrderType.StopLimit).Value.SubmitRequest.StopPrice, 2.2345m);
// SPY security should be added to the algorithm
Assert.Contains(Symbols.SPY, _algorithm.Securities.Select(x => x.Key).ToList());
}
[TestCaseSource(typeof(ExistingHoldingAndOrdersDataClass), nameof(ExistingHoldingAndOrdersDataClass.GetExistingHoldingsAndOrdersTestCaseData))]
public void SecondExistingHoldingsAndOrdersResolution(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
{
ExistingHoldingsAndOrdersResolution(getHoldings, getOrders, expected, Resolution.Second);
}
[TestCaseSource(typeof(ExistingHoldingAndOrdersDataClass), nameof(ExistingHoldingAndOrdersDataClass.GetExistingHoldingsAndOrdersTestCaseData))]
public void MinuteExistingHoldingsAndOrdersResolution(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
{
ExistingHoldingsAndOrdersResolution(getHoldings, getOrders, expected, Resolution.Minute);
}
[TestCaseSource(typeof(ExistingHoldingAndOrdersDataClass), nameof(ExistingHoldingAndOrdersDataClass.GetExistingHoldingsAndOrdersTestCaseData))]
public void TickExistingHoldingsAndOrdersResolution(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
{
ExistingHoldingsAndOrdersResolution(getHoldings, getOrders, expected, Resolution.Tick);
}
public void ExistingHoldingsAndOrdersResolution(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected, Resolution resolution)
{
var algorithm = new TestAlgorithm { UniverseSettings = { Resolution = resolution } };
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var objectStore = new Mock<IObjectStore>();
var brokerage = new Mock<IBrokerage>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(getHoldings);
brokerage.Setup(x => x.GetOpenOrders()).Returns(getOrders);
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
var result = setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider));
Assert.AreEqual(expected, result);
foreach (var symbol in algorithm.Securities.Keys)
{
var configs = algorithm.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol);
Assert.AreEqual(algorithm.UniverseSettings.Resolution, configs.First().Resolution);
}
}
[TestCaseSource(typeof(ExistingHoldingAndOrdersDataClass), nameof(ExistingHoldingAndOrdersDataClass.GetExistingHoldingsAndOrdersTestCaseData))]
public void ExistingHoldingsAndOrdersUniverseSettings(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
{
// Set our universe settings
var hasCrypto = false;
try
{
hasCrypto = getHoldings().Any(x => x.Symbol.Value == "BTCUSD");
}
catch
{
}
var algorithm = new TestAlgorithm { UniverseSettings = { Resolution = Resolution.Daily, Leverage = (hasCrypto ? 1 : 20), FillForward = false, ExtendedMarketHours = true } };
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var objectStore = new Mock<IObjectStore>();
var brokerage = new Mock<IBrokerage>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(getHoldings);
brokerage.Setup(x => x.GetOpenOrders()).Returns(getOrders);
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
var result = setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider));
if (result != expected)
{
Assert.Fail("SetupHandler result did not match expected value");
}
foreach (var symbol in algorithm.Securities.Keys)
{
var config = algorithm.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).First();
// Assert Resolution and FillForward settings persisted
Assert.AreEqual(algorithm.UniverseSettings.Resolution, config.Resolution);
Assert.AreEqual(algorithm.UniverseSettings.FillForward, config.FillDataForward);
// Assert ExtendedHours setting persisted for equities
if (config.SecurityType == SecurityType.Equity)
{
Assert.AreEqual(algorithm.UniverseSettings.ExtendedMarketHours, config.ExtendedMarketHours);
}
// Assert Leverage setting persisted for non options or futures (Blocked from setting leverage in Security.SetLeverage())
if (!symbol.SecurityType.IsOption() && symbol.SecurityType != SecurityType.Future)
{
var security = algorithm.Securities[symbol];
Assert.AreEqual(algorithm.UniverseSettings.Leverage, security.Leverage);
}
}
}
[TestCaseSource(typeof(ExistingHoldingAndOrdersDataClass), nameof(ExistingHoldingAndOrdersDataClass.GetExistingHoldingsAndOrdersTestCaseData))]
public void LoadsExistingHoldingsAndOrders(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
{
var algorithm = new TestAlgorithm();
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
TestLoadExistingHoldingsAndOrders(algorithm, getHoldings, getOrders, expected);
foreach (var security in algorithm.Securities.Values)
{
if (security.Symbol.SecurityType == SecurityType.Option)
{
Assert.AreEqual(DataNormalizationMode.Raw, security.DataNormalizationMode);
var underlyingSecurity = algorithm.Securities[security.Symbol.Underlying];
Assert.AreEqual(DataNormalizationMode.Raw, underlyingSecurity.DataNormalizationMode);
}
}
}
[TestCaseSource(nameof(GetExistingHoldingsAndOrdersWithCustomDataTestCase))]
public void LoadsExistingHoldingsAndOrdersWithCustomData(Func<List<Holding>> getHoldings, Func<List<Order>> getOrders)
{
var algorithm = new TestAlgorithm();
algorithm.AddData<Bitcoin>("BTC");
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
TestLoadExistingHoldingsAndOrders(algorithm, getHoldings, getOrders, true);
}
[TestCase(true)]
[TestCase(false)]
public void EnforcesTotalPortfolioValue(bool fails)
{
var algorithm = new TestAlgorithm();
algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
algorithm.SetHistoryProvider(new TestHistoryProvider());
var job = GetJob();
job.BrokerageData[BrokerageSetupHandler.MaxAllocationLimitConfig] = fails ? "1" : "1000000000";
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount> { new CashAmount(10000, Currencies.USD), new CashAmount(11, Currencies.GBP) });
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>());
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.AreEqual(!fails, setupHandler.Setup(new SetupHandlerParameters(algorithm.DataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
Assert.AreEqual(10000, algorithm.Portfolio.CashBook[Currencies.USD].Amount);
Assert.AreEqual(11, algorithm.Portfolio.CashBook[Currencies.GBP].Amount);
}
[TestCase(true)]
[TestCase(false)]
public void EnforcesAccountCurrency(bool enforceAccountCurrency)
{
var algorithm = new TestAlgorithm();
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
if (enforceAccountCurrency)
{
job.BrokerageData[BrokerageSetupHandler.MaxAllocationLimitConfig] = "200000";
}
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>());
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.EUR);
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.IsTrue(setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
Assert.AreEqual(enforceAccountCurrency ? Currencies.USD : Currencies.EUR, algorithm.AccountCurrency);
}
[Test]
public void HandlesErrorOnInitializeCorrectly()
{
var algorithm = new BacktestingSetupHandlerTests.TestAlgorithmThrowsOnInitialize();
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>());
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.IsFalse(setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
setupHandler.DisposeSafely();
Assert.AreEqual(1, setupHandler.Errors.Count);
Assert.IsTrue(setupHandler.Errors[0].InnerException.Message.Equals("Some failure"));
}
[Test]
public void HoldingsPositionGroupResolved()
{
var algorithm = new TestAlgorithm();
algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>
{
// covered call
new Holding { Symbol = Symbols.SPY_C_192_Feb19_2016, Quantity = -1 },
new Holding { Symbol = Symbols.SPY, Quantity = 100 }
});
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.IsTrue(setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
// let's assert be detect the covered call option strategy for existing position correctly
if (algorithm.Portfolio.Positions.Groups.Where(group => group.BuyingPowerModel is OptionStrategyPositionGroupBuyingPowerModel)
.Count(group => ((OptionStrategyPositionGroupBuyingPowerModel)@group.BuyingPowerModel).ToString() == OptionStrategyDefinitions.CoveredCall.Name
&& (Math.Abs(group.Quantity) == 1)) != 1)
{
factory.Dispose();
throw new RegressionTestException($"Option strategy: '{OptionStrategyDefinitions.CoveredCall.Name}' was not found!");
}
}
[Test]
public void LoadsHoldingsForExpectedMarket()
{
var symbol = Symbol.Create("AUDUSD", SecurityType.Forex, Market.Oanda);
var algorithm = new TestAlgorithm();
algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>
{
new Holding { Symbol = symbol, Quantity = 100 }
});
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.IsTrue(setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
Security security;
Assert.IsTrue(algorithm.Portfolio.Securities.TryGetValue(symbol, out security));
Assert.AreEqual(symbol, security.Symbol);
}
[Test]
public void SeedsSecurityCorrectly()
{
var symbol = Symbol.Create("AUDUSD", SecurityType.Forex, Market.Oanda);
var algorithm = new TestAlgorithm();
algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>
{
new Holding { Symbol = symbol, Quantity = 100, MarketPrice = 99}
});
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.IsTrue(setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
Security security;
Assert.IsTrue(algorithm.Portfolio.Securities.TryGetValue(symbol, out security));
Assert.AreEqual(symbol, security.Symbol);
Assert.AreEqual(99, security.Price);
var last = security.GetLastData();
Assert.IsTrue((DateTime.UtcNow.ConvertFromUtc(security.Exchange.TimeZone) - last.Time) < TimeSpan.FromSeconds(1));
}
[Test]
public void AlgorithmTimeIsSetToUtcNowBeforePostInitialize()
{
var time = DateTime.UtcNow;
TestAlgorithm algorithm = null;
algorithm = new TestAlgorithm(() =>
{
Assert.That(algorithm.UtcTime > time);
});
Assert.AreEqual(new DateTime(1998, 1, 1), algorithm.UtcTime);
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>());
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
Assert.IsTrue(setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
Assert.Greater(algorithm.UtcTime, time);
}
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void HasErrorWithZeroTotalPortfolioValue(bool hasCashBalance, bool hasHoldings)
{
var algorithm = new TestAlgorithm();
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
job.Brokerage = "TestBrokerage";
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
var objectStore = new Mock<IObjectStore>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(
hasCashBalance
? new List<CashAmount>
{
new CashAmount(1000, "USD")
}
: new List<CashAmount>()
);
brokerage.Setup(x => x.GetAccountHoldings()).Returns(
hasHoldings
? new List<Holding>
{
new Holding { Symbol = Symbols.SPY, Quantity = 1, AveragePrice = 100, MarketPrice = 100 }
}
: new List<Holding>());
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
var dataManager = new DataManagerStub(algorithm, new MockDataFeed(), true);
Assert.IsTrue(setupHandler.Setup(new SetupHandlerParameters(dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider)));
if (!hasCashBalance && !hasHoldings)
{
Assert.IsFalse(algorithm.DebugMessages.IsEmpty);
Assert.That(algorithm.DebugMessages.Any(x => x.Contains("No cash balances or holdings were found in the brokerage account.")));
}
}
[Test]
public void ZeroQuantityCurrenciesAreNotAddedToCashBook()
{
var algorithm = new TestAlgorithm();
algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
algorithm.SetHistoryProvider(new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.EmptyHistoryProvider());
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var brokerage = new Mock<IBrokerage>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
// EUR with zero quantity, should NOT be added to CashBook
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>
{
new CashAmount(0, "USD"),
new CashAmount(0, "EUR"),
new CashAmount(0, "BNFCR"),
new CashAmount(123, "ETH")
});
brokerage.Setup(x => x.GetAccountHoldings()).Returns(new List<Holding>());
brokerage.Setup(x => x.GetOpenOrders()).Returns(new List<Order>());
using var setupHandler = new BrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
var result = setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider));
Assert.IsTrue(result);
// USD should be present even though it has zero quantity because it's the account currency
Assert.IsTrue(algorithm.Portfolio.CashBook.ContainsKey("USD"));
// EUR should NOT be present (zero amount)
Assert.IsFalse(algorithm.Portfolio.CashBook.ContainsKey("EUR"));
// ETH should be present
Assert.IsTrue(algorithm.Portfolio.CashBook.ContainsKey("ETH"));
// special case used in binance future fees
Assert.IsTrue(algorithm.Portfolio.CashBook.ContainsKey("BNFCR"));
}
private void TestLoadExistingHoldingsAndOrders(IAlgorithm algorithm, Func<List<Holding>> getHoldings, Func<List<Order>> getOrders, bool expected)
{
var job = GetJob();
var resultHandler = new Mock<IResultHandler>();
var transactionHandler = new Mock<ITransactionHandler>();
var realTimeHandler = new Mock<IRealTimeHandler>();
var objectStore = new Mock<IObjectStore>();
var brokerage = new Mock<IBrokerage>();
brokerage.Setup(x => x.IsConnected).Returns(true);
brokerage.Setup(x => x.AccountBaseCurrency).Returns(Currencies.USD);
brokerage.Setup(x => x.GetCashBalance()).Returns(new List<CashAmount>());
brokerage.Setup(x => x.GetAccountHoldings()).Returns(getHoldings);
brokerage.Setup(x => x.GetOpenOrders()).Returns(getOrders);
using var setupHandler = new TestableBrokerageSetupHandler();
IBrokerageFactory factory;
setupHandler.CreateBrokerage(job, algorithm, out factory);
factory.Dispose();
var parameters = new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider);
var result = setupHandler.Setup(new SetupHandlerParameters(_dataManager.UniverseSelection, algorithm, brokerage.Object, job, resultHandler.Object,
transactionHandler.Object, realTimeHandler.Object, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider));
Assert.AreEqual(expected, result);
}
private static object[] GetExistingHoldingsAndOrdersWithCustomDataTestCase =
{
new object[] {
new Func<List<Holding>>(() => new List<Holding> { new Holding { Symbol = new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), Quantity = 1 }}),
new Func<List<Order>>(() => new List<Order>())},
new object[] {
new Func<List<Holding>>(() => new List<Holding> { new Holding { Symbol = Symbols.SPY, Quantity = 1 }}),
new Func<List<Order>>(() => new List<Order>())},
new object[] {
new Func<List<Holding>>(() => new List<Holding>()),
new Func<List<Order>>(() => new List<Order>() { new LimitOrder(new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), 1, 1, DateTime.UtcNow)})},
new object[] {
new Func<List<Holding>>(() => new List<Holding> { new Holding { Symbol = new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), Quantity = 1 }}),
new Func<List<Order>>(() => new List<Order>() { new LimitOrder(new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), 1, 1, DateTime.UtcNow)})},
new object[] {
new Func<List<Holding>>(() => new List<Holding> { new Holding { Symbol = new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), Quantity = 1 },
new Holding { Symbol = Symbols.SPY, Quantity = 1 }}),
new Func<List<Order>>(() => new List<Order>() { new LimitOrder(new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), 1, 1, DateTime.UtcNow)})},
new object[] {
new Func<List<Holding>>(() => new List<Holding> { new Holding { Symbol = new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), Quantity = 1 },
new Holding { Symbol = Symbols.SPY, Quantity = 1 }}),
new Func<List<Order>>(() => new List<Order>() { new LimitOrder(new Symbol(
SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), 1, 1, DateTime.UtcNow),
new LimitOrder(Symbols.SPY, 1, 1, DateTime.UtcNow)})}
};
private class ExistingHoldingAndOrdersDataClass
{
public static IEnumerable GetExistingHoldingsAndOrdersTestCaseData
{
get
{
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>()),
new Func<List<Order>>(() => new List<Order>()), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.SPY, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.SPY, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.SPY_C_192_Feb19_2016, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.SPY_C_192_Feb19_2016, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.SPY, Quantity = 1 },
new Holding { Symbol = Symbols.SPY_C_192_Feb19_2016, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.SPY, 1, 1, DateTime.UtcNow),
new LimitOrder(Symbols.SPY_C_192_Feb19_2016, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.SPY_C_192_Feb19_2016, Quantity = 1 },
new Holding { Symbol = Symbols.SPY, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.SPY_C_192_Feb19_2016, 1, 1, DateTime.UtcNow),
new LimitOrder(Symbols.SPY, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.SPY_C_192_Feb19_2016, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.SPY, 1, 1, DateTime.UtcNow),
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.EURUSD, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.EURUSD, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.BTCUSD, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.BTCUSD, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbols.Fut_SPY_Feb19_2016, Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(Symbols.Fut_SPY_Feb19_2016, 1, 1, DateTime.UtcNow)
}), true);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = Symbol.Create("XYZ", SecurityType.Base, Market.USA), Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
#pragma warning disable CS0618
new LimitOrder("XYZ", 1, 1, DateTime.UtcNow)
#pragma warning restore CS0618
}), false);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>
{
new Holding { Symbol = new Symbol(SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), Quantity = 1 }
}),
new Func<List<Order>>(() => new List<Order>
{
new LimitOrder(new Symbol(SecurityIdentifier.GenerateBase(typeof(Bitcoin), "BTC", Market.USA, false), "BTC"), 1, 1, DateTime.UtcNow)
}), false);
yield return new TestCaseData(
new Func<List<Holding>>(() => { throw new RegressionTestException(); }),
new Func<List<Order>>(() => new List<Order>()), false);
yield return new TestCaseData(
new Func<List<Holding>>(() => new List<Holding>()),
new Func<List<Order>>(() => { throw new RegressionTestException(); }), false);
}
}
}
internal class TestAlgorithm : QCAlgorithm
{
private readonly Action _beforePostInitializeAction;
public DataManager DataManager { get; set; }
public TestAlgorithm(Action beforePostInitializeAction = null)
{
_beforePostInitializeAction = beforePostInitializeAction;
DataManager = new DataManagerStub(this, new MockDataFeed(), liveMode: true);
SubscriptionManager.SetDataManager(DataManager);
}
public override void Initialize() { }
public override void PostInitialize()
{
_beforePostInitializeAction?.Invoke();
base.PostInitialize();
}
}
internal static LiveNodePacket GetJob()
{
var job = new LiveNodePacket
{
UserId = 1,
ProjectId = 1,
DeployId = "1",
Brokerage = "PaperBrokerage",
DataQueueHandler = "none"
};
// Increasing RAM limit, else the tests fail. This is happening in master, when running all the tests together, locally (not travis).
job.Controls.RamAllocation = 1024 * 1024 * 1024;
return job;
}
private class NonDequeingTestResultsHandler : TestResultHandler
{
private readonly AlgorithmNodePacket _job = new BacktestNodePacket();
public readonly ConcurrentQueue<Packet> PersistentMessages = new ConcurrentQueue<Packet>();
public override void DebugMessage(string message)
{
PersistentMessages.Enqueue(new DebugPacket(_job.ProjectId, _job.AlgorithmId, _job.CompileId, message));
}
}
private class TestableBrokerageSetupHandler : BrokerageSetupHandler
{
public void PublicGetOpenOrders(IAlgorithm algorithm, IResultHandler resultHandler, ITransactionHandler transactionHandler, IBrokerage brokerage)
{
GetOpenOrders(algorithm, resultHandler, transactionHandler, brokerage);
}
public bool TestLoadExistingHoldingsAndOrders(IBrokerage brokerage, IAlgorithm algorithm, SetupHandlerParameters parameters)
{
return LoadExistingHoldingsAndOrders(brokerage, algorithm, parameters);
}
}
private class TestHistoryProvider : HistoryProviderBase
{
public override int DataPointCount { get; }
public override void Initialize(HistoryProviderInitializeParameters parameters)
{
throw new NotImplementedException();
}
public override IEnumerable<Slice> GetHistory(IEnumerable<Data.HistoryRequest> requests, DateTimeZone sliceTimeZone)
{
var requestsList = requests.ToList();
if (requestsList.Count == 0)
{
return Enumerable.Empty<Slice>();
}
var request = requestsList[0];
return new List<Slice>{ new Slice(DateTime.UtcNow,
new List<BaseData> {new QuoteBar(DateTime.MinValue, request.Symbol, new Bar(1, 2, 3, 4), 5, new Bar(1, 2, 3, 4), 5) }, DateTime.UtcNow)};
}
}
}
internal class TestBrokerageFactory : BrokerageFactory
{
public TestBrokerageFactory() : base(typeof(TestBrokerage))
{
}
public override Dictionary<string, string> BrokerageData => new Dictionary<string, string>();
public override IBrokerageModel GetBrokerageModel(IOrderProvider orderProvider) => new BrokerageTransactionHandlerTests.BrokerageTransactionHandlerTests.TestBrokerageModel();
public override IBrokerage CreateBrokerage(LiveNodePacket job, IAlgorithm algorithm) => new TestBrokerage();
public override void Dispose() { }
}
/// <summary>
/// Public so that mock can access it
/// </summary>
public class TestBrokerage : Brokerage
{
public override bool IsConnected { get; } = true;
public int GetCashBalanceCallCount { get; set; }
public TestBrokerage() : base("Test")
{
}
public TestBrokerage(string name) : base(name)
{
}
public override List<Order> GetOpenOrders()
{
const decimal delta = 1m;
const decimal price = 1.2345m;
const int quantity = 100;
const decimal pricePlusDelta = price + delta;
const decimal priceMinusDelta = price - delta;
var tz = TimeZones.NewYork;
var time = new DateTime(2016, 2, 4, 16, 0, 0).ConvertToUtc(tz);
var marketOrderWithPrice = new MarketOrder(Symbols.SPY, quantity, time)
{
Price = price
};
return new List<Order>
{
marketOrderWithPrice,
new LimitOrder(Symbols.SPY, -quantity, pricePlusDelta, time),
new StopMarketOrder(Symbols.SPY, quantity, pricePlusDelta, time),
new StopLimitOrder(Symbols.SPY, quantity, pricePlusDelta, priceMinusDelta, time)
};
}
public override List<CashAmount> GetCashBalance()
{
GetCashBalanceCallCount++;
return new List<CashAmount> { new CashAmount(10, Currencies.USD) };
}
#region UnusedMethods
public override List<Holding> GetAccountHoldings()
{
throw new NotImplementedException();
}
public override bool PlaceOrder(Order order)
{
throw new NotImplementedException();
}
public override bool UpdateOrder(Order order)
{
throw new NotImplementedException();
}
public override bool CancelOrder(Order order)
{
throw new NotImplementedException();
}
public override void Connect()
{
throw new NotImplementedException();
}
public override void Disconnect()
{
throw new NotImplementedException();
}
#endregion
}
}