/*
* 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 QuantConnect.Securities;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.ToolBox.RandomDataGenerator
{
///
/// Provide the base symbol generator implementation
///
public abstract class BaseSymbolGenerator
{
///
/// instance producing random values for use in random data generation
///
protected IRandomValueGenerator Random { get; }
///
/// Settings of current random data generation run
///
protected RandomDataGeneratorSettings Settings { get; }
///
/// Exchange hours and raw data times zones in various markets
///
protected MarketHoursDatabase MarketHoursDatabase { get; }
///
/// Access to specific properties for various symbols
///
protected SymbolPropertiesDatabase SymbolPropertiesDatabase { get; }
// used to prevent generating duplicates, but also caps
// the memory allocated to checking for duplicates
private readonly FixedSizeHashQueue _symbols;
///
/// Base constructor implementation for Symbol generator
///
/// random data generation run settings
/// produces random values for use in random data generation
protected BaseSymbolGenerator(RandomDataGeneratorSettings settings, IRandomValueGenerator random)
{
Settings = settings;
Random = random;
_symbols = new FixedSizeHashQueue(1000);
SymbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder();
MarketHoursDatabase = MarketHoursDatabase.FromDataFolder();
}
///
/// Creates a ad-hoc symbol generator depending on settings
///
/// random data generator settings
/// produces random values for use in random data generation
/// New symbol generator
public static BaseSymbolGenerator Create(RandomDataGeneratorSettings settings, IRandomValueGenerator random)
{
if (settings is null)
{
throw new ArgumentNullException(nameof(settings), "Settings cannot be null or empty");
}
if (random is null)
{
throw new ArgumentNullException(nameof(random), "Randomizer cannot be null");
}
switch (settings.SecurityType)
{
case SecurityType.Option:
return new OptionSymbolGenerator(settings, random, 100m, 75m);
case SecurityType.Future:
return new FutureSymbolGenerator(settings, random);
default:
return new DefaultSymbolGenerator(settings, random);
}
}
///
/// Generates specified number of symbols
///
/// Set of random symbols
public IEnumerable GenerateRandomSymbols()
{
if (!Settings.Tickers.IsNullOrEmpty())
{
foreach (var symbol in Settings.Tickers.SelectMany(GenerateAsset))
{
yield return symbol;
}
}
else
{
for (var i = 0; i < Settings.SymbolCount; i++)
{
foreach (var symbol in GenerateAsset())
{
yield return symbol;
}
}
}
}
///
/// Generates a random asset
///
/// Optionally can provide a ticker that should be used
/// Random asset
protected abstract IEnumerable GenerateAsset(string ticker = null);
///
/// Generates random symbol, used further down for asset
///
/// security type
/// market
/// Optionally can provide a ticker to use
/// Random symbol
public Symbol NextSymbol(SecurityType securityType, string market, string ticker = null)
{
if (securityType == SecurityType.Option || securityType == SecurityType.Future)
{
throw new ArgumentException("Please use OptionSymbolGenerator or FutureSymbolGenerator for SecurityType.Option and SecurityType.Future respectively.");
}
if (ticker == null)
{
// we must return a Symbol matching an entry in the Symbol properties database
// if there is a wildcard entry, we can generate a truly random Symbol
// if there is no wildcard entry, the symbols we can generate are limited by the entries in the database
if (SymbolPropertiesDatabase.ContainsKey(market, SecurityDatabaseKey.Wildcard, securityType))
{
// let's make symbols all have 3 chars as it's acceptable for all security types with wildcard entries
ticker = NextUpperCaseString(3, 3);
}
else
{
ticker = NextTickerFromSymbolPropertiesDatabase(securityType, market);
}
}
// by chance we may generate a ticker that actually exists, and if map files exist that match this
// ticker then we'll end up resolving the first trading date for use in the SID, otherwise, all
// generated Symbol will have a date equal to SecurityIdentifier.DefaultDate
var symbol = Symbol.Create(ticker, securityType, market);
if (_symbols.Add(symbol))
{
return symbol;
}
// lo' and behold, we created a duplicate --recurse to find a unique value
// this is purposefully done as the last statement to enable the compiler to
// unroll this method into a tail-recursion loop :)
return NextSymbol(securityType, market);
}
///
/// Return a Ticker matching an entry in the Symbol properties database
///
/// security type
///
/// Random Ticker matching an entry in the Symbol properties database
protected string NextTickerFromSymbolPropertiesDatabase(SecurityType securityType, string market)
{
// prevent returning a ticker matching any previously generated Symbol
var existingTickers = _symbols
.Where(sym => sym.ID.Market == market && sym.ID.SecurityType == securityType)
.Select(sym => sym.Value);
// get the available tickers from the Symbol properties database and remove previously generated tickers
var availableTickers = Enumerable.Except(SymbolPropertiesDatabase.GetSymbolPropertiesList(market, securityType)
.Select(kvp => kvp.Key.Symbol), existingTickers)
.ToList();
// there is a limited number of entries in the Symbol properties database so we may run out of tickers
if (availableTickers.Count == 0)
{
throw new NoTickersAvailableException(securityType, market);
}
return availableTickers[Random.NextInt(availableTickers.Count)];
}
///
/// Generates random expiration date on a friday within specified time range
///
/// market hours
/// minimum expiration date
/// maximum expiration date
/// Random date on a friday within specified time range
protected DateTime GetRandomExpiration(SecurityExchangeHours marketHours, DateTime minExpiry, DateTime maxExpiry)
{
// generate a random expiration date on a friday
var expiry = Random.NextDate(minExpiry, maxExpiry, DayOfWeek.Friday);
// check to see if we're open on this date and if not, back track until we are
// we're using the equity market hours as a proxy since we haven't generated the option Symbol yet
while (!marketHours.IsDateOpen(expiry))
{
expiry = expiry.AddDays(-1);
}
return expiry;
}
///
/// Generates a random within the specified lengths.
///
/// The minimum length, inclusive
/// The maximum length, inclusive
/// A new upper case string within the specified lengths
public string NextUpperCaseString(int minLength, int maxLength)
{
var str = string.Empty;
var length = Random.NextInt(minLength, maxLength);
for (int i = 0; i < length; i++)
{
// A=65 - inclusive lower bound
// Z=90 - inclusive upper bound
var c = (char)Random.NextInt(65, 91);
str += c;
}
return str;
}
///
/// Returns the number of symbols with the specified parameters can be generated.
/// Returns int.MaxValue if there is no limit for the given parameters.
///
/// The number of available symbols for the given parameters, or int.MaxValue if no limit
public abstract int GetAvailableSymbolCount();
}
}