Skip to content

Commit 8149c29

Browse files
authored
Dynamic market hours for time and date rules (#8541)
* Add unit test reproducing the bug * Improve unit test * Apply market hours database updates to instance Make sure entries are updated instead of overridden so that consumers holding the mhdb can pick up the changes * Improved unit tests * Update data folder MHDB entries instead of overriding instance * Optionally reset custom mhdb entries on reset * Simplify mhdb reset in live trading Cleanup and minor fixes * Ensure symbol properties database updates are picked up by consumers Update SPDB entries instead of overriding them, just like for the MHDB so that consumers pick up updates after refresh * Fix for symbol properties update thread safety * Minor improvements and cleanup * Mhdb and spdb update logic simplification * Move "force exchange always open" logic to mhdb * Simplify symbol properties holder and updates * Refactor security databases to use a common base class Introduce BaseSecurityDatabase<T, TEntry> to encapsulate common functionality for MarketHoursDatabase and SymbolPropertiesDatabase. * Cleanup
1 parent de9f9bf commit 8149c29

File tree

15 files changed

+1051
-410
lines changed

15 files changed

+1051
-410
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
21+
namespace QuantConnect.Securities
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+
{
79+
lock (DataFolderDatabaseLock)
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+
{
90+
lock (DataFolderDatabaseLock)
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+
}

0 commit comments

Comments
 (0)