Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Common/Data/SubscriptionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class SubscriptionManager
/// Returns an IEnumerable of Subscriptions
/// </summary>
/// <remarks>Will not return internal subscriptions</remarks>
public IEnumerable<SubscriptionDataConfig> Subscriptions => _subscriptionManager.SubscriptionManagerSubscriptions.Where(config => !config.IsInternalFeed);
public IEnumerable<SubscriptionDataConfig> Subscriptions => _subscriptionManager.SubscriptionManagerSubscriptions.Where(config => !config.IsInternalFeed).Memoize();

/// <summary>
/// The different <see cref="TickType" /> each <see cref="SecurityType" /> supports
Expand Down
3 changes: 2 additions & 1 deletion Common/Securities/Cash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Securities.CurrencyConversion;
using QuantConnect.Util;

namespace QuantConnect.Securities
{
Expand Down Expand Up @@ -52,7 +53,7 @@ public class Cash
/// If this cash represents the account currency, then an empty enumerable is returned.
/// </summary>
public IEnumerable<Symbol> SecuritySymbols => CurrencyConversion.ConversionRateSecurities.Any()
? CurrencyConversion.ConversionRateSecurities.Select(x => x.Symbol)
? CurrencyConversion.ConversionRateSecurities.Select(x => x.Symbol).Memoize()
// we do this only because Newtonsoft.Json complains about empty enumerables
: new List<Symbol>(0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Util;

namespace QuantConnect.Securities.CurrencyConversion
{
Expand Down Expand Up @@ -65,7 +66,7 @@ public decimal ConversionRate
/// <summary>
/// The securities which the conversion rate is based on
/// </summary>
public IEnumerable<Security> ConversionRateSecurities => Enumerable.Empty<Security>();
public IEnumerable<Security> ConversionRateSecurities => Enumerable.Empty<Security>().Memoize();

/// <summary>
/// Initializes a new instance of the <see cref="ConstantCurrencyConversion"/> class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public decimal ConversionRate
/// <summary>
/// The securities which the conversion rate is based on
/// </summary>
public IEnumerable<Security> ConversionRateSecurities => _steps.Select(step => step.RateSecurity);
public IEnumerable<Security> ConversionRateSecurities => _steps.Select(step => step.RateSecurity).Memoize();

/// <summary>
/// Initializes a new instance of the <see cref="SecurityCurrencyConversion"/> class.
Expand Down
50 changes: 39 additions & 11 deletions Common/Securities/SecurityTransactionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Util;
using Python.Runtime;

namespace QuantConnect.Securities
Expand Down Expand Up @@ -255,7 +256,7 @@ public List<OrderTicket> CancelOpenOrders()
}

var cancelledOrders = new List<OrderTicket>();
foreach (var ticket in GetOpenOrderTickets())
foreach (var ticket in GetOpenOrderTickets(null, memoize: false))
{
ticket.Cancel(Messages.SecurityTransactionManager.OrderCanceledByCancelOpenOrders(_algorithm.UtcTime));
cancelledOrders.Add(ticket);
Expand All @@ -277,7 +278,7 @@ public List<OrderTicket> CancelOpenOrders(Symbol symbol, string tag = null)
}

var cancelledOrders = new List<OrderTicket>();
foreach (var ticket in GetOpenOrderTickets(x => x.Symbol == symbol))
foreach (var ticket in GetOpenOrderTickets(x => x.Symbol == symbol, memoize: false))
{
ticket.Cancel(tag);
cancelledOrders.Add(ticket);
Expand All @@ -302,7 +303,7 @@ public OrderTicket RemoveOrder(int orderId, string tag = null)
/// <returns>An enumerable of <see cref="OrderTicket"/> matching the specified <paramref name="filter"/></returns>
public IEnumerable<OrderTicket> GetOrderTickets(Func<OrderTicket, bool> filter = null)
{
return _orderProcessor.GetOrderTickets(filter ?? (x => true));
return GetOrderTickets(filter, memoize: true);
}

/// <summary>
Expand All @@ -312,7 +313,21 @@ public IEnumerable<OrderTicket> GetOrderTickets(Func<OrderTicket, bool> filter =
/// <returns>An enumerable of <see cref="OrderTicket"/> matching the specified <paramref name="filter"/></returns>
public IEnumerable<OrderTicket> GetOrderTickets(PyObject filter)
{
return _orderProcessor.GetOrderTickets(filter.SafeAs<Func<OrderTicket, bool>>());
return GetOrderTickets(filter.SafeAs<Func<OrderTicket, bool>>(), memoize: true);
}

/// <summary>
/// Gets an enumerable of <see cref="OrderTicket"/> matching the specified <paramref name="filter"/>
/// </summary>
/// <param name="filter">The filter predicate used to find the required order tickets, null matches all</param>
/// <param name="memoize">True for enumerables handed back to user algorithms, which may enumerate them
/// multiple times. Engine paths that enumerate a single time should pass false, since memoization there
/// only adds allocation and locking overhead</param>
/// <returns>An enumerable of <see cref="OrderTicket"/> matching the specified <paramref name="filter"/></returns>
private IEnumerable<OrderTicket> GetOrderTickets(Func<OrderTicket, bool> filter, bool memoize)
{
var tickets = _orderProcessor.GetOrderTickets(filter ?? (x => true));
return memoize ? tickets.Memoize() : tickets;
}

/// <summary>
Expand All @@ -332,7 +347,7 @@ public IEnumerable<OrderTicket> GetOpenOrderTickets(Symbol symbol)
/// <returns>An enumerable of opened <see cref="OrderTicket"/> matching the specified <paramref name="filter"/></returns>
public IEnumerable<OrderTicket> GetOpenOrderTickets(Func<OrderTicket, bool> filter = null)
{
return _orderProcessor.GetOpenOrderTickets(filter ?? (x => true));
return GetOpenOrderTickets(filter, memoize: true);
}

/// <summary>
Expand All @@ -350,7 +365,21 @@ public IEnumerable<OrderTicket> GetOpenOrderTickets(PyObject filter)
{
return GetOpenOrderTickets(pythonSymbol);
}
return _orderProcessor.GetOpenOrderTickets(filter.SafeAs<Func<OrderTicket, bool>>());
return GetOpenOrderTickets(filter.SafeAs<Func<OrderTicket, bool>>(), memoize: true);
}

/// <summary>
/// Gets an enumerable of opened <see cref="OrderTicket"/> matching the specified <paramref name="filter"/>
/// </summary>
/// <param name="filter">The filter predicate used to find the required order tickets, null matches all</param>
/// <param name="memoize">True for enumerables handed back to user algorithms, which may enumerate them
/// multiple times. Engine paths that enumerate a single time should pass false, since memoization there
/// only adds allocation and locking overhead</param>
/// <returns>An enumerable of opened <see cref="OrderTicket"/> matching the specified <paramref name="filter"/></returns>
private IEnumerable<OrderTicket> GetOpenOrderTickets(Func<OrderTicket, bool> filter, bool memoize)
{
var tickets = _orderProcessor.GetOpenOrderTickets(filter ?? (x => true));
return memoize ? tickets.Memoize() : tickets;
}

/// <summary>
Expand All @@ -360,7 +389,7 @@ public IEnumerable<OrderTicket> GetOpenOrderTickets(PyObject filter)
/// <returns>Total quantity that hasn't been filled yet for all orders that were not filtered</returns>
public decimal GetOpenOrdersRemainingQuantity(Func<OrderTicket, bool> filter = null)
{
return GetOpenOrderTickets(filter)
return GetOpenOrderTickets(filter, memoize: false)
.Aggregate(0m, (d, t) => d + t.QuantityRemaining);
}

Expand All @@ -380,8 +409,7 @@ public decimal GetOpenOrdersRemainingQuantity(PyObject filter)
return GetOpenOrdersRemainingQuantity(pythonSymbol);
}

return GetOpenOrderTickets(filter)
.Aggregate(0m, (d, t) => d + t.QuantityRemaining);
return GetOpenOrdersRemainingQuantity(filter.SafeAs<Func<OrderTicket, bool>>());
}

/// <summary>
Expand Down Expand Up @@ -512,7 +540,7 @@ public List<Order> GetOrdersByBrokerageId(string brokerageId)
/// <returns>All orders this order provider currently holds by the specified filter</returns>
public IEnumerable<Order> GetOrders(Func<Order, bool> filter = null)
{
return _orderProcessor.GetOrders(filter ?? (x => true));
return _orderProcessor.GetOrders(filter ?? (x => true)).Memoize();
}

/// <summary>
Expand All @@ -522,7 +550,7 @@ public IEnumerable<Order> GetOrders(Func<Order, bool> filter = null)
/// <returns>All orders this order provider currently holds by the specified filter</returns>
public IEnumerable<Order> GetOrders(PyObject filter)
{
return _orderProcessor.GetOrders(filter.SafeAs<Func<Order, bool>>());
return _orderProcessor.GetOrders(filter.SafeAs<Func<Order, bool>>()).Memoize();
}

/// <summary>
Expand Down
19 changes: 19 additions & 0 deletions Tests/Common/Data/SubscriptionManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
using NodaTime;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Logging;
using QuantConnect.Statistics;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util;

namespace QuantConnect.Tests.Common.Data
{
Expand All @@ -53,6 +55,23 @@ public void GetsSubscriptionDataTypesLowResolution(SecurityType securityType, Re
}
}

[Test]
public void SubscriptionsAreMemoizedExposingCount()
{
var algorithm = new QCAlgorithm();
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
algorithm.AddEquity("SPY");
algorithm.AddForex("EURUSD");

var subscriptions = algorithm.SubscriptionManager.Subscriptions;

// memoized so it exposes a Count property, which also enables len() in Python through Python.NET
Assert.IsInstanceOf<MemoizingEnumerable<SubscriptionDataConfig>>(subscriptions);
var memoizedSubscriptions = (MemoizingEnumerable<SubscriptionDataConfig>)subscriptions;
Assert.Greater(memoizedSubscriptions.Count, 0);
Assert.AreEqual(subscriptions.Count(), memoizedSubscriptions.Count);
}

[Test]
[TestCase(SecurityType.Base, Resolution.Minute, typeof(TradeBar), TickType.Trade)]
[TestCase(SecurityType.Base, Resolution.Tick, typeof(Tick), TickType.Trade)]
Expand Down
35 changes: 35 additions & 0 deletions Tests/Common/Securities/CashTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using QuantConnect.Securities.CurrencyConversion;
using QuantConnect.Tests.Common.Data.UniverseSelection;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util;

namespace QuantConnect.Tests.Common.Securities
{
Expand Down Expand Up @@ -115,6 +116,40 @@ public void EnsureCurrencyDataFeedAddsSubscription()
Assert.AreEqual(1, securities.Values.Count(x => x.Symbol == Symbols.USDJPY));
}

[Test]
public void SecuritySymbolsAreMemoizedExposingCount()
{
const int quantity = 100;
const decimal conversionRate = 1 / 100m;
var cash = new Cash("JPY", quantity, conversionRate);
var cashBook = new CashBook();
cashBook.Add("JPY", cash);
var subscriptions = new SubscriptionManager(NullTimeKeeper.Instance);
var dataManager = new DataManagerStub(TimeKeeper);
subscriptions.SetDataManager(dataManager);
var abcConfig = subscriptions.Add(Symbols.SPY, Resolution.Minute, TimeZone, TimeZone);
var securities = new SecurityManager(TimeKeeper);

securities.Add(
Symbols.SPY,
new Security(
SecurityExchangeHours,
abcConfig,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(cashBook.AccountCurrency),
cashBook,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()));
cash.EnsureCurrencyDataFeed(securities, subscriptions, MarketMap, SecurityChanges.None, dataManager.SecurityService, cashBook.AccountCurrency);

var securitySymbols = cash.SecuritySymbols;

// memoized so it exposes a Count property, which also enables len() in Python through Python.NET
Assert.IsInstanceOf<MemoizingEnumerable<Symbol>>(securitySymbols);
Assert.AreEqual(1, ((MemoizingEnumerable<Symbol>)securitySymbols).Count);
Assert.AreEqual(Symbols.USDJPY, securitySymbols.Single());
}

[Test]
public void EnsureCurrencyDataFeedChecksSecurityChangesForSecurity()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using QuantConnect.Securities;
using QuantConnect.Securities.CurrencyConversion;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util;

namespace QuantConnect.Tests.Common.Securities.CurrencyConversion
{
Expand Down Expand Up @@ -91,6 +92,34 @@ public void LinearSearchFindsTwoLegConversions()
Assert.AreEqual(Symbols.EURUSD, securities[1].Symbol);
}

[Test]
public void ConversionRateSecuritiesAreMemoizedExposingCount()
{
var currencyConversion = SecurityCurrencyConversion.LinearSearch(
"BTC",
"EUR",
new List<Security>(0),
new List<Symbol> { Symbols.BTCUSD, Symbols.EURUSD },
CreateSecurity);

var securities = currencyConversion.ConversionRateSecurities;

// memoized so it exposes a Count property, which also enables len() in Python through Python.NET
Assert.IsInstanceOf<MemoizingEnumerable<Security>>(securities);
Assert.AreEqual(2, ((MemoizingEnumerable<Security>)securities).Count);
}

[Test]
public void ConstantConversionRateSecuritiesAreMemoizedExposingCount()
{
var currencyConversion = new ConstantCurrencyConversion("EUR", "USD", 1.2m);

var securities = currencyConversion.ConversionRateSecurities;

Assert.IsInstanceOf<MemoizingEnumerable<Security>>(securities);
Assert.AreEqual(0, ((MemoizingEnumerable<Security>)securities).Count);
}

[Test]
public void LinearSearchPrefersExistingSecuritiesOverNewOnesOneLeg()
{
Expand Down
Loading
Loading