Skip to content

Commit 75d5cbc

Browse files
authored
Fix: RandomDataGenerator crashes when algorithm is null (#9211)
* Fix NullReferenceException in SecurityService * Simplify the unit test * Add unit test for RandomDataGenerator * Improve unit test name * Solve review comments
1 parent d702587 commit 75d5cbc

File tree

3 files changed

+133
-2
lines changed

3 files changed

+133
-2
lines changed

Common/Securities/SecurityService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ private void InitializeSecurity(bool initializeSecurity, Security security, bool
343343
{
344344
if (initializeSecurity && !security.IsInitialized)
345345
{
346-
if (seedSecurity && _algorithm.Settings.SeedInitialPrices)
346+
if (seedSecurity && _algorithm != null && _algorithm.Settings.SeedInitialPrices)
347347
{
348348
AlgorithmUtils.SeedSecurities([security], _algorithm);
349349
}

Tests/Common/Securities/SecurityServiceTests.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
using Moq;
2020
using NUnit.Framework;
2121
using QuantConnect.Algorithm.CSharp;
22+
using QuantConnect.Configuration;
2223
using QuantConnect.Data;
2324
using QuantConnect.Data.Auxiliary;
2425
using QuantConnect.Data.Market;
2526
using QuantConnect.Data.UniverseSelection;
2627
using QuantConnect.Interfaces;
2728
using QuantConnect.Securities;
2829
using QuantConnect.Tests.Engine.DataFeeds;
30+
using QuantConnect.ToolBox.RandomDataGenerator;
31+
using QuantConnect.Util;
2932

3033
namespace QuantConnect.Tests.Common.Securities
3134
{
@@ -216,7 +219,7 @@ public void CreatesEquityOptionWithContractMultiplierEqualsToContractUnitOfTrade
216219
var equityOptionSecurity = (QuantConnect.Securities.Option.Option)_securityService.CreateSecurity(equityOption, configs, 1.0m);
217220

218221
Assert.AreEqual(100, equityOptionSecurity.ContractMultiplier);
219-
Assert.AreEqual(100,equityOptionSecurity.ContractUnitOfTrade);
222+
Assert.AreEqual(100, equityOptionSecurity.ContractUnitOfTrade);
220223
}
221224

222225
[Test]
@@ -277,5 +280,36 @@ public void AddPrimaryExchangeToSecurityObject()
277280
Assert.AreEqual(equity.Subscriptions.First().TickType, TickType.Trade);
278281
Assert.AreEqual(((QuantConnect.Securities.Equity.Equity)equity).PrimaryExchange, Exchange.NASDAQ);
279282
}
283+
284+
[Test]
285+
public void CreateSecurityDoesNotThrowWithNullAlgorithm()
286+
{
287+
var startDate = new DateTime(2024, 1, 1);
288+
var securityManager = new SecurityManager(new TimeKeeper(startDate, new[] { TimeZones.Utc }));
289+
290+
var securityService = new SecurityService(
291+
new CashBook(),
292+
MarketHoursDatabase.FromDataFolder(),
293+
SymbolPropertiesDatabase.FromDataFolder(),
294+
new SecurityInitializerProvider(new FuncSecurityInitializer(security => { })),
295+
RegisteredSecurityDataTypesProvider.Null,
296+
new SecurityCacheProvider(
297+
new SecurityPortfolioManager(securityManager,
298+
new SecurityTransactionManager(null, securityManager),
299+
new AlgorithmSettings())),
300+
new MapFilePrimaryExchangeProvider(
301+
Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>(
302+
Config.Get("map-file-provider", "LocalDiskMapFileProvider"))),
303+
algorithm: null
304+
);
305+
306+
securityManager.SetSecurityService(securityService);
307+
308+
Assert.DoesNotThrow(() =>
309+
{
310+
var symbol = Symbol.Create("TEST", SecurityType.Equity, Market.USA);
311+
var security = securityManager.CreateSecurity(symbol, new List<SubscriptionDataConfig>(), underlying: null);
312+
});
313+
}
280314
}
281315
}

Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using static QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator;
3030
using QuantConnect.Algorithm;
3131
using System.Linq;
32+
using System.IO;
3233

3334
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
3435
{
@@ -162,6 +163,102 @@ public void GetProgressAsPercentageShouldLogWhenProgressExceedsThreshold(Resolut
162163
Assert.IsTrue(logs.All(p => p >= 0 && p <= 100));
163164
}
164165

166+
[Test]
167+
public void RandomDataGeneratorCompletesSuccessfully()
168+
{
169+
var tempFolder = Path.Combine(Path.GetTempPath(), $"LeanTest_{Guid.NewGuid()}");
170+
var originalDataFolder = Config.Get("data-folder");
171+
try
172+
{
173+
Directory.CreateDirectory(tempFolder);
174+
Config.Set("data-folder", tempFolder);
175+
Globals.Reset();
176+
177+
var hourPath = Path.Combine(tempFolder, "equity", "usa", "hour");
178+
var dailyPath = Path.Combine(tempFolder, "equity", "usa", "daily");
179+
var factorFilesPath = Path.Combine(tempFolder, "equity", "usa", "factor_files");
180+
var mapFilesPath = Path.Combine(tempFolder, "equity", "usa", "map_files");
181+
182+
// Create the required folders
183+
Directory.CreateDirectory(hourPath);
184+
Directory.CreateDirectory(dailyPath);
185+
Directory.CreateDirectory(factorFilesPath);
186+
Directory.CreateDirectory(mapFilesPath);
187+
188+
var settings = new RandomDataGeneratorSettings
189+
{
190+
Start = new DateTime(2024, 1, 1, 9, 30, 0),
191+
End = new DateTime(2024, 1, 2, 16, 0, 0),
192+
SymbolCount = 1,
193+
Market = "usa",
194+
SecurityType = SecurityType.Equity,
195+
Resolution = Resolution.Hour,
196+
DataDensity = DataDensity.Dense,
197+
IncludeCoarse = false,
198+
QuoteTradeRatio = 1.0,
199+
RandomSeed = 123456,
200+
HasDividendsPercentage = 0,
201+
HasSplitsPercentage = 0,
202+
HasIpoPercentage = 0,
203+
HasRenamePercentage = 0,
204+
Tickers = new List<string>() { "AAPL" }
205+
};
206+
207+
var generator = GetGenerator(settings);
208+
209+
Assert.DoesNotThrow(() => generator.Run());
210+
211+
var allFiles = Directory.GetFiles(tempFolder, "*", SearchOption.AllDirectories);
212+
Assert.Greater(allFiles.Length, 0);
213+
214+
var hourFiles = Directory.GetFiles(hourPath, "*.zip");
215+
Assert.Greater(hourFiles.Length, 0);
216+
}
217+
finally
218+
{
219+
Config.Set("data-folder", originalDataFolder);
220+
Globals.Reset();
221+
Directory.Delete(tempFolder, true);
222+
}
223+
}
224+
225+
private static QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator GetGenerator(RandomDataGeneratorSettings settings)
226+
{
227+
var securityManager = new SecurityManager(new TimeKeeper(settings.Start, new[] { TimeZones.Utc }));
228+
229+
var securityService = new SecurityService(
230+
new CashBook(),
231+
MarketHoursDatabase.FromDataFolder(),
232+
SymbolPropertiesDatabase.FromDataFolder(),
233+
new SecurityInitializerProvider(new FuncSecurityInitializer(security =>
234+
{
235+
// init price
236+
security.SetMarketPrice(new Tick(settings.Start, security.Symbol, 100, 100));
237+
security.SetMarketPrice(new OpenInterest(settings.Start, security.Symbol, 10000));
238+
239+
// from settings
240+
security.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(settings.VolatilityModelResolution);
241+
242+
// from settings
243+
if (security is Option option)
244+
{
245+
option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName,
246+
_interestRateProvider.GetRiskFreeRate(settings.Start, settings.End));
247+
}
248+
})),
249+
RegisteredSecurityDataTypesProvider.Null,
250+
new SecurityCacheProvider(
251+
new SecurityPortfolioManager(securityManager, new SecurityTransactionManager(null, securityManager), new AlgorithmSettings())),
252+
new MapFilePrimaryExchangeProvider(Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>(Config.Get("map-file-provider", "LocalDiskMapFileProvider")))
253+
);
254+
255+
securityManager.SetSecurityService(securityService);
256+
257+
var generator = new QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator();
258+
generator.Init(settings, securityManager);
259+
return generator;
260+
}
261+
165262
private static readonly IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
166263

167264
private static SecurityService GetSecurityService(RandomDataGeneratorSettings settings, SecurityManager securityManager)

0 commit comments

Comments
 (0)