Lean  $LEAN_TAG$
UserDefinedUniverse.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Linq;
18 using QuantConnect.Util;
21 using System.Collections.Generic;
22 using System.Collections.Specialized;
23 
25 {
26  /// <summary>
27  /// Represents the universe defined by the user's algorithm. This is
28  /// the default universe where manually added securities live by
29  /// market/security type. They can also be manually generated and
30  /// can be configured to fire on certain interval and will always
31  /// return the internal list of symbols.
32  /// </summary>
33  public class UserDefinedUniverse : Universe, INotifyCollectionChanged, ITimeTriggeredUniverse
34  {
35  private readonly TimeSpan _interval;
36  private readonly HashSet<SubscriptionDataConfig> _subscriptionDataConfigs = new HashSet<SubscriptionDataConfig>();
37  private readonly HashSet<Symbol> _symbols = new HashSet<Symbol>();
38  // `UniverseSelection.RemoveSecurityFromUniverse()` will query us at `GetSubscriptionRequests()` to get the `SubscriptionDataConfig` and remove it from the DF
39  // and we need to return the config even after the call to `Remove()`
40  private readonly HashSet<SubscriptionDataConfig> _pendingRemovedConfigs = new HashSet<SubscriptionDataConfig>();
41  private readonly Func<DateTime, IEnumerable<Symbol>> _selector;
42  private readonly object _lock = new ();
43 
44  /// <summary>
45  /// Event fired when a symbol is added or removed from this universe
46  /// </summary>
47  public event NotifyCollectionChangedEventHandler CollectionChanged;
48 
49  /// <summary>
50  /// Gets the interval of this user defined universe
51  /// </summary>
52  public TimeSpan Interval
53  {
54  get { return _interval; }
55  }
56 
57  /// <summary>
58  /// Initializes a new instance of the <see cref="UserDefinedUniverse"/> class
59  /// </summary>
60  /// <param name="configuration">The configuration used to resolve the data for universe selection</param>
61  /// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
62  /// <param name="interval">The interval at which selection should be performed</param>
63  /// <param name="symbols">The initial set of symbols in this universe</param>
64  public UserDefinedUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, TimeSpan interval, IEnumerable<Symbol> symbols)
65  : base(configuration)
66  {
67  _interval = interval;
68  _symbols = symbols.ToHashSet();
69  UniverseSettings = universeSettings;
70  // the selector Func will be the union of the provided symbols and the added symbols or subscriptions data configurations
71  _selector = time => {
72  lock(_lock)
73  {
74  return _subscriptionDataConfigs.Select(x => x.Symbol).Union(_symbols).ToHashSet();
75  }
76  };
77  }
78 
79  /// <summary>
80  /// Initializes a new instance of the <see cref="UserDefinedUniverse"/> class
81  /// </summary>
82  /// <param name="configuration">The configuration used to resolve the data for universe selection</param>
83  /// <param name="universeSettings">The settings used for new subscriptions generated by this universe</param>
84  /// <param name="interval">The interval at which selection should be performed</param>
85  /// <param name="selector">Universe selection function invoked for each time returned via GetTriggerTimes.
86  /// The function parameter is a DateTime in the time zone of configuration.ExchangeTimeZone</param>
87  public UserDefinedUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings, TimeSpan interval, Func<DateTime, IEnumerable<string>> selector)
88  : base(configuration)
89  {
90  _interval = interval;
91  UniverseSettings = universeSettings;
92  _selector = time =>
93  {
94  var selectSymbolsResult = selector(time.ConvertFromUtc(Configuration.ExchangeTimeZone));
95  // if we received an unchaged then short circuit the symbol creation and return it directly
96  if (ReferenceEquals(selectSymbolsResult, Unchanged)) return Unchanged;
97  return selectSymbolsResult.Select(sym => Symbol.Create(sym, Configuration.SecurityType, Configuration.Market));
98  };
99  }
100 
101  /// <summary>
102  /// Creates a user defined universe symbol
103  /// </summary>
104  /// <param name="securityType">The security</param>
105  /// <param name="market">The market</param>
106  /// <returns>A symbol for user defined universe of the specified security type and market</returns>
107  public static Symbol CreateSymbol(SecurityType securityType, string market)
108  {
109  var ticker = $"qc-universe-userdefined-{market.ToLowerInvariant()}-{securityType}";
110  return UniverseExtensions.CreateSymbol(securityType, market, ticker);
111  }
112 
113  /// <summary>
114  /// Adds the specified <see cref="Symbol"/> to this universe
115  /// </summary>
116  /// <param name="symbol">The symbol to be added to this universe</param>
117  /// <returns>True if the symbol was added, false if it was already present</returns>
118  public bool Add(Symbol symbol)
119  {
120  var added = false;
121  lock (_lock)
122  {
123  // let's not call the event having the lock if we don't need too
124  added = _symbols.Add(symbol);
125  }
126 
127  if (added)
128  {
129  OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, symbol));
130  return true;
131  }
132  return false;
133  }
134 
135  /// <summary>
136  /// Adds the specified <see cref="SubscriptionDataConfig"/> to this universe
137  /// </summary>
138  /// <param name="subscriptionDataConfig">The subscription data configuration to be added to this universe</param>
139  /// <returns>True if the subscriptionDataConfig was added, false if it was already present</returns>
140  public bool Add(SubscriptionDataConfig subscriptionDataConfig)
141  {
142  var added = false;
143  lock (_lock)
144  {
145  // let's not call the event having the lock if we don't need too
146  added = _subscriptionDataConfigs.Add(subscriptionDataConfig);
147  }
148 
149  if (added)
150  {
151  OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, subscriptionDataConfig.Symbol));
152  return true;
153  }
154  return false;
155  }
156 
157  /// <summary>
158  /// Removes the specified <see cref="Symbol"/> from this universe
159  /// </summary>
160  /// <param name="symbol">The symbol to be removed</param>
161  /// <returns>True if the symbol was removed, false if the symbol was not present</returns>
162  public bool Remove(Symbol symbol)
163  {
164  if (RemoveAndKeepTrack(symbol))
165  {
166  OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, symbol));
167  return true;
168  }
169  return false;
170  }
171 
172  /// <summary>
173  /// Returns the symbols defined by the user for this universe
174  /// </summary>
175  /// <param name="utcTime">The current utc time</param>
176  /// <param name="data">The symbols to remain in the universe</param>
177  /// <returns>The data that passes the filter</returns>
178  public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
179  {
180  return _selector(utcTime);
181  }
182 
183  /// <summary>
184  /// Returns an enumerator that defines when this user defined universe will be invoked
185  /// </summary>
186  /// <returns>An enumerator of DateTime that defines when this universe will be invoked</returns>
187  public virtual IEnumerable<DateTime> GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase)
188  {
189  var exchangeHours = marketHoursDatabase.GetExchangeHours(Configuration);
190  var localStartTime = startTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
191  var localEndTime = endTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
192 
193  var first = true;
194  foreach (var dateTime in LinqExtensions.Range(localStartTime, localEndTime, dt => dt + Interval))
195  {
196  if (first)
197  {
198  yield return dateTime;
199  first = false;
200  }
201  else if (exchangeHours.IsOpen(dateTime, dateTime + Interval, Configuration.ExtendedMarketHours))
202  {
203  yield return dateTime;
204  }
205  }
206  }
207 
208  /// <summary>
209  /// Event invocator for the <see cref="CollectionChanged"/> event
210  /// </summary>
211  /// <param name="e">The notify collection changed event arguments</param>
212  protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
213  {
214  CollectionChanged?.Invoke(this, e);
215  }
216 
217  /// <summary>
218  /// Gets the subscription requests to be added for the specified security
219  /// </summary>
220  /// <param name="security">The security to get subscriptions for</param>
221  /// <param name="currentTimeUtc">The current time in utc. This is the frontier time of the algorithm</param>
222  /// <param name="maximumEndTimeUtc">The max end time</param>
223  /// <param name="subscriptionService">Instance which implements <see cref="ISubscriptionDataConfigService"/> interface</param>
224  /// <returns>All subscriptions required by this security</returns>
225  public override IEnumerable<SubscriptionRequest> GetSubscriptionRequests(Security security,
226  DateTime currentTimeUtc,
227  DateTime maximumEndTimeUtc,
228  ISubscriptionDataConfigService subscriptionService)
229  {
230  List<SubscriptionDataConfig> result;
231  lock (_lock)
232  {
233  result = _subscriptionDataConfigs.Where(x => x.Symbol == security.Symbol).ToList();
234  if (!result.Any())
235  {
236  result = _pendingRemovedConfigs.Where(x => x.Symbol == security.Symbol).ToList();
237  if (result.Any())
238  {
239  _pendingRemovedConfigs.RemoveWhere(x => x.Symbol == security.Symbol);
240  }
241  else
242  {
243  result = base.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc, subscriptionService).Select(x => x.Configuration).ToList();
244  // we create subscription data configs ourselves, add the configs
245  _subscriptionDataConfigs.UnionWith(result);
246  }
247  }
248  }
249  return result.Select(config => new SubscriptionRequest(isUniverseSubscription: false,
250  universe: this,
251  security: security,
252  configuration: config,
253  startTimeUtc: currentTimeUtc,
254  endTimeUtc: maximumEndTimeUtc));
255  }
256 
257  /// <summary>
258  /// Tries to remove the specified security from the universe.
259  /// </summary>
260  /// <param name="utcTime">The current utc time</param>
261  /// <param name="security">The security to be removed</param>
262  /// <returns>True if the security was successfully removed, false if
263  /// we're not allowed to remove or if the security didn't exist</returns>
264  internal override bool RemoveMember(DateTime utcTime, Security security)
265  {
266  if (base.RemoveMember(utcTime, security))
267  {
268  RemoveAndKeepTrack(security.Symbol);
269  return true;
270  }
271  return false;
272  }
273 
274  private bool RemoveAndKeepTrack(Symbol symbol)
275  {
276  lock (_lock)
277  {
278  var toBeRemoved = _subscriptionDataConfigs.Where(x => x.Symbol == symbol).ToList();
279  var removedSymbol = _symbols.Remove(symbol);
280 
281  if (removedSymbol || toBeRemoved.Any())
282  {
283  _subscriptionDataConfigs.RemoveWhere(x => x.Symbol == symbol);
284  _pendingRemovedConfigs.UnionWith(toBeRemoved);
285  return true;
286  }
287 
288  return false;
289  }
290  }
291  }
292 }