Lean  $LEAN_TAG$
BaseSecurityDatabase.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.Collections.Generic;
18 using System.Linq;
19 using QuantConnect.Util;
20 
22 {
23  /// <summary>
24  /// Base class for security databases, including market hours and symbol properties.
25  /// </summary>
26  public abstract class BaseSecurityDatabase<T, TEntry>
27  where T : BaseSecurityDatabase<T, TEntry>
28  {
29  /// <summary>
30  /// The database instance loaded from the data folder
31  /// </summary>
32  protected static T DataFolderDatabase { get; set; }
33 
34  /// <summary>
35  /// Lock object for the data folder database
36  /// </summary>
37  protected static readonly object DataFolderDatabaseLock = new object();
38 
39  /// <summary>
40  /// The database entries
41  /// </summary>
42  protected Dictionary<SecurityDatabaseKey, TEntry> Entries { get; set; }
43 
44  /// <summary>
45  /// Custom entries set by the user.
46  /// </summary>
47  protected HashSet<SecurityDatabaseKey> CustomEntries { get; }
48 
49  // _loadFromFromDataFolder and _updateEntry are used to load the database from
50  // the data folder and update an entry respectively.
51  // These are not abstract or virtual methods because they might be static methods.
52  private readonly Func<T> _loadFromFromDataFolder;
53  private readonly Action<TEntry, TEntry> _updateEntry;
54 
55  /// <summary>
56  /// Initializes a new instance of the <see cref="BaseSecurityDatabase{T, TEntry}"/> class
57  /// </summary>
58  /// <param name="entries">The full listing of exchange hours by key</param>
59  /// <param name="fromDataFolder">Method to load the database form the data folder</param>
60  /// <param name="updateEntry">Method to update a database entry</param>
61  protected BaseSecurityDatabase(Dictionary<SecurityDatabaseKey, TEntry> entries,
62  Func<T> fromDataFolder, Action<TEntry, TEntry> updateEntry)
63  {
64  Entries = entries;
65  CustomEntries = new();
66  _loadFromFromDataFolder = fromDataFolder;
67  _updateEntry = updateEntry;
68  }
69 
70  /// <summary>
71  /// Resets the database, forcing a reload when reused.
72  /// Called in tests where multiple algorithms are run sequentially,
73  /// and we need to guarantee that every test starts with the same environment.
74  /// </summary>
75 #pragma warning disable CA1000 // Do not declare static members on generic types
76  public static void Reset()
77 #pragma warning restore CA1000 // Do not declare static members on generic types
78  {
80  {
81  DataFolderDatabase = null;
82  }
83  }
84 
85  /// <summary>
86  /// Reload entries dictionary from file and merge them with previous custom ones
87  /// </summary>
88  internal void UpdateDataFolderDatabase()
89  {
91  {
92  Reset();
93  var newDatabase = _loadFromFromDataFolder();
94  Merge(newDatabase, resetCustomEntries: false);
95  // Make sure we keep this as the data folder database
96  DataFolderDatabase = (T)this;
97  }
98  }
99 
100  /// <summary>
101  /// Updates the entries dictionary with the new entries from the specified database
102  /// </summary>
103  internal virtual void Merge(T newDatabase, bool resetCustomEntries)
104  {
105  var newEntries = new List<KeyValuePair<SecurityDatabaseKey, TEntry>>();
106 
107  foreach (var newEntry in newDatabase.Entries)
108  {
109  if (Entries.TryGetValue(newEntry.Key, out var entry))
110  {
111  if (resetCustomEntries || !CustomEntries.Contains(newEntry.Key))
112  {
113  _updateEntry(entry, newEntry.Value);
114  }
115  }
116  else
117  {
118  newEntries.Add(KeyValuePair.Create(newEntry.Key, newEntry.Value));
119  }
120  }
121 
122  Entries = Entries
123  .Where(kvp => (!resetCustomEntries && CustomEntries.Contains(kvp.Key)) || newDatabase.Entries.ContainsKey(kvp.Key))
124  .Concat(newEntries)
125  .ToDictionary();
126 
127  if (resetCustomEntries)
128  {
129  CustomEntries.Clear();
130  }
131  }
132 
133  /// <summary>
134  /// Determines if the database contains the specified key
135  /// </summary>
136  /// <param name="key">The key to search for</param>
137  /// <returns>True if an entry is found, otherwise false</returns>
138  protected bool ContainsKey(SecurityDatabaseKey key)
139  {
140  return Entries.ContainsKey(key);
141  }
142 
143  /// <summary>
144  /// Check whether an entry exists for the specified market/symbol/security-type
145  /// </summary>
146  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
147  /// <param name="symbol">The particular symbol being traded</param>
148  /// <param name="securityType">The security type of the symbol</param>
149  public bool ContainsKey(string market, string symbol, SecurityType securityType)
150  {
151  return ContainsKey(new SecurityDatabaseKey(market, symbol, securityType));
152  }
153 
154  /// <summary>
155  /// Check whether an entry exists for the specified market/symbol/security-type
156  /// </summary>
157  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
158  /// <param name="symbol">The particular symbol being traded (Symbol class)</param>
159  /// <param name="securityType">The security type of the symbol</param>
160  public bool ContainsKey(string market, Symbol symbol, SecurityType securityType)
161  {
162  return ContainsKey(
163  market,
164  GetDatabaseSymbolKey(symbol),
165  securityType);
166  }
167 
168  /// <summary>
169  /// Gets the correct string symbol to use as a database key
170  /// </summary>
171  /// <param name="symbol">The symbol</param>
172  /// <returns>The symbol string used in the database ke</returns>
173 #pragma warning disable CA1000 // Do not declare static members on generic types
174  public static string GetDatabaseSymbolKey(Symbol symbol)
175 #pragma warning restore CA1000 // Do not declare static members on generic types
176  {
177  string stringSymbol;
178  if (symbol == null)
179  {
180  stringSymbol = string.Empty;
181  }
182  else
183  {
184  switch (symbol.ID.SecurityType)
185  {
186  case SecurityType.Option:
187  stringSymbol = symbol.HasUnderlying ? symbol.Underlying.Value : string.Empty;
188  break;
189  case SecurityType.IndexOption:
190  case SecurityType.FutureOption:
191  stringSymbol = symbol.HasUnderlying ? symbol.ID.Symbol : string.Empty;
192  break;
193  case SecurityType.Base:
194  case SecurityType.Future:
195  stringSymbol = symbol.ID.Symbol;
196  break;
197  default:
198  stringSymbol = symbol.Value;
199  break;
200  }
201  }
202 
203  return stringSymbol;
204  }
205  }
206 }