/*
* 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 QuantConnect.Util;
namespace QuantConnect.Securities
{
///
/// Base class for security databases, including market hours and symbol properties.
///
public abstract class BaseSecurityDatabase
where T : BaseSecurityDatabase
{
///
/// The database instance loaded from the data folder
///
protected static T DataFolderDatabase { get; set; }
///
/// Lock object for the data folder database
///
protected static readonly object DataFolderDatabaseLock = new object();
///
/// The database entries
///
protected Dictionary Entries { get; set; }
///
/// Custom entries set by the user.
///
protected HashSet CustomEntries { get; }
// _loadFromFromDataFolder and _updateEntry are used to load the database from
// the data folder and update an entry respectively.
// These are not abstract or virtual methods because they might be static methods.
private readonly Func _loadFromFromDataFolder;
private readonly Action _updateEntry;
///
/// Initializes a new instance of the class
///
/// The full listing of exchange hours by key
/// Method to load the database form the data folder
/// Method to update a database entry
protected BaseSecurityDatabase(Dictionary entries,
Func fromDataFolder, Action updateEntry)
{
Entries = entries;
CustomEntries = new();
_loadFromFromDataFolder = fromDataFolder;
_updateEntry = updateEntry;
}
///
/// Resets the database, forcing a reload when reused.
/// Called in tests where multiple algorithms are run sequentially,
/// and we need to guarantee that every test starts with the same environment.
///
#pragma warning disable CA1000 // Do not declare static members on generic types
public static void Reset()
#pragma warning restore CA1000 // Do not declare static members on generic types
{
lock (DataFolderDatabaseLock)
{
DataFolderDatabase = null;
}
}
///
/// Reload entries dictionary from file and merge them with previous custom ones
///
internal void UpdateDataFolderDatabase()
{
lock (DataFolderDatabaseLock)
{
Reset();
var newDatabase = _loadFromFromDataFolder();
Merge(newDatabase, resetCustomEntries: false);
// Make sure we keep this as the data folder database
DataFolderDatabase = (T)this;
}
}
///
/// Updates the entries dictionary with the new entries from the specified database
///
internal virtual void Merge(T newDatabase, bool resetCustomEntries)
{
var newEntries = new List>();
foreach (var newEntry in newDatabase.Entries)
{
if (Entries.TryGetValue(newEntry.Key, out var entry))
{
if (resetCustomEntries || !CustomEntries.Contains(newEntry.Key))
{
_updateEntry(entry, newEntry.Value);
}
}
else
{
newEntries.Add(KeyValuePair.Create(newEntry.Key, newEntry.Value));
}
}
Entries = Entries
.Where(kvp => (!resetCustomEntries && CustomEntries.Contains(kvp.Key)) || newDatabase.Entries.ContainsKey(kvp.Key))
.Concat(newEntries)
.ToDictionary();
if (resetCustomEntries)
{
CustomEntries.Clear();
}
}
///
/// Determines if the database contains the specified key
///
/// The key to search for
/// True if an entry is found, otherwise false
protected bool ContainsKey(SecurityDatabaseKey key)
{
return Entries.ContainsKey(key);
}
///
/// Check whether an entry exists for the specified market/symbol/security-type
///
/// The market the exchange resides in, i.e, 'usa', 'fxcm', ect...
/// The particular symbol being traded
/// The security type of the symbol
public bool ContainsKey(string market, string symbol, SecurityType securityType)
{
return ContainsKey(new SecurityDatabaseKey(market, symbol, securityType));
}
///
/// Check whether an entry exists for the specified market/symbol/security-type
///
/// The market the exchange resides in, i.e, 'usa', 'fxcm', ect...
/// The particular symbol being traded (Symbol class)
/// The security type of the symbol
public bool ContainsKey(string market, Symbol symbol, SecurityType securityType)
{
return ContainsKey(
market,
GetDatabaseSymbolKey(symbol),
securityType);
}
///
/// Gets the correct string symbol to use as a database key
///
/// The symbol
/// The symbol string used in the database ke
#pragma warning disable CA1000 // Do not declare static members on generic types
public static string GetDatabaseSymbolKey(Symbol symbol)
#pragma warning restore CA1000 // Do not declare static members on generic types
{
string stringSymbol;
if (symbol == null)
{
stringSymbol = string.Empty;
}
else
{
switch (symbol.ID.SecurityType)
{
case SecurityType.Option:
stringSymbol = symbol.HasUnderlying ? symbol.Underlying.Value : string.Empty;
break;
case SecurityType.IndexOption:
case SecurityType.FutureOption:
stringSymbol = symbol.HasUnderlying ? symbol.ID.Symbol : string.Empty;
break;
case SecurityType.Base:
case SecurityType.Future:
stringSymbol = symbol.ID.Symbol;
break;
default:
stringSymbol = symbol.Value;
break;
}
}
return stringSymbol;
}
}
}