chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Algorithm.Selection;
|
||||
using QuantConnect.AlgorithmFactory.Python.Wrappers;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Auxiliary;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.Custom.IconicTypes;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
using Bitcoin = QuantConnect.Algorithm.CSharp.LiveTradingFeaturesAlgorithm.Bitcoin;
|
||||
using HistoryRequest = QuantConnect.Data.HistoryRequest;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmAddDataTests
|
||||
{
|
||||
[Test]
|
||||
public void DefaultDataFeeds_CanBeOverwritten_Successfully()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
|
||||
// forex defult - should be quotebar
|
||||
var forexTrade = algo.AddForex("EURUSD");
|
||||
Assert.IsTrue(forexTrade.Subscriptions.Count() == 1);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, forexTrade.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
// Change
|
||||
Config.Set("security-data-feeds", "{ Forex: [\"Trade\"] }");
|
||||
var dataFeedsConfigString = Config.Get("security-data-feeds");
|
||||
Dictionary<SecurityType, List<TickType>> dataFeeds = new Dictionary<SecurityType, List<TickType>>();
|
||||
if (!string.IsNullOrEmpty(dataFeedsConfigString))
|
||||
{
|
||||
dataFeeds = JsonConvert.DeserializeObject<Dictionary<SecurityType, List<TickType>>>(dataFeedsConfigString);
|
||||
}
|
||||
|
||||
algo.SetAvailableDataTypes(dataFeeds);
|
||||
|
||||
// new forex - should be tradebar
|
||||
var forexQuote = algo.AddForex("EURUSD");
|
||||
// The quote bar subscription is kept
|
||||
Assert.IsTrue(forexQuote.Subscriptions.Count() == 2);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, forexQuote.Symbol, typeof(TradeBar)) != null);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, forexQuote.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
// reset to empty string, affects other tests because config is static
|
||||
Config.Set("security-data-feeds", "");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultDataFeeds_AreAdded_Successfully()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
|
||||
// forex
|
||||
var forex = algo.AddSecurity(SecurityType.Forex, "eurusd");
|
||||
Assert.IsTrue(forex.Subscriptions.Count() == 1);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, forex.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
// equity high resolution
|
||||
var equityMinute = algo.AddSecurity(SecurityType.Equity, "goog");
|
||||
Assert.IsTrue(equityMinute.Subscriptions.Count() == 2);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, equityMinute.Symbol, typeof(TradeBar)) != null);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, equityMinute.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
// equity low resolution
|
||||
var equityDaily = algo.AddSecurity(SecurityType.Equity, "goog", Resolution.Daily);
|
||||
Assert.IsTrue(equityDaily.Subscriptions.Count() == 3);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, equityDaily.Symbol, typeof(TradeBar)) != null);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, equityMinute.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
Assert.IsTrue(ReferenceEquals(equityMinute, equityDaily));
|
||||
|
||||
var equitySubscriptions = algo.SubscriptionManager.SubscriptionDataConfigService
|
||||
.GetSubscriptionDataConfigs(equityMinute.Symbol);
|
||||
Assert.IsTrue(equitySubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Minute) != null);
|
||||
Assert.IsTrue(equitySubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Quote && s.Type == typeof(QuoteBar) && s.Resolution == Resolution.Minute) != null);
|
||||
Assert.IsTrue(equitySubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Daily) != null);
|
||||
|
||||
// option
|
||||
var option = algo.AddSecurity(SecurityType.Option, "goog");
|
||||
Assert.IsTrue(option.Subscriptions.Count() == 1);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, option.Symbol, typeof(OptionUniverse)) != null);
|
||||
|
||||
// index option
|
||||
var indexOption = algo.AddSecurity(SecurityType.IndexOption, "spx");
|
||||
Assert.IsTrue(indexOption.Subscriptions.Count() == 1);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, indexOption.Symbol, typeof(OptionUniverse)) != null);
|
||||
|
||||
// cfd
|
||||
var cfd = algo.AddSecurity(SecurityType.Cfd, "abc");
|
||||
Assert.IsTrue(cfd.Subscriptions.Count() == 1);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, cfd.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
// future
|
||||
var future = algo.AddSecurity(SecurityType.Future, "ES");
|
||||
Assert.IsTrue(future.Subscriptions.Count() == 1);
|
||||
Assert.IsTrue(future.Subscriptions.FirstOrDefault(x => typeof(FutureUniverse) == x.Type) != null);
|
||||
|
||||
// Crypto high resolution
|
||||
var cryptoMinute = algo.AddSecurity(SecurityType.Crypto, "btcusd");
|
||||
Assert.IsTrue(cryptoMinute.Subscriptions.Count() == 2);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, cryptoMinute.Symbol, typeof(TradeBar)) != null);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, cryptoMinute.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
// Crypto low resolution
|
||||
var cryptoHourly = algo.AddSecurity(SecurityType.Crypto, "btcusd", Resolution.Hour);
|
||||
Assert.IsTrue(cryptoHourly.Subscriptions.Count() == 4);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, cryptoHourly.Symbol, typeof(TradeBar)) != null);
|
||||
Assert.IsTrue(GetMatchingSubscription(algo, cryptoHourly.Symbol, typeof(QuoteBar)) != null);
|
||||
|
||||
Assert.IsTrue(ReferenceEquals(cryptoMinute, cryptoHourly));
|
||||
|
||||
var cryptoSubscriptions = algo.SubscriptionManager.SubscriptionDataConfigService
|
||||
.GetSubscriptionDataConfigs(cryptoMinute.Symbol);
|
||||
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Minute) != null);
|
||||
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Quote && s.Type == typeof(QuoteBar) && s.Resolution == Resolution.Minute) != null);
|
||||
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Hour) != null);
|
||||
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
|
||||
s => s.TickType == TickType.Quote && s.Type == typeof(QuoteBar) && s.Resolution == Resolution.Hour) != null);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void CustomDataTypes_AreAddedToSubscriptions_Successfully()
|
||||
{
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
// Add a bitcoin subscription
|
||||
qcAlgorithm.AddData<Bitcoin>("BTC");
|
||||
var bitcoinSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Type == typeof(Bitcoin));
|
||||
Assert.AreEqual(bitcoinSubscription.Type, typeof(Bitcoin));
|
||||
|
||||
// Add a unlinkedData subscription
|
||||
qcAlgorithm.AddData<UnlinkedData>("EURCAD");
|
||||
var unlinkedDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Type == typeof(UnlinkedData));
|
||||
Assert.AreEqual(unlinkedDataSubscription.Type, typeof(UnlinkedData));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnEndOfTimeStepSeedsUnderlyingSecuritiesThatHaveNoData()
|
||||
{
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm, new MockDataFeed()));
|
||||
qcAlgorithm.SetLiveMode(true);
|
||||
qcAlgorithm.Settings.SeedInitialPrices = false;
|
||||
var testHistoryProvider = new TestHistoryProvider();
|
||||
qcAlgorithm.HistoryProvider = testHistoryProvider;
|
||||
|
||||
var option = qcAlgorithm.AddSecurity(SecurityType.Option, testHistoryProvider.underlyingSymbol);
|
||||
var option2 = qcAlgorithm.AddSecurity(SecurityType.Option, testHistoryProvider.underlyingSymbol2);
|
||||
Assert.IsFalse(qcAlgorithm.Securities.ContainsKey(option.Symbol.Underlying));
|
||||
Assert.IsFalse(qcAlgorithm.Securities.ContainsKey(option2.Symbol.Underlying));
|
||||
qcAlgorithm.OnEndOfTimeStep();
|
||||
var data = qcAlgorithm.Securities[testHistoryProvider.underlyingSymbol].GetLastData();
|
||||
var data2 = qcAlgorithm.Securities[testHistoryProvider.underlyingSymbol2].GetLastData();
|
||||
Assert.IsNotNull(data);
|
||||
Assert.IsNotNull(data2);
|
||||
Assert.AreEqual(data.Price, 2);
|
||||
Assert.AreEqual(data2.Price, 3);
|
||||
}
|
||||
|
||||
[Test, Parallelizable(ParallelScope.Self)]
|
||||
public void OnEndOfTimeStepDoesNotThrowWhenSeedsSameUnderlyingForTwoSecurities()
|
||||
{
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm, new MockDataFeed()));
|
||||
qcAlgorithm.SetLiveMode(true);
|
||||
qcAlgorithm.Settings.SeedInitialPrices = false;
|
||||
var testHistoryProvider = new TestHistoryProvider();
|
||||
qcAlgorithm.HistoryProvider = testHistoryProvider;
|
||||
var option = qcAlgorithm.AddOption(testHistoryProvider.underlyingSymbol);
|
||||
|
||||
var symbol = Symbol.CreateOption(testHistoryProvider.underlyingSymbol, Market.USA, OptionStyle.American,
|
||||
OptionRight.Call, 1, new DateTime(2015, 12, 24));
|
||||
var symbol2 = Symbol.CreateOption(testHistoryProvider.underlyingSymbol, Market.USA, OptionStyle.American,
|
||||
OptionRight.Put, 1, new DateTime(2015, 12, 24));
|
||||
|
||||
var optionContract = qcAlgorithm.AddOptionContract(symbol);
|
||||
var optionContract2 = qcAlgorithm.AddOptionContract(symbol2);
|
||||
|
||||
qcAlgorithm.OnEndOfTimeStep();
|
||||
var data = qcAlgorithm.Securities[testHistoryProvider.underlyingSymbol].GetLastData();
|
||||
Assert.IsNotNull(data);
|
||||
Assert.AreEqual(data.Price, 2);
|
||||
}
|
||||
|
||||
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Cfd, false, true)]
|
||||
[TestCase("BTCUSD", typeof(IndexedLinkedData), SecurityType.Crypto, false, true)]
|
||||
[TestCase("CL", typeof(IndexedLinkedData), SecurityType.Future, true, true)]
|
||||
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Forex, false, true)]
|
||||
[TestCase("AAPL", typeof(IndexedLinkedData), SecurityType.Equity, true, true)]
|
||||
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Cfd, false, false)]
|
||||
[TestCase("BTCUSD", typeof(UnlinkedData), SecurityType.Crypto, false, false)]
|
||||
[TestCase("CL", typeof(UnlinkedData), SecurityType.Future, true, false)]
|
||||
[TestCase("AAPL", typeof(UnlinkedData), SecurityType.Equity, true, false)]
|
||||
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Forex, false, false)]
|
||||
public void AddDataSecuritySymbolWithUnderlying(string ticker, Type customDataType, SecurityType securityType, bool securityShouldBeMapped, bool customDataShouldBeMapped)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
Security asset;
|
||||
|
||||
switch (securityType)
|
||||
{
|
||||
case SecurityType.Cfd:
|
||||
asset = qcAlgorithm.AddCfd(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Crypto:
|
||||
asset = qcAlgorithm.AddCrypto(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Equity:
|
||||
asset = qcAlgorithm.AddEquity(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Forex:
|
||||
asset = qcAlgorithm.AddForex(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
asset = qcAlgorithm.AddFuture(ticker, Resolution.Minute);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"SecurityType {securityType} is not valid for this test");
|
||||
}
|
||||
|
||||
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
|
||||
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
|
||||
// This covers the case where two idential data subscriptions are created.
|
||||
var dummy = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
|
||||
var customData = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
|
||||
|
||||
Assert.IsTrue(customData.Symbol.HasUnderlying, $"{customDataType.Name} added as {ticker} Symbol with SecurityType {securityType} does not have underlying");
|
||||
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol, $"Custom data underlying does not match {securityType} Symbol for {ticker}");
|
||||
|
||||
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First();
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
|
||||
|
||||
var assetShouldBeMapped = assetSubscription.TickerShouldBeMapped();
|
||||
var customShouldBeMapped = customDataSubscription.TickerShouldBeMapped();
|
||||
|
||||
Assert.AreEqual(securityShouldBeMapped, assetShouldBeMapped);
|
||||
Assert.AreEqual(customDataShouldBeMapped, customShouldBeMapped);
|
||||
|
||||
Assert.AreNotEqual(assetSubscription, customDataSubscription);
|
||||
|
||||
if (assetShouldBeMapped == customShouldBeMapped)
|
||||
{
|
||||
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
|
||||
Assert.AreEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Cfd, false, false)]
|
||||
[TestCase("BTCUSD", typeof(IndexedLinkedData), SecurityType.Crypto, false, false)]
|
||||
[TestCase("CL", typeof(IndexedLinkedData), SecurityType.Future, false, false)]
|
||||
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Forex, false, false)]
|
||||
[TestCase("AAPL", typeof(IndexedLinkedData), SecurityType.Equity, true, true)]
|
||||
public void AddDataSecurityTickerWithUnderlying(string ticker, Type customDataType, SecurityType securityType, bool securityShouldBeMapped, bool customDataShouldBeMapped)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
Security asset;
|
||||
|
||||
switch (securityType)
|
||||
{
|
||||
case SecurityType.Cfd:
|
||||
asset = qcAlgorithm.AddCfd(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Crypto:
|
||||
asset = qcAlgorithm.AddCrypto(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Equity:
|
||||
asset = qcAlgorithm.AddEquity(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Forex:
|
||||
asset = qcAlgorithm.AddForex(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
asset = qcAlgorithm.AddFuture(ticker, Resolution.Minute);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"SecurityType {securityType} is not valid for this test");
|
||||
}
|
||||
|
||||
// Aliased value for Futures contains a forward-slash, which causes the
|
||||
// lookup in the SymbolCache to fail
|
||||
if (securityType == SecurityType.Future)
|
||||
{
|
||||
ticker = asset.Symbol.Value;
|
||||
}
|
||||
|
||||
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
|
||||
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
|
||||
// This covers the case where two idential data subscriptions are created.
|
||||
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
|
||||
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
|
||||
|
||||
Assert.IsTrue(customData.Symbol.HasUnderlying, $"Custom data added as {ticker} Symbol with SecurityType {securityType} does not have underlying");
|
||||
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol, $"Custom data underlying does not match {securityType} Symbol for {ticker}");
|
||||
|
||||
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First();
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
|
||||
|
||||
var assetShouldBeMapped = assetSubscription.TickerShouldBeMapped();
|
||||
var customShouldBeMapped = customDataSubscription.TickerShouldBeMapped();
|
||||
|
||||
if (securityType == SecurityType.Equity)
|
||||
{
|
||||
Assert.AreEqual(securityShouldBeMapped, assetShouldBeMapped);
|
||||
Assert.AreEqual(customDataShouldBeMapped, customShouldBeMapped);
|
||||
|
||||
Assert.AreNotEqual(assetSubscription, customDataSubscription);
|
||||
|
||||
if (assetShouldBeMapped == customShouldBeMapped)
|
||||
{
|
||||
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
|
||||
Assert.AreEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Cfd, false, false)]
|
||||
[TestCase("BTCUSD", typeof(UnlinkedData), SecurityType.Crypto, false, false)]
|
||||
[TestCase("CL", typeof(UnlinkedData), SecurityType.Future, true, false)]
|
||||
[TestCase("AAPL", typeof(UnlinkedData), SecurityType.Equity, true, false)]
|
||||
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Forex, false, false)]
|
||||
public void AddDataSecurityTickerNoUnderlying(string ticker, Type customDataType, SecurityType securityType, bool securityShouldBeMapped, bool customDataShouldBeMapped)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
Security asset;
|
||||
|
||||
switch (securityType)
|
||||
{
|
||||
case SecurityType.Cfd:
|
||||
asset = qcAlgorithm.AddCfd(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Crypto:
|
||||
asset = qcAlgorithm.AddCrypto(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Equity:
|
||||
asset = qcAlgorithm.AddEquity(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Forex:
|
||||
asset = qcAlgorithm.AddForex(ticker, Resolution.Daily);
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
asset = qcAlgorithm.AddFuture(ticker, Resolution.Minute);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"SecurityType {securityType} is not valid for this test");
|
||||
}
|
||||
|
||||
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
|
||||
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
|
||||
// This covers the case where two idential data subscriptions are created.
|
||||
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
|
||||
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
|
||||
|
||||
// Check to see if we have an underlying symbol when we shouldn't
|
||||
Assert.IsFalse(customData.Symbol.HasUnderlying, $"{customDataType.Name} has underlying symbol for SecurityType {securityType} with ticker {ticker}");
|
||||
Assert.AreEqual(customData.Symbol.Underlying, null, $"{customDataType.Name} - Custom data underlying Symbol for SecurityType {securityType} is not null");
|
||||
|
||||
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First();
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
|
||||
|
||||
var assetShouldBeMapped = assetSubscription.TickerShouldBeMapped();
|
||||
var customShouldBeMapped = customDataSubscription.TickerShouldBeMapped();
|
||||
|
||||
Assert.AreEqual(securityShouldBeMapped, assetShouldBeMapped);
|
||||
Assert.AreEqual(customDataShouldBeMapped, customShouldBeMapped);
|
||||
|
||||
Assert.AreNotEqual(assetSubscription, customDataSubscription);
|
||||
|
||||
if (assetShouldBeMapped == customShouldBeMapped)
|
||||
{
|
||||
// Would fail with CL future without this check because MappedSymbol returns "/CL" for the Future symbol
|
||||
if (assetSubscription.SecurityType == SecurityType.Future)
|
||||
{
|
||||
Assert.AreNotEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
|
||||
Assert.AreNotEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
|
||||
Assert.AreEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddOptionWithUnderlyingFuture()
|
||||
{
|
||||
// Adds an option containing a Future as its underlying Symbol.
|
||||
// This is an essential step in enabling custom derivatives
|
||||
// based on any asset class provided to Option. This test
|
||||
// checks the ability to create Future Options.
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
|
||||
var underlying = algo.AddFuture("ES", Resolution.Minute, Market.CME);
|
||||
underlying.SetFilter(0, 365);
|
||||
|
||||
var futureOption = algo.AddOption(underlying.Symbol, Resolution.Minute);
|
||||
|
||||
Assert.IsTrue(futureOption.Symbol.HasUnderlying);
|
||||
Assert.AreEqual(underlying.Symbol, futureOption.Symbol.Underlying);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddFutureOptionContractNonEquityOption()
|
||||
{
|
||||
// Adds an option contract containing an underlying future contract.
|
||||
// We test to make sure that the security returned is a specific option
|
||||
// contract and with the future as the underlying.
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
|
||||
var underlying = algo.AddFutureContract(
|
||||
Symbol.CreateFuture("ES", Market.CME, new DateTime(2021, 3, 19)),
|
||||
Resolution.Minute);
|
||||
|
||||
var futureOptionContract = algo.AddFutureOptionContract(
|
||||
Symbol.CreateOption(underlying.Symbol, Market.CME, OptionStyle.American, OptionRight.Call, 2550m, new DateTime(2021, 3, 19)),
|
||||
Resolution.Minute);
|
||||
|
||||
Assert.AreEqual(underlying.Symbol, futureOptionContract.Symbol.Underlying);
|
||||
Assert.AreEqual(underlying, futureOptionContract.Underlying);
|
||||
Assert.IsFalse(underlying.Symbol.IsCanonical());
|
||||
Assert.IsFalse(futureOptionContract.Symbol.IsCanonical());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddFutureOptionAddsUniverseSelectionModel()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
|
||||
var underlying = algo.AddFuture("ES", Resolution.Minute, Market.CME);
|
||||
underlying.SetFilter(0, 365);
|
||||
|
||||
algo.AddFutureOption(underlying.Symbol, _ => _);
|
||||
Assert.IsTrue(algo.UniverseSelection is CompositeUniverseSelectionModel);
|
||||
}
|
||||
|
||||
[TestCase("AAPL", typeof(IndexedLinkedData), true)]
|
||||
[TestCase("TWX", typeof(IndexedLinkedData), true)]
|
||||
[TestCase("FB", typeof(IndexedLinkedData), true)]
|
||||
[TestCase("NFLX", typeof(IndexedLinkedData), true)]
|
||||
[TestCase("TWX", typeof(UnlinkedData), false)]
|
||||
[TestCase("AAPL", typeof(UnlinkedData), false)]
|
||||
public void AddDataOptionsSymbolHasChainedUnderlyingSymbols(string ticker, Type customDataType, bool customDataShouldBeMapped)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
var asset = qcAlgorithm.AddOption(ticker);
|
||||
|
||||
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
|
||||
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
|
||||
// This covers the case where two idential data subscriptions are created.
|
||||
var dummy = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
|
||||
var customData = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
|
||||
|
||||
// Check to see if we have an underlying symbol when we shouldn't
|
||||
Assert.IsTrue(customData.Symbol.HasUnderlying, $"{customDataType.Name} - {ticker} has no underlying Symbol");
|
||||
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol);
|
||||
Assert.AreEqual(customData.Symbol.Underlying.Underlying, asset.Symbol.Underlying);
|
||||
Assert.AreEqual(customData.Symbol.Underlying.Underlying.Underlying, null);
|
||||
|
||||
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single();
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
|
||||
|
||||
Assert.IsTrue(assetSubscription.TickerShouldBeMapped());
|
||||
Assert.AreEqual(customDataShouldBeMapped, customDataSubscription.TickerShouldBeMapped());
|
||||
|
||||
Assert.AreEqual($"?{assetSubscription.MappedSymbol}", customDataSubscription.MappedSymbol);
|
||||
}
|
||||
|
||||
[TestCase("AAPL", typeof(IndexedLinkedData))]
|
||||
[TestCase("TWX", typeof(IndexedLinkedData))]
|
||||
[TestCase("FB", typeof(IndexedLinkedData))]
|
||||
[TestCase("NFLX", typeof(IndexedLinkedData))]
|
||||
public void AddDataOptionsTickerHasChainedUnderlyingSymbol(string ticker, Type customDataType)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
var asset = qcAlgorithm.AddOption(ticker);
|
||||
|
||||
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
|
||||
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
|
||||
// This covers the case where two idential data subscriptions are created.
|
||||
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
|
||||
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
|
||||
|
||||
// Check to see if we have an underlying symbol when we shouldn't
|
||||
Assert.IsTrue(customData.Symbol.HasUnderlying, $"{customDataType.Name} - {ticker} has no underlying Symbol");
|
||||
Assert.AreNotEqual(customData.Symbol.Underlying, asset.Symbol);
|
||||
Assert.IsFalse(customData.Symbol.Underlying.HasUnderlying);
|
||||
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol.Underlying);
|
||||
|
||||
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single();
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
|
||||
|
||||
Assert.IsTrue(assetSubscription.TickerShouldBeMapped());
|
||||
Assert.IsTrue(customDataSubscription.TickerShouldBeMapped());
|
||||
|
||||
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
|
||||
}
|
||||
|
||||
[TestCase("AAPL", typeof(UnlinkedData))]
|
||||
[TestCase("FDTR", typeof(UnlinkedData))]
|
||||
public void AddDataOptionsTickerHasNoChainedUnderlyingSymbols(string ticker, Type customDataType)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
var asset = qcAlgorithm.AddOption(ticker);
|
||||
|
||||
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
|
||||
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
|
||||
// This covers the case where two idential data subscriptions are created.
|
||||
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
|
||||
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
|
||||
|
||||
// Check to see if we have an underlying symbol when we shouldn't
|
||||
Assert.IsFalse(customData.Symbol.HasUnderlying, $"{customDataType.Name} has an underlying Symbol");
|
||||
|
||||
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single();
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
|
||||
|
||||
Assert.IsTrue(assetSubscription.TickerShouldBeMapped());
|
||||
Assert.IsFalse(customDataSubscription.TickerShouldBeMapped());
|
||||
|
||||
//Assert.AreNotEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCustomDataTypes_AreAddedToSubscriptions_Successfully()
|
||||
{
|
||||
var qcAlgorithm = new AlgorithmPythonWrapper("Test_CustomDataAlgorithm");
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
// Initialize contains the statements:
|
||||
// self.AddData(Nifty, "NIFTY")
|
||||
// self.AddData(CustomPythonData, "IBM", Resolution.Daily)
|
||||
qcAlgorithm.Initialize();
|
||||
|
||||
var niftySubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Symbol.Value == "NIFTY");
|
||||
Assert.IsNotNull(niftySubscription);
|
||||
|
||||
var niftyFactory = (BaseData)ObjectActivator.GetActivator(niftySubscription.Type).Invoke(new object[] { niftySubscription.Type });
|
||||
Assert.DoesNotThrow(() => niftyFactory.GetSource(niftySubscription, DateTime.UtcNow, false));
|
||||
|
||||
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Symbol.Value == "IBM");
|
||||
Assert.IsNotNull(customDataSubscription);
|
||||
Assert.IsTrue(customDataSubscription.IsCustomData);
|
||||
Assert.AreEqual("custom_data.CustomPythonData", customDataSubscription.Type.ToString());
|
||||
|
||||
var customDataFactory = (BaseData)ObjectActivator.GetActivator(customDataSubscription.Type).Invoke(new object[] { customDataSubscription.Type });
|
||||
Assert.DoesNotThrow(() => customDataFactory.GetSource(customDataSubscription, DateTime.UtcNow, false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCustomDataTypes_AreAddedToConsolidator_Successfully()
|
||||
{
|
||||
var qcAlgorithm = new AlgorithmPythonWrapper("Test_CustomDataAlgorithm");
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
// Initialize contains the statements:
|
||||
// self.AddData(Nifty, "NIFTY")
|
||||
// self.AddData(CustomPythonData, "IBM", Resolution.Daily)
|
||||
qcAlgorithm.Initialize();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
using var niftyConsolidator = new DynamicDataConsolidator(TimeSpan.FromDays(2));
|
||||
Assert.DoesNotThrow(() => qcAlgorithm.SubscriptionManager.AddConsolidator("NIFTY", niftyConsolidator));
|
||||
|
||||
using var customDataConsolidator = new DynamicDataConsolidator(TimeSpan.FromDays(2));
|
||||
Assert.DoesNotThrow(() => qcAlgorithm.SubscriptionManager.AddConsolidator("IBM", customDataConsolidator));
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddingInvalidDataTypeThrows()
|
||||
{
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
Assert.Throws<ArgumentException>(() => qcAlgorithm.AddData(typeof(double),
|
||||
"double",
|
||||
Resolution.Daily,
|
||||
DateTimeZone.Utc));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddDataWithStringAsTypeArgumentThrowsClearError()
|
||||
{
|
||||
var qcAlgorithm = new QCAlgorithm();
|
||||
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
|
||||
|
||||
using var _ = Py.GIL();
|
||||
using var pyTicker = "VIX".ToPython();
|
||||
|
||||
// Passing a string instead of a custom data class as the first argument used to silently build a
|
||||
// dynamic assembly named after the string and later hang/fail downstream. It must now throw.
|
||||
var ex = Assert.Throws<ArgumentException>(() => qcAlgorithm.AddData(pyTicker, "VIX", Resolution.Daily));
|
||||
StringAssert.Contains("AddData", ex.Message);
|
||||
StringAssert.Contains("AddEquity", ex.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AppendsCustomDataTypeName_ToSecurityIdentifierSymbol()
|
||||
{
|
||||
const string ticker = "ticker";
|
||||
var algorithm = Algorithm();
|
||||
|
||||
var security = algorithm.AddData<UnlinkedData>(ticker);
|
||||
Assert.AreEqual(ticker.ToUpperInvariant(), security.Symbol.Value);
|
||||
Assert.AreEqual($"{ticker.ToUpperInvariant()}.{typeof(UnlinkedData).Name}", security.Symbol.ID.Symbol);
|
||||
Assert.AreEqual(SecurityIdentifier.GenerateBaseSymbol(typeof(UnlinkedData), ticker), security.Symbol.ID.Symbol);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegistersSecurityIdentifierSymbol_AsTickerString_InSymbolCache()
|
||||
{
|
||||
var algorithm = Algorithm();
|
||||
|
||||
Symbol cachedSymbol;
|
||||
var security = algorithm.AddData<UnlinkedData>("ticker");
|
||||
var symbolCacheAlias = security.Symbol.ID.Symbol;
|
||||
|
||||
Assert.IsTrue(SymbolCache.TryGetSymbol(symbolCacheAlias, out cachedSymbol));
|
||||
Assert.AreSame(security.Symbol, cachedSymbol);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotCauseCollision_WhenRegisteringMultipleDifferentCustomDataTypes_WithSameTicker()
|
||||
{
|
||||
const string ticker = "ticker";
|
||||
var algorithm = Algorithm();
|
||||
|
||||
var security1 = algorithm.AddData<UnlinkedData>(ticker);
|
||||
var security2 = algorithm.AddData<Bitcoin>(ticker);
|
||||
|
||||
var unlinkedData = algorithm.Securities[security1.Symbol];
|
||||
Assert.AreSame(security1, unlinkedData);
|
||||
|
||||
var bitcoin = algorithm.Securities[security2.Symbol];
|
||||
Assert.AreSame(security2, bitcoin);
|
||||
|
||||
Assert.AreNotSame(unlinkedData, bitcoin);
|
||||
}
|
||||
|
||||
[TestCase(SecurityType.Equity)]
|
||||
[TestCase(SecurityType.Index)]
|
||||
[TestCase(SecurityType.Future)]
|
||||
public void AddOptionContractWithDelistedUnderlyingThrows(SecurityType underlyingSecurityType)
|
||||
{
|
||||
var algorithm = Algorithm();
|
||||
algorithm.SetStartDate(2007, 05, 25);
|
||||
|
||||
Security underlying = underlyingSecurityType switch
|
||||
{
|
||||
SecurityType.Equity => algorithm.AddEquity("SPY"),
|
||||
SecurityType.Index => algorithm.AddIndex("SPX"),
|
||||
SecurityType.Future => algorithm.AddFuture("ES"),
|
||||
_ => throw new ArgumentException($"Invalid test underlying security type {underlyingSecurityType}")
|
||||
};
|
||||
|
||||
underlying.IsDelisted = true;
|
||||
// let's remove the underlying since it's delisted
|
||||
algorithm.RemoveSecurity(underlying.Symbol);
|
||||
|
||||
var optionContractSymbol = Symbol.CreateOption(underlying.Symbol, Market.USA, OptionStyle.American, OptionRight.Call, 100,
|
||||
new DateTime(2007, 06, 15));
|
||||
|
||||
var exception = Assert.Throws<ArgumentException>(() => algorithm.AddOptionContract(optionContractSymbol));
|
||||
Assert.IsTrue(exception.Message.Contains("is delisted"), $"Unexpected exception message: {exception.Message}");
|
||||
}
|
||||
|
||||
private static SubscriptionDataConfig GetMatchingSubscription(QCAlgorithm algorithm, Symbol symbol, Type type)
|
||||
{
|
||||
// find a subscription matchin the requested type with a higher resolution than requested
|
||||
return algorithm.SubscriptionManager.SubscriptionDataConfigService
|
||||
.GetSubscriptionDataConfigs(symbol)
|
||||
.Where(config => type.IsAssignableFrom(config.Type))
|
||||
.OrderByDescending(s => s.Resolution)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private static QCAlgorithm Algorithm()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
private class TestHistoryProvider : HistoryProviderBase
|
||||
{
|
||||
public string underlyingSymbol = "GOOG";
|
||||
public string underlyingSymbol2 = "AAPL";
|
||||
public override int DataPointCount { get; }
|
||||
|
||||
public override void Initialize(HistoryProviderInitializeParameters parameters)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
#pragma warning disable CS0618
|
||||
var tradeBar1 = new TradeBar(now, underlyingSymbol, 1, 1, 1, 1, 1, TimeSpan.FromDays(1));
|
||||
var tradeBar2 = new TradeBar(now, underlyingSymbol2, 3, 3, 3, 3, 3, TimeSpan.FromDays(1));
|
||||
var slice1 = new Slice(now, new List<BaseData> { tradeBar1, tradeBar2 },
|
||||
new TradeBars(now) { tradeBar1, tradeBar2 }, new QuoteBars(),
|
||||
new Ticks(), new OptionChains(),
|
||||
new FuturesChains(), new Splits(),
|
||||
new Dividends(now), new Delistings(),
|
||||
new SymbolChangedEvents(), new MarginInterestRates(), now);
|
||||
var tradeBar1_2 = new TradeBar(now, underlyingSymbol, 2, 2, 2, 2, 2, TimeSpan.FromDays(1));
|
||||
#pragma warning restore CS0618
|
||||
var slice2 = new Slice(now, new List<BaseData> { tradeBar1_2 },
|
||||
new TradeBars(now) { tradeBar1_2 }, new QuoteBars(),
|
||||
new Ticks(), new OptionChains(),
|
||||
new FuturesChains(), new Splits(),
|
||||
new Dividends(now), new Delistings(),
|
||||
new SymbolChangedEvents(), new MarginInterestRates(), now);
|
||||
return new[] { slice1, slice2 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,764 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Data.Shortable;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Orders.Fills;
|
||||
using QuantConnect.Orders.Slippage;
|
||||
using QuantConnect.Python;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Cfd;
|
||||
using QuantConnect.Securities.Crypto;
|
||||
using QuantConnect.Securities.CryptoFuture;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using QuantConnect.Securities.Forex;
|
||||
using QuantConnect.Securities.Future;
|
||||
using QuantConnect.Securities.IndexOption;
|
||||
using QuantConnect.Securities.Option;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Index = QuantConnect.Securities.Index.Index;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmAddSecurityTests
|
||||
{
|
||||
private QCAlgorithm _algo;
|
||||
private NullDataFeed _dataFeed;
|
||||
|
||||
/// <summary>
|
||||
/// Instatiate a new algorithm before each test.
|
||||
/// Clear the <see cref="SymbolCache"/> so that no symbols and associated brokerage models are cached between test
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_algo = new QCAlgorithm();
|
||||
_dataFeed = new NullDataFeed
|
||||
{
|
||||
ShouldThrow = false
|
||||
};
|
||||
_algo.SubscriptionManager.SetDataManager(new DataManagerStub(_dataFeed, _algo));
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(TestAddSecurityWithSymbol))]
|
||||
public void AddSecurityWithSymbol(Symbol symbol, Type type = null)
|
||||
{
|
||||
var security = type != null ? _algo.AddData(type, symbol.Underlying) : _algo.AddSecurity(symbol);
|
||||
Assert.AreEqual(security.Symbol, symbol);
|
||||
Assert.AreEqual((Symbol)security, symbol);
|
||||
Assert.IsTrue(_algo.Securities.ContainsKey(symbol));
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
switch (symbol.SecurityType)
|
||||
{
|
||||
case SecurityType.Equity:
|
||||
var equity = (Equity)security;
|
||||
break;
|
||||
case SecurityType.Option:
|
||||
var option = (Option)security;
|
||||
break;
|
||||
case SecurityType.Forex:
|
||||
var forex = (Forex)security;
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
var future = (Future)security;
|
||||
break;
|
||||
case SecurityType.Cfd:
|
||||
var cfd = (Cfd)security;
|
||||
break;
|
||||
case SecurityType.Index:
|
||||
var index = (Index)security;
|
||||
break;
|
||||
case SecurityType.IndexOption:
|
||||
var indexOption = (IndexOption)security;
|
||||
break;
|
||||
case SecurityType.Crypto:
|
||||
var crypto = (Crypto)security;
|
||||
break;
|
||||
case SecurityType.CryptoFuture:
|
||||
var cryptoFuture = (CryptoFuture)security;
|
||||
break;
|
||||
case SecurityType.Base:
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Invalid Security Type: {symbol.SecurityType}");
|
||||
}
|
||||
});
|
||||
|
||||
if (symbol.IsCanonical())
|
||||
{
|
||||
Assert.DoesNotThrow(() => _algo.OnEndOfTimeStep());
|
||||
|
||||
Assert.IsTrue(_algo.UniverseManager.ContainsKey(symbol));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetDataNormalizationModes))]
|
||||
public void AddsEquityWithExpectedDataNormalizationMode(DataNormalizationMode dataNormalizationMode)
|
||||
{
|
||||
var equity = _algo.AddEquity("AAPL", dataNormalizationMode: dataNormalizationMode);
|
||||
Assert.That(_algo.SubscriptionManager.Subscriptions.Where(x => x.Symbol == equity.Symbol).Select(x => x.DataNormalizationMode),
|
||||
Has.All.EqualTo(dataNormalizationMode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProperlyAddsFutureWithExtendedMarketHours(
|
||||
[Values(true, false)] bool extendedMarketHours,
|
||||
[ValueSource(nameof(FuturesTestCases))] Func<QCAlgorithm, Security> getFuture)
|
||||
{
|
||||
var future = _algo.AddFuture(Futures.Indices.VIX, Resolution.Minute, extendedMarketHours: extendedMarketHours);
|
||||
var subscriptions = _algo.SubscriptionManager.Subscriptions.Where(x => x.Symbol == future.Symbol).ToList();
|
||||
|
||||
var universeSubscriptions = subscriptions.Where(x => x.Type == typeof(FutureUniverse)).ToList();
|
||||
Assert.AreEqual(1, universeSubscriptions.Count);
|
||||
// Universe does not support extended market hours
|
||||
Assert.IsFalse(universeSubscriptions[0].ExtendedMarketHours);
|
||||
|
||||
var nonUniverseSubscriptions = subscriptions.Where(x => x.Type != typeof(FutureUniverse)).ToList();
|
||||
Assert.Greater(nonUniverseSubscriptions.Count, 0);
|
||||
Assert.That(nonUniverseSubscriptions.Select(x => x.ExtendedMarketHours),
|
||||
Has.All.EqualTo(extendedMarketHours));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(FuturesTestCases))]
|
||||
public void AddFutureWithExtendedMarketHours(Func<QCAlgorithm, Security> getFuture)
|
||||
{
|
||||
string file = Path.Combine("TestData", "SampleMarketHoursDatabase.json");
|
||||
var marketHoursDatabase = MarketHoursDatabase.FromFile(file);
|
||||
var securityService = new SecurityService(
|
||||
_algo.Portfolio.CashBook,
|
||||
marketHoursDatabase,
|
||||
SymbolPropertiesDatabase.FromDataFolder(),
|
||||
_algo,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCacheProvider(_algo.Portfolio),
|
||||
algorithm: _algo);
|
||||
_algo.Securities.SetSecurityService(securityService);
|
||||
|
||||
var future = getFuture(_algo);
|
||||
|
||||
var now = new DateTime(2022, 6, 26, 17, 0, 0);
|
||||
Assert.AreEqual(DayOfWeek.Sunday, now.DayOfWeek);
|
||||
var regularMarketStartTime = new TimeSpan(8, 30, 0);
|
||||
var regularMarketEndTime = new TimeSpan(15, 0, 0);
|
||||
var firstExtendedMarketStartTime = regularMarketEndTime;
|
||||
var firstExtendedMarketEndTime = new TimeSpan(16, 0, 0);
|
||||
var secondExtendedMarketStartTime = new TimeSpan(17, 0, 0);
|
||||
|
||||
Action<DateTime> checkExtendedHours = (date) =>
|
||||
{
|
||||
Assert.IsFalse(future.Exchange.Hours.IsOpen(now, false));
|
||||
Assert.IsTrue(future.Exchange.Hours.IsOpen(now, true));
|
||||
};
|
||||
Action<DateTime> checkRegularHours = (date) =>
|
||||
{
|
||||
Assert.IsTrue(future.Exchange.Hours.IsOpen(now, false));
|
||||
Assert.IsTrue(future.Exchange.Hours.IsOpen(now, true));
|
||||
};
|
||||
Action<DateTime> checkClosed = (date) =>
|
||||
{
|
||||
Assert.IsFalse(future.Exchange.Hours.IsOpen(now, false));
|
||||
Assert.IsFalse(future.Exchange.Hours.IsOpen(now, true));
|
||||
};
|
||||
|
||||
while (now.DayOfWeek < DayOfWeek.Saturday)
|
||||
{
|
||||
while (now.TimeOfDay < regularMarketStartTime)
|
||||
{
|
||||
checkExtendedHours(now);
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
|
||||
while (now.TimeOfDay < regularMarketEndTime)
|
||||
{
|
||||
checkRegularHours(now);
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
|
||||
while (now.TimeOfDay >= firstExtendedMarketStartTime && now.TimeOfDay < firstExtendedMarketEndTime)
|
||||
{
|
||||
checkExtendedHours(now);
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
|
||||
while (now.TimeOfDay < secondExtendedMarketStartTime)
|
||||
{
|
||||
checkClosed(now);
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
|
||||
var endOfDay = now.AddDays(1).Date;
|
||||
if (now.DayOfWeek < DayOfWeek.Friday)
|
||||
{
|
||||
while (now < endOfDay)
|
||||
{
|
||||
checkExtendedHours(now);
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
now = endOfDay;
|
||||
}
|
||||
}
|
||||
|
||||
while (now.DayOfWeek < DayOfWeek.Sunday)
|
||||
{
|
||||
checkClosed(now);
|
||||
now = now.AddMinutes(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Reproduces https://github.com/QuantConnect/Lean/issues/7451
|
||||
[Test]
|
||||
public void DoesNotAddExtraIndexSubscriptionAfterAddingIndexOptionContract()
|
||||
{
|
||||
var spx = _algo.AddIndex("SPX", Resolution.Minute, fillForward: false);
|
||||
|
||||
Assert.AreEqual(1, _algo.SubscriptionManager.Subscriptions.Count());
|
||||
Assert.AreEqual(spx.Symbol, _algo.SubscriptionManager.Subscriptions.Single().Symbol);
|
||||
|
||||
var spxOption = Symbol.CreateOption(
|
||||
spx.Symbol,
|
||||
Market.USA,
|
||||
OptionStyle.European,
|
||||
OptionRight.Call,
|
||||
3200m,
|
||||
new DateTime(2021, 1, 15));
|
||||
_algo.AddIndexOptionContract(spxOption, Resolution.Minute);
|
||||
|
||||
Assert.Greater(_algo.SubscriptionManager.Subscriptions.Count(), 1);
|
||||
Assert.AreEqual(1, _algo.SubscriptionManager.Subscriptions.Count(x => x.Symbol == spx.Symbol));
|
||||
}
|
||||
|
||||
[TestCase("SPXW", "SPX")]
|
||||
[TestCase("RUTW", "RUT")]
|
||||
[TestCase("VIXW", "VIX")]
|
||||
[TestCase("NDXP", "NDX")]
|
||||
[TestCase("NQX", "NDX")]
|
||||
[TestCase("SPX", "SPX")]
|
||||
[TestCase("VIX", "VIX")]
|
||||
public void AddIndexOptionMapsNonStandardOptionTickerToItsUnderlyingIndex(string ticker, string expectedUnderlyingTicker)
|
||||
{
|
||||
var option = _algo.AddIndexOption(ticker);
|
||||
|
||||
// the canonical keeps the provided option ticker
|
||||
Assert.AreEqual($"?{ticker}", option.Symbol.Value);
|
||||
Assert.AreEqual(ticker, option.Symbol.ID.Symbol);
|
||||
// but the underlying is the actual index, which has data. Else the option contracts would reference
|
||||
// a data-less underlying index security which would never get a price
|
||||
Assert.AreEqual(SecurityType.Index, option.Symbol.Underlying.SecurityType);
|
||||
Assert.AreEqual(expectedUnderlyingTicker, option.Symbol.Underlying.Value);
|
||||
|
||||
// the auto-added underlying index security is the mapped index
|
||||
_algo.OnEndOfTimeStep();
|
||||
Assert.IsTrue(_algo.Securities.ContainsKey(option.Symbol.Underlying));
|
||||
Assert.AreEqual(option.Symbol.Underlying, option.Underlying.Symbol);
|
||||
if (ticker != expectedUnderlyingTicker)
|
||||
{
|
||||
Assert.IsFalse(_algo.Securities.Keys.Any(x => x.SecurityType == SecurityType.Index && x.Value == ticker));
|
||||
// a one time warning is sent about the mapping
|
||||
Assert.AreEqual(1, _algo.DebugMessages.Count(x => x.Contains(ticker, StringComparison.InvariantCulture)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsEmpty(_algo.DebugMessages);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void AddSecurityInitializerAppendsInitializer(Language language)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
|
||||
Assert.IsNotAssignableFrom<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var classInitializer1 = new TestCustomSecurityInitializer();
|
||||
algorithm.AddSecurityInitializer(classInitializer1);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
var classInitializer2 = new TestCustomSecurityInitializer();
|
||||
algorithm.AddSecurityInitializer(classInitializer2);
|
||||
|
||||
var funcInitializer1CallCount = 0;
|
||||
algorithm.AddSecurityInitializer((_) => funcInitializer1CallCount++);
|
||||
|
||||
var funcInitializer2CallCount = 0;
|
||||
algorithm.AddSecurityInitializer((_) => funcInitializer2CallCount++);
|
||||
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
Assert.AreEqual(1, classInitializer1.CallCount);
|
||||
Assert.AreEqual(1, classInitializer2.CallCount);
|
||||
Assert.AreEqual(1, funcInitializer1CallCount);
|
||||
Assert.AreEqual(1, funcInitializer2CallCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var _ = Py.GIL();
|
||||
using var module = PyModule.FromString("AddSecurityInitializerAppendsInitializer", @"
|
||||
class TestCustomSecurityInitializer:
|
||||
def __init__(self):
|
||||
self.call_count = 0
|
||||
|
||||
def initialize(self, security):
|
||||
self.call_count += 1
|
||||
|
||||
class_initializer1 = TestCustomSecurityInitializer()
|
||||
class_initializer2 = TestCustomSecurityInitializer()
|
||||
func_call_count1 = 0
|
||||
func_call_count2 = 0
|
||||
|
||||
def add_security_initializers(algorithm):
|
||||
algorithm.add_security_initializer(class_initializer1)
|
||||
algorithm.add_security_initializer(class_initializer2)
|
||||
|
||||
algorithm.add_security_initializer(func_security_initializer1)
|
||||
algorithm.add_security_initializer(func_security_initializer2)
|
||||
|
||||
def func_security_initializer1(security):
|
||||
global func_call_count1
|
||||
func_call_count1 += 1
|
||||
|
||||
def func_security_initializer2(security):
|
||||
global func_call_count2
|
||||
func_call_count2 += 1
|
||||
");
|
||||
|
||||
using var addSecurityInitializers = module.GetAttr("add_security_initializers");
|
||||
using var pyAlgorithm = algorithm.ToPython();
|
||||
addSecurityInitializers.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
|
||||
using var classInitializer1 = module.GetAttr("class_initializer1");
|
||||
var classInitializer1CallCount = classInitializer1.GetAttr("call_count").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, classInitializer1CallCount);
|
||||
|
||||
using var classInitializer2 = module.GetAttr("class_initializer2");
|
||||
var classInitializer2CallCount = classInitializer2.GetAttr("call_count").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, classInitializer2CallCount);
|
||||
|
||||
var funcCallCount1 = module.GetAttr("func_call_count1").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, funcCallCount1);
|
||||
|
||||
var funcCallCount2 = module.GetAttr("func_call_count2").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, funcCallCount2);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void SetSecurityInitializerReplacesInitializer(Language language)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
|
||||
Assert.IsNotAssignableFrom<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var classInitializer1 = new TestCustomSecurityInitializer();
|
||||
algorithm.AddSecurityInitializer(classInitializer1);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
algorithm.SetSecurityInitializer(classInitializer1);
|
||||
Assert.IsInstanceOf<TestCustomSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var _ = Py.GIL();
|
||||
using var module = PyModule.FromString("AddSecurityInitializerAppendsInitializer", @"
|
||||
class TestCustomSecurityInitializer:
|
||||
def initialize(self, security):
|
||||
pass
|
||||
|
||||
def add_security_initializer(algorithm):
|
||||
algorithm.add_security_initializer(TestCustomSecurityInitializer())
|
||||
|
||||
def set_security_initializer(algorithm):
|
||||
algorithm.set_security_initializer(TestCustomSecurityInitializer())
|
||||
");
|
||||
|
||||
using var pyAlgorithm = algorithm.ToPython();
|
||||
using var addSecurityInitializer = module.GetAttr("add_security_initializer");
|
||||
addSecurityInitializer.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
using var setSecurityInitializer = module.GetAttr("set_security_initializer");
|
||||
setSecurityInitializer.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<SecurityInitializerPythonWrapper>(algorithm.SecurityInitializer);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void AddsSecurityInitializerAfterSetting(Language language)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var classInitializer1 = new TestCustomSecurityInitializer();
|
||||
algorithm.SetSecurityInitializer(classInitializer1);
|
||||
|
||||
Assert.IsAssignableFrom<TestCustomSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
var classInitializer2 = new TestCustomSecurityInitializer();
|
||||
algorithm.AddSecurityInitializer(classInitializer2);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
var funcInitializer1CallCount = 0;
|
||||
algorithm.AddSecurityInitializer((_) => funcInitializer1CallCount++);
|
||||
|
||||
var funcInitializer2CallCount = 0;
|
||||
algorithm.AddSecurityInitializer((_) => funcInitializer2CallCount++);
|
||||
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
Assert.AreEqual(1, classInitializer1.CallCount);
|
||||
Assert.AreEqual(1, classInitializer2.CallCount);
|
||||
Assert.AreEqual(1, funcInitializer1CallCount);
|
||||
Assert.AreEqual(1, funcInitializer2CallCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var _ = Py.GIL();
|
||||
using var module = PyModule.FromString("AddSecurityInitializerAppendsInitializer", @"
|
||||
class TestCustomSecurityInitializer:
|
||||
def __init__(self):
|
||||
self.call_count = 0
|
||||
|
||||
def initialize(self, security):
|
||||
self.call_count += 1
|
||||
|
||||
class_initializer1 = TestCustomSecurityInitializer()
|
||||
class_initializer2 = TestCustomSecurityInitializer()
|
||||
func_call_count1 = 0
|
||||
func_call_count2 = 0
|
||||
|
||||
def set_security_initializer(algorithm):
|
||||
algorithm.set_security_initializer(class_initializer1)
|
||||
|
||||
def add_security_initializers(algorithm):
|
||||
algorithm.add_security_initializer(class_initializer2)
|
||||
|
||||
algorithm.add_security_initializer(func_security_initializer1)
|
||||
algorithm.add_security_initializer(func_security_initializer2)
|
||||
|
||||
def func_security_initializer1(security):
|
||||
global func_call_count1
|
||||
func_call_count1 += 1
|
||||
|
||||
def func_security_initializer2(security):
|
||||
global func_call_count2
|
||||
func_call_count2 += 1
|
||||
");
|
||||
|
||||
using var pyAlgorithm = algorithm.ToPython();
|
||||
|
||||
using var setSecurityInitializer = module.GetAttr("set_security_initializer");
|
||||
setSecurityInitializer.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<SecurityInitializerPythonWrapper>(algorithm.SecurityInitializer);
|
||||
|
||||
using var addSecurityInitializers = module.GetAttr("add_security_initializers");
|
||||
addSecurityInitializers.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
|
||||
using var classInitializer1 = module.GetAttr("class_initializer1");
|
||||
var classInitializer1CallCount = classInitializer1.GetAttr("call_count").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, classInitializer1CallCount);
|
||||
|
||||
using var classInitializer2 = module.GetAttr("class_initializer2");
|
||||
var classInitializer2CallCount = classInitializer2.GetAttr("call_count").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, classInitializer2CallCount);
|
||||
|
||||
var funcCallCount1 = module.GetAttr("func_call_count1").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, funcCallCount1);
|
||||
|
||||
var funcCallCount2 = module.GetAttr("func_call_count2").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, funcCallCount2);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void SetBrokerageModelAppendsSecurityIntializerAfterAddSecurityInitializer(Language language)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
|
||||
Assert.IsNotAssignableFrom<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
var brokerageModel = default(TestBrokerageModel);
|
||||
var security = default(Security);
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var classInitializer = new TestCustomSecurityInitializer();
|
||||
algorithm.AddSecurityInitializer(classInitializer);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
brokerageModel = new TestBrokerageModel();
|
||||
algorithm.SetBrokerageModel(brokerageModel);
|
||||
|
||||
Assert.AreSame(brokerageModel, algorithm.BrokerageModel);
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
security = algorithm.AddEquity("SPY");
|
||||
|
||||
Assert.AreEqual(1, classInitializer.CallCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var _ = Py.GIL();
|
||||
using var module = PyModule.FromString("SetBrokerageModelAppendsSecurityIntializerAfterAddSecurityInitializer", @"
|
||||
from QuantConnect.Tests.Algorithm import AlgorithmAddSecurityTests
|
||||
|
||||
class TestCustomSecurityInitializer:
|
||||
def __init__(self):
|
||||
self.call_count = 0
|
||||
|
||||
def initialize(self, security):
|
||||
security.set_fill_model(AlgorithmAddSecurityTests.TestCustomSecurityInitializer.TestFillModel())
|
||||
self.call_count += 1
|
||||
|
||||
class_initializer = TestCustomSecurityInitializer()
|
||||
|
||||
def add_security_initializers(algorithm):
|
||||
algorithm.add_security_initializer(class_initializer)
|
||||
|
||||
def set_brokerage_model(algorithm):
|
||||
algorithm.set_brokerage_model(AlgorithmAddSecurityTests.TestBrokerageModel())
|
||||
");
|
||||
|
||||
using var addSecurityInitializers = module.GetAttr("add_security_initializers");
|
||||
using var pyAlgorithm = algorithm.ToPython();
|
||||
addSecurityInitializers.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
|
||||
using var setBrokerageModel = module.GetAttr("set_brokerage_model");
|
||||
setBrokerageModel.Invoke(pyAlgorithm);
|
||||
|
||||
Assert.IsInstanceOf<CompositeSecurityInitializer>(algorithm.SecurityInitializer);
|
||||
Assert.IsInstanceOf<TestBrokerageModel>(algorithm.BrokerageModel);
|
||||
|
||||
brokerageModel = (TestBrokerageModel)algorithm.BrokerageModel;
|
||||
|
||||
security = algorithm.AddEquity("SPY");
|
||||
|
||||
using var classInitializer = module.GetAttr("class_initializer");
|
||||
var classInitializer1CallCount = classInitializer.GetAttr("call_count").GetAndDispose<int>();
|
||||
Assert.AreEqual(1, classInitializer1CallCount);
|
||||
}
|
||||
|
||||
Assert.IsTrue(brokerageModel.GetFillModelCalled);
|
||||
Assert.IsTrue(brokerageModel.GetFeeModelCalled);
|
||||
Assert.IsTrue(brokerageModel.GetSlippageModelCalled);
|
||||
Assert.IsTrue(brokerageModel.GetSettlementModelCalled);
|
||||
Assert.IsTrue(brokerageModel.GetBuyingPowerModelCalled);
|
||||
Assert.IsTrue(brokerageModel.GetMarginInterestRateModelCalled);
|
||||
Assert.IsTrue(brokerageModel.GetLeverageCalled);
|
||||
Assert.IsTrue(brokerageModel.GetShortableProviderCalled);
|
||||
|
||||
Assert.IsInstanceOf<TestBrokerageModel.TestFeeModel>(security.FeeModel);
|
||||
Assert.IsInstanceOf<TestBrokerageModel.TestSlippageModel>(security.SlippageModel);
|
||||
Assert.IsInstanceOf<TestBrokerageModel.TestSettlementModel>(security.SettlementModel);
|
||||
Assert.IsInstanceOf<TestBrokerageModel.TestBuyingPowerModel>(security.BuyingPowerModel);
|
||||
Assert.IsInstanceOf<TestBrokerageModel.TestMarginInterestRateModel>(security.MarginInterestRateModel);
|
||||
Assert.IsInstanceOf<TestBrokerageModel.TestShortableProvider>(security.ShortableProvider);
|
||||
Assert.AreEqual(5000, security.Leverage);
|
||||
|
||||
// All models should've been set my the TestBrokerageModel, except the fill model,
|
||||
// which should have been set by the TestCustomSecurityInitializer, because user defined
|
||||
// initializer should run after the brokerage model initializer
|
||||
Assert.IsInstanceOf<TestCustomSecurityInitializer.TestFillModel>(security.FillModel);
|
||||
}
|
||||
|
||||
public class TestCustomSecurityInitializer : ISecurityInitializer
|
||||
{
|
||||
public int CallCount { get; private set; }
|
||||
|
||||
public class TestFillModel : FillModel { }
|
||||
|
||||
public void Initialize(Security security)
|
||||
{
|
||||
CallCount++;
|
||||
security.SetFillModel(new TestFillModel());
|
||||
}
|
||||
}
|
||||
|
||||
public class TestBrokerageModel : DefaultBrokerageModel
|
||||
{
|
||||
public bool GetFillModelCalled { get; private set; }
|
||||
public bool GetFeeModelCalled { get; private set; }
|
||||
public bool GetSlippageModelCalled { get; private set; }
|
||||
public bool GetSettlementModelCalled { get; private set; }
|
||||
public bool GetBuyingPowerModelCalled { get; private set; }
|
||||
public bool GetMarginInterestRateModelCalled { get; private set; }
|
||||
public bool GetLeverageCalled { get; private set; }
|
||||
public bool GetShortableProviderCalled { get; private set; }
|
||||
|
||||
public class TestFillModel : FillModel { }
|
||||
public class TestFeeModel : FeeModel { }
|
||||
public class TestSlippageModel : ISlippageModel
|
||||
{
|
||||
public decimal GetSlippageApproximation(Security asset, Order order)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
public class TestSettlementModel : ImmediateSettlementModel { }
|
||||
public class TestBuyingPowerModel : BuyingPowerModel { }
|
||||
public class TestMarginInterestRateModel : IMarginInterestRateModel
|
||||
{
|
||||
public void ApplyMarginInterestRate(MarginInterestRateParameters marginInterestRateParameters)
|
||||
{
|
||||
}
|
||||
}
|
||||
public class TestShortableProvider : NullShortableProvider { }
|
||||
|
||||
public override IFillModel GetFillModel(Security security)
|
||||
{
|
||||
GetFillModelCalled = true;
|
||||
return new TestFillModel();
|
||||
}
|
||||
|
||||
public override IFeeModel GetFeeModel(Security security)
|
||||
{
|
||||
GetFeeModelCalled = true;
|
||||
return new TestFeeModel();
|
||||
}
|
||||
|
||||
public override ISlippageModel GetSlippageModel(Security security)
|
||||
{
|
||||
GetSlippageModelCalled = true;
|
||||
return new TestSlippageModel();
|
||||
}
|
||||
|
||||
public override ISettlementModel GetSettlementModel(Security security)
|
||||
{
|
||||
GetSettlementModelCalled = true;
|
||||
return new TestSettlementModel();
|
||||
}
|
||||
|
||||
public override IBuyingPowerModel GetBuyingPowerModel(Security security)
|
||||
{
|
||||
GetBuyingPowerModelCalled = true;
|
||||
return new TestBuyingPowerModel();
|
||||
}
|
||||
|
||||
public override IMarginInterestRateModel GetMarginInterestRateModel(Security security)
|
||||
{
|
||||
GetMarginInterestRateModelCalled = true;
|
||||
return new TestMarginInterestRateModel();
|
||||
}
|
||||
|
||||
public override decimal GetLeverage(Security security)
|
||||
{
|
||||
GetLeverageCalled = true;
|
||||
return 5000;
|
||||
}
|
||||
|
||||
public override IShortableProvider GetShortableProvider(Security security)
|
||||
{
|
||||
GetShortableProviderCalled = true;
|
||||
return new TestShortableProvider();
|
||||
}
|
||||
}
|
||||
|
||||
private static TestCaseData[] TestAddSecurityWithSymbol
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new List<TestCaseData>()
|
||||
{
|
||||
new TestCaseData(Symbols.SPY, null),
|
||||
new TestCaseData(Symbols.EURUSD, null),
|
||||
new TestCaseData(Symbols.DE30EUR, null),
|
||||
new TestCaseData(Symbols.BTCUSD, null),
|
||||
new TestCaseData(Symbols.ES_Future_Chain, null),
|
||||
new TestCaseData(Symbols.Future_ESZ18_Dec2018, null),
|
||||
new TestCaseData(Symbols.SPY_Option_Chain, null),
|
||||
new TestCaseData(Symbols.SPY_C_192_Feb19_2016, null),
|
||||
new TestCaseData(Symbols.SPY_P_192_Feb19_2016, null),
|
||||
new TestCaseData(Symbol.Create("CustomData", SecurityType.Base, Market.Binance), null),
|
||||
new TestCaseData(Symbol.Create("CustomData2", SecurityType.Base, Market.COMEX), null)
|
||||
};
|
||||
|
||||
foreach (var market in Market.SupportedMarkets())
|
||||
{
|
||||
foreach (var kvp in SymbolPropertiesDatabase.FromDataFolder().GetSymbolPropertiesList(market))
|
||||
{
|
||||
var securityDatabaseKey = kvp.Key;
|
||||
if (securityDatabaseKey.SecurityType != SecurityType.FutureOption)
|
||||
{
|
||||
result.Add(new TestCaseData(Symbol.Create(securityDatabaseKey.Symbol, securityDatabaseKey.SecurityType,
|
||||
securityDatabaseKey.Market), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static DataNormalizationMode[] GetDataNormalizationModes()
|
||||
{
|
||||
return ((DataNormalizationMode[])Enum.GetValues(typeof(DataNormalizationMode)))
|
||||
.Where(x => x != DataNormalizationMode.ScaledRaw).ToArray();
|
||||
}
|
||||
|
||||
private static Func<QCAlgorithm, Security>[] FuturesTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Func<QCAlgorithm, Security>[]
|
||||
{
|
||||
(algo) => algo.AddFuture(Futures.Indices.VIX, Resolution.Minute, extendedMarketHours: true),
|
||||
(algo) => algo.AddFutureContract(Symbol.CreateFuture(Futures.Indices.VIX, Market.CFE, new DateTime(2022, 8, 1)),
|
||||
Resolution.Minute, extendedMarketHours: true)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
public class AlgorithmAddUniverseTests
|
||||
{
|
||||
[TestCaseSource(nameof(ETFConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithETFConstituentUniverseDefinitionTicker(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, false, false, true);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ETFConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithETFConstituentUniverseDefinitionTickerPython(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, false, true, true);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ETFConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithETFConstituentUniverseDefinitionSymbol(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, true, false, true);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ETFConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithETFConstituentUniverseDefinitionSymbolPython(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, true, true, true);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndexConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithIndexConstituentUniverseDefinitionTicker(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, false, false, false);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndexConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithIndexConstituentUniverseDefinitionSymbol(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, true, false, false);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndexConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithIndexConstituentUniverseDefinitionTickerPython(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, false, true, false);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndexConstituentUniverseTestCases))]
|
||||
public void AddUniverseWithIndexConstituentUniverseDefinitionSymbolPython(string ticker, string market)
|
||||
{
|
||||
AssertConstituentUniverseDefinitionsSymbol(ticker, market, true, true, false);
|
||||
}
|
||||
|
||||
private void AssertConstituentUniverseDefinitionsSymbol(string ticker, string market, bool isSymbol, bool isPython, bool isEtf)
|
||||
{
|
||||
var algo = CreateAlgorithm();
|
||||
algo.SetStartDate(2021, 8, 23);
|
||||
algo.SetEndDate(2021, 8, 24);
|
||||
|
||||
Universe constituentUniverse;
|
||||
var symbol = Symbol.Create(ticker, isEtf ? SecurityType.Equity : SecurityType.Index, market ?? Market.USA);
|
||||
|
||||
if (isSymbol && isEtf)
|
||||
{
|
||||
constituentUniverse = isPython
|
||||
? algo.Universe.ETF(symbol, algo.UniverseSettings, (PyObject)null)
|
||||
: algo.Universe.ETF(symbol, algo.UniverseSettings, CreateReturnAllFunc());
|
||||
}
|
||||
else if (isEtf)
|
||||
{
|
||||
constituentUniverse = isPython
|
||||
? algo.Universe.ETF(ticker, market, algo.UniverseSettings, (PyObject)null)
|
||||
: algo.Universe.ETF(ticker, market, algo.UniverseSettings, CreateReturnAllFunc());
|
||||
}
|
||||
else if (isSymbol)
|
||||
{
|
||||
constituentUniverse = isPython
|
||||
? algo.Universe.Index(symbol, algo.UniverseSettings, (PyObject)null)
|
||||
: algo.Universe.Index(symbol, algo.UniverseSettings, CreateReturnAllFunc());
|
||||
}
|
||||
else
|
||||
{
|
||||
constituentUniverse = isPython
|
||||
? algo.Universe.Index(ticker, market, algo.UniverseSettings, (PyObject)null)
|
||||
: algo.Universe.Index(ticker, market, algo.UniverseSettings, CreateReturnAllFunc());
|
||||
}
|
||||
|
||||
Assert.IsTrue(constituentUniverse.Configuration.Symbol.HasUnderlying);
|
||||
Assert.AreEqual(symbol, constituentUniverse.Configuration.Symbol.Underlying);
|
||||
|
||||
Assert.AreEqual(symbol.SecurityType, constituentUniverse.Configuration.Symbol.SecurityType);
|
||||
Assert.IsTrue(constituentUniverse.Configuration.Symbol.ID.Symbol.StartsWithInvariant("qc-universe-"));
|
||||
}
|
||||
|
||||
private static TestCaseData[] ETFConstituentUniverseTestCases()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TestCaseData("SPY", Market.USA),
|
||||
new TestCaseData("SPY", null),
|
||||
new TestCaseData("GDVD", Market.USA),
|
||||
new TestCaseData("GDVD", null)
|
||||
};
|
||||
}
|
||||
|
||||
private static TestCaseData[] IndexConstituentUniverseTestCases()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TestCaseData("SPX", Market.USA),
|
||||
new TestCaseData("SPX", null),
|
||||
new TestCaseData("NDX", Market.USA),
|
||||
new TestCaseData("NDX", null)
|
||||
};
|
||||
}
|
||||
|
||||
private QCAlgorithm CreateAlgorithm()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
return algo;
|
||||
}
|
||||
|
||||
private Func<IEnumerable<ETFConstituentUniverse>, IEnumerable<Symbol>> CreateReturnAllFunc()
|
||||
{
|
||||
return x => x.Select(y => y.Symbol);
|
||||
}
|
||||
|
||||
[TestCase(typeof(BaseDataCollection))]
|
||||
[TestCase(typeof(FundamentalUniverse))]
|
||||
[TestCase(typeof(ETFConstituentsUniverseFactory))]
|
||||
public void UniverseSymbolsSortInCreationOrder(Type dataType)
|
||||
{
|
||||
Symbol symbol1, symbol2, symbol3;
|
||||
if (dataType == typeof(ETFConstituentsUniverseFactory))
|
||||
{
|
||||
var composite = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
|
||||
using var universe1 = new ETFConstituentsUniverseFactory(composite, null);
|
||||
using var universe2 = new ETFConstituentsUniverseFactory(composite, null);
|
||||
using var universe3 = new ETFConstituentsUniverseFactory(composite, null);
|
||||
symbol1 = universe1.Configuration.Symbol;
|
||||
symbol2 = universe2.Configuration.Symbol;
|
||||
symbol3 = universe3.Configuration.Symbol;
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = (BaseDataCollection)Activator.CreateInstance(dataType);
|
||||
symbol1 = instance.UniverseSymbol();
|
||||
symbol2 = instance.UniverseSymbol();
|
||||
symbol3 = instance.UniverseSymbol();
|
||||
}
|
||||
|
||||
var comparer = StringComparer.OrdinalIgnoreCase;
|
||||
Assert.That(comparer.Compare(symbol1.Value, symbol2.Value) < 0);
|
||||
Assert.That(comparer.Compare(symbol2.Value, symbol3.Value) < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Benchmarks;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Statistics;
|
||||
using QuantConnect.Securities;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmBenchmarkTests
|
||||
{
|
||||
private TestBenchmarkAlgorithm _algorithm;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
Config.Reset();
|
||||
}
|
||||
|
||||
[TestCase(SecurityType.Forex)]
|
||||
[TestCase(SecurityType.Equity)]
|
||||
[TestCase(SecurityType.Crypto)]
|
||||
[TestCase(SecurityType.Index)]
|
||||
[TestCase(SecurityType.Option)]
|
||||
[TestCase(SecurityType.Future)]
|
||||
[TestCase(SecurityType.Cfd)]
|
||||
public void SetBenchmarksSecurityTypes(SecurityType securityType)
|
||||
{
|
||||
|
||||
_algorithm = BenchmarkTestSetupHandler.TestAlgorithm = new TestBenchmarkAlgorithm(securityType);
|
||||
_algorithm.StartDateToUse = new DateTime(2014, 05, 03);
|
||||
_algorithm.EndDateToUse = new DateTime(2014, 05, 04);
|
||||
|
||||
var results = AlgorithmRunner.RunLocalBacktest(nameof(TestBenchmarkAlgorithm),
|
||||
new Dictionary<string, string> { { PerformanceMetrics.TotalOrders, "0" } },
|
||||
Language.CSharp,
|
||||
AlgorithmStatus.Completed,
|
||||
setupHandler: "BenchmarkTestSetupHandler");
|
||||
|
||||
|
||||
var benchmark = _algorithm.Benchmark as SecurityBenchmark;
|
||||
Assert.IsNotNull(benchmark);
|
||||
Assert.AreEqual(securityType, benchmark.Security.Type);
|
||||
Assert.IsNull(_algorithm.RunTimeError);
|
||||
}
|
||||
|
||||
[TestCase(Resolution.Daily)]
|
||||
[TestCase(Resolution.Hour)]
|
||||
[TestCase(Resolution.Minute)]
|
||||
[TestCase(Resolution.Second)]
|
||||
[TestCase(Resolution.Daily, true)]
|
||||
[TestCase(Resolution.Hour, true)]
|
||||
[TestCase(Resolution.Minute, true)]
|
||||
[TestCase(Resolution.Second, true)]
|
||||
public void MisalignedBenchmarkAndAlgorithmTimeZones(Resolution resolution, bool useUniverseSubscription = false)
|
||||
{
|
||||
// Verify that if we have algorithm:
|
||||
// - subscribed to a daily resolution via universe or directly
|
||||
// - a benchmark with timezone that is not algorithm time zone
|
||||
// that we post an warning via log that statistics will be affected
|
||||
|
||||
// Setup a empty algorithm for the test
|
||||
var algorithm = new QCAlgorithm();
|
||||
var dataManager = new DataManagerStub(algorithm, new MockDataFeed(), liveMode: true);
|
||||
algorithm.SubscriptionManager.SetDataManager(dataManager);
|
||||
|
||||
if (useUniverseSubscription)
|
||||
{
|
||||
// Change our universe resolution
|
||||
algorithm.UniverseSettings.Resolution = resolution;
|
||||
}
|
||||
else
|
||||
{
|
||||
// subscribe to an equity in our provided resolution
|
||||
algorithm.AddEquity("AAPL", resolution);
|
||||
}
|
||||
|
||||
// Default benchmark is SPY which is NY TimeZone,
|
||||
// Set timezone to UTC.
|
||||
algorithm.SetTimeZone(DateTimeZone.Utc);
|
||||
algorithm.PostInitialize();
|
||||
|
||||
// Verify if our log is there (Should only be there in Daily case)
|
||||
switch (resolution)
|
||||
{
|
||||
case Resolution.Daily:
|
||||
if (algorithm.LogMessages.TryPeek(out string result))
|
||||
{
|
||||
Assert.IsTrue(result.Contains("Using a security benchmark of a different timezone", StringComparison.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail("Warning was not posted");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Assert.AreEqual(0, algorithm.LogMessages.Count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BenchmarkIsNotInitializeWithCustomSecurityInitializer()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var dataManager = new DataManagerStub(algorithm, new MockDataFeed());
|
||||
algorithm.SubscriptionManager.SetDataManager(dataManager);
|
||||
|
||||
var securityInitializer = new CustomSecurityInitializer();
|
||||
algorithm.SetSecurityInitializer(securityInitializer);
|
||||
|
||||
var spy = algorithm.AddEquity("SPY");
|
||||
|
||||
algorithm.SetBenchmark("AAPL");
|
||||
var aapl = (algorithm.Benchmark as SecurityBenchmark).Security;
|
||||
|
||||
algorithm.PostInitialize();
|
||||
|
||||
Assert.IsTrue(securityInitializer.InitializedSecurities.Contains(spy));
|
||||
Assert.IsFalse(securityInitializer.InitializedSecurities.Contains(aapl));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BenchmarkIsNotAffectedBySecuritySeederDataNormalizationMode()
|
||||
{
|
||||
var algorithm = new TestBenchmarkDataNormalizationModeAlgorithm();
|
||||
BenchmarkTestSetupHandler.TestAlgorithm = algorithm;
|
||||
algorithm.StartDateToUse = new DateTime(2013, 10, 07);
|
||||
algorithm.EndDateToUse = new DateTime(2013, 10, 11);
|
||||
|
||||
var results = AlgorithmRunner.RunLocalBacktest(nameof(TestBenchmarkAlgorithm),
|
||||
new Dictionary<string, string> { { PerformanceMetrics.TotalOrders, "0" } },
|
||||
Language.CSharp,
|
||||
AlgorithmStatus.Completed,
|
||||
setupHandler: nameof(BenchmarkTestSetupHandler));
|
||||
|
||||
var benchmark = algorithm.Benchmark as SecurityBenchmark;
|
||||
Assert.IsNotNull(benchmark);
|
||||
Assert.AreEqual(Symbols.SPY, benchmark.Security.Symbol);
|
||||
|
||||
// All values must be between 142 and 148 (expected adjusted data for the time range) for the benchmark
|
||||
Assert.IsTrue(algorithm.BenchmarkValues.All(x => x >= 142m && x <= 148m),
|
||||
$"Benchmark values are:\n{string.Join('\n', algorithm.BenchmarkValues)}");
|
||||
}
|
||||
|
||||
public class BenchmarkTestSetupHandler : AlgorithmRunner.RegressionSetupHandlerWrapper
|
||||
{
|
||||
public static TestBenchmarkAlgorithm TestAlgorithm { get; set; }
|
||||
|
||||
public override IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
|
||||
{
|
||||
Algorithm = TestAlgorithm;
|
||||
return Algorithm;
|
||||
}
|
||||
}
|
||||
|
||||
public class TestBenchmarkAlgorithm : QCAlgorithm
|
||||
{
|
||||
private Symbol _symbol;
|
||||
|
||||
public SecurityType SecurityType { get; set; }
|
||||
|
||||
public DateTime StartDateToUse { get; set; }
|
||||
|
||||
public DateTime EndDateToUse { get; set; }
|
||||
|
||||
public int WarmUpDataCount { get; set; }
|
||||
|
||||
public TestBenchmarkAlgorithm(SecurityType securityType)
|
||||
{
|
||||
SecurityType = securityType;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(StartDateToUse);
|
||||
SetEndDate(EndDateToUse);
|
||||
|
||||
_symbol = Symbols.GetBySecurityType(SecurityType);
|
||||
AddSecurity(_symbol);
|
||||
SetBenchmark(_symbol);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestBenchmarkDataNormalizationModeAlgorithm : TestBenchmarkAlgorithm
|
||||
{
|
||||
public List<decimal> BenchmarkValues { get; } = new();
|
||||
|
||||
public TestBenchmarkDataNormalizationModeAlgorithm()
|
||||
: base(SecurityType.Equity)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(StartDateToUse);
|
||||
SetEndDate(EndDateToUse);
|
||||
|
||||
UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
|
||||
// If the benchmark is initialized using this security initializer, the security seeder would source data
|
||||
// for using the data normalization mode from the UniverseSettings, which is set to Raw
|
||||
SetSecurityInitializer(new BrokerageModelSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)));
|
||||
|
||||
SetBenchmark("SPY");
|
||||
|
||||
Schedule.On(DateRules.EveryDay(), TimeRules.Every(TimeSpan.FromHours(1)), () =>
|
||||
{
|
||||
var value = (Benchmark as SecurityBenchmark).Evaluate(UtcTime);
|
||||
BenchmarkValues.Add(value);
|
||||
Log($"Benchmark: {value}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomSecurityInitializer : ISecurityInitializer
|
||||
{
|
||||
public HashSet<Security> InitializedSecurities { get; } = new();
|
||||
|
||||
public void Initialize(Security security)
|
||||
{
|
||||
InitializedSecurities.Add(security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmChainsTest
|
||||
{
|
||||
private QCAlgorithm _algorithm;
|
||||
private BacktestingOptionChainProvider _optionChainProvider;
|
||||
private BacktestingFutureChainProvider _futureChainProvider;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SetHistoryProvider(TestGlobals.HistoryProvider);
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
|
||||
_optionChainProvider = GetOptionChainProvider(TestGlobals.HistoryProvider);
|
||||
_algorithm.SetOptionChainProvider(_optionChainProvider);
|
||||
|
||||
_futureChainProvider = GetFutureChainProvider(TestGlobals.HistoryProvider);
|
||||
_algorithm.SetFutureChainProvider(_futureChainProvider);
|
||||
}
|
||||
|
||||
private static TestCaseData[] OptionChainTestCases => new TestCaseData[]
|
||||
{
|
||||
// By underlying
|
||||
new(Symbols.AAPL, new DateTime(2014, 06, 06, 12, 0, 0)),
|
||||
new(Symbols.SPX, new DateTime(2021, 01, 04, 12, 0, 0)),
|
||||
// By canonical
|
||||
new(Symbol.CreateCanonicalOption(Symbols.AAPL), new DateTime(2014, 06, 06, 12, 0, 0)),
|
||||
new(Symbol.CreateCanonicalOption(Symbols.SPX), new DateTime(2021, 01, 04, 12, 0, 0)),
|
||||
new(Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 6, 19)), new DateTime(2020, 01, 05, 12, 0, 0)),
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(OptionChainTestCases))]
|
||||
public void GetsFullDataOptionChain(Symbol symbol, DateTime date)
|
||||
{
|
||||
_algorithm.SetDateTime(date.ConvertToUtc(_algorithm.TimeZone));
|
||||
var optionContractsData = _algorithm.OptionChain(symbol).ToList();
|
||||
Assert.IsNotEmpty(optionContractsData);
|
||||
|
||||
var optionContractsSymbols = _optionChainProvider.GetOptionContractList(symbol, date.Date).ToList();
|
||||
|
||||
CollectionAssert.AreEquivalent(optionContractsSymbols, optionContractsData.Select(x => x.Symbol));
|
||||
}
|
||||
|
||||
private static TestCaseData[] PythonOptionChainTestCases => OptionChainTestCases.SelectMany(x =>
|
||||
{
|
||||
return new object[] { true, false }.Select(y => new TestCaseData(x.OriginalArguments.Concat(new[] { y }).ToArray()));
|
||||
}).ToArray();
|
||||
|
||||
[TestCaseSource(nameof(PythonOptionChainTestCases))]
|
||||
public void GetsFullDataOptionChainAsDataFrame(Symbol symbol, DateTime date, bool flatten)
|
||||
{
|
||||
_algorithm.SetPandasConverter();
|
||||
_algorithm.SetDateTime(date.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
using var _ = Py.GIL();
|
||||
|
||||
using var dataFrame = _algorithm.OptionChain(symbol, flatten).DataFrame;
|
||||
List<Symbol> symbols = null;
|
||||
|
||||
var expectedOptionContractsSymbols = _optionChainProvider.GetOptionContractList(symbol, date.Date).ToList();
|
||||
|
||||
if (flatten)
|
||||
{
|
||||
symbols = AssertFlattenedSingleChainDataFrame(dataFrame, symbol, hasCanonicalIndex: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dfLength = dataFrame.GetAttr("shape")[0].GetAndDispose<int>();
|
||||
Assert.AreEqual(1, dfLength);
|
||||
|
||||
symbols = AssertUnflattenedSingleChainDataFrame<OptionContract>(dataFrame, symbol);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(symbols);
|
||||
CollectionAssert.AreEquivalent(expectedOptionContractsSymbols, symbols);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetsMultipleFullDataOptionChainAsDataFrame([Values] bool flatten)
|
||||
{
|
||||
var date = new DateTime(2015, 12, 24, 12, 0, 0);
|
||||
_algorithm.SetPandasConverter();
|
||||
_algorithm.SetDateTime(date.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
using var _ = Py.GIL();
|
||||
|
||||
var symbols = new[] { Symbols.GOOG, Symbols.SPX };
|
||||
using var dataFrame = _algorithm.OptionChains(symbols, flatten).DataFrame;
|
||||
|
||||
var expectedOptionChains = symbols.ToDictionary(x => x, x => _optionChainProvider.GetOptionContractList(x, date).ToList());
|
||||
|
||||
AssertMultiChainsDataFrame<OptionContract>(flatten, symbols, dataFrame, expectedOptionChains, isOptionChain: true);
|
||||
}
|
||||
|
||||
private static List<Symbol> AssertFlattenedSingleChainDataFrame(PyObject dataFrame, Symbol symbol, bool hasCanonicalIndex = true,
|
||||
bool isOptionChain = true)
|
||||
{
|
||||
PyObject subDataFrame = null;
|
||||
try
|
||||
{
|
||||
subDataFrame = GetCanonicalSubDataFrame(dataFrame, symbol, isOptionChain, hasCanonicalIndex, out var canonicalSymbol);
|
||||
|
||||
using var dfColumns = subDataFrame.GetAttr("columns");
|
||||
using var dfColumnsList = dfColumns.InvokeMethod("tolist");
|
||||
using var dfColumnsIterator = dfColumnsList.GetIterator();
|
||||
var columns = new List<string>();
|
||||
foreach (PyObject item in dfColumnsIterator)
|
||||
{
|
||||
columns.Add(item.ToString());
|
||||
item.DisposeSafely();
|
||||
}
|
||||
|
||||
var expectedColumns = canonicalSymbol.SecurityType switch
|
||||
{
|
||||
SecurityType.Future => new[] { "expiry", "volume", "askprice", "asksize", "bidprice", "bidsize", "lastprice", "openinterest" },
|
||||
SecurityType.FutureOption => new[]
|
||||
{
|
||||
"expiry", "strike", "scaledstrike", "right", "style", "volume", "askprice", "asksize", "bidprice", "bidsize",
|
||||
"lastprice", "underlyingsymbol", "underlyinglastprice"
|
||||
},
|
||||
_ => new[]
|
||||
{
|
||||
"expiry", "strike", "scaledstrike", "right", "style", "volume", "askprice", "asksize", "bidprice", "bidsize",
|
||||
"lastprice", "openinterest", "impliedvolatility", "delta", "gamma", "vega", "theta", "rho",
|
||||
"underlyingsymbol", "underlyinglastprice"
|
||||
}
|
||||
};
|
||||
|
||||
CollectionAssert.AreEquivalent(expectedColumns, columns);
|
||||
using var dfIndex = subDataFrame.GetAttr("index");
|
||||
|
||||
return dfIndex.InvokeMethod("tolist").GetAndDispose<List<Symbol>>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (hasCanonicalIndex)
|
||||
{
|
||||
subDataFrame?.DisposeSafely();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Symbol> AssertUnflattenedSingleChainDataFrame<T>(PyObject dataFrame, Symbol symbol, bool isOptionChain = true)
|
||||
where T : BaseContract
|
||||
{
|
||||
using var subDataFrame = GetCanonicalSubDataFrame(dataFrame, symbol, isOptionChain, true, out _);
|
||||
|
||||
using var dfOptionChainList = subDataFrame["contracts"];
|
||||
var contracts = dfOptionChainList.GetAndDispose<IEnumerable<T>>().ToList();
|
||||
|
||||
return contracts.Select(x => x.Symbol).ToList();
|
||||
}
|
||||
|
||||
private static PyObject GetCanonicalSubDataFrame(PyObject dataFrame, Symbol symbol, bool forOptionChain, bool hasCanonicalIndex,
|
||||
out Symbol canonicalSymbol)
|
||||
{
|
||||
canonicalSymbol = symbol;
|
||||
if (canonicalSymbol.SecurityType == SecurityType.Future && !forOptionChain)
|
||||
{
|
||||
canonicalSymbol = canonicalSymbol.Canonical;
|
||||
}
|
||||
else if (!canonicalSymbol.SecurityType.IsOption())
|
||||
{
|
||||
canonicalSymbol = Symbol.CreateCanonicalOption(symbol);
|
||||
}
|
||||
|
||||
if (!hasCanonicalIndex)
|
||||
{
|
||||
return dataFrame;
|
||||
}
|
||||
|
||||
using var pySymbol = canonicalSymbol.ToPython();
|
||||
return dataFrame.GetAttr("loc")[pySymbol];
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetOptionChainApisTestData()
|
||||
{
|
||||
var indexSymbol = Symbols.SPX;
|
||||
var equitySymbol = Symbols.GOOG;
|
||||
var futureSymbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 6, 19));
|
||||
|
||||
foreach (var withSecurityAdded in new[] { true, false })
|
||||
{
|
||||
var extendedMarketHoursCases = withSecurityAdded ? [true, false] : new[] { false };
|
||||
foreach (var withExtendedMarketHours in extendedMarketHoursCases)
|
||||
{
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 23, 23, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(indexSymbol, new DateTime(2015, 12, 24, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
|
||||
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(equitySymbol, new DateTime(2015, 12, 24, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 04, 23, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 05, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 0, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 1, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 2, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 6, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 12, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(futureSymbol, new DateTime(2020, 01, 06, 16, 0, 0), withSecurityAdded, withExtendedMarketHours);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetOptionChainApisTestData))]
|
||||
public void OptionChainApisAreConsistent(Symbol symbol, DateTime dateTime, bool withSecurityAdded, bool withExtendedMarketHours)
|
||||
{
|
||||
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
if (withSecurityAdded)
|
||||
{
|
||||
if (symbol.SecurityType == SecurityType.Future)
|
||||
{
|
||||
var future = _algorithm.AddFuture(symbol.ID.Symbol, extendedMarketHours: withExtendedMarketHours);
|
||||
_algorithm.AddFutureOption(future.Symbol);
|
||||
_algorithm.AddFutureContract(symbol, extendedMarketHours: withExtendedMarketHours);
|
||||
}
|
||||
else
|
||||
{
|
||||
_algorithm.AddSecurity(symbol, extendedMarketHours: withExtendedMarketHours);
|
||||
}
|
||||
}
|
||||
|
||||
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var chainFromAlgorithmApi = _algorithm.OptionChain(symbol).Select(x => x.Symbol).ToList();
|
||||
var chainFromChainProviderApi = _optionChainProvider.GetOptionContractList(symbol,
|
||||
dateTime.ConvertTo(_algorithm.TimeZone, exchange.TimeZone)).ToList();
|
||||
|
||||
CollectionAssert.IsNotEmpty(chainFromAlgorithmApi);
|
||||
CollectionAssert.AreEquivalent(chainFromAlgorithmApi, chainFromChainProviderApi);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> GetFutureChainApisTestData()
|
||||
{
|
||||
var futureSymbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 6, 19));
|
||||
var canonicalFutureSymbol = futureSymbol.Canonical;
|
||||
var futureOptionSymbol = Symbol.CreateOption(futureSymbol, futureSymbol.ID.Market, OptionStyle.American, OptionRight.Call,
|
||||
75m, new DateTime(2020, 5, 19));
|
||||
|
||||
foreach (var symbol in new[] { futureSymbol, canonicalFutureSymbol, futureOptionSymbol })
|
||||
{
|
||||
foreach (var withFutureAdded in new[] { true, false })
|
||||
{
|
||||
var extendedMarketHoursCases = withFutureAdded ? [true, false] : new[] { false };
|
||||
foreach (var withExtendedMarketHours in extendedMarketHoursCases)
|
||||
{
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 06, 23, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 0, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 1, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 2, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 6, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 12, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
yield return new TestCaseData(symbol, new DateTime(2013, 10, 07, 16, 0, 0), withFutureAdded, withExtendedMarketHours);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetFutureChainApisTestData))]
|
||||
public void FuturesChainApisAreConsistent(Symbol symbol, DateTime dateTime, bool withFutureAdded, bool withExtendedMarketHours)
|
||||
{
|
||||
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
if (withFutureAdded)
|
||||
{
|
||||
// It should work regardless of whether the future is added to the algorithm
|
||||
_algorithm.AddFuture(symbol.ID.Symbol, extendedMarketHours: withExtendedMarketHours);
|
||||
}
|
||||
|
||||
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var chainFromAlgorithmApi = _algorithm.FuturesChain(symbol).Select(x => x.Symbol).ToList();
|
||||
var chainFromChainProviderApi = _futureChainProvider.GetFutureContractList(symbol,
|
||||
dateTime.ConvertTo(_algorithm.TimeZone, exchange.TimeZone)).ToList();
|
||||
|
||||
CollectionAssert.IsNotEmpty(chainFromAlgorithmApi);
|
||||
CollectionAssert.AreEquivalent(chainFromAlgorithmApi, chainFromChainProviderApi);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetsFullDataFuturesChainAsDataFrame([Values] bool flatten, [Values] bool withFutureAdded)
|
||||
{
|
||||
_algorithm.SetPandasConverter();
|
||||
var date = new DateTime(2013, 10, 07);
|
||||
_algorithm.SetDateTime(date.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
using var _ = Py.GIL();
|
||||
|
||||
// It should work regardless of whether the future is added to the algorithm
|
||||
var symbol = withFutureAdded ? _algorithm.AddFuture(Futures.Indices.SP500EMini).Symbol : Symbols.ES_Future_Chain;
|
||||
using var dataFrame = _algorithm.FuturesChain(symbol, flatten).DataFrame;
|
||||
List<Symbol> symbols = null;
|
||||
|
||||
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var exchangeTime = date.ConvertTo(_algorithm.TimeZone, exchange.TimeZone);
|
||||
var expectedFutureContractSymbols = _futureChainProvider.GetFutureContractList(symbol, exchangeTime).ToList();
|
||||
|
||||
if (flatten)
|
||||
{
|
||||
symbols = AssertFlattenedSingleChainDataFrame(dataFrame, symbol, hasCanonicalIndex: false, isOptionChain: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dfLength = dataFrame.GetAttr("shape")[0].GetAndDispose<int>();
|
||||
Assert.AreEqual(1, dfLength);
|
||||
|
||||
symbols = AssertUnflattenedSingleChainDataFrame<FuturesContract>(dataFrame, symbol, isOptionChain: false);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(symbols);
|
||||
CollectionAssert.AreEquivalent(expectedFutureContractSymbols, symbols);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetsMultipleFullDataFuturesChainsAsDataFrame([Values] bool flatten, [Values] bool withFutureAdded)
|
||||
{
|
||||
var dateTime = new DateTime(2013, 10, 07, 12, 0, 0);
|
||||
_algorithm.SetPandasConverter();
|
||||
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
using var _ = Py.GIL();
|
||||
|
||||
var symbols = withFutureAdded
|
||||
? new[] { Symbols.ES_Future_Chain, Symbols.CreateFuturesCanonicalSymbol("GC") }
|
||||
: new[] { _algorithm.AddFuture(Futures.Indices.SP500EMini).Symbol, _algorithm.AddFuture("GC").Symbol };
|
||||
using var dataFrame = _algorithm.FuturesChains(symbols, flatten).DataFrame;
|
||||
|
||||
var expectedFuturesChains = symbols.ToDictionary(x => x, x =>
|
||||
{
|
||||
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(x.ID.Market, x, x.SecurityType);
|
||||
return _futureChainProvider.GetFutureContractList(x, dateTime.ConvertTo(_algorithm.TimeZone, exchange.TimeZone)).ToList();
|
||||
});
|
||||
|
||||
AssertMultiChainsDataFrame<FuturesContract>(flatten, symbols, dataFrame, expectedFuturesChains, isOptionChain: false);
|
||||
}
|
||||
|
||||
private static TestCaseData[] FillForwardTestData => new[] { true, false }
|
||||
.Select(useAlgorithmApi => new TestCaseData[]
|
||||
{
|
||||
new(Symbols.SPY_Option_Chain, new DateTime(2024, 01, 03), useAlgorithmApi),
|
||||
new(Symbol.CreateCanonicalOption(Symbols.SPX), new DateTime(2021, 01, 08), useAlgorithmApi),
|
||||
new(Symbol.CreateCanonicalOption(Symbols.CreateFutureSymbol(Futures.Indices.SP500EMini, new DateTime(2020, 03, 20))),
|
||||
new DateTime(2020, 01, 07),
|
||||
useAlgorithmApi),
|
||||
new(Symbols.ES_Future_Chain, new DateTime(2020, 01, 07), useAlgorithmApi)
|
||||
})
|
||||
.SelectMany(x => x)
|
||||
.ToArray();
|
||||
|
||||
[TestCaseSource(nameof(FillForwardTestData))]
|
||||
public void FillForwardsChainFromPreviousTradableDateIfCurrentOneIsNotAvailable(Symbol symbol, DateTime dateTime, bool useAlgorithmApi)
|
||||
{
|
||||
var historyProvider = new FillForwardTestHistoryProvider(_algorithm.HistoryProvider);
|
||||
_algorithm.SetHistoryProvider(historyProvider);
|
||||
_algorithm.SetOptionChainProvider(GetOptionChainProvider(historyProvider));
|
||||
_algorithm.SetFutureChainProvider(GetFutureChainProvider(historyProvider));
|
||||
|
||||
var exchange = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
_algorithm.SetTimeZone(exchange.TimeZone);
|
||||
|
||||
// Get the previous tradable date chain
|
||||
var prevTradableDate = exchange.GetPreviousTradingDay(dateTime);
|
||||
|
||||
historyProvider.RequestDateTime = prevTradableDate;
|
||||
historyProvider.SimulateMissingFile = false;
|
||||
historyProvider.Requests.Clear();
|
||||
var prevDateChain = GetChain(symbol, prevTradableDate, useAlgorithmApi);
|
||||
|
||||
Assert.AreEqual(1, historyProvider.Requests.Count);
|
||||
Assert.AreEqual(1, historyProvider.Requests[0].Count);
|
||||
|
||||
// Get the current date chain, which should be fill-forwarded from the previous date
|
||||
// because the universe file for the current date is missing
|
||||
historyProvider.RequestDateTime = dateTime;
|
||||
historyProvider.SimulateMissingFile = true;
|
||||
historyProvider.Requests.Clear();
|
||||
var currentDateChain = GetChain(symbol, dateTime, useAlgorithmApi);
|
||||
|
||||
Assert.AreEqual(2, historyProvider.Requests.Count);
|
||||
var requestList1 = historyProvider.Requests[0];
|
||||
Assert.AreEqual(1, requestList1.Count);
|
||||
var requestList2 = historyProvider.Requests[1];
|
||||
Assert.AreEqual(1, requestList2.Count);
|
||||
var request1 = requestList1[0];
|
||||
var request2 = requestList2[0];
|
||||
Assert.AreEqual(request1.EndTimeLocal, request2.EndTimeLocal);
|
||||
Assert.Less(request2.StartTimeLocal, request1.StartTimeLocal);
|
||||
|
||||
Assert.IsNotEmpty(currentDateChain);
|
||||
Assert.IsNotEmpty(prevDateChain);
|
||||
CollectionAssert.IsSubsetOf(currentDateChain, prevDateChain);
|
||||
CollectionAssert.AreEquivalent(currentDateChain, prevDateChain.Where(symbol => symbol.ID.Date >= dateTime));
|
||||
}
|
||||
|
||||
private List<Symbol> GetChain(Symbol symbol, DateTime date, bool useAlgorithmApi)
|
||||
{
|
||||
if (useAlgorithmApi)
|
||||
{
|
||||
_algorithm.SetDateTime(date.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
return symbol.SecurityType == SecurityType.Future
|
||||
? _algorithm.FuturesChain(symbol).Select(x => x.Symbol).ToList()
|
||||
: _algorithm.OptionChain(symbol).Select(x => x.Symbol).ToList();
|
||||
}
|
||||
|
||||
return symbol.SecurityType == SecurityType.Future
|
||||
? _algorithm.FutureChainProvider.GetFutureContractList(symbol, date).ToList()
|
||||
: _algorithm.OptionChainProvider.GetOptionContractList(symbol, date).ToList();
|
||||
}
|
||||
|
||||
private static void AssertMultiChainsDataFrame<T>(bool flatten, Symbol[] symbols, PyObject dataFrame,
|
||||
Dictionary<Symbol, List<Symbol>> expectedChains, bool isOptionChain)
|
||||
where T : BaseContract
|
||||
{
|
||||
var chainsTotalCount = expectedChains.Values.Sum(x => x.Count);
|
||||
|
||||
if (flatten)
|
||||
{
|
||||
var dfLength = dataFrame.GetAttr("shape")[0].GetAndDispose<int>();
|
||||
Assert.AreEqual(chainsTotalCount, dfLength);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var (symbol, expectedChain) in expectedChains)
|
||||
{
|
||||
var chainSymbols = AssertFlattenedSingleChainDataFrame(dataFrame, symbol, isOptionChain: isOptionChain);
|
||||
|
||||
Assert.IsNotNull(chainSymbols);
|
||||
CollectionAssert.AreEquivalent(expectedChain, chainSymbols);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var dfLength = dataFrame.GetAttr("shape")[0].GetAndDispose<int>();
|
||||
Assert.AreEqual(symbols.Length, dfLength);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var (symbol, expectedChain) in expectedChains)
|
||||
{
|
||||
var chainSymbols = AssertUnflattenedSingleChainDataFrame<T>(dataFrame, symbol, isOptionChain);
|
||||
|
||||
Assert.IsNotNull(chainSymbols);
|
||||
CollectionAssert.AreEquivalent(expectedChain, chainSymbols);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class FillForwardTestHistoryProvider : IHistoryProvider
|
||||
{
|
||||
private readonly IHistoryProvider _historyProvider;
|
||||
|
||||
public DateTime RequestDateTime { get; set; }
|
||||
|
||||
public bool SimulateMissingFile { get; set; }
|
||||
|
||||
public List<List<HistoryRequest>> Requests { get; } = new();
|
||||
|
||||
public int DataPointCount => _historyProvider.DataPointCount;
|
||||
|
||||
public event EventHandler<InvalidConfigurationDetectedEventArgs> InvalidConfigurationDetected
|
||||
{
|
||||
add { _historyProvider.InvalidConfigurationDetected += value; }
|
||||
remove { _historyProvider.InvalidConfigurationDetected -= value; }
|
||||
}
|
||||
|
||||
public event EventHandler<NumericalPrecisionLimitedEventArgs> NumericalPrecisionLimited
|
||||
{
|
||||
add { _historyProvider.NumericalPrecisionLimited += value; }
|
||||
remove { _historyProvider.NumericalPrecisionLimited -= value; }
|
||||
}
|
||||
|
||||
public event EventHandler<DownloadFailedEventArgs> DownloadFailed
|
||||
{
|
||||
add { _historyProvider.DownloadFailed += value; }
|
||||
remove { _historyProvider.DownloadFailed -= value; }
|
||||
}
|
||||
|
||||
public event EventHandler<ReaderErrorDetectedEventArgs> ReaderErrorDetected
|
||||
{
|
||||
add { _historyProvider.ReaderErrorDetected += value; }
|
||||
remove { _historyProvider.ReaderErrorDetected -= value; }
|
||||
}
|
||||
|
||||
public event EventHandler<StartDateLimitedEventArgs> StartDateLimited
|
||||
{
|
||||
add { _historyProvider.StartDateLimited += value; }
|
||||
remove { _historyProvider.StartDateLimited -= value; }
|
||||
}
|
||||
|
||||
public FillForwardTestHistoryProvider(IHistoryProvider historyProvider)
|
||||
{
|
||||
_historyProvider = historyProvider;
|
||||
}
|
||||
|
||||
public IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
|
||||
{
|
||||
// This test history provider will always be used for single requests
|
||||
var historyRequests = requests.ToList();
|
||||
Assert.AreEqual(1, historyRequests.Count);
|
||||
Requests.Add(historyRequests);
|
||||
|
||||
var history = _historyProvider.GetHistory(historyRequests, sliceTimeZone).ToList();
|
||||
|
||||
// Let's ditch the last one to simulate a missing universe file
|
||||
var toSkip = 0;
|
||||
if (SimulateMissingFile)
|
||||
{
|
||||
if (Requests.Count == 1)
|
||||
{
|
||||
toSkip = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
toSkip = Requests.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return history.SkipLast(toSkip);
|
||||
}
|
||||
|
||||
public void Initialize(HistoryProviderInitializeParameters parameters)
|
||||
{
|
||||
_historyProvider.Initialize(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
private static BacktestingOptionChainProvider GetOptionChainProvider(IHistoryProvider historyProvider)
|
||||
{
|
||||
var initParameters = new ChainProviderInitializeParameters(TestGlobals.MapFileProvider, historyProvider);
|
||||
var optionChainProvider = new BacktestingOptionChainProvider();
|
||||
optionChainProvider.Initialize(initParameters);
|
||||
return optionChainProvider;
|
||||
}
|
||||
|
||||
private static BacktestingFutureChainProvider GetFutureChainProvider(IHistoryProvider historyProvider)
|
||||
{
|
||||
var initParameters = new ChainProviderInitializeParameters(TestGlobals.MapFileProvider, historyProvider);
|
||||
var futureChainProvider = new BacktestingFutureChainProvider();
|
||||
futureChainProvider.Initialize(initParameters);
|
||||
return futureChainProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
// For now these tests are excluded from the Travis build because of occasional web server errors.
|
||||
[TestFixture, Category("TravisExclude")]
|
||||
public class AlgorithmDownloadTests
|
||||
{
|
||||
[Test, Parallelizable(ParallelScope.Self)]
|
||||
public void Download_Without_Parameters_Successfully()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
using var api = new Api.Api();
|
||||
algo.SetApi(api);
|
||||
var content = string.Empty;
|
||||
Assert.DoesNotThrow(() => content = algo.Download("https://www.quantconnect.com/"));
|
||||
Assert.IsNotEmpty(content);
|
||||
}
|
||||
|
||||
[Test, Parallelizable(ParallelScope.Self)]
|
||||
public void Download_With_CSharp_Parameter_Successfully()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
using var api = new Api.Api();
|
||||
algo.SetApi(api);
|
||||
|
||||
var byteKey = Encoding.ASCII.GetBytes($"UserName:Password");
|
||||
var headers = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("Authorization", $"Basic ({Convert.ToBase64String(byteKey)})")
|
||||
};
|
||||
|
||||
var content = string.Empty;
|
||||
Assert.DoesNotThrow(() => content = algo.Download("https://www.quantconnect.com/", headers));
|
||||
Assert.IsNotEmpty(content);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_With_Python_Parameter_Successfully()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
using var api = new Api.Api();
|
||||
algo.SetApi(api);
|
||||
|
||||
var byteKey = Encoding.ASCII.GetBytes($"UserName:Password");
|
||||
var value = $"Basic ({Convert.ToBase64String(byteKey)})";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
using var headers = new PyDict();
|
||||
headers.SetItem("Authorization".ToPython(), value.ToPython());
|
||||
|
||||
var content = string.Empty;
|
||||
Assert.DoesNotThrow(() => content = algo.Download("https://www.quantconnect.com/", headers));
|
||||
Assert.IsNotEmpty(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmGetParameterTests
|
||||
{
|
||||
[TestCase(Language.CSharp, "numeric_parameter", 2, false)]
|
||||
[TestCase(Language.CSharp, "not_a_parameter", 2, true)]
|
||||
[TestCase(Language.CSharp, "string_parameter", 2, true)]
|
||||
[TestCase(Language.Python, "numeric_parameter", 2, false)]
|
||||
[TestCase(Language.Python, "not_a_parameter", 2, true)]
|
||||
[TestCase(Language.Python, "string_parameter", 2, true)]
|
||||
public void GetParameterConvertsToNumericTypes(Language language, string parameterName, int defaultValue, bool shouldReturnDefaultValue)
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "numeric_parameter", "1" },
|
||||
{ "string_parameter", "string value" },
|
||||
};
|
||||
var doubleDefaultValue = Convert.ToDouble(defaultValue);
|
||||
var decimalDefaultValue = Convert.ToDecimal(defaultValue);
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetParameters(parameters);
|
||||
|
||||
var intValue = algorithm.GetParameter(parameterName, defaultValue);
|
||||
var doubleValue = algorithm.GetParameter(parameterName, doubleDefaultValue);
|
||||
var decimalValue = algorithm.GetParameter(parameterName, decimalDefaultValue);
|
||||
|
||||
Assert.AreEqual(typeof(int), intValue.GetType());
|
||||
Assert.AreEqual(typeof(double), doubleValue.GetType());
|
||||
Assert.AreEqual(typeof(decimal), decimalValue.GetType());
|
||||
|
||||
// If the parameter is not found or is not numeric, the default value should be returned
|
||||
if (!shouldReturnDefaultValue && parameters.TryGetValue(parameterName, out var parameterValue))
|
||||
{
|
||||
Assert.AreEqual(int.Parse(parameterValue), intValue);
|
||||
Assert.AreEqual(double.Parse(parameterValue), doubleValue);
|
||||
Assert.AreEqual(decimal.Parse(parameterValue), decimalValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(defaultValue, intValue);
|
||||
Assert.AreEqual(doubleDefaultValue, doubleValue);
|
||||
Assert.AreEqual(decimalDefaultValue, decimalValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getAlgorithm():
|
||||
return QCAlgorithm()
|
||||
|
||||
def isInt(value):
|
||||
return isinstance(value, int)
|
||||
|
||||
def isFloat(value):
|
||||
return isinstance(value, float)
|
||||
");
|
||||
|
||||
var getAlgorithm = testModule.GetAttr("getAlgorithm");
|
||||
var algorithm = getAlgorithm.Invoke();
|
||||
algorithm.GetAttr("SetParameters").Invoke(PyDict.FromManagedObject(parameters));
|
||||
|
||||
var intValue = algorithm.GetAttr("GetParameter").Invoke(parameterName.ToPython(), defaultValue.ToPython());
|
||||
var doubleValue = algorithm.GetAttr("GetParameter").Invoke(parameterName.ToPython(), doubleDefaultValue.ToPython());
|
||||
var decimalValue = algorithm.GetAttr("GetParameter").Invoke(parameterName.ToPython(), decimalDefaultValue.ToPython());
|
||||
|
||||
Assert.IsTrue(testModule.GetAttr("isInt").Invoke(intValue).As<bool>(),
|
||||
$"Expected 'intValue' to be of type int but was {intValue.GetPythonType().ToString()} instead");
|
||||
Assert.IsTrue(testModule.GetAttr("isFloat").Invoke(doubleValue).As<bool>(),
|
||||
$"Expected 'doubleValue' to be of type float but was {doubleValue.GetPythonType().ToString()} instead");
|
||||
Assert.IsTrue(testModule.GetAttr("isFloat").Invoke(decimalValue).As<bool>(),
|
||||
$"Expected 'decimalValue' to be of type float but was {decimalValue.GetPythonType().ToString()} instead");
|
||||
|
||||
// If the parameter is not found or is not numeric, the default value should be returned
|
||||
if (!shouldReturnDefaultValue && parameters.TryGetValue(parameterName, out var parameterValue))
|
||||
{
|
||||
Assert.AreEqual(int.Parse(parameterValue), intValue.As<int>());
|
||||
Assert.AreEqual(double.Parse(parameterValue), doubleValue.As<double>());
|
||||
Assert.AreEqual(decimal.Parse(parameterValue), decimalValue.As<decimal>());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(defaultValue, intValue.As<int>());
|
||||
Assert.AreEqual(doubleDefaultValue, doubleValue.As<double>());
|
||||
Assert.AreEqual(decimalDefaultValue, decimalValue.As<decimal>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, "numeric_parameter")]
|
||||
[TestCase(Language.CSharp, "string_parameter")]
|
||||
[TestCase(Language.CSharp, "not_a_parameter")]
|
||||
[TestCase(Language.Python, "numeric_parameter")]
|
||||
[TestCase(Language.Python, "string_parameter")]
|
||||
[TestCase(Language.Python, "not_a_parameter")]
|
||||
public void GetsParameterWithoutADefaultValue(Language language, string parameterName)
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "numeric_parameter", "1" },
|
||||
{ "string_parameter", "string value" },
|
||||
};
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetParameters(parameters);
|
||||
var parameterWithoutDefault = algorithm.GetParameter(parameterName);
|
||||
var parameterWithNullDefault = algorithm.GetParameter(parameterName, null);
|
||||
|
||||
if (parameters.TryGetValue(parameterName, out var parameterValue))
|
||||
{
|
||||
Assert.AreEqual(typeof(string), parameterWithoutDefault.GetType());
|
||||
Assert.AreEqual(typeof(string), parameterWithNullDefault.GetType());
|
||||
Assert.AreEqual(parameterValue, parameterWithoutDefault);
|
||||
Assert.AreEqual(parameterValue, parameterWithNullDefault);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(parameterWithoutDefault);
|
||||
Assert.IsNull(parameterWithNullDefault);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getAlgorithm():
|
||||
return QCAlgorithm()
|
||||
|
||||
def isString(value):
|
||||
return isinstance(value, str)
|
||||
");
|
||||
|
||||
dynamic getAlgorithm = testModule.GetAttr("getAlgorithm");
|
||||
dynamic algorithm = getAlgorithm();
|
||||
algorithm.SetParameters(PyDict.FromManagedObject(parameters));
|
||||
dynamic parameterWithoutDefault = algorithm.GetParameter(parameterName.ToPython());
|
||||
dynamic parameterWithNullDefault = algorithm.GetParameter(parameterName.ToPython(), null);
|
||||
|
||||
if (parameters.TryGetValue(parameterName, out var parameterValue))
|
||||
{
|
||||
dynamic isString = testModule.GetAttr("isString");
|
||||
Assert.IsTrue(isString(parameterWithoutDefault).As<bool>(),
|
||||
$"Expected 'parameterWithoutDefault' to be of type string but was {parameterWithoutDefault.GetPythonType().ToString()} instead");
|
||||
Assert.IsTrue(isString(parameterWithNullDefault).As<bool>(),
|
||||
$"Expected 'parameterWithNullDefault' to be of type string but was {parameterWithNullDefault.GetPythonType().ToString()} instead");
|
||||
Assert.AreEqual(parameterValue, parameterWithoutDefault.As<string>());
|
||||
Assert.AreEqual(parameterValue, parameterWithNullDefault.As<string>());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(parameterWithoutDefault);
|
||||
Assert.IsNull(parameterWithNullDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void GetParameterCannotConvertoToNumberFromNonNumericValues(Language language)
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "date_parameter", "2022-8-8" },
|
||||
{ "string_parameter", "string value" },
|
||||
};
|
||||
int defaultInt = 0;
|
||||
double defaultDouble = 0;
|
||||
decimal defaultDecimal = 0;
|
||||
int intValue;
|
||||
double doubleValue;
|
||||
decimal decimalValue;
|
||||
|
||||
foreach (var parameterName in parameters.Keys)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetParameters(parameters);
|
||||
intValue = algorithm.GetParameter(parameterName, defaultInt);
|
||||
doubleValue = algorithm.GetParameter(parameterName, defaultDouble);
|
||||
decimalValue = algorithm.GetParameter(parameterName, defaultDecimal);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getAlgorithm():
|
||||
return QCAlgorithm()
|
||||
");
|
||||
|
||||
var getAlgorithm = testModule.GetAttr("getAlgorithm");
|
||||
dynamic algorithm = getAlgorithm.Invoke();
|
||||
algorithm.GetAttr("SetParameters").Invoke(PyDict.FromManagedObject(parameters));
|
||||
intValue = algorithm.GetParameter(parameterName.ToPython(), defaultInt.ToPython()).As<int>();
|
||||
doubleValue = algorithm.GetParameter(parameterName.ToPython(), defaultDouble.ToPython()).As<double>();
|
||||
decimalValue = algorithm.GetParameter(parameterName.ToPython(), defaultDecimal.ToPython()).As<decimal>();
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(defaultInt, intValue, $"Expected '{parameterName}' to be {defaultInt} but was {intValue} instead");
|
||||
Assert.AreEqual(defaultDouble, doubleValue, $"Expected '{parameterName}' to be {defaultDouble} but was {doubleValue} instead");
|
||||
Assert.AreEqual(defaultDecimal, decimalValue, $"Expected '{parameterName}' to be {defaultDecimal} but was {decimalValue} instead");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void GetParameterConvertsToFloatingPointTypesFromDifferentFormats(Language language)
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "int_parameter", "1" },
|
||||
{ "float_parameter", "1.0" },
|
||||
{ "comma_parameter", "1,234.56" },
|
||||
{ "scientific_parameter", "-1.643e6" },
|
||||
};
|
||||
var expectedDoubles = new Dictionary<string, double>
|
||||
{
|
||||
{ "int_parameter", 1.0 },
|
||||
{ "float_parameter", 1.0 },
|
||||
{ "comma_parameter", 1234.56 },
|
||||
{ "scientific_parameter", -1643000.0 },
|
||||
};
|
||||
var expectedDecimals = new Dictionary<string, decimal>
|
||||
{
|
||||
{ "int_parameter", 1m },
|
||||
{ "float_parameter", 1m },
|
||||
{ "comma_parameter", 1234.56m },
|
||||
{ "scientific_parameter", -1643000m },
|
||||
};
|
||||
double defaultDouble = 0;
|
||||
decimal defaultDecimal = 0;
|
||||
double doubleValue;
|
||||
decimal decimalValue;
|
||||
|
||||
foreach (var parameterName in parameters.Keys)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetParameters(parameters);
|
||||
doubleValue = algorithm.GetParameter(parameterName, defaultDouble);
|
||||
decimalValue = algorithm.GetParameter(parameterName, defaultDecimal);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getAlgorithm():
|
||||
return QCAlgorithm()
|
||||
");
|
||||
|
||||
var getAlgorithm = testModule.GetAttr("getAlgorithm");
|
||||
dynamic algorithm = getAlgorithm.Invoke();
|
||||
algorithm.GetAttr("SetParameters").Invoke(PyDict.FromManagedObject(parameters));
|
||||
doubleValue = algorithm.GetParameter(parameterName.ToPython(), defaultDouble.ToPython()).As<double>();
|
||||
decimalValue = algorithm.GetParameter(parameterName.ToPython(), defaultDecimal.ToPython()).As<decimal>();
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(expectedDoubles[parameterName], doubleValue,
|
||||
$"Expected '{parameterName}' to be {expectedDoubles[parameterName]} but was {doubleValue} instead");
|
||||
Assert.AreEqual(expectedDecimals[parameterName], decimalValue,
|
||||
$"Expected '{parameterName}' to be {expectedDecimals[parameterName]} but was {decimalValue} instead");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,849 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Python;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Tests.Research;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmIndicatorsTests
|
||||
{
|
||||
private QCAlgorithm _algorithm;
|
||||
private Symbol _equity;
|
||||
private Symbol _option;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_algorithm = new AlgorithmStub();
|
||||
var historyProvider = new SubscriptionDataReaderHistoryProvider();
|
||||
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null,
|
||||
TestGlobals.DataProvider, TestGlobals.DataCacheProvider, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider,
|
||||
null, true, new DataPermissionManager(), _algorithm.ObjectStore, _algorithm.Settings));
|
||||
_algorithm.SetHistoryProvider(historyProvider);
|
||||
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11, 15, 0, 0));
|
||||
_equity = _algorithm.AddEquity("SPY").Symbol;
|
||||
_option = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Call, 450m, new DateTime(2023, 9, 1));
|
||||
_algorithm.AddOptionContract(_option);
|
||||
_algorithm.Settings.AutomaticIndicatorWarmUp = true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsPassSelectorToWarmUp()
|
||||
{
|
||||
var mockSelector = new Mock<Func<IBaseData, TradeBar>>();
|
||||
mockSelector.Setup(_ => _(It.IsAny<IBaseData>())).Returns<TradeBar>(_ => (TradeBar)_);
|
||||
|
||||
var indicator = _algorithm.ABANDS(Symbols.SPY, 20, selector: mockSelector.Object);
|
||||
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
mockSelector.Verify(_ => _(It.IsAny<IBaseData>()), Times.Exactly(indicator.WarmUpPeriod));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SharpeRatioIndicatorUsesAlgorithmsRiskFreeRateModelSetAfterIndicatorRegistration()
|
||||
{
|
||||
// Register indicator
|
||||
var sharpeRatio = _algorithm.SR(Symbols.SPY, 10);
|
||||
|
||||
// Setup risk free rate model
|
||||
var interestRateProviderMock = new Mock<IRiskFreeInterestRateModel>();
|
||||
var reference = new DateTime(2023, 11, 21, 10, 0, 0);
|
||||
interestRateProviderMock.Setup(x => x.GetInterestRate(reference)).Verifiable();
|
||||
|
||||
// Update indicator
|
||||
sharpeRatio.Update(new IndicatorDataPoint(Symbols.SPY, reference, 300m));
|
||||
|
||||
// Our interest rate provider shouldn't have been called yet since it's hasn't been set to the algorithm
|
||||
interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Never);
|
||||
|
||||
// Set the interest rate provider to the algorithm
|
||||
_algorithm.SetRiskFreeInterestRateModel(interestRateProviderMock.Object);
|
||||
|
||||
// Update indicator
|
||||
sharpeRatio.Update(new IndicatorDataPoint(Symbols.SPY, reference, 300m));
|
||||
|
||||
// Our interest rate provider should have been called once
|
||||
interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Once);
|
||||
}
|
||||
|
||||
[TestCase("Span", Language.CSharp)]
|
||||
[TestCase("Count", Language.CSharp)]
|
||||
[TestCase("StartAndEndDate", Language.CSharp)]
|
||||
[TestCase("Span", Language.Python)]
|
||||
[TestCase("Count", Language.Python)]
|
||||
[TestCase("StartAndEndDate", Language.Python)]
|
||||
public void IndicatorsDataPoint(string testCase, Language language)
|
||||
{
|
||||
var period = 10;
|
||||
var indicator = new BollingerBands(period, 2);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
int dataCount;
|
||||
|
||||
IndicatorHistory indicatorValues;
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
if (testCase == "StartAndEndDate")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute);
|
||||
}
|
||||
else if (testCase == "Span")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, TimeSpan.FromDays(5), Resolution.Minute);
|
||||
}
|
||||
else
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, (int)(4 * 60 * 6.5), Resolution.Minute);
|
||||
}
|
||||
// BollingerBands, upper, lower, mid bands, std, band width, percentB, price
|
||||
Assert.AreEqual(8, indicatorValues.First().GetStorageDictionary().Count);
|
||||
dataCount = indicatorValues.ToList().Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
if (testCase == "StartAndEndDate")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute);
|
||||
}
|
||||
else if (testCase == "Span")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute);
|
||||
}
|
||||
else
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), (int)(4 * 60 * 6.5), Resolution.Minute);
|
||||
}
|
||||
dataCount = QuantBookIndicatorsTests.GetDataFrameLength(indicatorValues.DataFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// the historical indicator current values
|
||||
Assert.AreEqual(1550 + period, indicatorValues.Current.Count);
|
||||
Assert.AreEqual(1550 + period, indicatorValues["current"].Count);
|
||||
Assert.AreEqual(indicatorValues.Current, indicatorValues["current"]);
|
||||
Assert.IsNull(indicatorValues["NonExisting"]);
|
||||
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
Assert.AreEqual(1550 + period, dataCount);
|
||||
|
||||
var lastData = indicatorValues.Current.Last();
|
||||
Assert.AreEqual(new DateTime(2013, 10, 10, 16, 0, 0), lastData.EndTime);
|
||||
Assert.AreEqual(lastData.EndTime, indicatorValues.Last().EndTime);
|
||||
}
|
||||
|
||||
[TestCase("Span", Language.CSharp)]
|
||||
[TestCase("Count", Language.CSharp)]
|
||||
[TestCase("StartAndEndDate", Language.CSharp)]
|
||||
[TestCase("Span", Language.Python)]
|
||||
[TestCase("Count", Language.Python)]
|
||||
[TestCase("StartAndEndDate", Language.Python)]
|
||||
public void IndicatorsBar(string testCase, Language language)
|
||||
{
|
||||
var period = 10;
|
||||
var indicator = new AverageTrueRange(period);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
|
||||
IndicatorHistory indicatorValues;
|
||||
int dataCount;
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
if (testCase == "StartAndEndDate")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute);
|
||||
}
|
||||
else if (testCase == "Span")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, TimeSpan.FromDays(5), Resolution.Minute);
|
||||
}
|
||||
else
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, _equity, (int)(4 * 60 * 6.5), Resolution.Minute);
|
||||
}
|
||||
// the TrueRange & the AVGTrueRange
|
||||
Assert.AreEqual(2, indicatorValues.First().GetStorageDictionary().Count);
|
||||
dataCount = indicatorValues.ToList().Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
if (testCase == "StartAndEndDate")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), new DateTime(2013, 10, 07), new DateTime(2013, 10, 11), Resolution.Minute);
|
||||
}
|
||||
else if (testCase == "Span")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute);
|
||||
}
|
||||
else
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), _equity.ToPython(), (int)(4 * 60 * 6.5), Resolution.Minute);
|
||||
}
|
||||
dataCount = QuantBookIndicatorsTests.GetDataFrameLength(indicatorValues.DataFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// the historical indicator current values
|
||||
Assert.AreEqual(1550 + period, indicatorValues.Current.Count);
|
||||
Assert.AreEqual(1550 + period, indicatorValues["current"].Count);
|
||||
Assert.AreEqual(indicatorValues.Current, indicatorValues["current"]);
|
||||
Assert.IsNull(indicatorValues["NonExisting"]);
|
||||
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
Assert.AreEqual(1550 + period, dataCount);
|
||||
|
||||
var lastData = indicatorValues.Current.Last();
|
||||
Assert.AreEqual(new DateTime(2013, 10, 10, 16, 0, 0), lastData.EndTime);
|
||||
Assert.AreEqual(lastData.EndTime, indicatorValues.Last().EndTime);
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void IndicatorMultiSymbol(Language language)
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new Beta(_equity, referenceSymbol, 10);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
|
||||
int dataCount;
|
||||
IndicatorHistory indicatorValues;
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, new[] { _equity, referenceSymbol }, TimeSpan.FromDays(5));
|
||||
Assert.AreEqual(1, indicatorValues.First().GetStorageDictionary().Count);
|
||||
dataCount = indicatorValues.ToList().Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator.ToPython(), (new[] { _equity, referenceSymbol }).ToPython(), TimeSpan.FromDays(5));
|
||||
dataCount = QuantBookIndicatorsTests.GetDataFrameLength(indicatorValues.DataFrame);
|
||||
}
|
||||
}
|
||||
|
||||
// the historical indicator current values
|
||||
Assert.AreEqual(1560, indicatorValues.Current.Count);
|
||||
Assert.AreEqual(1560, indicatorValues["current"].Count);
|
||||
Assert.AreEqual(indicatorValues.Current, indicatorValues["current"]);
|
||||
Assert.IsNull(indicatorValues["NonExisting"]);
|
||||
|
||||
Assert.AreEqual(1560, dataCount);
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BetaCalculation()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new Beta(_equity, referenceSymbol, 10);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
|
||||
var indicatorValues = _algorithm.IndicatorHistory(indicator, new[] { _equity, referenceSymbol }, TimeSpan.FromDays(50), Resolution.Daily);
|
||||
var lastPoint = indicatorValues.Last();
|
||||
Assert.AreEqual(0.477585951081753m, lastPoint.Price);
|
||||
Assert.AreEqual(0.477585951081753m, lastPoint.Current.Value);
|
||||
Assert.AreEqual(new DateTime(2013, 10, 10, 16, 0, 0), lastPoint.Current.EndTime);
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void IndicatorsPassingHistory(Language language)
|
||||
{
|
||||
var period = 10;
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new Beta(_equity, referenceSymbol, period);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
|
||||
var history = _algorithm.History(new[] { _equity, referenceSymbol }, TimeSpan.FromDays(5), Resolution.Minute);
|
||||
int dataCount;
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var indicatorValues = _algorithm.IndicatorHistory(indicator, history);
|
||||
Assert.AreEqual(1, indicatorValues.First().GetStorageDictionary().Count);
|
||||
dataCount = indicatorValues.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var pandasFrame = _algorithm.IndicatorHistory(indicator.ToPython(), history);
|
||||
dataCount = QuantBookIndicatorsTests.GetDataFrameLength(pandasFrame.DataFrame);
|
||||
}
|
||||
}
|
||||
Assert.AreEqual((int)(4 * 60 * 6.5) - period, dataCount);
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonIndicatorCanBeWarmedUpWithTimespan()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new SimpleMovingAverage("SMA", 100);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
_algorithm.AddEquity(referenceSymbol);
|
||||
using (Py.GIL())
|
||||
{
|
||||
var pythonIndicator = indicator.ToPython();
|
||||
_algorithm.WarmUpIndicator(referenceSymbol, pythonIndicator, TimeSpan.FromMinutes(60));
|
||||
Assert.IsTrue(pythonIndicator.GetAttr("is_ready").GetAndDispose<bool>());
|
||||
Assert.IsTrue(pythonIndicator.GetAttr("samples").GetAndDispose<int>() >= 100);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorCanBeWarmedUpWithTimespan()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
_algorithm.AddEquity(referenceSymbol);
|
||||
var indicator = new SimpleMovingAverage("SMA", 100);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
_algorithm.WarmUpIndicator(referenceSymbol, indicator, TimeSpan.FromMinutes(60));
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
Assert.IsTrue(indicator.Samples >= 100);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorCanBeWarmedUpWithoutSymbolInSecurities()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new SimpleMovingAverage("SMA", 100);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
Assert.DoesNotThrow(() => _algorithm.WarmUpIndicator(referenceSymbol, indicator, TimeSpan.FromMinutes(60)));
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
Assert.IsTrue(indicator.Samples >= 100);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCustomIndicatorCanBeWarmedUpWithTimespan()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
_algorithm.AddEquity(referenceSymbol);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
from collections import deque
|
||||
|
||||
class CustomSimpleMovingAverage(PythonIndicator):
|
||||
def __init__(self, name, period):
|
||||
super().__init__()
|
||||
self.warm_up_period = period
|
||||
self.name = name
|
||||
self.value = 0
|
||||
self.queue = deque(maxlen=period)
|
||||
|
||||
# Update method is mandatory
|
||||
def update(self, input):
|
||||
self.queue.appendleft(input.value)
|
||||
count = len(self.queue)
|
||||
self.value = np.sum(self.queue) / count
|
||||
return count == self.queue.maxlen");
|
||||
|
||||
var customIndicator = testModule.GetAttr("CustomSimpleMovingAverage").Invoke("custom".ToPython(), 100.ToPython());
|
||||
_algorithm.WarmUpIndicator(referenceSymbol, customIndicator, TimeSpan.FromMinutes(60));
|
||||
Assert.IsTrue(customIndicator.GetAttr("is_ready").GetAndDispose<bool>());
|
||||
Assert.IsTrue(customIndicator.GetAttr("samples").GetAndDispose<int>() >= 100);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("count")]
|
||||
[TestCase("StartAndEndDate")]
|
||||
public void IndicatorUpdatedWithSymbol(string testCase)
|
||||
{
|
||||
var time = new DateTime(2014, 06, 07);
|
||||
|
||||
var put = Symbols.CreateOptionSymbol("AAPL", OptionRight.Call, 250m, new DateTime(2016, 01, 15));
|
||||
var call = Symbols.CreateOptionSymbol("AAPL", OptionRight.Put, 250m, new DateTime(2016, 01, 15));
|
||||
var indicator = new Delta(option: put, mirrorOption: call, optionModel: OptionPricingModelType.BlackScholes, ivModel: OptionPricingModelType.BlackScholes);
|
||||
_algorithm.SetDateTime(time);
|
||||
|
||||
IndicatorHistory indicatorValues;
|
||||
if (testCase == "count")
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, new[] { put, call, put.Underlying }, 60 * 10, resolution: Resolution.Minute);
|
||||
}
|
||||
else
|
||||
{
|
||||
indicatorValues = _algorithm.IndicatorHistory(indicator, new[] { put, call, put.Underlying }, TimeSpan.FromMinutes(60 * (10 + 2)), resolution: Resolution.Minute);
|
||||
}
|
||||
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
Assert.AreEqual(0.9942989m, indicator.Current.Value);
|
||||
Assert.AreEqual(0.3514844m, indicator.ImpliedVolatility.Current.Value);
|
||||
Assert.AreEqual(390, indicatorValues.Count);
|
||||
|
||||
var lastData = indicatorValues.Current.Last();
|
||||
Assert.AreEqual(new DateTime(2014, 6, 6, 16, 0, 0), lastData.EndTime);
|
||||
Assert.AreEqual(lastData.EndTime, indicatorValues.Last().EndTime);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
public void PythonCustomIndicator(int testCases)
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
using (Py.GIL())
|
||||
{
|
||||
PyModule module;
|
||||
if (testCases == 1)
|
||||
{
|
||||
module = PyModule.FromString("PythonCustomIndicator",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
class GoodCustomIndicator(PythonIndicator):
|
||||
def __init__(self):
|
||||
self.Value = 0
|
||||
def Update(self, input):
|
||||
self.Value = input.Value
|
||||
return True");
|
||||
}
|
||||
else
|
||||
{
|
||||
module = PyModule.FromString("PythonCustomIndicator",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
class GoodCustomIndicator:
|
||||
def __init__(self):
|
||||
self.IsReady = True
|
||||
self.Value = 0
|
||||
def Update(self, input):
|
||||
self.Value = input.Value
|
||||
return True");
|
||||
}
|
||||
|
||||
var goodIndicator = module.GetAttr("GoodCustomIndicator").Invoke();
|
||||
var pandasFrame = _algorithm.IndicatorHistory(goodIndicator, _equity.ToPython(), TimeSpan.FromDays(5), Resolution.Minute);
|
||||
var dataCount = QuantBookIndicatorsTests.GetDataFrameLength(pandasFrame.DataFrame);
|
||||
|
||||
Assert.IsTrue((bool)((dynamic)goodIndicator).IsReady);
|
||||
Assert.AreEqual((int)(4 * 60 * 6.5), dataCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SpecificTTypeIndicator()
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new CustomIndicator();
|
||||
var result = _algorithm.IndicatorHistory(indicator, referenceSymbol, TimeSpan.FromDays(1), Resolution.Minute).ToList();
|
||||
Assert.AreEqual(390, result.Count);
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
}
|
||||
|
||||
[TestCase("span", 1)]
|
||||
[TestCase("count", 1)]
|
||||
[TestCase("span", 2)]
|
||||
[TestCase("count", 2)]
|
||||
public void SMAAssertDataCount(string testCase, int requestCount)
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new SimpleMovingAverage(10);
|
||||
IndicatorHistory result;
|
||||
if (testCase == "span")
|
||||
{
|
||||
result = _algorithm.IndicatorHistory(indicator, referenceSymbol, TimeSpan.FromDays(requestCount), Resolution.Daily);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _algorithm.IndicatorHistory(indicator, referenceSymbol, requestCount, Resolution.Daily);
|
||||
}
|
||||
Assert.AreEqual(requestCount, result.Count);
|
||||
Assert.AreEqual(10 + requestCount - 1, indicator.Samples);
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorHistoryIsSupportedInPythonForOptionsIndicators([Range(1, 4)] int overload, [Values] bool useMirrorContract)
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2014, 06, 07));
|
||||
|
||||
var contract = Symbol.CreateOption("AAPL", Market.USA, OptionStyle.American, OptionRight.Call, 505, new DateTime(2014, 6, 27));
|
||||
var mirrorContract = useMirrorContract
|
||||
? Symbol.CreateOption("AAPL", Market.USA, OptionStyle.American, OptionRight.Put, 505, new DateTime(2014, 6, 27))
|
||||
: null;
|
||||
var underlying = contract.Underlying;
|
||||
|
||||
var indicator = new ImpliedVolatility(contract, optionModel: OptionPricingModelType.BlackScholes, mirrorOption: mirrorContract);
|
||||
|
||||
using var _ = Py.GIL();
|
||||
|
||||
using var pyIndicator = indicator.ToPython();
|
||||
var symbols = useMirrorContract ? new[] { contract, mirrorContract, underlying } : new[] { contract, underlying };
|
||||
using var pySymbols = symbols.ToPyListUnSafe();
|
||||
|
||||
var symbolsHistory = overload != 4
|
||||
? null
|
||||
: _algorithm.History(symbols, TimeSpan.FromDays(2), Resolution.Minute);
|
||||
|
||||
var indicatorHistory = overload switch
|
||||
{
|
||||
1 => _algorithm.IndicatorHistory(pyIndicator, pySymbols, TimeSpan.FromDays(2), Resolution.Minute),
|
||||
2 => _algorithm.IndicatorHistory(pyIndicator, pySymbols, 60 * 24 * 2, Resolution.Minute),
|
||||
3 => _algorithm.IndicatorHistory(pyIndicator, pySymbols, new DateTime(2014, 6, 6), new DateTime(2014, 6, 7), Resolution.Minute),
|
||||
4 => _algorithm.IndicatorHistory(pyIndicator, symbolsHistory),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(overload), "Invalid overload")
|
||||
};
|
||||
|
||||
Assert.AreEqual(390, indicatorHistory.Count);
|
||||
|
||||
using var dataFrame = indicatorHistory.DataFrame;
|
||||
Assert.AreEqual(390, dataFrame.GetAttr("shape")[0].GetAndDispose<int>());
|
||||
// Assert dataframe column names are current, price, oppositeprice and underlyingprice
|
||||
var columns = dataFrame.GetAttr("columns").InvokeMethod<List<string>>("tolist");
|
||||
var expectedColumns = new[] { "current", "price", "oppositeprice", "underlyingprice", "theoreticalprice" };
|
||||
CollectionAssert.AreEquivalent(expectedColumns, columns);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WarmUpIndicatorIsSupportedInPythonForOptionsIndicators([Values(1, 2)] int overload, [Values] bool useMirrorContract)
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2014, 06, 07));
|
||||
|
||||
var contract = Symbol.CreateOption("AAPL", Market.USA, OptionStyle.American, OptionRight.Call, 505, new DateTime(2014, 07, 19));
|
||||
var mirrorContract = useMirrorContract
|
||||
? Symbol.CreateOption("AAPL", Market.USA, OptionStyle.American, OptionRight.Put, 505, new DateTime(2014, 07, 19))
|
||||
: null;
|
||||
var underlying = contract.Underlying;
|
||||
|
||||
var indicator = new ImpliedVolatility(contract, optionModel: OptionPricingModelType.BlackScholes, mirrorOption: mirrorContract);
|
||||
|
||||
using var _ = Py.GIL();
|
||||
|
||||
using var pyIndicator = indicator.ToPython();
|
||||
var symbols = useMirrorContract ? new[] { contract, mirrorContract, underlying } : new[] { contract, underlying };
|
||||
using var pySymbols = symbols.ToPyListUnSafe();
|
||||
|
||||
switch (overload)
|
||||
{
|
||||
case 1:
|
||||
_algorithm.WarmUpIndicator(pySymbols, pyIndicator, TimeSpan.FromDays(1));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
_algorithm.WarmUpIndicator(pySymbols, pyIndicator, Resolution.Daily);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(overload), "Invalid overload");
|
||||
}
|
||||
|
||||
Assert.IsTrue(indicator.IsReady);
|
||||
|
||||
if (useMirrorContract)
|
||||
{
|
||||
Assert.IsNotNull(indicator.OppositePrice);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(indicator.OppositePrice);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorHistoryDataFrameDoesNotCointainDefaultDateTimeIndex()
|
||||
{
|
||||
var pandasConverter = new PandasConverter();
|
||||
var indicatorsDataPointPerProperty = new List<InternalIndicatorValues>
|
||||
{
|
||||
new InternalIndicatorValues(null, "current")
|
||||
};
|
||||
var lazyDataFrame = new Lazy<PyObject>(
|
||||
() => pandasConverter.GetIndicatorDataFrame(indicatorsDataPointPerProperty.Select(x => new KeyValuePair<string, List<IndicatorDataPoint>>(x.Name, x.Values))),
|
||||
isThreadSafe: false);
|
||||
var indicatorHistory = new IndicatorHistory(new List<IndicatorDataPoints>(), indicatorsDataPointPerProperty, lazyDataFrame);
|
||||
indicatorHistory.Current.Add(new IndicatorDataPoint(Symbols.SPY, new DateTime(2018, 1, 1), 100));
|
||||
indicatorHistory.Current.Add(new IndicatorDataPoint(Symbols.SPY, new DateTime(2018, 1, 2), 100));
|
||||
// Force insertion of a default(DateTime) timestamp to ensure it's excluded from the DataFrame
|
||||
indicatorHistory.Current.Add(new IndicatorDataPoint(Symbols.SPY, default, 100));
|
||||
|
||||
dynamic dataframe = indicatorHistory.DataFrame;
|
||||
using (Py.GIL())
|
||||
{
|
||||
var index = dataframe.index;
|
||||
foreach (dynamic time in index)
|
||||
{
|
||||
DateTime timestamp = (DateTime)time.AsManagedObject(typeof(DateTime));
|
||||
// Ensure that no timestamp in the DataFrame index is equal to default(DateTime)
|
||||
Assert.AreNotEqual(default(DateTime), timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorHistoryShouldIncludeValidIndicatorsAndExplicitlyIncludedProperties()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
var indicator = new TestIndicator();
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
var history = _algorithm.History(new[] { referenceSymbol }, TimeSpan.FromDays(5), Resolution.Minute);
|
||||
var indicatorValues = _algorithm.IndicatorHistory(indicator, history);
|
||||
|
||||
dynamic dataframe = indicatorValues.DataFrame;
|
||||
using (Py.GIL())
|
||||
{
|
||||
var index = dataframe.index;
|
||||
var columns = dataframe.columns;
|
||||
var expectedColumns = new List<string> { "smaprop", "genericprop", "current", "nongenericprop", "counter", "indicatortype", "description" };
|
||||
var columnsCount = 0;
|
||||
foreach (dynamic col in columns)
|
||||
{
|
||||
columnsCount++;
|
||||
var columnName = (string)col.AsManagedObject(typeof(string));
|
||||
Assert.IsTrue(expectedColumns.Contains(columnName));
|
||||
}
|
||||
Assert.AreEqual(expectedColumns.Count, columnsCount);
|
||||
|
||||
// Validate that the number of rows in the "current" column
|
||||
// matches the row count in "counter", "indicatortype", and "description" columns
|
||||
// Get the number of rows in each relevant column using __len__()
|
||||
int currentLen = dataframe["current"].InvokeMethod("__len__").As<int>();
|
||||
int counterLen = dataframe["counter"].InvokeMethod("__len__").As<int>();
|
||||
int indicatorTypeLen = dataframe["indicatortype"].InvokeMethod("__len__").As<int>();
|
||||
int descriptionLen = dataframe["description"].InvokeMethod("__len__").As<int>();
|
||||
|
||||
// Assert that all lengths match the length of "current"
|
||||
Assert.AreEqual(currentLen, counterLen);
|
||||
Assert.AreEqual(currentLen, indicatorTypeLen);
|
||||
Assert.AreEqual(currentLen, descriptionLen);
|
||||
}
|
||||
}
|
||||
|
||||
private enum TestIndicatorType
|
||||
{
|
||||
TypeA,
|
||||
TypeB
|
||||
}
|
||||
|
||||
private class TestIndicator : IndicatorBase<QuoteBar>, IIndicatorWarmUpPeriodProvider
|
||||
{
|
||||
[PandasIgnore]
|
||||
public Identity IgnoredProp { get; }
|
||||
public SimpleMovingAverage SmaProp { get; }
|
||||
public IndicatorBase<IndicatorDataPoint> GenericProp { get; }
|
||||
public IndicatorBase NonGenericProp { get; }
|
||||
public int Counter { get; set; }
|
||||
public TestIndicatorType IndicatorType { get; set; }
|
||||
public string Description { get; set; }
|
||||
private bool _isReady;
|
||||
public int WarmUpPeriod => 1;
|
||||
public override bool IsReady => _isReady;
|
||||
public TestIndicator() : base("Pepe")
|
||||
{
|
||||
SmaProp = new SimpleMovingAverage("SMA", 5);
|
||||
GenericProp = new Identity("Generic");
|
||||
IgnoredProp = new Identity("Ignored");
|
||||
NonGenericProp = new Identity("NoGeneric");
|
||||
}
|
||||
protected override decimal ComputeNextValue(QuoteBar input)
|
||||
{
|
||||
Counter++;
|
||||
_isReady = true;
|
||||
return input.Ask.High;
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomIndicator : IndicatorBase<QuoteBar>, IIndicatorWarmUpPeriodProvider
|
||||
{
|
||||
private bool _isReady;
|
||||
public int WarmUpPeriod => 1;
|
||||
public override bool IsReady => _isReady;
|
||||
public CustomIndicator() : base("Pepe")
|
||||
{ }
|
||||
protected override decimal ComputeNextValue(QuoteBar input)
|
||||
{
|
||||
_isReady = true;
|
||||
return input.Ask.High;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SupportsConversionToIndicatorBaseBaseDataCorrectly([Range(1, 6)] int scenario)
|
||||
{
|
||||
const string code = @"
|
||||
from AlgorithmImports import *
|
||||
from QuantConnect.Indicators import *
|
||||
|
||||
def create_intraday_vwap_indicator(name):
|
||||
return IntradayVwap(name)
|
||||
def create_consolidator():
|
||||
return TradeBarConsolidator(timedelta(minutes=1))
|
||||
";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(), code);
|
||||
string name = "test";
|
||||
|
||||
// Creates the IntradayVWAP (IndicatorBase<BaseData>)
|
||||
var indicator = module.GetAttr("create_intraday_vwap_indicator").Invoke(name.ToPython());
|
||||
var consolidator = module.GetAttr("create_consolidator").Invoke();
|
||||
var SymbolList = new List<Symbol>
|
||||
{
|
||||
Symbols.SPY,
|
||||
Symbols.IBM,
|
||||
};
|
||||
|
||||
// Tests different scenarios based on the "scenario" parameter
|
||||
switch (scenario)
|
||||
{
|
||||
case 1:
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(Symbols.SPY, indicator, consolidator));
|
||||
break;
|
||||
case 2:
|
||||
Assert.DoesNotThrow(() => _algorithm.WarmUpIndicator(SymbolList.ToPyList(), indicator));
|
||||
break;
|
||||
case 3:
|
||||
Assert.DoesNotThrow(() => _algorithm.WarmUpIndicator(SymbolList.ToPyList(), indicator, TimeSpan.FromDays(1)));
|
||||
break;
|
||||
case 4:
|
||||
Assert.DoesNotThrow(() => _algorithm.IndicatorHistory(indicator, SymbolList.ToPyList(), 10));
|
||||
break;
|
||||
case 5:
|
||||
Assert.DoesNotThrow(() => _algorithm.IndicatorHistory(indicator, SymbolList.ToPyList(), new DateTime(2014, 6, 6), new DateTime(2014, 6, 7)));
|
||||
break;
|
||||
case 6:
|
||||
var symbolsHistory = _algorithm.History(SymbolList, TimeSpan.FromDays(2), Resolution.Minute);
|
||||
Assert.DoesNotThrow(() => _algorithm.IndicatorHistory(indicator, symbolsHistory));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IchimokuIndicatorHistoryDataFrameDoesNotContainNaNInCurrentColumn()
|
||||
{
|
||||
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
var history = _algorithm.History(new[] { referenceSymbol }, TimeSpan.FromDays(5), Resolution.Minute);
|
||||
var indicator = new IchimokuKinkoHyo(9, 26, 17, 52, 26, 26);
|
||||
var indicatorValues = _algorithm.IndicatorHistory(indicator, history);
|
||||
indicatorValues.Current.Add(new IndicatorDataPoint(referenceSymbol, default(DateTime), 1));
|
||||
|
||||
dynamic dataframe = indicatorValues.DataFrame;
|
||||
using (Py.GIL())
|
||||
{
|
||||
var currentColumn = dataframe["current"];
|
||||
foreach (PyObject value in currentColumn)
|
||||
{
|
||||
double doubleValue = value.As<double>();
|
||||
Assert.IsFalse(double.IsNaN(doubleValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanRegisterIndicatorsWithPythonSelector()
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
_algorithm.Settings.AutomaticIndicatorWarmUp = true;
|
||||
var symbol = _algorithm.AddEquity("SPY").Symbol;
|
||||
|
||||
using var _ = Py.GIL();
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class LastInputTracker:
|
||||
last_input = None
|
||||
|
||||
def selector(bar):
|
||||
LastInputTracker.last_input = bar
|
||||
return bar.close
|
||||
|
||||
def get_indicator(algo, symbol):
|
||||
indicator = SimpleMovingAverage(10)
|
||||
algo.register_indicator(symbol, indicator, Resolution.MINUTE, selector=selector)
|
||||
algo.warm_up_indicator(symbol, indicator, selector=selector)
|
||||
return indicator
|
||||
");
|
||||
|
||||
using var pyAlgo = _algorithm.ToPython();
|
||||
using var pySymbol = symbol.ToPython();
|
||||
var indicator = testModule.GetAttr("get_indicator").Invoke(pyAlgo, pySymbol).GetAndDispose<IndicatorBase>();
|
||||
|
||||
// The indicator should have been updated during the warm-up period
|
||||
var lastInput = testModule.GetAttr("LastInputTracker").GetAttr("last_input").GetAndDispose<TradeBar>();
|
||||
Assert.IsNotNull(lastInput);
|
||||
}
|
||||
|
||||
// Some specific indicator helper methods tests
|
||||
[TestCase("abands", "symbol, 2", false)]
|
||||
[TestCase("ad", "symbol", false)]
|
||||
[TestCase("adosc", "symbol, 2, 3", false)]
|
||||
[TestCase("sma", "symbol, 3", true)]
|
||||
[TestCase("ema", "symbol, 3", true)]
|
||||
[TestCase("arima", "symbol, 1, 1, 1, 10", true)]
|
||||
public void IndicatorHelperMethodsWorkWithPythonSelectors(string indicatorName, string indicatorArgs, bool decimalSelector)
|
||||
{
|
||||
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
|
||||
_algorithm.Settings.AutomaticIndicatorWarmUp = true;
|
||||
|
||||
var symbol = _algorithm.AddEquity("SPY").Symbol;
|
||||
var selector = decimalSelector ? "decimal_selector" : "selector";
|
||||
|
||||
using var _ = Py.GIL();
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@$"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class LastInputTracker:
|
||||
last_input = None
|
||||
|
||||
def selector(bar):
|
||||
LastInputTracker.last_input = bar
|
||||
return bar
|
||||
|
||||
def decimal_selector(bar):
|
||||
LastInputTracker.last_input = bar
|
||||
return bar.close
|
||||
|
||||
def get_indicator(algo, symbol):
|
||||
return algo.{indicatorName}({indicatorArgs}, selector={selector})
|
||||
");
|
||||
|
||||
using var pyAlgo = _algorithm.ToPython();
|
||||
using var pySymbol = symbol.ToPython();
|
||||
var indicator = testModule.GetAttr("get_indicator").Invoke(pyAlgo, pySymbol).GetAndDispose<IndicatorBase>();
|
||||
|
||||
// The indicator should have been updated during the warm-up period
|
||||
var lastInput = testModule.GetAttr("LastInputTracker").GetAttr("last_input").GetAndDispose<TradeBar>();
|
||||
Assert.IsNotNull(lastInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
/*
|
||||
* 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 QuantConnect.Algorithm;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Test mixed call order combinations of SetSecurityInitializer, SetBrokerageModel and AddSecurity
|
||||
/// </summary>
|
||||
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
|
||||
public class AlgorithmInitializeTests
|
||||
{
|
||||
private const string Ticker = "EURUSD";
|
||||
private const Resolution Resolution = QuantConnect.Resolution.Second;
|
||||
private const string Market = QuantConnect.Market.FXCM;
|
||||
private const BrokerageName BrokerageName = QuantConnect.Brokerages.BrokerageName.FxcmBrokerage;
|
||||
private const int RoundingPrecision = 20;
|
||||
private readonly MarketOrder _order = new MarketOrder { Quantity = 1000 };
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void Validates_SetEndTime(bool explicitSet)
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
// We initialize the algorithm with `now` in the algorithm default time zone
|
||||
var end = DateTime.UtcNow.ConvertFromUtc(algorithm.TimeZone);
|
||||
|
||||
if (explicitSet)
|
||||
{
|
||||
algorithm.SetEndDate(end);
|
||||
}
|
||||
var excepted = end.RoundDown(TimeSpan.FromDays(1)).AddTicks(-1);
|
||||
Assert.AreEqual(algorithm.EndDate, excepted);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_AddForex()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
|
||||
// Leverage and FeeModel from BrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_IB_AddForex()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
|
||||
// Leverage and FeeModel from BrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForex_SetBrokerageModel()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
|
||||
// Leverage and FeeModel from BrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_AddForexWithLeverage()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from BrokerageModel
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForexWithLeverage_SetBrokerageModel()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from BrokerageModel
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetSecurityInitializer_AddForex()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
|
||||
// Leverage and FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(100, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForex_SetSecurityInitializer()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
|
||||
// SetSecurityInitializer does not apply to securities added before the call
|
||||
// Leverage and FeeModel from DefaultBrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(ConstantFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetSecurityInitializer_AddForexWithLeverage()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForexWithLeverage_SetSecurityInitializer()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// SetSecurityInitializer does not apply to securities added before the call
|
||||
// Leverage from AddForex, FeeModel from DefaultBrokerageModel
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(ConstantFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetSecurityInitializer_AddForex_SetBrokerageModel()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
|
||||
// SetSecurityInitializer overrides the brokerage model
|
||||
// Leverage and FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(100, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetSecurityInitializer_SetBrokerageModel_AddForex()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
|
||||
// SetSecurityInitializer overrides the brokerage model
|
||||
// Leverage and FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(100, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_SetSecurityInitializer_AddForex()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
|
||||
// SetSecurityInitializer overrides the brokerage model
|
||||
// Leverage and FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(100, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_AddForex_SetSecurityInitializer()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
|
||||
// SetSecurityInitializer does not apply to securities added before the call
|
||||
// Leverage and FeeModel from BrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForex_SetSecurityInitializer_SetBrokerageModel()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
|
||||
// SetSecurityInitializer does not apply to securities added before the call
|
||||
// Leverage and FeeModel from DefaultBrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(ConstantFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForex_SetBrokerageModel_SetSecurityInitializer()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market);
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
|
||||
// SetSecurityInitializer does not apply to securities added before the call
|
||||
// Leverage and FeeModel from BrokerageModel
|
||||
Assert.AreEqual(50, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetSecurityInitializer_AddForexWithLeverage_SetBrokerageModel()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from Initializer
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetSecurityInitializer_SetBrokerageModel_AddForexWithLeverage()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// SetSecurityInitializer overrides the brokerage model
|
||||
// Leverage from AddForex, FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_SetSecurityInitializer_AddForexWithLeverage()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// SetSecurityInitializer overrides the brokerage model
|
||||
// Leverage from AddForex, FeeModel from SecurityInitializer
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(InteractiveBrokersFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(2, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_SetBrokerageModel_AddForexWithLeverage_SetSecurityInitializer()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from BrokerageModel
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForexWithLeverage_SetSecurityInitializer_SetBrokerageModel()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from DefaultBrokerageModel
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(ConstantFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validates_AddForexWithLeverage_SetBrokerageModel_SetSecurityInitializer()
|
||||
{
|
||||
var algorithm = GetAlgorithm();
|
||||
|
||||
var security = algorithm.AddForex(Ticker, Resolution, Market, true, 25);
|
||||
algorithm.SetBrokerageModel(BrokerageName);
|
||||
algorithm.SetSecurityInitializer(x =>
|
||||
{
|
||||
x.SetLeverage(100);
|
||||
x.FeeModel = new InteractiveBrokersFeeModel();
|
||||
});
|
||||
|
||||
// Leverage passed to AddForex always takes precedence
|
||||
// Leverage from AddForex, FeeModel from BrokerageModel
|
||||
Assert.AreEqual(25, Math.Round(security.Leverage, RoundingPrecision));
|
||||
Assert.IsInstanceOf(typeof(FxcmFeeModel), security.FeeModel);
|
||||
var fee = security.FeeModel.GetOrderFee(
|
||||
new OrderFeeParameters(security, _order));
|
||||
Assert.AreEqual(0.04, fee.Value.Amount);
|
||||
Assert.AreEqual(Currencies.USD, fee.Value.Currency);
|
||||
}
|
||||
|
||||
private QCAlgorithm GetAlgorithm()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
return algorithm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
using QuantConnect.Lean.Engine.TransactionHandlers;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
|
||||
public class AlgorithmLiveTradingTests
|
||||
{
|
||||
[Test]
|
||||
public void SetHoldingsTakesIntoAccountPendingMarketOrders()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetLiveMode(false);
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
security.Exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
|
||||
security.Exchange.SetLocalDateTimeFrontierProvider(algorithm.TimeKeeper.GetLocalTimeKeeper(TimeZones.NewYork));
|
||||
security.SetMarketPrice(new Tick { Value = 270m });
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
using var brokerage = new NullBrokerage();
|
||||
var transactionHandler = new BrokerageTransactionHandler();
|
||||
|
||||
transactionHandler.Initialize(algorithm, brokerage, new LiveTradingResultHandler());
|
||||
Thread.Sleep(250);
|
||||
algorithm.Transactions.SetOrderProcessor(transactionHandler);
|
||||
|
||||
var symbol = security.Symbol;
|
||||
|
||||
// this order should timeout (no fills received within 5 seconds)
|
||||
algorithm.SetHoldings(symbol, 1m);
|
||||
Thread.Sleep(2000);
|
||||
|
||||
var openOrders = algorithm.Transactions.GetOpenOrders();
|
||||
Assert.AreEqual(1, openOrders.Count);
|
||||
|
||||
// this order should never be submitted because of the pending order
|
||||
algorithm.SetHoldings(symbol, 1m);
|
||||
Thread.Sleep(2000);
|
||||
|
||||
openOrders = algorithm.Transactions.GetOpenOrders();
|
||||
Assert.AreEqual(1, openOrders.Count);
|
||||
|
||||
transactionHandler.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
public class NullBrokerage : IBrokerage
|
||||
{
|
||||
public virtual void Dispose() {}
|
||||
#pragma warning disable 0067 // NullBrokerage doesn't use any of these so we will just ignore them
|
||||
public event EventHandler<List<OrderEvent>> OrdersStatusChanged;
|
||||
public event EventHandler<OrderEvent> OptionPositionAssigned;
|
||||
public event EventHandler<OptionNotificationEventArgs> OptionNotification;
|
||||
public event EventHandler<AccountEvent> AccountChanged;
|
||||
public event EventHandler<BrokerageMessageEvent> Message;
|
||||
public event EventHandler<DelistingNotificationEventArgs> DelistingNotification;
|
||||
public event EventHandler<BrokerageOrderIdChangedEvent> OrderIdChanged;
|
||||
public event EventHandler<NewBrokerageOrderNotificationEventArgs> NewBrokerageOrderNotification;
|
||||
public event EventHandler<OrderUpdateEvent> OrderUpdated;
|
||||
#pragma warning restore 0067
|
||||
|
||||
public string Name => "NullBrokerage";
|
||||
public bool IsConnected { get; } = true;
|
||||
public List<Order> GetOpenOrders() { return new List<Order>(); }
|
||||
public List<Holding> GetAccountHoldings() { return new List<Holding>(); }
|
||||
public List<CashAmount> GetCashBalance() { return new List<CashAmount>(); }
|
||||
public bool PlaceOrder(Order order) { return true; }
|
||||
public bool UpdateOrder(Order order) { return true; }
|
||||
public bool CancelOrder(Order order) { return true; }
|
||||
public void Connect() {}
|
||||
public void Disconnect() {}
|
||||
public bool AccountInstantlyUpdated { get; } = true;
|
||||
public string AccountBaseCurrency => Currencies.USD;
|
||||
public virtual IEnumerable<BaseData> GetHistory(HistoryRequest request) { return Enumerable.Empty<BaseData>(); }
|
||||
public DateTime LastSyncDateTimeUtc { get; } = DateTime.UtcNow;
|
||||
public bool ConcurrencyEnabled { get; set; }
|
||||
|
||||
public bool ShouldPerformCashSync(DateTime currentTimeUtc) { return false; }
|
||||
public bool PerformCashSync(IAlgorithm algorithm, DateTime currentTimeUtc, Func<TimeSpan> getTimeSinceLastFill) { return true; }
|
||||
|
||||
public void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
OrdersStatusChanged?.Invoke(this, [orderEvent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class AlgorithmNamingTest
|
||||
{
|
||||
#region Naming tests
|
||||
|
||||
private static void SetAlgorithmNameUsingPropertySetter(QCAlgorithm algorithm, string name)
|
||||
{
|
||||
algorithm.Name = name;
|
||||
}
|
||||
|
||||
private static void SetAlgorithmNameUsingSetMethod(QCAlgorithm algorithm, string name)
|
||||
{
|
||||
algorithm.SetName(name);
|
||||
}
|
||||
|
||||
private static void TestSettingAlgorithmName(Action<QCAlgorithm, string> setName)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var name = "TestName";
|
||||
setName(algorithm, name);
|
||||
Assert.AreEqual(name, algorithm.Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmNameIsSetUsingPropertySetter()
|
||||
{
|
||||
TestSettingAlgorithmName(SetAlgorithmNameUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmNameIsSetUsingSetMethod()
|
||||
{
|
||||
TestSettingAlgorithmName(SetAlgorithmNameUsingSetMethod);
|
||||
}
|
||||
|
||||
private static void TestSettingLongAlgorithmName(Action<QCAlgorithm, string> setName)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var name = new string('a', MaxNameAndTagsLength + 1);
|
||||
setName(algorithm, name);
|
||||
Assert.AreEqual(name.Substring(0, MaxNameAndTagsLength), algorithm.Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmNameTruncatedUsingPropertySetter()
|
||||
{
|
||||
TestSettingLongAlgorithmName(SetAlgorithmNameUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmNameTruncatedUsingSetMethod()
|
||||
{
|
||||
TestSettingLongAlgorithmName(SetAlgorithmNameUsingSetMethod);
|
||||
}
|
||||
|
||||
private static void TestSettingAlgorithmNameAfterInitialization(Action<QCAlgorithm, string> setName)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetLocked();
|
||||
Assert.Throws<InvalidOperationException>(() => setName(algorithm, "TestName"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmNameCannotBeSetAfterInitializationUsingPropertySetter()
|
||||
{
|
||||
TestSettingAlgorithmNameAfterInitialization(SetAlgorithmNameUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmNameCannotBeSetAfterInitializationUsingSetMethod()
|
||||
{
|
||||
TestSettingAlgorithmNameAfterInitialization(SetAlgorithmNameUsingSetMethod);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tagging tests
|
||||
|
||||
private static void SetAlgorithmTagsUsingPropertySetter(QCAlgorithm algorithm, string[] tags)
|
||||
{
|
||||
algorithm.Tags = tags?.ToHashSet();
|
||||
}
|
||||
|
||||
private static void SetAlgorithmTagsUsingSetMethod(QCAlgorithm algorithm, string[] tags)
|
||||
{
|
||||
algorithm.SetTags(tags?.ToHashSet());
|
||||
}
|
||||
|
||||
private static void TestAlgorithmTagsAreSet(Action<QCAlgorithm, string[]> setTags, QCAlgorithm algorithm, string[] tags)
|
||||
{
|
||||
setTags(algorithm, tags);
|
||||
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
|
||||
}
|
||||
|
||||
private static void TestSettingAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tags = new[] { "tag1", "tag2" };
|
||||
TestAlgorithmTagsAreSet(setTags, algorithm, tags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreSetUsingPropertySetter()
|
||||
{
|
||||
TestSettingAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreSetUsingSetMethod()
|
||||
{
|
||||
TestSettingAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
|
||||
}
|
||||
|
||||
private static void TestOverridingAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
|
||||
var tags = new[] { "tag1", "tag2" };
|
||||
TestAlgorithmTagsAreSet(setTags, algorithm, tags);
|
||||
|
||||
var newTags = new[] { "tag3", "tag4" };
|
||||
TestAlgorithmTagsAreSet(setTags, algorithm, newTags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsCanBeOverriddenUsingPropertySetter()
|
||||
{
|
||||
TestOverridingAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsCanBeOverriddenUsingSetMethod()
|
||||
{
|
||||
TestOverridingAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
|
||||
}
|
||||
|
||||
public static void TestSettingNullAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
setTags(algorithm, null);
|
||||
CollectionAssert.IsEmpty(algorithm.Tags);
|
||||
|
||||
var tags = new[] { "tag1", "tag2" };
|
||||
setTags(algorithm, tags);
|
||||
setTags(algorithm, null);
|
||||
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreLeftUntouchedWhenTryingToSetNullUsingPropertySetter()
|
||||
{
|
||||
TestSettingNullAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreLeftUntouchedWhenTryingToSetNullUsingSetMethod()
|
||||
{
|
||||
TestSettingNullAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
|
||||
}
|
||||
|
||||
private static void TestSettingTooManyAlgorithmTags(Action<QCAlgorithm, string[]> setTags)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tags = Enumerable.Range(0, MaxTagsCount + 1).Select(i => $"tag{i}").ToArray();
|
||||
setTags(algorithm, tags);
|
||||
CollectionAssert.AreEquivalent(tags.ToHashSet().Take(MaxTagsCount), algorithm.Tags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsCollectionIsTruncatedWhenSettingTooManyUsingPropertySetter()
|
||||
{
|
||||
TestSettingTooManyAlgorithmTags(SetAlgorithmTagsUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsCollectionIsTruncatedWhenSettingTooManyUsingSetMethod()
|
||||
{
|
||||
TestSettingTooManyAlgorithmTags(SetAlgorithmTagsUsingSetMethod);
|
||||
}
|
||||
|
||||
private static void TestSettingTagsAreTruncatedWhenTooLong(Action<QCAlgorithm, string[]> setTags)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tags = Enumerable.Range(0, MaxTagsCount)
|
||||
.Select(i => "tag" + string.Concat(Enumerable.Repeat(i, MaxNameAndTagsLength + 1)))
|
||||
.ToArray();
|
||||
setTags(algorithm, tags);
|
||||
CollectionAssert.AreEquivalent(tags.ToHashSet(t => t.Substring(0, MaxNameAndTagsLength)), algorithm.Tags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreTruncatedWhenTooLongUsingPropertySetter()
|
||||
{
|
||||
TestSettingTagsAreTruncatedWhenTooLong(SetAlgorithmTagsUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreTruncatedWhenTooLongUsingSetMethod()
|
||||
{
|
||||
TestSettingTagsAreTruncatedWhenTooLong(SetAlgorithmTagsUsingSetMethod);
|
||||
}
|
||||
|
||||
public static void TestSettingEmptyTagsAreIgnored(Action<QCAlgorithm, string[]> setTags)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tags = new[] { "tag1", "", "tag2", null, "tag3", " " };
|
||||
setTags(algorithm, tags);
|
||||
|
||||
var expectedTags = new[] { "tag1", "tag2", "tag3" };
|
||||
CollectionAssert.AreEquivalent(expectedTags, algorithm.Tags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmSetContainingEmptyTagsAreIgnoredUsingPropertySetter()
|
||||
{
|
||||
TestSettingEmptyTagsAreIgnored(SetAlgorithmTagsUsingPropertySetter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmSetContainingEmptyTagsAreIgnoredUsingSetMethod()
|
||||
{
|
||||
TestSettingEmptyTagsAreIgnored(SetAlgorithmTagsUsingSetMethod);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AlgorithmTagsAreAddedOneByOne()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tags = new List<string>();
|
||||
for (var i = 0; i < MaxTagsCount; i++)
|
||||
{
|
||||
tags.Add($"tag{i}");
|
||||
algorithm.AddTag(tags.Last());
|
||||
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DuplicatedTagsAreIgnored()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
|
||||
var tag = "tag";
|
||||
algorithm.AddTag(tag);
|
||||
Assert.AreEqual(1, algorithm.Tags.Count);
|
||||
Assert.AreEqual(tag, algorithm.Tags.Single());
|
||||
|
||||
algorithm.AddTag(tag);
|
||||
Assert.AreEqual(1, algorithm.Tags.Count);
|
||||
Assert.AreEqual(tag, algorithm.Tags.Single());
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase(" ")]
|
||||
[TestCase(null)]
|
||||
public void EmptyTagsAreIgnored(string emptyTag)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
|
||||
algorithm.AddTag(emptyTag);
|
||||
CollectionAssert.IsEmpty(algorithm.Tags);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LongTagsAreTruncatedWhenAddedOneByOne()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tag = new string('a', MaxNameAndTagsLength + 1);
|
||||
algorithm.AddTag(tag);
|
||||
Assert.AreEqual(1, algorithm.Tags.Count);
|
||||
Assert.AreEqual(tag.Substring(0, MaxNameAndTagsLength), algorithm.Tags.Single());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsAreIgnoredWhenAddedOneByOneIfCollectionIsFull()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var tags = Enumerable.Range(0, MaxTagsCount).Select(i => $"tag{i}").ToArray();
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
algorithm.AddTag(tag);
|
||||
}
|
||||
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
|
||||
|
||||
// This will not be added
|
||||
algorithm.AddTag("LastTag");
|
||||
CollectionAssert.AreEquivalent(tags, algorithm.Tags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class TestQCAlgorithm : QCAlgorithm
|
||||
{
|
||||
public static int PublicMaxNameAndTagsLength => MaxNameAndTagsLength;
|
||||
|
||||
public static int PublicMaxTagsCount => MaxTagsCount;
|
||||
}
|
||||
|
||||
private static int MaxNameAndTagsLength => TestQCAlgorithm.PublicMaxNameAndTagsLength;
|
||||
private static int MaxTagsCount => TestQCAlgorithm.PublicMaxTagsCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Tests.Indicators;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
|
||||
public class AlgorithmPlottingTests
|
||||
{
|
||||
private Symbol _spy;
|
||||
private QCAlgorithm _algorithm;
|
||||
private IEnumerable<Type> _indicatorTestsTypes;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_algorithm = new AlgorithmStub();
|
||||
_spy = _algorithm.AddEquity("SPY").Symbol;
|
||||
|
||||
_indicatorTestsTypes =
|
||||
from type in GetType().Assembly.GetTypes()
|
||||
where type.IsPublic && !type.IsAbstract
|
||||
where
|
||||
typeof(CommonIndicatorTests<TradeBar>).IsAssignableFrom(type) ||
|
||||
typeof(CommonIndicatorTests<IBaseDataBar>).IsAssignableFrom(type) ||
|
||||
typeof(CommonIndicatorTests<IndicatorDataPoint>).IsAssignableFrom(type)
|
||||
select type;
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void IgnorePlotDuringLiveWarmup(bool liveMode)
|
||||
{
|
||||
_algorithm.SetLiveMode(liveMode);
|
||||
|
||||
_algorithm.Plot("Chart", 1);
|
||||
_algorithm.Plot("Chart", "Series", 2);
|
||||
|
||||
foreach (var chart in _algorithm.GetChartUpdates(true))
|
||||
{
|
||||
foreach (var serie in chart.Series)
|
||||
{
|
||||
if (liveMode)
|
||||
{
|
||||
Assert.IsEmpty(serie.Value.Values);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotEmpty(serie.Value.Values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_algorithm.SetFinishedWarmingUp();
|
||||
_algorithm.Plot("Chart", 1);
|
||||
_algorithm.Plot("Chart", "Series", 2);
|
||||
|
||||
foreach (var chart in _algorithm.GetChartUpdates(true))
|
||||
{
|
||||
foreach (var serie in chart.Series)
|
||||
{
|
||||
Assert.IsNotEmpty(serie.Value.Values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGetChartUpdatesWhileAdding()
|
||||
{
|
||||
var task1 = Task.Factory.StartNew(() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
_algorithm.AddChart(new Chart($"Test_{i}"));
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
});
|
||||
|
||||
var task2 = Task.Factory.StartNew(() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
_algorithm.GetChartUpdates(true).ToList();
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
});
|
||||
|
||||
Task.WaitAll(task1, task2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlotPythonIndicatorInSeparateChart()
|
||||
{
|
||||
PyObject indicator;
|
||||
|
||||
foreach (var type in _indicatorTestsTypes)
|
||||
{
|
||||
var indicatorTest = Activator.CreateInstance(type);
|
||||
if (indicatorTest is CommonIndicatorTests<IndicatorDataPoint>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<IndicatorDataPoint>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IBaseDataBar>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<IBaseDataBar>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<TradeBar>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<TradeBar>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"RegistersIndicatorProperlyPython(): Unsupported indicator data type: {indicatorTest.GetType()}");
|
||||
}
|
||||
|
||||
Assert.DoesNotThrow(() => _algorithm.Plot($"TestIndicatorPlot-{type.Name}", indicator));
|
||||
var charts = _algorithm.GetChartUpdates();
|
||||
Assert.IsTrue(charts.Select(x => x.Name == $"TestIndicatorPlot-{type.Name}").Any());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlotPythonCustomIndicatorProperly()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(
|
||||
Guid.NewGuid().ToString(),
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
class PythonCustomIndicator(PythonIndicator):
|
||||
def __init__(self):
|
||||
self.Value = 0
|
||||
def Update(self, input):
|
||||
self.Value = input.Value
|
||||
return True"
|
||||
);
|
||||
|
||||
dynamic customIndicator = module.GetAttr("PythonCustomIndicator").Invoke();
|
||||
customIndicator.Name = "custom";
|
||||
var input = new IndicatorDataPoint();
|
||||
input.Value = 10;
|
||||
customIndicator.Update(input);
|
||||
customIndicator.Current.Value = customIndicator.Value;
|
||||
Assert.DoesNotThrow(() => _algorithm.Plot("PlotTest", customIndicator));
|
||||
var charts = _algorithm.GetChartUpdates().ToList();
|
||||
Assert.IsTrue(charts.Where(x => x.Name == "PlotTest").Any());
|
||||
Assert.AreEqual(10, charts.First().Series["custom"].GetValues<ChartPoint>().First().y);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlotCustomIndicatorAsDefault()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(
|
||||
Guid.NewGuid().ToString(),
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class CustomIndicator:
|
||||
def __init__(self):
|
||||
self.Value = 10
|
||||
def Update(self, input):
|
||||
self.Value = input.Value
|
||||
return True"
|
||||
);
|
||||
|
||||
var customIndicator = module.GetAttr("CustomIndicator").Invoke();
|
||||
Assert.DoesNotThrow(() => _algorithm.Plot("PlotTest", customIndicator));
|
||||
var charts = _algorithm.GetChartUpdates().ToList();
|
||||
Assert.IsFalse(charts.Where(x => x.Name == "PlotTest").Any());
|
||||
Assert.IsTrue(charts.Where(x => x.Name == "Strategy Equity").Any());
|
||||
Assert.AreEqual(10, charts.First().Series["PlotTest"].GetValues<ChartPoint>().First().y);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlotIndicatorPlotsBaseIndicator()
|
||||
{
|
||||
var sma1 = new SimpleMovingAverage(1);
|
||||
var sma2 = new SimpleMovingAverage(1);
|
||||
var ratio = sma1.Over(sma2);
|
||||
|
||||
Assert.DoesNotThrow(() => _algorithm.PlotIndicator("PlotTest", ratio));
|
||||
|
||||
sma1.Update(new DateTime(2022, 11, 15), 1);
|
||||
sma2.Update(new DateTime(2022, 11, 15), 2);
|
||||
|
||||
var charts = _algorithm.GetChartUpdates().ToList();
|
||||
Assert.IsTrue(charts.Where(x => x.Name == "PlotTest").Any());
|
||||
|
||||
var chart = charts.First();
|
||||
Assert.AreEqual("PlotTest", chart.Name);
|
||||
Assert.AreEqual(sma1.Current.Value / sma2.Current.Value, chart.Series[ratio.Name].GetValues<ChartPoint>().First().y);
|
||||
}
|
||||
|
||||
private static string[] ReservedSummaryStatisticNames => Enumerable.Range(0, 101).Select(i => i.ToStringInvariant()).ToArray();
|
||||
|
||||
[TestCaseSource(nameof(ReservedSummaryStatisticNames))]
|
||||
public void ThrowsOnReservedSummaryStatisticName(string statisticName)
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => _algorithm.SetSummaryStatistic(statisticName, 0.1m));
|
||||
Assert.Throws<ArgumentException>(() => _algorithm.SetSummaryStatistic(statisticName, 0.1));
|
||||
Assert.Throws<ArgumentException>(() => _algorithm.SetSummaryStatistic(statisticName, 1));
|
||||
Assert.Throws<ArgumentException>(() => _algorithm.SetSummaryStatistic(statisticName, "0.1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Tests.Indicators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using Moq;
|
||||
using NodaTime;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmRegisterIndicatorTests
|
||||
{
|
||||
private Symbol _spy;
|
||||
private Symbol _option;
|
||||
private QCAlgorithm _algorithm;
|
||||
private IEnumerable<Type> _indicatorTestsTypes;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
_spy = _algorithm.AddEquity("SPY").Symbol;
|
||||
_option = _algorithm.AddOption("SPY").Symbol;
|
||||
|
||||
_indicatorTestsTypes =
|
||||
from type in GetType().Assembly.GetTypes()
|
||||
where type.IsPublic && !type.IsAbstract
|
||||
where
|
||||
typeof(CommonIndicatorTests<TradeBar>).IsAssignableFrom(type) ||
|
||||
typeof(CommonIndicatorTests<IBaseDataBar>).IsAssignableFrom(type) ||
|
||||
typeof(CommonIndicatorTests<IndicatorDataPoint>).IsAssignableFrom(type)
|
||||
select type;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegistersIndicatorProperly()
|
||||
{
|
||||
var expected = 0;
|
||||
|
||||
foreach (var type in _indicatorTestsTypes)
|
||||
{
|
||||
var indicatorTest = Activator.CreateInstance(type);
|
||||
if (indicatorTest is OptionBaseIndicatorTests<OptionIndicatorBase>)
|
||||
{
|
||||
var indicator = (indicatorTest as OptionBaseIndicatorTests<OptionIndicatorBase>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_option, indicator, Resolution.Minute));
|
||||
expected++;
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IndicatorDataPoint>)
|
||||
{
|
||||
var indicator = (indicatorTest as CommonIndicatorTests<IndicatorDataPoint>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute, Field.Close));
|
||||
expected++;
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IBaseDataBar>)
|
||||
{
|
||||
var indicator = (indicatorTest as CommonIndicatorTests<IBaseDataBar>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute));
|
||||
expected++;
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<TradeBar>)
|
||||
{
|
||||
var indicator = (indicatorTest as CommonIndicatorTests<TradeBar>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute));
|
||||
expected++;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"RegistersIndicatorProperlyPython(): Unsupported indicator data type: {indicatorTest.GetType()}");
|
||||
}
|
||||
var actual = _algorithm.SubscriptionManager.Subscriptions
|
||||
.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity))
|
||||
.Consolidators.Count;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static TestCaseData[] IndicatorNameParameters => new[]
|
||||
{
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Tick, "TEST(SPY_tick)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Second, "TEST(SPY_sec)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Minute, "TEST(SPY_min)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Hour, "TEST(SPY_hr)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Daily, "TEST(SPY_day)"),
|
||||
new TestCaseData(Symbol.Empty, "TEST", Resolution.Minute, "TEST(min)"),
|
||||
new TestCaseData(Symbol.None, "TEST", Resolution.Minute, "TEST(min)"),
|
||||
new TestCaseData(Symbol.Empty, "TEST", null, "TEST()"),
|
||||
new TestCaseData(Symbol.None, "TEST", null, "TEST()")
|
||||
};
|
||||
|
||||
[Test, TestCaseSource(nameof(IndicatorNameParameters))]
|
||||
public void CreateIndicatorName(Symbol symbol, string baseName, Resolution? resolution, string expectation)
|
||||
{
|
||||
Assert.AreEqual(expectation, _algorithm.CreateIndicatorName(symbol, baseName, resolution));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlotAndRegistersIndicatorProperlyPython()
|
||||
{
|
||||
var expected = 0;
|
||||
PyObject indicator;
|
||||
|
||||
foreach (var type in _indicatorTestsTypes)
|
||||
{
|
||||
var indicatorTest = Activator.CreateInstance(type);
|
||||
if (indicatorTest is OptionBaseIndicatorTests<OptionIndicatorBase>)
|
||||
{
|
||||
indicator = (indicatorTest as OptionBaseIndicatorTests<OptionIndicatorBase>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IndicatorDataPoint>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<IndicatorDataPoint>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IBaseDataBar>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<IBaseDataBar>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<TradeBar>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<TradeBar>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"RegistersIndicatorProperlyPython(): Unsupported indicator data type: {indicatorTest.GetType()}");
|
||||
}
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute));
|
||||
Assert.DoesNotThrow(() => _algorithm.Plot(_spy.Value, indicator));
|
||||
expected++;
|
||||
|
||||
var actual = _algorithm.SubscriptionManager.Subscriptions
|
||||
.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity))
|
||||
.Consolidators.Count;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterPythonCustomIndicatorProperly()
|
||||
{
|
||||
const string code = @"
|
||||
class GoodCustomIndicator:
|
||||
def __init__(self):
|
||||
self.IsReady = True
|
||||
self.Value = 0
|
||||
def Update(self, input):
|
||||
self.Value = input.Value
|
||||
return True
|
||||
class BadCustomIndicator:
|
||||
def __init__(self):
|
||||
self.IsReady = True
|
||||
self.Value = 0
|
||||
def Updat(self, input):
|
||||
self.Value = input.Value
|
||||
return True";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(), code);
|
||||
|
||||
var goodIndicator = module.GetAttr("GoodCustomIndicator").Invoke();
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, goodIndicator, Resolution.Minute));
|
||||
|
||||
var actual = _algorithm.SubscriptionManager.Subscriptions
|
||||
.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity))
|
||||
.Consolidators.Count;
|
||||
Assert.AreEqual(1, actual);
|
||||
|
||||
var badIndicator = module.GetAttr("BadCustomIndicator").Invoke();
|
||||
Assert.Throws<NotImplementedException>(() => _algorithm.RegisterIndicator(_spy, badIndicator, Resolution.Minute));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegistersIndicatorProperlyPythonScript()
|
||||
{
|
||||
const string code = @"
|
||||
from AlgorithmImports import *
|
||||
|
||||
AddReference('QuantConnect.Lean.Engine')
|
||||
from QuantConnect.Lean.Engine.DataFeeds import *
|
||||
|
||||
algo = QCAlgorithm()
|
||||
|
||||
marketHoursDatabase = MarketHoursDatabase.FromDataFolder()
|
||||
symbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder()
|
||||
securityService = SecurityService(algo.Portfolio.CashBook, marketHoursDatabase, symbolPropertiesDatabase, algo, RegisteredSecurityDataTypesProvider.Null, SecurityCacheProvider(algo.Portfolio), algorithm=algo)
|
||||
algo.Securities.SetSecurityService(securityService)
|
||||
dataPermissionManager = DataPermissionManager()
|
||||
dataManager = DataManager(None, UniverseSelection(algo, securityService, dataPermissionManager, None), algo, algo.TimeKeeper, marketHoursDatabase, False, RegisteredSecurityDataTypesProvider.Null, dataPermissionManager)
|
||||
algo.SubscriptionManager.SetDataManager(dataManager)
|
||||
|
||||
|
||||
forex = algo.AddForex('EURUSD', Resolution.Daily)
|
||||
indicator = IchimokuKinkoHyo('EURUSD', 9, 26, 26, 52, 26, 26)
|
||||
algo.RegisterIndicator(forex.Symbol, indicator, Resolution.Daily)";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
Assert.DoesNotThrow(() => PyModule.FromString("RegistersIndicatorProperlyPythonScript", code));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsCanBeRegisteredWithTickDataSelectors()
|
||||
{
|
||||
var ibm = _algorithm.AddEquity("IBM", Resolution.Tick).Symbol;
|
||||
var indicator = _algorithm.Identity(ibm, Resolution.Tick, Field.BidPrice);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(new Tick() { BidPrice = 101 });
|
||||
Assert.AreEqual(101, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndicatorUseDefaultSelectorWhenResolutionDoesNotMatchWithSelectorDataTypeTestCases))]
|
||||
public void IndicatorUseDefaultSelectorWhenDataTypeDoesNotMatchWithSelectorDataType(
|
||||
Symbol symbol,
|
||||
SecurityType securityType,
|
||||
Resolution resolution,
|
||||
Func<IBaseData, decimal> selector,
|
||||
IBaseData input,
|
||||
decimal expectedValue)
|
||||
{
|
||||
_algorithm.AddSecurity(symbol, resolution);
|
||||
var indicator = _algorithm.Identity(symbol, resolution, selector);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(input);
|
||||
Assert.AreEqual(expectedValue, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsCanBeRegisteredWithQuoteDataSelectors()
|
||||
{
|
||||
var ibm = _algorithm.AddEquity("IBM", Resolution.Minute).Symbol;
|
||||
var indicator = _algorithm.Identity(ibm, Resolution.Minute, Field.BidClose);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(new QuoteBar() { Bid = new Bar() { Close = 101 }});
|
||||
Assert.AreEqual(101, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndicatorsCanBeWarmedUpWithDataSelectorsTestCases))]
|
||||
public void IndicatorsCanBeWarmedUpWithDataSelectors(Symbol symbol,
|
||||
SecurityType securityType,
|
||||
Resolution resolution,
|
||||
Func<IBaseData, decimal> selector,
|
||||
Slice warmUpinput,
|
||||
decimal expectedValue)
|
||||
{
|
||||
_algorithm.Settings.AutomaticIndicatorWarmUp = true;
|
||||
_algorithm.AddSecurity(symbol, resolution);
|
||||
|
||||
var historyProvider = new Mock<SubscriptionDataReaderHistoryProvider>();
|
||||
historyProvider.Setup(x => x.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>())).Returns(new List<Slice>() { warmUpinput });
|
||||
_algorithm.SetHistoryProvider(historyProvider.Object);
|
||||
var indicator = _algorithm.Identity(symbol, resolution, selector);
|
||||
|
||||
Assert.AreEqual(expectedValue, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsCanBeRegisteredWithTradeDataSelectors()
|
||||
{
|
||||
var ibm = _algorithm.AddEquity("IBM", Resolution.Minute).Symbol;
|
||||
var indicator = _algorithm.Identity(ibm, Resolution.Minute, Field.Volume);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(new TradeBar() { Volume = 101 });
|
||||
Assert.AreEqual(101, indicator.Current.Value);
|
||||
}
|
||||
|
||||
public static object[] IndicatorUseDefaultSelectorWhenResolutionDoesNotMatchWithSelectorDataTypeTestCases =
|
||||
{
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Tick, Field.BidClose, new Tick() { BidPrice = 101, Value = 102 }, 102m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Tick, Field.Volume, new Tick() { Quantity = 101, Value = 102 }, 101m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.BidPrice, new QuoteBar() { Value = 102, Bid = new Bar() { Close = 103 } }, 103m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.AskPrice, new QuoteBar() { Value = 102, Ask = new Bar() { Close = 103 } }, 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.BidPrice, new QuoteBar() { Value = 102, Bid = new Bar() { Close = 103} }, 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.AskPrice, new QuoteBar() { Value = 102, Ask = new Bar() { Close = 103} }, 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.BidPrice, new QuoteBar() { Value = 102, Bid = new Bar() { Close = 103 } }, 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.AskPrice, new QuoteBar() { Value = 102, Ask = new Bar() { Close = 103 } }, 103m }
|
||||
};
|
||||
|
||||
public static object[] IndicatorsCanBeWarmedUpWithDataSelectorsTestCases =
|
||||
{
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.BidPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.IBM, Bid = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.Fut_SPY_Feb19_2016, SecurityType.Future, Resolution.Minute, Field.Volume, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars() { new TradeBar() { Symbol = Symbols.IBM, Volume = 103m } },
|
||||
new QuoteBars(),
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.AskPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.IBM, Ask = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.BidPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.EURGBP, Bid = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.AskPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.EURGBP, Ask = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.BidPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.SPY_C_192_Feb19_2016, Bid = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.AskPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.EURGBP, Ask = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class AlgorithmResolveConsolidatorTests
|
||||
{
|
||||
[TestCase(SecurityType.Equity, TickType.Trade, "SPY")]
|
||||
[TestCase(SecurityType.Crypto, TickType.Trade, "BTCUSD")]
|
||||
[TestCase(SecurityType.Forex, TickType.Quote, "EURUSD")]
|
||||
[TestCase(SecurityType.Cfd, TickType.Quote, "WTICOUSD")]
|
||||
public void ConsolidatorHasSameTypeAsSubscriptionDataConfig(SecurityType securityType, TickType expectedTickType, string ticker)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddSecurity(securityType, ticker);
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Minute);
|
||||
|
||||
var inputType = security.Subscriptions.Single(s=>s.TickType==expectedTickType).Type;
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(inputType, outputType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TradeBarToTradeBar()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Minute);
|
||||
|
||||
var inputType = security.Subscriptions.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity)).Type;
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(inputType, outputType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QuoteBarToQuoteBar()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddForex("EURUSD");
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Minute);
|
||||
|
||||
var inputType = security.SubscriptionDataConfig.Type;
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(inputType, outputType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TickTypeTradeToTradeBar()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddEquity("SPY", Resolution.Tick);
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Minute);
|
||||
|
||||
var tickType = security.Subscriptions.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity)).TickType;
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(TickType.Trade, tickType);
|
||||
Assert.AreEqual(typeof(TradeBar), outputType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TickTypeQuoteToQuoteBar()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddForex("EURUSD", Resolution.Tick);
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Minute);
|
||||
|
||||
var tickType = security.Subscriptions.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Forex)).TickType;
|
||||
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(TickType.Quote, tickType);
|
||||
Assert.AreEqual(typeof(QuoteBar), outputType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TickTypeTradeToTick()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddEquity("SPY", Resolution.Tick);
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Tick);
|
||||
|
||||
var tickType = security.Subscriptions.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity)).TickType;
|
||||
var inputType = security.SubscriptionDataConfig.Type;
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(TickType.Trade, tickType);
|
||||
Assert.AreEqual(inputType, outputType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TickTypeQuoteToTick()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddForex("EURUSD", Resolution.Tick);
|
||||
var consolidator = algorithm.ResolveConsolidator(security.Symbol, Resolution.Tick);
|
||||
|
||||
var tickType = security.SubscriptionDataConfig.TickType;
|
||||
var inputType = security.SubscriptionDataConfig.Type;
|
||||
var outputType = consolidator.OutputType;
|
||||
|
||||
Assert.AreEqual(TickType.Quote, tickType);
|
||||
Assert.AreEqual(inputType, outputType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Benchmarks;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Orders.Fills;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders.Slippage;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Test class for
|
||||
/// - SetBrokerageModel() in QCAlgorithm
|
||||
/// - Default market for new securities
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class AlgorithmSetBrokerageTests
|
||||
{
|
||||
private QCAlgorithm _algo;
|
||||
private const string ForexSym = "EURUSD";
|
||||
private const string Sym = "SPY";
|
||||
|
||||
/// <summary>
|
||||
/// Instatiate a new algorithm before each test.
|
||||
/// Clear the <see cref="SymbolCache"/> so that no symbols and associated brokerage models are cached between test
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_algo = new QCAlgorithm();
|
||||
_algo.SubscriptionManager.SetDataManager(new DataManagerStub(_algo));
|
||||
SymbolCache.TryRemove(ForexSym);
|
||||
SymbolCache.TryRemove(Sym);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default market for FOREX should be Oanda
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DefaultBrokerageModel_IsOanda_ForForex()
|
||||
{
|
||||
var forex = _algo.AddForex(ForexSym);
|
||||
|
||||
|
||||
Assert.IsTrue(forex.Symbol.ID.Market == Market.Oanda);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(DefaultBrokerageModel));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCallPureCSharpSetBrokerageModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var model = new AlphaStreamsBrokerageModel().ToPython();
|
||||
_algo.SetBrokerageModel(model);
|
||||
Assert.DoesNotThrow(() => _algo.BrokerageModel.ApplySplit(new List<OrderTicket>(), new Split()));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCallSetBrokerageModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var model = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class Test(AlphaStreamsBrokerageModel):
|
||||
def GetLeverage(self, security):
|
||||
return 12").GetAttr("Test");
|
||||
_algo.SetBrokerageModel(model.Invoke());
|
||||
|
||||
var equity = _algo.AddEquity(Sym);
|
||||
Assert.DoesNotThrow(() => _algo.BrokerageModel.ApplySplit(new List<OrderTicket>(), new Split()));
|
||||
Assert.AreEqual(12m, _algo.BrokerageModel.GetLeverage(equity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default market for equities should be USA
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DefaultBrokerageModel_IsUSA_ForEquity()
|
||||
{
|
||||
var equity = _algo.AddEquity(Sym);
|
||||
|
||||
|
||||
Assert.IsTrue(equity.Symbol.ID.Market == Market.USA);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(DefaultBrokerageModel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default market for options should be USA
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DefaultBrokerageModel_IsUSA_ForOption()
|
||||
{
|
||||
var option = _algo.AddOption(Sym);
|
||||
|
||||
|
||||
Assert.IsTrue(option.Symbol.ID.Market == Market.USA);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(DefaultBrokerageModel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brokerage model for an algorithm can be changed using <see cref="QCAlgorithm.SetBrokerageModel(IBrokerageModel)"/>
|
||||
/// This changes the brokerage models used when forex currency pairs are added via AddForex and no brokerage is specified.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BrokerageModel_CanBeSpecifiedWith_SetBrokerageModel()
|
||||
{
|
||||
_algo.SetBrokerageModel(BrokerageName.OandaBrokerage);
|
||||
var forex = _algo.AddForex(ForexSym);
|
||||
|
||||
string brokerage = GetDefaultBrokerageForSecurityType(SecurityType.Forex);
|
||||
|
||||
|
||||
Assert.IsTrue(forex.Symbol.ID.Market == Market.Oanda);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(OandaBrokerageModel));
|
||||
Assert.IsTrue(brokerage == Market.Oanda);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifying the market in <see cref="QCAlgorithm.AddForex"/> will change the market of the security created.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BrokerageModel_CanBeSpecifiedWith_AddForex()
|
||||
{
|
||||
var forex = _algo.AddForex(ForexSym, Resolution.Minute, Market.FXCM);
|
||||
|
||||
string brokerage = GetDefaultBrokerageForSecurityType(SecurityType.Forex);
|
||||
|
||||
|
||||
Assert.IsTrue(forex.Symbol.ID.Market == Market.FXCM);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(DefaultBrokerageModel));
|
||||
Assert.IsTrue(brokerage == Market.Oanda); // Doesn't change brokerage defined in BrokerageModel.DefaultMarkets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method <see cref="QCAlgorithm.AddSecurity(SecurityType, string, Resolution, bool, bool)"/> should use the default brokerage for the sepcific security.
|
||||
/// Setting the brokerage with <see cref="QCAlgorithm.SetBrokerageModel(IBrokerageModel)"/> will affect the market of securities added with <see cref="QCAlgorithm.AddSecurity(SecurityType, string, Resolution, bool, bool)"/>
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddSecurity_Follows_SetBrokerageModel()
|
||||
{
|
||||
// No brokerage set
|
||||
var equity = _algo.AddSecurity(SecurityType.Equity, Sym);
|
||||
|
||||
string equityBrokerage = GetDefaultBrokerageForSecurityType(SecurityType.Equity);
|
||||
|
||||
|
||||
Assert.IsTrue(equity.Symbol.ID.Market == Market.USA);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(DefaultBrokerageModel));
|
||||
Assert.IsTrue(equityBrokerage == Market.USA);
|
||||
|
||||
// Set Brokerage
|
||||
_algo.SetBrokerageModel(BrokerageName.OandaBrokerage);
|
||||
|
||||
var sec = _algo.AddSecurity(SecurityType.Forex, ForexSym, Resolution.Daily, false, 1, false);
|
||||
|
||||
string forexBrokerage = GetDefaultBrokerageForSecurityType(SecurityType.Forex);
|
||||
|
||||
|
||||
Assert.IsTrue(sec.Symbol.ID.Market == Market.Oanda);
|
||||
Assert.IsTrue(_algo.BrokerageModel.GetType() == typeof(OandaBrokerageModel));
|
||||
Assert.IsTrue(forexBrokerage == Market.Oanda);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddSecurityCanAddWithSameTickerAndDifferentMarket()
|
||||
{
|
||||
var fxcmSecurity = _algo.AddSecurity(SecurityType.Forex, "EURUSD", Resolution.Minute, Market.FXCM, true, 1m, true);
|
||||
var oandaSecurity = _algo.AddSecurity(SecurityType.Forex, "EURUSD", Resolution.Minute, Market.Oanda, true, 1m, true);
|
||||
|
||||
Assert.AreEqual(2, _algo.Securities.Count);
|
||||
Assert.IsNotNull(_algo.Securities.Single(pair => pair.Key.ID.Market == Market.FXCM));
|
||||
Assert.IsNotNull(_algo.Securities.Single(pair => pair.Key.ID.Market == Market.Oanda));
|
||||
Assert.AreEqual(Market.FXCM, fxcmSecurity.Symbol.ID.Market);
|
||||
Assert.AreEqual(Market.Oanda, oandaSecurity.Symbol.ID.Market);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddForexCanAddWithSameTickerAndDifferentMarket()
|
||||
{
|
||||
var fxcmSecurity = _algo.AddForex("EURUSD", Resolution.Minute, Market.FXCM);
|
||||
var oandaSecurity = _algo.AddForex("EURUSD", Resolution.Minute, Market.Oanda);
|
||||
|
||||
Assert.AreEqual(2, _algo.Securities.Count);
|
||||
Assert.IsNotNull(_algo.Securities.Single(pair => pair.Key.ID.Market == Market.FXCM));
|
||||
Assert.IsNotNull(_algo.Securities.Single(pair => pair.Key.ID.Market == Market.Oanda));
|
||||
Assert.AreEqual(Market.FXCM, fxcmSecurity.Symbol.ID.Market);
|
||||
Assert.AreEqual(Market.Oanda, oandaSecurity.Symbol.ID.Market);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void BrokerageNameFollowsSetBrokerageModel(Language language)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
Assert.AreEqual(BrokerageName.Default, _algo.BrokerageName);
|
||||
|
||||
_algo.SetBrokerageModel(BrokerageName.OandaBrokerage);
|
||||
Assert.AreEqual(BrokerageName.OandaBrokerage, _algo.BrokerageName);
|
||||
|
||||
_algo.SetBrokerageModel(new InteractiveBrokersBrokerageModel());
|
||||
Assert.AreEqual(BrokerageName.InteractiveBrokersBrokerage, _algo.BrokerageName);
|
||||
|
||||
_algo.SetBrokerageModel(new CustomBrokerageModel());
|
||||
Assert.AreEqual(BrokerageName.Default, _algo.BrokerageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getAlgorithm():
|
||||
return QCAlgorithm()
|
||||
|
||||
def setBrokerageModel(algorithm, brokerageModel):
|
||||
algorithm.SetBrokerageModel(brokerageModel)
|
||||
|
||||
def getBrokerageName(algorithm):
|
||||
return algorithm.BrokerageName
|
||||
");
|
||||
|
||||
var getAlgorithm = testModule.GetAttr("getAlgorithm");
|
||||
var algorithm = getAlgorithm.Invoke();
|
||||
|
||||
var setBrokerageModel = testModule.GetAttr("setBrokerageModel");
|
||||
var getBrokerageName = testModule.GetAttr("getBrokerageName");
|
||||
|
||||
Assert.AreEqual(BrokerageName.Default, getBrokerageName.Invoke(algorithm).AsManagedObject(typeof(BrokerageName)));
|
||||
|
||||
setBrokerageModel.Invoke(algorithm, BrokerageName.OandaBrokerage.ToPython());
|
||||
Assert.AreEqual(BrokerageName.OandaBrokerage, getBrokerageName.Invoke(algorithm).AsManagedObject(typeof(BrokerageName)));
|
||||
|
||||
setBrokerageModel.Invoke(algorithm, new InteractiveBrokersBrokerageModel().ToPython());
|
||||
Assert.AreEqual(BrokerageName.InteractiveBrokersBrokerage, getBrokerageName.Invoke(algorithm).AsManagedObject(typeof(BrokerageName)));
|
||||
|
||||
setBrokerageModel.Invoke(algorithm, new CustomBrokerageModel().ToPython());
|
||||
Assert.AreEqual(BrokerageName.Default, getBrokerageName.Invoke(algorithm).AsManagedObject(typeof(BrokerageName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default market for a security type
|
||||
/// </summary>
|
||||
/// <param name="secType">The type of security</param>
|
||||
/// <returns>A string representing the default market of a security</returns>
|
||||
private string GetDefaultBrokerageForSecurityType(SecurityType secType)
|
||||
{
|
||||
string brokerage;
|
||||
_algo.BrokerageModel.DefaultMarkets.TryGetValue(secType, out brokerage);
|
||||
return brokerage;
|
||||
}
|
||||
|
||||
private class CustomBrokerageModel : IBrokerageModel
|
||||
{
|
||||
public AccountType AccountType => throw new System.NotImplementedException();
|
||||
|
||||
public decimal RequiredFreeBuyingPowerPercent => throw new System.NotImplementedException();
|
||||
|
||||
public IReadOnlyDictionary<SecurityType, string> DefaultMarkets => throw new System.NotImplementedException();
|
||||
|
||||
public void ApplySplit(List<OrderTicket> tickets, Split split)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool CanExecuteOrder(Security security, Order order)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IBenchmark GetBenchmark(SecurityManager securities)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IBuyingPowerModel GetBuyingPowerModel(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IBuyingPowerModel GetBuyingPowerModel(Security security, AccountType accountType)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IFeeModel GetFeeModel(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IFillModel GetFillModel(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public decimal GetLeverage(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IMarginInterestRateModel GetMarginInterestRateModel(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ISettlementModel GetSettlementModel(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ISettlementModel GetSettlementModel(Security security, AccountType accountType)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IShortableProvider GetShortableProvider(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ISlippageModel GetSlippageModel(Security security)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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 QuantConnect.Util;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Tests.Common.Securities;
|
||||
using QuantConnect.Lean.Engine.Setup;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class AlgorithmSettingsTest
|
||||
{
|
||||
[Test]
|
||||
public void DefaultTrueValueOfLiquidateWorksCorrectly()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
var fakeOrderProcessor = InitializeAndGetFakeOrderProcessor(algo);
|
||||
|
||||
algo.Liquidate();
|
||||
|
||||
// It should send a order to set us flat
|
||||
Assert.IsFalse(fakeOrderProcessor.ProcessedOrdersRequests.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DisablingLiquidateWorksCorrectly()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
algo.Settings.LiquidateEnabled = false;
|
||||
var fakeOrderProcessor = InitializeAndGetFakeOrderProcessor(algo);
|
||||
|
||||
algo.Liquidate();
|
||||
|
||||
// It should NOT send a order to set us flat
|
||||
Assert.IsTrue(fakeOrderProcessor.ProcessedOrdersRequests.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SettingSetHoldingsBufferWorksCorrectly()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
algo.Settings.FreePortfolioValue = 0;
|
||||
InitializeAndGetFakeOrderProcessor(algo);
|
||||
|
||||
var actual = algo.CalculateOrderQuantity(Symbols.SPY, 1m);
|
||||
// 100000 / 20 - 2 due to fee =
|
||||
Assert.AreEqual(4998m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultValueOfSetHoldingsBufferWorksCorrectly()
|
||||
{
|
||||
var algo = new QCAlgorithm();
|
||||
|
||||
InitializeAndGetFakeOrderProcessor(algo);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
var actual = algo.CalculateOrderQuantity(Symbols.SPY, 1m);
|
||||
// 100000 / 20 - 1 due to fee - effect of the target being reduced because of FreePortfolioValuePercentage
|
||||
Assert.AreEqual(4986m, actual);
|
||||
}
|
||||
|
||||
[TestCase(BrokerageName.FTX, 365)]
|
||||
[TestCase(BrokerageName.RBI, 252)]
|
||||
[TestCase(BrokerageName.Eze, 252)]
|
||||
[TestCase(BrokerageName.Axos, 252)]
|
||||
[TestCase(BrokerageName.Samco, 252)]
|
||||
[TestCase(BrokerageName.FTXUS, 365)]
|
||||
[TestCase(BrokerageName.Bybit, 365)]
|
||||
[TestCase(BrokerageName.Kraken, 365)]
|
||||
[TestCase(BrokerageName.Exante, 252)]
|
||||
[TestCase(BrokerageName.Binance, 365)]
|
||||
[TestCase(BrokerageName.Default, 252)]
|
||||
[TestCase(BrokerageName.Zerodha, 252)]
|
||||
[TestCase(BrokerageName.Bitfinex, 365)]
|
||||
[TestCase(BrokerageName.Wolverine, 252)]
|
||||
[TestCase(BrokerageName.TDAmeritrade, 252)]
|
||||
[TestCase(BrokerageName.FxcmBrokerage, 252)]
|
||||
[TestCase(BrokerageName.OandaBrokerage, 252)]
|
||||
[TestCase(BrokerageName.BinanceFutures, 365)]
|
||||
[TestCase(BrokerageName.TradierBrokerage, 252)]
|
||||
[TestCase(BrokerageName.BinanceCoinFutures, 365)]
|
||||
[TestCase(BrokerageName.TradingTechnologies, 252)]
|
||||
[TestCase(BrokerageName.QuantConnectBrokerage, 252)]
|
||||
[TestCase(BrokerageName.Coinbase, 365, AccountType.Cash)]
|
||||
[TestCase(BrokerageName.BinanceUS, 365, AccountType.Cash)]
|
||||
[TestCase(BrokerageName.InteractiveBrokersBrokerage, 252)]
|
||||
public void ReturnUniqueTradingDayPerYearDependOnBrokerageName(BrokerageName brokerageName, int expectedTradingDayPerYear, AccountType accountType = AccountType.Margin)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetBrokerageModel(brokerageName, accountType);
|
||||
|
||||
BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm);
|
||||
|
||||
Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear.Value);
|
||||
}
|
||||
|
||||
[TestCase(BrokerageName.Bybit, 202, 365)]
|
||||
[TestCase(BrokerageName.InteractiveBrokersBrokerage, 404, 252)]
|
||||
public void ReturnCustomTradingDayPerYearIndependentlyFromBrokerageName(BrokerageName brokerageName, int customTradingDayPerYear, int expectedDefaultTradingDayPerYearForBrokerage)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetBrokerageModel(brokerageName);
|
||||
algorithm.Settings.TradingDaysPerYear = customTradingDayPerYear;
|
||||
|
||||
// duplicate: make sure that custom value is assigned
|
||||
BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm);
|
||||
|
||||
Assert.AreNotEqual(expectedDefaultTradingDayPerYearForBrokerage, algorithm.Settings.TradingDaysPerYear);
|
||||
}
|
||||
|
||||
[TestCase(252, null)]
|
||||
[TestCase(404, 404)]
|
||||
public void ReturnTradingDayPerYearWithoutSetBrokerage(int expectedTradingDayPerYear, int? customTradingDayPerYear = null)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
|
||||
if (customTradingDayPerYear.HasValue)
|
||||
{
|
||||
algorithm.Settings.TradingDaysPerYear = customTradingDayPerYear.Value;
|
||||
}
|
||||
|
||||
BaseSetupHandler.SetBrokerageTradingDayPerYear(algorithm);
|
||||
|
||||
Assert.AreEqual(expectedTradingDayPerYear, algorithm.Settings.TradingDaysPerYear);
|
||||
}
|
||||
|
||||
private FakeOrderProcessor InitializeAndGetFakeOrderProcessor(QCAlgorithm algo)
|
||||
{
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
algo.SetFinishedWarmingUp();
|
||||
algo.SetCash(100000);
|
||||
var symbol = algo.AddEquity("SPY").Symbol;
|
||||
var fakeOrderProcessor = new FakeOrderProcessor();
|
||||
algo.Transactions.SetOrderProcessor(fakeOrderProcessor);
|
||||
algo.Portfolio[symbol].SetHoldings(1, 10);
|
||||
var security = algo.Securities[symbol];
|
||||
security.SetMarketPrice(new TradeBar
|
||||
{
|
||||
Time = DateTime.Now,
|
||||
Symbol = security.Symbol,
|
||||
Open = 20,
|
||||
High = 20,
|
||||
Low = 20,
|
||||
Close = 20
|
||||
});
|
||||
|
||||
Assert.IsTrue(fakeOrderProcessor.ProcessedOrdersRequests.IsNullOrEmpty());
|
||||
return fakeOrderProcessor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class AlgorithmSubscriptionManagerRemoveConsolidatorTests
|
||||
{
|
||||
[Test]
|
||||
public void RemoveConsolidatorClearsEventHandlers()
|
||||
{
|
||||
bool eventHandlerFired = false;
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var security = algorithm.AddEquity("SPY");
|
||||
var consolidator = new IdentityDataConsolidator<BaseData>();
|
||||
consolidator.DataConsolidated += (sender, consolidated) => eventHandlerFired = true;
|
||||
security.Subscriptions.First().Consolidators.Add(consolidator);
|
||||
|
||||
algorithm.SubscriptionManager.RemoveConsolidator(security.Symbol, consolidator);
|
||||
|
||||
consolidator.Update(new Tick());
|
||||
Assert.IsFalse(eventHandlerFired);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmUniverseSettingsTests
|
||||
{
|
||||
[TestCase(DataNormalizationMode.Raw)]
|
||||
[TestCase(DataNormalizationMode.Adjusted)]
|
||||
[TestCase(DataNormalizationMode.SplitAdjusted)]
|
||||
[TestCase(DataNormalizationMode.TotalReturn)]
|
||||
public void CheckUniverseSelectionSecurityDataNormalizationMode(DataNormalizationMode dataNormalizationMode)
|
||||
{
|
||||
var tuple = GetAlgorithmAndDataManager();
|
||||
var algorithm = tuple.Item1;
|
||||
var dataManager = tuple.Item2;
|
||||
|
||||
var symbol = Symbols.SPY;
|
||||
algorithm.UniverseSettings.DataNormalizationMode = dataNormalizationMode;
|
||||
algorithm.AddUniverse(coarse => new[] { symbol });
|
||||
// OnEndOfTimeStep will add all pending universe additions
|
||||
algorithm.OnEndOfTimeStep();
|
||||
|
||||
var changes = dataManager.UniverseSelection
|
||||
.ApplyUniverseSelection(
|
||||
algorithm.UniverseManager.First().Value,
|
||||
algorithm.UtcTime,
|
||||
new BaseDataCollection(algorithm.UtcTime, null, Enumerable.Empty<CoarseFundamental>()));
|
||||
|
||||
Assert.AreEqual(1, changes.AddedSecurities.Count);
|
||||
|
||||
var security = changes.AddedSecurities.First();
|
||||
Assert.AreEqual(symbol, security.Symbol);
|
||||
Assert.AreEqual(dataNormalizationMode, security.DataNormalizationMode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CheckManualSecurityDataNormalizationModePersistence()
|
||||
{
|
||||
var tuple = GetAlgorithmAndDataManager();
|
||||
var algorithm = tuple.Item1;
|
||||
var dataManager = tuple.Item2;
|
||||
|
||||
var expected = DataNormalizationMode.Adjusted;
|
||||
var spy = algorithm.AddEquity("SPY");
|
||||
Assert.AreEqual(expected, spy.DataNormalizationMode);
|
||||
|
||||
var symbol = spy.Symbol;
|
||||
algorithm.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
|
||||
algorithm.AddUniverse(coarse => new[] { symbol });
|
||||
// OnEndOfTimeStep will add all pending universe additions
|
||||
algorithm.OnEndOfTimeStep();
|
||||
|
||||
var changes = dataManager.UniverseSelection
|
||||
.ApplyUniverseSelection(
|
||||
algorithm.UniverseManager.First().Value,
|
||||
algorithm.UtcTime,
|
||||
new BaseDataCollection(algorithm.UtcTime, null, Enumerable.Empty<CoarseFundamental>()));
|
||||
|
||||
Assert.AreEqual(1, changes.AddedSecurities.Count);
|
||||
|
||||
var security = changes.AddedSecurities.First();
|
||||
Assert.AreEqual(symbol, security.Symbol);
|
||||
Assert.AreEqual(spy, security);
|
||||
Assert.AreEqual(expected, security.DataNormalizationMode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CheckManualSecurityDataNormalizationModePersistenceAfterUniverseSetting()
|
||||
{
|
||||
var tuple = GetAlgorithmAndDataManager();
|
||||
var algorithm = tuple.Item1;
|
||||
var dataManager = tuple.Item2;
|
||||
|
||||
// Set our universe mode to raw
|
||||
var universeMode = DataNormalizationMode.Raw;
|
||||
algorithm.UniverseSettings.DataNormalizationMode = universeMode;
|
||||
|
||||
// Verify that the security was set to raw
|
||||
var spy = algorithm.AddEquity("SPY");
|
||||
Assert.AreEqual(universeMode, spy.DataNormalizationMode);
|
||||
|
||||
// Modify the mode of the security manually
|
||||
var manualMode = DataNormalizationMode.TotalReturn;
|
||||
spy.SetDataNormalizationMode(manualMode);
|
||||
Assert.AreEqual(manualMode, spy.DataNormalizationMode);
|
||||
|
||||
var symbol = spy.Symbol;
|
||||
algorithm.AddUniverse(coarse => new[] { symbol });
|
||||
// OnEndOfTimeStep will add all pending universe additions
|
||||
algorithm.OnEndOfTimeStep();
|
||||
|
||||
var changes = dataManager.UniverseSelection
|
||||
.ApplyUniverseSelection(
|
||||
algorithm.UniverseManager.First().Value,
|
||||
algorithm.UtcTime,
|
||||
new BaseDataCollection(algorithm.UtcTime, null, Enumerable.Empty<CoarseFundamental>()));
|
||||
|
||||
Assert.AreEqual(1, changes.AddedSecurities.Count);
|
||||
|
||||
var security = changes.AddedSecurities.First();
|
||||
Assert.AreEqual(symbol, security.Symbol);
|
||||
Assert.AreEqual(spy, security);
|
||||
|
||||
// Assert that our manual setting persisted
|
||||
Assert.AreEqual(manualMode, security.DataNormalizationMode);
|
||||
}
|
||||
|
||||
private Tuple<QCAlgorithm, DataManager> GetAlgorithmAndDataManager()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
|
||||
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
|
||||
var symbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder();
|
||||
var dataPermissionManager = new DataPermissionManager();
|
||||
var dataManager = new DataManager(
|
||||
new MockDataFeed(),
|
||||
new UniverseSelection(
|
||||
algorithm,
|
||||
new SecurityService(
|
||||
algorithm.Portfolio.CashBook,
|
||||
marketHoursDatabase,
|
||||
symbolPropertiesDatabase,
|
||||
algorithm,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCacheProvider(algorithm.Portfolio),
|
||||
algorithm: algorithm),
|
||||
dataPermissionManager,
|
||||
TestGlobals.DataProvider),
|
||||
algorithm,
|
||||
algorithm.TimeKeeper,
|
||||
marketHoursDatabase,
|
||||
false,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
dataPermissionManager);
|
||||
|
||||
var securityService = new SecurityService(
|
||||
algorithm.Portfolio.CashBook,
|
||||
marketHoursDatabase,
|
||||
symbolPropertiesDatabase,
|
||||
algorithm,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCacheProvider(algorithm.Portfolio),
|
||||
algorithm: algorithm);
|
||||
|
||||
algorithm.SubscriptionManager.SetDataManager(dataManager);
|
||||
algorithm.Securities.SetSecurityService(securityService);
|
||||
return Tuple.Create(algorithm, dataManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Statistics;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmWarmupTests
|
||||
{
|
||||
private TestWarmupAlgorithm _algorithm;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
Config.Reset();
|
||||
}
|
||||
|
||||
[TestCase(Resolution.Tick, SecurityType.Forex)]
|
||||
[TestCase(Resolution.Second, SecurityType.Forex)]
|
||||
[TestCase(Resolution.Hour, SecurityType.Forex)]
|
||||
[TestCase(Resolution.Minute, SecurityType.Forex)]
|
||||
[TestCase(Resolution.Daily, SecurityType.Forex)]
|
||||
[TestCase(Resolution.Tick, SecurityType.Equity)]
|
||||
[TestCase(Resolution.Second, SecurityType.Equity)]
|
||||
[TestCase(Resolution.Hour, SecurityType.Equity)]
|
||||
[TestCase(Resolution.Minute, SecurityType.Equity)]
|
||||
[TestCase(Resolution.Daily, SecurityType.Equity)]
|
||||
[TestCase(Resolution.Minute, SecurityType.Crypto)]
|
||||
[TestCase(Resolution.Daily, SecurityType.Crypto)]
|
||||
public void WarmupDifferentResolutions(Resolution resolution, SecurityType securityType)
|
||||
{
|
||||
var warmupPeriod = resolution != Resolution.Tick ? TimeSpan.FromDays(2) : TimeSpan.FromHours(10);
|
||||
_algorithm = TestSetupHandler.TestAlgorithm = new TestWarmupAlgorithm(resolution, warmupPeriod);
|
||||
|
||||
_algorithm.SecurityType = securityType;
|
||||
if (securityType == SecurityType.Forex)
|
||||
{
|
||||
_algorithm.StartDateToUse = new DateTime(2014, 05, 03);
|
||||
_algorithm.EndDateToUse = new DateTime(2014, 05, 04);
|
||||
}
|
||||
else if (securityType == SecurityType.Equity)
|
||||
{
|
||||
_algorithm.StartDateToUse = new DateTime(2013, 10, 09);
|
||||
_algorithm.EndDateToUse = new DateTime(2013, 10, 10);
|
||||
}
|
||||
else if (securityType == SecurityType.Crypto)
|
||||
{
|
||||
_algorithm.StartDateToUse = new DateTime(2018, 04, 06);
|
||||
_algorithm.EndDateToUse = new DateTime(2018, 04, 07);
|
||||
}
|
||||
|
||||
AlgorithmRunner.RunLocalBacktest(nameof(TestWarmupAlgorithm),
|
||||
new Dictionary<string, string> { { PerformanceMetrics.TotalOrders, "1" } },
|
||||
Language.CSharp,
|
||||
AlgorithmStatus.Completed,
|
||||
setupHandler: "TestSetupHandler");
|
||||
|
||||
int estimateExpectedDataCount;
|
||||
switch (resolution)
|
||||
{
|
||||
case Resolution.Tick:
|
||||
estimateExpectedDataCount = 2 * (securityType == SecurityType.Forex ? 19 : 4) * 60;
|
||||
break;
|
||||
case Resolution.Second:
|
||||
estimateExpectedDataCount = 2 * (securityType == SecurityType.Forex ? 19 : 6) * 60 * 60;
|
||||
break;
|
||||
case Resolution.Minute:
|
||||
estimateExpectedDataCount = 2 * (securityType == SecurityType.Forex ? 19 : 6) * 60;
|
||||
break;
|
||||
case Resolution.Hour:
|
||||
estimateExpectedDataCount = 2 * (securityType == SecurityType.Forex ? 19 : 6);
|
||||
break;
|
||||
case Resolution.Daily:
|
||||
// Warmup is 2 days. During warmup we expect the daily data point which goes from T-2 to T-1, once warmup finished,
|
||||
// we will get T-1 to T data point which is let through but the data feed since the algorithm starts at T
|
||||
estimateExpectedDataCount = 1;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
|
||||
}
|
||||
|
||||
Log.Debug($"WarmUpDataCount: {_algorithm.WarmUpDataCount}. Resolution {resolution}. SecurityType {securityType}");
|
||||
Assert.GreaterOrEqual(_algorithm.WarmUpDataCount, estimateExpectedDataCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WarmUpInternalSubscriptions()
|
||||
{
|
||||
var algo = new AlgorithmStub(new MockDataFeed())
|
||||
{
|
||||
HistoryProvider = new SubscriptionDataReaderHistoryProvider()
|
||||
};
|
||||
algo.Settings.SeedInitialPrices = false;
|
||||
|
||||
algo.SetStartDate(2013, 10, 08);
|
||||
algo.AddCfd("DE30EUR", Resolution.Second, Market.Oanda);
|
||||
algo.SetWarmup(10);
|
||||
algo.PostInitialize();
|
||||
algo.DataManager.UniverseSelection.EnsureCurrencyDataFeeds(SecurityChanges.None);
|
||||
|
||||
Assert.AreEqual(algo.StartDate - TimeSpan.FromSeconds(10), algo.Time);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WarmUpUniverseSelection()
|
||||
{
|
||||
var algo = new AlgorithmStub(new MockDataFeed())
|
||||
{
|
||||
HistoryProvider = new SubscriptionDataReaderHistoryProvider()
|
||||
};
|
||||
|
||||
algo.SetStartDate(2013, 10, 08);
|
||||
var universe = algo.AddUniverse((_) => Enumerable.Empty<Symbol>());
|
||||
var barCount = 3;
|
||||
algo.SetWarmup(barCount);
|
||||
algo.PostInitialize();
|
||||
|
||||
// +2 is due to the weekend
|
||||
Assert.AreEqual(algo.StartDate - universe.Configuration.Resolution.ToTimeSpan() * (barCount + 2), algo.Time);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WarmUpPythonIndicatorProperly()
|
||||
{
|
||||
var algo = new AlgorithmStub
|
||||
{
|
||||
HistoryProvider = new SubscriptionDataReaderHistoryProvider()
|
||||
};
|
||||
algo.HistoryProvider.Initialize(new HistoryProviderInitializeParameters(
|
||||
null,
|
||||
null,
|
||||
TestGlobals.DataProvider,
|
||||
TestGlobals.DataCacheProvider,
|
||||
TestGlobals.MapFileProvider,
|
||||
TestGlobals.FactorFileProvider,
|
||||
null,
|
||||
false,
|
||||
new DataPermissionManager(),
|
||||
algo.ObjectStore,
|
||||
algo.Settings));
|
||||
algo.SetStartDate(2013, 10, 08);
|
||||
algo.AddEquity("SPY", Resolution.Minute);
|
||||
|
||||
// Different types of indicators
|
||||
var indicatorDataPoint = new SimpleMovingAverage("SPY", 10);
|
||||
var indicatorDataBar = new AverageTrueRange("SPY", 10);
|
||||
var indicatorTradeBar = new VolumeWeightedAveragePriceIndicator("SPY", 10);
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var sma = indicatorDataPoint.ToPython();
|
||||
var atr = indicatorTradeBar.ToPython();
|
||||
var vwapi = indicatorDataBar.ToPython();
|
||||
#pragma warning disable CS0618
|
||||
Assert.DoesNotThrow(() => algo.WarmUpIndicator("SPY", sma, Resolution.Minute));
|
||||
Assert.DoesNotThrow(() => algo.WarmUpIndicator("SPY", atr, Resolution.Minute));
|
||||
Assert.DoesNotThrow(() => algo.WarmUpIndicator("SPY", vwapi, Resolution.Minute));
|
||||
#pragma warning restore CS0618
|
||||
var smaIsReady = ((dynamic)sma).IsReady;
|
||||
var atrIsReady = ((dynamic)atr).IsReady;
|
||||
var vwapiIsReady = ((dynamic)vwapi).IsReady;
|
||||
|
||||
Assert.IsTrue(smaIsReady.IsTrue());
|
||||
Assert.IsTrue(atrIsReady.IsTrue());
|
||||
Assert.IsTrue(vwapiIsReady.IsTrue());
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void WarmupStartDate_NoAsset(bool withResolution)
|
||||
{
|
||||
var algo = new AlgorithmStub();
|
||||
algo.SetStartDate(2013, 10, 01);
|
||||
DateTime expected;
|
||||
if (withResolution)
|
||||
{
|
||||
algo.SetWarmUp(100, Resolution.Daily);
|
||||
expected = new DateTime(2013, 06, 23);
|
||||
}
|
||||
else
|
||||
{
|
||||
algo.SetWarmUp(100);
|
||||
// defaults to universe settings
|
||||
expected = new DateTime(2013, 09, 30, 22, 20, 0);
|
||||
}
|
||||
algo.PostInitialize();
|
||||
|
||||
Assert.AreEqual(expected, algo.Time);
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void WarmupStartDate_Equity_BarCount(bool withResolution)
|
||||
{
|
||||
var algo = new AlgorithmStub(new NullDataFeed { ShouldThrow = false });
|
||||
algo.SetStartDate(2013, 10, 01);
|
||||
algo.AddEquity("AAPL");
|
||||
// since SPY is a smaller resolution, won't affect in the bar count case, only the smallest warmup start time will be used
|
||||
algo.AddEquity("SPY", Resolution.Tick);
|
||||
DateTime expected;
|
||||
if (withResolution)
|
||||
{
|
||||
algo.SetWarmUp(100, Resolution.Daily);
|
||||
expected = new DateTime(2013, 05, 09);
|
||||
}
|
||||
else
|
||||
{
|
||||
algo.SetWarmUp(100);
|
||||
// uses the assets resolution
|
||||
expected = new DateTime(2013, 9, 30, 14, 20, 0);
|
||||
}
|
||||
algo.PostInitialize();
|
||||
|
||||
// before than the case with no asset because takes into account 100 tradable dates of AAPL
|
||||
Assert.AreEqual(expected, algo.Time);
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
public void WarmupStart_Equivalents(int testCase)
|
||||
{
|
||||
var algo = new AlgorithmStub(new NullDataFeed { ShouldThrow = false });
|
||||
algo.SetStartDate(2013, 10, 01);
|
||||
algo.AddEquity("AAPL", Resolution.Daily);
|
||||
// since SPY is a smaller resolution, won't affect in the bar count case, only the smallest warmup start time will be used
|
||||
algo.AddEquity("SPY", Resolution.Tick);
|
||||
var expected = new DateTime(2013, 09, 20);
|
||||
if (testCase == 0)
|
||||
{
|
||||
algo.SetWarmUp(7, Resolution.Daily);
|
||||
}
|
||||
else if (testCase == 1)
|
||||
{
|
||||
algo.SetWarmUp(7);
|
||||
}
|
||||
else if (testCase == 2)
|
||||
{
|
||||
algo.SetWarmUp(7);
|
||||
algo.Settings.WarmupResolution = Resolution.Daily;
|
||||
}
|
||||
else if (testCase == 3)
|
||||
{
|
||||
// account for 2 weeknds
|
||||
algo.SetWarmUp(TimeSpan.FromDays(11), Resolution.Daily);
|
||||
}
|
||||
else if (testCase == 4)
|
||||
{
|
||||
// account for 2 weeknds
|
||||
algo.SetWarmUp(TimeSpan.FromDays(11));
|
||||
algo.Settings.WarmupResolution = Resolution.Daily;
|
||||
}
|
||||
else if (testCase == 5)
|
||||
{
|
||||
// account for 2 weeknds
|
||||
algo.SetWarmUp(TimeSpan.FromDays(11));
|
||||
}
|
||||
algo.PostInitialize();
|
||||
|
||||
Assert.AreEqual(expected, algo.Time);
|
||||
}
|
||||
|
||||
[TestCase("UTC")]
|
||||
[TestCase("Asia/Hong_Kong")]
|
||||
[TestCase("America/New_York")]
|
||||
public void WarmupEndTime(string timeZone)
|
||||
{
|
||||
var algo = new AlgorithmStub(new NullDataFeed { ShouldThrow = false });
|
||||
algo.SetLiveMode(true);
|
||||
|
||||
algo.SetWarmup(TimeSpan.FromDays(1));
|
||||
algo.SetTimeZone(timeZone);
|
||||
algo.PostInitialize();
|
||||
algo.SetLocked();
|
||||
|
||||
Assert.IsTrue(algo.IsWarmingUp);
|
||||
|
||||
var start = DateTime.UtcNow;
|
||||
|
||||
algo.SetDateTime(start.AddMinutes(-1));
|
||||
Assert.IsTrue(algo.IsWarmingUp);
|
||||
algo.SetDateTime(start);
|
||||
Assert.IsFalse(algo.IsWarmingUp);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WarmupResolutionPython()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic algo = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
from QuantConnect.Tests.Engine.DataFeeds import *
|
||||
|
||||
class TestAlgo(AlgorithmStub):
|
||||
def initialize(self):
|
||||
self.data_feed.should_throw = False
|
||||
|
||||
self.set_start_date(2013, 10, 1)
|
||||
self.add_equity(""AAPL"")
|
||||
self.set_warm_up(60)
|
||||
").GetAttr("TestAlgo").Invoke();
|
||||
|
||||
algo.initialize();
|
||||
algo.post_initialize();
|
||||
|
||||
// the last trading hour of the previous day
|
||||
Assert.AreEqual(new DateTime(2013, 09, 30, 15, 0, 0), (DateTime)algo.time);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void WarmupResolutionPythonPassThrough(bool passThrough)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic algo = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
from QuantConnect.Tests.Engine.DataFeeds import *
|
||||
|
||||
class TestAlgo(AlgorithmStub):
|
||||
def __init__(self, pass_through):
|
||||
self.pass_through = pass_through
|
||||
|
||||
def initialize(self):
|
||||
self.data_feed.should_throw = False
|
||||
|
||||
self.set_start_date(2013, 10, 1)
|
||||
self.add_equity(""AAPL"")
|
||||
self.set_warm_up(10)
|
||||
if self.pass_through:
|
||||
self.settings.warm_up_resolution = Resolution.DAILY
|
||||
else:
|
||||
self.settings.warmup_resolution = Resolution.DAILY
|
||||
").GetAttr("TestAlgo").Invoke(passThrough.ToPython());
|
||||
|
||||
algo.initialize();
|
||||
algo.post_initialize();
|
||||
|
||||
Assert.AreEqual(passThrough, (bool)algo.pass_through);
|
||||
// 10 daily bars including 2 weekends
|
||||
Assert.AreEqual(new DateTime(2013, 09, 17), (DateTime)algo.time);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestSetupHandler : AlgorithmRunner.RegressionSetupHandlerWrapper
|
||||
{
|
||||
public static TestWarmupAlgorithm TestAlgorithm { get; set; }
|
||||
|
||||
public override IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
|
||||
{
|
||||
Algorithm = TestAlgorithm;
|
||||
return Algorithm;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestWarmupAlgorithm : QCAlgorithm
|
||||
{
|
||||
private readonly Resolution _resolution;
|
||||
private readonly TimeSpan _warmupPeriod;
|
||||
private Symbol _symbol;
|
||||
public SecurityType SecurityType { get; set; }
|
||||
|
||||
public DateTime StartDateToUse { get; set; }
|
||||
|
||||
public DateTime EndDateToUse { get; set; }
|
||||
|
||||
public int WarmUpDataCount { get; set; }
|
||||
|
||||
public TestWarmupAlgorithm(Resolution resolution, TimeSpan warmupPeriod)
|
||||
{
|
||||
_resolution = resolution;
|
||||
_warmupPeriod = warmupPeriod;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(StartDateToUse);
|
||||
SetEndDate(EndDateToUse);
|
||||
|
||||
if (SecurityType == SecurityType.Forex)
|
||||
{
|
||||
SetCash("NZD", 1);
|
||||
_symbol = AddForex("EURUSD", _resolution).Symbol;
|
||||
}
|
||||
else if (SecurityType == SecurityType.Equity)
|
||||
{
|
||||
_symbol = AddEquity("SPY", _resolution).Symbol;
|
||||
}
|
||||
else if (SecurityType == SecurityType.Crypto)
|
||||
{
|
||||
_symbol = AddCrypto("BTCUSD", _resolution).Symbol;
|
||||
}
|
||||
SetWarmUp(_warmupPeriod);
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (IsWarmingUp)
|
||||
{
|
||||
WarmUpDataCount += data.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
SetHoldings(_symbol, 1);
|
||||
Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Securities;
|
||||
using Moq;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Lean.Engine.TransactionHandlers;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Tests.Common.Securities;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class CashModelAlgorithmTradingTests
|
||||
{
|
||||
private static readonly Symbol _symbol = Symbols.BTCUSD;
|
||||
private static readonly string _cashSymbol = "BTC";
|
||||
|
||||
/*****************************************************/
|
||||
// Isostatic market conditions tests.
|
||||
/*****************************************************/
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToLong()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(1995m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToLong_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
// $100k total value * 0.5 target * 0.9975 FreePortfolioValuePercentage / 25 ~= 1995 - fees
|
||||
Assert.AreEqual(1994.96m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToLong_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
// 10k in fees = 400 shares (400*25)
|
||||
// $100k total value * 0.5 target * 0.9975 FreePortfolioValuePercentage / 25 ~= 1995 - 400 because of fees
|
||||
Assert.AreEqual(1595m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToShort()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
// no shorting allowed
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToShort_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
// no shorting allowed
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToShort_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
// no shorting allowed
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToLonger()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Calculate the new holdings:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.75m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(992.5m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToFullLong()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Calculate the new holdings:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 1m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
// 100k total value * 1 target * 0.9975 setHoldings buffer - 50K holdings -10K fees / @ 25 ~= 1590m
|
||||
Assert.AreEqual(1590m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToLonger_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Calculate the new holdings:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.75m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
// 100k total value * 0.75 target * 0.9975 setHoldings buffer - 50K holdings / @ 25 ~= 992m
|
||||
Assert.AreEqual(992.46m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToLonger_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Calculate the new holdings:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.75m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(592.5m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongerToLong()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//75% cash spent on 3000 shares.
|
||||
algo.Portfolio.SetCash(25000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 3000, 25);
|
||||
//Sell all 2000 held:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(-1005m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongerToLong_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
//75% cash spent on 3000 shares.
|
||||
algo.Portfolio.SetCash(25000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 3000, 25);
|
||||
// 100k total value * 0.5 target * 0.9975 setHoldings buffer - 75K holdings / @ 25 ~= -1005m
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(-1004.96m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongerToLong_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//75% cash spent on 3000 shares.
|
||||
algo.Portfolio.SetCash(25000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 3000, 25);
|
||||
//Sell all 2000 held:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(-605m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToZero()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Sell all 2000 held:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(-2000, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToZero_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Sell all 2000 held:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(-2000, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToZero_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
//Sell all 2000 held:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
Assert.AreEqual(-2000, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToShort()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
// no shorting allowed
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToShort_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
// no shorting allowed
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToShort_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
// no shorting allowed
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_HalfLongToFullShort()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
// no shorting allowed
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -1m);
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_HalfLongToFullShort_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
// no shorting allowed
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -1m);
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_HalfLongToFullShort_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
// no shorting allowed
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -1m);
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
/*****************************************************/
|
||||
// Rising market conditions tests.
|
||||
/*****************************************************/
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongFixed_PriceRise()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
Assert.AreEqual(100000, algo.Portfolio.TotalPortfolioValue);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
algo.Portfolio.InvalidateTotalPortfolioValue();
|
||||
|
||||
Assert.AreEqual(150000, algo.Portfolio.TotalPortfolioValue);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k.
|
||||
//Calculate the new holdings for 50% security::
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// 150k total value * 0.5 target * 0.9975 setHoldings buffer - 100K holdings / @ 50 = -503.75m
|
||||
Assert.AreEqual(-503.75m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongFixed_PriceRise_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k.
|
||||
//Calculate the new holdings for 50% security::
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// 150k total value * 0.5 target * 0.9975 setHoldings buffer - 100K holdings / @ 50 = -503.75m - $1 in fees
|
||||
Assert.AreEqual(-503.73m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongFixed_PriceRise_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k.
|
||||
//Calculate the new holdings for 50% security::
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// 150k total value * 0.5 target * 0.9975 setHoldings buffer - 100K holdings / @ 50 = -503.75m - -200 in fees
|
||||
Assert.AreEqual(-303.75m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToLonger_PriceRise()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k. security is already 66% of holdings.
|
||||
//Calculate the order for 75% security:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.75m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// 150k total value * 0.75 target * 0.9975 setHoldings buffer - 100K holdings / @ 50 = 244.375m
|
||||
Assert.AreEqual(244.375m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToLonger_PriceRise_SmallConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 1);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k. security is already 66% of holdings.
|
||||
//Calculate the order for 75% security:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.75m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// 150k total value * 0.75 target * 0.9975 setHoldings buffer - 100K holdings / @ 50 = 244.375m -$1 in fees
|
||||
Assert.AreEqual(244.355m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToLonger_PriceRise_HighConstantFeeStructure()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 10000);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k. security is already 66% of holdings.
|
||||
//Calculate the order for 75% security:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.75m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// 150k total value * 0.75 target * 0.9975 setHoldings buffer - 100K holdings -10k in fees / @ 50 = 44.375m
|
||||
Assert.AreEqual(44.375m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongerToLong_PriceRise()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
|
||||
//75% cash spent on 3000 shares.
|
||||
algo.Portfolio.SetCash(25000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 3000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
algo.Settings.FreePortfolioValue =
|
||||
algo.Portfolio.TotalPortfolioValue * algo.Settings.FreePortfolioValuePercentage;
|
||||
|
||||
//Now: 3000 * 50 = $150k Holdings, $25k Cash: $175k. security is 86% of holdings.
|
||||
//Calculate the order for 50% security:
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 0.5m);
|
||||
|
||||
Assert.IsTrue(security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio, security,
|
||||
new MarketOrder(_symbol, actual, DateTime.UtcNow)).IsSufficient);
|
||||
|
||||
// $175k total value * 0.5 target * 0.9975 setHoldings buffer - $150k holdings / @ 50 = -1254.375m
|
||||
Assert.AreEqual(-1254.375m, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_LongToShort_PriceRise()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
//Half cash spent on 2000 shares.
|
||||
algo.Portfolio.SetCash(50000);
|
||||
algo.Portfolio.CashBook.Add(_cashSymbol, 2000, 25);
|
||||
|
||||
//Price rises to $50.
|
||||
Update(algo.Portfolio.CashBook, security, 50);
|
||||
|
||||
//Now: 2000 * 50 = $100k Holdings, $50k Cash: $150k. security is 66% of holdings.
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, -0.5m);
|
||||
|
||||
// no shorting allowed
|
||||
Assert.AreEqual(0, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_ZeroToFullLong()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 1m * security.BuyingPowerModel.GetLeverage(security));
|
||||
// 100000 * 0.9975 / 25 = 3990m
|
||||
Assert.AreEqual(3990m, actual);
|
||||
var hashSufficientBuyingPower = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio,
|
||||
security, new MarketOrder(_symbol, actual, DateTime.UtcNow));
|
||||
Assert.IsTrue(hashSufficientBuyingPower.IsSufficient);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_Long_TooBigOfATarget()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 1m * security.BuyingPowerModel.GetLeverage(security) + 0.1m);
|
||||
|
||||
Assert.AreEqual(4389m, actual);
|
||||
var hashSufficientBuyingPower = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio,
|
||||
security, new MarketOrder(_symbol, actual, DateTime.UtcNow));
|
||||
Assert.IsFalse(hashSufficientBuyingPower.IsSufficient);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_PriceRise_VolatilityCoveredByBuffer()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 1m);
|
||||
Assert.AreEqual(3990m, actual);
|
||||
|
||||
//Price rises to 0.25%. We should be covered by buffer
|
||||
Update(algo.Portfolio.CashBook, security, security.Price * 1.0025m);
|
||||
|
||||
var hashSufficientBuyingPower = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio,
|
||||
security, new MarketOrder(_symbol, actual, DateTime.UtcNow));
|
||||
Assert.IsTrue(hashSufficientBuyingPower.IsSufficient);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetHoldings_PriceRise_VolatilityNotCoveredByBuffer()
|
||||
{
|
||||
Security security;
|
||||
var algo = GetAlgorithm(out security, 0);
|
||||
|
||||
var actual = algo.CalculateOrderQuantity(_symbol, 1m);
|
||||
Assert.AreEqual(3990m, actual);
|
||||
|
||||
// Price rises to 0.26%. We will not be covered by buffer
|
||||
Update(algo.Portfolio.CashBook, security, security.Price * 1.0026m);
|
||||
|
||||
var hashSufficientBuyingPower = security.BuyingPowerModel.HasSufficientBuyingPowerForOrder(algo.Portfolio,
|
||||
security, new MarketOrder(_symbol, actual, DateTime.UtcNow));
|
||||
Assert.IsFalse(hashSufficientBuyingPower.IsSufficient);
|
||||
}
|
||||
|
||||
|
||||
[TestCase(SecurityType.Forex)]
|
||||
[TestCase(SecurityType.Crypto)]
|
||||
public void OrderQuantityConversionTest(SecurityType securityType)
|
||||
{
|
||||
var algo = GetAlgorithm(out var security, 0, securityType);
|
||||
var symbol = security.Symbol;
|
||||
algo.Portfolio.SetCash(150000);
|
||||
|
||||
var mock = new Mock<ITransactionHandler>();
|
||||
var request = new Mock<SubmitOrderRequest>(null, null, null, null, null, null, null, null, null, null, null);
|
||||
mock.Setup(m => m.Process(It.IsAny<OrderRequest>())).Returns(new OrderTicket(null, request.Object));
|
||||
mock.Setup(m => m.GetOpenOrders(It.IsAny<Func<Order, bool>>())).Returns(new List<Order>());
|
||||
algo.Transactions.SetOrderProcessor(mock.Object);
|
||||
|
||||
algo.Buy(symbol, 1);
|
||||
algo.Buy(symbol, 1.0);
|
||||
algo.Buy(symbol, 1.0m);
|
||||
algo.Buy(symbol, 1.0f);
|
||||
|
||||
algo.Sell(symbol, 1);
|
||||
algo.Sell(symbol, 1.0);
|
||||
algo.Sell(symbol, 1.0m);
|
||||
algo.Sell(symbol, 1.0f);
|
||||
|
||||
algo.Order(symbol, 1);
|
||||
algo.Order(symbol, 1.0);
|
||||
algo.Order(symbol, 1.0m);
|
||||
algo.Order(symbol, 1.0f);
|
||||
|
||||
algo.MarketOrder(symbol, 1);
|
||||
algo.MarketOrder(symbol, 1.0);
|
||||
algo.MarketOrder(symbol, 1.0m);
|
||||
algo.MarketOrder(symbol, 1.0f);
|
||||
|
||||
int expected = 32;
|
||||
if (securityType == SecurityType.Crypto)
|
||||
{
|
||||
expected -= 6;
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => algo.MarketOnCloseOrder(symbol, 1));
|
||||
Assert.Throws<InvalidOperationException>(() => algo.MarketOnCloseOrder(symbol, 1.0));
|
||||
Assert.Throws<InvalidOperationException>(() => algo.MarketOnCloseOrder(symbol, 1.0m));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => algo.MarketOnOpenOrder(symbol, 1));
|
||||
Assert.Throws<InvalidOperationException>(() => algo.MarketOnOpenOrder(symbol, 1.0));
|
||||
Assert.Throws<InvalidOperationException>(() => algo.MarketOnOpenOrder(symbol, 1.0m));
|
||||
}
|
||||
else
|
||||
{
|
||||
algo.SetDateTime(new DateTime(2025, 04, 26, 18, 30, 0).ConvertToUtc(algo.TimeZone));
|
||||
algo.MarketOnOpenOrder(symbol, 1);
|
||||
algo.MarketOnOpenOrder(symbol, 1.0);
|
||||
algo.MarketOnOpenOrder(symbol, 1.0m);
|
||||
|
||||
algo.MarketOnCloseOrder(symbol, 1);
|
||||
algo.MarketOnCloseOrder(symbol, 1.0);
|
||||
algo.MarketOnCloseOrder(symbol, 1.0m);
|
||||
}
|
||||
|
||||
algo.LimitOrder(symbol, 1, 1);
|
||||
algo.LimitOrder(symbol, 1.0, 1);
|
||||
algo.LimitOrder(symbol, 1.0m, 1);
|
||||
|
||||
algo.StopMarketOrder(symbol, 1, 1);
|
||||
algo.StopMarketOrder(symbol, 1.0, 1);
|
||||
algo.StopMarketOrder(symbol, 1.0m, 1);
|
||||
|
||||
algo.SetHoldings(symbol, 1);
|
||||
algo.SetHoldings(symbol, 1.0);
|
||||
algo.SetHoldings(symbol, 1.0m);
|
||||
algo.SetHoldings(symbol, 1.0f);
|
||||
|
||||
Assert.AreEqual(expected, algo.Transactions.LastOrderId);
|
||||
}
|
||||
|
||||
private static QCAlgorithm GetAlgorithm(out Security security, decimal fee, SecurityType securityType = SecurityType.Crypto)
|
||||
{
|
||||
SymbolCache.Clear();
|
||||
// Initialize algorithm
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
algo.SetCash(100000);
|
||||
algo.SetBrokerageModel(BrokerageName.Coinbase, AccountType.Cash);
|
||||
algo.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
algo.SetFinishedWarmingUp();
|
||||
var cashSymbol = string.Empty;
|
||||
if (securityType == SecurityType.Crypto)
|
||||
{
|
||||
cashSymbol = "BTC";
|
||||
security = algo.AddSecurity(securityType, "BTCUSD");
|
||||
}
|
||||
else if(securityType == SecurityType.Forex)
|
||||
{
|
||||
cashSymbol = "EUR";
|
||||
security = algo.AddSecurity(securityType, "EURUSD");
|
||||
// set BPM to cash, since it's not the default
|
||||
security.BuyingPowerModel = new CashBuyingPowerModel();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Unexpected security type");
|
||||
}
|
||||
security.FeeModel = new ConstantFeeModel(fee);
|
||||
//Set price to $25
|
||||
Update(algo.Portfolio.CashBook, security, 25, cashSymbol);
|
||||
return algo;
|
||||
}
|
||||
|
||||
private static void Update(CashBook cashBook, Security security, decimal close, string cashSymbol = "BTC")
|
||||
{
|
||||
security.SetMarketPrice(new TradeBar
|
||||
{
|
||||
Time = new DateTime(2022, 3, 15, 8, 0, 0),
|
||||
Symbol = security.Symbol,
|
||||
Open = close,
|
||||
High = close,
|
||||
Low = close,
|
||||
Close = close
|
||||
});
|
||||
|
||||
if (cashBook.TryGetValue(cashSymbol, out var cash))
|
||||
{
|
||||
cash.ConversionRate = close;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class BasePairsTradingAlphaModelTests : CommonAlphaModelTests
|
||||
{
|
||||
private const int _lookback = 15;
|
||||
private const Resolution _resolution = Resolution.Minute;
|
||||
|
||||
protected override int MaxSliceCount => 1500;
|
||||
|
||||
protected override IAlphaModel CreateCSharpAlphaModel()
|
||||
{
|
||||
return new BasePairsTradingAlphaModel(_lookback, _resolution);
|
||||
}
|
||||
|
||||
protected override void InitializeAlgorithm(QCAlgorithm algorithm)
|
||||
{
|
||||
algorithm.SetUniverseSelection(new ManualUniverseSelectionModel(
|
||||
Symbol.Create("AIG", SecurityType.Equity, Market.USA),
|
||||
Symbol.Create("BAC", SecurityType.Equity, Market.USA)));
|
||||
}
|
||||
|
||||
protected override string GetExpectedModelName(IAlphaModel model)
|
||||
{
|
||||
return $"{nameof(BasePairsTradingAlphaModel)}({_lookback},{_resolution},1)";
|
||||
}
|
||||
|
||||
protected override IAlphaModel CreatePythonAlphaModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = Py.Import("BasePairsTradingAlphaModel").GetAttr("BasePairsTradingAlphaModel");
|
||||
var instance = model(_lookback, _resolution);
|
||||
return new AlphaModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Insight> ExpectedInsights()
|
||||
{
|
||||
Assert.Ignore("The CommonAlphaModelTests need to be refactored to support multiple securities with different prices for each security");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Python;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a framework for testing alpha models.
|
||||
/// </summary>
|
||||
public abstract class CommonAlphaModelTests
|
||||
{
|
||||
protected QCAlgorithm Algorithm { get; set; }
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Initialize()
|
||||
{
|
||||
PythonInitializer.Initialize();
|
||||
|
||||
Algorithm = new QCAlgorithm();
|
||||
Algorithm.PortfolioConstruction = new NullPortfolioConstructionModel();
|
||||
Algorithm.HistoryProvider = new SineHistoryProvider(Algorithm.Securities);
|
||||
Algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(Algorithm));
|
||||
InitializeAlgorithm(Algorithm);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void AddAlphaModel(Language language)
|
||||
{
|
||||
IAlphaModel model;
|
||||
IAlphaModel model2 = null;
|
||||
IAlphaModel model3 = null;
|
||||
if (!TryCreateModel(language, out model)
|
||||
|| !TryCreateModel(language, out model2)
|
||||
|| !TryCreateModel(language, out model3))
|
||||
{
|
||||
Assert.Ignore($"Ignore {GetType().Name}: Could not create {language} model.");
|
||||
}
|
||||
|
||||
// Set the alpha model
|
||||
Algorithm.SetAlpha(model);
|
||||
Algorithm.AddAlpha(model2);
|
||||
Algorithm.AddAlpha(model3);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
Algorithm.OnFrameworkSecuritiesChanged(changes);
|
||||
|
||||
var actualInsights = new List<Insight>();
|
||||
Algorithm.InsightsGenerated += (s, e) => actualInsights.AddRange(e.Insights);
|
||||
|
||||
var expectedInsights = ExpectedInsights().ToList();
|
||||
|
||||
var consolidators = Algorithm.Securities.SelectMany(kvp => kvp.Value.Subscriptions).SelectMany(x => x.Consolidators);
|
||||
var slices = CreateSlices();
|
||||
|
||||
foreach (var slice in slices.ToList())
|
||||
{
|
||||
Algorithm.SetDateTime(slice.Time);
|
||||
|
||||
foreach (var symbol in slice.Keys)
|
||||
{
|
||||
var data = slice[symbol];
|
||||
Algorithm.Securities[symbol].SetMarketPrice(data);
|
||||
|
||||
foreach (var consolidator in consolidators)
|
||||
{
|
||||
consolidator.Update(data);
|
||||
}
|
||||
}
|
||||
|
||||
Algorithm.OnFrameworkData(slice);
|
||||
}
|
||||
|
||||
Assert.AreEqual(expectedInsights.Count * 3, actualInsights.Count);
|
||||
|
||||
for (var i = 0; i < actualInsights.Count; i = i + 3)
|
||||
{
|
||||
var expected = expectedInsights[i / 3];
|
||||
for (int j = i; j < 3; j++)
|
||||
{
|
||||
var actual = actualInsights[j];
|
||||
Assert.AreEqual(expected.Symbol, actual.Symbol);
|
||||
Assert.AreEqual(expected.Type, actual.Type);
|
||||
Assert.AreEqual(expected.Direction, actual.Direction);
|
||||
Assert.LessOrEqual(expected.Period, actual.Period); // It can be canceled and discarded early
|
||||
Assert.AreEqual(expected.Magnitude, actual.Magnitude);
|
||||
Assert.AreEqual(expected.Confidence, actual.Confidence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void InsightsGenerationTest(Language language)
|
||||
{
|
||||
IAlphaModel model;
|
||||
if (!TryCreateModel(language, out model))
|
||||
{
|
||||
Assert.Ignore($"Ignore {GetType().Name}: Could not create {language} model.");
|
||||
}
|
||||
|
||||
// Set the alpha model
|
||||
Algorithm.SetAlpha(model);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
Algorithm.OnFrameworkSecuritiesChanged(changes);
|
||||
|
||||
var actualInsights = new List<Insight>();
|
||||
Algorithm.InsightsGenerated += (s, e) => actualInsights.AddRange(e.Insights);
|
||||
|
||||
var expectedInsights = ExpectedInsights().ToList();
|
||||
|
||||
var consolidators = Algorithm.Securities.SelectMany(kvp => kvp.Value.Subscriptions).SelectMany(x => x.Consolidators);
|
||||
var slices = CreateSlices();
|
||||
|
||||
foreach (var slice in slices.ToList())
|
||||
{
|
||||
Algorithm.SetDateTime(slice.Time);
|
||||
|
||||
foreach (var symbol in slice.Keys)
|
||||
{
|
||||
var data = slice[symbol];
|
||||
Algorithm.Securities[symbol].SetMarketPrice(data);
|
||||
|
||||
foreach (var consolidator in consolidators)
|
||||
{
|
||||
consolidator.Update(data);
|
||||
}
|
||||
}
|
||||
|
||||
Algorithm.OnFrameworkData(slice);
|
||||
}
|
||||
|
||||
Assert.AreEqual(expectedInsights.Count, actualInsights.Count);
|
||||
|
||||
for (var i = 0; i < actualInsights.Count; i++)
|
||||
{
|
||||
var actual = actualInsights[i];
|
||||
var expected = expectedInsights[i];
|
||||
Assert.AreEqual(expected.Symbol, actual.Symbol);
|
||||
Assert.AreEqual(expected.Type, actual.Type);
|
||||
Assert.AreEqual(expected.Direction, actual.Direction);
|
||||
Assert.LessOrEqual(expected.Period, actual.Period); // It can be canceled and discarded early
|
||||
Assert.AreEqual(expected.Magnitude, actual.Magnitude);
|
||||
Assert.AreEqual(expected.Confidence, actual.Confidence);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void AddedSecuritiesTest(Language language)
|
||||
{
|
||||
IAlphaModel model;
|
||||
if (!TryCreateModel(language, out model))
|
||||
{
|
||||
Assert.Ignore($"Ignore {GetType().Name}: Could not create {language} model.");
|
||||
}
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
|
||||
Assert.DoesNotThrow(() => model.OnSecuritiesChanged(Algorithm, changes));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void RemovedSecuritiesTest(Language language)
|
||||
{
|
||||
IAlphaModel model;
|
||||
if (!TryCreateModel(language, out model))
|
||||
{
|
||||
Assert.Ignore($"Ignore {GetType().Name}: Could not create {language} model.");
|
||||
}
|
||||
|
||||
var removedSecurities = Algorithm.Securities.Values;
|
||||
|
||||
// We have to add some security if we then want to remove it, that's why we cannot use here
|
||||
// RemovedSecurities, because it doesn't contain any security
|
||||
var changes = SecurityChangesTests.CreateNonInternal(removedSecurities, AddedSecurities);
|
||||
|
||||
Assert.DoesNotThrow(() => model.OnSecuritiesChanged(Algorithm, changes));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void ModelNameTest(Language language)
|
||||
{
|
||||
IAlphaModel model;
|
||||
if (!TryCreateModel(language, out model))
|
||||
{
|
||||
Assert.Ignore($"Ignore {GetType().Name}: Could not create {language} model.");
|
||||
}
|
||||
|
||||
var actual = model.GetModelName();
|
||||
var expected = GetExpectedModelName(model);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of the alpha model to test
|
||||
/// </summary>
|
||||
protected abstract IAlphaModel CreateCSharpAlphaModel();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance of the alpha model to test
|
||||
/// </summary>
|
||||
protected abstract IAlphaModel CreatePythonAlphaModel();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable with the expected insights
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<Insight> ExpectedInsights();
|
||||
|
||||
/// <summary>
|
||||
/// List of securities to be added to the model
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<Security> AddedSecurities => Algorithm.Securities.Values;
|
||||
|
||||
/// <summary>
|
||||
/// List of securities to be removed to the model
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<Security> RemovedSecurities => Enumerable.Empty<Security>();
|
||||
|
||||
/// <summary>
|
||||
/// To be override for model types that implement <see cref="INamedModel"/>
|
||||
/// </summary>
|
||||
protected abstract string GetExpectedModelName(IAlphaModel model);
|
||||
|
||||
/// <summary>
|
||||
/// Provides derived types a chance to initialize anything special they require
|
||||
/// </summary>
|
||||
protected virtual void InitializeAlgorithm(QCAlgorithm algorithm)
|
||||
{
|
||||
Algorithm.SetStartDate(2018, 1, 4);
|
||||
Algorithm.AddEquity(Symbols.SPY.Value, Resolution.Daily);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an enumerable of Slice to update the alpha model
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<Slice> CreateSlices()
|
||||
{
|
||||
var timeSliceFactory = new TimeSliceFactory(TimeZones.NewYork);
|
||||
var changes = SecurityChanges.None;
|
||||
var sliceDateTimes = GetSliceDateTimes(MaxSliceCount);
|
||||
|
||||
for (var i = 0; i < sliceDateTimes.Count; i++)
|
||||
{
|
||||
var utcDateTime = sliceDateTimes[i];
|
||||
|
||||
var packets = new List<DataFeedPacket>();
|
||||
|
||||
// TODO : Give securities different values -- will require updating all derived types
|
||||
var last = Convert.ToDecimal(100 + 10 * Math.Sin(Math.PI * i / 180.0));
|
||||
var high = last * 1.005m;
|
||||
var low = last / 1.005m;
|
||||
foreach (var kvp in Algorithm.Securities)
|
||||
{
|
||||
var security = kvp.Value;
|
||||
var exchange = security.Exchange.Hours;
|
||||
var configs = Algorithm.SubscriptionManager.SubscriptionDataConfigService
|
||||
.GetSubscriptionDataConfigs(security.Symbol);
|
||||
var extendedMarket = configs.IsExtendedMarketHours();
|
||||
var localDateTime = utcDateTime.ConvertFromUtc(exchange.TimeZone);
|
||||
if (!exchange.IsOpen(localDateTime, extendedMarket))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var configuration = security.Subscriptions.FirstOrDefault();
|
||||
var period = configs.GetHighestResolution().ToTimeSpan();
|
||||
var time = (utcDateTime - period).ConvertFromUtc(configuration.DataTimeZone);
|
||||
var tradeBar = new TradeBar(time, security.Symbol, last, high, low, last, 1000, period);
|
||||
packets.Add(new DataFeedPacket(security, configuration, new List<BaseData> { tradeBar }));
|
||||
}
|
||||
|
||||
if (packets.Count > 0)
|
||||
{
|
||||
yield return timeSliceFactory.Create(utcDateTime, packets, changes, new Dictionary<Universe, BaseDataCollection>()).Slice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set up the HistoryProvider for algorithm
|
||||
/// </summary>
|
||||
protected void SetUpHistoryProvider()
|
||||
{
|
||||
Algorithm.HistoryProvider = new SubscriptionDataReaderHistoryProvider();
|
||||
Algorithm.HistoryProvider.Initialize(new HistoryProviderInitializeParameters(
|
||||
null,
|
||||
null,
|
||||
TestGlobals.DataProvider,
|
||||
TestGlobals.DataCacheProvider,
|
||||
TestGlobals.MapFileProvider,
|
||||
TestGlobals.FactorFileProvider,
|
||||
null,
|
||||
false,
|
||||
new DataPermissionManager(),
|
||||
Algorithm.ObjectStore,
|
||||
Algorithm.Settings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum number of slice objects to generate
|
||||
/// </summary>
|
||||
protected virtual int MaxSliceCount => 360;
|
||||
|
||||
private List<DateTime> GetSliceDateTimes(int maxCount)
|
||||
{
|
||||
var i = 0;
|
||||
var sliceDateTimes = new List<DateTime>();
|
||||
var utcDateTime = Algorithm.StartDate;
|
||||
|
||||
while (sliceDateTimes.Count < maxCount)
|
||||
{
|
||||
foreach (var kvp in Algorithm.Securities)
|
||||
{
|
||||
var security = kvp.Value;
|
||||
var configs = Algorithm.SubscriptionManager.SubscriptionDataConfigService
|
||||
.GetSubscriptionDataConfigs(security.Symbol);
|
||||
var resolution = configs.GetHighestResolution().ToTimeSpan();
|
||||
utcDateTime = utcDateTime.Add(resolution);
|
||||
if (resolution == Time.OneDay && utcDateTime.TimeOfDay == TimeSpan.Zero)
|
||||
{
|
||||
utcDateTime = utcDateTime.AddHours(17);
|
||||
}
|
||||
var exchange = security.Exchange.Hours;
|
||||
var extendedMarket = configs.IsExtendedMarketHours();
|
||||
var localDateTime = utcDateTime.ConvertFromUtc(exchange.TimeZone);
|
||||
if (exchange.IsOpen(localDateTime, extendedMarket))
|
||||
{
|
||||
sliceDateTimes.Add(utcDateTime);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return sliceDateTimes;
|
||||
}
|
||||
|
||||
private bool TryCreateModel(Language language, out IAlphaModel model)
|
||||
{
|
||||
model = default(IAlphaModel);
|
||||
|
||||
switch (language)
|
||||
{
|
||||
case Language.CSharp:
|
||||
model = CreateCSharpAlphaModel();
|
||||
return true;
|
||||
case Language.Python:
|
||||
Algorithm.SetPandasConverter();
|
||||
model = CreatePythonAlphaModel();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using static System.FormattableString;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class ConstantAlphaModelTests : CommonAlphaModelTests
|
||||
{
|
||||
private InsightType _type = InsightType.Price;
|
||||
private InsightDirection _direction = InsightDirection.Up;
|
||||
private TimeSpan _period = Time.OneDay;
|
||||
private double? _magnitude = 0.025;
|
||||
private double? _confidence = null;
|
||||
|
||||
protected override IAlphaModel CreateCSharpAlphaModel() => new ConstantAlphaModel(_type, _direction, _period, _magnitude, _confidence);
|
||||
|
||||
protected override IAlphaModel CreatePythonAlphaModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = Py.Import("ConstantAlphaModel").GetAttr("ConstantAlphaModel");
|
||||
var instance = model(_type, _direction, _period, _magnitude, _confidence);
|
||||
return new AlphaModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void ConstructorWithWeightOnlySetsWeightCorrectly(Language language)
|
||||
{
|
||||
IAlphaModel alpha;
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
alpha = new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromDays(1), weight: 0.1);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("test_module",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def test_constructor():
|
||||
model = ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(1), weight=0.1)
|
||||
return model
|
||||
");
|
||||
|
||||
alpha = testModule.GetAttr("test_constructor").Invoke().As<ConstantAlphaModel>();
|
||||
}
|
||||
}
|
||||
|
||||
var magnitude = GetPrivateField(alpha, "_magnitude");
|
||||
var confidence = GetPrivateField(alpha, "_confidence");
|
||||
var weight = GetPrivateField(alpha, "_weight");
|
||||
|
||||
Assert.IsNull(magnitude);
|
||||
Assert.IsNull(confidence);
|
||||
Assert.AreEqual(0.1, weight);
|
||||
}
|
||||
|
||||
private static object GetPrivateField(object obj, string fieldName)
|
||||
{
|
||||
var field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
return field?.GetValue(obj);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Insight> ExpectedInsights()
|
||||
{
|
||||
return Enumerable.Range(0, 360).Select(x => new Insight(Symbols.SPY, _period, _type, _direction, _magnitude, _confidence));
|
||||
}
|
||||
|
||||
protected override string GetExpectedModelName(IAlphaModel model)
|
||||
{
|
||||
return Invariant($"{nameof(ConstantAlphaModel)}({_type},{_direction},{_period},{_magnitude})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class EmaCrossAlphaModelTests : CommonAlphaModelTests
|
||||
{
|
||||
protected override IAlphaModel CreateCSharpAlphaModel() => new EmaCrossAlphaModel();
|
||||
|
||||
protected override IAlphaModel CreatePythonAlphaModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = Py.Import("EmaCrossAlphaModel").GetAttr("EmaCrossAlphaModel");
|
||||
var instance = model();
|
||||
return new AlphaModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Insight> ExpectedInsights()
|
||||
{
|
||||
var period = TimeSpan.FromDays(12);
|
||||
|
||||
return new[]
|
||||
{
|
||||
Insight.Price(Symbols.SPY, period, InsightDirection.Down),
|
||||
Insight.Price(Symbols.SPY, period, InsightDirection.Up)
|
||||
};
|
||||
}
|
||||
|
||||
protected override string GetExpectedModelName(IAlphaModel model)
|
||||
{
|
||||
return $"{nameof(EmaCrossAlphaModel)}(12,26,Daily)";
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WarmsUpProperly()
|
||||
{
|
||||
SetUpHistoryProvider();
|
||||
|
||||
Algorithm.SetStartDate(2013, 10, 08);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
// Create a EmaCrossAlphaModel for the test
|
||||
var model = new TestEmaCrossAlphaModel();
|
||||
|
||||
// Set the alpha model
|
||||
Algorithm.SetAlpha(model);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
Algorithm.OnFrameworkSecuritiesChanged(changes);
|
||||
|
||||
// Get the dictionary of macd indicators
|
||||
var symbolData = model.GetSymbolData();
|
||||
|
||||
// Check the symbolData dictionary is not empty
|
||||
Assert.NotZero(symbolData.Count);
|
||||
|
||||
// Check all EmaCross indicators from the alpha are ready and have at least
|
||||
// one datapoint
|
||||
foreach (var item in symbolData)
|
||||
{
|
||||
var fast = item.Value.Fast;
|
||||
var slow = item.Value.Slow;
|
||||
|
||||
Assert.IsTrue(fast.IsReady);
|
||||
Assert.NotZero(fast.Samples);
|
||||
|
||||
Assert.IsTrue(slow.IsReady);
|
||||
Assert.NotZero(slow.Samples);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonVersionWarmsUpProperly()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
SetUpHistoryProvider();
|
||||
Algorithm.SetStartDate(2013, 10, 08);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
// Create and set alpha model
|
||||
dynamic model = Py.Import("EmaCrossAlphaModel").GetAttr("EmaCrossAlphaModel");
|
||||
var instance = model();
|
||||
Algorithm.SetAlpha(instance);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
Algorithm.OnFrameworkSecuritiesChanged(changes);
|
||||
|
||||
// Get the dictionary of ema cross indicators
|
||||
var symbolData = instance.symbol_data_by_symbol;
|
||||
|
||||
// Check the dictionary is not empty
|
||||
Assert.NotZero(symbolData.Length());
|
||||
|
||||
// Check all Ema Cross indicators from the alpha are ready and have at least
|
||||
// one datapoint
|
||||
foreach (var item in symbolData)
|
||||
{
|
||||
var fast = symbolData[item].fast;
|
||||
var slow = symbolData[item].slow;
|
||||
|
||||
Assert.IsTrue(fast.IsReady.IsTrue());
|
||||
Assert.NotZero(((PyObject)fast.Samples).GetAndDispose<int>());
|
||||
|
||||
Assert.IsTrue(slow.IsReady.IsTrue());
|
||||
Assert.NotZero(((PyObject)slow.Samples).GetAndDispose<int>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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 System.Collections.Generic;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class InsightCollectionTests
|
||||
{
|
||||
private static readonly DateTime _referenceTime = new DateTime(2019, 1, 1);
|
||||
|
||||
[Test]
|
||||
public void InsightCollectionShouldBeAbleToBeConvertedToListWithoutStackOverflow()
|
||||
{
|
||||
var insightCollection = new InsightCollection
|
||||
{
|
||||
new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up)
|
||||
{
|
||||
CloseTimeUtc = new DateTime(2019, 1, 1),
|
||||
},
|
||||
new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Volatility, InsightDirection.Up)
|
||||
{
|
||||
CloseTimeUtc = new DateTime(2019, 1, 2),
|
||||
}
|
||||
};
|
||||
|
||||
Assert.DoesNotThrow(() => insightCollection.OrderBy(x => x.CloseTimeUtc).ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HasActiveInsights()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
|
||||
Assert.IsFalse(collection.HasActiveInsights(Symbols.AAPL, DateTime.MinValue));
|
||||
|
||||
collection.AddRange(GetTestInsight());
|
||||
|
||||
Assert.IsFalse(collection.HasActiveInsights(Symbols.AAPL, DateTime.MaxValue));
|
||||
|
||||
Assert.IsTrue(collection.HasActiveInsights(Symbols.AAPL, _referenceTime));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetNextExpiryTime()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
|
||||
Assert.AreEqual(null, collection.GetNextExpiryTime());
|
||||
|
||||
collection.AddRange(GetTestInsight());
|
||||
|
||||
Assert.AreEqual(_referenceTime, collection.GetNextExpiryTime());
|
||||
|
||||
var nextDay = _referenceTime.AddDays(1);
|
||||
Assert.AreEqual(1, collection.RemoveExpiredInsights(nextDay).Count);
|
||||
Assert.AreEqual(nextDay, collection.GetNextExpiryTime());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetValue()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
|
||||
Assert.IsFalse(collection.TryGetValue(Symbols.AAPL, out var _));
|
||||
|
||||
collection.AddRange(GetTestInsight());
|
||||
Assert.IsTrue(collection.TryGetValue(Symbols.AAPL, out var insights));
|
||||
|
||||
Assert.AreEqual(2, insights.Count);
|
||||
Assert.AreEqual(2, insights.Count(insight => insight.Symbol == Symbols.AAPL));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void KeyNotFoundException()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
Assert.Throws<KeyNotFoundException>(() =>
|
||||
{
|
||||
var insight = collection[Symbols.AAPL];
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Contains()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
var insights = GetTestInsight();
|
||||
collection.AddRange(insights);
|
||||
|
||||
foreach (var insight in insights)
|
||||
{
|
||||
Assert.IsTrue(collection.Contains(insight));
|
||||
Assert.IsTrue(collection.ContainsKey(insight.Symbol));
|
||||
}
|
||||
Assert.IsFalse(collection.ContainsKey(Symbols.BTCEUR));
|
||||
|
||||
var anotherInsight = new Insight(Symbols.BTCEUR, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up);
|
||||
Assert.IsFalse(collection.Contains(anotherInsight));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Addition()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
var insight = new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = _referenceTime };
|
||||
collection.Add(insight);
|
||||
collection.Add(new Insight(Symbols.SPY, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = _referenceTime });
|
||||
collection.Add(new Insight(Symbols.IBM, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Down) { CloseTimeUtc = _referenceTime.AddDays(-1) });
|
||||
|
||||
var beforeExpiration = insight.CloseTimeUtc.AddDays(-1);
|
||||
|
||||
Assert.AreEqual(3, collection.Count);
|
||||
Assert.IsTrue(collection.TryGetValue(Symbols.AAPL, out var insightInCollection));
|
||||
Assert.IsTrue(collection.HasActiveInsights(Symbols.AAPL, beforeExpiration));
|
||||
Assert.AreEqual(insight, insightInCollection.Single());
|
||||
Assert.AreEqual(insight, collection[Symbols.AAPL].Single());
|
||||
Assert.AreEqual(3, collection.Count);
|
||||
Assert.AreEqual(3, collection.GetActiveInsights(beforeExpiration).Count);
|
||||
Assert.AreEqual(3, collection.GetInsights().Count);
|
||||
Assert.AreEqual(insight, collection.GetInsights(x => insight == x).Single());
|
||||
Assert.AreEqual(0, collection.GetActiveInsights(_referenceTime.AddYears(1)).Count);
|
||||
Assert.AreEqual(3, collection.TotalCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetInsights()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
var insights = GetTestInsight();
|
||||
collection.AddRange(insights);
|
||||
|
||||
Assert.AreEqual(5, collection.Count);
|
||||
|
||||
collection.RemoveInsights(x => x == insights[0]);
|
||||
Assert.AreEqual(4, collection.GetInsights().Count);
|
||||
Assert.AreEqual(4, collection.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Removal()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
var insights = GetTestInsight();
|
||||
collection.AddRange(insights);
|
||||
|
||||
var insightCount = collection.Count;
|
||||
foreach (var insight in insights)
|
||||
{
|
||||
Assert.IsTrue(collection.Remove(insight));
|
||||
Assert.AreEqual(--insightCount, collection.Count);
|
||||
}
|
||||
|
||||
// readd the first insight
|
||||
var firstInsight = insights[0];
|
||||
collection.Add(firstInsight);
|
||||
Assert.AreEqual(1, collection.Count);
|
||||
|
||||
// we only remove 'firstInsight' from the global collection
|
||||
collection.RemoveInsights(x => x == firstInsight);
|
||||
Assert.AreEqual(4, collection.GetInsights().Count);
|
||||
Assert.AreEqual(0, collection.Count);
|
||||
Assert.AreEqual(6, collection.TotalCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExpiredRemoval()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
var insights = GetTestInsight();
|
||||
collection.AddRange(insights);
|
||||
|
||||
Assert.AreEqual(5, collection.Count);
|
||||
Assert.AreEqual(0, collection.RemoveExpiredInsights(_referenceTime.AddDays(-1)).Count);
|
||||
|
||||
// expire 1 insight
|
||||
Assert.AreEqual(insights[0], collection.RemoveExpiredInsights(_referenceTime.AddDays(1)).Single());
|
||||
|
||||
// expire 2 insights
|
||||
Assert.AreEqual(2, collection.RemoveExpiredInsights(_referenceTime.AddDays(2)).Count);
|
||||
Assert.AreEqual(2, collection.Count);
|
||||
Assert.AreEqual(5, collection.TotalCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndexAccess()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
collection.AddRange(GetTestInsight());
|
||||
|
||||
collection[Symbols.AAPL] = null;
|
||||
Assert.AreEqual(3, collection.Count);
|
||||
|
||||
var insight = new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = new DateTime(2019, 1, 1) };
|
||||
var insight2 = new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = new DateTime(2019, 1, 2) };
|
||||
collection[Symbols.AAPL] = new() { insight, insight2 };
|
||||
Assert.AreEqual(5, collection.Count);
|
||||
|
||||
collection[Symbols.AAPL] = null;
|
||||
Assert.AreEqual(3, collection.Count);
|
||||
|
||||
Assert.AreEqual(7, collection.TotalCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddRange()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
var insights = GetTestInsight();
|
||||
collection.AddRange(insights);
|
||||
|
||||
Assert.AreEqual(5, collection.Count);
|
||||
|
||||
foreach (var insight in insights)
|
||||
{
|
||||
Assert.IsTrue(collection.Contains(insight));
|
||||
Assert.IsTrue(collection.ContainsKey(insight.Symbol));
|
||||
}
|
||||
Assert.AreEqual(5, collection.TotalCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearSymbols()
|
||||
{
|
||||
var collection = new InsightCollection();
|
||||
collection.AddRange(GetTestInsight());
|
||||
|
||||
collection.Clear(Array.Empty<Symbol>());
|
||||
Assert.AreEqual(5, collection.Count);
|
||||
|
||||
collection.Clear(new[] { Symbols.AAPL });
|
||||
Assert.AreEqual(3, collection.Count);
|
||||
Assert.IsTrue(collection.ContainsKey(Symbols.SPY));
|
||||
Assert.IsTrue(collection.ContainsKey(Symbols.IBM));
|
||||
Assert.IsFalse(collection.ContainsKey(Symbols.AAPL));
|
||||
Assert.AreEqual(5, collection.TotalCount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static List<Insight> GetTestInsight()
|
||||
{
|
||||
var insight = new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = _referenceTime };
|
||||
var insight2 = new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = _referenceTime.AddDays(1) };
|
||||
var insight3 = new Insight(Symbols.SPY, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up) { CloseTimeUtc = _referenceTime.AddDays(1) };
|
||||
|
||||
var insight4 = new Insight(Symbols.SPY, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Down) { CloseTimeUtc = _referenceTime.AddMonths(1) };
|
||||
var insight5 = new Insight(Symbols.IBM, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Down) { CloseTimeUtc = _referenceTime.AddMonths(1) };
|
||||
|
||||
return new List<Insight> { insight, insight2, insight3, insight4, insight5 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Alphas.Analysis;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class InsightManagerTests
|
||||
{
|
||||
private static readonly DateTime _utcNow = new (2019, 1, 1);
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void ExpireSameTime(bool useCancelApi)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetDateTime(_utcNow);
|
||||
var insightManager = new InsightManager(algorithm);
|
||||
insightManager.AddRange(GetInsights());
|
||||
|
||||
Assert.IsTrue(insightManager.All(insight => insight.IsActive(_utcNow)));
|
||||
|
||||
if (useCancelApi)
|
||||
{
|
||||
insightManager.Cancel(new[] { Symbols.IBM, Symbols.SPY });
|
||||
}
|
||||
else
|
||||
{
|
||||
insightManager.Expire(new[] { Symbols.IBM, Symbols.SPY });
|
||||
}
|
||||
|
||||
Assert.IsTrue(insightManager[Symbols.IBM].All(insight => insight.IsExpired(algorithm.UtcTime) && insight.Period == TimeSpan.FromSeconds(-1)));
|
||||
Assert.IsTrue(insightManager[Symbols.SPY].All(insight => insight.IsExpired(algorithm.UtcTime) && insight.Period == TimeSpan.FromSeconds(-1)));
|
||||
Assert.IsTrue(insightManager[Symbols.AAPL].All(insight => insight.IsActive(algorithm.UtcTime)));
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void ExpireBySymbol(bool useCancelApi)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetDateTime(_utcNow);
|
||||
var insightManager = new InsightManager(algorithm);
|
||||
insightManager.AddRange(GetInsights());
|
||||
|
||||
Assert.IsTrue(insightManager.All(insight => insight.IsActive(_utcNow)));
|
||||
|
||||
algorithm.SetDateTime(algorithm.UtcTime.AddMinutes(1));
|
||||
if (useCancelApi)
|
||||
{
|
||||
insightManager.Cancel(new[] { Symbols.IBM, Symbols.SPY });
|
||||
}
|
||||
else
|
||||
{
|
||||
insightManager.Expire(new[] { Symbols.IBM, Symbols.SPY });
|
||||
}
|
||||
|
||||
var expectedPeriod = Time.OneMinute.Subtract(Time.OneSecond);
|
||||
Assert.IsTrue(insightManager[Symbols.IBM].All(insight => insight.IsExpired(algorithm.UtcTime) && insight.Period == expectedPeriod));
|
||||
Assert.IsTrue(insightManager[Symbols.SPY].All(insight => insight.IsExpired(algorithm.UtcTime) && insight.Period == expectedPeriod));
|
||||
Assert.IsTrue(insightManager[Symbols.AAPL].All(insight => insight.IsActive(algorithm.UtcTime)));
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void ExpireByInsight(bool useCancelApi)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetDateTime(_utcNow);
|
||||
var insights = GetInsights();
|
||||
var insightManager = new InsightManager(algorithm);
|
||||
insightManager.AddRange(insights);
|
||||
|
||||
Assert.IsTrue(insightManager.All(insight => insight.IsActive(_utcNow)));
|
||||
|
||||
algorithm.SetDateTime(algorithm.UtcTime.AddMinutes(1));
|
||||
if (useCancelApi)
|
||||
{
|
||||
insightManager.Cancel(new[] { insights[2], insights[3] });
|
||||
}
|
||||
else
|
||||
{
|
||||
insightManager.Expire(new[] { insights[2], insights[3] });
|
||||
}
|
||||
|
||||
var expectedPeriod = Time.OneMinute.Subtract(Time.OneSecond);
|
||||
Assert.IsTrue(insightManager[Symbols.IBM].All(insight => insight.IsExpired(algorithm.UtcTime) && insight.Period == expectedPeriod));
|
||||
Assert.AreEqual(1, insightManager[Symbols.SPY].Count(insight => insight.IsExpired(algorithm.UtcTime) && insight.Period == expectedPeriod));
|
||||
Assert.AreEqual(1, insightManager[Symbols.SPY].Count(insight => insight.IsActive(algorithm.UtcTime)));
|
||||
Assert.IsTrue(insightManager[Symbols.AAPL].All(insight => insight.IsActive(algorithm.UtcTime)));
|
||||
}
|
||||
|
||||
private static Insight[] GetInsights()
|
||||
{
|
||||
return new[] {
|
||||
new Insight(Symbols.AAPL, new TimeSpan(1, 0, 0, 0), InsightType.Price, InsightDirection.Up)
|
||||
{
|
||||
GeneratedTimeUtc = _utcNow,
|
||||
CloseTimeUtc = _utcNow.AddDays(1),
|
||||
},
|
||||
new Insight(Symbols.SPY, new TimeSpan(2, 0, 0, 0), InsightType.Volatility, InsightDirection.Up)
|
||||
{
|
||||
GeneratedTimeUtc = _utcNow,
|
||||
CloseTimeUtc = _utcNow.AddDays(2),
|
||||
},
|
||||
new Insight(Symbols.SPY, new TimeSpan(3, 0, 0, 0), InsightType.Volatility, InsightDirection.Up)
|
||||
{
|
||||
GeneratedTimeUtc = _utcNow,
|
||||
CloseTimeUtc = _utcNow.AddDays(3),
|
||||
},
|
||||
new Insight(Symbols.IBM, new TimeSpan(4, 0, 0, 0), InsightType.Volatility, InsightDirection.Up)
|
||||
{
|
||||
GeneratedTimeUtc = _utcNow,
|
||||
CloseTimeUtc = _utcNow.AddDays(4),
|
||||
} };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class MacdAlphaModelTests : CommonAlphaModelTests
|
||||
{
|
||||
protected override IAlphaModel CreateCSharpAlphaModel() => new MacdAlphaModel();
|
||||
|
||||
protected override IAlphaModel CreatePythonAlphaModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = Py.Import("MacdAlphaModel").GetAttr("MacdAlphaModel");
|
||||
var instance = model();
|
||||
return new AlphaModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Insight> ExpectedInsights()
|
||||
{
|
||||
var period = TimeSpan.FromDays(12);
|
||||
return new[]
|
||||
{
|
||||
Insight.Price(Symbols.SPY, period, InsightDirection.Up),
|
||||
Insight.Price(Symbols.SPY, period, InsightDirection.Down),
|
||||
Insight.Price(Symbols.SPY, period, InsightDirection.Up)
|
||||
};
|
||||
}
|
||||
|
||||
protected override string GetExpectedModelName(IAlphaModel model)
|
||||
{
|
||||
return $"{nameof(MacdAlphaModel)}(12,26,9,Exponential,Daily)";
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MacdAlphaModelWarmsUpProperly()
|
||||
{
|
||||
SetUpHistoryProvider();
|
||||
Algorithm.SetStartDate(2013, 10, 08);
|
||||
|
||||
// Create a MacdAlphaModel for the test
|
||||
var model = new TestMacdAlphaModel();
|
||||
|
||||
// Set the alpha model
|
||||
Algorithm.SetAlpha(model);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
Algorithm.OnFrameworkSecuritiesChanged(changes);
|
||||
|
||||
// Get the dictionary of macd indicators
|
||||
var symbolData = model.GetSymbolData();
|
||||
|
||||
// Check the symbolData dictionary is not empty
|
||||
Assert.NotZero(symbolData.Count);
|
||||
|
||||
// Check all MACD indicators from the alpha are ready and have at least
|
||||
// one datapoint
|
||||
foreach (var item in symbolData)
|
||||
{
|
||||
var macd = item.Value.MACD;
|
||||
|
||||
Assert.IsTrue(macd.IsReady);
|
||||
Assert.NotZero(macd.Samples);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonMacdAlphaModelWarmsUpProperly()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
SetUpHistoryProvider();
|
||||
Algorithm.SetStartDate(2013, 10, 08);
|
||||
Algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
// Create and set alpha model
|
||||
dynamic model = Py.Import("MacdAlphaModel").GetAttr("MacdAlphaModel");
|
||||
var instance = model();
|
||||
Algorithm.SetAlpha(instance);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(AddedSecurities, RemovedSecurities);
|
||||
Algorithm.OnFrameworkSecuritiesChanged(changes);
|
||||
|
||||
// Get the dictionary of macd indicators
|
||||
var symbolData = instance.symbolData;
|
||||
|
||||
// Check the dictionary is not empty
|
||||
Assert.NotZero(symbolData.Length());
|
||||
|
||||
// Check all MACD indicators from the alpha are ready and have at least
|
||||
// one datapoint
|
||||
foreach (var item in symbolData)
|
||||
{
|
||||
var macd = symbolData[item].MACD;
|
||||
|
||||
Assert.IsTrue(macd.IsReady.IsTrue());
|
||||
Assert.NotZero(((PyObject)macd.Samples).GetAndDispose<int>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
[TestFixture]
|
||||
public class RsiAlphaModelTests : CommonAlphaModelTests
|
||||
{
|
||||
protected override IAlphaModel CreateCSharpAlphaModel() => new RsiAlphaModel();
|
||||
|
||||
protected override IAlphaModel CreatePythonAlphaModel()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = Py.Import("RsiAlphaModel").GetAttr("RsiAlphaModel");
|
||||
var instance = model();
|
||||
return new AlphaModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Insight> ExpectedInsights()
|
||||
{
|
||||
var period = TimeSpan.FromDays(14);
|
||||
foreach (var direction in new[] { InsightDirection.Up, InsightDirection.Down })
|
||||
{
|
||||
yield return Insight.Price(Symbols.SPY, period, direction);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetExpectedModelName(IAlphaModel model)
|
||||
{
|
||||
return $"{nameof(RsiAlphaModel)}(14,Daily)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* 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 Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Alphas.Serialization;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas.Serialization
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class InsightJsonConverterTests
|
||||
{
|
||||
private JsonSerializerSettings _serializerSettings = new()
|
||||
{
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new CamelCaseNamingStrategy
|
||||
{
|
||||
ProcessDictionaryKeys = false,
|
||||
OverrideSpecifiedNames = true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void DeserializesInsightWithoutScore()
|
||||
{
|
||||
var jObject = JObject.Parse(jsonNoScoreBackwardsCompatible);
|
||||
var result = JsonConvert.DeserializeObject<Insight>(jsonNoScoreBackwardsCompatible);
|
||||
Assert.AreEqual(jObject["id"].Value<string>(), result.Id.ToStringInvariant("N"));
|
||||
Assert.AreEqual(jObject["source-model"].Value<string>(), result.SourceModel);
|
||||
Assert.AreEqual(jObject["group-id"]?.Value<string>(), result.GroupId?.ToStringInvariant("N"));
|
||||
Assert.AreEqual(jObject["created-time"].Value<double>(), Time.DateTimeToUnixTimeStamp(result.GeneratedTimeUtc), 5e-4);
|
||||
Assert.AreEqual(jObject["close-time"].Value<double>(), Time.DateTimeToUnixTimeStamp(result.CloseTimeUtc), 5e-4);
|
||||
Assert.AreEqual(jObject["symbol"].Value<string>(), result.Symbol.ID.ToString());
|
||||
Assert.AreEqual(jObject["ticker"].Value<string>(), result.Symbol.Value);
|
||||
Assert.AreEqual(jObject["type"].Value<string>(), result.Type.ToLower());
|
||||
Assert.AreEqual(jObject["reference"].Value<decimal>(), result.ReferenceValue);
|
||||
Assert.AreEqual(jObject["direction"].Value<string>(), result.Direction.ToLower());
|
||||
Assert.AreEqual(jObject["period"].Value<double>(), result.Period.TotalSeconds);
|
||||
Assert.AreEqual(jObject["magnitude"].Value<double>(), result.Magnitude);
|
||||
Assert.AreEqual(null, result.Confidence);
|
||||
|
||||
// default values for scores
|
||||
Assert.AreEqual(false, result.Score.IsFinalScore);
|
||||
Assert.AreEqual(0, result.ReferenceValueFinal);
|
||||
Assert.AreEqual(0, result.Score.Magnitude);
|
||||
Assert.AreEqual(0, result.Score.Direction);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializesInsightWithScore()
|
||||
{
|
||||
var jObject = JObject.Parse(jsonWithScoreBackwardsCompatible);
|
||||
var result = JsonConvert.DeserializeObject<Insight>(jsonWithScoreBackwardsCompatible);
|
||||
Assert.AreEqual(jObject["id"].Value<string>(), result.Id.ToStringInvariant("N"));
|
||||
Assert.AreEqual(jObject["source-model"].Value<string>(), result.SourceModel);
|
||||
Assert.AreEqual(jObject["group-id"]?.Value<string>(), result.GroupId?.ToStringInvariant("N"));
|
||||
Assert.AreEqual(jObject["created-time"].Value<double>(), Time.DateTimeToUnixTimeStamp(result.GeneratedTimeUtc), 5e-4);
|
||||
Assert.AreEqual(jObject["close-time"].Value<double>(), Time.DateTimeToUnixTimeStamp(result.CloseTimeUtc), 5e-4);
|
||||
Assert.AreEqual(jObject["symbol"].Value<string>(), result.Symbol.ID.ToString());
|
||||
Assert.AreEqual(jObject["ticker"].Value<string>(), result.Symbol.Value);
|
||||
Assert.AreEqual(jObject["type"].Value<string>(), result.Type.ToLower());
|
||||
Assert.AreEqual(jObject["reference"].Value<decimal>(), result.ReferenceValue);
|
||||
Assert.AreEqual(jObject["direction"].Value<string>(), result.Direction.ToLower());
|
||||
Assert.AreEqual(jObject["period"].Value<double>(), result.Period.TotalSeconds);
|
||||
Assert.AreEqual(jObject["magnitude"].Value<double>(), result.Magnitude);
|
||||
Assert.AreEqual(null, result.Confidence);
|
||||
Assert.AreEqual(true, result.Score.IsFinalScore);
|
||||
Assert.AreEqual(jObject["score-magnitude"].Value<double>(), result.Score.Magnitude);
|
||||
Assert.AreEqual(jObject["score-direction"].Value<double>(), result.Score.Direction);
|
||||
Assert.AreEqual(jObject["reference-final"].Value<decimal>(), result.ReferenceValueFinal);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SerializesInsightWithoutScore(bool backwardsCompatible)
|
||||
{
|
||||
var serializedInsight = JsonConvert.DeserializeObject<SerializedInsight>(backwardsCompatible ? jsonNoScoreBackwardsCompatible : jsonNoScore2);
|
||||
var insight = Insight.FromSerializedInsight(serializedInsight);
|
||||
var result = JsonConvert.SerializeObject(insight, Formatting.None, _serializerSettings);
|
||||
Assert.AreEqual(jsonNoScore2, result);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SerializesInsightWithScore(bool backwardsCompatible)
|
||||
{
|
||||
var serializedInsight = JsonConvert.DeserializeObject<SerializedInsight>(backwardsCompatible ? jsonWithScoreBackwardsCompatible : jsonWithScore);
|
||||
var insight = Insight.FromSerializedInsight(serializedInsight);
|
||||
var result = JsonConvert.SerializeObject(insight, Formatting.None, _serializerSettings);
|
||||
Assert.AreEqual(jsonWithScore, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SerializesOldInsightWithMissingCreatedTime()
|
||||
{
|
||||
var serializedInsight = JsonConvert.DeserializeObject<SerializedInsight>(jsonWithMissingCreatedTimeBackwardsCompatible);
|
||||
var insight = Insight.FromSerializedInsight(serializedInsight);
|
||||
var result = JsonConvert.SerializeObject(insight, Formatting.None, _serializerSettings);
|
||||
|
||||
Assert.AreEqual(serializedInsight.CreatedTime, serializedInsight.GeneratedTime);
|
||||
Assert.AreEqual(jsonWithExpectedOutputFromMissingCreatedTimeValue, result);
|
||||
}
|
||||
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SerializesInsightWithTag(bool backwardsCompatible)
|
||||
{
|
||||
var serializedInsight = JsonConvert.DeserializeObject<SerializedInsight>(backwardsCompatible ? jsonWithTagBackwardsCompatible : jsonWithTag);
|
||||
var insight = Insight.FromSerializedInsight(serializedInsight);
|
||||
var result = JsonConvert.SerializeObject(insight, Formatting.None, _serializerSettings);
|
||||
Assert.AreEqual(jsonWithTag, result);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SerializesInsightWithoutTag(bool backwardsCompatible)
|
||||
{
|
||||
var serializedInsight = JsonConvert.DeserializeObject<SerializedInsight>(backwardsCompatible ? jsonWithoutTagBackwardsCompatible : jsonWithoutTag);
|
||||
var insight = Insight.FromSerializedInsight(serializedInsight);
|
||||
var result = JsonConvert.SerializeObject(insight, Formatting.None, _serializerSettings);
|
||||
Assert.AreEqual(jsonWithoutTag, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializesInsightWithTag()
|
||||
{
|
||||
var jObject = JObject.Parse(jsonWithTagBackwardsCompatible);
|
||||
var result = JsonConvert.DeserializeObject<Insight>(jsonWithTagBackwardsCompatible);
|
||||
Assert.AreEqual(jObject["tag"].Value<string>(), result.Tag);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializesInsightWithoutTag()
|
||||
{
|
||||
var result = JsonConvert.DeserializeObject<Insight>(jsonWithoutTagBackwardsCompatible);
|
||||
Assert.IsNull(result.Tag);
|
||||
}
|
||||
|
||||
private string jsonNoScore2 = @"{""id"":""e02be50f56a8496b9ba995d19a904ada"",""groupId"":null,""sourceModel"":""mySourceModel-1"",""generatedTime"":1520711961.00055,
|
||||
""createdTime"":1520711961.00055,""closeTime"":1520711961.00055,""symbol"":""BTCUSD XJ"",""ticker"":""BTCUSD"",""type"":""price"",""reference"":9143.53,""referenceValueFinal"":0.0,
|
||||
""direction"":""up"",""period"":5.0,""magnitude"":""0.025"",""confidence"":null,""weight"":null,""scoreIsFinal"":false,""scoreMagnitude"":""0"",""scoreDirection"":""0"",
|
||||
""estimatedValue"":""0"",""tag"":null}".ReplaceLineEndings(string.Empty);
|
||||
|
||||
private const string jsonNoScoreBackwardsCompatible =
|
||||
"{" +
|
||||
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"group-id\":null," +
|
||||
"\"source-model\":\"mySourceModel-1\"," +
|
||||
"\"generated-time\":1520711961.00055," +
|
||||
"\"created-time\":1520711961.00055," +
|
||||
"\"close-time\":1520711961.00055," +
|
||||
"\"symbol\":\"BTCUSD XJ\"," +
|
||||
"\"ticker\":\"BTCUSD\"," +
|
||||
"\"type\":\"price\"," +
|
||||
"\"reference\":9143.53," +
|
||||
"\"reference-final\":0.0," +
|
||||
"\"direction\":\"up\"," +
|
||||
"\"period\":5.0," +
|
||||
"\"magnitude\":\"0.025\"," +
|
||||
"\"confidence\":null," +
|
||||
"\"weight\":null," +
|
||||
"\"score-final\":false," +
|
||||
"\"score-magnitude\":\"0\"," +
|
||||
"\"score-direction\":\"0\"," +
|
||||
"\"estimated-value\":\"0\"," +
|
||||
"\"tag\":null}";
|
||||
|
||||
private string jsonWithScore = @"{""id"":""e02be50f56a8496b9ba995d19a904ada"",""groupId"":""a02be50f56a8496b9ba995d19a904ada"",""sourceModel"":""mySourceModel-1"",
|
||||
""generatedTime"":1520711961.00055,""createdTime"":1520711961.00055,""closeTime"":1520711961.00055,""symbol"":""BTCUSD XJ"",""ticker"":""BTCUSD"",""type"":""price"",
|
||||
""reference"":9143.53,""referenceValueFinal"":9243.53,""direction"":""up"",""period"":5.0,""magnitude"":""0.025"",""confidence"":null,""weight"":null,
|
||||
""scoreIsFinal"":true,""scoreMagnitude"":""1"",""scoreDirection"":""1"",""estimatedValue"":""1113.2484"",""tag"":null}".ReplaceLineEndings(string.Empty);
|
||||
private const string jsonWithScoreBackwardsCompatible =
|
||||
"{" +
|
||||
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"source-model\":\"mySourceModel-1\"," +
|
||||
"\"generated-time\":1520711961.00055," +
|
||||
"\"created-time\":1520711961.00055," +
|
||||
"\"close-time\":1520711961.00055," +
|
||||
"\"symbol\":\"BTCUSD XJ\"," +
|
||||
"\"ticker\":\"BTCUSD\"," +
|
||||
"\"type\":\"price\"," +
|
||||
"\"reference\":9143.53," +
|
||||
"\"reference-final\":9243.53," +
|
||||
"\"direction\":\"up\"," +
|
||||
"\"period\":5.0," +
|
||||
"\"magnitude\":\"0.025\"," +
|
||||
"\"confidence\":null," +
|
||||
"\"weight\":null," +
|
||||
"\"score-final\":true," +
|
||||
"\"score-magnitude\":\"1\"," +
|
||||
"\"score-direction\":\"1\"," +
|
||||
"\"estimated-value\":\"1113.2484\"," +
|
||||
"\"tag\":null}";
|
||||
|
||||
private const string jsonWithMissingCreatedTimeBackwardsCompatible =
|
||||
"{" +
|
||||
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"source-model\":\"mySourceModel-1\"," +
|
||||
"\"generated-time\":1520711961.00055," +
|
||||
"\"close-time\":1520711961.00055," +
|
||||
"\"symbol\":\"BTCUSD XJ\"," +
|
||||
"\"ticker\":\"BTCUSD\"," +
|
||||
"\"type\":\"price\"," +
|
||||
"\"reference\":9143.53," +
|
||||
"\"reference-final\":9243.53," +
|
||||
"\"direction\":\"up\"," +
|
||||
"\"period\":5.0," +
|
||||
"\"magnitude\":0.025," +
|
||||
"\"confidence\":null," +
|
||||
"\"weight\":null," +
|
||||
"\"score-final\":true," +
|
||||
"\"score-magnitude\":\"1\"," +
|
||||
"\"score-direction\":\"1\"," +
|
||||
"\"estimated-value\":\"1113.2484\"," +
|
||||
"\"tag\":null}";
|
||||
|
||||
private string jsonWithExpectedOutputFromMissingCreatedTimeValue = @"{""id"":""e02be50f56a8496b9ba995d19a904ada"",""groupId"":""a02be50f56a8496b9ba995d19a904ada"",
|
||||
""sourceModel"":""mySourceModel-1"",""generatedTime"":1520711961.00055,""createdTime"":1520711961.00055,""closeTime"":1520711961.00055,""symbol"":""BTCUSD XJ"",""ticker"":
|
||||
""BTCUSD"",""type"":""price"",""reference"":9143.53,""referenceValueFinal"":9243.53,""direction"":""up"",""period"":5.0,""magnitude"":""0.025"",""confidence"":null,
|
||||
""weight"":null,""scoreIsFinal"":true,""scoreMagnitude"":""1"",""scoreDirection"":""1"",""estimatedValue"":""1113.2484"",""tag"":null}".ReplaceLineEndings(string.Empty);
|
||||
|
||||
private string jsonWithTag = @"{""id"":""e02be50f56a8496b9ba995d19a904ada"",""groupId"":""a02be50f56a8496b9ba995d19a904ada"",""sourceModel"":""mySourceModel-1"",
|
||||
""generatedTime"":1520711961.00055,""createdTime"":1520711961.00055,""closeTime"":1520711961.00055,""symbol"":""BTCUSD XJ"",""ticker"":""BTCUSD"",""type"":
|
||||
""price"",""reference"":9143.53,""referenceValueFinal"":9243.53,""direction"":""up"",""period"":5.0,""magnitude"":null,""confidence"":null,""weight"":null,
|
||||
""scoreIsFinal"":true,""scoreMagnitude"":""1"",""scoreDirection"":""1"",""estimatedValue"":""1113.2484"",""tag"":""additional information""}".ReplaceLineEndings(string.Empty);
|
||||
private const string jsonWithTagBackwardsCompatible =
|
||||
"{" +
|
||||
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"source-model\":\"mySourceModel-1\"," +
|
||||
"\"generated-time\":1520711961.00055," +
|
||||
"\"created-time\":1520711961.00055," +
|
||||
"\"close-time\":1520711961.00055," +
|
||||
"\"symbol\":\"BTCUSD XJ\"," +
|
||||
"\"ticker\":\"BTCUSD\"," +
|
||||
"\"type\":\"price\"," +
|
||||
"\"reference\":9143.53," +
|
||||
"\"reference-final\":9243.53," +
|
||||
"\"direction\":\"up\"," +
|
||||
"\"period\":5.0," +
|
||||
"\"magnitude\":null," +
|
||||
"\"confidence\":null," +
|
||||
"\"weight\":null," +
|
||||
"\"score-final\":true," +
|
||||
"\"score-magnitude\":\"1\"," +
|
||||
"\"score-direction\":\"1\"," +
|
||||
"\"estimated-value\":\"1113.2484\"," +
|
||||
"\"tag\":\"additional information\"}";
|
||||
|
||||
private string jsonWithoutTag = @"{""id"":""e02be50f56a8496b9ba995d19a904ada"",""groupId"":""a02be50f56a8496b9ba995d19a904ada"",
|
||||
""sourceModel"":""mySourceModel-1"",""generatedTime"":1520711961.00055,""createdTime"":1520711961.00055,""closeTime"":1520711961.00055,""symbol"":""BTCUSD XJ"",
|
||||
""ticker"":""BTCUSD"",""type"":""price"",""reference"":9143.53,""referenceValueFinal"":9243.53,""direction"":""up"",""period"":5.0,""magnitude"":null,
|
||||
""confidence"":null,""weight"":null,""scoreIsFinal"":true,""scoreMagnitude"":""1"",""scoreDirection"":""1"",""estimatedValue"":""1113.2484"",""tag"":null}".ReplaceLineEndings(string.Empty);
|
||||
|
||||
private const string jsonWithoutTagBackwardsCompatible =
|
||||
"{" +
|
||||
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
|
||||
"\"source-model\":\"mySourceModel-1\"," +
|
||||
"\"generated-time\":1520711961.00055," +
|
||||
"\"created-time\":1520711961.00055," +
|
||||
"\"close-time\":1520711961.00055," +
|
||||
"\"symbol\":\"BTCUSD XJ\"," +
|
||||
"\"ticker\":\"BTCUSD\"," +
|
||||
"\"type\":\"price\"," +
|
||||
"\"reference\":9143.53," +
|
||||
"\"reference-final\":9243.53," +
|
||||
"\"direction\":\"up\"," +
|
||||
"\"period\":5.0," +
|
||||
"\"magnitude\":null," +
|
||||
"\"confidence\":null," +
|
||||
"\"weight\":null," +
|
||||
"\"score-final\":true," +
|
||||
"\"score-magnitude\":\"1\"," +
|
||||
"\"score-direction\":\"1\"," +
|
||||
"\"estimated-value\":\"1113.2484\"," +
|
||||
"\"tag\":null}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 QuantConnect.Algorithm.Framework.Alphas;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
class TestEmaCrossAlphaModel : EmaCrossAlphaModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the _symbolDataBySymbol dictionary from EmaCrossAlphaModel
|
||||
/// </summary>
|
||||
/// <returns>_symbolDataBySymbol dictionary from EmaCrossAlphaModel</returns>
|
||||
public Dictionary<Symbol, SymbolData> GetSymbolData()
|
||||
{
|
||||
return SymbolDataBySymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 QuantConnect.Algorithm.Framework.Alphas;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Alphas
|
||||
{
|
||||
class TestMacdAlphaModel: MacdAlphaModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the _symbolData dictionary from MacdAlphaModel
|
||||
/// </summary>
|
||||
/// <returns>_symbolData dictionary from MacdAlphaModel</returns>
|
||||
public Dictionary<Symbol, SymbolData> GetSymbolData()
|
||||
{
|
||||
return _symbolData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.Results;
|
||||
using QuantConnect.Lean.Engine.TransactionHandlers;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Python;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Execution
|
||||
{
|
||||
[TestFixture]
|
||||
public class ImmediateExecutionModelTests
|
||||
{
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void OrdersAreNotSubmittedWhenNoTargetsToExecute(Language language)
|
||||
{
|
||||
var actualOrdersSubmitted = new List<SubmitOrderRequest>();
|
||||
|
||||
var orderProcessor = new Mock<IOrderProcessor>();
|
||||
orderProcessor.Setup(m => m.Process(It.IsAny<SubmitOrderRequest>()))
|
||||
.Returns((OrderTicket)null)
|
||||
.Callback((OrderRequest request) => actualOrdersSubmitted.Add((SubmitOrderRequest)request));
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.Transactions.SetOrderProcessor(orderProcessor.Object);
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
model.Execute(algorithm, new IPortfolioTarget[0]);
|
||||
|
||||
Assert.AreEqual(0, actualOrdersSubmitted.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, 0, 1, 10)]
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, 3, 1, 7)]
|
||||
[TestCase(Language.Python, new[] { 270d, 260d, 250d }, 0, 1, 10)]
|
||||
[TestCase(Language.Python, new[] { 270d, 260d, 250d }, 3, 1, 7)]
|
||||
public void OrdersAreSubmittedImmediatelyForTargetsToExecute(
|
||||
Language language,
|
||||
double[] historicalPrices,
|
||||
decimal openOrdersQuantity,
|
||||
int expectedOrdersSubmitted,
|
||||
decimal expectedTotalQuantity)
|
||||
{
|
||||
var time = new DateTime(2018, 8, 2, 16, 0, 0);
|
||||
var historyProvider = new Mock<IHistoryProvider>();
|
||||
historyProvider.Setup(m => m.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns(historicalPrices.Select((x, i) =>
|
||||
new Slice(time.AddMinutes(i),
|
||||
new List<BaseData>
|
||||
{
|
||||
new TradeBar
|
||||
{
|
||||
Time = time.AddMinutes(i),
|
||||
Symbol = Symbols.AAPL,
|
||||
Open = Convert.ToDecimal(x),
|
||||
High = Convert.ToDecimal(x),
|
||||
Low = Convert.ToDecimal(x),
|
||||
Close = Convert.ToDecimal(x),
|
||||
Volume = 100m
|
||||
}
|
||||
}, time.AddMinutes(i))));
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetHistoryProvider(historyProvider.Object);
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var openOrderRequest = new SubmitOrderRequest(OrderType.Market, SecurityType.Equity, Symbols.AAPL, openOrdersQuantity, 0, 0, DateTime.MinValue, "");
|
||||
openOrderRequest.SetOrderId(1);
|
||||
var order = Order.CreateOrder(openOrderRequest);
|
||||
orderProcessor.AddOpenOrder(order, algorithm);
|
||||
|
||||
var model = GetExecutionModel(language, false);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
Assert.AreEqual(expectedOrdersSubmitted + 1, orderProcessor.GetOpenOrders().Count);
|
||||
|
||||
var executionOrder = orderProcessor.GetOpenOrders().OrderByDescending(o => o.Id).First();
|
||||
Assert.AreEqual(expectedTotalQuantity, executionOrder.Quantity);
|
||||
Assert.AreEqual(algorithm.UtcTime, executionOrder.Time);
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void PartiallyFilledOrdersAreTakenIntoAccount(Language language)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var openOrderRequest = new SubmitOrderRequest(OrderType.Market, SecurityType.Equity, Symbols.AAPL, 100, 0, 0, DateTime.MinValue, "");
|
||||
openOrderRequest.SetOrderId(1);
|
||||
var order = Order.CreateOrder(openOrderRequest);
|
||||
orderProcessor.AddOpenOrder(order, algorithm);
|
||||
|
||||
brokerage.OnOrderEvent(new OrderEvent(order.Id, order.Symbol, DateTime.MinValue, OrderStatus.PartiallyFilled, OrderDirection.Buy, 250, 70, OrderFee.Zero));
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 80) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
Assert.AreEqual(2, orderProcessor.OrdersCount);
|
||||
|
||||
// Remaining quantity for partially filled order = 100 - 70 = 30
|
||||
// Holdings from partially filled order = 70
|
||||
// Quantity submitted = target - holdings - remaining open orders quantity = 80 - 70 - 30 = -20
|
||||
Assert.AreEqual(-20, orderProcessor.GetOpenOrders().OrderByDescending(o => o.Id).First().Quantity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void NonFilledAsyncOrdersAreTakenIntoAccount(Language language)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var model = GetExecutionModel(language, true);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
var targetQuantity = 80;
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, targetQuantity) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
Assert.AreEqual(1, orderProcessor.OrdersCount);
|
||||
|
||||
// Quantity submitted = 80
|
||||
Assert.AreEqual(targetQuantity, orderProcessor.GetOpenOrders().First().Quantity);
|
||||
|
||||
var newTargetQuantity = 100;
|
||||
var newTargets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, newTargetQuantity) };
|
||||
model.Execute(algorithm, newTargets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
Assert.AreEqual(2, orderProcessor.OrdersCount);
|
||||
|
||||
// Remaining quantity for non-filled order = targetQuantity = 80
|
||||
// Quantity submitted = newTargetQuantity - targetQuantity = 100 - 80 = 20
|
||||
Assert.AreEqual(newTargetQuantity - targetQuantity, orderProcessor.GetOpenOrders().OrderByDescending(o => o.Id).First().Quantity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, -1)]
|
||||
[TestCase(Language.Python, -1)]
|
||||
[TestCase(Language.CSharp, 1)]
|
||||
[TestCase(Language.Python, 1)]
|
||||
public void LotSizeIsRespected(Language language, int side)
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.Settings.MinimumOrderMarginPortfolioPercentage = 0;
|
||||
var security = algorithm.AddForex(Symbols.EURUSD.Value);
|
||||
algorithm.Portfolio.SetCash("EUR", 1, 1);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
model.Execute(algorithm,
|
||||
new IPortfolioTarget[] { new PortfolioTarget(Symbols.EURUSD, security.SymbolProperties.LotSize * 1.5m * side) });
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
var orders = orderProcessor.GetOrders().ToList();
|
||||
Assert.AreEqual(1, orders.Count);
|
||||
Assert.AreEqual(security.SymbolProperties.LotSize * side, orders.Single().Quantity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CustomPythonExecutionModelDoesNotRequireOnOrderEventMethod()
|
||||
{
|
||||
using var _ = Py.GIL();
|
||||
const string pythonCode = @"
|
||||
class CustomExecutionModel:
|
||||
def execute(self, algorithm, targets):
|
||||
pass
|
||||
def on_securities_changed(self, algorithm, changes):
|
||||
pass
|
||||
";
|
||||
using var module = PyModule.FromString("CustomExecutionModelModule", pythonCode);
|
||||
using var instance = module.GetAttr("CustomExecutionModel").Invoke();
|
||||
var model = new ExecutionModelPythonWrapper(instance);
|
||||
Assert.DoesNotThrow(() => model.OnOrderEvent(new AlgorithmStub(), new OrderEvent()));
|
||||
}
|
||||
|
||||
private static IExecutionModel GetExecutionModel(Language language, bool asynchronous = false)
|
||||
{
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(ImmediateExecutionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(asynchronous);
|
||||
return new ExecutionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
return new ImmediateExecutionModel(asynchronous);
|
||||
}
|
||||
|
||||
|
||||
internal static BrokerageTransactionHandler GetAndSetBrokerageTransactionHandler(IAlgorithm algorithm, out NullBrokerage brokerage)
|
||||
{
|
||||
brokerage = new NullBrokerage();
|
||||
var orderProcessor = new BrokerageTransactionHandler();
|
||||
orderProcessor.Initialize(algorithm, brokerage, new BacktestingResultHandler());
|
||||
algorithm.Transactions.SetOrderProcessor(orderProcessor);
|
||||
algorithm.Transactions.MarketOrderFillTimeout = TimeSpan.Zero;
|
||||
|
||||
return orderProcessor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Execution
|
||||
{
|
||||
[TestFixture]
|
||||
public class SpreadExecutionModelTests
|
||||
{
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void OrdersAreNotSubmittedWhenNoTargetsToExecute(Language language)
|
||||
{
|
||||
var actualOrdersSubmitted = new List<SubmitOrderRequest>();
|
||||
|
||||
var orderProcessor = new Mock<IOrderProcessor>();
|
||||
orderProcessor.Setup(m => m.Process(It.IsAny<SubmitOrderRequest>()))
|
||||
.Returns((OrderTicket)null)
|
||||
.Callback((OrderRequest request) => actualOrdersSubmitted.Add((SubmitOrderRequest)request));
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.Transactions.SetOrderProcessor(orderProcessor.Object);
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
model.Execute(algorithm, new IPortfolioTarget[0]);
|
||||
|
||||
Assert.AreEqual(0, actualOrdersSubmitted.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, 240, 1, 10)]
|
||||
[TestCase(Language.CSharp, 250, 0, 0)]
|
||||
[TestCase(Language.Python, 240, 1, 10)]
|
||||
[TestCase(Language.Python, 250, 0, 0)]
|
||||
public void OrdersAreSubmittedWhenRequiredForTargetsToExecute(
|
||||
Language language,
|
||||
decimal currentPrice,
|
||||
int expectedOrdersSubmitted,
|
||||
decimal expectedTotalQuantity)
|
||||
{
|
||||
var time = new DateTime(2018, 8, 2, 14, 0, 0);
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
// pushing the ask higher will cause the spread the widen and no trade to happen
|
||||
var ask = expectedOrdersSubmitted == 0 ? currentPrice * 1.1m : currentPrice;
|
||||
security.SetMarketPrice(new QuoteBar
|
||||
{
|
||||
Time = time,
|
||||
Symbol = Symbols.AAPL,
|
||||
Ask = new Bar(ask, ask, ask, ask),
|
||||
Bid = new Bar(currentPrice, currentPrice, currentPrice, currentPrice)
|
||||
});
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = ImmediateExecutionModelTests.GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
var orders = orderProcessor.GetOrders().ToList();
|
||||
|
||||
Assert.AreEqual(expectedOrdersSubmitted, orders.Count);
|
||||
Assert.AreEqual(expectedTotalQuantity, orders.Sum(x => x.Quantity));
|
||||
|
||||
if (expectedOrdersSubmitted == 1)
|
||||
{
|
||||
var order = orders[0];
|
||||
Assert.AreEqual(expectedTotalQuantity, order.Quantity);
|
||||
Assert.AreEqual(algorithm.UtcTime, order.Time);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, 1, 10, true)]
|
||||
[TestCase(Language.Python, 1, 10, true)]
|
||||
[TestCase(Language.CSharp, 0, 0, false)]
|
||||
[TestCase(Language.Python, 0, 0, false)]
|
||||
public void FillsOnTradesOnlyRespectingExchangeOpen(Language language, int expectedOrdersSubmitted, decimal expectedTotalQuantity, bool exchangeOpen)
|
||||
{
|
||||
var time = new DateTime(2018, 8, 2, 0, 0, 0);
|
||||
if (exchangeOpen)
|
||||
{
|
||||
time = time.AddHours(14);
|
||||
}
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = ImmediateExecutionModelTests.GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
var orders = orderProcessor.GetOrders().ToList();
|
||||
|
||||
Assert.AreEqual(expectedOrdersSubmitted, orders.Count);
|
||||
Assert.AreEqual(expectedTotalQuantity, orders.Sum(x => x.Quantity));
|
||||
|
||||
if (expectedOrdersSubmitted == 1)
|
||||
{
|
||||
var order = orders[0];
|
||||
Assert.AreEqual(expectedTotalQuantity, order.Quantity);
|
||||
Assert.AreEqual(algorithm.UtcTime, order.Time);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, MarketDataType.TradeBar)]
|
||||
[TestCase(Language.Python, MarketDataType.TradeBar)]
|
||||
[TestCase(Language.CSharp, MarketDataType.QuoteBar)]
|
||||
[TestCase(Language.Python, MarketDataType.QuoteBar)]
|
||||
public void OnSecuritiesChangeDoesNotThrow(
|
||||
Language language,
|
||||
MarketDataType marketDataType)
|
||||
{
|
||||
var time = new DateTime(2018, 8, 2, 16, 0, 0);
|
||||
|
||||
Func<double, int, BaseData> func = (x, i) =>
|
||||
{
|
||||
var price = Convert.ToDecimal(x);
|
||||
switch (marketDataType)
|
||||
{
|
||||
case MarketDataType.TradeBar:
|
||||
return new TradeBar(time.AddMinutes(i), Symbols.AAPL, price, price, price, price, 100m);
|
||||
case MarketDataType.QuoteBar:
|
||||
var bar = new Bar(price, price, price, price);
|
||||
return new QuoteBar(time.AddMinutes(i), Symbols.AAPL, bar, 10m, bar, 10m);
|
||||
default:
|
||||
throw new ArgumentException($"Invalid MarketDataType: {marketDataType}");
|
||||
}
|
||||
};
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
Assert.DoesNotThrow(() => model.OnSecuritiesChanged(algorithm, changes));
|
||||
}
|
||||
|
||||
private static IExecutionModel GetExecutionModel(Language language)
|
||||
{
|
||||
const decimal acceptingSpreadPercent = 0.005m;
|
||||
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(SpreadExecutionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(acceptingSpreadPercent.ToPython());
|
||||
return new ExecutionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
return new SpreadExecutionModel(acceptingSpreadPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Execution
|
||||
{
|
||||
[TestFixture]
|
||||
public class StandardDeviationExecutionModelTests
|
||||
{
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void OrdersAreNotSubmittedWhenNoTargetsToExecute(Language language)
|
||||
{
|
||||
var actualOrdersSubmitted = new List<SubmitOrderRequest>();
|
||||
|
||||
var orderProcessor = new Mock<IOrderProcessor>();
|
||||
orderProcessor.Setup(m => m.Process(It.IsAny<SubmitOrderRequest>()))
|
||||
.Returns((OrderTicket)null)
|
||||
.Callback((OrderRequest request) => actualOrdersSubmitted.Add((SubmitOrderRequest)request));
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.Transactions.SetOrderProcessor(orderProcessor.Object);
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
model.Execute(algorithm, new IPortfolioTarget[0]);
|
||||
|
||||
Assert.AreEqual(0, actualOrdersSubmitted.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, 240, 1, 10)]
|
||||
[TestCase(Language.CSharp, new[] { 250d, 250d, 250d }, 250, 0, 0)]
|
||||
[TestCase(Language.Python, new[] { 270d, 260d, 250d }, 240, 1, 10)]
|
||||
[TestCase(Language.Python, new[] { 250d, 250d, 250d }, 250, 0, 0)]
|
||||
public void OrdersAreSubmittedWhenRequiredForTargetsToExecute(
|
||||
Language language,
|
||||
double[] historicalPrices,
|
||||
decimal currentPrice,
|
||||
int expectedOrdersSubmitted,
|
||||
decimal expectedTotalQuantity)
|
||||
{
|
||||
var time = new DateTime(2018, 8, 2, 16, 0, 0);
|
||||
var historyProvider = new Mock<IHistoryProvider>();
|
||||
historyProvider.Setup(m => m.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns(historicalPrices.Select((x,i) =>
|
||||
new Slice(time.AddMinutes(i),
|
||||
new List<BaseData>
|
||||
{
|
||||
new TradeBar
|
||||
{
|
||||
Time = time.AddMinutes(i),
|
||||
Symbol = Symbols.AAPL,
|
||||
Open = Convert.ToDecimal(x),
|
||||
High = Convert.ToDecimal(x),
|
||||
Low = Convert.ToDecimal(x),
|
||||
Close = Convert.ToDecimal(x),
|
||||
Volume = 100m
|
||||
}
|
||||
}, time.AddMinutes(i))));
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SetHistoryProvider(historyProvider.Object);
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = currentPrice });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = ImmediateExecutionModelTests.GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
var orders = orderProcessor.GetOrders().ToList();
|
||||
|
||||
Assert.AreEqual(expectedOrdersSubmitted, orders.Count);
|
||||
Assert.AreEqual(expectedTotalQuantity, orders.Sum(x => x.Quantity));
|
||||
|
||||
if (expectedOrdersSubmitted == 1)
|
||||
{
|
||||
var order = orders[0];
|
||||
Assert.AreEqual(expectedTotalQuantity, order.Quantity);
|
||||
Assert.AreEqual(algorithm.UtcTime, order.Time);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, MarketDataType.TradeBar)]
|
||||
[TestCase(Language.Python, new[] { 250d, 250d, 250d }, MarketDataType.TradeBar)]
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, MarketDataType.QuoteBar)]
|
||||
[TestCase(Language.Python, new[] { 250d, 250d, 250d }, MarketDataType.QuoteBar)]
|
||||
public void OnSecuritiesChangeDoesNotThrow(
|
||||
Language language,
|
||||
double[] historicalPrices,
|
||||
MarketDataType marketDataType)
|
||||
{
|
||||
var time = new DateTime(2018, 8, 2, 16, 0, 0);
|
||||
|
||||
Func<double, int, BaseData> func = (x, i) =>
|
||||
{
|
||||
var price = Convert.ToDecimal(x);
|
||||
switch (marketDataType)
|
||||
{
|
||||
case MarketDataType.TradeBar:
|
||||
return new TradeBar(time.AddMinutes(i), Symbols.AAPL, price, price, price, price, 100m);
|
||||
case MarketDataType.QuoteBar:
|
||||
var bar = new Bar(price, price, price, price);
|
||||
return new QuoteBar(time.AddMinutes(i), Symbols.AAPL, bar, 10m, bar, 10m);
|
||||
default:
|
||||
throw new ArgumentException($"Invalid MarketDataType: {marketDataType}");
|
||||
}
|
||||
};
|
||||
|
||||
var historyProvider = new Mock<IHistoryProvider>();
|
||||
historyProvider.Setup(m => m.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns(historicalPrices.Select((x, i) => new Slice(time.AddMinutes(i), new List<BaseData> { func(x, i) }, time.AddMinutes(i))));
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SetHistoryProvider(historyProvider.Object);
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250 });
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
Assert.DoesNotThrow(() => model.OnSecuritiesChanged(algorithm, changes));
|
||||
}
|
||||
|
||||
private static IExecutionModel GetExecutionModel(Language language)
|
||||
{
|
||||
const int period = 2;
|
||||
const decimal deviations = 1.5m;
|
||||
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(StandardDeviationExecutionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(period.ToPython(), deviations.ToPython());
|
||||
return new ExecutionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
return new StandardDeviationExecutionModel(period, deviations);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Execution
|
||||
{
|
||||
[TestFixture]
|
||||
public class VolumeWeightedAveragePriceExecutionModelTests
|
||||
{
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void OrdersAreNotSubmittedWhenNoTargetsToExecute(Language language)
|
||||
{
|
||||
var actualOrdersSubmitted = new List<SubmitOrderRequest>();
|
||||
|
||||
var orderProcessor = new Mock<IOrderProcessor>();
|
||||
orderProcessor.Setup(m => m.Process(It.IsAny<SubmitOrderRequest>()))
|
||||
.Returns((OrderTicket)null)
|
||||
.Callback((OrderRequest request) => actualOrdersSubmitted.Add((SubmitOrderRequest)request));
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.Transactions.SetOrderProcessor(orderProcessor.Object);
|
||||
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
model.Execute(algorithm, new IPortfolioTarget[0]);
|
||||
|
||||
Assert.AreEqual(0, actualOrdersSubmitted.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, 5000, 1, 10)]
|
||||
[TestCase(Language.Python, new[] { 270d, 260d, 250d }, 5000, 1, 10)]
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, 500, 1, 5)]
|
||||
[TestCase(Language.Python, new[] { 270d, 260d, 250d }, 500, 1, 5)]
|
||||
[TestCase(Language.CSharp, new[] { 270d, 260d, 250d }, 50, 0, 0)]
|
||||
[TestCase(Language.Python, new[] { 270d, 260d, 250d }, 50, 0, 0)]
|
||||
[TestCase(Language.CSharp, new[] { 230d, 240d, 250d }, 50000, 0, 0)]
|
||||
[TestCase(Language.Python, new[] { 230d, 240d, 250d }, 50000, 0, 0)]
|
||||
public void OrdersAreSubmittedWhenRequiredForTargetsToExecute(
|
||||
Language language,
|
||||
double[] historicalPrices,
|
||||
decimal lastVolume,
|
||||
int expectedOrdersSubmitted,
|
||||
decimal expectedTotalQuantity)
|
||||
{
|
||||
var actualOrdersSubmitted = new List<SubmitOrderRequest>();
|
||||
|
||||
var time = new DateTime(2018, 8, 2, 16, 0, 0);
|
||||
var historyProvider = new Mock<IHistoryProvider>();
|
||||
historyProvider.Setup(m => m.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns(historicalPrices.Select((x, i) =>
|
||||
new Slice(time.AddMinutes(i),
|
||||
new List<BaseData>
|
||||
{
|
||||
new TradeBar
|
||||
{
|
||||
Time = time.AddMinutes(i),
|
||||
Symbol = Symbols.AAPL,
|
||||
Open = Convert.ToDecimal(x),
|
||||
High = Convert.ToDecimal(x),
|
||||
Low = Convert.ToDecimal(x),
|
||||
Close = Convert.ToDecimal(x),
|
||||
Volume = 100m
|
||||
}
|
||||
}, time.AddMinutes(i))));
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.SetHistoryProvider(historyProvider.Object);
|
||||
algorithm.SetDateTime(time.AddMinutes(5));
|
||||
|
||||
var security = algorithm.AddEquity(Symbols.AAPL.Value);
|
||||
security.SetMarketPrice(new TradeBar { Value = 250, Volume = lastVolume });
|
||||
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var orderProcessor = ImmediateExecutionModelTests.GetAndSetBrokerageTransactionHandler(algorithm, out var brokerage);
|
||||
|
||||
try
|
||||
{
|
||||
var model = GetExecutionModel(language);
|
||||
algorithm.SetExecution(model);
|
||||
|
||||
var changes = SecurityChangesTests.CreateNonInternal(new[] { security }, Enumerable.Empty<Security>());
|
||||
model.OnSecuritiesChanged(algorithm, changes);
|
||||
|
||||
algorithm.History(new List<Symbol> { security.Symbol }, historicalPrices.Length, Resolution.Minute)
|
||||
.PushThroughConsolidators(symbol => algorithm.Securities[symbol].Subscriptions.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity)).Consolidators.First());
|
||||
|
||||
var targets = new IPortfolioTarget[] { new PortfolioTarget(security.Symbol, 10) };
|
||||
model.Execute(algorithm, targets);
|
||||
orderProcessor.ProcessSynchronousEvents();
|
||||
|
||||
var orders = orderProcessor.GetOrders().ToList();
|
||||
|
||||
Assert.AreEqual(expectedOrdersSubmitted, orders.Count);
|
||||
Assert.AreEqual(expectedTotalQuantity, orders.Sum(x => x.Quantity));
|
||||
|
||||
if (expectedOrdersSubmitted == 1)
|
||||
{
|
||||
var order = orders[0];
|
||||
Assert.AreEqual(expectedTotalQuantity, order.Quantity);
|
||||
Assert.AreEqual(algorithm.UtcTime, order.Time);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderProcessor.Exit();
|
||||
brokerage?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static IExecutionModel GetExecutionModel(Language language)
|
||||
{
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(VolumeWeightedAveragePriceExecutionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke();
|
||||
return new ExecutionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
return new VolumeWeightedAveragePriceExecutionModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework
|
||||
{
|
||||
[TestFixture]
|
||||
public class FrameworkModelsPythonInheritanceTests
|
||||
{
|
||||
[Test]
|
||||
public void ManualUniverseSelectionModelCanBeInherited()
|
||||
{
|
||||
var code = @"
|
||||
from clr import AddReference
|
||||
AddReference('QuantConnect.Common')
|
||||
|
||||
from QuantConnect import Market, SecurityType, Symbol
|
||||
from Selection.ManualUniverseSelectionModel import ManualUniverseSelectionModel
|
||||
|
||||
class MockUniverseSelectionModel(ManualUniverseSelectionModel):
|
||||
def __init__(self):
|
||||
super().__init__([Symbol.Create('SPY', SecurityType.Equity, Market.USA)])";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic pyModel = PyModule.FromString(Guid.NewGuid().ToString(), code)
|
||||
.GetAttr("MockUniverseSelectionModel");
|
||||
|
||||
var model = new UniverseSelectionModelPythonWrapper(pyModel());
|
||||
|
||||
var universes = model.CreateUniverses(new QCAlgorithm()).ToList();
|
||||
Assert.AreEqual(1, universes.Count);
|
||||
|
||||
var universe = universes.First();
|
||||
var symbols = universe.SelectSymbols(DateTime.Now, null).ToList();
|
||||
Assert.AreEqual(1, symbols.Count);
|
||||
|
||||
var expected = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
|
||||
var symbol = symbols.First();
|
||||
Assert.AreEqual(expected, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FundamentalUniverseSelectionModelCanBeInherited()
|
||||
{
|
||||
var code = @"
|
||||
from AlgorithmImports import *
|
||||
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
|
||||
|
||||
class MockUniverseSelectionModel(FundamentalUniverseSelectionModel):
|
||||
def __init__(self):
|
||||
super().__init__(False)
|
||||
def SelectCoarse(self, algorithm, coarse):
|
||||
return [Symbol.Create('SPY', SecurityType.Equity, Market.USA)]";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic pyModel = PyModule.FromString(Guid.NewGuid().ToString(), code)
|
||||
.GetAttr("MockUniverseSelectionModel");
|
||||
|
||||
var model = new UniverseSelectionModelPythonWrapper(pyModel());
|
||||
|
||||
var universes = model.CreateUniverses(new QCAlgorithm()).ToList();
|
||||
Assert.AreEqual(1, universes.Count);
|
||||
|
||||
var data = new BaseDataCollection();
|
||||
data.Add(new CoarseFundamental());
|
||||
|
||||
var universe = universes.First();
|
||||
var symbols = universe.SelectSymbols(DateTime.Now, data).ToList();
|
||||
Assert.AreEqual(1, symbols.Count);
|
||||
|
||||
var expected = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
|
||||
var symbol = symbols.First();
|
||||
Assert.AreEqual(expected, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCanInheritFromFundamentalUniverseSelectionModelAndOverrideMethods()
|
||||
{
|
||||
var code = @"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class MockUniverseSelectionModel(FundamentalUniverseSelectionModel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.select_call_count = 0
|
||||
self.select_coarse_call_count = 0
|
||||
self.select_fine_call_count = 0
|
||||
self.create_coarse_call_count = 0
|
||||
|
||||
def select(self, algorithm: QCAlgorithm, fundamental: list[Fundamental]) -> list[Symbol]:
|
||||
self.select_call_count += 1
|
||||
return [Futures.Metals.GOLD]
|
||||
|
||||
def select_coarse(self, algorithm, coarse):
|
||||
self.select_coarse_call_count += 1
|
||||
self.select_coarse_called = True
|
||||
|
||||
filtered = [c for c in coarse if c.price > 10]
|
||||
return [c.symbol for c in filtered[:2]]
|
||||
|
||||
def select_fine(self, algorithm, fine):
|
||||
self.select_fine_call_count += 1
|
||||
self.select_fine_called = True
|
||||
|
||||
return [f.symbol for f in fine[:2]]
|
||||
|
||||
def create_coarse_fundamental_universe(self, algorithm):
|
||||
self.create_coarse_call_count += 1
|
||||
self.create_coarse_called = True
|
||||
|
||||
return CoarseFundamentalUniverse(
|
||||
algorithm.universe_settings,
|
||||
self.custom_coarse_selector
|
||||
)
|
||||
|
||||
def custom_coarse_selector(self, coarse):
|
||||
filtered = [c for c in coarse if c.has_fundamental_data]
|
||||
return [c.symbol for c in filtered[:5]]";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic pyModel = PyModule.FromString(Guid.NewGuid().ToString(), code)
|
||||
.GetAttr("MockUniverseSelectionModel");
|
||||
|
||||
PyObject pyModelInstance = pyModel();
|
||||
var algorithm = new QCAlgorithm();
|
||||
var model = new FundamentalUniverseSelectionModel();
|
||||
model.SetPythonInstance(pyModelInstance);
|
||||
|
||||
// call the create_universes method
|
||||
var universes = model.CreateUniverses(algorithm).ToList();
|
||||
var universe = universes.First();
|
||||
var selectedSymbols = universe.SelectSymbols(DateTime.Now, new BaseDataCollection()).ToList();
|
||||
int selectCount = pyModelInstance.GetAttr("select_call_count").As<int>();
|
||||
Assert.Greater(selectCount, 0);
|
||||
|
||||
// call the select method
|
||||
model.Select(algorithm, new List<Fundamental>());
|
||||
selectCount = pyModelInstance.GetAttr("select_call_count").As<int>();
|
||||
Assert.Greater(selectCount, 1);
|
||||
|
||||
// call the select_coarse method
|
||||
model.SelectCoarse(algorithm, new List<CoarseFundamental>());
|
||||
int selectCoarseCount = pyModelInstance.GetAttr("select_coarse_call_count").As<int>();
|
||||
Assert.Greater(selectCoarseCount, 0);
|
||||
|
||||
// call the select_fine method
|
||||
model.SelectFine(algorithm, new List<FineFundamental>());
|
||||
int selectFineCount = pyModelInstance.GetAttr("select_fine_call_count").As<int>();
|
||||
Assert.Greater(selectFineCount, 0);
|
||||
|
||||
// call the create_coarse_fundamental_universe method
|
||||
model.CreateCoarseFundamentalUniverse(algorithm);
|
||||
int createCoarseCount = pyModelInstance.GetAttr("create_coarse_call_count").As<int>();
|
||||
Assert.Greater(createCoarseCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonCanInheritFromBasePairsTradingAlphaModelAndOverrideMethods()
|
||||
{
|
||||
var code = @"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class MockPairsTradingAlphaModel(BasePairsTradingAlphaModel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.has_passed_test_call_count = 0
|
||||
|
||||
def has_passed_test(self, algorithm, asset1, asset2):
|
||||
self.has_passed_test_call_count += 1
|
||||
return False";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var pyModel = PyModule.FromString("test", code).GetAttr("MockPairsTradingAlphaModel");
|
||||
var pyInstance = pyModel.Invoke();
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
var model = new BasePairsTradingAlphaModel();
|
||||
model.SetPythonInstance(pyInstance);
|
||||
|
||||
var security1 = new Equity(
|
||||
Symbols.SPY,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
|
||||
new Cash("USD", 1m, 1m),
|
||||
SymbolProperties.GetDefault("USD"),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
new RegisteredSecurityDataTypesProvider(),
|
||||
new SecurityCache()
|
||||
);
|
||||
|
||||
var security2 = new Equity(
|
||||
Symbols.AAPL,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
|
||||
new Cash("USD", 1m, 1m),
|
||||
SymbolProperties.GetDefault("USD"),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
new RegisteredSecurityDataTypesProvider(),
|
||||
new SecurityCache()
|
||||
);
|
||||
|
||||
var changes = SecurityChanges.Create(
|
||||
new List<Security> { security1, security2 },
|
||||
new List<Security>(),
|
||||
new List<Security>(),
|
||||
new List<Security>()
|
||||
);
|
||||
|
||||
model.OnSecuritiesChanged(new QCAlgorithm(), changes);
|
||||
|
||||
int hasPassedTestCallCount = pyInstance.GetAttr("has_passed_test_call_count").As<int>();
|
||||
Assert.AreEqual(1, hasPassedTestCallCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
* 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 Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework
|
||||
{
|
||||
[TestFixture, Parallelizable(ParallelScope.All)]
|
||||
public class InsightTests
|
||||
{
|
||||
[Test]
|
||||
public void HasReferenceTypeEqualitySemantics()
|
||||
{
|
||||
var one = Insight.Price(Symbols.SPY, Time.OneSecond, InsightDirection.Up);
|
||||
var two = Insight.Price(Symbols.SPY, Time.OneSecond, InsightDirection.Up);
|
||||
Assert.AreNotEqual(one, two);
|
||||
Assert.AreEqual(one, one);
|
||||
Assert.AreEqual(two, two);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SurvivesRoundTripSerializationUsingJsonConvert()
|
||||
{
|
||||
var time = new DateTime(2000, 01, 02, 03, 04, 05, 06);
|
||||
var insight = new Insight(time, Symbols.SPY, Time.OneMinute, InsightType.Volatility, InsightDirection.Up, 1, 2, "source-model", 1);
|
||||
Insight.Group(insight);
|
||||
insight.ReferenceValueFinal = 10;
|
||||
var serialized = JsonConvert.SerializeObject(insight);
|
||||
var deserialized = JsonConvert.DeserializeObject<Insight>(serialized);
|
||||
|
||||
Assert.AreEqual(insight.CloseTimeUtc, deserialized.CloseTimeUtc);
|
||||
Assert.AreEqual(insight.Confidence, deserialized.Confidence);
|
||||
Assert.AreEqual(insight.Direction, deserialized.Direction);
|
||||
Assert.AreEqual(insight.EstimatedValue, deserialized.EstimatedValue);
|
||||
Assert.AreEqual(insight.GeneratedTimeUtc, deserialized.GeneratedTimeUtc);
|
||||
Assert.AreEqual(insight.GroupId, deserialized.GroupId);
|
||||
Assert.AreEqual(insight.Id, deserialized.Id);
|
||||
Assert.AreEqual(insight.Magnitude, deserialized.Magnitude);
|
||||
Assert.AreEqual(insight.Period, deserialized.Period);
|
||||
Assert.AreEqual(insight.SourceModel, deserialized.SourceModel);
|
||||
Assert.AreEqual(insight.Score.Direction, deserialized.Score.Direction);
|
||||
Assert.AreEqual(insight.Score.Magnitude, deserialized.Score.Magnitude);
|
||||
Assert.AreEqual(insight.Score.UpdatedTimeUtc, deserialized.Score.UpdatedTimeUtc);
|
||||
Assert.AreEqual(insight.Score.IsFinalScore, deserialized.Score.IsFinalScore);
|
||||
Assert.AreEqual(insight.Symbol, deserialized.Symbol);
|
||||
Assert.AreEqual(insight.Type, deserialized.Type);
|
||||
Assert.AreEqual(insight.Weight, deserialized.Weight);
|
||||
Assert.AreEqual(insight.ReferenceValueFinal, deserialized.ReferenceValueFinal);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CancelInsight()
|
||||
{
|
||||
var time = new DateTime(2000, 01, 02, 03, 04, 05, 06);
|
||||
var insight = new Insight(time, Symbols.SPY, Time.OneMinute, InsightType.Volatility, InsightDirection.Up, 1, 2, "source-model");
|
||||
|
||||
insight.Cancel(time.AddMinutes(1));
|
||||
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(59), insight.Period);
|
||||
|
||||
insight.Cancel(time.AddDays(1));
|
||||
|
||||
Assert.AreEqual(TimeSpan.FromSeconds(59), insight.Period);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SerializationUsingJsonConvertTrimsEstimatedValue()
|
||||
{
|
||||
var time = new DateTime(2000, 01, 02, 03, 04, 05, 06);
|
||||
var insight = new Insight(time, Symbols.SPY, Time.OneMinute, InsightType.Volatility, InsightDirection.Up, 1, 2, "source-model");
|
||||
insight.EstimatedValue = 0.00001m;
|
||||
insight.Score.SetScore(InsightScoreType.Direction, 0.00001, DateTime.UtcNow);
|
||||
insight.Score.SetScore(InsightScoreType.Magnitude, 0.00001, DateTime.UtcNow);
|
||||
var serialized = JsonConvert.SerializeObject(insight);
|
||||
var deserialized = JsonConvert.DeserializeObject<Insight>(serialized);
|
||||
|
||||
Assert.AreEqual(0, deserialized.EstimatedValue);
|
||||
Assert.AreEqual(0, deserialized.Score.Direction);
|
||||
Assert.AreEqual(0, deserialized.Score.Magnitude);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SurvivesRoundTripCopy()
|
||||
{
|
||||
var time = new DateTime(2000, 01, 02, 03, 04, 05, 06);
|
||||
var original = new Insight(time, Symbols.SPY, Time.OneMinute, InsightType.Volatility, InsightDirection.Up, 1, 2, "source-model", 1);
|
||||
original.ReferenceValueFinal = 10;
|
||||
Insight.Group(original);
|
||||
|
||||
var copy = original.Clone();
|
||||
|
||||
Assert.AreEqual(original.CloseTimeUtc, copy.CloseTimeUtc);
|
||||
Assert.AreEqual(original.Confidence, copy.Confidence);
|
||||
Assert.AreEqual(original.Direction, copy.Direction);
|
||||
Assert.AreEqual(original.EstimatedValue, copy.EstimatedValue);
|
||||
Assert.AreEqual(original.GeneratedTimeUtc, copy.GeneratedTimeUtc);
|
||||
Assert.AreEqual(original.GroupId, copy.GroupId);
|
||||
Assert.AreEqual(original.Id, copy.Id);
|
||||
Assert.AreEqual(original.Magnitude, copy.Magnitude);
|
||||
Assert.AreEqual(original.Period, copy.Period);
|
||||
Assert.AreEqual(original.SourceModel, copy.SourceModel);
|
||||
Assert.AreEqual(original.Score.Direction, copy.Score.Direction);
|
||||
Assert.AreEqual(original.Score.Magnitude, copy.Score.Magnitude);
|
||||
Assert.AreEqual(original.Score.UpdatedTimeUtc, copy.Score.UpdatedTimeUtc);
|
||||
Assert.AreEqual(original.Score.IsFinalScore, copy.Score.IsFinalScore);
|
||||
Assert.AreEqual(original.Symbol, copy.Symbol);
|
||||
Assert.AreEqual(original.Type, copy.Type);
|
||||
Assert.AreEqual(original.Weight, copy.Weight);
|
||||
Assert.AreEqual(original.ReferenceValueFinal, copy.ReferenceValueFinal);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupAssignsGroupId()
|
||||
{
|
||||
var insight1 = Insight.Price(Symbols.SPY, Time.OneMinute, InsightDirection.Up);
|
||||
var insight2 = Insight.Price(Symbols.SPY, Time.OneMinute, InsightDirection.Up);
|
||||
var group = Insight.Group(insight1, insight2).ToList();
|
||||
foreach (var member in group)
|
||||
{
|
||||
Assert.IsTrue(member.GroupId.HasValue);
|
||||
}
|
||||
var groupId = insight1.GroupId.Value;
|
||||
foreach (var member in group)
|
||||
{
|
||||
Assert.AreEqual(groupId, member.GroupId);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GroupThrowsExceptionIfInsightAlreadyHasGroupId()
|
||||
{
|
||||
var insight1 = Insight.Price(Symbols.SPY, Time.OneMinute, InsightDirection.Up);
|
||||
Insight.Group(insight1);
|
||||
Assert.That(() => Insight.Group(insight1), Throws.InvalidOperationException);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Resolution.Tick, 1)]
|
||||
[TestCase(Resolution.Tick, 10)]
|
||||
[TestCase(Resolution.Tick, 100)]
|
||||
[TestCase(Resolution.Second, 1)]
|
||||
[TestCase(Resolution.Second, 10)]
|
||||
[TestCase(Resolution.Second, 100)]
|
||||
[TestCase(Resolution.Minute, 1)]
|
||||
[TestCase(Resolution.Minute, 10)]
|
||||
[TestCase(Resolution.Minute, 100)]
|
||||
[TestCase(Resolution.Hour, 1)]
|
||||
[TestCase(Resolution.Hour, 10)]
|
||||
[TestCase(Resolution.Hour, 100)]
|
||||
[TestCase(Resolution.Daily, 1)]
|
||||
[TestCase(Resolution.Daily, 10)]
|
||||
[TestCase(Resolution.Daily, 100)]
|
||||
public void SetPeriodAndCloseTimeUsingResolutionBarCount(Resolution resolution, int barCount)
|
||||
{
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 06, 13, 31, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
|
||||
var symbol = Symbols.SPY;
|
||||
var insight = Insight.Price(symbol, resolution, barCount, InsightDirection.Up);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
insight.SetPeriodAndCloseTime(exchangeHours);
|
||||
var expectedCloseTime = Insight.ComputeCloseTime(exchangeHours, insight.GeneratedTimeUtc, resolution, barCount);
|
||||
Assert.AreEqual(expectedCloseTime, insight.CloseTimeUtc);
|
||||
Assert.AreEqual(expectedCloseTime - generatedTimeUtc, insight.Period);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Resolution.Second, 1)]
|
||||
[TestCase(Resolution.Second, 10)]
|
||||
[TestCase(Resolution.Second, 100)]
|
||||
[TestCase(Resolution.Minute, 1)]
|
||||
[TestCase(Resolution.Minute, 10)]
|
||||
[TestCase(Resolution.Minute, 100)]
|
||||
[TestCase(Resolution.Hour, 1)]
|
||||
[TestCase(Resolution.Hour, 10)]
|
||||
[TestCase(Resolution.Hour, 100)]
|
||||
[TestCase(Resolution.Daily, 1)]
|
||||
[TestCase(Resolution.Daily, 10)]
|
||||
[TestCase(Resolution.Daily, 100)]
|
||||
public void SetPeriodAndCloseTimeUsingPeriod(Resolution resolution, int barCount)
|
||||
{
|
||||
var period = resolution.ToTimeSpan().Multiply(barCount);
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 06, 13, 31, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
|
||||
var symbol = Symbols.SPY;
|
||||
var insight = Insight.Price(symbol, period, InsightDirection.Up);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
insight.SetPeriodAndCloseTime(exchangeHours);
|
||||
Assert.AreEqual(Time.Max(period, Time.OneSecond), insight.Period);
|
||||
|
||||
var expectedCloseTime = Insight.ComputeCloseTime(exchangeHours, insight.GeneratedTimeUtc, period);
|
||||
Assert.AreEqual(expectedCloseTime, insight.CloseTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Resolution.Tick, 1)]
|
||||
[TestCase(Resolution.Tick, 10)]
|
||||
[TestCase(Resolution.Tick, 100)]
|
||||
[TestCase(Resolution.Second, 1)]
|
||||
[TestCase(Resolution.Second, 10)]
|
||||
[TestCase(Resolution.Second, 100)]
|
||||
[TestCase(Resolution.Minute, 1)]
|
||||
[TestCase(Resolution.Minute, 10)]
|
||||
[TestCase(Resolution.Minute, 100)]
|
||||
[TestCase(Resolution.Hour, 1)]
|
||||
[TestCase(Resolution.Hour, 10)]
|
||||
[TestCase(Resolution.Hour, 100)]
|
||||
[TestCase(Resolution.Daily, 1)]
|
||||
[TestCase(Resolution.Daily, 10)]
|
||||
[TestCase(Resolution.Daily, 100)]
|
||||
public void SetPeriodAndCloseTimeUsingCloseTime(Resolution resolution, int barCount)
|
||||
{
|
||||
// consistency test -- first compute expected close time and then back-compute period to verify
|
||||
var symbol = Symbols.SPY;
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 06, 13, 31, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
|
||||
var baseline = Insight.Price(symbol, resolution, barCount, InsightDirection.Up);
|
||||
baseline.GeneratedTimeUtc = generatedTimeUtc;
|
||||
baseline.SetPeriodAndCloseTime(exchangeHours);
|
||||
var baselineCloseTimeLocal = baseline.CloseTimeUtc.ConvertFromUtc(TimeZones.NewYork);
|
||||
|
||||
var insight = Insight.Price(symbol, baselineCloseTimeLocal, baseline.Direction);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.SetPeriodAndCloseTime(exchangeHours);
|
||||
|
||||
Assert.AreEqual(baseline.Period, insight.Period);
|
||||
Assert.AreEqual(baseline.CloseTimeUtc, insight.CloseTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("SPY", SecurityType.Equity, Market.USA, 2018, 12, 4, 9, 30)]
|
||||
[TestCase("EURUSD", SecurityType.Forex, Market.FXCM, 2018, 12, 4, 0, 0)]
|
||||
public void SetPeriodAndCloseTimeUsingExpiryEndOfDay(string ticker, SecurityType securityType, string market, int year, int month, int day, int hour, int minute)
|
||||
{
|
||||
var symbol = Symbol.Create(ticker, securityType, market);
|
||||
var generatedTimeUtc = new DateTime(2018, 12, 3, 9, 31, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.EndOfDay, InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("SPY", SecurityType.Equity, Market.USA, 2018, 12, 10, 9, 30)]
|
||||
[TestCase("EURUSD", SecurityType.Forex, Market.FXCM, 2018, 12, 10, 0, 0)]
|
||||
public void SetPeriodAndCloseTimeUsingExpiryEndOfWeek(string ticker, SecurityType securityType, string market, int year, int month, int day, int hour, int minute)
|
||||
{
|
||||
var symbol = Symbol.Create(ticker, securityType, market);
|
||||
var generatedTime = new DateTime(2018, 12, 3, 9, 31, 0);
|
||||
var generatedTimeUtc = generatedTime.ConvertToUtc(TimeZones.NewYork);
|
||||
|
||||
// Using Expiry Func
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.EndOfWeek, InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
|
||||
// Using DateTime
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.EndOfWeek(generatedTime), InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("SPY", SecurityType.Equity, Market.USA, 2019, 1, 2, 9, 30)]
|
||||
[TestCase("EURUSD", SecurityType.Forex, Market.FXCM, 2019, 1, 2, 0, 0)]
|
||||
public void SetPeriodAndCloseTimeUsingExpiryEndOfMonth(string ticker, SecurityType securityType, string market, int year, int month, int day, int hour, int minute)
|
||||
{
|
||||
var symbol = Symbol.Create(ticker, securityType, market);
|
||||
var generatedTime = new DateTime(2018, 12, 3, 9, 31, 0);
|
||||
var generatedTimeUtc = generatedTime.ConvertToUtc(TimeZones.NewYork);
|
||||
|
||||
// Using Expiry Func
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.EndOfMonth, InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
|
||||
// Using DateTime
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.EndOfMonth(generatedTime), InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("SPY", SecurityType.Equity, Market.USA, 2019, 1, 3, 9, 31)]
|
||||
[TestCase("EURUSD", SecurityType.Forex, Market.FXCM, 2019, 1, 3, 9, 31)]
|
||||
public void SetPeriodAndCloseTimeUsingExpiryOneMonth(string ticker, SecurityType securityType, string market, int year, int month, int day, int hour, int minute)
|
||||
{
|
||||
var symbol = Symbol.Create(ticker, securityType, market);
|
||||
var generatedTime = new DateTime(2018, 12, 3, 9, 31, 0);
|
||||
var generatedTimeUtc = generatedTime.ConvertToUtc(TimeZones.NewYork);
|
||||
|
||||
// Using Expiry Func
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.OneMonth, InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
|
||||
// Using DateTime
|
||||
SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(
|
||||
Insight.Price(symbol, Expiry.OneMonth(generatedTime), InsightDirection.Up),
|
||||
generatedTimeUtc,
|
||||
new DateTime(year, month, day, hour, minute, 0).ConvertToUtc(TimeZones.NewYork));
|
||||
}
|
||||
|
||||
private void SetPeriodAndCloseTimeUsingExpiryFuncOrDateTime(Insight insight, DateTime generatedTimeUtc, DateTime expected)
|
||||
{
|
||||
var symbol = insight.Symbol;
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
insight.SetPeriodAndCloseTime(exchangeHours);
|
||||
|
||||
Assert.AreEqual(expected, insight.CloseTimeUtc);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void ComputeCloseTimeHandlesFractionalDays()
|
||||
{
|
||||
var symbol = Symbols.SPY;
|
||||
// Friday @ 3PM + 2.5 days => Wednesday @ 12:45 by counting 2 dates (Mon, Tues@3PM) and then half a trading day (+3.25hrs) => Wed@11:45AM
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 03, 12+3, 0, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var expectedClosedTimeUtc = new DateTime(2018, 08, 08, 11, 45, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var actualCloseTimeUtc = Insight.ComputeCloseTime(exchangeHours, generatedTimeUtc, TimeSpan.FromDays(2.5));
|
||||
Assert.AreEqual(expectedClosedTimeUtc, actualCloseTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComputeCloseTimeHandlesFractionalHours()
|
||||
{
|
||||
var symbol = Symbols.SPY;
|
||||
// Friday @ 3PM + 2.5 hours => Monday @ 11:00 (1 hr on Friday, 1.5 hours on Monday)
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 03, 12 + 3, 0, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var expectedClosedTimeUtc = new DateTime(2018, 08, 06, 11, 0, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var actualCloseTimeUtc = Insight.ComputeCloseTime(exchangeHours, generatedTimeUtc, TimeSpan.FromHours(2.5));
|
||||
Assert.AreEqual(expectedClosedTimeUtc, actualCloseTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComputeCloseHandlesOffsetHourOverMarketClosuresUsingTimeSpan()
|
||||
{
|
||||
var symbol = Symbols.SPY;
|
||||
// Friday @ 3:59PM + 1 hours => Monday @ 10:29 (1 min on Friday, 59 min on Monday)
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 03, 12 + 3, 59, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var expectedClosedTimeUtc = new DateTime(2018, 08, 06, 10, 29, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var actualCloseTimeUtc = Insight.ComputeCloseTime(exchangeHours, generatedTimeUtc, TimeSpan.FromHours(1));
|
||||
Assert.AreEqual(expectedClosedTimeUtc, actualCloseTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComputeCloseHandlesOffsetHourOverMarketClosuresUsingResolutionBarCount()
|
||||
{
|
||||
var symbol = Symbols.SPY;
|
||||
// Friday @ 3:59PM + 1 hours => Monday @ 10:29 (1 min on Friday, 59 min on Monday)
|
||||
var generatedTimeUtc = new DateTime(2018, 08, 03, 12 + 3, 59, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var expectedClosedTimeUtc = new DateTime(2018, 08, 06, 10, 29, 0).ConvertToUtc(TimeZones.NewYork);
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
var actualCloseTimeUtc = Insight.ComputeCloseTime(exchangeHours, generatedTimeUtc, Resolution.Hour, 1);
|
||||
Assert.AreEqual(expectedClosedTimeUtc, actualCloseTimeUtc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetPeriodAndCloseTimeThrowsWhenGeneratedTimeUtcNotSet()
|
||||
{
|
||||
var insight = Insight.Price(Symbols.SPY, Time.OneDay, InsightDirection.Up);
|
||||
var exchangeHours = SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork);
|
||||
|
||||
Assert.That(() => insight.SetPeriodAndCloseTime(exchangeHours),
|
||||
Throws.InvalidOperationException);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetPeriodAndCloseTimeDoesNotThrowWhenGeneratedTimeUtcIsSet()
|
||||
{
|
||||
var insight = Insight.Price(Symbols.SPY, Time.OneDay, InsightDirection.Up);
|
||||
var exchangeHours = SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork);
|
||||
|
||||
insight.GeneratedTimeUtc = new DateTime(2018, 08, 07, 00, 33, 00).ConvertToUtc(TimeZones.NewYork);
|
||||
Assert.That(() => insight.SetPeriodAndCloseTime(exchangeHours),
|
||||
Throws.Nothing);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsExpiredUsesOpenIntervalSemantics()
|
||||
{
|
||||
var generatedTime = new DateTime(2000, 01, 01);
|
||||
var insight = Insight.Price(Symbols.SPY, Time.OneMinute, InsightDirection.Up);
|
||||
insight.GeneratedTimeUtc = generatedTime;
|
||||
insight.CloseTimeUtc = insight.GeneratedTimeUtc + insight.Period;
|
||||
|
||||
Assert.IsFalse(insight.IsExpired(insight.CloseTimeUtc));
|
||||
Assert.IsTrue(insight.IsExpired(insight.CloseTimeUtc.AddTicks(1)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsActiveUsesClosedIntervalSemantics()
|
||||
{
|
||||
var generatedTime = new DateTime(2000, 01, 01);
|
||||
var insight = Insight.Price(Symbols.SPY, Time.OneMinute, InsightDirection.Up);
|
||||
insight.GeneratedTimeUtc = generatedTime;
|
||||
insight.CloseTimeUtc = insight.GeneratedTimeUtc + insight.Period;
|
||||
|
||||
Assert.IsTrue(insight.IsActive(insight.CloseTimeUtc));
|
||||
Assert.IsFalse(insight.IsActive(insight.CloseTimeUtc.AddTicks(1)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConstructorsSetsCorrectValues()
|
||||
{
|
||||
var tag = "Insight Tag";
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
var insight1 = new Insight(Symbols.SPY, Time.OneDay, InsightType.Price, InsightDirection.Up, tag);
|
||||
AssertInsigthValues(insight1, Symbols.SPY, Time.OneDay, InsightType.Price, InsightDirection.Up, null, null, null, null, tag,
|
||||
default(DateTime), default(DateTime));
|
||||
|
||||
var insight2 = new Insight(Symbols.SPY, Time.OneDay, InsightType.Price, InsightDirection.Up, 1.0, 0.5, "Model", 0.25, tag);
|
||||
AssertInsigthValues(insight2, Symbols.SPY, Time.OneDay, InsightType.Price, InsightDirection.Up, 1.0, 0.5, "Model", 0.25, tag,
|
||||
default(DateTime), default(DateTime));
|
||||
|
||||
var insight3 = new Insight(Symbols.SPY, time => time.AddDays(1), InsightType.Price, InsightDirection.Up, tag);
|
||||
AssertInsigthValues(insight3, Symbols.SPY, new TimeSpan(0), InsightType.Price, InsightDirection.Up, null, null, null, null, tag,
|
||||
default(DateTime), default(DateTime));
|
||||
|
||||
var insight4 = new Insight(Symbols.SPY, time => time.AddDays(1), InsightType.Price, InsightDirection.Up, 1.0, 0.5, "Model", 0.25, tag);
|
||||
AssertInsigthValues(insight4, Symbols.SPY, new TimeSpan(0), InsightType.Price, InsightDirection.Up, 1.0, 0.5, "Model", 0.25, tag,
|
||||
default(DateTime), default(DateTime));
|
||||
|
||||
var generatedTime = new DateTime(2024, 05, 23);
|
||||
var insight5 = new Insight(generatedTime, Symbols.SPY, Time.OneDay, InsightType.Price, InsightDirection.Up, 1.0, 0.5, "Model", 0.25, tag);
|
||||
AssertInsigthValues(insight5, Symbols.SPY, Time.OneDay, InsightType.Price, InsightDirection.Up, 1.0, 0.5, "Model", 0.25, tag,
|
||||
generatedTime, generatedTime + Time.OneDay);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static void AssertInsigthValues(Insight insight, Symbol symbol, TimeSpan period, InsightType type, InsightDirection direction,
|
||||
double? magnitude, double? confidence, string sourceModel, double? weight, string tag, DateTime generatedTimeUtc, DateTime closeTimeUtc)
|
||||
{
|
||||
Assert.AreEqual(symbol, insight.Symbol);
|
||||
Assert.AreEqual(period, insight.Period);
|
||||
Assert.AreEqual(type, insight.Type);
|
||||
Assert.AreEqual(direction, insight.Direction);
|
||||
Assert.AreEqual(magnitude, insight.Magnitude);
|
||||
Assert.AreEqual(confidence, insight.Confidence);
|
||||
Assert.AreEqual(sourceModel, insight.SourceModel);
|
||||
Assert.AreEqual(weight, insight.Weight);
|
||||
Assert.AreEqual(tag, insight.Tag);
|
||||
Assert.AreEqual(generatedTimeUtc, insight.GeneratedTimeUtc);
|
||||
Assert.AreEqual(closeTimeUtc, insight.CloseTimeUtc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework
|
||||
{
|
||||
[TestFixture]
|
||||
public class NotifiedSecurityChangesTests
|
||||
{
|
||||
[Test]
|
||||
public void UpdateCallsDisposeOnDisposableInstances()
|
||||
{
|
||||
var disposable = new Disposable(Symbols.SPY);
|
||||
var dictionary = new Dictionary<Symbol, Disposable>
|
||||
{
|
||||
[Symbols.SPY] = disposable
|
||||
};
|
||||
|
||||
var SPY = new Equity(
|
||||
Symbols.SPY,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
|
||||
new Cash("USD", 1m, 1m),
|
||||
SymbolProperties.GetDefault("USD"),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
new RegisteredSecurityDataTypesProvider(),
|
||||
new SecurityCache()
|
||||
);
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(SPY);
|
||||
NotifiedSecurityChanges.UpdateDictionary(dictionary, changes,
|
||||
security => security.Symbol,
|
||||
security => new Disposable(security.Symbol)
|
||||
);
|
||||
|
||||
Assert.IsTrue(disposable.Disposed);
|
||||
}
|
||||
|
||||
private class Disposable : IDisposable
|
||||
{
|
||||
public bool Disposed { get; private set; }
|
||||
public Symbol Symbol { get; private set; }
|
||||
|
||||
public Disposable(Symbol symbol)
|
||||
{
|
||||
Symbol = symbol;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+516
@@ -0,0 +1,516 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class AccumulativeInsightPortfolioConstructionModelTests
|
||||
{
|
||||
private QCAlgorithm _algorithm;
|
||||
private const decimal _startingCash = 100000;
|
||||
private const double DefaultPercent = 0.03;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
|
||||
var prices = new Dictionary<Symbol, decimal>
|
||||
{
|
||||
{ Symbol.Create("AIG", SecurityType.Equity, Market.USA), 55.22m },
|
||||
{ Symbol.Create("IBM", SecurityType.Equity, Market.USA), 145.17m },
|
||||
{ Symbol.Create("SPY", SecurityType.Equity, Market.USA), 281.79m },
|
||||
};
|
||||
|
||||
foreach (var kvp in prices)
|
||||
{
|
||||
var symbol = kvp.Key;
|
||||
var security = GetSecurity(symbol);
|
||||
security.SetMarketPrice(new Tick(_algorithm.Time, symbol, kvp.Value, kvp.Value));
|
||||
_algorithm.Securities.Add(symbol, security);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void EmptyInsightsReturnsEmptyTargets(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
public void InsightsReturnsTargetsConsistentWithDirection(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)DefaultPercent;
|
||||
var expectedTargets = _algorithm.Securities
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)direction
|
||||
* Math.Floor(amount / x.Value.Price)));
|
||||
|
||||
var insights = _algorithm.Securities.Keys.Select(x => GetInsight(x, direction, _algorithm.UtcTime));
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets( expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void LongTermInsightCanceledByNew(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// First emit long term insight
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, -1m * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emits short term insight to cancel long
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Up, _algorithm.UtcTime, Time.OneMinute) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, 0) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emit empty insights array, short term insight expires but should stay -1 since long term insight is still valid
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(1.1));
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, -1m * (decimal)DefaultPercent) };
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
|
||||
// should stay 0 *after* the long expires
|
||||
SetUtcTime(_algorithm.Time.AddYears(1));
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, 0) };
|
||||
|
||||
actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
public void LongTermInsightAccumulatesByNew(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// First emit long term insight
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emits short term insight to add long
|
||||
SetUtcTime(_algorithm.UtcTime.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, Time.OneMinute) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * 2 * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emit empty insights array, should return to nomral after the long expires
|
||||
SetUtcTime(_algorithm.UtcTime.AddMinutes(1.1));
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
public void FlatUndoesAccumulation(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// First emit long term insight
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emits insight to add to portfolio
|
||||
SetUtcTime(_algorithm.UtcTime.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * 2 * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emits flat insight
|
||||
SetUtcTime(_algorithm.UtcTime.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Flat, _algorithm.UtcTime) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// now we should reach 0 percent
|
||||
insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Flat, _algorithm.UtcTime) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, 0) };
|
||||
|
||||
AssertTargets(expectedTargets, targets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
public void InsightExpirationUndoesAccumulationBySteps(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// First emit long term insight
|
||||
SetUtcTime(_algorithm.Time);
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, TimeSpan.FromMinutes(10)) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// One minute later, emits insight to add to portfolio
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, TimeSpan.FromMinutes(10)) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * 2 * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// the first insight should expire
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(10));
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// the second insight should expire
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(1));
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, 0) };
|
||||
|
||||
AssertTargets(expectedTargets, targets);
|
||||
}
|
||||
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
public void RespectsRebalancingPeriod(Language language, InsightDirection direction)
|
||||
{
|
||||
PortfolioConstructionModel model = new AccumulativeInsightPortfolioConstructionModel(Resolution.Daily);
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = nameof(AccumulativeInsightPortfolioConstructionModel);
|
||||
dynamic instance = Py.Import(name).GetAttr(name);
|
||||
model = new PortfolioConstructionModelPythonWrapper(instance(Resolution.Daily));
|
||||
}
|
||||
}
|
||||
|
||||
model.RebalanceOnSecurityChanges = false;
|
||||
model.RebalanceOnInsightChanges = false;
|
||||
|
||||
SetUtcTime(new DateTime(2018, 7, 31));
|
||||
// First emit long term insight
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, TimeSpan.FromDays(10)) };
|
||||
AssertTargets(new List<IPortfolioTarget>(), model.CreateTargets(_algorithm, insights).ToList());
|
||||
|
||||
// One minute later, emits insight to add to portfolio
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, TimeSpan.FromMinutes(10)) };
|
||||
AssertTargets(new List<IPortfolioTarget>(), model.CreateTargets(_algorithm, insights));
|
||||
|
||||
// the second insight should expire
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(1));
|
||||
AssertTargets(new List<IPortfolioTarget>(), model.CreateTargets(_algorithm, insights));
|
||||
|
||||
// the rebalancing period is due and the first insight is still valid
|
||||
SetUtcTime(_algorithm.Time.AddDays(1));
|
||||
var targets = model.CreateTargets(_algorithm, new Insight[0]);
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// the rebalancing period is due and no insight is valid
|
||||
SetUtcTime(_algorithm.Time.AddDays(10));
|
||||
AssertTargets(
|
||||
new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, 0) },
|
||||
model.CreateTargets(_algorithm, new Insight[0]));
|
||||
|
||||
AssertTargets(new List<IPortfolioTarget>(), model.CreateTargets(_algorithm, new Insight[0]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
public void InsightExpirationUndoesAccumulation(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// First emit long term insight
|
||||
SetUtcTime(_algorithm.Time);
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, TimeSpan.FromMinutes(10)),
|
||||
GetInsight(Symbols.SPY, direction, _algorithm.UtcTime, TimeSpan.FromMinutes(10))
|
||||
};
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, (int)direction * 1m * 2 * (decimal)DefaultPercent) };
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// both insights should expire
|
||||
SetUtcTime(_algorithm.Time.AddMinutes(11));
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]).ToList();
|
||||
|
||||
expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, 0) };
|
||||
|
||||
AssertTargets(expectedTargets, targets);
|
||||
|
||||
// we expect no target
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]).ToList();
|
||||
AssertTargets(new List<IPortfolioTarget>(), targets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void WeightsProportionally(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime),
|
||||
GetInsight(Symbol.Create("IBM", SecurityType.Equity, Market.USA),
|
||||
InsightDirection.Down, _algorithm.UtcTime)
|
||||
};
|
||||
|
||||
// they will each share, proportionally, the total portfolio value
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)DefaultPercent;
|
||||
|
||||
var expectedTargets = _algorithm.Securities.Where(pair => insights.Any(insight => pair.Key == insight.Symbol))
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)InsightDirection.Down * Math.Floor(amount / x.Value.Price)));
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(2, actualTargets.Count);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void GeneratesTargetsForInsightsWithNoConfidence(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime, confidence:null)
|
||||
};
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, actualTargets.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void GeneratesNormalTargetForZeroInsightConfidence(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime, confidence:0)
|
||||
};
|
||||
|
||||
// they will each share, proportionally, the total portfolio value
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)DefaultPercent;
|
||||
|
||||
var expectedTargets = _algorithm.Securities.Where(pair => insights.Any(insight => pair.Key == insight.Symbol))
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)InsightDirection.Down * Math.Floor(amount / x.Value.Price)));
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, PortfolioBias.Long)]
|
||||
[TestCase(Language.Python, PortfolioBias.Long)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Short)]
|
||||
[TestCase(Language.Python, PortfolioBias.Short)]
|
||||
public void PortfolioBiasIsRespected(Language language, PortfolioBias bias)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm, bias);
|
||||
var now = new DateTime(2018, 7, 31);
|
||||
SetUtcTime(now.ConvertFromUtc(_algorithm.TimeZone));
|
||||
var appl = _algorithm.AddEquity("AAPL");
|
||||
appl.SetMarketPrice(new Tick(now, appl.Symbol, 10, 10));
|
||||
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
spy.SetMarketPrice(new Tick(now, spy.Symbol, 20, 20));
|
||||
|
||||
var ibm = _algorithm.AddEquity("IBM");
|
||||
ibm.SetMarketPrice(new Tick(now, ibm.Symbol, 30, 30));
|
||||
|
||||
var aig = _algorithm.AddEquity("AIG");
|
||||
aig.SetMarketPrice(new Tick(now, aig.Symbol, 30, 30));
|
||||
|
||||
var qqq = _algorithm.AddEquity("QQQ");
|
||||
qqq.SetMarketPrice(new Tick(now, qqq.Symbol, 30, 30));
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
new Insight(now, appl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, 0.1d, null),
|
||||
new Insight(now, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Down, -0.1d, null),
|
||||
new Insight(now, ibm.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Down, 0d, null),
|
||||
new Insight(now, aig.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Down, -0.1d, null),
|
||||
new Insight(now, qqq.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, 0.1d, null)
|
||||
};
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(appl, spy, ibm, aig, qqq));
|
||||
|
||||
var createdValidTarget = false;
|
||||
_algorithm.Insights.AddRange(insights);
|
||||
foreach (var target in _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights))
|
||||
{
|
||||
QuantConnect.Logging.Log.Trace($"{target.Symbol}: {target.Quantity}");
|
||||
if (target.Quantity == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
createdValidTarget = true;
|
||||
Assert.AreEqual(Math.Sign((int)bias), Math.Sign(target.Quantity));
|
||||
}
|
||||
|
||||
Assert.IsTrue(createdValidTarget);
|
||||
}
|
||||
|
||||
private Security GetSecurity(Symbol symbol)
|
||||
{
|
||||
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
||||
return new Equity(
|
||||
symbol,
|
||||
exchangeHours,
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
}
|
||||
|
||||
private Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? confidence = DefaultPercent)
|
||||
{
|
||||
period ??= TimeSpan.FromDays(1);
|
||||
var insight = Insight.Price(symbol, period.Value, direction, confidence: confidence);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.CloseTimeUtc = generatedTimeUtc.Add(period.Value);
|
||||
_algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, PortfolioBias bias= PortfolioBias.LongShort)
|
||||
{
|
||||
algorithm.SetPortfolioConstruction(new AccumulativeInsightPortfolioConstructionModel((Func<DateTime,DateTime>)null, bias));
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = nameof(AccumulativeInsightPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)null).ToPython(), ((int)bias).ToPython());
|
||||
var model = new PortfolioConstructionModelPythonWrapper(instance);
|
||||
algorithm.SetPortfolioConstruction(model);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in _algorithm.Portfolio)
|
||||
{
|
||||
kvp.Value.SetHoldings(kvp.Value.Price, 0);
|
||||
}
|
||||
_algorithm.Portfolio.SetCash(_startingCash);
|
||||
SetUtcTime(new DateTime(2018, 7, 31));
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(_algorithm.Securities.Values.ToArray());
|
||||
algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
}
|
||||
|
||||
private void SetUtcTime(DateTime dateTime)
|
||||
{
|
||||
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
|
||||
}
|
||||
|
||||
private void AssertTargets(IEnumerable<IPortfolioTarget> expectedTargets, IEnumerable<IPortfolioTarget> actualTargets)
|
||||
{
|
||||
var list = actualTargets.ToList();
|
||||
Assert.AreEqual(expectedTargets.Count(), list.Count);
|
||||
|
||||
foreach (var expected in expectedTargets)
|
||||
{
|
||||
var actual = list.FirstOrDefault(x => x.Symbol == expected.Symbol);
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(expected.Quantity, actual.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 NodaTime;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public abstract class BaseWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
protected decimal StartingCash => 100000;
|
||||
|
||||
protected QCAlgorithm Algorithm { get; set; }
|
||||
|
||||
public virtual double? Weight => Algorithm.Securities.Count == 0 ? default(double) : 1d / Algorithm.Securities.Count;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public virtual void SetUp()
|
||||
{
|
||||
Algorithm = new QCAlgorithm();
|
||||
Algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(Algorithm));
|
||||
}
|
||||
[TearDown]
|
||||
public void TearDown() => Algorithm.Insights.Clear(Algorithm.Securities.Keys.ToArray());
|
||||
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void AutomaticallyRemoveInvestedWithoutNewInsights(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// Let's create a position for SPY
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Up, Algorithm.UtcTime) };
|
||||
|
||||
foreach (var target in Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights))
|
||||
{
|
||||
var holding = Algorithm.Portfolio[target.Symbol];
|
||||
holding.SetHoldings(holding.Price, target.Quantity);
|
||||
Algorithm.Portfolio.SetCash(StartingCash - holding.HoldingsValue);
|
||||
}
|
||||
|
||||
SetUtcTime(Algorithm.UtcTime.AddDays(2));
|
||||
|
||||
var expectedTargets = new List<IPortfolioTarget> { new PortfolioTarget(Symbols.SPY, 0) };
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DelistedSecurityEmitsFlatTargetWithoutNewInsights(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime) };
|
||||
var targets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(Algorithm.Securities[Symbols.SPY]);
|
||||
Algorithm.PortfolioConstruction.OnSecuritiesChanged(Algorithm, changes);
|
||||
|
||||
var expectedTargets = new List<IPortfolioTarget> { new PortfolioTarget(Symbols.SPY, 0) };
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotReturnTargetsIfSecurityPriceIsZero(Language language)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.AddEquity(Symbols.SPY.Value);
|
||||
algorithm.SetDateTime(DateTime.MinValue.ConvertToUtc(Algorithm.TimeZone));
|
||||
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Up, algorithm.UtcTime) };
|
||||
var actualTargets = algorithm.PortfolioConstruction.CreateTargets(algorithm, insights);
|
||||
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotThrowWithAlternativeOverloads(Language language)
|
||||
{
|
||||
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, Resolution.Minute));
|
||||
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, TimeSpan.FromDays(1)));
|
||||
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, Expiry.EndOfWeek));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void EmptyInsightsReturnsEmptyTargets(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, new Insight[0]);
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void LongTermInsightPreservesPosition(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// First emit long term insight
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime) };
|
||||
var targets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// One minute later, emits short term insight
|
||||
SetUtcTime(Algorithm.UtcTime.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Up, Algorithm.UtcTime, Time.OneMinute) };
|
||||
targets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// One minute later, emit empty insights array
|
||||
SetUtcTime(Algorithm.UtcTime.AddMinutes(1.1));
|
||||
|
||||
var expectedTargets = GetTargetsForSPY();
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public abstract void AutomaticallyRemoveInvestedWithNewInsights(Language language, InsightDirection direction);
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public abstract void DelistedSecurityEmitsFlatTargetWithNewInsights(Language language, InsightDirection direction);
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public abstract void FlatDirectionNotAccountedToAllocation(Language language, InsightDirection direction);
|
||||
|
||||
public abstract IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null);
|
||||
|
||||
public abstract Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? weight = 0.01);
|
||||
|
||||
public void AssertTargets(IEnumerable<IPortfolioTarget> expectedTargets, IEnumerable<IPortfolioTarget> actualTargets)
|
||||
{
|
||||
var list = actualTargets.ToList();
|
||||
Assert.AreEqual(expectedTargets.Count(), list.Count);
|
||||
|
||||
foreach (var expected in expectedTargets)
|
||||
{
|
||||
var actual = list.FirstOrDefault(x => x.Symbol == expected.Symbol);
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(expected.Quantity, actual.Quantity);
|
||||
}
|
||||
}
|
||||
|
||||
protected Security GetSecurity(Symbol symbol) =>
|
||||
new Equity(
|
||||
symbol,
|
||||
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
|
||||
public virtual List<IPortfolioTarget> GetTargetsForSPY()
|
||||
{
|
||||
return new List<IPortfolioTarget> { PortfolioTarget.Percent(Algorithm, Symbols.SPY, -1m) };
|
||||
}
|
||||
|
||||
protected void SetPortfolioConstruction(Language language, dynamic paramenter = null)
|
||||
{
|
||||
var model = GetPortfolioConstructionModel(language, paramenter ?? Resolution.Daily);
|
||||
Algorithm.SetPortfolioConstruction(model);
|
||||
|
||||
foreach (var kvp in Algorithm.Portfolio)
|
||||
{
|
||||
kvp.Value.SetHoldings(kvp.Value.Price, 0);
|
||||
}
|
||||
Algorithm.Portfolio.SetCash(StartingCash);
|
||||
SetUtcTime(new DateTime(2018, 7, 31));
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(Algorithm.Securities.Values.ToArray());
|
||||
Algorithm.PortfolioConstruction.OnSecuritiesChanged(Algorithm, changes);
|
||||
}
|
||||
|
||||
protected void SetUtcTime(DateTime dateTime) => Algorithm.SetDateTime(dateTime.ConvertToUtc(Algorithm.TimeZone));
|
||||
}
|
||||
}
|
||||
+546
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* 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 Accord.Math;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class BlackLittermanOptimizationPortfolioConstructionModelTests
|
||||
{
|
||||
private QCAlgorithm _algorithm;
|
||||
private Insight[] _view1Insights;
|
||||
private Insight[] _view2Insights;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
|
||||
SetUtcTime(new DateTime(2018, 8, 7));
|
||||
|
||||
// Germany will outperform the other European markets by 5%
|
||||
_view1Insights = new[]
|
||||
{
|
||||
GetInsight("View 1", "AUS", 0),
|
||||
GetInsight("View 1", "CAN", 0),
|
||||
GetInsight("View 1", "FRA", -0.01475),
|
||||
GetInsight("View 1", "GER", 0.05000),
|
||||
GetInsight("View 1", "JAP", 0),
|
||||
GetInsight("View 1", "UK" , -0.03525),
|
||||
GetInsight("View 1", "USA", 0)
|
||||
};
|
||||
|
||||
// Canadian Equities will outperform US equities by 3 %
|
||||
_view2Insights = new[]
|
||||
{
|
||||
GetInsight("View 2", "AUS", 0),
|
||||
GetInsight("View 2", "CAN", 0.03),
|
||||
GetInsight("View 2", "FRA", 0),
|
||||
GetInsight("View 2", "GER", 0),
|
||||
GetInsight("View 2", "JAP", 0),
|
||||
GetInsight("View 2", "UK" , 0),
|
||||
GetInsight("View 2", "USA", -0.03)
|
||||
};
|
||||
|
||||
foreach (var symbol in _view1Insights.Select(x => x.Symbol))
|
||||
{
|
||||
var security = GetSecurity(symbol, Resolution.Daily);
|
||||
security.SetMarketPrice(new Tick(_algorithm.Time, symbol, 1m, 1m));
|
||||
_algorithm.Securities.Add(symbol, security);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void EmptyInsightsReturnsEmptyTargets(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
var insights = new Insight[0];
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void OneViewTest(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// Add outdated insight to check if only the latest one was considered
|
||||
var outdatedInsight = GetInsight("View 1", "CAN", 0.05);
|
||||
outdatedInsight.GeneratedTimeUtc -= TimeSpan.FromHours(1);
|
||||
outdatedInsight.CloseTimeUtc -= TimeSpan.FromHours(1);
|
||||
|
||||
// Results from http://www.blacklitterman.org/code/hl_py.html (View 1)
|
||||
var expectedTargets = new[]
|
||||
{
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("AUS"), 0.0152381),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("CAN"), 0.02095238),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("FRA"), -0.03948465),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("GER"), 0.35410454),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("JAP"), 0.11047619),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("UK"), -0.09461989),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("USA"), 0.58571429)
|
||||
};
|
||||
|
||||
var insights = _view1Insights.Concat(new[] { outdatedInsight }).ToArray();
|
||||
Clear();
|
||||
_algorithm.Insights.AddRange(insights);
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
|
||||
Assert.AreEqual(expectedTargets.Length, actualTargets.Count());
|
||||
|
||||
foreach (var expected in expectedTargets)
|
||||
{
|
||||
var actual = actualTargets.FirstOrDefault(x => x.Symbol == expected.Symbol);
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(expected.Quantity, actual.Quantity);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void TwoViewsTest(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// Results from http://www.blacklitterman.org/code/hl_py.html (View 1+2)
|
||||
var expectedTargets = new[]
|
||||
{
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("AUS"), 0.0152381),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("CAN"), 0.41863571),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("FRA"), -0.03409321),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("GER"), 0.33582847),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("JAP"), 0.11047619),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("UK"), -0.08173526),
|
||||
PortfolioTarget.Percent(_algorithm, GetSymbol("USA"), 0.18803095)
|
||||
};
|
||||
|
||||
// Add outdated insight to check if only the latest one was considered
|
||||
var outdatedInsight = GetInsight("View 2", "USA", 0.05);
|
||||
outdatedInsight.GeneratedTimeUtc -= TimeSpan.FromHours(1);
|
||||
outdatedInsight.CloseTimeUtc -= TimeSpan.FromHours(1);
|
||||
|
||||
var insights = _view1Insights.Concat(_view2Insights).Concat(new[] { outdatedInsight });
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights.ToArray());
|
||||
|
||||
Assert.AreEqual(expectedTargets.Length, actualTargets.Count());
|
||||
|
||||
foreach (var expected in expectedTargets)
|
||||
{
|
||||
var actual = actualTargets.FirstOrDefault(x => x.Symbol == expected.Symbol);
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(expected.Quantity, actual.Quantity);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void OneViewDimensionTest(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
double[,] P;
|
||||
double[] Q;
|
||||
((BLOPCM)_algorithm.PortfolioConstruction).TestTryGetViews(_view1Insights, out P, out Q);
|
||||
|
||||
Assert.AreEqual(P.GetLength(0), 1);
|
||||
Assert.AreEqual(P.GetLength(1), 7);
|
||||
Assert.AreEqual(Q.GetLength(0), 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = nameof(BLOPCM);
|
||||
var instance = PyModule.FromString(name, GetPythonBLOPCM()).GetAttr(name).Invoke(((int)PortfolioBias.LongShort).ToPython());
|
||||
var result = PyList.AsList(instance.InvokeMethod("get_views", _view1Insights.ToPython()));
|
||||
Assert.AreEqual(result[0].Length(), 1);
|
||||
Assert.AreEqual(result[0][0].Length(), 7);
|
||||
Assert.AreEqual(result[1].Length(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void TwoViewsDimensionTest(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// Test if a symbol has no view in one of the source models
|
||||
var insights = _view1Insights.Concat(_view2Insights.Skip(1)).ToList();
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
double[,] P;
|
||||
double[] Q;
|
||||
((BLOPCM)_algorithm.PortfolioConstruction).TestTryGetViews(insights, out P, out Q);
|
||||
|
||||
Assert.AreEqual(P.GetLength(0), 2);
|
||||
Assert.AreEqual(P.GetLength(1), 7);
|
||||
Assert.AreEqual(Q.GetLength(0), 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = nameof(BLOPCM);
|
||||
var instance = PyModule.FromString(name, GetPythonBLOPCM()).GetAttr(name).Invoke(((int)PortfolioBias.LongShort).ToPython());
|
||||
var result = PyList.AsList(instance.InvokeMethod("get_views", insights.ToPython()));
|
||||
Assert.AreEqual(result[0].Length(), 2);
|
||||
Assert.AreEqual(result[0][0].Length(), 7);
|
||||
Assert.AreEqual(result[1].Length(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, 11, true)]
|
||||
[TestCase(Language.CSharp, -11, true)]
|
||||
[TestCase(Language.CSharp, 0.001d, true)]
|
||||
[TestCase(Language.CSharp, -0.001d, true)]
|
||||
[TestCase(Language.CSharp, 0.1, false)]
|
||||
[TestCase(Language.CSharp, -0.1, false)]
|
||||
[TestCase(Language.CSharp, 0.011d, false)]
|
||||
[TestCase(Language.CSharp, -0.011d, false)]
|
||||
[TestCase(Language.CSharp, 0, true)]
|
||||
[TestCase(Language.Python, 0, true)]
|
||||
[TestCase(Language.Python, 11, true)]
|
||||
[TestCase(Language.Python, -11, true)]
|
||||
[TestCase(Language.Python, 0.001d, true)]
|
||||
[TestCase(Language.Python, -0.001d, true)]
|
||||
[TestCase(Language.Python, 0.1, false)]
|
||||
[TestCase(Language.Python, -0.1, false)]
|
||||
[TestCase(Language.Python, 0.011d, false)]
|
||||
[TestCase(Language.Python, -0.011d, false)]
|
||||
public void IgnoresInsightsWithInvalidMagnitudeValue(Language language, double magnitude, bool expectZero)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
_algorithm.Settings.MaxAbsolutePortfolioTargetPercentage = 10;
|
||||
_algorithm.Settings.MinAbsolutePortfolioTargetPercentage = 0.01m;
|
||||
Clear();
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight("View 1", "AUS", magnitude),
|
||||
GetInsight("View 1", "CAN", magnitude),
|
||||
GetInsight("View 1", "FRA", magnitude),
|
||||
GetInsight("View 1", "GER", magnitude),
|
||||
GetInsight("View 1", "JAP", magnitude),
|
||||
GetInsight("View 1", "UK" , magnitude),
|
||||
GetInsight("View 1", "USA", magnitude)
|
||||
};
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
|
||||
if (expectZero)
|
||||
{
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreNotEqual(0, actualTargets.Count());
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, PortfolioBias.Long)]
|
||||
[TestCase(Language.Python, PortfolioBias.Long)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Short)]
|
||||
[TestCase(Language.Python, PortfolioBias.Short)]
|
||||
public void PortfolioBiasIsRespected(Language language, PortfolioBias bias)
|
||||
{
|
||||
SetPortfolioConstruction(language, bias);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight("View 1", "AUS", -10.1),
|
||||
GetInsight("View 1", "CAN", -0.1),
|
||||
GetInsight("View 1", "FRA", 0.1),
|
||||
GetInsight("View 1", "GER", -0.1),
|
||||
GetInsight("View 1", "JAP", -0.1),
|
||||
GetInsight("View 1", "UK" , 0.1),
|
||||
GetInsight("View 1", "USA", -0.1)
|
||||
};
|
||||
|
||||
var createdValidTarget = false;
|
||||
foreach (var target in _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights))
|
||||
{
|
||||
QuantConnect.Logging.Log.Trace($"{target.Symbol}: {target.Quantity}");
|
||||
if (target.Quantity == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
createdValidTarget = true;
|
||||
Assert.AreEqual(Math.Sign((int)bias), Math.Sign(target.Quantity));
|
||||
}
|
||||
|
||||
Assert.IsTrue(createdValidTarget);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NewSymbolPortfolioConstructionModelDoesNotThrow()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var timezone = algorithm.TimeZone;
|
||||
algorithm.SetDateTime(new DateTime(2018, 8, 7).ConvertToUtc(timezone));
|
||||
algorithm.SetPortfolioConstruction(new NewSymbolPortfolioConstructionModel());
|
||||
|
||||
var spySymbol = Symbols.SPY;
|
||||
var spy = GetSecurity(spySymbol, Resolution.Daily);
|
||||
|
||||
spy.SetMarketPrice(new Tick(algorithm.Time, spySymbol, 1m, 1m));
|
||||
algorithm.Securities.Add(spySymbol, spy);
|
||||
|
||||
algorithm.PortfolioConstruction.OnSecuritiesChanged(algorithm, SecurityChangesTests.AddedNonInternal(spy));
|
||||
|
||||
var insights = new[] { Insight.Price(spySymbol, Time.OneMinute, InsightDirection.Up, .1) };
|
||||
|
||||
Assert.DoesNotThrow(() => algorithm.PortfolioConstruction.CreateTargets(algorithm, insights));
|
||||
|
||||
algorithm.SetDateTime(algorithm.Time.AddDays(1));
|
||||
|
||||
var aaplSymbol = Symbols.AAPL;
|
||||
var aapl = GetSecurity(spySymbol, Resolution.Daily);
|
||||
|
||||
aapl.SetMarketPrice(new Tick(algorithm.Time, aaplSymbol, 1m, 1m));
|
||||
algorithm.Securities.Add(aaplSymbol, aapl);
|
||||
|
||||
algorithm.PortfolioConstruction.OnSecuritiesChanged(algorithm, SecurityChangesTests.AddedNonInternal(aapl));
|
||||
|
||||
insights = new[] { spySymbol, aaplSymbol }
|
||||
.Select(x => Insight.Price(x, Time.OneMinute, InsightDirection.Up, .1)).ToArray();
|
||||
|
||||
Assert.DoesNotThrow(() => algorithm.PortfolioConstruction.CreateTargets(algorithm, insights));
|
||||
}
|
||||
|
||||
private Security GetSecurity(Symbol symbol, Resolution resolution)
|
||||
{
|
||||
var timezone = _algorithm.TimeZone;
|
||||
var exchangeHours = SecurityExchangeHours.AlwaysOpen(timezone);
|
||||
var config = new SubscriptionDataConfig(typeof(TradeBar), symbol, resolution, timezone, timezone, true, false, false);
|
||||
return new Security(
|
||||
exchangeHours,
|
||||
config,
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
}
|
||||
|
||||
private Symbol GetSymbol(string ticker) => Symbol.Create(ticker, SecurityType.Equity, Market.USA);
|
||||
|
||||
private Insight GetInsight(string SourceModel, string ticker, double magnitude)
|
||||
{
|
||||
var period = Time.OneDay;
|
||||
var direction = (InsightDirection)Math.Sign(magnitude);
|
||||
var insight = Insight.Price(GetSymbol(ticker), period, direction, magnitude, sourceModel: SourceModel);
|
||||
insight.GeneratedTimeUtc = _algorithm.UtcTime;
|
||||
insight.CloseTimeUtc = _algorithm.UtcTime.Add(insight.Period);
|
||||
_algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
private void SetPortfolioConstruction(Language language, PortfolioBias portfolioBias = PortfolioBias.LongShort)
|
||||
{
|
||||
_algorithm.SetPortfolioConstruction(new BLOPCM(new UnconstrainedMeanVariancePortfolioOptimizer(), portfolioBias));
|
||||
if (language == Language.Python)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = nameof(BLOPCM);
|
||||
var instance = PyModule.FromString(name, GetPythonBLOPCM()).GetAttr(name).Invoke(((int)portfolioBias).ToPython());
|
||||
var model = new PortfolioConstructionModelPythonWrapper(instance);
|
||||
_algorithm.SetPortfolioConstruction(model);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Ignore(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(_algorithm.Securities.Values.ToList().ToArray());
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
}
|
||||
|
||||
private void SetUtcTime(DateTime dateTime)
|
||||
{
|
||||
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
|
||||
}
|
||||
|
||||
private class BLOPCM : BlackLittermanOptimizationPortfolioConstructionModel
|
||||
{
|
||||
public BLOPCM(IPortfolioOptimizer optimizer, PortfolioBias portfolioBias)
|
||||
: base(optimizer: optimizer, portfolioBias: portfolioBias)
|
||||
{
|
||||
}
|
||||
|
||||
public override double[] GetEquilibriumReturns(double[,] returns, out double[,] Σ)
|
||||
{
|
||||
// Take the values from He & Litterman, 1999.
|
||||
var C = new[,]
|
||||
{
|
||||
{ 1.000, 0.488, 0.478, 0.515, 0.439, 0.512, 0.491 },
|
||||
{ 0.488, 1.000, 0.664, 0.655, 0.310, 0.608, 0.779 },
|
||||
{ 0.478, 0.664, 1.000, 0.861, 0.355, 0.783, 0.668 },
|
||||
{ 0.515, 0.655, 0.861, 1.000, 0.354, 0.777, 0.653 },
|
||||
{ 0.439, 0.310, 0.355, 0.354, 1.000, 0.405, 0.306 },
|
||||
{ 0.512, 0.608, 0.783, 0.777, 0.405, 1.000, 0.652 },
|
||||
{ 0.491, 0.779, 0.668, 0.653, 0.306, 0.652, 1.000 }
|
||||
};
|
||||
var σ = new[] { 0.160, 0.203, 0.248, 0.271, 0.210, 0.200, 0.187 };
|
||||
var w = new[] { 0.016, 0.022, 0.052, 0.055, 0.116, 0.124, 0.615 };
|
||||
var delta = 2.5;
|
||||
|
||||
// Equilibrium covariance matrix
|
||||
Σ = Elementwise.Multiply(C, σ.Outer(σ));
|
||||
return w.Dot(Σ.Multiply(delta));
|
||||
}
|
||||
|
||||
public bool TestTryGetViews(ICollection<Insight> insights, out double[,] P, out double[] Q)
|
||||
{
|
||||
return base.TryGetViews(insights, out P, out Q);
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPythonBLOPCM()
|
||||
{
|
||||
return @"
|
||||
from AlgorithmImports import *
|
||||
|
||||
from Portfolio.BlackLittermanOptimizationPortfolioConstructionModel import BlackLittermanOptimizationPortfolioConstructionModel
|
||||
from Portfolio.UnconstrainedMeanVariancePortfolioOptimizer import UnconstrainedMeanVariancePortfolioOptimizer
|
||||
|
||||
def GetSymbol(ticker):
|
||||
return str(Symbol.Create(ticker, SecurityType.Equity, Market.USA))
|
||||
|
||||
class BLOPCM(BlackLittermanOptimizationPortfolioConstructionModel):
|
||||
|
||||
def __init__(self, portfolioBias):
|
||||
super().__init__(portfolio_bias = portfolioBias, optimizer = UnconstrainedMeanVariancePortfolioOptimizer())
|
||||
|
||||
def get_equilibrium_return(self, returns):
|
||||
|
||||
# Take the values from He & Litterman, 1999.
|
||||
weq = np.array([0.016, 0.022, 0.052, 0.055, 0.116, 0.124, 0.615])
|
||||
C = np.array([[ 1.000, 0.488, 0.478, 0.515, 0.439, 0.512, 0.491],
|
||||
[0.488, 1.000, 0.664, 0.655, 0.310, 0.608, 0.779],
|
||||
[0.478, 0.664, 1.000, 0.861, 0.355, 0.783, 0.668],
|
||||
[0.515, 0.655, 0.861, 1.000, 0.354, 0.777, 0.653],
|
||||
[0.439, 0.310, 0.355, 0.354, 1.000, 0.405, 0.306],
|
||||
[0.512, 0.608, 0.783, 0.777, 0.405, 1.000, 0.652],
|
||||
[0.491, 0.779, 0.668, 0.653, 0.306, 0.652, 1.000]])
|
||||
Sigma = np.array([0.160, 0.203, 0.248, 0.271, 0.210, 0.200, 0.187])
|
||||
refPi = np.array([0.039, 0.069, 0.084, 0.090, 0.043, 0.068, 0.076])
|
||||
assets= [GetSymbol(x) for x in ['AUS', 'CAN', 'FRA', 'GER', 'JAP', 'UK', 'USA']]
|
||||
delta = 2.5
|
||||
|
||||
# Equilibrium covariance matrix
|
||||
V = np.multiply(np.outer(Sigma,Sigma), C)
|
||||
|
||||
return weq.dot(V * delta), pd.DataFrame(V, columns=assets, index=assets)
|
||||
|
||||
def on_securities_changed(self, algorithm, changes):
|
||||
pass";
|
||||
}
|
||||
|
||||
private void Clear() => _algorithm.Insights.Clear(_algorithm.Securities.Keys.ToArray());
|
||||
|
||||
private class NewSymbolPortfolioConstructionModel : BlackLittermanOptimizationPortfolioConstructionModel
|
||||
{
|
||||
private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
|
||||
|
||||
public override IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
|
||||
{
|
||||
// Updates the ReturnsSymbolData with insights
|
||||
foreach (var insight in insights)
|
||||
{
|
||||
ReturnsSymbolData symbolData;
|
||||
if (_symbolDataDict.TryGetValue(insight.Symbol, out symbolData))
|
||||
{
|
||||
symbolData.Add(algorithm.Time, .1m);
|
||||
}
|
||||
}
|
||||
|
||||
double[,] returns = null;
|
||||
Assert.DoesNotThrow(() => returns = _symbolDataDict.FormReturnsMatrix(insights.Select(x => x.Symbol)));
|
||||
|
||||
// Calculate posterior estimate of the mean and uncertainty in the mean
|
||||
double[,] Σ;
|
||||
var Π = GetEquilibriumReturns(returns, out Σ);
|
||||
|
||||
Assert.IsFalse(double.IsNaN(Π[0]));
|
||||
|
||||
return Enumerable.Empty<PortfolioTarget>();
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
const int period = 2;
|
||||
var reference = algorithm.Time.AddDays(-period);
|
||||
|
||||
foreach (var security in changes.AddedSecurities)
|
||||
{
|
||||
var symbol = security.Symbol;
|
||||
var symbolData = new ReturnsSymbolData(symbol, 1, period);
|
||||
|
||||
for (var i = 0; i <= period * 2; i++)
|
||||
{
|
||||
symbolData.Update(reference.AddDays(i), i);
|
||||
}
|
||||
|
||||
_symbolDataDict[symbol] = symbolData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class ConfidenceWeightedPortfolioConstructionModelTests
|
||||
{
|
||||
private QCAlgorithm _algorithm;
|
||||
private const decimal _startingCash = 100000;
|
||||
private const double Confidence = 0.01;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
|
||||
var prices = new Dictionary<Symbol, decimal>
|
||||
{
|
||||
{ Symbol.Create("AIG", SecurityType.Equity, Market.USA), 55.22m },
|
||||
{ Symbol.Create("IBM", SecurityType.Equity, Market.USA), 145.17m },
|
||||
{ Symbol.Create("SPY", SecurityType.Equity, Market.USA), 281.79m },
|
||||
};
|
||||
|
||||
foreach (var kvp in prices)
|
||||
{
|
||||
var symbol = kvp.Key;
|
||||
var security = GetSecurity(symbol);
|
||||
security.SetMarketPrice(new Tick(_algorithm.Time, symbol, kvp.Value, kvp.Value));
|
||||
_algorithm.Securities.Add(symbol, security);
|
||||
}
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown() => _algorithm.Insights.Clear(_algorithm.Securities.Keys.ToArray());
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void EmptyInsightsReturnsEmptyTargets(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public void InsightsReturnsTargetsConsistentWithDirection(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)Confidence;
|
||||
var expectedTargets = _algorithm.Securities
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)direction
|
||||
* Math.Floor(amount * (1 - _algorithm.Settings.FreePortfolioValuePercentage)
|
||||
/ x.Value.Price)));
|
||||
|
||||
var insights = _algorithm.Securities.Keys.Select(x => GetInsight(x, direction, _algorithm.UtcTime));
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public void FlatDirectionNotAccountedToAllocation(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// Modifying fee model for a constant one so numbers are simplified
|
||||
foreach (var security in _algorithm.Securities)
|
||||
{
|
||||
security.Value.FeeModel = new ConstantFeeModel(1);
|
||||
}
|
||||
|
||||
// Equity, minus $1 for fees
|
||||
var amount = (_algorithm.Portfolio.TotalPortfolioValue - 1 * (_algorithm.Securities.Count - 1)) * (decimal)Confidence;
|
||||
var expectedTargets = _algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since its insight will have flat direction
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction
|
||||
* Math.Floor(amount * (1 - _algorithm.Settings.FreePortfolioValuePercentage)
|
||||
/ x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
var insights = _algorithm.Securities.Keys.Select(x =>
|
||||
{
|
||||
// SPY insight direction is flat
|
||||
var actualDirection = x.Value == "SPY" ? InsightDirection.Flat : direction;
|
||||
return GetInsight(x, actualDirection, _algorithm.UtcTime);
|
||||
});
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public void AutomaticallyRemoveInvestedWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// Let's create a position for SPY
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, _algorithm.UtcTime) };
|
||||
|
||||
foreach (var target in _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights))
|
||||
{
|
||||
var holding = _algorithm.Portfolio[target.Symbol];
|
||||
holding.SetHoldings(holding.Price, target.Quantity);
|
||||
_algorithm.Portfolio.SetCash(_startingCash - holding.HoldingsValue);
|
||||
}
|
||||
|
||||
SetUtcTime(_algorithm.UtcTime.AddDays(2));
|
||||
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)Confidence;
|
||||
var expectedTargets = _algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since it will be removed
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction
|
||||
* Math.Floor(amount * (1 - _algorithm.Settings.FreePortfolioValuePercentage)
|
||||
/ x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = _algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, _algorithm.UtcTime)).ToArray();
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void AutomaticallyRemoveInvestedWithoutNewInsights(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// Let's create a position for SPY
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Up, _algorithm.UtcTime) };
|
||||
|
||||
foreach (var target in _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights))
|
||||
{
|
||||
var holding = _algorithm.Portfolio[target.Symbol];
|
||||
holding.SetHoldings(holding.Price, target.Quantity);
|
||||
_algorithm.Portfolio.SetCash(_startingCash - holding.HoldingsValue);
|
||||
}
|
||||
|
||||
SetUtcTime(_algorithm.UtcTime.AddDays(2));
|
||||
|
||||
var expectedTargets = new List<IPortfolioTarget> { new PortfolioTarget(Symbols.SPY, 0) };
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void LongTermInsightPreservesPosition(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// First emit long term insight
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// One minute later, emits short term insight
|
||||
SetUtcTime(_algorithm.UtcTime.AddMinutes(1));
|
||||
insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Up, _algorithm.UtcTime, Time.OneMinute) };
|
||||
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// One minute later, emit empty insights array
|
||||
SetUtcTime(_algorithm.UtcTime.AddMinutes(1.1));
|
||||
|
||||
var expectedTargets = new List<IPortfolioTarget> { PortfolioTarget.Percent(_algorithm, Symbols.SPY, -1m * (decimal)Confidence) };
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DelistedSecurityEmitsFlatTargetWithoutNewInsights(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(_algorithm.Securities[Symbols.SPY]);
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
|
||||
var expectedTargets = new List<IPortfolioTarget> { new PortfolioTarget(Symbols.SPY, 0) };
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new Insight[0]);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public void DelistedSecurityEmitsFlatTargetWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime) };
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// Removing SPY should clear the key in the insight collection
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(_algorithm.Securities[Symbols.SPY]);
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)Confidence;
|
||||
var expectedTargets = _algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since it will be removed
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction
|
||||
* Math.Floor(amount * (1 - _algorithm.Settings.FreePortfolioValuePercentage)
|
||||
/ x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = _algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, _algorithm.UtcTime)).ToArray();
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void WeightsProportionally(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
// create two insights whose confidences sums up to 2
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime, confidence:1),
|
||||
GetInsight(Symbol.Create("IBM", SecurityType.Equity, Market.USA),
|
||||
InsightDirection.Down, _algorithm.UtcTime, confidence:1)
|
||||
};
|
||||
|
||||
// they will each share, proportionally, the total portfolio value
|
||||
var amount = _algorithm.Portfolio.TotalPortfolioValue * (decimal)0.5;
|
||||
var expectedTargets = _algorithm.Securities.Where(pair => insights.Any(insight => pair.Key == insight.Symbol))
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)InsightDirection.Down
|
||||
* Math.Floor(amount * (1 - _algorithm.Settings.FreePortfolioValuePercentage)
|
||||
/ x.Value.Price)));
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(2, actualTargets.Count);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void GeneratesNoTargetsForInsightsWithNoConfidence(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime, confidence:null)
|
||||
};
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(0, actualTargets.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void GeneratesZeroTargetForZeroInsightConfidence(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, _algorithm);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, _algorithm.UtcTime, confidence:0)
|
||||
};
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, actualTargets.Count);
|
||||
AssertTargets(actualTargets, new[] {new PortfolioTarget(Symbols.SPY, 0)});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotThrowWithAlternativeOverloads(Language language)
|
||||
{
|
||||
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, Resolution.Minute));
|
||||
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, TimeSpan.FromDays(1)));
|
||||
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, Expiry.EndOfWeek));
|
||||
}
|
||||
|
||||
private Security GetSecurity(Symbol symbol)
|
||||
{
|
||||
var config = SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc);
|
||||
return new Equity(
|
||||
symbol,
|
||||
config,
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
}
|
||||
|
||||
private Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? confidence = Confidence)
|
||||
{
|
||||
period ??= TimeSpan.FromDays(1);
|
||||
var insight = Insight.Price(symbol, period.Value, direction, confidence: confidence);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.CloseTimeUtc = generatedTimeUtc.Add(period.Value);
|
||||
_algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, dynamic paramenter = null)
|
||||
{
|
||||
paramenter = paramenter ?? Resolution.Daily;
|
||||
algorithm.SetPortfolioConstruction(new ConfidenceWeightedPortfolioConstructionModel(paramenter));
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = nameof(ConfidenceWeightedPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython());
|
||||
var model = new PortfolioConstructionModelPythonWrapper(instance);
|
||||
algorithm.SetPortfolioConstruction(model);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in _algorithm.Portfolio)
|
||||
{
|
||||
kvp.Value.SetHoldings(kvp.Value.Price, 0);
|
||||
}
|
||||
_algorithm.Portfolio.SetCash(_startingCash);
|
||||
SetUtcTime(new DateTime(2018, 7, 31));
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(_algorithm.Securities.Values.ToArray());
|
||||
algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
}
|
||||
|
||||
private void SetUtcTime(DateTime dateTime)
|
||||
{
|
||||
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
|
||||
}
|
||||
|
||||
private void AssertTargets(IEnumerable<IPortfolioTarget> expectedTargets, IEnumerable<IPortfolioTarget> actualTargets)
|
||||
{
|
||||
var list = actualTargets.ToList();
|
||||
Assert.AreEqual(expectedTargets.Count(), list.Count);
|
||||
|
||||
foreach (var expected in expectedTargets)
|
||||
{
|
||||
var actual = list.FirstOrDefault(x => x.Symbol == expected.Symbol);
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(expected.Quantity, actual.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class EqualWeightingPortfolioConstructionModelTests : BaseWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
public override double? Weight => Algorithm.Securities.Count == 0 ? default(double) : 1d / Algorithm.Securities.Count;
|
||||
|
||||
public virtual PortfolioBias PortfolioBias => PortfolioBias.LongShort;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var prices = new Dictionary<Symbol, decimal>
|
||||
{
|
||||
{ Symbol.Create("AIG", SecurityType.Equity, Market.USA), 55.22m },
|
||||
{ Symbol.Create("IBM", SecurityType.Equity, Market.USA), 145.17m },
|
||||
{ Symbol.Create("SPY", SecurityType.Equity, Market.USA), 281.79m },
|
||||
};
|
||||
|
||||
foreach (var kvp in prices)
|
||||
{
|
||||
var symbol = kvp.Key;
|
||||
var security = GetSecurity(symbol);
|
||||
security.SetMarketPrice(new Tick(Algorithm.Time, symbol, kvp.Value, kvp.Value));
|
||||
Algorithm.Securities.Add(symbol, security);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void AutomaticallyRemoveInvestedWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
|
||||
// Let's create a position for SPY
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, Algorithm.UtcTime) };
|
||||
|
||||
foreach (var target in Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights))
|
||||
{
|
||||
var holding = Algorithm.Portfolio[target.Symbol];
|
||||
holding.SetHoldings(holding.Price, target.Quantity);
|
||||
Algorithm.Portfolio.SetCash(StartingCash - holding.HoldingsValue);
|
||||
}
|
||||
SetUtcTime(Algorithm.UtcTime.AddDays(2));
|
||||
|
||||
// Equity will be divided by all securities minus 1, since SPY is already invested and we want to remove it
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / (decimal)(1 / Weight - 1) *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
var expectedTargets = Algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since it will be removed
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction * Math.Floor(amount / x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = Algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, Algorithm.UtcTime)).ToArray();
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void DelistedSecurityEmitsFlatTargetWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime) };
|
||||
var targets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// Removing SPY should clear the key in the insight collection
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(Algorithm.Securities[Symbols.SPY]);
|
||||
Algorithm.PortfolioConstruction.OnSecuritiesChanged(Algorithm, changes);
|
||||
|
||||
// Equity will be divided by all securities minus 1, since SPY is already invested and we want to remove it
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / (decimal)(1 / Weight - 1) *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
var expectedTargets = Algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since it will be removed
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction * Math.Floor(amount / x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = Algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, Algorithm.UtcTime)).ToArray();
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void FlatDirectionNotAccountedToAllocation(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
|
||||
// Equity, minus $1 for fees, will be divided by all securities minus 1, since its insight will have flat direction
|
||||
var amount = (Algorithm.Portfolio.TotalPortfolioValue - 1 * (Algorithm.Securities.Count - 1)) * 1 /
|
||||
(decimal)((1 / Weight) - 1) * (1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
var expectedTargets = Algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since its insight will have flat direction
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction * Math.Floor(amount / x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
var insights = Algorithm.Securities.Keys.Select(x =>
|
||||
{
|
||||
// SPY insight direction is flat
|
||||
var actualDirection = x.Value == "SPY" ? InsightDirection.Flat : direction;
|
||||
return GetInsight(x, actualDirection, Algorithm.UtcTime);
|
||||
});
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, 1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, -1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down, 1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down, -1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat, 1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat, -1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, 1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, -1)]
|
||||
[TestCase(Language.Python, InsightDirection.Down, 1)]
|
||||
[TestCase(Language.Python, InsightDirection.Down, -1)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat, 1)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat, -1)]
|
||||
public virtual void InsightsReturnsTargetsConsistentWithDirection(Language language, InsightDirection direction, int weightSign)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
// Equity will be divided by all securities
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue * (decimal)Weight *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
var expectedTargets = Algorithm.Securities
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)direction * Math.Floor(amount / x.Value.Price)));
|
||||
|
||||
var insights = Algorithm.Securities.Keys.Select(x => GetInsight(x, direction, Algorithm.UtcTime, weight: weightSign * Weight));
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
public override Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? weight = 0.01)
|
||||
{
|
||||
period ??= TimeSpan.FromDays(1);
|
||||
var insight = Insight.Price(symbol, period.Value, direction, weight: Math.Max(0.01, Algorithm.Securities.Count));
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.CloseTimeUtc = generatedTimeUtc.Add(period.Value);
|
||||
Algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
public override IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new EqualWeightingPortfolioConstructionModel(paramenter);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(EqualWeightingPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class InsightWeightingPortfolioConstructionModelTests : EqualWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
private const double _weight = 0.01;
|
||||
|
||||
public override double? Weight => _weight;
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void WeightsProportionally(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// create two insights whose weights sums up to 2
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Up, Algorithm.UtcTime, weight:1),
|
||||
GetInsight(Symbol.Create("IBM", SecurityType.Equity, Market.USA),
|
||||
InsightDirection.Up, Algorithm.UtcTime, weight:1)
|
||||
};
|
||||
|
||||
// they will each share, proportionally, the total portfolio value
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue * (decimal)0.5;
|
||||
var expectedTargets = Algorithm.Securities.Where(pair => insights.Any(insight => pair.Key == insight.Symbol))
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)InsightDirection.Up
|
||||
* Math.Floor(amount * (1 - Algorithm.Settings.FreePortfolioValuePercentage)
|
||||
/ x.Value.Price)));
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(2, actualTargets.Count);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void GeneratesNoTargetsForInsightsWithNoWeight(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime, weight:null)
|
||||
};
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(0, actualTargets.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void GeneratesZeroTargetForZeroInsightWeight(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime, weight:0)
|
||||
};
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, actualTargets.Count);
|
||||
AssertTargets(actualTargets, new[] {new PortfolioTarget(Symbols.SPY, 0)});
|
||||
}
|
||||
|
||||
public override IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new InsightWeightingPortfolioConstructionModel(paramenter);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(InsightWeightingPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
public override Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? weight = _weight)
|
||||
{
|
||||
period ??= TimeSpan.FromDays(1);
|
||||
var insight = Insight.Price(symbol, period.Value, direction, weight: weight);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.CloseTimeUtc = generatedTimeUtc.Add(period.Value);
|
||||
Algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
public override List<IPortfolioTarget> GetTargetsForSPY()
|
||||
{
|
||||
return new List<IPortfolioTarget> { PortfolioTarget.Percent(Algorithm, Symbols.SPY, -_weight) };
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class LongOnlyEqualWeightingPortfolioConstructionModelTests : EqualWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
public override PortfolioBias PortfolioBias => PortfolioBias.Long;
|
||||
|
||||
public override IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new EqualWeightingPortfolioConstructionModel(paramenter, PortfolioBias.Long);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(EqualWeightingPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython(), ((int) PortfolioBias.Long).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
public override List<IPortfolioTarget> GetTargetsForSPY()
|
||||
{
|
||||
return new List<IPortfolioTarget> { PortfolioTarget.Percent(Algorithm, Symbols.SPY, 0m) };
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class LongOnlyInsightWeightingPortfolioConstructionModelTests : InsightWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
public override PortfolioBias PortfolioBias => PortfolioBias.Long;
|
||||
|
||||
public override IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new InsightWeightingPortfolioConstructionModel(paramenter, PortfolioBias.Long);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(InsightWeightingPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython(), ((int)PortfolioBias.Long).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
public override List<IPortfolioTarget> GetTargetsForSPY()
|
||||
{
|
||||
return new List<IPortfolioTarget> { PortfolioTarget.Percent(Algorithm, Symbols.SPY, 0m) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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 aaplicable 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 Accord.Math;
|
||||
using Accord.Statistics;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class MaximumSharpeRatioPortfolioOptimizerTests : PortfolioOptimizerTestsBase
|
||||
{
|
||||
[OneTimeSetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
HistoricalReturns = new List<double[,]>
|
||||
{
|
||||
new double[,] { { 0.02, -0.02, 0.28 }, { -0.50, -0.29, -0.13 }, { 0.81, 0.29, 0.31 }, { -0.03, -0.00, 0.01 } },
|
||||
new double[,] { { 0.10, 0.20, 0.4 }, { 0.12, 0.25, 0.4 }, { 0.11, 0.22, 0.4 } },
|
||||
new double[,] { { -0.19, 0.50, 0.45 }, { -0.62, -0.65, 0.07 }, { -0.14, 1.02, 0.01 }, { 0.00, -0.03, 0.01 } },
|
||||
new double[,] { { 0.46, 0.28, 0.58, 0.26, 0.14 }, { 0.52, 0.31, 0.43, 7.43, -0.00 }, { 0.13, 0.65, 0.52, 0.50, -0.08 }, { -0.41, -0.39, -0.28, -0.65, -0.20 }, { 0.77, 0.58, 0.58, 1.02, 0.03 }, { -0.03, -0.01, -0.01, -0.03, 0.07 } },
|
||||
new double[,] { { -0.50, -0.13 }, { 0.81, 0.31 }, { -0.02, 0.01 } },
|
||||
new double[,] { { 0.31, 0.25, 0.43 }, { 0.65, 0.60, 0.52 }, { -0.39, -0.22, -0.28 }, { 0.58, 0.13, 0.58 }, { -0.01, -0.00, -0.01 } },
|
||||
new double[,] { { 0.13, 0.65, 1.25 }, { -0.41, -0.39, -0.50 }, { 0.77, 0.58, 2.39 }, { -0.03, -0.01, 0.04 } },
|
||||
new double[,] { { 0.31, 0.43, 1.22, 0.03 }, { 0.65, 0.52, 1.25, 0.67 }, { -0.39, -0.28, -0.50, -0.10 }, { 0.58, 0.58, 2.39, -0.41 }, { -0.01, -0.01, 0.04, 0.03 } }
|
||||
};
|
||||
|
||||
ExpectedReturns = new List<double[]>
|
||||
{
|
||||
new double[] { 0.08, -0.01, 0.12 },
|
||||
new double[] { 0.11, 0.23, 0.4 },
|
||||
new double[] { -0.24, 0.21, 0.14 },
|
||||
null,
|
||||
new double[] { 0.10, 0.06 },
|
||||
new double[] { 0.23, 0.15, 0.25 },
|
||||
null,
|
||||
new double[] { 0.23, 0.25, 0.88, 0.04 }
|
||||
};
|
||||
|
||||
Covariances = new List<double[,]>
|
||||
{
|
||||
new double[,] { { 0.29, 0.13, 0.10 }, { 0.13, 0.06, 0.04 }, { 0.10, 0.04, 0.05 } },
|
||||
null,
|
||||
new double[,] { { 0.07, 0.12, -0.00 }, { 0.12, 0.51, 0.03 }, { -0.00, 0.03, 0.04 } },
|
||||
null,
|
||||
new double[,] { { 0.44, 0.15 }, { 0.15, 0.05 } },
|
||||
new double[,] { { 0.19, 0.11, 0.16 }, { 0.11, 0.09, 0.09 }, { 0.16, 0.09, 0.14 } },
|
||||
new double[,] { { 0.24, 0.20, 0.61 }, { 0.20, 0.25, 0.58 }, { 0.61, 0.58, 1.67 } },
|
||||
new double[,] { { 0.19, 0.16, 0.44, 0.05 }, { 0.16, 0.14, 0.40, 0.02 }, { 0.44, 0.40, 1.29, -0.06 }, { 0.05, 0.02, -0.06, 0.15 } }
|
||||
};
|
||||
|
||||
ExpectedResults = new List<double[]>
|
||||
{
|
||||
new double[] { -0.5, 0.5, 1 },
|
||||
new double[] { 0, 0, 1 },
|
||||
new double[] { -0.404692, 0.404692, 1 },
|
||||
new double[] { -0.418338, 0.023261, 1, 0.040668, 0.35441 },
|
||||
new double[] { 0.5, 0.5 },
|
||||
new double[] { -0.670213, 0.670213, 1 },
|
||||
new double[] { -1, 1, 1 },
|
||||
new double[] { -1, 0.315476, 0.684524, 1 },
|
||||
};
|
||||
}
|
||||
|
||||
protected override IPortfolioOptimizer CreateOptimizer()
|
||||
{
|
||||
return new MaximumSharpeRatioPortfolioOptimizer();
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
[TestCase(7)]
|
||||
public override void OptimizeWeightings(int testCaseNumber)
|
||||
{
|
||||
base.OptimizeWeightings(testCaseNumber);
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
public void OptimizeWeightingsSpecifyingLowerBoundAndRiskFreeRate(int testCaseNumber)
|
||||
{
|
||||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(lower: 0, riskFreeRate: 0.04);
|
||||
var expectedResult = new double[] { 0, 0, 1 };
|
||||
|
||||
var result = testOptimizer.Optimize(HistoricalReturns[testCaseNumber]);
|
||||
|
||||
Assert.AreEqual(expectedResult, result.Select(x => Math.Round(x, 6)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SingleSecurityPortfolioReturnsFullWeight()
|
||||
{
|
||||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer();
|
||||
var historicalReturns = new double[,] { { -0.1 } };
|
||||
var expectedReturns = new double[] { -0.1 };
|
||||
|
||||
// With a single security the budget constraint Σw = 1 leaves it fully invested
|
||||
var expectedResult = new double[] { 1 };
|
||||
|
||||
var result = testOptimizer.Optimize(historicalReturns, expectedReturns);
|
||||
|
||||
Assert.AreEqual(result, expectedResult);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EqualWeightingsWhenNoSolutionFound()
|
||||
{
|
||||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer();
|
||||
var historicalReturns = new double[,] { { -0.10, -0.20 }, { -0.12, -0.25 } };
|
||||
var expectedReturns = new double[] { -0.10, -0.25 };
|
||||
var covariance = new double[,] { { 0.25, 0.12 }, { 0.45, 0.2 } }; // non positive definite
|
||||
|
||||
var expectedResult = new double[] { 0.5, 0.5 };
|
||||
|
||||
var result = testOptimizer.Optimize(historicalReturns, expectedReturns, covariance);
|
||||
|
||||
Assert.AreEqual(result, expectedResult);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BoundariesAreNotViolated()
|
||||
{
|
||||
var testCaseNumber = 1;
|
||||
var lower = 0d;
|
||||
var upper = 0.5d;
|
||||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(lower, upper);
|
||||
|
||||
var result = testOptimizer.Optimize(HistoricalReturns[testCaseNumber], null, Covariances[testCaseNumber]);
|
||||
|
||||
foreach (var x in result)
|
||||
{
|
||||
var rounded = Math.Round(x, 6);
|
||||
Assert.GreaterOrEqual(rounded, lower);
|
||||
Assert.LessOrEqual(rounded, upper);
|
||||
};
|
||||
}
|
||||
|
||||
// Every case whose maximum Sharpe ratio portfolio is well defined. Case 1 has a
|
||||
// zero-variance asset (the ratio is unbounded) and case 4 an indefinite covariance
|
||||
// (it falls back to equal weights), so neither has a finite interior optimum and
|
||||
// both are excluded.
|
||||
[TestCase(0)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
[TestCase(7)]
|
||||
public void OptimizedWeightsMaximizeSharpeRatio(int testCaseNumber)
|
||||
{
|
||||
// Independent of the hardcoded ExpectedResults: the returned portfolio must
|
||||
// achieve a Sharpe ratio no lower than equal weights or any other feasible
|
||||
// portfolio drawn from the constraint set. The mean and covariance are
|
||||
// reconstructed exactly as the optimizer does so the check uses the same inputs.
|
||||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer();
|
||||
var historicalReturns = HistoricalReturns[testCaseNumber];
|
||||
var expectedReturns = ExpectedReturns[testCaseNumber] ?? historicalReturns.Mean(0);
|
||||
var covariance = Covariances[testCaseNumber] ?? historicalReturns.Covariance();
|
||||
|
||||
var result = testOptimizer.Optimize(historicalReturns, ExpectedReturns[testCaseNumber], Covariances[testCaseNumber]);
|
||||
var optimalSharpe = SharpeRatio(result, expectedReturns, covariance);
|
||||
|
||||
var size = result.Length;
|
||||
var equalWeights = Enumerable.Repeat(1.0 / size, size).ToArray();
|
||||
Assert.GreaterOrEqual(optimalSharpe, SharpeRatio(equalWeights, expectedReturns, covariance));
|
||||
|
||||
// The Sharpe ratio cannot exceed the unconstrained tangency portfolio, a hard
|
||||
// analytic ceiling (Cauchy-Schwarz) that no feasible portfolio can beat.
|
||||
Assert.LessOrEqual(optimalSharpe, TangencySharpe(expectedReturns, covariance) + 1e-6);
|
||||
|
||||
var random = new Random(0);
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
var candidate = RandomFeasibleWeights(random, size, lower: -1.0, upper: 1.0);
|
||||
Assert.GreaterOrEqual(optimalSharpe + 1e-6, SharpeRatio(candidate, expectedReturns, covariance));
|
||||
}
|
||||
}
|
||||
|
||||
private static double SharpeRatio(double[] weights, double[] expectedReturns, double[,] covariance)
|
||||
{
|
||||
var size = weights.Length;
|
||||
var portfolioReturn = 0.0;
|
||||
var portfolioVariance = 0.0;
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
portfolioReturn += weights[i] * expectedReturns[i];
|
||||
for (var j = 0; j < size; j++)
|
||||
{
|
||||
portfolioVariance += weights[i] * covariance[i, j] * weights[j];
|
||||
}
|
||||
}
|
||||
return portfolioReturn / Math.Sqrt(portfolioVariance);
|
||||
}
|
||||
|
||||
private static double TangencySharpe(double[] expectedReturns, double[,] covariance)
|
||||
{
|
||||
// The unconstrained maximum Sharpe ratio is sqrt(mu' inv(S) mu), the largest
|
||||
// value any portfolio can reach regardless of the budget or weight bounds.
|
||||
var inverse = covariance.Inverse();
|
||||
var size = expectedReturns.Length;
|
||||
var quadraticForm = 0.0;
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
for (var j = 0; j < size; j++)
|
||||
{
|
||||
quadraticForm += expectedReturns[i] * inverse[i, j] * expectedReturns[j];
|
||||
}
|
||||
}
|
||||
return Math.Sqrt(quadraticForm);
|
||||
}
|
||||
|
||||
private static double[] RandomFeasibleWeights(Random random, int size, double lower, double upper)
|
||||
{
|
||||
// Draw weights uniformly from the box and keep only those summing to one.
|
||||
while (true)
|
||||
{
|
||||
var weights = new double[size];
|
||||
var sum = 0.0;
|
||||
for (var i = 0; i < size - 1; i++)
|
||||
{
|
||||
weights[i] = lower + random.NextDouble() * (upper - lower);
|
||||
sum += weights[i];
|
||||
}
|
||||
var last = 1.0 - sum;
|
||||
if (last >= lower && last <= upper)
|
||||
{
|
||||
weights[size - 1] = last;
|
||||
return weights;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* 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 aaplicable 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.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class MeanReversionPortfolioConstructionModelTests
|
||||
{
|
||||
private DateTime _nowUtc;
|
||||
private QCAlgorithm _algorithm;
|
||||
private List<double> _simplexTestArray;
|
||||
private double[] _simplexExpectedArray1, _simplexExpectedArray2;
|
||||
|
||||
[SetUp]
|
||||
public virtual void SetUp()
|
||||
{
|
||||
_nowUtc = new DateTime(2021, 1, 10);
|
||||
_algorithm = new AlgorithmStub();
|
||||
_algorithm.SetFinishedWarmingUp();
|
||||
_algorithm.Settings.MinimumOrderMarginPortfolioPercentage = 0;
|
||||
_algorithm.Settings.FreePortfolioValue = 250;
|
||||
_algorithm.SetDateTime(_nowUtc.ConvertToUtc(_algorithm.TimeZone));
|
||||
_algorithm.SetCash(1200);
|
||||
var historyProvider = new SubscriptionDataReaderHistoryProvider();
|
||||
_algorithm.SetHistoryProvider(historyProvider);
|
||||
|
||||
historyProvider.Initialize(new HistoryProviderInitializeParameters(
|
||||
new BacktestNodePacket(),
|
||||
null,
|
||||
TestGlobals.DataProvider,
|
||||
TestGlobals.DataCacheProvider,
|
||||
TestGlobals.MapFileProvider,
|
||||
TestGlobals.FactorFileProvider,
|
||||
i => { },
|
||||
true,
|
||||
new DataPermissionManager(),
|
||||
_algorithm.ObjectStore,
|
||||
_algorithm.Settings));
|
||||
|
||||
_simplexTestArray = new List<double> {0.2d, 0.5d, 0.4d, -0.1d, 0d};
|
||||
_simplexExpectedArray1 = new double[] {1d/6, 7d/15, 11d/30, 0d, 0d};
|
||||
_simplexExpectedArray2 = new double[] {0d, 0.3d, 0.2d, 0d, 0d};
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotReturnTargetsIfSecurityPriceIsZero(Language language)
|
||||
{
|
||||
_algorithm.AddEquity(Symbols.SPY.Value);
|
||||
_algorithm.SetDateTime(_nowUtc.ConvertToUtc(_algorithm.TimeZone));
|
||||
|
||||
SetPortfolioConstruction(language, PortfolioBias.Long);
|
||||
|
||||
var insights = new[] { new Insight(_nowUtc, Symbols.SPY, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null) };
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, PortfolioBias.Long)]
|
||||
[TestCase(Language.Python, PortfolioBias.Long)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Short)]
|
||||
[TestCase(Language.Python, PortfolioBias.Short)]
|
||||
public void PortfolioBiasIsRespected(Language language, PortfolioBias bias)
|
||||
{
|
||||
if (bias == PortfolioBias.Short)
|
||||
{
|
||||
var throwsConstraint = language == Language.CSharp
|
||||
? Throws.InstanceOf<ArgumentException>()
|
||||
: Throws.InstanceOf<ClrBubbledException>().With.InnerException.InstanceOf<ArgumentException>();
|
||||
Assert.That(() => GetPortfolioConstructionModel(language, bias, Resolution.Daily),
|
||||
throwsConstraint.And.Message.EqualTo("Long position must be allowed in MeanReversionPortfolioConstructionModel."));
|
||||
return;
|
||||
}
|
||||
|
||||
var targets = GeneratePortfolioTargets(language, InsightDirection.Up, InsightDirection.Up, 1, 1);
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target.Quantity == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Assert.AreEqual(Math.Sign((int)bias), Math.Sign(target.Quantity));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, null, null, 47, 47)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, null, null, 47, 47)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0, 0, 47, 47)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0, 0, 47, 47)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 1, -0.5, 31, 63)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 1, -0.5, 31, 63)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Down, 1, 0.5, 31, 63)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Down, 1, 0.5, 31, 63)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0, -0.5, 47, 47)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0, -0.5, 47, 47)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0, 1, 94, 0)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0, 1, 94, 0)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0.5, -1, 47, 47)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0.5, -1, 47, 47)]
|
||||
public void CorrectWeightings(Language language,
|
||||
InsightDirection direction1,
|
||||
InsightDirection direction2,
|
||||
double? magnitude1,
|
||||
double? magnitude2,
|
||||
decimal expectedQty1,
|
||||
decimal expectedQty2)
|
||||
{
|
||||
var targets = GeneratePortfolioTargets(language, direction1, direction2, magnitude1, magnitude2);
|
||||
var quantities = targets.ToDictionary(target => {
|
||||
QuantConnect.Logging.Log.Debug($"{target.Symbol}: {target.Quantity}");
|
||||
return target.Symbol.Value;
|
||||
},
|
||||
target => target.Quantity);
|
||||
|
||||
Assert.AreEqual(expectedQty1, quantities["AAPL"]);
|
||||
Assert.AreEqual(expectedQty2, quantities.ContainsKey("SPY") ? quantities["SPY"] : 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CumulativeSum()
|
||||
{
|
||||
var list = new List<double>{1.1d, 2.5d, 0.7d, 13.6d, -5.2d, 3.9d, -1.6d};
|
||||
var expected = new List<double>{1.1d, 3.6d, 4.3d, 17.9d, 12.7d, 16.6d, 15.0d};
|
||||
|
||||
var result = MeanReversionPortfolioConstructionModel.CumulativeSum(list)
|
||||
.Select(x => Math.Round(x, 1));
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPriceRelatives()
|
||||
{
|
||||
var model = new TestMeanReversionPortfolioConstructionModel();
|
||||
SetPortfolioConstruction(Language.CSharp, PortfolioBias.Long, model);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
var insights = new List<Insight>
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null),
|
||||
};
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(aapl, spy));
|
||||
|
||||
var history = _algorithm.History<TradeBar>(new[] {aapl.Symbol, spy.Symbol}, 2, Resolution.Daily);
|
||||
var aaplHist = history.Select(slice => slice[aapl.Symbol].Close);
|
||||
var spyHist = history.Select(slice => slice[spy.Symbol].Close);
|
||||
var aaplRelative = (double) (aaplHist.Last() / aaplHist.Average());
|
||||
var spyRelative = (double) (spyHist.Last() / spyHist.Average());
|
||||
|
||||
var result = model.TestGetPriceRelatives(insights).Select(x => Math.Round(x, 8)).ToArray();
|
||||
var expected = new double[] {aaplRelative, spyRelative};
|
||||
expected = expected.Select(x => Math.Round(x, 8)).ToArray();
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPriceRelativesPython()
|
||||
{
|
||||
SetPortfolioConstruction(Language.Python, PortfolioBias.Long);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
var insights = new List<Insight>
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null),
|
||||
};
|
||||
|
||||
var history = _algorithm.History<TradeBar>(new[] {aapl.Symbol, spy.Symbol}, 2, Resolution.Daily);
|
||||
var aaplHist = history.Select(slice => slice[aapl.Symbol].Close);
|
||||
var spyHist = history.Select(slice => slice[spy.Symbol].Close);
|
||||
var aaplRelative = (double) (aaplHist.Last() / aaplHist.Average());
|
||||
var spyRelative = (double) (spyHist.Last() / spyHist.Average());
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MeanReversionPortfolioConstructionModel);
|
||||
var model = Py.Import(name).GetAttr(name).Invoke(((int)Resolution.Daily).ToPython(), ((int)PortfolioBias.LongShort).ToPython(), 1.ToPython(), 2.ToPython());
|
||||
model.InvokeMethod("OnSecuritiesChanged", _algorithm.ToPython(), SecurityChangesTests.AddedNonInternal(aapl, spy).ToPython());
|
||||
|
||||
var result = PyList.AsList(model.InvokeMethod("GetPriceRelatives", insights.ToPython()));
|
||||
var resultArray = result.Select(x => Math.Round(Convert.ToDouble(x), 8)).ToArray();
|
||||
var expected = new double[] {aaplRelative, spyRelative};
|
||||
expected = expected.Select(x => Math.Round(x, 8)).ToArray();
|
||||
Assert.AreEqual(expected, resultArray);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPriceRelativesWithInsightMagnitude()
|
||||
{
|
||||
var model = new TestMeanReversionPortfolioConstructionModel();
|
||||
SetPortfolioConstruction(Language.CSharp, PortfolioBias.Long, model);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
var insights = new List<Insight>
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, 1, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, -0.5, null),
|
||||
};
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(aapl, spy));
|
||||
|
||||
var result = model.TestGetPriceRelatives(insights);
|
||||
var expected = new double[] {2d, 0.5d};
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPriceRelativesWithInsightMagnitudePython()
|
||||
{
|
||||
SetPortfolioConstruction(Language.Python, PortfolioBias.Long);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
var insights = new List<Insight>
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, 1, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, -0.5, null),
|
||||
};
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MeanReversionPortfolioConstructionModel);
|
||||
var model = Py.Import(name).GetAttr(name).Invoke(((int)Resolution.Daily).ToPython());
|
||||
model.InvokeMethod("OnSecuritiesChanged", _algorithm.ToPython(), SecurityChangesTests.AddedNonInternal(aapl, spy).ToPython());
|
||||
|
||||
var result = PyList.AsList(model.InvokeMethod("GetPriceRelatives", insights.ToPython()));
|
||||
var resultArray = result.Select(x => Convert.ToDouble(x)).ToArray();
|
||||
var expected = new double[] {2d, 0.5d};
|
||||
Assert.AreEqual(expected, resultArray);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(0.5)]
|
||||
[TestCase(0)]
|
||||
[TestCase(-0.5)]
|
||||
public void SimplexProjection(double regulator)
|
||||
{
|
||||
if (regulator <= 0)
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentException>(() => MeanReversionPortfolioConstructionModel.SimplexProjection(_simplexTestArray, regulator));
|
||||
Assert.That(exception.Message, Is.EqualTo("Total must be > 0 for Euclidean Projection onto the Simplex."));
|
||||
return;
|
||||
}
|
||||
|
||||
double[] expected;
|
||||
if (regulator == 1d)
|
||||
{
|
||||
expected = _simplexExpectedArray1;
|
||||
}
|
||||
else
|
||||
{
|
||||
expected = _simplexExpectedArray2;
|
||||
}
|
||||
expected = expected.Select(x => Math.Round(x, 8)).ToArray();
|
||||
|
||||
var result = MeanReversionPortfolioConstructionModel.SimplexProjection(_simplexTestArray, regulator);
|
||||
result = result.Select(x => Math.Round(x, 8)).ToArray();
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(0.5)]
|
||||
[TestCase(0)]
|
||||
[TestCase(-0.5)]
|
||||
public void SimplexProjectionPython(double regulator)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MeanReversionPortfolioConstructionModel);
|
||||
var model = Py.Import(name).GetAttr(name).Invoke(((int)Resolution.Daily).ToPython());
|
||||
|
||||
if (regulator <= 0)
|
||||
{
|
||||
Assert.That(() => model.InvokeMethod("SimplexProjection", _simplexTestArray.ToPython(), new PyFloat(regulator)),
|
||||
Throws.InstanceOf<ClrBubbledException>()
|
||||
.With.InnerException.InstanceOf<ArgumentException>()
|
||||
.And.Message.EqualTo("Total must be > 0 for Euclidean Projection onto the Simplex."));
|
||||
return;
|
||||
}
|
||||
|
||||
double[] expected;
|
||||
if (regulator == 1d)
|
||||
{
|
||||
expected = _simplexExpectedArray1;
|
||||
}
|
||||
else
|
||||
{
|
||||
expected = _simplexExpectedArray2;
|
||||
}
|
||||
var expectedArray = expected.Select(x => Math.Round(x, 8)).ToArray();
|
||||
|
||||
var result = PyList.AsList(model.InvokeMethod("SimplexProjection", _simplexTestArray.ToPython(), new PyFloat(regulator)));
|
||||
var resultArray = result.Select(x => Math.Round(Convert.ToDouble(x), 8)).ToArray();
|
||||
Assert.AreEqual(expectedArray, resultArray);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IPortfolioTarget> GeneratePortfolioTargets(Language language, InsightDirection direction1, InsightDirection direction2, double? magnitude1, double? magnitude2)
|
||||
{
|
||||
SetPortfolioConstruction(language, PortfolioBias.Long);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
foreach (var equity in new[] { aapl, spy })
|
||||
{
|
||||
equity.SetMarketPrice(new Tick(_nowUtc, equity.Symbol, 10, 10));
|
||||
}
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, direction1, magnitude1, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, direction2, magnitude2, null),
|
||||
};
|
||||
_algorithm.Insights.AddRange(insights);
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(aapl, spy));
|
||||
|
||||
return _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
}
|
||||
|
||||
protected void SetPortfolioConstruction(Language language, PortfolioBias bias, IPortfolioConstructionModel defaultModel = null)
|
||||
{
|
||||
var model = defaultModel ?? GetPortfolioConstructionModel(language, bias, Resolution.Daily);
|
||||
_algorithm.SetPortfolioConstruction(model);
|
||||
|
||||
foreach (var kvp in _algorithm.Portfolio)
|
||||
{
|
||||
kvp.Value.SetHoldings(kvp.Value.Price, 0);
|
||||
}
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(_algorithm.Securities.Values.ToArray());
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
}
|
||||
|
||||
public IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, PortfolioBias bias, Resolution resolution)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new MeanReversionPortfolioConstructionModel(resolution, bias, 1, 1, resolution);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MeanReversionPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name)
|
||||
.Invoke(((int)resolution).ToPython(), ((int)bias).ToPython(), 1.ToPython(), 1.ToPython(), ((int)resolution).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestMeanReversionPortfolioConstructionModel : MeanReversionPortfolioConstructionModel
|
||||
{
|
||||
public TestMeanReversionPortfolioConstructionModel()
|
||||
: base(Resolution.Daily, windowSize: 2)
|
||||
{
|
||||
}
|
||||
|
||||
public double[] TestGetPriceRelatives(List<Insight> insights)
|
||||
{
|
||||
return base.GetPriceRelatives(insights);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class MeanVarianceOptimizationPortfolioConstructionModelTests
|
||||
{
|
||||
private DateTime _nowUtc;
|
||||
private QCAlgorithm _algorithm;
|
||||
|
||||
[SetUp]
|
||||
public virtual void SetUp()
|
||||
{
|
||||
_nowUtc = new DateTime(2013, 10, 8);
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SetFinishedWarmingUp();
|
||||
_algorithm.SetPandasConverter();
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
_algorithm.SetDateTime(_nowUtc.ConvertToUtc(_algorithm.TimeZone));
|
||||
var historyProvider = new SubscriptionDataReaderHistoryProvider();
|
||||
_algorithm.SetHistoryProvider(historyProvider);
|
||||
|
||||
historyProvider.Initialize(new HistoryProviderInitializeParameters(
|
||||
null,
|
||||
null,
|
||||
TestGlobals.DataProvider,
|
||||
TestGlobals.DataCacheProvider,
|
||||
TestGlobals.MapFileProvider,
|
||||
TestGlobals.FactorFileProvider,
|
||||
i => { },
|
||||
true,
|
||||
new DataPermissionManager(),
|
||||
_algorithm.ObjectStore,
|
||||
_algorithm.Settings));
|
||||
}
|
||||
|
||||
private void Clear() => _algorithm.Insights.Clear(_algorithm.Securities.Keys.ToArray());
|
||||
|
||||
[TestCase(Language.CSharp, PortfolioBias.Long, 0.1, -0.1)]
|
||||
[TestCase(Language.Python, PortfolioBias.Long, 0.1, -0.1)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Short, -0.1, 0.1)]
|
||||
[TestCase(Language.Python, PortfolioBias.Short, -0.1, 0.1)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Long, -0.1, 0.1)]
|
||||
[TestCase(Language.Python, PortfolioBias.Long, -0.1, 0.1)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Short, 0.1, -0.1)]
|
||||
[TestCase(Language.Python, PortfolioBias.Short, 0.1, -0.1)]
|
||||
public void PortfolioBiasIsRespected(Language language, PortfolioBias bias, double magnitude1, double magnitude2)
|
||||
{
|
||||
var targets = GeneratePortfolioTargets(language, InsightDirection.Up, InsightDirection.Down, magnitude1, magnitude2, bias);
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
QuantConnect.Logging.Log.Trace($"{target.Symbol}: {target.Quantity}");
|
||||
if (target.Quantity == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Assert.AreEqual(Math.Sign((int)bias), Math.Sign(target.Quantity));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0, 0, 4155, 2493)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0, 0, 4155, 2493)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Down, 0.1, 0.05, 4155, 2493)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Down, 0.1, 0.05, 4155, 2493)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0.1, 0, 4155, 2493)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0.1, 0, 4155, 2493)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Down, 0, 0.1, 4155, 2493)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Down, 0, 0.1, 4155, 2493)]
|
||||
public void CorrectWeightings(Language language,
|
||||
InsightDirection direction1,
|
||||
InsightDirection direction2,
|
||||
double? magnitude1,
|
||||
double? magnitude2,
|
||||
decimal expectedQty1,
|
||||
decimal expectedQty2)
|
||||
{
|
||||
var targets = GeneratePortfolioTargets(language, direction1, direction2, magnitude1, magnitude2).ToList();
|
||||
Clear();
|
||||
var quantities = targets.ToDictionary(target => {
|
||||
QuantConnect.Logging.Log.Trace($"{target.Symbol}: {target.Quantity}");
|
||||
return target.Symbol.Value;
|
||||
},
|
||||
target => target.Quantity);
|
||||
|
||||
Assert.AreEqual(expectedQty1, quantities["AAPL"]);
|
||||
Assert.AreEqual(expectedQty2, quantities.ContainsKey("SPY") ? quantities["SPY"] : 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotReturnTargetsIfNoInsightMagnitude(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, PortfolioBias.LongShort);
|
||||
|
||||
var appl = _algorithm.AddEquity("AAPL");
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
new Insight(_nowUtc, appl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null)
|
||||
};
|
||||
|
||||
var actualTargets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToList();
|
||||
Assert.AreEqual(0, actualTargets.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0.1, 0.1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0.1, 0.1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0.1, -0.1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0.1, -0.1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Up, 0.1, 0)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Up, 0.1, 0)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, InsightDirection.Down, 0.1, 0.1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, InsightDirection.Down, 0.1, 0.1)]
|
||||
public void ObeyBudgetConstraint(Language language,
|
||||
InsightDirection direction1,
|
||||
InsightDirection direction2,
|
||||
double? magnitude1,
|
||||
double? magnitude2)
|
||||
{
|
||||
var targets = GeneratePortfolioTargets(language, direction1, direction2, magnitude1, magnitude2);
|
||||
var totalCost = targets.Sum(x => Math.Abs(x.Quantity) * 10); // Set market price at $10 in the helper method
|
||||
Assert.LessOrEqual(totalCost, _algorithm.Portfolio.TotalPortfolioValue);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
[TestCase(7)]
|
||||
public void PythonConstructorWorksWithDifferentArguments(int arguments)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(),
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
timeDelta = timedelta(days=1)
|
||||
class CustomPortfolioOptimizer:
|
||||
def Optimize(self, historicalReturns, expectedReturns, covariance):
|
||||
return [0.5]*(np.array(historicalReturns)).shape[1]"
|
||||
);
|
||||
var timeDelta = module.GetAttr("timeDelta");
|
||||
var portfolioBias = PortfolioBias.LongShort;
|
||||
var lookback = 1;
|
||||
var period = 63;
|
||||
var resolution = Resolution.Daily;
|
||||
var targetReturn = 0.02;
|
||||
var optimizer = module.GetAttr("CustomPortfolioOptimizer").Invoke();
|
||||
|
||||
switch (arguments)
|
||||
{
|
||||
case 1:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta));
|
||||
break;
|
||||
case 2:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta, portfolioBias));
|
||||
break;
|
||||
case 3:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta, portfolioBias, lookback));
|
||||
break;
|
||||
case 4:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta, portfolioBias, lookback, period));
|
||||
break;
|
||||
case 5:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta, portfolioBias, lookback, period, resolution));
|
||||
break;
|
||||
case 6:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta, portfolioBias, lookback, period, resolution, targetReturn));
|
||||
break;
|
||||
case 7:
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(timeDelta, portfolioBias, lookback, period, resolution, targetReturn, optimizer));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("timeDelta")]
|
||||
[TestCase("pyFunc")]
|
||||
public void PythonConstructorWorksWithDifferentArgumentRebalance(string rebalanceName)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(),
|
||||
@"from AlgorithmImports import *
|
||||
timeDelta = timedelta(days=1)
|
||||
pyFunc = lambda x: x + timedelta(days=1)");
|
||||
var rebalance = module.GetAttr(rebalanceName);
|
||||
Assert.DoesNotThrow(() => new MeanReversionPortfolioConstructionModel(rebalance));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("CustomPortfolioOptimizer")]
|
||||
[TestCase("csharpOptimizer")]
|
||||
public void PythonConstructorWorksWithDifferentOptimizers(string optimizerName)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(),
|
||||
@"from AlgorithmImports import *
|
||||
rebalance = timedelta(days=1)
|
||||
csharpOptimizer = MinimumVariancePortfolioOptimizer()
|
||||
|
||||
class CustomPortfolioOptimizer:
|
||||
def Optimize(self, historicalReturns, expectedReturns, covariance):
|
||||
pass");
|
||||
|
||||
var rebalance = module.GetAttr("rebalance");
|
||||
var optimizer = module.GetAttr(optimizerName);
|
||||
if (optimizerName == "customOptimizer")
|
||||
{
|
||||
optimizer = optimizer.Invoke();
|
||||
}
|
||||
|
||||
Assert.DoesNotThrow(() => new MeanVarianceOptimizationPortfolioConstructionModel(rebalance, optimizer: optimizer));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonConstructorFailsWhenOptimizerTypeIsInvalid()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(),
|
||||
@"from AlgorithmImports import *
|
||||
rebalance = timedelta(days=1)
|
||||
class CustomPortfolioOptimizer:
|
||||
pass");
|
||||
var rebalance = module.GetAttr("rebalance");
|
||||
var optimizer = module.GetAttr("CustomPortfolioOptimizer").Invoke();
|
||||
|
||||
var message = Assert.Throws<NotImplementedException>(() => new MeanVarianceOptimizationPortfolioConstructionModel(rebalance, optimizer: optimizer));
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetPortfolioConstruction(Language language, PortfolioBias bias)
|
||||
{
|
||||
var model = GetPortfolioConstructionModel(language, Resolution.Daily, bias);
|
||||
_algorithm.SetPortfolioConstruction(model);
|
||||
|
||||
foreach (var kvp in _algorithm.Portfolio)
|
||||
{
|
||||
kvp.Value.SetHoldings(kvp.Value.Price, 0);
|
||||
}
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(_algorithm.Securities.Values.ToArray());
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
}
|
||||
|
||||
public IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, Resolution resolution, PortfolioBias bias)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new MeanVarianceOptimizationPortfolioConstructionModel(resolution, bias, 1, 63, Resolution.Daily, 0.0001);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MeanVarianceOptimizationPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name)
|
||||
.Invoke(((int)resolution).ToPython(), ((int)bias).ToPython(), 1.ToPython(), 63.ToPython(), ((int)Resolution.Daily).ToPython(), 0.0001.ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IPortfolioTarget> GeneratePortfolioTargets(Language language, InsightDirection direction1, InsightDirection direction2,
|
||||
double? magnitude1, double? magnitude2, PortfolioBias bias = PortfolioBias.LongShort)
|
||||
{
|
||||
SetPortfolioConstruction(language, bias);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
aapl.SetMarketPrice(new Tick(_nowUtc, aapl.Symbol, 10, 10));
|
||||
spy.SetMarketPrice(new Tick(_nowUtc, spy.Symbol, 10, 10));
|
||||
aapl.SetMarketPrice(new Tick(_nowUtc.AddDays(1), aapl.Symbol, 8, 8));
|
||||
spy.SetMarketPrice(new Tick(_nowUtc.AddDays(1), spy.Symbol, 15, 15));
|
||||
aapl.SetMarketPrice(new Tick(_nowUtc.AddDays(2), aapl.Symbol, 12, 12));
|
||||
spy.SetMarketPrice(new Tick(_nowUtc.AddDays(2), spy.Symbol, 20, 20));
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, direction1, magnitude1, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, direction2, magnitude2, null),
|
||||
};
|
||||
_algorithm.Insights.AddRange(insights);
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(aapl, spy));
|
||||
|
||||
return _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 aaplicable 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 NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class MinimumVariancePortfolioOptimizerTests : PortfolioOptimizerTestsBase
|
||||
{
|
||||
private Dictionary<int, double> _targetReturns;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var historicalReturns1 = new double[,] { { 0.76, -0.06, 1.22, 0.17 }, { 0.02, 0.28, 1.25, -0.00 }, { -0.50, -0.13, -0.50, -0.03 }, { 0.81, 0.31, 2.39, 0.26 }, { -0.02, 0.02, 0.06, 0.01 } };
|
||||
var historicalReturns2 = new double[,] { { -0.15, 0.67, 0.45 }, { -0.44, -0.10, 0.07 }, { 0.04, -0.41, 0.01 }, { 0.01, 0.03, 0.02 } };
|
||||
var historicalReturns3 = new double[,] { { -0.02, 0.65, 1.25 }, { -0.29, -0.39, -0.50 }, { 0.29, 0.58, 2.39 }, { 0.00, -0.01, 0.06 } };
|
||||
var historicalReturns4 = new double[,] { { 0.76, 0.25, 0.21 }, { 0.02, -0.15, 0.45 }, { -0.50, -0.44, 0.07 }, { 0.81, 0.04, 0.01 }, { -0.02, 0.01, 0.02 } };
|
||||
|
||||
var expectedReturns1 = new double[] { 0.21, 0.08, 0.88, 0.08 };
|
||||
var expectedReturns2 = new double[] { -0.13, 0.05, 0.14 };
|
||||
var expectedReturns3 = (double[])null;
|
||||
var expectedReturns4 = (double[])null;
|
||||
|
||||
var covariance1 = new double[,] { { 0.31, 0.05, 0.55, 0.07 }, { 0.05, 0.04, 0.18, 0.01 }, { 0.55, 0.18, 1.28, 0.12 }, { 0.07, 0.01, 0.12, 0.02 } };
|
||||
var covariance2 = new double[,] { { 0.05, -0.02, -0.01 }, { -0.02, 0.21, 0.09 }, { -0.01, 0.09, 0.04 } };
|
||||
var covariance3 = new double[,] { { 0.06, 0.09, 0.28 }, { 0.09, 0.25, 0.58 }, { 0.28, 0.58, 1.66 } };
|
||||
var covariance4 = (double[,])null;
|
||||
|
||||
HistoricalReturns = new List<double[,]>
|
||||
{
|
||||
historicalReturns1,
|
||||
historicalReturns2,
|
||||
historicalReturns3,
|
||||
historicalReturns4,
|
||||
historicalReturns1,
|
||||
historicalReturns2,
|
||||
historicalReturns3,
|
||||
historicalReturns4
|
||||
};
|
||||
|
||||
ExpectedReturns = new List<double[]>
|
||||
{
|
||||
expectedReturns1,
|
||||
expectedReturns2,
|
||||
expectedReturns3,
|
||||
expectedReturns4,
|
||||
expectedReturns1,
|
||||
expectedReturns2,
|
||||
expectedReturns3,
|
||||
expectedReturns4
|
||||
};
|
||||
|
||||
Covariances = new List<double[,]>
|
||||
{
|
||||
covariance1,
|
||||
covariance2,
|
||||
covariance3,
|
||||
covariance4,
|
||||
covariance1,
|
||||
covariance2,
|
||||
covariance3,
|
||||
covariance4
|
||||
};
|
||||
|
||||
ExpectedResults = new List<double[]>
|
||||
{
|
||||
new double[] { -0.089212, 0.23431, -0.040975, 0.635503 },
|
||||
new double[] { 0.366812, -0.139738, 0.49345 },
|
||||
new double[] { 0.562216, 0.36747, -0.070314 },
|
||||
new double[] { -0.119241, 0.443464, 0.437295 },
|
||||
new double[] { -0.215505, 0.130699, 0.084806, 0.56899 },
|
||||
new double[] { -0.275, 0.275, 0.45 },
|
||||
new double[] { -0.129512, 0.551139, 0.319349 },
|
||||
new double[] { 0.052859, 0.144177, 0.802964 },
|
||||
};
|
||||
|
||||
_targetReturns = new Dictionary<int, double>
|
||||
{
|
||||
{ 4, 0.15d },
|
||||
{ 5, 0.25d },
|
||||
{ 6, 0.5d },
|
||||
{ 7, 0.125d }
|
||||
};
|
||||
}
|
||||
|
||||
protected override IPortfolioOptimizer CreateOptimizer()
|
||||
{
|
||||
return new MinimumVariancePortfolioOptimizer();
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public override void OptimizeWeightings(int testCaseNumber)
|
||||
{
|
||||
base.OptimizeWeightings(testCaseNumber);
|
||||
}
|
||||
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
[TestCase(7)]
|
||||
public void OptimizeWeightingsSpecifyingTargetReturns(int testCaseNumber)
|
||||
{
|
||||
var testOptimizer = new MinimumVariancePortfolioOptimizer(targetReturn: _targetReturns[testCaseNumber]);
|
||||
|
||||
var result = testOptimizer.Optimize(
|
||||
HistoricalReturns[testCaseNumber],
|
||||
ExpectedReturns[testCaseNumber],
|
||||
Covariances[testCaseNumber]);
|
||||
|
||||
Assert.AreEqual(ExpectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6)));
|
||||
Assert.AreEqual(1d, result.Select(x => Math.Round(Math.Abs(x), 6)).Sum());
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
public void EqualWeightingsWhenNoSolutionFound(int testCaseNumber)
|
||||
{
|
||||
var testOptimizer = new MinimumVariancePortfolioOptimizer(upper: -1);
|
||||
var expectedResult = new double[] { 0.25, 0.25, 0.25, 0.25 };
|
||||
|
||||
var result = testOptimizer.Optimize(HistoricalReturns[testCaseNumber]);
|
||||
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void BoundariesAreNotViolated(int testCaseNumber)
|
||||
{
|
||||
var lower = 0d;
|
||||
var upper = 0.5d;
|
||||
var testOptimizer = new MinimumVariancePortfolioOptimizer(lower, upper);
|
||||
|
||||
var result = testOptimizer.Optimize(
|
||||
HistoricalReturns[testCaseNumber],
|
||||
ExpectedReturns[testCaseNumber],
|
||||
Covariances[testCaseNumber]);
|
||||
|
||||
foreach (var x in result)
|
||||
{
|
||||
var rounded = Math.Round(x, 6);
|
||||
Assert.GreaterOrEqual(rounded, lower);
|
||||
Assert.LessOrEqual(rounded, upper);
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SingleSecurityPortfolioReturnsOne()
|
||||
{
|
||||
var testOptimizer = new MinimumVariancePortfolioOptimizer();
|
||||
var historicalReturns = new double[,] { { 0.76 }, { 0.02 }, { -0.50 } };
|
||||
var expectedResult = new double[] { 1 };
|
||||
|
||||
var result = testOptimizer.Optimize(historicalReturns);
|
||||
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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 Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class PortfolioConstructionModelPythonWrapperTests
|
||||
{
|
||||
[Test]
|
||||
public void PythonCompleteImplementation()
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = PyModule.FromString(
|
||||
"TestPCM",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class PyPCM(EqualWeightingPortfolioConstructionModel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CreateTargets_WasCalled = False
|
||||
self.OnSecuritiesChanged_WasCalled = False
|
||||
self.ShouldCreateTargetForInsight_WasCalled = False
|
||||
self.IsRebalanceDue_WasCalled = False
|
||||
self.GetTargetInsights_WasCalled = False
|
||||
self.DetermineTargetPercent_WasCalled = False
|
||||
|
||||
def CreateTargets(self, algorithm, insights):
|
||||
self.CreateTargets_WasCalled = True
|
||||
return super().CreateTargets(algorithm, insights)
|
||||
|
||||
def OnSecuritiesChanged(self, algorithm, changes):
|
||||
self.OnSecuritiesChanged_WasCalled = True
|
||||
super().OnSecuritiesChanged(algorithm, changes)
|
||||
|
||||
def ShouldCreateTargetForInsight(self, insight):
|
||||
self.ShouldCreateTargetForInsight_WasCalled = True
|
||||
return super().ShouldCreateTargetForInsight(insight)
|
||||
|
||||
def IsRebalanceDue(self, insights, algorithmUtc):
|
||||
self.IsRebalanceDue_WasCalled = True
|
||||
return True
|
||||
|
||||
def GetTargetInsights(self):
|
||||
self.GetTargetInsights_WasCalled = True
|
||||
return super().GetTargetInsights()
|
||||
|
||||
def DetermineTargetPercent(self, activeInsights):
|
||||
self.DetermineTargetPercent_WasCalled = True
|
||||
return super().DetermineTargetPercent(activeInsights)
|
||||
"
|
||||
).GetAttr("PyPCM").Invoke();
|
||||
var now = new DateTime(2020, 1, 10);
|
||||
var wrappedModel = new PortfolioConstructionModelPythonWrapper(model);
|
||||
var aapl = algorithm.AddEquity("AAPL");
|
||||
aapl.SetMarketPrice(new Tick(now, aapl.Symbol, 10, 10));
|
||||
algorithm.SetDateTime(now);
|
||||
|
||||
wrappedModel.OnSecuritiesChanged(algorithm, new SecurityChanges(SecurityChangesTests.AddedNonInternal(aapl)));
|
||||
Assert.IsTrue((bool)model.OnSecuritiesChanged_WasCalled);
|
||||
|
||||
var insight = new Insight(now, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Down, null, null);
|
||||
algorithm.Insights.Add(insight);
|
||||
var result = wrappedModel.CreateTargets(algorithm, new[] { insight }).ToList();
|
||||
Assert.AreEqual(1, result.Count);
|
||||
Assert.IsTrue((bool)model.CreateTargets_WasCalled);
|
||||
Assert.IsTrue((bool)model.GetTargetInsights_WasCalled);
|
||||
Assert.IsTrue((bool)model.IsRebalanceDue_WasCalled);
|
||||
Assert.IsTrue((bool)model.ShouldCreateTargetForInsight_WasCalled);
|
||||
Assert.IsTrue((bool)model.DetermineTargetPercent_WasCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PythonDoesNotImplementDetermineTargetPercent()
|
||||
{
|
||||
var algorithm = new AlgorithmStub();
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic model = PyModule.FromString(
|
||||
"TestPCM",
|
||||
@"
|
||||
|
||||
from clr import AddReference
|
||||
AddReference(""QuantConnect.Algorithm.Framework"")
|
||||
|
||||
from QuantConnect.Algorithm.Framework.Portfolio import *
|
||||
|
||||
class PyPCM(EqualWeightingPortfolioConstructionModel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.CreateTargets_WasCalled = False
|
||||
self.OnSecuritiesChanged_WasCalled = False
|
||||
self.ShouldCreateTargetForInsight_WasCalled = False
|
||||
self.IsRebalanceDue_WasCalled = False
|
||||
self.GetTargetInsights_WasCalled = False
|
||||
|
||||
def CreateTargets(self, algorithm, insights):
|
||||
self.CreateTargets_WasCalled = True
|
||||
return super().CreateTargets(algorithm, insights)
|
||||
|
||||
def OnSecuritiesChanged(self, algorithm, changes):
|
||||
self.OnSecuritiesChanged_WasCalled = True
|
||||
super().OnSecuritiesChanged(algorithm, changes)
|
||||
|
||||
def ShouldCreateTargetForInsight(self, insight):
|
||||
self.ShouldCreateTargetForInsight_WasCalled = True
|
||||
return super().ShouldCreateTargetForInsight(insight)
|
||||
|
||||
def IsRebalanceDue(self, insights, algorithmUtc):
|
||||
self.IsRebalanceDue_WasCalled = True
|
||||
return True
|
||||
|
||||
def GetTargetInsights(self):
|
||||
self.GetTargetInsights_WasCalled = True
|
||||
return super().GetTargetInsights()
|
||||
"
|
||||
).GetAttr("PyPCM").Invoke();
|
||||
|
||||
var now = new DateTime(2020, 1, 10);
|
||||
var wrappedModel = new PortfolioConstructionModelPythonWrapper(model);
|
||||
var aapl = algorithm.AddEquity("AAPL");
|
||||
aapl.SetMarketPrice(new Tick(now, aapl.Symbol, 10, 10));
|
||||
algorithm.SetDateTime(now);
|
||||
|
||||
wrappedModel.OnSecuritiesChanged(algorithm, new SecurityChanges(SecurityChangesTests.AddedNonInternal(aapl)));
|
||||
Assert.IsTrue((bool)model.OnSecuritiesChanged_WasCalled);
|
||||
|
||||
var insight = new Insight(now, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Down, null, null);
|
||||
algorithm.Insights.Add(insight);
|
||||
var result = wrappedModel.CreateTargets(algorithm, new[] { insight }).ToList();
|
||||
Assert.AreEqual(1, result.Count);
|
||||
Assert.IsTrue((bool)model.CreateTargets_WasCalled);
|
||||
Assert.IsTrue((bool)model.GetTargetInsights_WasCalled);
|
||||
Assert.IsTrue((bool)model.IsRebalanceDue_WasCalled);
|
||||
Assert.IsTrue((bool)model.ShouldCreateTargetForInsight_WasCalled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* 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 NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Scheduling;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using DateTime = System.DateTime;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class PortfolioConstructionModelTests
|
||||
{
|
||||
private QCAlgorithm _algorithm;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void RebalanceFunctionPeriodDue(Language language)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
var func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc(time):
|
||||
return time + timedelta(days=1)").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel(time => time.AddDays(1));
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 1, 23, 0, 0), new Insight[0]));
|
||||
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 2), new Insight[0]));
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 2, 1, 0, 0), new Insight[0]));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void RebalanceFunctionSecurityChanges(Language language)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
var func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc(time):
|
||||
return time + timedelta(days=1)").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel(time => time.AddDays(1));
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 2, 1, 0, 0), new Insight[0]));
|
||||
|
||||
var security = new Security(Symbols.SPY,
|
||||
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
|
||||
new Cash(Currencies.USD, 1, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
new IdentityCurrencyConverter(Currencies.USD),
|
||||
new RegisteredSecurityDataTypesProvider(),
|
||||
new SecurityCache());
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(security));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 2), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 2), new Insight[0]));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void RebalanceFunctionNewInsights(Language language)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
var func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc(time):
|
||||
return time + timedelta(days=1)").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel(time => time.AddDays(1));
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), new Insight[0]));
|
||||
|
||||
var insights = new[] { Insight.Price(Symbols.SPY, Resolution.Daily, 1, InsightDirection.Down) };
|
||||
|
||||
constructionModel.RebalanceOnInsightChanges = false;
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), insights));
|
||||
constructionModel.RebalanceOnInsightChanges = true;
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), insights));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void RebalanceFunctionInsightExpiration(Language language)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
var func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc(time):
|
||||
return time + timedelta(days=10)").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel(time => time.AddDays(10));
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
constructionModel.SetNextExpiration(new DateTime(2020, 1, 2));
|
||||
constructionModel.RebalanceOnInsightChanges = false;
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 3), new Insight[0]));
|
||||
constructionModel.RebalanceOnInsightChanges = true;
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 3), new Insight[0]));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void NoRebalanceFunction(Language language)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
var func = PyModule.FromString(
|
||||
"RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc():
|
||||
return None"
|
||||
).GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func.Invoke());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 2), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 3), new Insight[0]));
|
||||
|
||||
var security = new Security(Symbols.SPY,
|
||||
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
|
||||
new Cash(Currencies.USD, 1, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
new IdentityCurrencyConverter(Currencies.USD),
|
||||
new RegisteredSecurityDataTypesProvider(),
|
||||
new SecurityCache());
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(security));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), new Insight[0]));
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), new Insight[0]));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void RebalanceFunctionNull(Language language)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
var func = PyModule.FromString(
|
||||
"RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc(time):
|
||||
if time.day == 17:
|
||||
return time + timedelta(hours=1)
|
||||
if time.day == 18:
|
||||
return time
|
||||
return None"
|
||||
).GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel(
|
||||
time =>
|
||||
{
|
||||
if (time.Day == 18)
|
||||
{
|
||||
return time;
|
||||
}
|
||||
if (time.Day == 17)
|
||||
{
|
||||
return time.AddHours(1);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 1), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 1, 23, 0, 0), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 2), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 2, 0, 0, 1), new Insight[0]));
|
||||
|
||||
// day number '17' should trigger rebalance in the next hour
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 17), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 17, 0, 59, 59), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(
|
||||
new DateTime(2020, 1, 17, 1, 0, 0), new Insight[0]));
|
||||
|
||||
constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 20), new Insight[0]);
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 21), new Insight[0]));
|
||||
|
||||
// day number '18' should trigger rebalance immediately
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 18), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 1, 21), new Insight[0]));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python)]
|
||||
[TestCase(Language.CSharp)]
|
||||
public void RebalanceFunctionDateRules(Language language)
|
||||
{
|
||||
var mhdb = MarketHoursDatabase.FromDataFolder();
|
||||
var dateRules = new DateRules(null, new SecurityManager(
|
||||
new TimeKeeper(new DateTime(2015, 1, 1), DateTimeZone.Utc)), DateTimeZone.Utc, mhdb);
|
||||
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
import datetime
|
||||
|
||||
def RebalanceFunc(dateRules):
|
||||
return dateRules.On(datetime.datetime(2015, 1, 10), datetime.datetime(2015, 1, 30))").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func(dateRules));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var dateRule = dateRules.On(new DateTime(2015, 1, 10), new DateTime(2015, 1, 30));
|
||||
constructionModel = new TestPortfolioConstructionModel(dateRule);
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 1), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 10), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 20), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 29), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 2, 1), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 2, 2), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2020, 10, 2), new Insight[0]));
|
||||
}
|
||||
|
||||
[TestCase(Language.Python, 2)]
|
||||
[TestCase(Language.Python, 1)]
|
||||
[TestCase(Language.Python, 0)]
|
||||
[TestCase(Language.CSharp, 0)]
|
||||
public void RebalanceFunctionTimeSpan(Language language, int version)
|
||||
{
|
||||
TestPortfolioConstructionModel constructionModel;
|
||||
if (language == Language.Python)
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel();
|
||||
using (Py.GIL())
|
||||
{
|
||||
if (version == 1)
|
||||
{
|
||||
dynamic func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from System import *
|
||||
|
||||
def RebalanceFunc(timeSpan):
|
||||
return timeSpan").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func(TimeSpan.FromMinutes(20)));
|
||||
}
|
||||
else if(version == 2)
|
||||
{
|
||||
dynamic func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc(constructionModel):
|
||||
return constructionModel.SetRebalancingFunc(timedelta(minutes = 20))").GetAttr("RebalanceFunc");
|
||||
func(constructionModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamic func = PyModule.FromString("RebalanceFunc",
|
||||
@"
|
||||
from datetime import timedelta
|
||||
|
||||
def RebalanceFunc():
|
||||
return timedelta(minutes = 20)").GetAttr("RebalanceFunc");
|
||||
constructionModel.SetRebalancingFunc(func());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constructionModel = new TestPortfolioConstructionModel(time => time.AddMinutes(20));
|
||||
}
|
||||
|
||||
constructionModel.OnSecuritiesChanged(_algorithm, SecurityChanges.None);
|
||||
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 1), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 1, 0, 20, 0), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 1, 0, 22, 0), new Insight[0]));
|
||||
Assert.IsFalse(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 1, 0, 21, 0), new Insight[0]));
|
||||
Assert.IsTrue(constructionModel.IsRebalanceDueWrapper(new DateTime(2015, 1, 1, 0, 40, 0), new Insight[0]));
|
||||
}
|
||||
|
||||
class TestPortfolioConstructionModel : PortfolioConstructionModel
|
||||
{
|
||||
public TestPortfolioConstructionModel(IDateRule dateRule)
|
||||
: this(dateRule.ToFunc())
|
||||
{
|
||||
}
|
||||
|
||||
public TestPortfolioConstructionModel(Func<DateTime, DateTime> func = null)
|
||||
: base(func)
|
||||
{
|
||||
}
|
||||
|
||||
public TestPortfolioConstructionModel(Func<DateTime, DateTime?> func)
|
||||
: base(func)
|
||||
{
|
||||
}
|
||||
|
||||
public new void SetRebalancingFunc(PyObject rebalancingFunc)
|
||||
{
|
||||
base.SetRebalancingFunc(rebalancingFunc);
|
||||
}
|
||||
|
||||
public bool IsRebalanceDueWrapper(DateTime now, Insight[] insights)
|
||||
{
|
||||
return base.IsRebalanceDue(insights, now);
|
||||
}
|
||||
|
||||
public void SetNextExpiration(DateTime nextExpiration)
|
||||
{
|
||||
Algorithm.Insights.Add(
|
||||
new Insight(Symbols.SPY, time => nextExpiration, InsightType.Price, InsightDirection.Down));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 aaplicable 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio;
|
||||
|
||||
[TestFixture]
|
||||
public class PortfolioOptimizerPythonWrapperTests
|
||||
{
|
||||
[Test]
|
||||
public void OptimizeIsCalled()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(),
|
||||
@$"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class CustomPortfolioOptimizer:
|
||||
def __init__(self):
|
||||
self.OptimizeWasCalled = False
|
||||
|
||||
def Optimize(self, historicalReturns, expectedReturns = None, covariance = None):
|
||||
self.OptimizeWasCalled= True");
|
||||
|
||||
var pyCustomOptimizer = module.GetAttr("CustomPortfolioOptimizer").Invoke();
|
||||
var wrapper = new PortfolioOptimizerPythonWrapper(pyCustomOptimizer);
|
||||
var historicalReturns = new double[,] { { -0.50, -0.13 }, { 0.81, 0.31 }, { -0.02, 0.01 } };
|
||||
|
||||
wrapper.Optimize(historicalReturns);
|
||||
pyCustomOptimizer
|
||||
.GetAttr("OptimizeWasCalled")
|
||||
.TryConvert(out bool optimizerWasCalled);
|
||||
|
||||
Assert.IsTrue(optimizerWasCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WrapperThrowsIfOptimizerDoesNotImplementInterface()
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(),
|
||||
@$"
|
||||
from AlgorithmImports import *
|
||||
|
||||
class CustomPortfolioOptimizer:
|
||||
def __init__(self):
|
||||
self.OptimizeWasCalled = False
|
||||
|
||||
def Calculate(self, historicalReturns, expectedReturns = None, covariance = None):
|
||||
pass");
|
||||
|
||||
var pyCustomOptimizer = module.GetAttr("CustomPortfolioOptimizer").Invoke();
|
||||
|
||||
Assert.Throws<NotImplementedException>(() => new PortfolioOptimizerPythonWrapper(pyCustomOptimizer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 aaplicable 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 NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio;
|
||||
|
||||
public abstract class PortfolioOptimizerTestsBase
|
||||
{
|
||||
protected IList<double[,]> HistoricalReturns { get; set; }
|
||||
|
||||
protected IList<double[]> ExpectedReturns { get; set; }
|
||||
|
||||
protected IList<double[,]> Covariances { get; set; }
|
||||
|
||||
protected IList<double[]> ExpectedResults { get; set; }
|
||||
|
||||
protected abstract IPortfolioOptimizer CreateOptimizer();
|
||||
|
||||
public virtual void OptimizeWeightings(int testCaseNumber)
|
||||
{
|
||||
var testOptimizer = CreateOptimizer();
|
||||
|
||||
var result = testOptimizer.Optimize(
|
||||
HistoricalReturns[testCaseNumber],
|
||||
ExpectedReturns[testCaseNumber],
|
||||
Covariances[testCaseNumber]);
|
||||
|
||||
Assert.AreEqual(ExpectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public virtual void EmptyPortfolioReturnsEmptyArrayOfDouble()
|
||||
{
|
||||
var testOptimizer = CreateOptimizer();
|
||||
var historicalReturns = new double[,] { { } };
|
||||
var expectedResult = Array.Empty<double>();
|
||||
|
||||
var result = testOptimizer.Optimize(historicalReturns);
|
||||
|
||||
Assert.AreEqual(result, expectedResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* 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;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class PortfolioTargetCollectionTests
|
||||
{
|
||||
private string _symbol = "SPY";
|
||||
|
||||
[Test]
|
||||
public void TryGetValue()
|
||||
{
|
||||
var collection = new PortfolioTargetCollection();
|
||||
|
||||
Assert.IsFalse(collection.TryGetValue(Symbols.SPY, out var target));
|
||||
|
||||
collection[Symbols.SPY] = new PortfolioTarget(Symbols.SPY, 1);
|
||||
|
||||
Assert.IsTrue(collection.TryGetValue(Symbols.SPY, out target));
|
||||
Assert.AreEqual(target, collection[Symbols.SPY]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndexAccess()
|
||||
{
|
||||
var collection = new PortfolioTargetCollection();
|
||||
collection[Symbols.SPY] = new PortfolioTarget(Symbols.SPY, 1);
|
||||
|
||||
Assert.AreEqual(1, collection.Count);
|
||||
Assert.AreEqual(1, collection.Values.Count);
|
||||
Assert.AreEqual(1, collection.Keys.Count);
|
||||
|
||||
collection[Symbols.IBM] = new PortfolioTarget(Symbols.IBM, 1);
|
||||
|
||||
Assert.AreEqual(2, collection.Count);
|
||||
Assert.AreEqual(2, collection.Values.Count);
|
||||
Assert.AreEqual(2, collection.Keys.Count);
|
||||
|
||||
collection[Symbols.IBM] = null;
|
||||
|
||||
Assert.AreEqual(2, collection.Count);
|
||||
Assert.AreEqual(2, collection.Values.Count);
|
||||
Assert.AreEqual(2, collection.Keys.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Count()
|
||||
{
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var targets = new[] { new PortfolioTarget(Symbols.SPY, 1) };
|
||||
collection.AddRange(targets);
|
||||
|
||||
Assert.AreEqual(1, collection.Count);
|
||||
collection.AddRange(new[] { new PortfolioTarget(Symbols.IBM, 1), new PortfolioTarget(Symbols.AAPL, 1) });
|
||||
Assert.AreEqual(3, collection.Count);
|
||||
|
||||
collection.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsEmpty()
|
||||
{
|
||||
var collection = new PortfolioTargetCollection();
|
||||
Assert.IsTrue(collection.IsEmpty);
|
||||
Assert.IsFalse(collection.ContainsKey(Symbols.SPY));
|
||||
|
||||
collection.Add(new PortfolioTarget(Symbols.SPY, 1));
|
||||
Assert.AreEqual(1, collection.Count);
|
||||
Assert.IsFalse(collection.IsEmpty);
|
||||
Assert.IsTrue(collection.ContainsKey(Symbols.SPY));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddRange()
|
||||
{
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var targets = new[] { new PortfolioTarget(Symbols.SPY, 1), new PortfolioTarget(Symbols.AAPL, 1) };
|
||||
collection.AddRange(targets);
|
||||
Assert.AreEqual(2, collection.Count);
|
||||
Assert.IsTrue(collection.ContainsKey(Symbols.SPY));
|
||||
Assert.IsTrue(collection.ContainsKey(Symbols.AAPL));
|
||||
|
||||
Assert.AreEqual(targets[0], collection[Symbols.SPY]);
|
||||
Assert.AreEqual(targets[1], collection[Symbols.AAPL]);
|
||||
|
||||
Assert.AreEqual(1, collection.Values.Count(target => target == targets[0]));
|
||||
Assert.AreEqual(1, collection.Values.Count(target => target == targets[1]));
|
||||
Assert.AreEqual(1, collection.Keys.Count(symbol => symbol == Symbols.SPY));
|
||||
Assert.AreEqual(1, collection.Keys.Count(symbol => symbol == Symbols.AAPL));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveTargetRespectsReference()
|
||||
{
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateBase(null, _symbol, Market.USA), _symbol);
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, 1);
|
||||
collection.Add(target);
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.IsTrue(collection.Contains(target));
|
||||
// removes by reference even if same symbol
|
||||
Assert.IsFalse(collection.Remove(new PortfolioTarget(symbol, 1)));
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddContainsAndRemoveWork()
|
||||
{
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateBase(null, _symbol, Market.USA), _symbol);
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, 1);
|
||||
collection.Add(target);
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.IsTrue(collection.Contains(target));
|
||||
Assert.IsTrue(collection.Remove(target));
|
||||
Assert.AreEqual(collection.Count, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearFulfilledDoesNotRemoveUnreachedTarget()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
#pragma warning disable CS0618
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
var dummySecurityHolding = new FakeSecurityHolding(equity);
|
||||
equity.Holdings = dummySecurityHolding;
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, -1);
|
||||
collection.Add(target);
|
||||
|
||||
collection.ClearFulfilled(algorithm);
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearRemovesUnreachedTarget()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
var dummySecurityHolding = new FakeSecurityHolding(equity);
|
||||
equity.Holdings = dummySecurityHolding;
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, -1);
|
||||
collection.Add(target);
|
||||
|
||||
collection.Clear();
|
||||
Assert.AreEqual(collection.Count, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearFulfilledRemovesPositiveTarget()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
var dummySecurityHolding = new FakeSecurityHolding(equity);
|
||||
equity.Holdings = dummySecurityHolding;
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, 1);
|
||||
collection.Add(target);
|
||||
|
||||
dummySecurityHolding.SetQuantity(1);
|
||||
collection.ClearFulfilled(algorithm);
|
||||
Assert.AreEqual(collection.Count, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearFulfilledRemovesNegativeTarget()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
var dummySecurityHolding = new FakeSecurityHolding(equity);
|
||||
equity.Holdings = dummySecurityHolding;
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, -1);
|
||||
collection.Add(target);
|
||||
|
||||
dummySecurityHolding.SetQuantity(-1);
|
||||
collection.ClearFulfilled(algorithm);
|
||||
Assert.AreEqual(collection.Count, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByMarginImpactDoesNotReturnTargetsWithNoData()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
algorithm.AddEquity(symbol);
|
||||
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, -1);
|
||||
collection.Add(target);
|
||||
var targets = collection.OrderByMarginImpact(algorithm);
|
||||
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.IsTrue(targets.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByMarginImpactReturnsExpectedTargets()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
equity.Cache.AddData(new TradeBar(DateTime.UtcNow, symbol, 1, 1, 1, 1, 1));
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, -1);
|
||||
collection.Add(target);
|
||||
|
||||
var targets = collection.OrderByMarginImpact(algorithm);
|
||||
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.AreEqual(targets.Count(), 1);
|
||||
Assert.AreEqual(targets.First(), target);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByMarginImpactDoesNotReturnTargetsForWhichUnorderedQuantityIsZeroBecauseTargetIsZero()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
equity.Cache.AddData(new TradeBar(DateTime.UtcNow, symbol, 1, 1, 1, 1, 1));
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, 0);
|
||||
collection.Add(target);
|
||||
|
||||
var targets = collection.OrderByMarginImpact(algorithm);
|
||||
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.IsTrue(targets.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByMarginImpactDoesNotReturnTargetsForWhichUnorderedQuantityIsZeroBecauseTargetReached()
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
var dummySecurityHolding = new FakeSecurityHolding(equity);
|
||||
equity.Holdings = dummySecurityHolding;
|
||||
equity.Cache.AddData(new TradeBar(DateTime.UtcNow, symbol, 1, 1, 1, 1, 1));
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, 1);
|
||||
collection.Add(target);
|
||||
dummySecurityHolding.SetQuantity(1);
|
||||
|
||||
var targets = collection.OrderByMarginImpact(algorithm);
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.IsTrue(targets.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByMarginImpactDoesNotReturnTargetsForWhichUnorderedQuantityIsZeroBecauseOpenOrder()
|
||||
{
|
||||
var orderProcessor = new FakeOrderProcessor();
|
||||
var algorithm = GetAlgorithm(orderProcessor);
|
||||
var symbol = new Symbol(SecurityIdentifier.GenerateEquity(_symbol, Market.USA), _symbol);
|
||||
var equity = algorithm.AddEquity(symbol);
|
||||
#pragma warning restore CS0618
|
||||
equity.Cache.AddData(new TradeBar(DateTime.UtcNow, symbol, 1, 1, 1, 1, 1));
|
||||
var collection = new PortfolioTargetCollection();
|
||||
var target = new PortfolioTarget(symbol, 1);
|
||||
collection.Add(target);
|
||||
|
||||
var openOrderRequest = new SubmitOrderRequest(OrderType.Market, symbol.SecurityType, symbol, 1, 0, 0, DateTime.UtcNow, "");
|
||||
openOrderRequest.SetOrderId(1);
|
||||
var openOrderTicket = new OrderTicket(algorithm.Transactions, openOrderRequest);
|
||||
|
||||
orderProcessor.AddOrder(new MarketOrder(symbol, 1, DateTime.UtcNow));
|
||||
orderProcessor.AddTicket(openOrderTicket);
|
||||
|
||||
var targets = collection.OrderByMarginImpact(algorithm);
|
||||
Assert.AreEqual(collection.Count, 1);
|
||||
Assert.IsTrue(targets.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
private QCAlgorithm GetAlgorithm(IOrderProcessor orderProcessor)
|
||||
{
|
||||
var algorithm = new FakeAlgorithm();
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.Transactions.SetOrderProcessor(orderProcessor);
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
private class FakeSecurityHolding : SecurityHolding
|
||||
{
|
||||
public FakeSecurityHolding(Security security) :
|
||||
base(security, new IdentityCurrencyConverter(security.QuoteCurrency.Symbol))
|
||||
{
|
||||
}
|
||||
public void SetQuantity(int quantity)
|
||||
{
|
||||
Quantity = quantity;
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeAlgorithm : QCAlgorithm
|
||||
{
|
||||
public FakeAlgorithm()
|
||||
{
|
||||
SubscriptionManager.SetDataManager(new DataManagerStub(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Engine;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class PortfolioTargetTests
|
||||
{
|
||||
[Test]
|
||||
public void PercentInvokesBuyingPowerModelAndAddsInExistingHoldings()
|
||||
{
|
||||
const decimal bpmQuantity = 100;
|
||||
const decimal holdings = 50;
|
||||
const decimal targetPercent = 0.5m;
|
||||
|
||||
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
|
||||
algorithm.Initialize();
|
||||
algorithm.PostInitialize();
|
||||
var security = algorithm.Securities.Single().Value;
|
||||
security.SetMarketPrice(new Tick{Value = 1m});
|
||||
security.Holdings.SetHoldings(1m, holdings);
|
||||
|
||||
var buyingPowerMock = new Mock<IBuyingPowerModel>();
|
||||
buyingPowerMock.Setup(bpm => bpm.GetMaximumOrderQuantityForTargetBuyingPower(It.IsAny<GetMaximumOrderQuantityForTargetBuyingPowerParameters>()))
|
||||
.Returns(new GetMaximumOrderQuantityResult(bpmQuantity, null, false));
|
||||
security.BuyingPowerModel = buyingPowerMock.Object;
|
||||
buyingPowerMock.Setup(bpm => bpm.GetLeverage(It.IsAny<Security>())).Returns(1);
|
||||
|
||||
var target = PortfolioTarget.Percent(algorithm, security.Symbol, targetPercent);
|
||||
|
||||
Assert.AreEqual(security.Symbol, target.Symbol);
|
||||
Assert.AreEqual(bpmQuantity + holdings, target.Quantity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PercentReturnsNullIfPriceIsZero()
|
||||
{
|
||||
const decimal holdings = 50;
|
||||
const decimal targetPercent = 1m;
|
||||
|
||||
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
|
||||
algorithm.Initialize();
|
||||
algorithm.PostInitialize();
|
||||
var security = algorithm.Securities.Single().Value;
|
||||
security.SetMarketPrice(new Tick { Value = 0m });
|
||||
security.Holdings.SetHoldings(1m, holdings);
|
||||
|
||||
var target = PortfolioTarget.Percent(algorithm, security.Symbol, targetPercent);
|
||||
|
||||
Assert.IsNull(target);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PercentReturnsNullIfBuyingPowerModelError()
|
||||
{
|
||||
const decimal holdings = 50;
|
||||
const decimal targetPercent = 1m;
|
||||
|
||||
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
|
||||
algorithm.Initialize();
|
||||
algorithm.PostInitialize();
|
||||
var security = algorithm.Securities.Single().Value;
|
||||
security.SetMarketPrice(new Tick { Value = 1m });
|
||||
security.Holdings.SetHoldings(1m, holdings);
|
||||
|
||||
var buyingPowerMock = new Mock<IBuyingPowerModel>();
|
||||
buyingPowerMock.Setup(bpm => bpm.GetMaximumOrderQuantityForTargetBuyingPower(It.IsAny<GetMaximumOrderQuantityForTargetBuyingPowerParameters>()))
|
||||
.Returns(new GetMaximumOrderQuantityResult(0, "The portfolio does not have enough margin available."));
|
||||
security.BuyingPowerModel = buyingPowerMock.Object;
|
||||
buyingPowerMock.Setup(bpm => bpm.GetLeverage(It.IsAny<Security>())).Returns(1);
|
||||
|
||||
var target = PortfolioTarget.Percent(algorithm, security.Symbol, targetPercent);
|
||||
|
||||
Assert.IsNull(target);
|
||||
}
|
||||
|
||||
[TestCase(-3, true)]
|
||||
[TestCase(3, true)]
|
||||
[TestCase(2, false)]
|
||||
[TestCase(-2, false)]
|
||||
[TestCase(0.01, true)]
|
||||
[TestCase(-0.01, true)]
|
||||
[TestCase(0.1, false)]
|
||||
[TestCase(-0.1, false)]
|
||||
[TestCase(0, false)]
|
||||
public void PercentIgnoresExtremeValuesBasedOnSettings(double value, bool shouldBeNull)
|
||||
{
|
||||
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
|
||||
algorithm.Settings.MaxAbsolutePortfolioTargetPercentage = 2;
|
||||
algorithm.Settings.MinAbsolutePortfolioTargetPercentage = 0.1m;
|
||||
algorithm.Initialize();
|
||||
algorithm.PostInitialize();
|
||||
var security = algorithm.Securities.Single().Value;
|
||||
security.SetMarketPrice(new Tick { Value = 1m });
|
||||
|
||||
var buyingPowerMock = new Mock<IBuyingPowerModel>();
|
||||
buyingPowerMock.Setup(bpm => bpm.GetMaximumOrderQuantityForTargetBuyingPower(It.IsAny<GetMaximumOrderQuantityForTargetBuyingPowerParameters>()))
|
||||
.Returns(new GetMaximumOrderQuantityResult(1, null, false));
|
||||
buyingPowerMock.Setup(bpm => bpm.GetLeverage(It.IsAny<Security>())).Returns(1);
|
||||
security.BuyingPowerModel = buyingPowerMock.Object;
|
||||
|
||||
var target = PortfolioTarget.Percent(algorithm, security.Symbol, value);
|
||||
|
||||
if (shouldBeNull)
|
||||
{
|
||||
Assert.IsNull(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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 Accord.Math;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Python;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReturnsSymbolDataTests
|
||||
{
|
||||
[Test]
|
||||
public void ReturnsDoesNotThrowIndexOutOfRangeException()
|
||||
{
|
||||
var dateTime = new DateTime(2020, 02, 01);
|
||||
|
||||
var returnsSymbolData = new ReturnsSymbolData(Symbols.EURGBP, 1, 5);
|
||||
returnsSymbolData.Add(dateTime, 10);
|
||||
returnsSymbolData.Add(dateTime.AddDays(1), 100);
|
||||
returnsSymbolData.Add(dateTime.AddDays(2), 5);
|
||||
|
||||
var returnsSymbolData2 = new ReturnsSymbolData(Symbols.BTCUSD, 1, 5);
|
||||
returnsSymbolData2.Add(dateTime.AddDays(1), 33);
|
||||
returnsSymbolData2.Add(dateTime.AddDays(2), 2);
|
||||
|
||||
var returnsSymbolDatas = new Dictionary<Symbol, ReturnsSymbolData>
|
||||
{
|
||||
{Symbols.EURGBP, returnsSymbolData},
|
||||
{Symbols.BTCUSD, returnsSymbolData2},
|
||||
};
|
||||
|
||||
var result = returnsSymbolDatas.FormReturnsMatrix(new List<Symbol> {Symbols.EURGBP, Symbols.BTCUSD });
|
||||
|
||||
Assert.AreEqual(4, result.Length);
|
||||
|
||||
Assert.AreEqual(5m, result[0, 0]);
|
||||
Assert.AreEqual(2m, result[0, 1]);
|
||||
|
||||
Assert.AreEqual(100m, result[1, 0]);
|
||||
Assert.AreEqual(33m, result[1, 1]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReturnsDoesNotThrowKeyAlreadyExistsException()
|
||||
{
|
||||
var returnsSymbolData = new ReturnsSymbolData(Symbols.EURGBP, 1, 5);
|
||||
|
||||
var dateTime = new DateTime(2020, 02, 01);
|
||||
returnsSymbolData.Add(dateTime, 10);
|
||||
returnsSymbolData.Add(dateTime, 100);
|
||||
|
||||
Assert.AreEqual(1, returnsSymbolData.Returns.Count);
|
||||
Assert.AreEqual(returnsSymbolData.Returns[dateTime], 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void ReturnsSymbolDataMatrixIsSimilarToPandasDataFrame(bool odd)
|
||||
{
|
||||
var symbols = new[] { "A", "B", "C", "D", "E" }
|
||||
.Select(x => Symbol.Create(x, SecurityType.Equity, Market.USA, x));
|
||||
|
||||
var slices = GetHistory(symbols, odd);
|
||||
|
||||
var expected = GetExpectedValue(slices);
|
||||
|
||||
var symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
|
||||
|
||||
slices.PushThrough(bar =>
|
||||
{
|
||||
ReturnsSymbolData symbolData;
|
||||
if (!symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
|
||||
{
|
||||
symbolData = new ReturnsSymbolData(bar.Symbol, 1, 5);
|
||||
symbolDataDict.Add(bar.Symbol, symbolData);
|
||||
}
|
||||
symbolData.Update(bar.EndTime, bar.Value);
|
||||
});
|
||||
|
||||
var matrix = symbolDataDict.FormReturnsMatrix(symbols);
|
||||
var actual = matrix.Determinant();
|
||||
|
||||
Assert.AreEqual(expected, actual, 0.000001);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DuplicateKeyPortfolioConstructionModelDoesNotThrow()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var timezone = algorithm.TimeZone;
|
||||
algorithm.SetDateTime(new DateTime(2018, 8, 7).ConvertToUtc(timezone));
|
||||
algorithm.SetPortfolioConstruction(new DuplicateKeyPortfolioConstructionModel());
|
||||
|
||||
var symbol = Symbols.SPY;
|
||||
|
||||
var security = new Security(
|
||||
SecurityExchangeHours.AlwaysOpen(timezone),
|
||||
new SubscriptionDataConfig(
|
||||
typeof(TradeBar),
|
||||
symbol,
|
||||
Resolution.Daily,
|
||||
timezone,
|
||||
timezone,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
),
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
|
||||
security.SetMarketPrice(new Tick(algorithm.Time, symbol, 1m, 1m));
|
||||
algorithm.Securities.Add(symbol, security);
|
||||
|
||||
algorithm.PortfolioConstruction.OnSecuritiesChanged(algorithm, SecurityChangesTests.AddedNonInternal(security));
|
||||
|
||||
var insights = new[] {Insight.Price(symbol, Time.OneMinute, InsightDirection.Up, .1)};
|
||||
|
||||
Assert.DoesNotThrow(() => algorithm.PortfolioConstruction.CreateTargets(algorithm, insights));
|
||||
}
|
||||
|
||||
private List<Slice> GetHistory(IEnumerable<Symbol> symbols, bool odd)
|
||||
{
|
||||
var reference = DateTime.Now;
|
||||
var rnd = new Random(reference.Second);
|
||||
var symbolsArray = symbols.ToArray();
|
||||
|
||||
return Enumerable.Range(0, 10).Select(t =>
|
||||
{
|
||||
var time = reference.AddDays(t);
|
||||
|
||||
var data = Enumerable.Range(0, symbolsArray.Length).Select(i =>
|
||||
{
|
||||
// Odd data means that one of the symbols have a different timestamp
|
||||
if (odd && i == symbolsArray.Length - 1)
|
||||
{
|
||||
time = time.AddMinutes(10);
|
||||
}
|
||||
return new Tick(time, symbolsArray[i], (decimal)rnd.NextDouble(), 0, 0) {TickType = TickType.Trade};
|
||||
});
|
||||
|
||||
return new Slice(time, data, time);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private double GetExpectedValue(List<Slice> history)
|
||||
{
|
||||
var code = @"
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import math
|
||||
from BlackLittermanOptimizationPortfolioConstructionModel import BlackLittermanOptimizationPortfolioConstructionModel as blopcm
|
||||
|
||||
def GetDeterminantFromHistory(history):
|
||||
returns = dict()
|
||||
history = history.lastprice.unstack(0)
|
||||
|
||||
for symbol, df in history.items():
|
||||
symbolData = blopcm.BlackLittermanSymbolData(symbol, 1, 5)
|
||||
for time, close in df.dropna().items():
|
||||
symbolData.update(time, close)
|
||||
|
||||
returns[symbol] = symbolData.return_
|
||||
|
||||
df = pd.DataFrame(returns)
|
||||
if df.isna().sum().sum() > 0:
|
||||
df[df.columns[-1]] = df[df.columns[-1]].bfill()
|
||||
df = df.dropna()
|
||||
|
||||
return np.linalg.det(df)";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic GetDeterminantFromHistory = PyModule.FromString("GetDeterminantFromHistory", code)
|
||||
.GetAttr("GetDeterminantFromHistory");
|
||||
|
||||
dynamic df = new PandasConverter().GetDataFrame(history);
|
||||
return GetDeterminantFromHistory(df);
|
||||
}
|
||||
}
|
||||
|
||||
private class DuplicateKeyPortfolioConstructionModel : IPortfolioConstructionModel
|
||||
{
|
||||
private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
|
||||
|
||||
public IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
|
||||
{
|
||||
// Updates the ReturnsSymbolData with insights
|
||||
foreach (var insight in insights)
|
||||
{
|
||||
ReturnsSymbolData symbolData;
|
||||
if (_symbolDataDict.TryGetValue(insight.Symbol, out symbolData))
|
||||
{
|
||||
symbolData.Add(algorithm.Time, .1m);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.DoesNotThrow(() => _symbolDataDict.FormReturnsMatrix(insights.Select(x => x.Symbol)));
|
||||
|
||||
return Enumerable.Empty<PortfolioTarget>();
|
||||
}
|
||||
|
||||
public void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
const int period = 2;
|
||||
var reference = algorithm.Time.AddDays(-period);
|
||||
|
||||
foreach (var security in changes.AddedSecurities)
|
||||
{
|
||||
var symbol = security.Symbol;
|
||||
var symbolData = new ReturnsSymbolData(symbol, 1, period);
|
||||
|
||||
for (var i = 0; i <= period; i++)
|
||||
{
|
||||
symbolData.Update(reference.AddDays(i), 1);
|
||||
}
|
||||
|
||||
_symbolDataDict[symbol] = symbolData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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 aaplicable 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 Accord.Math;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class RiskParityPortfolioConstructionModelTests
|
||||
{
|
||||
private DateTime _nowUtc;
|
||||
private QCAlgorithm _algorithm;
|
||||
|
||||
[SetUp]
|
||||
public virtual void SetUp()
|
||||
{
|
||||
_nowUtc = new DateTime(2021, 1, 10);
|
||||
_algorithm = new AlgorithmStub();
|
||||
_algorithm.SetFinishedWarmingUp();
|
||||
_algorithm.Settings.MinimumOrderMarginPortfolioPercentage = 0;
|
||||
_algorithm.Settings.FreePortfolioValue = 250;
|
||||
_algorithm.SetDateTime(_nowUtc.ConvertToUtc(_algorithm.TimeZone));
|
||||
_algorithm.SetCash(1200);
|
||||
var historyProvider = new SubscriptionDataReaderHistoryProvider();
|
||||
_algorithm.SetHistoryProvider(historyProvider);
|
||||
|
||||
historyProvider.Initialize(new HistoryProviderInitializeParameters(
|
||||
new BacktestNodePacket(),
|
||||
null,
|
||||
TestGlobals.DataProvider,
|
||||
TestGlobals.DataCacheProvider,
|
||||
TestGlobals.MapFileProvider,
|
||||
TestGlobals.FactorFileProvider,
|
||||
i => { },
|
||||
true,
|
||||
new DataPermissionManager(),
|
||||
_algorithm.ObjectStore,
|
||||
_algorithm.Settings));
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotReturnTargetsIfSecurityPriceIsZero(Language language)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.AddEquity(Symbols.SPY.Value);
|
||||
algorithm.SetDateTime(DateTime.MinValue.ConvertToUtc(algorithm.TimeZone));
|
||||
|
||||
SetPortfolioConstruction(language, PortfolioBias.Long);
|
||||
|
||||
var insights = new[] { new Insight(_nowUtc, Symbols.SPY, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null) };
|
||||
var actualTargets = algorithm.PortfolioConstruction.CreateTargets(algorithm, insights);
|
||||
|
||||
Assert.AreEqual(0, actualTargets.Count());
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, PortfolioBias.Long)]
|
||||
[TestCase(Language.Python, PortfolioBias.Long)]
|
||||
[TestCase(Language.CSharp, PortfolioBias.Short)]
|
||||
[TestCase(Language.Python, PortfolioBias.Short)]
|
||||
public void PortfolioBiasIsRespected(Language language, PortfolioBias bias)
|
||||
{
|
||||
if (bias == PortfolioBias.Short)
|
||||
{
|
||||
var throwsConstraint = language == Language.CSharp
|
||||
? Throws.InstanceOf<ArgumentException>()
|
||||
: Throws.InstanceOf<ClrBubbledException>().With.InnerException.InstanceOf<ArgumentException>();
|
||||
Assert.That(() => GetPortfolioConstructionModel(language, bias, Resolution.Daily),
|
||||
throwsConstraint.And.Message.EqualTo("Long position must be allowed in RiskParityPortfolioConstructionModel."));
|
||||
return;
|
||||
}
|
||||
|
||||
SetPortfolioConstruction(language, bias);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
foreach (var equity in new[] { aapl, spy })
|
||||
{
|
||||
equity.SetMarketPrice(new Tick(_nowUtc, equity.Symbol, 10, 10));
|
||||
}
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(aapl, spy));
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
new Insight(_nowUtc, aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null),
|
||||
new Insight(_nowUtc, spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Down, null, null)
|
||||
};
|
||||
|
||||
foreach (var target in _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights))
|
||||
{
|
||||
if (target.Quantity == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Assert.AreEqual(Math.Sign((int)bias), Math.Sign(target.Quantity));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void CorrectWeightings(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language, PortfolioBias.Long);
|
||||
|
||||
var aapl = _algorithm.AddEquity("AAPL");
|
||||
var spy = _algorithm.AddEquity("SPY");
|
||||
|
||||
aapl.SetMarketPrice(new Tick(_nowUtc, aapl.Symbol, 10, 10));
|
||||
spy.SetMarketPrice(new Tick(_nowUtc, spy.Symbol, 10, 10));
|
||||
aapl.SetMarketPrice(new Tick(_nowUtc.AddDays(1), aapl.Symbol, 12, 12));
|
||||
spy.SetMarketPrice(new Tick(_nowUtc.AddDays(1), spy.Symbol, 20, 20));
|
||||
aapl.SetMarketPrice(new Tick(_nowUtc.AddDays(2), aapl.Symbol, 13, 13));
|
||||
spy.SetMarketPrice(new Tick(_nowUtc.AddDays(2), spy.Symbol, 30, 30));
|
||||
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, SecurityChangesTests.AddedNonInternal(aapl, spy));
|
||||
|
||||
var insights = new[]
|
||||
{
|
||||
new Insight(_nowUtc.AddDays(2), aapl.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null),
|
||||
new Insight(_nowUtc.AddDays(2), spy.Symbol, TimeSpan.FromDays(1), InsightType.Price, InsightDirection.Up, null, null)
|
||||
};
|
||||
|
||||
_algorithm.Insights.AddRange(insights);
|
||||
|
||||
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, insights).ToArray();
|
||||
Assert.AreEqual(targets[0].Quantity, 30m); // AAPL
|
||||
Assert.AreEqual(targets[1].Quantity, 18m); // SPY
|
||||
}
|
||||
|
||||
protected void SetPortfolioConstruction(Language language, PortfolioBias bias, IPortfolioConstructionModel defaultModel = null)
|
||||
{
|
||||
var model = defaultModel ?? GetPortfolioConstructionModel(language, bias, Resolution.Daily);
|
||||
_algorithm.SetPortfolioConstruction(model);
|
||||
|
||||
foreach (var kvp in _algorithm.Portfolio)
|
||||
{
|
||||
kvp.Value.SetHoldings(kvp.Value.Price, 0);
|
||||
}
|
||||
|
||||
var changes = SecurityChangesTests.AddedNonInternal(_algorithm.Securities.Values.ToArray());
|
||||
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
|
||||
}
|
||||
|
||||
public IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, PortfolioBias bias, Resolution resolution)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new RiskParityPortfolioConstructionModel(resolution, bias, 1, 252, resolution);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(RiskParityPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name)
|
||||
.Invoke(((int)resolution).ToPython(), ((int)bias).ToPython(), 1.ToPython(), 252.ToPython(), ((int)resolution).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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 aaplicable 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.Generic;
|
||||
using System.Linq;
|
||||
using Accord.Math;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class RiskParityPortfolioOptimizerTests
|
||||
{
|
||||
private Dictionary<int, double[][]> _covariances = new();
|
||||
private Dictionary<int, double[]> _riskBudgets = new();
|
||||
private Dictionary<int, double[]> _expectedResults = new();
|
||||
|
||||
[SetUp]
|
||||
public virtual void SetUp()
|
||||
{
|
||||
var riskBudget1 = new double[] {0.5d, 0.5d}; // equal risk distribution
|
||||
var riskBudget2 = new double[] {0.25d, 0.75d}; // 25% risk assigned to A, 75% risk assigned to B
|
||||
|
||||
var covariance1 = new double[][] {new[]{0.25, -0.2}, new[]{-0.2, 0.25}}; // both 50% variance, -80% correlation
|
||||
var covariance2 = new double[][] {new[]{0.01, 0}, new[]{0, 0.04}}; // sigma(A) 10%, sigma(B) 20%, 0% correlation
|
||||
var covariance3 = new double[][] {new[]{1, 0.45}, new[]{0.45, 0.25}}; // sigma(A) 100%, sigma(B) 50%, 90% correlation
|
||||
var covariance4 = new double[][] {new[]{0.25, 0.05}, new[]{0.05, 0.04}}; // sigma(A) 50%, sigma(B) 10%, 25% correlation
|
||||
|
||||
var expectedResult1 = new double[] {3.162278d, 3.162278d};
|
||||
var expectedResult2 = new double[] {7.071068d, 3.535534d};
|
||||
var expectedResult3 = new double[] {0.512989d, 1.025978d};
|
||||
var expectedResult4 = new double[] {1.154701d, 2.886751d};
|
||||
var expectedResult5 = new double[] {2.965685d, 3.285618d};
|
||||
var expectedResult6 = new double[] {5d, 4.330127d};
|
||||
var expectedResult7 = new double[] {0.264749d, 1.510089d};
|
||||
var expectedResult8 = new double[] {0.681774d, 3.924933d};
|
||||
|
||||
_covariances.TryAdd(1, covariance1);
|
||||
_covariances.TryAdd(2, covariance2);
|
||||
_covariances.TryAdd(3, covariance3);
|
||||
_covariances.TryAdd(4, covariance4);
|
||||
_covariances.TryAdd(5, covariance1);
|
||||
_covariances.TryAdd(6, covariance2);
|
||||
_covariances.TryAdd(7, covariance3);
|
||||
_covariances.TryAdd(8, covariance4);
|
||||
|
||||
_riskBudgets.TryAdd(1, riskBudget1);
|
||||
_riskBudgets.TryAdd(2, riskBudget1);
|
||||
_riskBudgets.TryAdd(3, riskBudget1);
|
||||
_riskBudgets.TryAdd(4, riskBudget1);
|
||||
_riskBudgets.TryAdd(5, riskBudget2);
|
||||
_riskBudgets.TryAdd(6, riskBudget2);
|
||||
_riskBudgets.TryAdd(7, riskBudget2);
|
||||
_riskBudgets.TryAdd(8, riskBudget2);
|
||||
|
||||
_expectedResults.TryAdd(1, expectedResult1);
|
||||
_expectedResults.TryAdd(2, expectedResult2);
|
||||
_expectedResults.TryAdd(3, expectedResult3);
|
||||
_expectedResults.TryAdd(4, expectedResult4);
|
||||
_expectedResults.TryAdd(5, expectedResult5);
|
||||
_expectedResults.TryAdd(6, expectedResult6);
|
||||
_expectedResults.TryAdd(7, expectedResult7);
|
||||
_expectedResults.TryAdd(8, expectedResult8);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(0)]
|
||||
[TestCase(1001)]
|
||||
public void TestForExtremeNumberOfVariablesRiskParityNewtonMethodOptimization(int numberOfVariables)
|
||||
{
|
||||
var testOptimizer = new TestRiskParityPortfolioOptimizer();
|
||||
|
||||
if (numberOfVariables < 1 || numberOfVariables > 1000)
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentException>(() => testOptimizer.TestOptimization(numberOfVariables, new double[,]{}, new double[]{}));
|
||||
Assert.That(exception.Message, Is.EqualTo("Argument \"numberOfVariables\" must be a positive integer between 1 and 1000"));
|
||||
return;
|
||||
}
|
||||
|
||||
var result = testOptimizer.TestOptimization(numberOfVariables, new double[,]{}, new double[]{});
|
||||
Assert.AreEqual(new double[] {1d}, result);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
[TestCase(7)]
|
||||
[TestCase(8)]
|
||||
public void TestRiskParityNewtonMethodOptimizationWeightings(int testCaseNumber)
|
||||
{
|
||||
var testOptimizer = new TestRiskParityPortfolioOptimizer();
|
||||
var covariance = JaggedArrayTo2DArray(_covariances[testCaseNumber]);
|
||||
|
||||
var result = testOptimizer.TestOptimization(2, covariance, _riskBudgets[testCaseNumber]);
|
||||
result = result.Select(x => Math.Round(x, 6)).ToArray();
|
||||
|
||||
Assert.AreEqual(_expectedResults[testCaseNumber], result);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
[TestCase(7)]
|
||||
[TestCase(8)]
|
||||
public void TestOptimizeWeightings(int testCaseNumber)
|
||||
{
|
||||
var testOptimizer = new TestRiskParityPortfolioOptimizer();
|
||||
var covariance = JaggedArrayTo2DArray(_covariances[testCaseNumber]);
|
||||
var result = testOptimizer.Optimize(new double[,]{}, _riskBudgets[testCaseNumber], covariance);
|
||||
result = result.Select(x => Math.Round(x, 6)).ToArray();
|
||||
|
||||
var expected = _expectedResults[testCaseNumber];
|
||||
expected = Elementwise.Divide(expected, expected.Sum()).Select(x => Math.Clamp(x, 1e-05, double.MaxValue)).ToArray();
|
||||
expected = expected.Select(x => Math.Round(x, 6)).ToArray();
|
||||
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
private T[,] JaggedArrayTo2DArray<T>(T[][] source)
|
||||
{
|
||||
int FirstDim = source.Length;
|
||||
int SecondDim = source.GroupBy(row => row.Length).Single().Key;
|
||||
|
||||
var result = new T[FirstDim, SecondDim];
|
||||
for (int i = 0; i < FirstDim; ++i)
|
||||
for (int j = 0; j < SecondDim; ++j)
|
||||
result[i, j] = source[i][j];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private class TestRiskParityPortfolioOptimizer : RiskParityPortfolioOptimizer
|
||||
{
|
||||
public TestRiskParityPortfolioOptimizer()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public double[] TestOptimization(int numOfVar, double[,] covariance, double[] budget)
|
||||
{
|
||||
return base.RiskParityNewtonMethodOptimization(numOfVar, covariance, budget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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 NodaTime;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class SectorWeightingPortfolioConstructionModelTests : BaseWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
private const double _weight = 0.01;
|
||||
|
||||
public override double? Weight => Algorithm.Securities.Count == 0 ? default(double) : 1d / Algorithm.Securities.Count;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var prices = new Dictionary<Symbol, Tuple<decimal, string>>
|
||||
{
|
||||
{ Symbol.Create("XXX", SecurityType.Equity, Market.USA), Tuple.Create(55.22m, "") },
|
||||
{ Symbol.Create("B01", SecurityType.Equity, Market.USA), Tuple.Create(55.22m, "B") },
|
||||
{ Symbol.Create("B02", SecurityType.Equity, Market.USA), Tuple.Create(55.22m, "B") },
|
||||
{ Symbol.Create("B03", SecurityType.Equity, Market.USA), Tuple.Create(55.22m, "B") },
|
||||
{ Symbol.Create("T01", SecurityType.Equity, Market.USA), Tuple.Create(145.17m, "T") },
|
||||
{ Symbol.Create("T02", SecurityType.Equity, Market.USA), Tuple.Create(145.17m, "T") },
|
||||
{ Symbol.Create("SPY", SecurityType.Equity, Market.USA), Tuple.Create(281.79m, "X") },
|
||||
};
|
||||
|
||||
Func<Symbol, Security> GetSecurity = symbol =>
|
||||
new Equity(
|
||||
symbol,
|
||||
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
|
||||
var industryTemplateCodeDict = new Dictionary<SecurityIdentifier, string>();
|
||||
foreach (var kvp in prices)
|
||||
{
|
||||
var symbol = kvp.Key;
|
||||
var security = GetSecurity(symbol);
|
||||
var price = kvp.Value.Item1;
|
||||
var sectorCode = kvp.Value.Item2;
|
||||
// The first item does not have a valid sector code,
|
||||
// This procedure shows that the model ignores the securities without it
|
||||
if (!string.IsNullOrEmpty(sectorCode))
|
||||
{
|
||||
security.SetMarketPrice(new Fundamental(Algorithm.Time, symbol)
|
||||
{
|
||||
Value = price
|
||||
});
|
||||
industryTemplateCodeDict[symbol.ID] = kvp.Value.Item2;
|
||||
}
|
||||
security.SetMarketPrice(new Tick(Algorithm.Time, symbol, price, price));
|
||||
Algorithm.Securities.Add(symbol, security);
|
||||
}
|
||||
|
||||
FundamentalService.Initialize(TestGlobals.DataProvider, new TestFundamentalDataProvider(industryTemplateCodeDict), false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void AutomaticallyRemoveInvestedWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// Let's create a position for SPY
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, Algorithm.UtcTime) };
|
||||
|
||||
foreach (var target in Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights))
|
||||
{
|
||||
var holding = Algorithm.Portfolio[target.Symbol];
|
||||
holding.SetHoldings(holding.Price, target.Quantity);
|
||||
Algorithm.Portfolio.SetCash(StartingCash - holding.HoldingsValue);
|
||||
}
|
||||
|
||||
SetUtcTime(Algorithm.UtcTime.AddDays(2));
|
||||
|
||||
// Since we have 3 B, 2 T and 1 X, each security in each sector will get
|
||||
// B => .166%, T => .25, X => 0% (removed)
|
||||
var sectorCount = 2;
|
||||
var groupedBySector = Algorithm.Securities
|
||||
.Where(pair => pair.Value.Fundamentals?.CompanyReference.IndustryTemplateCode != null)
|
||||
.GroupBy(pair => pair.Value.Fundamentals.CompanyReference.IndustryTemplateCode);
|
||||
|
||||
var expectedTargets = new List<PortfolioTarget>();
|
||||
|
||||
foreach (var securities in groupedBySector)
|
||||
{
|
||||
var list = securities.ToList();
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / list.Count / sectorCount *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
expectedTargets.AddRange(list
|
||||
.Select(x => new PortfolioTarget(x.Key, x.Key.Value == "SPY" ? 0
|
||||
: (int)direction * Math.Floor(amount / x.Value.Price))));
|
||||
}
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = Algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, Algorithm.UtcTime)).ToArray();
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights);
|
||||
|
||||
Assert.Greater(Algorithm.Securities.Count, expectedTargets.Count);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void DelistedSecurityEmitsFlatTargetWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime) };
|
||||
var targets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// Removing SPY should clear the key in the insight collection
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(Algorithm.Securities[Symbols.SPY]);
|
||||
Algorithm.PortfolioConstruction.OnSecuritiesChanged(Algorithm, changes);
|
||||
|
||||
// Since we have 3 B, 2 T and 1 X, each security in each sector will get
|
||||
// B => .166%, T => .25, X => 0% (removed)
|
||||
var sectorCount = 2;
|
||||
var groupedBySector = Algorithm.Securities
|
||||
.Where(pair => pair.Value.Fundamentals?.CompanyReference.IndustryTemplateCode != null)
|
||||
.GroupBy(pair => pair.Value.Fundamentals.CompanyReference.IndustryTemplateCode);
|
||||
|
||||
var expectedTargets = new List<PortfolioTarget>();
|
||||
|
||||
foreach (var securities in groupedBySector)
|
||||
{
|
||||
var list = securities.ToList();
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / list.Count / sectorCount *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
expectedTargets.AddRange(list
|
||||
.Select(x => new PortfolioTarget(x.Key, x.Key.Value == "SPY" ? 0
|
||||
: (int)direction * Math.Floor(amount / x.Value.Price))));
|
||||
}
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = Algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, Algorithm.UtcTime)).ToArray();
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights);
|
||||
|
||||
Assert.Greater(Algorithm.Securities.Count, expectedTargets.Count);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void FlatDirectionNotAccountedToAllocation(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// Since we have 3 B, 2 T and 1 X, each security in each sector will get
|
||||
// B => .166%, T => .25, X => 0% (removed)
|
||||
var sectorCount = 2;
|
||||
var groupedBySector = Algorithm.Securities
|
||||
.Where(pair => pair.Value.Fundamentals?.CompanyReference.IndustryTemplateCode != null)
|
||||
.GroupBy(pair => pair.Value.Fundamentals.CompanyReference.IndustryTemplateCode);
|
||||
|
||||
var expectedTargets = new List<PortfolioTarget>();
|
||||
|
||||
foreach (var securities in groupedBySector)
|
||||
{
|
||||
var list = securities.ToList();
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / list.Count / sectorCount *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
expectedTargets.AddRange(list
|
||||
.Select(x => new PortfolioTarget(x.Key, x.Key.Value == "SPY" ? 0
|
||||
: (int)direction * Math.Floor(amount / x.Value.Price))));
|
||||
}
|
||||
|
||||
var insights = Algorithm.Securities.Keys.Select(x =>
|
||||
{
|
||||
// SPY insight direction is flat
|
||||
var actualDirection = x.Value == "SPY" ? InsightDirection.Flat : direction;
|
||||
return GetInsight(x, actualDirection, Algorithm.UtcTime);
|
||||
});
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights.ToArray());
|
||||
|
||||
Assert.Greater(Algorithm.Securities.Count, expectedTargets.Count);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void WeightsBySectorProportionally(Language language)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
// create two insights whose weights sums up to 2
|
||||
var insights = Algorithm.Securities
|
||||
.Select(x => GetInsight(x.Key, InsightDirection.Up, Algorithm.UtcTime))
|
||||
.ToArray();
|
||||
|
||||
// Since we have 3 B, 2 T and 1 X, each security in each secotr will get B => .11%, T => .165, X => .33%
|
||||
var sectorCount = 3;
|
||||
var groupedBySector = Algorithm.Securities
|
||||
.Where(pair => pair.Value.Fundamentals?.CompanyReference.IndustryTemplateCode != null)
|
||||
.Where(pair => insights.Any(insight => pair.Key == insight.Symbol))
|
||||
.GroupBy(pair => pair.Value.Fundamentals.CompanyReference.IndustryTemplateCode);
|
||||
|
||||
var expectedTargets = new List<PortfolioTarget>();
|
||||
|
||||
foreach (var securities in groupedBySector)
|
||||
{
|
||||
var list = securities.ToList();
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / list.Count / sectorCount *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
expectedTargets.AddRange(list
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)InsightDirection.Up * Math.Floor(amount / x.Value.Price))));
|
||||
}
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(6, actualTargets.Count);
|
||||
Assert.Greater(Algorithm.Securities.Count, expectedTargets.Count);
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
public override Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? weight = _weight)
|
||||
{
|
||||
period ??= TimeSpan.FromDays(1);
|
||||
var insight = Insight.Price(symbol, period.Value, direction, weight: weight);
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.CloseTimeUtc = generatedTimeUtc.Add(period.Value);
|
||||
Algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
public override IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new SectorWeightingPortfolioConstructionModel(paramenter);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(SectorWeightingPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestFundamentalDataProvider : IFundamentalDataProvider
|
||||
{
|
||||
private readonly Dictionary<SecurityIdentifier, string> _industryTemplateCodeDict;
|
||||
|
||||
public TestFundamentalDataProvider(Dictionary<SecurityIdentifier, string> industryTemplateCodeDict)
|
||||
{
|
||||
_industryTemplateCodeDict = industryTemplateCodeDict;
|
||||
}
|
||||
public T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
|
||||
{
|
||||
if (securityIdentifier == SecurityIdentifier.Empty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return Get(time, securityIdentifier, name);
|
||||
}
|
||||
private dynamic Get(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty enumName)
|
||||
{
|
||||
var name = Enum.GetName(enumName);
|
||||
switch (name)
|
||||
{
|
||||
case "CompanyReference_IndustryTemplateCode":
|
||||
if(_industryTemplateCodeDict.TryGetValue(securityIdentifier, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void Initialize(IDataProvider dataProvider, bool liveMode)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class SignalExportTargetTests
|
||||
{
|
||||
[TestCaseSource(nameof(SendsTargetsToCollective2AppropriatelyTestCases))]
|
||||
public void SendsTargetsToCollective2Appropriately(string currency, Symbol symbol, decimal quantity, string expectedMessage)
|
||||
{
|
||||
var targetList = new List<PortfolioTarget>() { new(symbol, quantity) };
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetDateTime(new DateTime(2016, 02, 16, 11, 53, 30));
|
||||
algorithm.Portfolio.SetAccountCurrency(currency);
|
||||
var security = algorithm.AddSecurity(symbol);
|
||||
security.QuoteCurrency.ConversionRate = 1;
|
||||
security.FeeModel = new ConstantFeeModel(0);
|
||||
security.SetMarketPrice(new Tick { Value = 100 });
|
||||
var initialMarginRequirement = security.BuyingPowerModel.GetInitialMarginRequirement(new(security, 1));
|
||||
if (initialMarginRequirement.Value == 0)
|
||||
{
|
||||
security.SetBuyingPowerModel(BuyingPowerModel.Null);
|
||||
}
|
||||
|
||||
algorithm.Portfolio.SetCash(1500000);
|
||||
|
||||
using var manager = new Collective2SignalExportHandler("", 0);
|
||||
|
||||
var message = manager.GetMessageSent(new SignalExportTargetParameters { Targets = targetList, Algorithm = algorithm });
|
||||
|
||||
Assert.AreEqual(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Collective2SignalExportManagerDoesNotLogMoreThanOnceWhenUnknownMarket()
|
||||
{
|
||||
var targetList = new List<PortfolioTarget>() { new(Symbols.EURUSD, 1), new PortfolioTarget(Symbols.GBPUSD, 1) };
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetDateTime(new DateTime(2016, 02, 16, 11, 53, 30));
|
||||
algorithm.Portfolio.SetAccountCurrency("USD");
|
||||
var security = algorithm.AddSecurity(Symbols.EURUSD);
|
||||
var security2 = algorithm.AddSecurity(Symbols.GBPUSD);
|
||||
security.SetMarketPrice(new Tick { Value = 100 });
|
||||
security2.SetMarketPrice(new Tick { Value = 100 });
|
||||
|
||||
algorithm.Portfolio.SetCash(50000);
|
||||
|
||||
using var manager = new Collective2SignalExportHandler("", 0);
|
||||
|
||||
for (int count = 0; count < 100; count++)
|
||||
{
|
||||
manager.GetMessageSent(new SignalExportTargetParameters { Targets = targetList, Algorithm = algorithm });
|
||||
}
|
||||
|
||||
Assert.AreEqual(3, algorithm.DebugMessages.Count);
|
||||
var debugMessages = algorithm.DebugMessages.ToList();
|
||||
Assert.IsTrue(debugMessages[0].Contains($"The market of the symbol {Symbols.EURUSD.Value} was unexpected: {Symbols.EURUSD.ID.Market}. Using 'DEFAULT' as market", StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.IsTrue(debugMessages[1].Contains($"Warning: Collective2 failed to calculate target quantity for {targetList[0]}. The smallest quantity C2 trades is \"1\" which is a mini-lot (10,000 currency units), and the target quantity is 498. Will return 0 for all similar cases.", StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.IsTrue(debugMessages[2].Contains($"The market of the symbol {Symbols.GBPUSD.Value} was unexpected: {Symbols.GBPUSD.ID.Market}. Using 'DEFAULT' as market", StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Collective2SignalExportManagerDoesNotLogMoreThanOnceWhenUnknownSecurityType()
|
||||
{
|
||||
var targetList = new List<PortfolioTarget>() { new(Symbols.BTCUSD, 1), new PortfolioTarget(Symbols.XAUJPY, 1) };
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
algorithm.SetDateTime(new DateTime(2016, 02, 16, 11, 53, 30));
|
||||
algorithm.Portfolio.SetAccountCurrency("USD");
|
||||
var security = algorithm.AddSecurity(Symbols.BTCUSD);
|
||||
var security2 = algorithm.AddSecurity(Symbols.XAUJPY);
|
||||
security.SetMarketPrice(new Tick { Value = 100 });
|
||||
security2.SetMarketPrice(new Tick { Value = 100 });
|
||||
|
||||
algorithm.Portfolio.SetCash(50000);
|
||||
|
||||
using var manager = new Collective2SignalExportHandler("", 0);
|
||||
|
||||
for (int count = 0; count < 100; count++)
|
||||
{
|
||||
manager.GetMessageSent(new SignalExportTargetParameters { Targets = targetList, Algorithm = algorithm });
|
||||
}
|
||||
|
||||
Assert.AreEqual(2, algorithm.DebugMessages.Count);
|
||||
var debugMessages = algorithm.DebugMessages.ToList();
|
||||
Assert.IsTrue(debugMessages[0].Contains($"Unexpected security type found: {security.Symbol.SecurityType}. Collective2 just accepts: Equity, Future, Option, Index Option and Stock", StringComparison.InvariantCultureIgnoreCase));
|
||||
Assert.IsTrue(debugMessages[1].Contains($"Unexpected security type found: {security2.Symbol.SecurityType}. Collective2 just accepts: Equity, Future, Option, Index Option and Stock", StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Collective2ConvertsPercentageToQuantityAppropriately()
|
||||
{
|
||||
var symbols = new List<Symbol>()
|
||||
{
|
||||
Symbols.SPY,
|
||||
Symbols.EURUSD,
|
||||
Symbols.AAPL,
|
||||
Symbols.IBM,
|
||||
Symbols.GOOG,
|
||||
Symbols.MSFT,
|
||||
Symbols.CAT
|
||||
};
|
||||
|
||||
var targetList = new List<PortfolioTarget>()
|
||||
{
|
||||
new PortfolioTarget(Symbols.SPY, (decimal)0.01),
|
||||
new PortfolioTarget(Symbols.EURUSD, (decimal)0.99),
|
||||
new PortfolioTarget(Symbols.AAPL, (decimal)(-0.01)),
|
||||
new PortfolioTarget(Symbols.IBM, (decimal)(-0.99)),
|
||||
new PortfolioTarget(Symbols.GOOG, (decimal)0.0),
|
||||
new PortfolioTarget(Symbols.MSFT, (decimal)1.0),
|
||||
new PortfolioTarget(Symbols.CAT, (decimal)(-1.0))
|
||||
};
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
AddSymbols(symbols, algorithm);
|
||||
algorithm.Portfolio.SetCash(50000);
|
||||
algorithm.Settings.MinimumOrderMarginPortfolioPercentage = 0;
|
||||
algorithm.Settings.FreePortfolioValue = 250;
|
||||
|
||||
using var manager = new Collective2SignalExportHandler("", 0);
|
||||
|
||||
var expectedQuantities = new Dictionary<string, int>()
|
||||
{
|
||||
{ "SPY", 4 },
|
||||
// 492 was rounded down to zero because 1 is a minilot of 10,000 USD
|
||||
// https://support.collective2.com/hc/en-us/articles/360038042774-Forex-minilots
|
||||
{ "EURUSD", 0},
|
||||
{ "AAPL", -4 },
|
||||
{ "IBM", -492 },
|
||||
{ "GOOG", 0 },
|
||||
{ "MSFT", 497 },
|
||||
{ "CAT", -497 }
|
||||
};
|
||||
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
var quantity = manager.ConvertPercentageToQuantity(algorithm, target);
|
||||
Assert.AreEqual(expectedQuantities[target.Symbol.Value], quantity);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendsTargetsToCrunchDAOAppropriately()
|
||||
{
|
||||
var symbols = new List<Symbol>()
|
||||
{
|
||||
Symbols.SPY,
|
||||
Symbols.AAPL,
|
||||
Symbols.CAT
|
||||
};
|
||||
|
||||
var targetList = new List<PortfolioTarget>()
|
||||
{
|
||||
new PortfolioTarget(Symbols.SPY, (decimal)0.2),
|
||||
new PortfolioTarget(Symbols.AAPL, (decimal)0.2),
|
||||
new PortfolioTarget(Symbols.CAT, (decimal)0.2),
|
||||
};
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
AddSymbols(symbols, algorithm);
|
||||
using var manager = new CrunchDAOSignalExportHandler("", "");
|
||||
|
||||
var message = manager.GetMessageSent(new SignalExportTargetParameters { Targets = targetList, Algorithm = algorithm });
|
||||
var expectedMessage = "ticker,date,signal\nSPY,2016-02-16,0.2\nAAPL,2016-02-16,0.2\nCAT,2016-02-16,0.2\n";
|
||||
|
||||
Assert.AreEqual(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CrunchDAOSignalExportReturnsFalseWhenSymbolIsNotAllowed()
|
||||
{
|
||||
var symbols = new List<Symbol>()
|
||||
{
|
||||
Symbols.ES_Future_Chain,
|
||||
Symbols.SPY_Option_Chain,
|
||||
Symbols.EURUSD,
|
||||
Symbols.BTCUSD,
|
||||
};
|
||||
|
||||
var targetList = new List<PortfolioTarget>();
|
||||
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
targetList.Add(new PortfolioTarget(symbol, (decimal)0.1));
|
||||
}
|
||||
|
||||
var algorithm = new AlgorithmStub();
|
||||
AddSymbols(symbols, algorithm);
|
||||
algorithm.Portfolio.SetCash(50000);
|
||||
using var manager = new CrunchDAOSignalExport("", "");
|
||||
|
||||
var result = manager.Send(new SignalExportTargetParameters { Targets = targetList, Algorithm = algorithm });
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CrunchDAOSignalExportReturnsFalseWhenPortfolioTargetListIsEmpty()
|
||||
{
|
||||
var targetList = new List<PortfolioTarget>();
|
||||
using var manager = new CrunchDAOSignalExport("", "");
|
||||
var algorithm = new QCAlgorithm();
|
||||
|
||||
var result = manager.Send(new SignalExportTargetParameters { Targets = targetList, Algorithm = algorithm });
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendsTargetsToNumeraiAppropriately()
|
||||
{
|
||||
var targets = new List<PortfolioTarget>()
|
||||
{
|
||||
new PortfolioTarget(Symbols.SGX, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.AAPL, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.MSFT, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.ZNGA, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.FXE, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.LODE, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.IBM, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.GOOG, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.NFLX, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.CAT, (decimal)0.1)
|
||||
};
|
||||
|
||||
using var manager = new NumeraiSignalExportHandler("", "", "");
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetDateTime(new DateTime(2023, 03, 03));
|
||||
|
||||
var message = manager.GetMessageSent(new SignalExportTargetParameters { Targets = targets, Algorithm = algorithm });
|
||||
var expectedMessage = "numerai_ticker,signal\nSGX SP,0.05\nAAPL US,0.1\nMSFT US,0.1\nZNGA US,0.05\nFXE US,0.05\nLODE US,0.05\nIBM US,0.05\nGOOG US,0.1\nNFLX US,0.1\nCAT US,0.1\n";
|
||||
|
||||
Assert.AreEqual(expectedMessage, message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NumeraiReturnsFalseWhenSymbolIsNotAllowed()
|
||||
{
|
||||
var targets = new List<PortfolioTarget>()
|
||||
{
|
||||
new PortfolioTarget(Symbols.SGX, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.AAPL, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.MSFT, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.ZNGA, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.FXE, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.LODE, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.IBM, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.GOOG, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.NFLX, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.EURUSD, (decimal)0.1)
|
||||
};
|
||||
|
||||
using var manager = new NumeraiSignalExport("", "", "");
|
||||
var algorithm = new QCAlgorithm();
|
||||
var result = manager.Send(new SignalExportTargetParameters { Targets = targets, Algorithm = algorithm });
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NumeraiReturnsFalseWhenNumberOfTargetsIsLessThanTen()
|
||||
{
|
||||
var targets = new List<PortfolioTarget>()
|
||||
{
|
||||
new PortfolioTarget(Symbols.SGX, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.AAPL, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.MSFT, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.ZNGA, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.FXE, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.LODE, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.IBM, (decimal)0.05),
|
||||
new PortfolioTarget(Symbols.GOOG, (decimal)0.1),
|
||||
new PortfolioTarget(Symbols.NFLX, (decimal)0.1),
|
||||
};
|
||||
|
||||
using var manager = new NumeraiSignalExport("", "", "");
|
||||
var algorithm = new QCAlgorithm();
|
||||
var result = manager.Send(new SignalExportTargetParameters { Targets = targets, Algorithm = algorithm });
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestCase(SecurityType.Equity, "SPY", 68)]
|
||||
[TestCase(SecurityType.Equity, "AAPL", -68)]
|
||||
[TestCase(SecurityType.Equity, "GOOG", 345)]
|
||||
[TestCase(SecurityType.Equity, "IBM", 0)]
|
||||
[TestCase(SecurityType.Forex, "EURUSD", 90)]
|
||||
[TestCase(SecurityType.Forex, "EURUSD", -90)]
|
||||
[TestCase(SecurityType.Future, "ES", 4)]
|
||||
[TestCase(SecurityType.Future, "ES", -4)]
|
||||
|
||||
public void SignalExportManagerGetsCorrectPortfolioTargetArray(SecurityType securityType, string ticker, int quantity)
|
||||
{
|
||||
var algorithm = new AlgorithmStub(true);
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.SetCash(100000);
|
||||
|
||||
var security = algorithm.AddSecurity(securityType, ticker);
|
||||
security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m));
|
||||
security.Holdings.SetHoldings(144.81m, quantity);
|
||||
|
||||
var signalExportManagerHandler = new SignalExportManagerHandler(algorithm);
|
||||
var result = signalExportManagerHandler.GetPortfolioTargets(out PortfolioTarget[] portfolioTargets);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
var target = portfolioTargets[0];
|
||||
var targetQuantity = (int)PortfolioTarget.Percent(algorithm, target.Symbol, target.Quantity).Quantity;
|
||||
// The quantites can differ by one because of the number of lots for certain securities
|
||||
Assert.AreEqual(quantity, targetQuantity, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SignalExportManagerDoesNotThrowOnZeroPrice()
|
||||
{
|
||||
var algorithm = new AlgorithmStub(true);
|
||||
algorithm.SetDateTime(new DateTime(2024, 02, 16, 11, 53, 30));
|
||||
|
||||
var security = algorithm.AddSecurity(Symbols.SPY);
|
||||
// Set the market price to 0 to simulate the edge case being tested
|
||||
security.SetMarketPrice(new Tick { Value = 0 });
|
||||
|
||||
using var manager = new Collective2SignalExportHandler("", 0);
|
||||
// Ensure ConvertPercentageToQuantity does not throw when price is 0
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
var result = manager.ConvertPercentageToQuantity(algorithm, new PortfolioTarget(Symbols.SPY, 0));
|
||||
Assert.AreEqual(0, result);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SignalExportManagerHandlesIndexOptions()
|
||||
{
|
||||
var algorithm = new AlgorithmStub(true);
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.SetCash(100000);
|
||||
|
||||
int quantity = 123;
|
||||
var underlying = algorithm.AddIndex("SPX", Resolution.Minute).Symbol;
|
||||
|
||||
// Create the option contract (IndexOption) with specific parameters
|
||||
var option = Symbol.CreateOption(
|
||||
underlying,
|
||||
"SPXW",
|
||||
Market.USA,
|
||||
OptionStyle.European,
|
||||
OptionRight.Call,
|
||||
3800m,
|
||||
new DateTime(2021, 1, 04));
|
||||
|
||||
var security = algorithm.AddIndexOptionContract(option, Resolution.Minute);
|
||||
security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m));
|
||||
security.Holdings.SetHoldings(144.81m, quantity);
|
||||
|
||||
// Initialize the SignalExportManagerHandler and get portfolio targets
|
||||
var signalExportManagerHandler = new SignalExportManagerHandler(algorithm);
|
||||
var result = signalExportManagerHandler.GetPortfolioTargets(out PortfolioTarget[] portfolioTargets);
|
||||
|
||||
// Assert that the result is successful
|
||||
Assert.IsTrue(result);
|
||||
|
||||
// Get the portfolio target and verify the quantity matches
|
||||
var target = portfolioTargets[0];
|
||||
var targetQuantity = (int)PortfolioTarget.Percent(algorithm, target.Symbol, target.Quantity).Quantity;
|
||||
Assert.AreEqual(quantity, targetQuantity);
|
||||
|
||||
// Ensure the symbol is of type IndexOption
|
||||
Assert.IsTrue(target.Symbol.SecurityType == SecurityType.IndexOption);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SignalExportManagerIgnoresIndexSecurities()
|
||||
{
|
||||
var algorithm = new AlgorithmStub(true);
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.SetCash(100000);
|
||||
|
||||
var security = algorithm.AddIndexOption("SPX", "SPXW");
|
||||
security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m));
|
||||
security.Holdings.SetHoldings(144.81m, 10);
|
||||
|
||||
var signalExportManagerHandler = new SignalExportManagerHandler(algorithm);
|
||||
var result = signalExportManagerHandler.GetPortfolioTargets(out PortfolioTarget[] portfolioTargets);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.IsFalse(portfolioTargets.Where(x => x.Symbol.SecurityType == SecurityType.Index).Any());
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
var algorithm = new AlgorithmStub(true);
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
algorithm.SetCash(-100000);
|
||||
|
||||
var signalExportManagerHandler = new SignalExportManagerHandler(algorithm);
|
||||
Assert.IsFalse(signalExportManagerHandler.GetPortfolioTargets(out _));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EmptySignalExportList()
|
||||
{
|
||||
var algorithm = new AlgorithmStub(true);
|
||||
algorithm.SetLiveMode(true);
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
algorithm.SetCash(100000);
|
||||
|
||||
var security = algorithm.AddSecurity(Symbols.SPY);
|
||||
security.SetMarketPrice(new Tick(new DateTime(2022, 01, 04), security.Symbol, 144.80m, 144.82m));
|
||||
security.Holdings.SetHoldings(144.81m, 100);
|
||||
|
||||
var utcTime = DateTime.UtcNow;
|
||||
var signalExportManagerHandler = new SignalExportManagerHandler(algorithm);
|
||||
signalExportManagerHandler.OnOrderEvent(new OrderEvent(0, security.Symbol, utcTime.AddMinutes(-1), OrderStatus.Filled, OrderDirection.Buy, 100, 100, new Orders.Fees.OrderFee(new Securities.CashAmount(1, "USD"))));
|
||||
|
||||
Assert.DoesNotThrow(() => signalExportManagerHandler.SetTargetPortfolioFromPortfolio());
|
||||
Assert.DoesNotThrow(() => signalExportManagerHandler.Flush(utcTime));
|
||||
}
|
||||
|
||||
private static object[] SendsTargetsToCollective2AppropriatelyTestCases =
|
||||
{
|
||||
new object[] { "USD", Symbols.SPY, 0.2m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""SPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""CS""},""quantity"":2992.0}]}" },
|
||||
new object[] { "USD", Symbols.EURUSD, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""EUR/USD"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "USD", Symbols.EURGBP, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""EUR/GBP"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "USD", Symbols.GBPJPY, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""GBP/JPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "USD", Symbols.GBPUSD, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""GBP/USD"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "USD", Symbols.USDJPY, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""USD/JPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "USD", Symbols.CreateOptionSymbol("SPY", OptionRight.Call, 192m, new DateTime(2016, 02, 15)), 0.2m, @"{""StrategyId"":0,""Positions"":[]}" },
|
||||
new object[] { "USD", Symbols.CreateOptionSymbol("SPY", OptionRight.Call, 192m, new DateTime(2056, 02, 19)), 0.2m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""SPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""OPT"",""maturityMonthYear"":""20560218"",""putOrCall"":1,""strikePrice"":192.0},""quantity"":29.0}]}" },
|
||||
new object[] { "USD", Symbols.CreateOptionSymbol("SPY", OptionRight.Put, 192m, new DateTime(2056, 02, 19)), 0.2m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""SPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""OPT"",""maturityMonthYear"":""20560218"",""putOrCall"":0,""strikePrice"":192.0},""quantity"":29.0}]}" },
|
||||
new object[] { "USD", Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""EUR/USD"",""currency"":""USD"",""securityExchange"":""FXCM"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "USD", Symbol.Create("NQX", SecurityType.IndexOption, Market.USA), 0.2m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""NQX"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""OPT"",""putOrCall"":1,""strikePrice"":0.0},""quantity"":29.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("NIFTY", Market.India, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""NIFTY"",""currency"":""INR"",""securityExchange"":""XNSE"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":299250.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("HSI", Market.HKFE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""HSI"",""currency"":""HKD"",""securityExchange"":""XHKF"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":24.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("ZG", Market.NYSELIFFE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""ZG"",""currency"":""USD"",""securityExchange"":""XNLI"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":73.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("FESX", Market.EUREX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""FESX"",""currency"":""EUR"",""securityExchange"":""XEUR"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":498.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("KC", Market.ICE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""KC"",""currency"":""USD"",""securityExchange"":""IEPA"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":415.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("VIX", Market.CFE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""VIX"",""currency"":""USD"",""securityExchange"":""XCBF"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":14962.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("ZC", Market.CBOT, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""ZC"",""currency"":""USD"",""securityExchange"":""XCBT"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":2770.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("GC", Market.COMEX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""GC"",""currency"":""USD"",""securityExchange"":""XCEC"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":316.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("CL", Market.NYMEX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""CL"",""currency"":""USD"",""securityExchange"":""XNYM"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":201.0}]}" },
|
||||
new object[] { "USD", Symbol.CreateFuture("NK", Market.SGX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""NK"",""currency"":""JPY"",""securityExchange"":""XSES"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":425.0}]}" },
|
||||
new object[] { "EUR", Symbols.SPY, 0.2m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""SPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""CS""},""quantity"":2992.0}]}" },
|
||||
new object[] { "EUR", Symbols.EURUSD, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""EUR/USD"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "EUR", Symbols.EURGBP, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""EUR/GBP"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "EUR", Symbols.GBPJPY, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""GBP/JPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "EUR", Symbols.GBPUSD, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""GBP/USD"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "EUR", Symbols.USDJPY, 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""USD/JPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "EUR", Symbols.CreateOptionSymbol("SPY", OptionRight.Call, 192m, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""SPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""OPT"",""maturityMonthYear"":""20560218"",""putOrCall"":1,""strikePrice"":192.0},""quantity"":149.0}]}" },
|
||||
new object[] { "EUR", Symbols.CreateOptionSymbol("SPY", OptionRight.Put, 192m, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""SPY"",""currency"":""USD"",""securityExchange"":""DEFAULT"",""securityType"":""OPT"",""maturityMonthYear"":""20560218"",""putOrCall"":0,""strikePrice"":192.0},""quantity"":149.0}]}" },
|
||||
new object[] { "EUR", Symbols.CreateOptionSymbol("SPY", OptionRight.Call, 192m, new DateTime(2016, 02, 15)), 0.2m, @"{""StrategyId"":0,""Positions"":[]}" },
|
||||
new object[] { "EUR", Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""EUR/USD"",""currency"":""USD"",""securityExchange"":""FXCM"",""securityType"":""FOR""},""quantity"":1.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("NIFTY", Market.India, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""NIFTY"",""currency"":""INR"",""securityExchange"":""XNSE"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":299250.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("HSI", Market.HKFE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""HSI"",""currency"":""HKD"",""securityExchange"":""XHKF"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":24.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("ZG", Market.NYSELIFFE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""ZG"",""currency"":""USD"",""securityExchange"":""XNLI"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":73.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("FESX", Market.EUREX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""FESX"",""currency"":""EUR"",""securityExchange"":""XEUR"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":498.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("KC", Market.ICE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""KC"",""currency"":""USD"",""securityExchange"":""IEPA"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":415.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("VIX", Market.CFE, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""VIX"",""currency"":""EUR"",""securityExchange"":""XCBF"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":14962.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("ZC", Market.CBOT, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""ZC"",""currency"":""USD"",""securityExchange"":""XCBT"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":2770.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("GC", Market.COMEX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""GC"",""currency"":""USD"",""securityExchange"":""XCEC"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":316.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("CL", Market.NYMEX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""CL"",""currency"":""USD"",""securityExchange"":""XNYM"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":201.0}]}" },
|
||||
new object[] { "EUR", Symbol.CreateFuture("NK", Market.SGX, new DateTime(2056, 02, 19)), 1m, @"{""StrategyId"":0,""Positions"":[{""exchangeSymbol"":{""symbol"":""NK"",""currency"":""JPY"",""securityExchange"":""XSES"",""securityType"":""FUT"",""maturityMonthYear"":""20560219""},""quantity"":425.0}]}" },
|
||||
};
|
||||
|
||||
private static void AddSymbols(List<Symbol> symbols, QCAlgorithm algorithm)
|
||||
{
|
||||
algorithm.SetDateTime(new DateTime(2016, 02, 16, 11, 53, 30));
|
||||
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
var security = algorithm.AddSecurity(symbol);
|
||||
security.SetMarketPrice(new Tick { Value = 100 });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TradebarConfiguration for the given symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">Symbol for which we want to create a TradeBarConfiguration</param>
|
||||
/// <returns>A new TradebarConfiguration for the given symbol</returns>
|
||||
private static SubscriptionDataConfig CreateTradeBarConfig(Symbol symbol)
|
||||
{
|
||||
return new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork, true, true, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler class to test how SignalExportManager obtains the portfolio targets from the algorithm's portfolio
|
||||
/// </summary>
|
||||
private class SignalExportManagerHandler : SignalExportManager
|
||||
{
|
||||
public SignalExportManagerHandler(IAlgorithm algorithm) : base(algorithm)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler method to obtain portfolio targets from algorithm's portfolio
|
||||
/// </summary>
|
||||
/// <param name="targets">An array of portfolio targets from the algorithm's Portfolio</param>
|
||||
/// <returns>True if TotalPortfolioValue was bigger than zero</returns>
|
||||
public bool GetPortfolioTargets(out PortfolioTarget[] targets)
|
||||
{
|
||||
return base.GetPortfolioTargets(out targets);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler class to test how Collective2SignalExport converts target percentage to number of shares. Additionally,
|
||||
/// to test the message sent to Collective2 API
|
||||
/// </summary>
|
||||
private class Collective2SignalExportHandler : Collective2SignalExport
|
||||
{
|
||||
public Collective2SignalExportHandler(string apiKey, int systemId) : base(apiKey, systemId)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler method to test how Collective2SignalExport converts target percentage to number of shares
|
||||
/// </summary>
|
||||
/// <param name="algorithm">Algorithm being ran</param>
|
||||
/// <param name="target">Target with quantity as percentage</param>
|
||||
/// <returns>Number of shares for the given target percentage</returns>
|
||||
public int ConvertPercentageToQuantity(IAlgorithm algorithm, PortfolioTarget target)
|
||||
{
|
||||
return base.ConvertPercentageToQuantity(algorithm, target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler method to test the message sent to Collective2 API
|
||||
/// </summary>
|
||||
/// <param name="parameters">A list of holdings from the portfolio
|
||||
/// expected to be sent to Collective2 API and the algorithm being ran</param>
|
||||
/// <returns>Message sent to Collective2 API</returns>
|
||||
public string GetMessageSent(SignalExportTargetParameters parameters)
|
||||
{
|
||||
ConvertHoldingsToCollective2(parameters, out List<Collective2Position> positions);
|
||||
var message = CreateMessage(positions);
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler class to test the message sent to CrunchDAO API
|
||||
/// </summary>
|
||||
private class CrunchDAOSignalExportHandler : CrunchDAOSignalExport
|
||||
{
|
||||
public CrunchDAOSignalExportHandler(string apiKey, string model, string submissionName = "", string comment = "") : base(apiKey, model, submissionName, comment)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler method to test the message sent to CrunchDAO API
|
||||
/// </summary>
|
||||
/// <param name="parameters">A list of holdings from the portfolio
|
||||
/// expected to be sent to CrunchDAO2 API and the algorithm being ran</param>
|
||||
/// <returns>Message sent to CrunchDAO API</returns>
|
||||
public string GetMessageSent(SignalExportTargetParameters parameters)
|
||||
{
|
||||
ConvertToCSVFormat(parameters, out string message);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler class to test message sent to Numerai API
|
||||
/// </summary>
|
||||
private class NumeraiSignalExportHandler : NumeraiSignalExport
|
||||
{
|
||||
public NumeraiSignalExportHandler(string publicId, string secretId, string modelId, string fileName = "predictions.csv") : base(publicId, secretId, modelId, fileName)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler method to test the message sent to Numerai API
|
||||
/// </summary>
|
||||
/// <param name="parameters">A list of holdings from the portfolio
|
||||
/// expected to be sent to Numerai API and the algorithm being ran</param>
|
||||
/// <returns>Message sent to Numerai API</returns>
|
||||
public string GetMessageSent(SignalExportTargetParameters parameters)
|
||||
{
|
||||
ConvertTargetsToNumerai(parameters, out string message);
|
||||
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 },
|
||||
};
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 aaplicable 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 NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class UnconstrainedMeanVariancePortfolioOptimizerTests : PortfolioOptimizerTestsBase
|
||||
{
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
HistoricalReturns = new List<double[,]>
|
||||
{
|
||||
new double[,] { { 0.76, -0.06, 1.22, 0.17 }, { 0.02, 0.28, 1.25, -0.00 }, { -0.50, -0.13, -0.50, -0.03 }, { 0.81, 0.31, 2.39, 0.26 }, { -0.02, 0.02, 0.06, 0.01 } },
|
||||
new double[,] { { -0.15, 0.67, 0.45 }, { -0.44, -0.10, 0.07 }, { 0.04, -0.41, 0.01 }, { 0.01, 0.03, 0.02 } },
|
||||
new double[,] { { -0.02, 0.65, 1.25 }, { -0.29, -0.39, -0.50 }, { 0.29, 0.58, 2.39 }, { 0.00, -0.01, 0.06 } },
|
||||
new double[,] { { 0.76, 0.25, 0.21 }, { 0.02, -0.15, 0.45 }, { -0.50, -0.44, 0.07 }, { 0.81, 0.04, 0.01 }, { -0.02, 0.01, 0.02 } }
|
||||
};
|
||||
|
||||
ExpectedReturns = new List<double[]>
|
||||
{
|
||||
new double[] { 0.21, 0.08, 0.88, 0.08 },
|
||||
new double[] { -0.13, 0.05, 0.14 },
|
||||
null,
|
||||
null
|
||||
};
|
||||
|
||||
Covariances = new List<double[,]>
|
||||
{
|
||||
new double[,] { { 0.31, 0.05, 0.55, 0.07 }, { 0.05, 0.04, 0.18, 0.01 }, { 0.55, 0.18, 1.28, 0.12 }, { 0.07, 0.01, 0.12, 0.02 } },
|
||||
new double[,] { { 0.05, -0.02, -0.01 }, { -0.02, 0.21, 0.09 }, { -0.01, 0.09, 0.04 } },
|
||||
new double[,] { { 0.06, 0.09, 0.28 }, { 0.09, 0.25, 0.58 }, { 0.28, 0.58, 1.66 } },
|
||||
null
|
||||
};
|
||||
|
||||
ExpectedResults = new List<double[]>
|
||||
{
|
||||
new double[] { -13.288136, -23.322034, 8.79661, 9.389831 },
|
||||
new double[] { -0.142857, -35.285714, 82.857143 },
|
||||
new double[] { -13.232262, -3.709534, 4.009978 },
|
||||
new double[] { 4.621852, -9.651736, 5.098332 },
|
||||
};
|
||||
}
|
||||
|
||||
protected override IPortfolioOptimizer CreateOptimizer()
|
||||
{
|
||||
return new UnconstrainedMeanVariancePortfolioOptimizer();
|
||||
}
|
||||
|
||||
[TestCase(0)]
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public override void OptimizeWeightings(int testCaseNumber)
|
||||
{
|
||||
base.OptimizeWeightings(testCaseNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Tests.Common.Securities;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework
|
||||
{
|
||||
[TestFixture]
|
||||
public class QCAlgorithmFrameworkTests
|
||||
{
|
||||
[Test]
|
||||
public void SetsInsightGeneratedAndCloseTimes()
|
||||
{
|
||||
var eventFired = false;
|
||||
var algo = new QCAlgorithm();
|
||||
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
|
||||
algo.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
algo.InsightsGenerated += (algorithm, data) =>
|
||||
{
|
||||
eventFired = true;
|
||||
var insights = data.Insights;
|
||||
Assert.AreEqual(1, insights.Length);
|
||||
Assert.IsTrue(insights.All(insight => insight.GeneratedTimeUtc != default(DateTime)));
|
||||
Assert.IsTrue(insights.All(insight => insight.CloseTimeUtc != default(DateTime)));
|
||||
};
|
||||
var security = algo.AddEquity("SPY");
|
||||
algo.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
var alpha = new FakeAlpha();
|
||||
algo.SetAlpha(alpha);
|
||||
|
||||
var construction = new FakePortfolioConstruction();
|
||||
algo.SetPortfolioConstruction(construction);
|
||||
|
||||
var tick = new Tick
|
||||
{
|
||||
Symbol = security.Symbol,
|
||||
Value = 1,
|
||||
Quantity = 2
|
||||
};
|
||||
security.SetMarketPrice(tick);
|
||||
|
||||
algo.OnFrameworkData(new Slice(new DateTime(2000, 01, 01), algo.Securities.Select(s => tick), new DateTime(2000, 01, 01)));
|
||||
|
||||
Assert.IsTrue(eventFired);
|
||||
Assert.AreEqual(1, construction.Insights.Count);
|
||||
Assert.IsTrue(construction.Insights.All(insight => insight.GeneratedTimeUtc != default(DateTime)));
|
||||
Assert.IsTrue(construction.Insights.All(insight => insight.CloseTimeUtc != default(DateTime)));
|
||||
}
|
||||
|
||||
[TestCase(true, 0)]
|
||||
[TestCase(false, 2)]
|
||||
public void DelistedSecuritiesInsightsTest(bool isDelisted, int expectedCount)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
|
||||
algorithm.SetStartDate(2007, 5, 16);
|
||||
algorithm.SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
algorithm.SetFinishedWarmingUp();
|
||||
|
||||
var alpha = new FakeAlpha();
|
||||
algorithm.SetAlpha(alpha);
|
||||
|
||||
var construction = new FakePortfolioConstruction();
|
||||
algorithm.SetPortfolioConstruction(construction);
|
||||
|
||||
var actualInsights = new List<Insight>();
|
||||
algorithm.InsightsGenerated += (s, e) => actualInsights.AddRange(e.Insights);
|
||||
|
||||
var security = algorithm.AddEquity("SPY", Resolution.Daily);
|
||||
var tick = new Tick
|
||||
{
|
||||
Symbol = security.Symbol,
|
||||
Value = 1,
|
||||
Quantity = 2
|
||||
};
|
||||
|
||||
security.SetMarketPrice(tick);
|
||||
security.IsDelisted = isDelisted;
|
||||
|
||||
// Trigger Alpha to emit insight
|
||||
algorithm.OnFrameworkData(new Slice(new DateTime(2000, 01, 01), new List<BaseData>() { tick }, new DateTime(2000, 01, 01)));
|
||||
|
||||
// Manually emit insight
|
||||
algorithm.EmitInsights(Insight.Price(Symbols.SPY, TimeSpan.FromDays(1), InsightDirection.Up, .5, .75));
|
||||
|
||||
// Should be zero because security is delisted
|
||||
Assert.AreEqual(expectedCount, actualInsights.Count);
|
||||
}
|
||||
|
||||
class FakeAlpha : AlphaModel
|
||||
{
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
yield return Insight.Price(Symbols.SPY, TimeSpan.FromDays(1), InsightDirection.Up, .5, .75);
|
||||
}
|
||||
}
|
||||
|
||||
class FakePortfolioConstruction : PortfolioConstructionModel
|
||||
{
|
||||
public IReadOnlyCollection<Insight> Insights { get; private set; }
|
||||
public override IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
|
||||
{
|
||||
Insights = insights;
|
||||
return insights.Select(insight => PortfolioTarget.Percent(algorithm, insight.Symbol, 0.01m));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Risk
|
||||
{
|
||||
[TestFixture]
|
||||
public class MaximumDrawdownPercentPerSecurityTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, 0.1, false, 0, 0, false)]
|
||||
[TestCase(Language.CSharp, 0.1, true, -50, 1000, false)]
|
||||
[TestCase(Language.CSharp, 0.1, true, -100, 1000, false)]
|
||||
[TestCase(Language.CSharp, 0.1, true, -150, 1000, true)]
|
||||
[TestCase(Language.Python, 0.1, false, 0, 0, false)]
|
||||
[TestCase(Language.Python, 0.1, true, -50, 1000, false)]
|
||||
[TestCase(Language.Python, 0.1, true, -100, 1000, false)]
|
||||
[TestCase(Language.Python, 0.1, true, -150, 1000, true)]
|
||||
public void ReturnsExpectedPortfolioTarget(
|
||||
Language language,
|
||||
decimal maxDrawdownPercent,
|
||||
bool invested,
|
||||
decimal unrealizedProfit,
|
||||
decimal absoluteHoldingsCost,
|
||||
bool shouldLiquidate)
|
||||
{
|
||||
var security = new Mock<Equity>(
|
||||
Symbols.AAPL,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache(),
|
||||
Exchange.UNKNOWN
|
||||
);
|
||||
security.Setup(m => m.Invested).Returns(invested);
|
||||
|
||||
var holding = new Mock<EquityHolding>(security.Object,
|
||||
new IdentityCurrencyConverter(Currencies.USD));
|
||||
holding.Setup(m => m.UnrealizedProfit).Returns(unrealizedProfit);
|
||||
holding.Setup(m => m.AbsoluteHoldingsCost).Returns(absoluteHoldingsCost);
|
||||
holding.Setup(m => m.UnrealizedProfitPercent).Returns(absoluteHoldingsCost == 0m? 0m : unrealizedProfit / absoluteHoldingsCost);
|
||||
|
||||
security.Object.Holdings = holding.Object;
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.Securities.Add(Symbols.AAPL, security.Object);
|
||||
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MaximumDrawdownPercentPerSecurity);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(maxDrawdownPercent.ToPython());
|
||||
var model = new RiskManagementModelPythonWrapper(instance);
|
||||
algorithm.SetRiskManagement(model);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var model = new MaximumDrawdownPercentPerSecurity(maxDrawdownPercent);
|
||||
algorithm.SetRiskManagement(model);
|
||||
}
|
||||
|
||||
var targets = algorithm.RiskManagement.ManageRisk(algorithm, null).ToList();
|
||||
|
||||
if (shouldLiquidate)
|
||||
{
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
Assert.AreEqual(Symbols.AAPL, targets[0].Symbol);
|
||||
Assert.AreEqual(0, targets[0].Quantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, targets.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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 Moq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Risk
|
||||
{
|
||||
[TestFixture]
|
||||
public class MaximumDrawdownPercentPortfolioTests
|
||||
{
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, false, 0, false)]
|
||||
[TestCase(Language.CSharp, true, -1000, false)]
|
||||
[TestCase(Language.CSharp, true, -10000, false)]
|
||||
[TestCase(Language.CSharp, true, -10001, true)]
|
||||
[TestCase(Language.Python, false, 0, false)]
|
||||
[TestCase(Language.Python, true, -1000, false)]
|
||||
[TestCase(Language.Python, true, -10000, false)]
|
||||
[TestCase(Language.Python, true, -10001, true)]
|
||||
public void ReturnsExpectedPortfolioTarget(
|
||||
Language language,
|
||||
bool invested,
|
||||
decimal absoluteHoldingsCost,
|
||||
bool shouldLiquidate)
|
||||
{
|
||||
var algorithm = CreateAlgorithm(language, 0.1m);
|
||||
var targets = algorithm.RiskManagement.ManageRisk(algorithm, new PortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) }).ToList();
|
||||
Assert.AreEqual(0, targets.Count);
|
||||
|
||||
algorithm.Securities.Add(Symbols.AAPL, GetSecurity(Symbols.AAPL, invested, absoluteHoldingsCost));
|
||||
algorithm.Portfolio.InvalidateTotalPortfolioValue();
|
||||
targets = algorithm.RiskManagement.ManageRisk(algorithm, new PortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10)}).ToList();
|
||||
|
||||
if (shouldLiquidate)
|
||||
{
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
Assert.AreEqual(Symbols.AAPL, targets[0].Symbol);
|
||||
Assert.AreEqual(0, targets[0].Quantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, targets.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void ReturnsExpectedPortfolioTargetsAfterReset(Language language)
|
||||
{
|
||||
var algorithm = CreateAlgorithm(language, 0.1m);
|
||||
var targets = algorithm.RiskManagement.ManageRisk(algorithm, new PortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) }).ToList();
|
||||
algorithm.Securities.Add(Symbols.AAPL, GetSecurity(Symbols.AAPL, true, -10001));
|
||||
algorithm.Portfolio.InvalidateTotalPortfolioValue();
|
||||
targets = algorithm.RiskManagement.ManageRisk(algorithm, new PortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) }).ToList();
|
||||
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
Assert.AreEqual(Symbols.AAPL, targets[0].Symbol);
|
||||
Assert.AreEqual(0, targets[0].Quantity);
|
||||
|
||||
algorithm.Securities.Add(Symbols.AAPL, GetSecurity(Symbols.AAPL, true, 10001));
|
||||
targets = algorithm.RiskManagement.ManageRisk(algorithm, new PortfolioTarget[] { new PortfolioTarget(Symbols.AAPL, 10) }).ToList();
|
||||
Assert.AreEqual(0, targets.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void ReturnsMoreThanOnePortfolioTarget(Language language)
|
||||
{
|
||||
var targetSymbols = new PortfolioTarget[] {
|
||||
new PortfolioTarget(Symbols.AAPL, 10),
|
||||
new PortfolioTarget(Symbols.SPY, 100),
|
||||
new PortfolioTarget(Symbols.MSFT, 1000),
|
||||
new PortfolioTarget(Symbols.GOOG, 10000),
|
||||
new PortfolioTarget(Symbols.IBM, 100000)};
|
||||
|
||||
var algorithm = CreateAlgorithm(language, 0.1m);
|
||||
var returnedTargets = algorithm.RiskManagement.ManageRisk(algorithm, targetSymbols).ToList();
|
||||
|
||||
targetSymbols.ToList().ForEach(x => algorithm.Securities.Add(x.Symbol, GetSecurity( x.Symbol, true, -x.Quantity)));
|
||||
algorithm.Portfolio.InvalidateTotalPortfolioValue();
|
||||
returnedTargets = algorithm.RiskManagement.ManageRisk(algorithm, targetSymbols).ToList();
|
||||
|
||||
Assert.AreEqual(targetSymbols.Length, returnedTargets.Count);
|
||||
Assert.AreEqual(targetSymbols.Select(x => x.Symbol), returnedTargets.Select(x => x.Symbol));
|
||||
Assert.IsTrue(returnedTargets.All(x => x.Quantity == 0));
|
||||
}
|
||||
|
||||
private QCAlgorithm CreateAlgorithm(Language language, decimal maxDrawdownPercent)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
|
||||
if (language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(MaximumDrawdownPercentPortfolio);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(maxDrawdownPercent.ToPython());
|
||||
var model = new RiskManagementModelPythonWrapper(instance);
|
||||
algorithm.SetRiskManagement(model);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var model = new MaximumDrawdownPercentPortfolio(maxDrawdownPercent);
|
||||
algorithm.SetRiskManagement(model);
|
||||
}
|
||||
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
private Security GetSecurity(Symbol symbol, bool invested, decimal absoluteHoldingsCost)
|
||||
{
|
||||
// Add security
|
||||
var security = new Mock<Equity>(
|
||||
symbol,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
|
||||
new Cash(Currencies.USD, 0, 1),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache(),
|
||||
Exchange.UNKNOWN
|
||||
);
|
||||
var holding = new Mock<EquityHolding>(security.Object,
|
||||
new IdentityCurrencyConverter(Currencies.USD));
|
||||
holding.Setup(m => m.Invested).Returns(invested);
|
||||
holding.Setup(m => m.HoldingsValue).Returns(absoluteHoldingsCost);
|
||||
|
||||
security.Object.Holdings = holding.Object;
|
||||
return security.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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 System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Equity;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Orders.Fees;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Risk
|
||||
{
|
||||
[TestFixture]
|
||||
public class TrailingStopRiskManagementModelTests
|
||||
{
|
||||
[Test, TestCaseSource(nameof(GenerateTestData))]
|
||||
public void ReturnsExpectedPortfolioTarget(
|
||||
TrailingStopRiskManagementModelTestParameters parameters)
|
||||
{
|
||||
var decimalPrices = System.Array.ConvertAll(parameters.Prices, x => (decimal) x);
|
||||
|
||||
var security = new Mock<Security>(
|
||||
Symbols.AAPL,
|
||||
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
|
||||
new Cash(Currencies.USD, 1000m, 1m),
|
||||
SymbolProperties.GetDefault(Currencies.USD),
|
||||
ErrorCurrencyConverter.Instance,
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCache()
|
||||
);
|
||||
security.CallBase = true;
|
||||
security.Object.FeeModel = new ConstantFeeModel(0);
|
||||
|
||||
var holding = new SecurityHolding(security.Object, new IdentityCurrencyConverter(Currencies.USD));
|
||||
holding.SetHoldings(parameters.InitialPrice, parameters.Quantity);
|
||||
security.Object.Holdings = holding;
|
||||
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SetPandasConverter();
|
||||
algorithm.Securities.Add(Symbols.AAPL, security.Object);
|
||||
|
||||
if (parameters.Language == Language.Python)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(TrailingStopRiskManagementModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(parameters.MaxDrawdownPercent.ToPython());
|
||||
var model = new RiskManagementModelPythonWrapper(instance);
|
||||
algorithm.SetRiskManagement(model);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var model = new TrailingStopRiskManagementModel(parameters.MaxDrawdownPercent);
|
||||
algorithm.SetRiskManagement(model);
|
||||
}
|
||||
|
||||
var quantity = parameters.Quantity;
|
||||
|
||||
for (int i = 0; i < decimalPrices.Length; i++)
|
||||
{
|
||||
var price = decimalPrices[i];
|
||||
security.Object.SetMarketPrice(new Tick(DateTime.Now, security.Object.Symbol, price, price));
|
||||
security.Setup((m => m.Invested)).Returns(parameters.InvestedArray[i]);
|
||||
|
||||
var targets = algorithm.RiskManagement.ManageRisk(algorithm, null).ToList();
|
||||
var shouldLiquidate = parameters.ShouldLiquidateArray[i];
|
||||
|
||||
if (shouldLiquidate)
|
||||
{
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
Assert.AreEqual(Symbols.AAPL, targets[0].Symbol);
|
||||
Assert.AreEqual(0, targets[0].Quantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(0, targets.Count);
|
||||
}
|
||||
|
||||
if (shouldLiquidate || parameters.ChangePosition[i])
|
||||
{
|
||||
// Go from long to short or viceversa
|
||||
holding.SetHoldings(price, quantity = -quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<TestCaseData> GenerateTestData()
|
||||
{
|
||||
Language[] languages = new Language[] { Language.CSharp, Language.Python };
|
||||
TrailingStopRiskManagementModelTestParameters[] datasets = new TrailingStopRiskManagementModelTestParameters[]
|
||||
{
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesOnCorrectPriceChangeInLongPosition",
|
||||
0.05m,
|
||||
1m,
|
||||
1m,
|
||||
new decimal[] { 100m, 99.95m, 99.94m, 95m, 94.99m },
|
||||
new bool[] { true, true, true, true, true },
|
||||
new bool[] { false, false, false, false, false },
|
||||
new bool[] { false, false, false, false, true }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesOnCorrectPriceChangeInShortPosition",
|
||||
0.1m,
|
||||
100m,
|
||||
-1m,
|
||||
new decimal[] { 50m, 54.99m, 55m, 55.01m },
|
||||
new bool[] { true, true, true, true },
|
||||
new bool[] { false, false, false, false },
|
||||
new bool[] { false, false, false, true }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"DoesntLiquidateIfSecurityIsNotInvested",
|
||||
0.05m,
|
||||
1m,
|
||||
1m,
|
||||
new decimal[] { 100m, 94.99m, 90m },
|
||||
new bool[] { false, false, false },
|
||||
new bool[] { false, false, false },
|
||||
new bool[] { false, false, false }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesOnCorrectPriceChangeInLongPositionWithUnivestedSecurityInFirstPrices",
|
||||
0.05m,
|
||||
1m,
|
||||
1m,
|
||||
new decimal[] { 10m, 100m, 99.95m, 99.94m, 95m, 94.99m },
|
||||
new bool[] { false, true, true, true, true, true },
|
||||
new bool[] { false, false, false, false, false, false },
|
||||
new bool[] { false, false, false, false, false, true }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesOnCorrectPriceChangeInShortPositionWithUnivestedSecurityInFirstPrices",
|
||||
0.1m,
|
||||
100m,
|
||||
-1m,
|
||||
new decimal[] { 90m, 100m, 50m, 54.99m, 55m, 55.01m },
|
||||
new bool[] { false, true, true, true, true, true },
|
||||
new bool[] { false, false, false, false, false, false },
|
||||
new bool[] { false, false, false, false, false, true }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"DoesntLiquidateIfPricesDontChangeInLongPosition",
|
||||
0.05m,
|
||||
1m,
|
||||
1m,
|
||||
new decimal[] { 1m, 1m, 1m, 1m },
|
||||
new bool[] { true, true, true, true },
|
||||
new bool[] { false, false, false, false },
|
||||
new bool[] { false, false, false, false }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"DoesntLiquidateIfPricesDontChangeInShortPosition",
|
||||
0.05m,
|
||||
1m,
|
||||
-1m,
|
||||
new decimal[] { 1m, 1m, 1m, 1m },
|
||||
new bool[] { true, true, true, true },
|
||||
new bool[] { false, false, false, false },
|
||||
new bool[] { false, false, false, false }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesAfterSwitchingToShortPosition",
|
||||
0.05m,
|
||||
1m,
|
||||
1m,
|
||||
new decimal[] { 100m, 90m, 70m, 50m, 52.6m },
|
||||
new bool[] { true, true, true, true, true },
|
||||
new bool[] { true, false, false, false, false },
|
||||
new bool[] { false, false, false, false, true }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesOnFirstCallForLongPosition",
|
||||
0.1m,
|
||||
100m,
|
||||
1m,
|
||||
new decimal[] { 89.99m },
|
||||
new bool[] { true },
|
||||
new bool[] { false },
|
||||
new bool[] { true }
|
||||
),
|
||||
new TrailingStopRiskManagementModelTestParameters(
|
||||
"LiquidatesOnFirstCallForShortPosition",
|
||||
0.1m,
|
||||
100m,
|
||||
-1m,
|
||||
new decimal[] { 110.01m },
|
||||
new bool[] { true },
|
||||
new bool[] { false },
|
||||
new bool[] { true }
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
from parameters in datasets
|
||||
from language in languages
|
||||
select new TrailingStopRiskManagementModelTestParameters(
|
||||
parameters.Name,
|
||||
parameters.MaxDrawdownPercent,
|
||||
parameters.InitialPrice,
|
||||
parameters.Quantity,
|
||||
parameters.Prices,
|
||||
parameters.InvestedArray,
|
||||
parameters.ChangePosition,
|
||||
parameters.ShouldLiquidateArray,
|
||||
language
|
||||
)
|
||||
)
|
||||
.OrderBy(c => c.Language)
|
||||
// generate test cases from test parameters
|
||||
.Select(x => new TestCaseData(x).SetName(x.Language + "/" + x.Name))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public class TrailingStopRiskManagementModelTestParameters
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public Language Language { get; init; }
|
||||
public decimal MaxDrawdownPercent { get; init; }
|
||||
public decimal InitialPrice { get; init; }
|
||||
public decimal Quantity { get; init; }
|
||||
public decimal[] Prices { get; init; }
|
||||
public bool[] InvestedArray { get; init; }
|
||||
public bool[] ChangePosition { get; init; }
|
||||
public bool[] ShouldLiquidateArray { get; init; }
|
||||
|
||||
public TrailingStopRiskManagementModelTestParameters(
|
||||
string name,
|
||||
decimal maxDrawdownPercent,
|
||||
decimal initialPrice,
|
||||
decimal quantity,
|
||||
decimal[] prices,
|
||||
bool[] investedArray,
|
||||
bool[] changePosition,
|
||||
bool[] shouldLiquidateArray,
|
||||
Language language = Language.CSharp
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
Language = language;
|
||||
MaxDrawdownPercent = maxDrawdownPercent;
|
||||
InitialPrice = initialPrice;
|
||||
Quantity = quantity;
|
||||
Prices = prices;
|
||||
InvestedArray = investedArray;
|
||||
ChangePosition = changePosition;
|
||||
ShouldLiquidateArray = shouldLiquidateArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 Python.Runtime;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Securities;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using Moq;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Selection
|
||||
{
|
||||
[TestFixture]
|
||||
public class ETFConstituentsUniverseSelectionModelTests
|
||||
{
|
||||
[TestCase("from Selection.ETFConstituentsUniverseSelectionModel import *", "Selection.ETFConstituentsUniverseSelectionModel.ETFConstituentsUniverseSelectionModel")]
|
||||
[TestCase("from QuantConnect.Algorithm.Framework.Selection import *", "QuantConnect.Algorithm.Framework.Selection.ETFConstituentsUniverseSelectionModel")]
|
||||
public void TestPythonAndCSharpImports(string importStatement, string expected)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic module = PyModule.FromString("testModule",
|
||||
@$"{importStatement}
|
||||
class ETFConstituentsFrameworkAlgorithm(QCAlgorithm):
|
||||
def initialize(self):
|
||||
self.universe_settings.resolution = Resolution.DAILY
|
||||
symbol = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)
|
||||
selection_model = ETFConstituentsUniverseSelectionModel(symbol, self.universe_settings, self.etf_constituents_filter)
|
||||
self.universe_type = str(type(selection_model))
|
||||
|
||||
def etf_constituents_filter(self, constituents):
|
||||
return [c.symbol for c in constituents]");
|
||||
|
||||
dynamic algorithm = module.GetAttr("ETFConstituentsFrameworkAlgorithm").Invoke();
|
||||
algorithm.initialize();
|
||||
string universeTypeStr = algorithm.universe_type.ToString();
|
||||
Assert.IsTrue(universeTypeStr.Contains(expected, StringComparison.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestCase("'SPY'")]
|
||||
[TestCase("'SPY', None")]
|
||||
[TestCase("'SPY', None, None")]
|
||||
[TestCase("'SPY', self.universe_settings")]
|
||||
[TestCase("'SPY', self.universe_settings, None")]
|
||||
[TestCase("'SPY', None, self.etf_constituents_filter")]
|
||||
[TestCase("'SPY', self.universe_settings, self.etf_constituents_filter")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA)")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA), None, None")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA), self.universe_settings")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA), self.universe_settings, None")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA), None, self.etf_constituents_filter")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA), self.universe_settings, self.etf_constituents_filter")]
|
||||
[TestCase("Symbol.create('SPY', SecurityType.EQUITY, Market.USA), universe_filter_func=self.etf_constituents_filter")]
|
||||
public void ETFConstituentsUniverseSelectionModelWithVariousConstructor(string constructorParameters)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
dynamic module = PyModule.FromString("testModule",
|
||||
@$"from AlgorithmImports import *
|
||||
from Selection.ETFConstituentsUniverseSelectionModel import *
|
||||
class ETFConstituentsFrameworkAlgorithm(QCAlgorithm):
|
||||
selection_model = None
|
||||
def initialize(self):
|
||||
self.universe_settings.resolution = Resolution.DAILY
|
||||
self.selection_model = ETFConstituentsUniverseSelectionModel({constructorParameters})
|
||||
|
||||
def etf_constituents_filter(self, constituents):
|
||||
return [c.symbol for c in constituents]");
|
||||
|
||||
dynamic algorithm = module.GetAttr("ETFConstituentsFrameworkAlgorithm").Invoke();
|
||||
algorithm.initialize();
|
||||
Assert.IsNotNull(algorithm.selection_model);
|
||||
Assert.IsTrue(algorithm.selection_model.etf_symbol.GetPythonType().ToString().Contains($"{nameof(Symbol)}", StringComparison.InvariantCulture));
|
||||
Assert.IsTrue(algorithm.selection_model.etf_symbol.ToString().Contains(Symbols.SPY, StringComparison.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("TSLA")]
|
||||
public void ETFConstituentsUniverseSelectionModelGetNoCachedSymbol(string ticker)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var etfAlgorithm = GetETFConstituentsFrameworkAlgorithm(ticker);
|
||||
etfAlgorithm.initialize();
|
||||
|
||||
Assert.IsNotNull(etfAlgorithm.selection_model);
|
||||
Assert.IsTrue(etfAlgorithm.selection_model.etf_symbol.GetPythonType().ToString().Contains($"{nameof(Symbol)}", StringComparison.InvariantCulture));
|
||||
|
||||
var etfSymbol = (Symbol)etfAlgorithm.selection_model.etf_symbol;
|
||||
|
||||
Assert.IsTrue(etfSymbol.Value.Contains(ticker, StringComparison.InvariantCulture));
|
||||
Assert.IsTrue(etfSymbol.SecurityType == SecurityType.Equity);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("SPY", "CACHED")]
|
||||
public void ETFConstituentsUniverseSelectionModelGetCachedSymbol(string ticker, string expectedAlias)
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var etfAlgorithm = GetETFConstituentsFrameworkAlgorithm(ticker);
|
||||
etfAlgorithm.initialize();
|
||||
|
||||
Assert.IsNotNull(etfAlgorithm.selection_model);
|
||||
Assert.IsTrue(etfAlgorithm.selection_model.etf_symbol.GetPythonType().ToString().Contains($"{nameof(Symbol)}", StringComparison.InvariantCulture));
|
||||
|
||||
var etfSymbol = (Symbol)etfAlgorithm.selection_model.etf_symbol;
|
||||
|
||||
Assert.IsTrue(etfSymbol.Value.Contains(expectedAlias, StringComparison.InvariantCulture));
|
||||
Assert.IsTrue(etfSymbol.ID == Symbols.SPY.ID);
|
||||
Assert.IsTrue(etfSymbol.SecurityType == SecurityType.Equity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void ETFConstituentsUniverseSelectionModelTestAllConstructor()
|
||||
{
|
||||
int numberOfOperation = 0;
|
||||
var ticker = "SPY";
|
||||
var symbol = Symbol.Create(ticker, SecurityType.Equity, Market.USA);
|
||||
var universeSettings = new UniverseSettings(Resolution.Minute, Security.NullLeverage, true, false, TimeSpan.FromDays(1));
|
||||
|
||||
do
|
||||
{
|
||||
ETFConstituentsUniverseSelectionModel etfConstituents = numberOfOperation switch
|
||||
{
|
||||
0 => new ETFConstituentsUniverseSelectionModel(ticker),
|
||||
1 => new ETFConstituentsUniverseSelectionModel(ticker, universeSettings),
|
||||
2 => new ETFConstituentsUniverseSelectionModel(ticker, ETFConstituentsFilter),
|
||||
3 => new ETFConstituentsUniverseSelectionModel(ticker, universeSettings, ETFConstituentsFilter),
|
||||
4 => new ETFConstituentsUniverseSelectionModel(ticker, universeSettings, default(PyObject)),
|
||||
5 => new ETFConstituentsUniverseSelectionModel(symbol),
|
||||
6 => new ETFConstituentsUniverseSelectionModel(symbol, universeSettings),
|
||||
7 => new ETFConstituentsUniverseSelectionModel(symbol, ETFConstituentsFilter),
|
||||
8 => new ETFConstituentsUniverseSelectionModel(symbol, universeSettings, ETFConstituentsFilter),
|
||||
9 => new ETFConstituentsUniverseSelectionModel(symbol, universeSettings, default(PyObject)),
|
||||
_ => throw new ArgumentException("Not recognize number of operation")
|
||||
};
|
||||
|
||||
var universe = etfConstituents.CreateUniverses(new QCAlgorithm()).First();
|
||||
|
||||
Assert.IsNotNull(etfConstituents);
|
||||
Assert.IsNotNull(universe);
|
||||
|
||||
Assert.IsTrue(universe.Configuration.Symbol.HasUnderlying);
|
||||
Assert.AreEqual(symbol, universe.Configuration.Symbol.Underlying);
|
||||
|
||||
Assert.AreEqual(symbol.SecurityType, universe.Configuration.Symbol.SecurityType);
|
||||
Assert.IsTrue(universe.Configuration.Symbol.ID.Symbol.StartsWithInvariant("qc-universe-"));
|
||||
var data = new Mock<BaseDataCollection>();
|
||||
Assert.DoesNotThrow(() => universe.PerformSelection(DateTime.UtcNow, data.Object));
|
||||
|
||||
} while (++numberOfOperation <= 9) ;
|
||||
}
|
||||
|
||||
private IEnumerable<Symbol> ETFConstituentsFilter(IEnumerable<ETFConstituentUniverse> constituents)
|
||||
{
|
||||
return constituents.Select(c => c.Symbol);
|
||||
}
|
||||
|
||||
private static dynamic GetETFConstituentsFrameworkAlgorithm(string etfTicker, string cachedAlias = "CACHED")
|
||||
{
|
||||
|
||||
dynamic module = PyModule.FromString("testModule",
|
||||
@$"from AlgorithmImports import *
|
||||
from Selection.ETFConstituentsUniverseSelectionModel import *
|
||||
class ETFConstituentsFrameworkAlgorithm(QCAlgorithm):
|
||||
selection_model = None
|
||||
def initialize(self):
|
||||
SymbolCache.set('SPY', Symbol.create('SPY', SecurityType.EQUITY, Market.USA, '{cachedAlias}'))
|
||||
self.universe_settings.resolution = Resolution.DAILY
|
||||
self.selection_model = ETFConstituentsUniverseSelectionModel(""{etfTicker}"")"
|
||||
);
|
||||
|
||||
dynamic algorithm = module.GetAttr("ETFConstituentsFrameworkAlgorithm").Invoke();
|
||||
return algorithm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.Selection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Selection
|
||||
{
|
||||
[TestFixture]
|
||||
public class ManualUniverseSelectionModelTests
|
||||
{
|
||||
[Test]
|
||||
public void ExcludesCanonicalSymbols()
|
||||
{
|
||||
var symbols = new[]
|
||||
{
|
||||
Symbols.SPY,
|
||||
Symbol.CreateOption(Symbols.SPY, Market.USA, default(OptionStyle), default(OptionRight), 0m, SecurityIdentifier.DefaultDate, "?SPY")
|
||||
};
|
||||
|
||||
var model = new ManualUniverseSelectionModel(symbols);
|
||||
var universe = model.CreateUniverses(new QCAlgorithm()).Single();
|
||||
var selectedSymbols = universe.SelectSymbols(default(DateTime), null).ToList();
|
||||
|
||||
Assert.AreEqual(1, selectedSymbols.Count);
|
||||
Assert.AreEqual(Symbols.SPY, selectedSymbols[0]);
|
||||
Assert.IsFalse(selectedSymbols.Any(s => s.IsCanonical()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NodaTime;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Selection
|
||||
{
|
||||
[TestFixture]
|
||||
public class OpenInterestFutureUniverseSelectionModelTests
|
||||
{
|
||||
private static readonly Symbol Jan = Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 01, 01));
|
||||
private static readonly Symbol Feb = Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 02, 01));
|
||||
private static readonly Symbol March = Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 03, 01));
|
||||
private static readonly Symbol April = Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 04, 01));
|
||||
private static readonly DateTime TestDate = new DateTime(2020, 05, 11, 0, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly DateTime ExpectedPreviousDate = new DateTime(2020, 05, 09, 20, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly IReadOnlyDictionary<Symbol, decimal> OpenInterestData = new Dictionary<Symbol, decimal>
|
||||
{
|
||||
[Jan] = 3,
|
||||
[Feb] = 6,
|
||||
[March] = 3, // Same as Jan.
|
||||
[April] = 1
|
||||
};
|
||||
private static readonly MarketHoursDatabase.Entry MarketHours = MarketHoursDatabase.FromDataFolder().GetEntry(Jan.ID.Market, Jan, Jan.SecurityType);
|
||||
private Mock<IHistoryProvider> _mockHistoryProvider;
|
||||
private OpenInterestFutureUniverseSelectionModel _underTest;
|
||||
|
||||
[Test]
|
||||
public void No_Open_Interest_Returns_Empty()
|
||||
{
|
||||
SetupSubject(OpenInterestData.Count, OpenInterestData.Count);
|
||||
_mockHistoryProvider.Setup(x => x.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns<IEnumerable<HistoryRequest>, DateTimeZone>((r, tz) => new Slice[0])
|
||||
.Verifiable();
|
||||
|
||||
var data = OpenInterestData.Keys.ToDictionary(x => x, x => MarketHours);
|
||||
var results = _underTest.FilterByOpenInterest(data).ToList();
|
||||
_mockHistoryProvider.Verify();
|
||||
Assert.IsEmpty(results);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Sort_By_Open_Interest()
|
||||
{
|
||||
SetupSubject(OpenInterestData.Count, OpenInterestData.Count);
|
||||
_mockHistoryProvider.Setup(x => x.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns<IEnumerable<HistoryRequest>, DateTimeZone>(
|
||||
(r, tz) =>
|
||||
{
|
||||
var requests = r.ToList();
|
||||
Assert.AreEqual(4, requests.Count);
|
||||
var slices = new List<Slice>(requests.Count);
|
||||
foreach (var request in requests)
|
||||
{
|
||||
Assert.NotNull(request.Symbol);
|
||||
Assert.AreEqual(typeof(Tick), request.DataType);
|
||||
Assert.AreEqual(DataNormalizationMode.Raw, request.DataNormalizationMode);
|
||||
Assert.AreEqual(ExpectedPreviousDate, request.StartTimeUtc);
|
||||
Assert.AreEqual(TestDate, request.EndTimeUtc);
|
||||
Assert.AreEqual(Resolution.Tick, request.Resolution);
|
||||
Assert.AreEqual(TickType.OpenInterest, request.TickType);
|
||||
Assert.AreEqual(tz, MarketHours.ExchangeHours.TimeZone);
|
||||
slices.Add(CreateReplySlice(request.Symbol, OpenInterestData[request.Symbol]));
|
||||
}
|
||||
|
||||
return slices;
|
||||
}
|
||||
)
|
||||
.Verifiable();
|
||||
|
||||
var data = OpenInterestData.Keys.ToDictionary(x => x, x => MarketHours);
|
||||
var results = _underTest.FilterByOpenInterest(data).ToList();
|
||||
|
||||
// Results should be sorted by open interest (descending), and then by the date.
|
||||
_mockHistoryProvider.Verify();
|
||||
Assert.AreEqual(4, results.Count);
|
||||
Assert.AreEqual(Feb, results[0]);
|
||||
Assert.AreEqual(Jan, results[1]);
|
||||
Assert.AreEqual(March, results[2]);
|
||||
Assert.AreEqual(April, results[3]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Limit_Number_Of_Contracts()
|
||||
{
|
||||
SetupSubject(6, 4);
|
||||
var expected = Enumerable.Range(1, 4).Select(d => Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 01, d))).ToList();
|
||||
|
||||
// Create 7 requests. Reverse the list so the order isn't correct, but remains consistent for tests.
|
||||
var data = expected.Concat(Enumerable.Range(5, 3).Select(d => Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 01, d))))
|
||||
.Reverse()
|
||||
.ToDictionary(x => x, _ => MarketHours);
|
||||
|
||||
// 7 input requests, but the look-up should be limited to only 6.
|
||||
_mockHistoryProvider.Setup(x => x.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns<IEnumerable<HistoryRequest>, DateTimeZone>((rq, tz) => rq.Select(r => CreateReplySlice(r.Symbol, 1)).ToArray());
|
||||
|
||||
// Run the test.
|
||||
var results = _underTest.FilterByOpenInterest(data).ToList();
|
||||
|
||||
// Verify the chain limit was applied.
|
||||
_mockHistoryProvider.Verify(x => x.GetHistory(It.Is<IEnumerable<HistoryRequest>>(r => r.Count() == 6), MarketHours.ExchangeHours.TimeZone), Times.Once);
|
||||
|
||||
// Verify the results.
|
||||
CollectionAssert.AreEqual(expected, results);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Limits_Do_Not_Need_To_Be_Provided()
|
||||
{
|
||||
SetupSubject(null, null);
|
||||
var startDate = new DateTime(2020, 01, 01);
|
||||
var items = Enumerable.Range(0, 100).ToDictionary(d => Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, startDate.AddDays(d)), _ => MarketHours);
|
||||
_mockHistoryProvider.Setup(x => x.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>()))
|
||||
.Returns<IEnumerable<HistoryRequest>, DateTimeZone>((rq, tz) => rq.Select(r => CreateReplySlice(r.Symbol, 1)).ToArray());
|
||||
var results = _underTest.FilterByOpenInterest(items).ToList();
|
||||
_mockHistoryProvider.Verify(x => x.GetHistory(It.Is<IEnumerable<HistoryRequest>>(r => r.Count() == 100), MarketHours.ExchangeHours.TimeZone), Times.Once);
|
||||
Assert.AreEqual(items.Keys, results);
|
||||
}
|
||||
|
||||
private static Slice CreateReplySlice(Symbol symbol, decimal openInterest)
|
||||
{
|
||||
var ticks = new Ticks {{symbol, new List<Tick> {new OpenInterest(TestDate, symbol, openInterest)}}};
|
||||
return new Slice(TestDate, null, null, null, ticks, null, null, null, null, null, null, null, TestDate, true);
|
||||
}
|
||||
|
||||
private void SetupSubject(int? testChainContractLookupLimit, int? testResultsLimit)
|
||||
{
|
||||
_mockHistoryProvider = new Mock<IHistoryProvider>();
|
||||
|
||||
var mockAlgorithm = new Mock<IAlgorithm>();
|
||||
mockAlgorithm.SetupGet(x => x.HistoryProvider).Returns(_mockHistoryProvider.Object);
|
||||
mockAlgorithm.SetupGet(x => x.UtcTime).Returns(TestDate);
|
||||
_underTest = new OpenInterestFutureUniverseSelectionModel(mockAlgorithm.Object, _ => OpenInterestData.Keys, testChainContractLookupLimit, testResultsLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Tests.Common.Data.Fundamental;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static QuantConnect.Data.UniverseSelection.CoarseFundamentalDataProvider;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Selection
|
||||
{
|
||||
[TestFixture]
|
||||
public class QC500UniverseSelectionModelTests
|
||||
{
|
||||
private readonly Dictionary<char, string> _industryTemplateCodeDict =
|
||||
new Dictionary<char, string>
|
||||
{
|
||||
{'0', "B"}, {'1', "I"}, {'2', "M"}, {'3', "N"}, {'4', "T"}, {'5', "U"}
|
||||
};
|
||||
|
||||
private readonly List<Symbol> _symbols = Enumerable.Range(0, 6000)
|
||||
.Select(x => Symbol.Create($"{x:0000}", SecurityType.Equity, Market.USA))
|
||||
.ToList();
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void FiltersUniverseCorrectlyWithValidData(Language language)
|
||||
{
|
||||
QCAlgorithm algorithm;
|
||||
Dictionary<DateTime, int> coarseCountByDateTime;
|
||||
Dictionary<DateTime, int> fineCountByDateTime;
|
||||
|
||||
RunSimulation(language,
|
||||
(symbol, time) => new CoarseFundamentalSource
|
||||
{
|
||||
Symbol = symbol,
|
||||
EndTime = time,
|
||||
Value = 100,
|
||||
VolumeSetter = 1000,
|
||||
DollarVolumeSetter = 100000 * double.Parse(symbol.Value.Substring(3)),
|
||||
HasFundamentalDataSetter = true
|
||||
},
|
||||
(symbol, time) => new FineFundamental(time, symbol)
|
||||
{
|
||||
Value = 100
|
||||
},
|
||||
new TestFundamentalDataProvider(_industryTemplateCodeDict),
|
||||
out algorithm,
|
||||
out coarseCountByDateTime,
|
||||
out fineCountByDateTime);
|
||||
|
||||
var months = (algorithm.EndDate - algorithm.StartDate).Days / 30;
|
||||
// Universe Changed 4 times
|
||||
Assert.AreEqual(months, coarseCountByDateTime.Count);
|
||||
Assert.AreEqual(months, fineCountByDateTime.Count);
|
||||
// Universe Changed on the 1st. Coarse returned 1000 and Fine 500
|
||||
Assert.IsTrue(coarseCountByDateTime.All(kvp => kvp.Key.Day == 1 && kvp.Value == 1000));
|
||||
Assert.IsTrue(fineCountByDateTime.All(kvp => kvp.Key.Day == 1 && kvp.Value == 500));
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotFilterUniverseWithCoarseDataHasFundamentalFalse(Language language)
|
||||
{
|
||||
QCAlgorithm algorithm;
|
||||
Dictionary<DateTime, int> coarseCountByDateTime;
|
||||
Dictionary<DateTime, int> fineCountByDateTime;
|
||||
|
||||
RunSimulation(language,
|
||||
(symbol, time) => new CoarseFundamentalSource
|
||||
{
|
||||
Symbol = symbol,
|
||||
EndTime = time,
|
||||
},
|
||||
(symbol, time) => new FineFundamental(time, symbol),
|
||||
new TestFundamentalDataProvider(_industryTemplateCodeDict),
|
||||
out algorithm,
|
||||
out coarseCountByDateTime,
|
||||
out fineCountByDateTime); ;
|
||||
|
||||
// No Universe Changes
|
||||
Assert.AreEqual(0, coarseCountByDateTime.Count);
|
||||
Assert.AreEqual(0, fineCountByDateTime.Count);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void DoesNotFilterUniverseWithInvalidFineData(Language language)
|
||||
{
|
||||
QCAlgorithm algorithm;
|
||||
Dictionary<DateTime, int> coarseCountByDateTime;
|
||||
Dictionary<DateTime, int> fineCountByDateTime;
|
||||
|
||||
RunSimulation(language,
|
||||
(symbol, time) => new CoarseFundamentalSource
|
||||
{
|
||||
Symbol = symbol,
|
||||
EndTime = time,
|
||||
Value = 100,
|
||||
VolumeSetter = 1000,
|
||||
DollarVolumeSetter = 100000 * double.Parse(symbol.Value.Substring(3)),
|
||||
HasFundamentalDataSetter = true
|
||||
},
|
||||
(symbol, time) => new FineFundamental(time, symbol)
|
||||
{
|
||||
Value = 100
|
||||
},
|
||||
new NullFundamentalDataProvider(),
|
||||
out algorithm,
|
||||
out coarseCountByDateTime,
|
||||
out fineCountByDateTime);
|
||||
|
||||
// Coarse Fundamental called every day.
|
||||
Assert.Greater(coarseCountByDateTime.Count, 4);
|
||||
Assert.IsTrue(coarseCountByDateTime.All(kvp => kvp.Value == 1000));
|
||||
// No Universe Changes
|
||||
Assert.AreEqual(0, fineCountByDateTime.Count);
|
||||
}
|
||||
|
||||
private void RunSimulation(Language language,
|
||||
Func<Symbol, DateTime, CoarseFundamental> getCoarseFundamental,
|
||||
Func<Symbol, DateTime, FineFundamental> getFineFundamental,
|
||||
IFundamentalDataProvider fundamentalDataProvider,
|
||||
out QCAlgorithm algorithm,
|
||||
out Dictionary<DateTime, int> coarseCountByDateTime,
|
||||
out Dictionary<DateTime, int> fineCountByDateTime)
|
||||
{
|
||||
algorithm = new QCAlgorithm();
|
||||
algorithm.SetStartDate(2019, 10, 1);
|
||||
algorithm.SetEndDate(2020, 1, 30);
|
||||
algorithm.SetDateTime(algorithm.StartDate.AddHours(6));
|
||||
|
||||
FundamentalService.Initialize(TestGlobals.DataProvider, fundamentalDataProvider, false);
|
||||
|
||||
coarseCountByDateTime = new Dictionary<DateTime, int>();
|
||||
fineCountByDateTime = new Dictionary<DateTime, int>();
|
||||
|
||||
Func<QCAlgorithm, IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> SelectCoarse;
|
||||
Func<QCAlgorithm, IEnumerable<FineFundamental>, IEnumerable<Symbol>> SelectFine;
|
||||
GetUniverseSelectionModel(language, out SelectCoarse, out SelectFine);
|
||||
|
||||
while (algorithm.EndDate > algorithm.UtcTime)
|
||||
{
|
||||
var time = algorithm.UtcTime;
|
||||
|
||||
var coarse = _symbols.Select(x => getCoarseFundamental(x, time));
|
||||
|
||||
var selectSymbolsResult = SelectCoarse(algorithm, coarse);
|
||||
|
||||
if (!ReferenceEquals(selectSymbolsResult, Universe.Unchanged))
|
||||
{
|
||||
coarseCountByDateTime[time] = selectSymbolsResult.Count();
|
||||
|
||||
var fine = selectSymbolsResult.Select(x => getFineFundamental(x, time));
|
||||
|
||||
selectSymbolsResult = SelectFine(algorithm, fine);
|
||||
if (!ReferenceEquals(selectSymbolsResult, Universe.Unchanged))
|
||||
{
|
||||
fineCountByDateTime[time] = selectSymbolsResult.Count();
|
||||
}
|
||||
}
|
||||
|
||||
algorithm.SetDateTime(time.AddDays(1));
|
||||
}
|
||||
}
|
||||
|
||||
private void GetUniverseSelectionModel(
|
||||
Language language,
|
||||
out Func<QCAlgorithm, IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> SelectCoarse,
|
||||
out Func<QCAlgorithm, IEnumerable<FineFundamental>, IEnumerable<Symbol>> SelectFine)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
var model = new QC500UniverseSelectionModel();
|
||||
SelectCoarse = model.SelectCoarse;
|
||||
SelectFine = model.SelectFine;
|
||||
return;
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var name = "QC500UniverseSelectionModel";
|
||||
dynamic model = Py.Import(name).GetAttr(name).Invoke();
|
||||
SelectCoarse = ConvertToUniverseSelectionSymbolDelegate<IEnumerable<CoarseFundamental>>(model.select_coarse);
|
||||
SelectFine = ConvertToUniverseSelectionSymbolDelegate<IEnumerable<FineFundamental>>(model.select_fine);
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<QCAlgorithm, T, IEnumerable<Symbol>> ConvertToUniverseSelectionSymbolDelegate<T>(PyObject pySelector)
|
||||
{
|
||||
Func<QCAlgorithm, T, object> selector;
|
||||
pySelector.TrySafeAs(out selector);
|
||||
|
||||
return (algorithm, data) =>
|
||||
{
|
||||
var result = selector(algorithm, data);
|
||||
return ReferenceEquals(result, Universe.Unchanged)
|
||||
? Universe.Unchanged
|
||||
: ((object[])result).Select(x => (Symbol)x);
|
||||
};
|
||||
}
|
||||
|
||||
private class TestFundamentalDataProvider : IFundamentalDataProvider
|
||||
{
|
||||
private readonly Dictionary<char, string> _industryTemplateCodeDict;
|
||||
|
||||
public TestFundamentalDataProvider(Dictionary<char, string> industryTemplateCodeDict)
|
||||
{
|
||||
_industryTemplateCodeDict = industryTemplateCodeDict;
|
||||
}
|
||||
public T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
|
||||
{
|
||||
if (securityIdentifier == SecurityIdentifier.Empty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return Get(time, securityIdentifier, name);
|
||||
}
|
||||
|
||||
private dynamic Get(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty enumName)
|
||||
{
|
||||
var name = Enum.GetName(enumName);
|
||||
switch (name)
|
||||
{
|
||||
case "CompanyProfile_MarketCap":
|
||||
return 500000001;
|
||||
case "SecurityReference_IPODate":
|
||||
return time.AddDays(-200);
|
||||
case "EarningReports_BasicAverageShares_ThreeMonths":
|
||||
return 5000000.01d;
|
||||
case "CompanyReference_CountryId":
|
||||
return "USA";
|
||||
case "CompanyReference_PrimaryExchangeID":
|
||||
return "NYS";
|
||||
case "CompanyReference_IndustryTemplateCode":
|
||||
return _industryTemplateCodeDict[securityIdentifier.Symbol[0]];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Initialize(IDataProvider dataProvider, bool liveMode)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class UniverseDefinitionsTests
|
||||
{
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void ETFOverloadsAreAllUsable(Language language)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var universeDefinitions = algorithm.Universe;
|
||||
var universeSettings = new UniverseSettings(Resolution.Minute, Security.NullLeverage, true, false, TimeSpan.FromDays(1));
|
||||
|
||||
List<Universe> etfs = null;
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
etfs = new()
|
||||
{
|
||||
universeDefinitions.ETF("SPY"),
|
||||
universeDefinitions.ETF("SPY", Market.USA),
|
||||
universeDefinitions.ETF("SPY", Market.USA, universeSettings),
|
||||
universeDefinitions.ETF("SPY", Market.USA, universeSettings, Filter),
|
||||
universeDefinitions.ETF("SPY", Market.USA, universeFilterFunc: Filter),
|
||||
universeDefinitions.ETF("SPY", universeSettings: universeSettings),
|
||||
universeDefinitions.ETF("SPY", universeFilterFunc: Filter),
|
||||
universeDefinitions.ETF("SPY", universeSettings: universeSettings, universeFilterFunc: Filter),
|
||||
universeDefinitions.ETF(Symbols.SPY),
|
||||
universeDefinitions.ETF(Symbols.SPY, universeSettings),
|
||||
universeDefinitions.ETF(Symbols.SPY, universeFilterFunc: Filter),
|
||||
universeDefinitions.ETF(Symbols.SPY, universeSettings, Filter),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from typing import List
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getETFs(algorithm: QCAlgorithm, symbol: Symbol, universeSettings: UniverseSettings) -> List[Universe]:
|
||||
universeDefinitions = algorithm.Universe;
|
||||
return [
|
||||
universeDefinitions.ETF('SPY'),
|
||||
universeDefinitions.ETF('SPY', Market.USA),
|
||||
universeDefinitions.ETF('SPY', Market.USA, universeSettings),
|
||||
universeDefinitions.ETF('SPY', Market.USA, universeSettings, filterETFs),
|
||||
universeDefinitions.ETF('SPY', Market.USA, universeFilterFunc=filterETFs),
|
||||
universeDefinitions.ETF('SPY', universeSettings, filterETFs),
|
||||
universeDefinitions.ETF('SPY', universeSettings=universeSettings),
|
||||
universeDefinitions.ETF('SPY', universeFilterFunc=filterETFs),
|
||||
universeDefinitions.ETF('SPY', universeSettings=universeSettings, universeFilterFunc=filterETFs),
|
||||
universeDefinitions.ETF(symbol),
|
||||
universeDefinitions.ETF(symbol, universeSettings),
|
||||
universeDefinitions.ETF(symbol, universeFilterFunc=filterETFs),
|
||||
universeDefinitions.ETF(symbol, universeSettings, filterETFs),
|
||||
]
|
||||
|
||||
def filterETFs(constituents: List[ETFConstituentData]) -> List[Symbol]:
|
||||
return [x.Symbol for x in constituents]
|
||||
");
|
||||
|
||||
var getETFs = testModule.GetAttr("getETFs");
|
||||
|
||||
etfs = getETFs.Invoke(algorithm.ToPython(), Symbols.SPY.ToPython(), universeSettings.ToPython()).As<List<Universe>>();
|
||||
}
|
||||
}
|
||||
|
||||
AssertETFConstituentsUniverses(etfs);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp)]
|
||||
[TestCase(Language.Python)]
|
||||
public void IndexOverloadsAreAllUsable(Language language)
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
var universeDefinitions = algorithm.Universe;
|
||||
var universeSettings = new UniverseSettings(Resolution.Minute, Security.NullLeverage, true, false, TimeSpan.FromDays(1));
|
||||
|
||||
List<Universe> indexes = null;
|
||||
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
indexes = new()
|
||||
{
|
||||
universeDefinitions.Index("SPY"),
|
||||
universeDefinitions.Index("SPY", Market.USA),
|
||||
universeDefinitions.Index("SPY", Market.USA, universeSettings),
|
||||
universeDefinitions.Index("SPY", Market.USA, universeSettings, Filter),
|
||||
universeDefinitions.Index("SPY", Market.USA, universeFilterFunc: Filter),
|
||||
universeDefinitions.Index("SPY", universeSettings: universeSettings),
|
||||
universeDefinitions.Index("SPY", universeFilterFunc: Filter),
|
||||
universeDefinitions.Index("SPY", universeSettings: universeSettings, universeFilterFunc: Filter),
|
||||
universeDefinitions.Index(Symbols.SPY),
|
||||
universeDefinitions.Index(Symbols.SPY, universeSettings),
|
||||
universeDefinitions.Index(Symbols.SPY, universeFilterFunc: Filter),
|
||||
universeDefinitions.Index(Symbols.SPY, universeSettings, Filter),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
var testModule = PyModule.FromString("testModule",
|
||||
@"
|
||||
from typing import List
|
||||
from AlgorithmImports import *
|
||||
|
||||
def getIndexes(algorithm: QCAlgorithm, symbol: Symbol, universeSettings: UniverseSettings) -> List[Universe]:
|
||||
universeDefinitions = algorithm.Universe;
|
||||
return [
|
||||
universeDefinitions.Index('SPY'),
|
||||
universeDefinitions.Index('SPY', Market.USA),
|
||||
universeDefinitions.Index('SPY', Market.USA, universeSettings),
|
||||
universeDefinitions.Index('SPY', Market.USA, universeSettings, filterIndexes),
|
||||
universeDefinitions.Index('SPY', Market.USA, universeFilterFunc=filterIndexes),
|
||||
universeDefinitions.Index('SPY', universeSettings, filterIndexes),
|
||||
universeDefinitions.Index('SPY', universeSettings=universeSettings),
|
||||
universeDefinitions.Index('SPY', universeFilterFunc=filterIndexes),
|
||||
universeDefinitions.Index('SPY', universeSettings=universeSettings, universeFilterFunc=filterIndexes),
|
||||
universeDefinitions.Index(symbol),
|
||||
universeDefinitions.Index(symbol, universeSettings),
|
||||
universeDefinitions.Index(symbol, universeFilterFunc=filterIndexes),
|
||||
universeDefinitions.Index(symbol, universeSettings, filterIndexes),
|
||||
]
|
||||
|
||||
def filterIndexes(constituents: List[ETFConstituentData]) -> List[Symbol]:
|
||||
return [x.Symbol for x in constituents]
|
||||
");
|
||||
|
||||
var getIndexes = testModule.GetAttr("getIndexes");
|
||||
|
||||
indexes = getIndexes.Invoke(algorithm.ToPython(), Symbols.SPY.ToPython(), universeSettings.ToPython()).As<List<Universe>>();
|
||||
}
|
||||
}
|
||||
|
||||
AssertETFConstituentsUniverses(indexes);
|
||||
}
|
||||
|
||||
private static void AssertETFConstituentsUniverses(List<Universe> universes)
|
||||
{
|
||||
CollectionAssert.AllItemsAreNotNull(universes, "Universes should not be null");
|
||||
CollectionAssert.AllItemsAreInstancesOfType(universes, typeof(ETFConstituentsUniverseFactory),
|
||||
"Universes should be of type ETFConstituentsUniverse");
|
||||
}
|
||||
|
||||
private static IEnumerable<Symbol> Filter(IEnumerable<ETFConstituentUniverse> constituents)
|
||||
{
|
||||
return constituents.Select(x => x.Symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user