chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide the base symbol generator implementation
|
||||
/// </summary>
|
||||
public abstract class BaseSymbolGenerator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IRandomValueGenerator"/> instance producing random values for use in random data generation
|
||||
/// </summary>
|
||||
protected IRandomValueGenerator Random { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Settings of current random data generation run
|
||||
/// </summary>
|
||||
protected RandomDataGeneratorSettings Settings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Exchange hours and raw data times zones in various markets
|
||||
/// </summary>
|
||||
protected MarketHoursDatabase MarketHoursDatabase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access to specific properties for various symbols
|
||||
/// </summary>
|
||||
protected SymbolPropertiesDatabase SymbolPropertiesDatabase { get; }
|
||||
|
||||
// used to prevent generating duplicates, but also caps
|
||||
// the memory allocated to checking for duplicates
|
||||
private readonly FixedSizeHashQueue<Symbol> _symbols;
|
||||
|
||||
/// <summary>
|
||||
/// Base constructor implementation for Symbol generator
|
||||
/// </summary>
|
||||
/// <param name="settings">random data generation run settings</param>
|
||||
/// <param name="random">produces random values for use in random data generation</param>
|
||||
protected BaseSymbolGenerator(RandomDataGeneratorSettings settings, IRandomValueGenerator random)
|
||||
{
|
||||
Settings = settings;
|
||||
Random = random;
|
||||
_symbols = new FixedSizeHashQueue<Symbol>(1000);
|
||||
SymbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder();
|
||||
MarketHoursDatabase = MarketHoursDatabase.FromDataFolder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ad-hoc symbol generator depending on settings
|
||||
/// </summary>
|
||||
/// <param name="settings">random data generator settings</param>
|
||||
/// <param name="random">produces random values for use in random data generation</param>
|
||||
/// <returns>New symbol generator</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates specified number of symbols
|
||||
/// </summary>
|
||||
/// <returns>Set of random symbols</returns>
|
||||
public IEnumerable<Symbol> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random asset
|
||||
/// </summary>
|
||||
/// <param name="ticker">Optionally can provide a ticker that should be used</param>
|
||||
/// <returns>Random asset</returns>
|
||||
protected abstract IEnumerable<Symbol> GenerateAsset(string ticker = null);
|
||||
|
||||
/// <summary>
|
||||
/// Generates random symbol, used further down for asset
|
||||
/// </summary>
|
||||
/// <param name="securityType">security type</param>
|
||||
/// <param name="market">market</param>
|
||||
/// <param name="ticker">Optionally can provide a ticker to use</param>
|
||||
/// <returns>Random symbol</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a Ticker matching an entry in the Symbol properties database
|
||||
/// </summary>
|
||||
/// <param name="securityType">security type</param>
|
||||
/// <param name="market"></param>
|
||||
/// <returns>Random Ticker matching an entry in the Symbol properties database</returns>
|
||||
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)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates random expiration date on a friday within specified time range
|
||||
/// </summary>
|
||||
/// <param name="marketHours">market hours</param>
|
||||
/// <param name="minExpiry">minimum expiration date</param>
|
||||
/// <param name="maxExpiry">maximum expiration date</param>
|
||||
/// <returns>Random date on a friday within specified time range</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="string"/> within the specified lengths.
|
||||
/// </summary>
|
||||
/// <param name="minLength">The minimum length, inclusive</param>
|
||||
/// <param name="maxLength">The maximum length, inclusive</param>
|
||||
/// <returns>A new upper case string within the specified lengths</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of symbols with the specified parameters can be generated.
|
||||
/// Returns int.MaxValue if there is no limit for the given parameters.
|
||||
/// </summary>
|
||||
/// <returns>The number of available symbols for the given parameters, or int.MaxValue if no limit</returns>
|
||||
public abstract int GetAvailableSymbolCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how dense data should be generated
|
||||
/// </summary>
|
||||
public enum DataDensity
|
||||
{
|
||||
/// <summary>
|
||||
/// At least once per resolution step
|
||||
/// </summary>
|
||||
Dense,
|
||||
|
||||
/// <summary>
|
||||
/// At least once per 5 resolution steps
|
||||
/// </summary>
|
||||
Sparse,
|
||||
|
||||
/// <summary>
|
||||
/// At least once per 50 resolution steps
|
||||
/// </summary>
|
||||
VerySparse
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.Securities;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a new random <see cref="Symbol"/> object of the specified security type.
|
||||
/// All returned symbols have a matching entry in the Symbol properties database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A valid implementation will keep track of generated Symbol objects to ensure duplicates
|
||||
/// are not generated.
|
||||
/// </remarks>
|
||||
public class DefaultSymbolGenerator : BaseSymbolGenerator
|
||||
{
|
||||
private readonly string _market;
|
||||
private readonly SecurityType _securityType;
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="DefaultSymbolGenerator"/> instance
|
||||
/// </summary>
|
||||
/// <param name="settings">random data generation run settings</param>
|
||||
/// <param name="random">produces random values for use in random data generation</param>
|
||||
public DefaultSymbolGenerator(RandomDataGeneratorSettings settings, IRandomValueGenerator random)
|
||||
: base(settings, random)
|
||||
{
|
||||
_market = settings.Market;
|
||||
_securityType = settings.SecurityType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a single-item list at a time using base random implementation
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override IEnumerable<Symbol> GenerateAsset(string ticker = null)
|
||||
{
|
||||
yield return NextSymbol(Settings.SecurityType, Settings.Market, ticker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of symbols with the specified parameters can be generated.
|
||||
/// Returns int.MaxValue if there is no limit for the given parameters.
|
||||
/// </summary>
|
||||
/// <returns>The number of available symbols for the given parameters, or int.MaxValue if no limit</returns>
|
||||
public override int GetAvailableSymbolCount()
|
||||
{
|
||||
// check the Symbol properties database to determine how many symbols we can generate
|
||||
// if there is a wildcard entry, we can generate as many symbols as we want
|
||||
// if there is no wildcard entry, we can only generate as many symbols as there are entries
|
||||
return SymbolPropertiesDatabase.ContainsKey(_market, SecurityDatabaseKey.Wildcard, _securityType)
|
||||
? int.MaxValue
|
||||
: SymbolPropertiesDatabase.GetSymbolPropertiesList(_market, _securityType).Count();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.Data.Market;
|
||||
using QuantConnect.Data.Auxiliary;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates random splits, random dividends, and map file
|
||||
/// </summary>
|
||||
public class DividendSplitMapGenerator
|
||||
{
|
||||
private const double _minimumFinalSplitFactorAllowed = 0.001;
|
||||
|
||||
/// <summary>
|
||||
/// The final factor to adjust all prices with in order to maintain price continuity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set default equal to 1 so that we can use it even in the event of no splits
|
||||
/// </remarks>
|
||||
public decimal FinalSplitFactor { get; set; } = 1m;
|
||||
|
||||
/// <summary>
|
||||
/// Stores <see cref="MapFileRow"/> instances
|
||||
/// </summary>
|
||||
public List<MapFileRow> MapRows { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Stores <see cref="CorporateFactorRow"/> instances
|
||||
/// </summary>
|
||||
public List<CorporateFactorRow> DividendsSplits { get; set; } = new List<CorporateFactorRow>();
|
||||
|
||||
/// <summary>
|
||||
/// Current Symbol value. Can be renamed
|
||||
/// </summary>
|
||||
public Symbol CurrentSymbol { get; private set; }
|
||||
|
||||
private readonly RandomValueGenerator _randomValueGenerator;
|
||||
private readonly Random _random;
|
||||
private readonly RandomDataGeneratorSettings _settings;
|
||||
private readonly DateTime _delistDate;
|
||||
private readonly bool _willBeDelisted;
|
||||
private readonly BaseSymbolGenerator _symbolGenerator;
|
||||
|
||||
public DividendSplitMapGenerator(
|
||||
Symbol symbol,
|
||||
RandomDataGeneratorSettings settings,
|
||||
RandomValueGenerator randomValueGenerator,
|
||||
BaseSymbolGenerator symbolGenerator,
|
||||
Random random,
|
||||
DateTime delistDate,
|
||||
bool willBeDelisted)
|
||||
{
|
||||
CurrentSymbol = symbol;
|
||||
_settings = settings;
|
||||
_randomValueGenerator = randomValueGenerator;
|
||||
_random = random;
|
||||
_delistDate = delistDate;
|
||||
_willBeDelisted = willBeDelisted;
|
||||
_symbolGenerator = symbolGenerator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the splits, dividends, and maps.
|
||||
/// Writes necessary output to public variables
|
||||
/// </summary>
|
||||
/// <param name="tickHistory"></param>
|
||||
public void GenerateSplitsDividends(IEnumerable<Tick> tickHistory)
|
||||
{
|
||||
var previousMonth = -1;
|
||||
var monthsTrading = 0;
|
||||
|
||||
var hasRename = _randomValueGenerator.NextBool(_settings.HasRenamePercentage);
|
||||
var hasSplits = _randomValueGenerator.NextBool(_settings.HasSplitsPercentage);
|
||||
var hasDividends = _randomValueGenerator.NextBool(_settings.HasDividendsPercentage);
|
||||
var dividendEveryQuarter = _randomValueGenerator.NextBool(_settings.DividendEveryQuarterPercentage);
|
||||
|
||||
var previousX = _random.NextDouble();
|
||||
|
||||
// Since the largest equity value we can obtain is 1 000 000, if we want this price divided by the FinalSplitFactor
|
||||
// to be upper bounded by 1 000 000 000 we need to make sure the FinalSplitFactor is lower bounded by 0.001. Therefore,
|
||||
// since in the worst of the cases FinalSplitFactor = (previousSplitFactor)^(2m), where m is the number of months
|
||||
// in the time span, we need to lower bound previousSplitFactor by (0.001)^(1/(2m))
|
||||
//
|
||||
// On the other hand, if the upper bound for the previousSplitFactor is 1, then the FinalSplitFactor will be, in the
|
||||
// worst of the cases as small as the minimum equity value we can obtain
|
||||
|
||||
var months = (int)Math.Round(_settings.End.Subtract(_settings.Start).Days / (365.25 / 12));
|
||||
months = months != 0 ? months : 1;
|
||||
var minPreviousSplitFactor = GetLowerBoundForPreviousSplitFactor(months);
|
||||
var maxPreviousSplitFactor = 1;
|
||||
var previousSplitFactor = hasSplits ? GetNextPreviousSplitFactor(_random, minPreviousSplitFactor, maxPreviousSplitFactor) : 1;
|
||||
var previousPriceFactor = hasDividends ? (decimal)Math.Tanh(previousX) : 1;
|
||||
|
||||
var splitDates = new List<DateTime>();
|
||||
var dividendDates = new List<DateTime>();
|
||||
|
||||
var firstTick = true;
|
||||
|
||||
// Iterate through all ticks and generate splits and dividend data
|
||||
if (_settings.SecurityType == SecurityType.Equity)
|
||||
{
|
||||
foreach (var tick in tickHistory)
|
||||
{
|
||||
// On the first trading day write relevant starting data to factor and map files
|
||||
if (firstTick)
|
||||
{
|
||||
DividendsSplits.Add(new CorporateFactorRow(tick.Time,
|
||||
previousPriceFactor,
|
||||
previousSplitFactor,
|
||||
tick.Value));
|
||||
|
||||
MapRows.Add(new MapFileRow(tick.Time, CurrentSymbol.Value));
|
||||
}
|
||||
|
||||
// Add the split to the DividendsSplits list if we have a pending
|
||||
// split. That way, we can use the correct referencePrice in the split event.
|
||||
if (splitDates.Count != 0)
|
||||
{
|
||||
var deleteDates = new List<DateTime>();
|
||||
|
||||
foreach (var splitDate in splitDates)
|
||||
{
|
||||
if (tick.Time > splitDate)
|
||||
{
|
||||
DividendsSplits.Add(new CorporateFactorRow(
|
||||
splitDate,
|
||||
previousPriceFactor,
|
||||
previousSplitFactor,
|
||||
tick.Value / FinalSplitFactor));
|
||||
|
||||
FinalSplitFactor *= previousSplitFactor;
|
||||
deleteDates.Add(splitDate);
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes dates we've already looped over
|
||||
splitDates.RemoveAll(x => deleteDates.Contains(x));
|
||||
}
|
||||
|
||||
if (dividendDates.Count != 0)
|
||||
{
|
||||
var deleteDates = new List<DateTime>();
|
||||
|
||||
foreach (var dividendDate in dividendDates)
|
||||
{
|
||||
if (tick.Time > dividendDate)
|
||||
{
|
||||
DividendsSplits.Add(new CorporateFactorRow(
|
||||
dividendDate,
|
||||
previousPriceFactor,
|
||||
previousSplitFactor,
|
||||
tick.Value / FinalSplitFactor));
|
||||
|
||||
deleteDates.Add(dividendDate);
|
||||
}
|
||||
}
|
||||
|
||||
dividendDates.RemoveAll(x => deleteDates.Contains(x));
|
||||
}
|
||||
|
||||
if (tick.Time.Month != previousMonth)
|
||||
{
|
||||
// Every quarter, try to generate dividend events
|
||||
if (hasDividends && (tick.Time.Month - 1) % 3 == 0)
|
||||
{
|
||||
// Make it so there's a 10% chance that dividends occur if there is no dividend every quarter
|
||||
if (dividendEveryQuarter || _randomValueGenerator.NextBool(10.0))
|
||||
{
|
||||
do
|
||||
{
|
||||
previousX += _random.NextDouble() / 10;
|
||||
previousPriceFactor = (decimal)Math.Tanh(previousX);
|
||||
} while (previousPriceFactor >= 1.0m || previousPriceFactor <= 0m);
|
||||
|
||||
dividendDates.Add(_randomValueGenerator.NextDate(tick.Time, tick.Time.AddMonths(1), (DayOfWeek)_random.Next(1, 5)));
|
||||
}
|
||||
}
|
||||
// Have a 5% chance of a split every month
|
||||
if (hasSplits && _randomValueGenerator.NextBool(_settings.MonthSplitPercentage))
|
||||
{
|
||||
// Produce another split factor that is also bounded by the min and max split factors allowed
|
||||
if (_randomValueGenerator.NextBool(5.0)) // Add the possibility of a reverse split
|
||||
{
|
||||
// A reverse split is a split that is smaller than the current previousSplitFactor
|
||||
// Update previousSplitFactor with a smaller value that is still bounded below by minPreviousSplitFactor
|
||||
previousSplitFactor = GetNextPreviousSplitFactor(_random, minPreviousSplitFactor, previousSplitFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update previousSplitFactor with a higher value that is still bounded by maxPreviousSplitFactor
|
||||
// Usually, the split factor tends to grow across the time span(See /Data/Equity/usa/factor_files/aapl for instance)
|
||||
previousSplitFactor = GetNextPreviousSplitFactor(_random, previousSplitFactor, maxPreviousSplitFactor);
|
||||
}
|
||||
|
||||
splitDates.Add(_randomValueGenerator.NextDate(tick.Time, tick.Time.AddMonths(1), (DayOfWeek)_random.Next(1, 5)));
|
||||
}
|
||||
// 10% chance of being renamed every month
|
||||
if (hasRename && _randomValueGenerator.NextBool(10.0))
|
||||
{
|
||||
var randomDate = _randomValueGenerator.NextDate(tick.Time, tick.Time.AddMonths(1), (DayOfWeek)_random.Next(1, 5));
|
||||
MapRows.Add(new MapFileRow(randomDate, CurrentSymbol.Value));
|
||||
|
||||
CurrentSymbol = _symbolGenerator.NextSymbol(_settings.SecurityType, _settings.Market);
|
||||
}
|
||||
|
||||
previousMonth = tick.Time.Month;
|
||||
monthsTrading++;
|
||||
}
|
||||
|
||||
if (monthsTrading >= 6 && _willBeDelisted && tick.Time > _delistDate)
|
||||
{
|
||||
MapRows.Add(new MapFileRow(tick.Time, CurrentSymbol.Value));
|
||||
break;
|
||||
}
|
||||
|
||||
firstTick = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a lower bound that guarantees the FinalSplitFactor, in all the possible
|
||||
/// cases, will never be smaller than the _minimumFinalSplitFactorAllowed (0.001)
|
||||
/// </summary>
|
||||
/// <param name="months">The lower bound for the previous split factor is based on
|
||||
/// the number of months between the start and end date from ticksHistory <see cref="GenerateSplitsDividends(IEnumerable{Tick})"></param>
|
||||
/// <returns>A valid lower bound that guarantees the FinalSplitFactor is always higher
|
||||
/// than the _minimumFinalSplitFactorAllowed</returns>
|
||||
public static decimal GetLowerBoundForPreviousSplitFactor(int months)
|
||||
{
|
||||
return (decimal)(Math.Pow(_minimumFinalSplitFactorAllowed, 1 / (double)(2 * months)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new valid previousSplitFactor that is still bounded by the given upper and lower
|
||||
/// bounds
|
||||
/// </summary>
|
||||
/// <param name="random">Random number generator</param>
|
||||
/// <param name="lowerBound">Minimum allowed value to obtain</param>
|
||||
/// <param name="upperBound">Maximum allowed value to obtain</param>
|
||||
/// <returns>A new valid previousSplitFactor that is still bounded by the given upper and lower
|
||||
/// bounds</returns>
|
||||
public static decimal GetNextPreviousSplitFactor(Random random, decimal lowerBound, decimal upperBound)
|
||||
{
|
||||
return ((decimal)random.NextDouble()) * (upperBound - lowerBound) + lowerBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.Securities.Future;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a new random future <see cref="Symbol"/>. The generates future contract Symbol will have an
|
||||
/// expiry between the specified time range.
|
||||
/// </summary>
|
||||
public class FutureSymbolGenerator : BaseSymbolGenerator
|
||||
{
|
||||
private readonly DateTime _minExpiry;
|
||||
private readonly DateTime _maxExpiry;
|
||||
private readonly string _market;
|
||||
|
||||
public FutureSymbolGenerator(RandomDataGeneratorSettings settings, IRandomValueGenerator random)
|
||||
: base(settings, random)
|
||||
{
|
||||
_minExpiry = settings.Start;
|
||||
_maxExpiry = settings.End;
|
||||
_market = settings.Market;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new random future <see cref="Symbol"/>. The generates future contract Symbol will have an
|
||||
/// expiry between the specified minExpiry and maxExpiry.
|
||||
/// </summary>
|
||||
/// <param name="ticker">Optionally can provide a ticker that should be used</param>
|
||||
/// <returns>A new future contract Symbol with the specified expiration parameters</returns>
|
||||
protected override IEnumerable<Symbol> GenerateAsset(string ticker = null)
|
||||
{
|
||||
if (ticker == null)
|
||||
{
|
||||
// get a valid ticker from the Symbol properties database
|
||||
ticker = NextTickerFromSymbolPropertiesDatabase(SecurityType.Future, _market);
|
||||
}
|
||||
|
||||
var marketHours = MarketHoursDatabase.GetExchangeHours(_market, ticker, SecurityType.Future);
|
||||
var expiry = GetRandomExpiration(marketHours, _minExpiry, _maxExpiry);
|
||||
|
||||
// Try to get the specific expiry function for this future, if available
|
||||
var symbol = Symbol.CreateFuture(ticker, _market, SecurityIdentifier.DefaultDate);
|
||||
if (!FuturesExpiryFunctions.FuturesExpiryDictionary.TryGetValue(symbol, out var expiryFunction))
|
||||
{
|
||||
// If no expiry function is found, return the future using the previously chosen expiry
|
||||
yield return Symbol.CreateFuture(ticker, _market, expiry);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Get all valid expiries in range using the expiry function
|
||||
// HashSet ensures unique expiry dates since multiple reference dates may map to same expiry
|
||||
var validExpiries = new HashSet<DateTime>();
|
||||
|
||||
// Extends range by ±1 month to catch all potential expiries.
|
||||
// Some futures (like NG) calculate expiry based on next month's date
|
||||
// (e.g., "3 business days before 1st of next month"), so we need to look ahead.
|
||||
// This buffer ensures we don't miss expiries near range boundaries.
|
||||
for (var date = _minExpiry.AddMonths(-1); date <= _maxExpiry.AddMonths(1); date = date.AddDays(1))
|
||||
{
|
||||
// Calculate expiry date using the futures-specific function
|
||||
var newExpiry = expiryFunction(date);
|
||||
|
||||
// Only include expiries within our target range
|
||||
if (_minExpiry < newExpiry && newExpiry <= _maxExpiry)
|
||||
{
|
||||
// Add to set of valid expiries (automatically handles duplicates)
|
||||
validExpiries.Add(newExpiry);
|
||||
}
|
||||
}
|
||||
|
||||
if (validExpiries.Count == 0)
|
||||
{
|
||||
yield return Symbol.CreateFuture(ticker, _market, expiry);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Randomly select one expiry from the valid set
|
||||
var skip = Random.NextInt(validExpiries.Count);
|
||||
expiry = validExpiries.Skip(skip).First();
|
||||
|
||||
// Return the future contract using the randomly selected valid expiry
|
||||
yield return Symbol.CreateFuture(ticker, _market, expiry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is no limit for the future symbols.
|
||||
/// </summary>
|
||||
/// <returns>Returns int.MaxValue</returns>
|
||||
public override int GetAvailableSymbolCount() => int.MaxValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a type capable of producing random prices
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any parameters referenced as a percentage value are always in 'percent space', meaning 1 is 1%.
|
||||
/// </remarks>
|
||||
public interface IPriceGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates an asset price
|
||||
/// </summary>
|
||||
/// <param name="maximumPercentDeviation">The maximum percent deviation. This value is in percent space,
|
||||
/// so a value of 1m is equal to 1%.</param>
|
||||
/// <param name="referenceDate">date used in price calculation</param>
|
||||
/// <returns>Returns a new decimal as price</returns>
|
||||
public decimal NextValue(decimal maximumPercentDeviation, DateTime referenceDate);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates Price generator warmed up and ready to generate new values
|
||||
/// </summary>
|
||||
public bool WarmedUp { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a type capable of producing random values for use in random data generation
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any parameters referenced as a percentage value are always in 'percent space', meaning 1 is 1%.
|
||||
/// </remarks>
|
||||
public interface IRandomValueGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Randomly return a <see cref="bool"/> value with the specified odds of being true
|
||||
/// </summary>
|
||||
/// <param name="percentOddsForTrue">The percent odds of being true in percent space, so 10 => 10%</param>
|
||||
/// <returns>True or false</returns>
|
||||
bool NextBool(double percentOddsForTrue);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0
|
||||
/// </summary>
|
||||
/// <returns>A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.</returns>
|
||||
double NextDouble();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="minValue">the inclusive lower bound of the random number returned</param>
|
||||
/// <param name="maxValue">the exclusive upper bound of the random number returned</param>
|
||||
/// <returns>A 32-bit signed integer greater than or equal to minValue and less than maxValue.</returns>
|
||||
int NextInt(int minValue, int maxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative random integer that is less than the specified maximum.
|
||||
/// </summary>
|
||||
/// <param name="maxValue">the exclusive upper bound of the random number to be generated.</param>
|
||||
/// <returns>A 32-bit signed integer that is greater than or equal to 0, and less than maxValue.</returns>
|
||||
int NextInt(int maxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="DateTime"/> between the specified <paramref name="minDateTime"/> and
|
||||
/// <paramref name="maxDateTime"/>. <paramref name="dayOfWeek"/> is optionally specified to force the
|
||||
/// result to a particular day of the week
|
||||
/// </summary>
|
||||
/// <param name="minDateTime">The minimum date time, inclusive</param>
|
||||
/// <param name="maxDateTime">The maximum date time, inclusive</param>
|
||||
/// <param name="dayOfWeek">Optional. The day of week to force</param>
|
||||
/// <returns>A new <see cref="DateTime"/> within the specified range and optionally of the specified day of week</returns>
|
||||
DateTime NextDate(DateTime minDateTime, DateTime maxDateTime, DayOfWeek? dayOfWeek);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="decimal"/> suitable as a price. This should observe minimum price
|
||||
/// variations if available in <see cref="SymbolPropertiesDatabase"/>, and if not, truncating to 2
|
||||
/// decimal places.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">Throw when the <paramref name="referencePrice"/> or <paramref name="maximumPercentDeviation"/>
|
||||
/// is less than or equal to zero.</exception>
|
||||
/// <param name="securityType">The security type the price is being generated for</param>
|
||||
/// <param name="market">The market of the security the price is being generated for</param>
|
||||
/// <param name="referencePrice">The reference price used as the mean of random price generation</param>
|
||||
/// <param name="maximumPercentDeviation">The maximum percent deviation. This value is in percent space,
|
||||
/// so a value of 1m is equal to 1%.</param>
|
||||
/// <returns>A new decimal suitable for usage as price within the specified deviation from the reference price</returns>
|
||||
decimal NextPrice(SecurityType securityType, string market, decimal referencePrice, decimal maximumPercentDeviation);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 QuantConnect.Data.Market;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes main methods for <see cref="TickGenerator"/>
|
||||
/// </summary>
|
||||
public interface ITickGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates and enumerates data points for current symbol
|
||||
/// </summary>
|
||||
IEnumerable<Tick> GenerateTicks();
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="Tick"/> that is at most the specified <paramref name="maximumPercentDeviation"/> away from the
|
||||
/// previous price and is of the requested <paramref name="tickType"/>
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The time of the generated tick</param>
|
||||
/// <param name="tickType">The type of <see cref="Tick"/> to be generated</param>
|
||||
/// <param name="maximumPercentDeviation">The maximum percentage to deviate from the
|
||||
/// previous price, for example, 1 would indicate a maximum of 1% deviation from the
|
||||
/// previous price. For a previous price of 100, this would yield a price between 99 and 101 inclusive</param>
|
||||
/// <returns>A random <see cref="Tick"/> value that is within the specified <paramref name="maximumPercentDeviation"/>
|
||||
/// from the previous price</returns>
|
||||
Tick NextTick(
|
||||
DateTime dateTime,
|
||||
TickType tickType,
|
||||
decimal maximumPercentDeviation
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="DateTime"/> suitable for use as a tick's emit time.
|
||||
/// If the density provided is <see cref="DataDensity.Dense"/>, then at least one tick will be generated per <paramref name="resolution"/> step.
|
||||
/// If the density provided is <see cref="DataDensity.Sparse"/>, then at least one tick will be generated every 5 <paramref name="resolution"/> steps.
|
||||
/// if the density provided is <see cref="DataDensity.VerySparse"/>, then at least one tick will be generated every 50 <paramref name="resolution"/> steps.
|
||||
/// Times returned are guaranteed to be within market hours for the specified Symbol
|
||||
/// </summary>
|
||||
/// <param name="previous">The previous tick time</param>
|
||||
/// <param name="resolution">The requested resolution of data</param>
|
||||
/// <param name="density">The requested data density</param>
|
||||
/// <returns>A new <see cref="DateTime"/> that is after <paramref name="previous"/> according to the specified <paramref name="resolution"/>
|
||||
/// and <paramref name="density"/> specified</returns>
|
||||
DateTime NextTickTime(DateTime previous, Resolution resolution, DataDensity density);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when there are no tickers left to generate for a certain combination of security type and market.
|
||||
/// </summary>
|
||||
public class NoTickersAvailableException : RandomValueGeneratorException
|
||||
{
|
||||
public NoTickersAvailableException(SecurityType securityType, string market)
|
||||
: base($"Failed to generate {securityType} symbol for {market}, there are no tickers left")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Securities.Option;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Pricing model used to determine the fair price or theoretical value for a call or a put option price
|
||||
/// by default using the Black-Scholes-Merton model
|
||||
/// </summary>
|
||||
public class OptionPriceModelPriceGenerator : IPriceGenerator
|
||||
{
|
||||
private readonly Option _option;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="RandomPriceGenerator"/> is always ready to generate new price values as it does not depend on volatility model
|
||||
/// </summary>
|
||||
public bool WarmedUp => _option.PriceModel is QLOptionPriceModel optionPriceModel && optionPriceModel.VolatilityEstimatorWarmedUp || _option.PriceModel is not QLOptionPriceModel;
|
||||
|
||||
/// <summary>
|
||||
/// Creates instance of <see cref="OptionPriceModelPriceGenerator"/>
|
||||
/// </summary>
|
||||
///<param name="security"><see cref="Security"/> object for which to generate price data</param>
|
||||
public OptionPriceModelPriceGenerator(Security security)
|
||||
{
|
||||
if (security == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(security), "security cannot be null");
|
||||
}
|
||||
|
||||
if (!security.Symbol.SecurityType.IsOption())
|
||||
{
|
||||
throw new ArgumentException($"{nameof(OptionPriceModelPriceGenerator)} model cannot be applied to non-option security.");
|
||||
}
|
||||
|
||||
_option = security as Option;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For Black-Scholes-Merton model price calculation relies <see cref="IOptionPriceModel"/> of the security
|
||||
/// </summary>
|
||||
/// <param name="maximumPercentDeviation">The maximum percent deviation. This value is in percent space,
|
||||
/// so a value of 1m is equal to 1%.</param>
|
||||
/// <param name="referenceDate">current reference date</param>
|
||||
/// <returns>A new decimal suitable for usage as new security price</returns>
|
||||
public decimal NextValue(decimal maximumPercentDeviation, DateTime referenceDate)
|
||||
{
|
||||
var underlying = _option.Underlying;
|
||||
var price = underlying.Price;
|
||||
|
||||
var tick = new Tick(referenceDate, underlying.Symbol, price, price);
|
||||
var contract = OptionContract.Create(referenceDate, _option, tick);
|
||||
|
||||
var parameters = new OptionPriceModelParameters(_option, null, contract);
|
||||
return _option.PriceModel.Evaluate(parameters).TheoreticalPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a new random option <see cref="Symbol"/>.
|
||||
/// </summary>
|
||||
public class OptionSymbolGenerator : BaseSymbolGenerator
|
||||
{
|
||||
private readonly DateTime _minExpiry;
|
||||
private readonly DateTime _maxExpiry;
|
||||
private readonly string _market;
|
||||
private readonly int _symbolChainSize;
|
||||
private readonly decimal _underlyingPrice;
|
||||
private readonly decimal _maximumStrikePriceDeviation;
|
||||
private readonly SecurityType _underlyingSecurityType = SecurityType.Equity;
|
||||
|
||||
public OptionSymbolGenerator(RandomDataGeneratorSettings settings, IRandomValueGenerator random, decimal underlyingPrice, decimal maximumStrikePriceDeviation)
|
||||
: base(settings, random)
|
||||
{
|
||||
// We add seven days more because TickGenerator for options needs first three underlying data points to warm up
|
||||
// the price generator, so if the expiry date is before settings.Start plus three days no quote or trade data is
|
||||
// generated for this option
|
||||
_minExpiry = (settings.Start).AddDays(7);
|
||||
_maxExpiry = (settings.End).AddDays(7);
|
||||
_market = settings.Market;
|
||||
_underlyingPrice = underlyingPrice;
|
||||
_symbolChainSize = settings.ChainSymbolCount;
|
||||
_maximumStrikePriceDeviation = maximumStrikePriceDeviation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new random option <see cref="Symbol"/>. The generated option contract Symbol will have an
|
||||
/// expiry between the specified min and max expiration. The strike
|
||||
/// price will be within the specified maximum strike price deviation of the underlying symbol price
|
||||
/// and should be rounded to reasonable value for the given price. For example, a price of 100 dollars would round
|
||||
/// to 5 dollar increments and a price of 5 dollars would round to 50 cent increments
|
||||
/// </summary>
|
||||
/// <param name="ticker">Optionally can provide a ticker that should be used</param>
|
||||
/// <remarks>
|
||||
/// Standard contracts expiry on the third Friday.
|
||||
/// Weekly contracts expiry every week on Friday
|
||||
/// </remarks>
|
||||
/// <returns>A new option contract Symbol within the specified expiration and strike price parameters along with its underlying symbol</returns>
|
||||
protected override IEnumerable<Symbol> GenerateAsset(string ticker = null)
|
||||
{
|
||||
// first generate the underlying
|
||||
var underlying = NextSymbol(_underlyingSecurityType, _market, ticker);
|
||||
yield return underlying;
|
||||
|
||||
var marketHours = MarketHoursDatabase.GetExchangeHours(_market, underlying, _underlyingSecurityType);
|
||||
var expiry = GetRandomExpiration(marketHours, _minExpiry, _maxExpiry);
|
||||
|
||||
var strikes = new HashSet<decimal>();
|
||||
for (var i = 0; i < _symbolChainSize; i++)
|
||||
{
|
||||
decimal strike;
|
||||
do
|
||||
{
|
||||
// generate a random strike while respecting the maximum deviation from the underlying's price
|
||||
// since these are underlying prices, use Equity as the security type
|
||||
strike = Random.NextPrice(_underlyingSecurityType, _market, _underlyingPrice,
|
||||
_maximumStrikePriceDeviation);
|
||||
|
||||
// round the strike price to something reasonable
|
||||
var order = 1 + Math.Log10((double)strike);
|
||||
strike = strike.RoundToSignificantDigits((int)order);
|
||||
}
|
||||
// don't allow duplicate strikes
|
||||
while (!strikes.Add(strike));
|
||||
|
||||
foreach (var optionRight in new [] { OptionRight.Put, OptionRight.Call })
|
||||
{
|
||||
// when providing a null option w/ an expiry, it will automatically create the OSI ticker string for the Value
|
||||
yield return Symbol.CreateOption(underlying, _market, underlying.SecurityType.DefaultOptionStyle(), optionRight, strike, expiry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of symbols with the specified parameters can be generated.
|
||||
/// There is no limit for the options.
|
||||
/// </summary>
|
||||
/// <returns>returns int.MaxValue</returns>
|
||||
public override int GetAvailableSymbolCount() => int.MaxValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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.Data;
|
||||
using QuantConnect.Data.Auxiliary;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
|
||||
using QuantConnect.Logging;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates random data according to the specified parameters
|
||||
/// </summary>
|
||||
public class RandomDataGenerator
|
||||
{
|
||||
private RandomDataGeneratorSettings _settings;
|
||||
private SecurityManager _securityManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes <see cref="RandomDataGenerator"/> instance fields
|
||||
/// </summary>
|
||||
/// <param name="settings">random data generation settings</param>
|
||||
/// <param name="securityManager">security management</param>
|
||||
public void Init(RandomDataGeneratorSettings settings, SecurityManager securityManager)
|
||||
{
|
||||
_settings = settings;
|
||||
_securityManager = securityManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts data generation
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
var tickTypesPerSecurityType = SubscriptionManager.DefaultDataTypes();
|
||||
// can specify a seed value in this ctor if determinism is desired
|
||||
var random = new Random();
|
||||
var randomValueGenerator = new RandomValueGenerator();
|
||||
if (_settings.RandomSeedSet)
|
||||
{
|
||||
random = new Random(_settings.RandomSeed);
|
||||
randomValueGenerator = new RandomValueGenerator(_settings.RandomSeed);
|
||||
}
|
||||
|
||||
var symbolGenerator = BaseSymbolGenerator.Create(_settings, randomValueGenerator);
|
||||
|
||||
var maxSymbolCount = symbolGenerator.GetAvailableSymbolCount();
|
||||
if (_settings.SymbolCount > maxSymbolCount)
|
||||
{
|
||||
Log.Error($"RandomDataGenerator.Run(): Limiting Symbol count to {maxSymbolCount}, we don't have more {_settings.SecurityType} tickers for {_settings.Market}");
|
||||
_settings.SymbolCount = maxSymbolCount;
|
||||
}
|
||||
|
||||
Log.Trace($"RandomDataGenerator.Run(): Begin data generation of {_settings.SymbolCount} randomly generated {_settings.SecurityType} assets...");
|
||||
|
||||
// iterate over our randomly generated symbols
|
||||
var count = 0;
|
||||
var progress = 0d;
|
||||
var previousMonth = -1;
|
||||
|
||||
foreach (var (symbolRef, currentSymbolGroup) in symbolGenerator.GenerateRandomSymbols()
|
||||
.GroupBy(s => s.HasUnderlying ? s.Underlying : s)
|
||||
.Select(g => (g.Key, g.OrderBy(s => s.HasUnderlying).ToList())))
|
||||
{
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{++count}]: {symbolRef} Progress: {progress:0.0}% - Generating data...");
|
||||
|
||||
var tickGenerators = new List<IEnumerator<Tick>>();
|
||||
var tickHistories = new Dictionary<Symbol, List<Tick>>();
|
||||
Security underlyingSecurity = null;
|
||||
foreach (var currentSymbol in currentSymbolGroup)
|
||||
{
|
||||
if (!_securityManager.TryGetValue(currentSymbol, out var security))
|
||||
{
|
||||
security = _securityManager.CreateSecurity(
|
||||
currentSymbol,
|
||||
new List<SubscriptionDataConfig>(),
|
||||
underlying: underlyingSecurity);
|
||||
_securityManager.Add(security);
|
||||
}
|
||||
|
||||
underlyingSecurity ??= security;
|
||||
|
||||
tickGenerators.Add(
|
||||
new TickGenerator(_settings, tickTypesPerSecurityType[currentSymbol.SecurityType].ToArray(), security, randomValueGenerator)
|
||||
.GenerateTicks()
|
||||
.GetEnumerator());
|
||||
|
||||
tickHistories.Add(
|
||||
currentSymbol,
|
||||
new List<Tick>());
|
||||
}
|
||||
|
||||
using var sync = new SynchronizingBaseDataEnumerator(tickGenerators);
|
||||
|
||||
var lastLoggedProgress = 0.0;
|
||||
Log.Trace("[0%] Initializing tick data generation");
|
||||
while (sync.MoveNext())
|
||||
{
|
||||
var dataPoint = sync.Current;
|
||||
if (!_securityManager.TryGetValue(dataPoint.Symbol, out var security))
|
||||
{
|
||||
Log.Error($"RandomDataGenerator.Run(): Could not find security for symbol {sync.Current.Symbol}");
|
||||
continue;
|
||||
}
|
||||
|
||||
tickHistories[security.Symbol].Add(dataPoint as Tick);
|
||||
security.Update(new List<BaseData> { dataPoint }, dataPoint.GetType(), false);
|
||||
|
||||
// Calculate and log progress percentage when it increases by more than 3%
|
||||
var currentProgress = RandomDataGeneratorHelper.GetProgressAsPercentage(_settings.Start, _settings.End, dataPoint.EndTime);
|
||||
if (currentProgress - lastLoggedProgress >= 3.0)
|
||||
{
|
||||
Log.Trace($"[{currentProgress:0.00}%] Generating tick data");
|
||||
lastLoggedProgress = currentProgress;
|
||||
}
|
||||
}
|
||||
Log.Trace("[100%] Tick data generation completed successfully.");
|
||||
foreach (var (currentSymbol, tickHistory) in tickHistories)
|
||||
{
|
||||
var symbol = currentSymbol;
|
||||
|
||||
// This is done so that we can update the Symbol in the case of a rename event
|
||||
var delistDate = GetDelistingDate(_settings.Start, _settings.End, randomValueGenerator);
|
||||
var willBeDelisted = randomValueGenerator.NextBool(1.0);
|
||||
|
||||
// Companies rarely IPO then disappear within 6 months
|
||||
if (willBeDelisted && tickHistory.Select(tick => tick.Time.Month).Distinct().Count() <= 6)
|
||||
{
|
||||
willBeDelisted = false;
|
||||
}
|
||||
|
||||
var dividendsSplitsMaps = new DividendSplitMapGenerator(
|
||||
symbol,
|
||||
_settings,
|
||||
randomValueGenerator,
|
||||
symbolGenerator,
|
||||
random,
|
||||
delistDate,
|
||||
willBeDelisted);
|
||||
|
||||
// Keep track of renamed symbols and the time they were renamed.
|
||||
var renamedSymbols = new Dictionary<Symbol, DateTime>();
|
||||
|
||||
if (_settings.SecurityType == SecurityType.Equity)
|
||||
{
|
||||
dividendsSplitsMaps.GenerateSplitsDividends(tickHistory);
|
||||
|
||||
if (!willBeDelisted)
|
||||
{
|
||||
dividendsSplitsMaps.DividendsSplits.Add(new CorporateFactorRow(new DateTime(2050, 12, 31), 1m, 1m));
|
||||
|
||||
if (dividendsSplitsMaps.MapRows.Count > 1)
|
||||
{
|
||||
// Remove the last element if we're going to have a 20501231 entry
|
||||
dividendsSplitsMaps.MapRows.RemoveAt(dividendsSplitsMaps.MapRows.Count - 1);
|
||||
}
|
||||
dividendsSplitsMaps.MapRows.Add(new MapFileRow(new DateTime(2050, 12, 31), dividendsSplitsMaps.CurrentSymbol.Value));
|
||||
}
|
||||
|
||||
// If the Symbol value has changed, update the current Symbol
|
||||
if (symbol != dividendsSplitsMaps.CurrentSymbol)
|
||||
{
|
||||
// Add all Symbol rename events to dictionary
|
||||
// We skip the first row as it contains the listing event instead of a rename event
|
||||
foreach (var renameEvent in dividendsSplitsMaps.MapRows.Skip(1))
|
||||
{
|
||||
// Symbol.UpdateMappedSymbol does not update the underlying security ID Symbol, which
|
||||
// is used to create the hash code. Create a new equity Symbol from scratch instead.
|
||||
symbol = Symbol.Create(renameEvent.MappedSymbol, SecurityType.Equity, _settings.Market);
|
||||
renamedSymbols.Add(symbol, renameEvent.Date);
|
||||
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {symbol} will be renamed on {renameEvent.Date}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This ensures that ticks will be written for the current Symbol up until 9999-12-31
|
||||
renamedSymbols.Add(symbol, new DateTime(9999, 12, 31));
|
||||
}
|
||||
|
||||
symbol = dividendsSplitsMaps.CurrentSymbol;
|
||||
|
||||
// Write Splits and Dividend events to directory factor_files
|
||||
var factorFile = new CorporateFactorProvider(symbol.Value, dividendsSplitsMaps.DividendsSplits, _settings.Start);
|
||||
var mapFile = new MapFile(symbol.Value, dividendsSplitsMaps.MapRows);
|
||||
|
||||
factorFile.WriteToFile(symbol);
|
||||
mapFile.WriteToCsv(_settings.Market, symbol.SecurityType);
|
||||
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {symbol} Dividends, splits, and map files have been written to disk.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// This ensures that ticks will be written for the current Symbol up until 9999-12-31
|
||||
renamedSymbols.Add(symbol, new DateTime(9999, 12, 31));
|
||||
}
|
||||
|
||||
// define aggregators via settings
|
||||
var aggregators = CreateAggregators(_settings, tickTypesPerSecurityType[currentSymbol.SecurityType].ToArray()).ToList();
|
||||
Symbol previousSymbol = null;
|
||||
var currentCount = 0;
|
||||
var monthsTrading = 0;
|
||||
|
||||
foreach (var renamed in renamedSymbols)
|
||||
{
|
||||
var previousRenameDate = previousSymbol == null ? new DateTime(1, 1, 1) : renamedSymbols[previousSymbol];
|
||||
var previousRenameDateDay = new DateTime(previousRenameDate.Year, previousRenameDate.Month, previousRenameDate.Day);
|
||||
var renameDate = renamed.Value;
|
||||
var renameDateDay = new DateTime(renameDate.Year, renameDate.Month, renameDate.Day);
|
||||
|
||||
foreach (var tick in tickHistory.Where(tick => tick.Time >= previousRenameDate && previousRenameDateDay != TickDay(tick)))
|
||||
{
|
||||
// Prevents the aggregator from being updated with ticks after the rename event
|
||||
if (TickDay(tick) > renameDateDay)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (tick.Time.Month != previousMonth)
|
||||
{
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: Month: {tick.Time:MMMM}");
|
||||
previousMonth = tick.Time.Month;
|
||||
monthsTrading++;
|
||||
}
|
||||
|
||||
foreach (var item in aggregators)
|
||||
{
|
||||
tick.Value = tick.Value / dividendsSplitsMaps.FinalSplitFactor;
|
||||
item.Consolidator.Update(tick);
|
||||
}
|
||||
|
||||
if (monthsTrading >= 6 && willBeDelisted && tick.Time > delistDate)
|
||||
{
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {renamed.Key} delisted at {tick.Time:MMMM yyyy}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// count each stage as a point, so total points is 2*Symbol-count
|
||||
// and the current progress is twice the current, but less one because we haven't finished writing data yet
|
||||
progress = 100 * (2 * count - 1) / (2.0 * _settings.SymbolCount);
|
||||
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {renamed.Key} Progress: {progress:0.0}% - Saving data in LEAN format");
|
||||
|
||||
// persist consolidated data to disk
|
||||
foreach (var item in aggregators)
|
||||
{
|
||||
var writer = new LeanDataWriter(item.Resolution, renamed.Key, Globals.DataFolder, item.TickType);
|
||||
|
||||
// send the flushed data into the writer. pulling the flushed list is very important,
|
||||
// lest we likely wouldn't get the last piece of data stuck in the consolidator
|
||||
// Filter out the data we're going to write here because filtering them in the consolidator update phase
|
||||
// makes it write all dates for some unknown reason
|
||||
writer.Write(item.Flush().Where(data => data.Time > previousRenameDate && previousRenameDateDay != DataDay(data)));
|
||||
}
|
||||
|
||||
// update progress
|
||||
progress = 100 * (2 * count) / (2.0 * _settings.SymbolCount);
|
||||
Log.Trace($"RandomDataGenerator.Run(): Symbol[{count}]: {symbol} Progress: {progress:0.0}% - Symbol data generation and output completed");
|
||||
|
||||
previousSymbol = renamed.Key;
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Trace("RandomDataGenerator.Run(): Random data generation has completed.");
|
||||
|
||||
DateTime TickDay(Tick tick) => new(tick.Time.Year, tick.Time.Month, tick.Time.Day);
|
||||
DateTime DataDay(BaseData data) => new(data.Time.Year, data.Time.Month, data.Time.Day);
|
||||
}
|
||||
|
||||
public static DateTime GetDateMidpoint(DateTime start, DateTime end)
|
||||
{
|
||||
TimeSpan span = end.Subtract(start);
|
||||
int span_time = (int)span.TotalMinutes;
|
||||
double diff_span = -(span_time / 2.0);
|
||||
DateTime start_time = end.AddMinutes(Math.Round(diff_span, 2, MidpointRounding.ToEven));
|
||||
|
||||
//Returns a DateTime object that is halfway between start and end
|
||||
return start_time;
|
||||
}
|
||||
|
||||
public static DateTime GetDelistingDate(DateTime start, DateTime end, RandomValueGenerator randomValueGenerator)
|
||||
{
|
||||
var mid_point = GetDateMidpoint(start, end);
|
||||
var delist_Date = randomValueGenerator.NextDate(mid_point, end, null);
|
||||
|
||||
//Returns a DateTime object that is a random value between the mid_point and end
|
||||
return delist_Date;
|
||||
}
|
||||
|
||||
public static IEnumerable<TickAggregator> CreateAggregators(RandomDataGeneratorSettings settings, TickType[] tickTypes)
|
||||
{
|
||||
// create default aggregators for tick type/resolution
|
||||
foreach (var tickAggregator in TickAggregator.ForTickTypes(settings.SecurityType, settings.Resolution, tickTypes))
|
||||
{
|
||||
yield return tickAggregator;
|
||||
}
|
||||
|
||||
|
||||
// ensure we have a daily consolidator when coarse is enabled
|
||||
if (settings.IncludeCoarse && settings.Resolution != Resolution.Daily)
|
||||
{
|
||||
// prefer trades for coarse - in practice equity only does trades, but leaving this as configurable
|
||||
if (tickTypes.Contains(TickType.Trade))
|
||||
{
|
||||
yield return TickAggregator.ForTickTypes(settings.SecurityType, Resolution.Daily, TickType.Trade).Single();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return TickAggregator.ForTickTypes(settings.SecurityType, Resolution.Daily, TickType.Quote).Single();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper methods for the Random Data Generator
|
||||
/// </summary>
|
||||
public static class RandomDataGeneratorHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the progress percentage of the current time between a start and end time.
|
||||
/// </summary>
|
||||
/// <param name="start">The start time of the process.</param>
|
||||
/// <param name="end">The end time of the process.</param>
|
||||
/// <param name="currentTime">The current time to evaluate progress.</param>
|
||||
/// <returns>The progress as a percentage, rounded to two decimal places.</returns>
|
||||
public static double GetProgressAsPercentage(DateTime start, DateTime end, DateTime currentTime)
|
||||
{
|
||||
var totalDuration = end - start;
|
||||
return Math.Round((currentTime - start).TotalMilliseconds * 1.0 / totalDuration.TotalMilliseconds * 100, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.Configuration;
|
||||
using QuantConnect.Data.Auxiliary;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Option;
|
||||
using QuantConnect.ToolBox.CoarseUniverseGenerator;
|
||||
using QuantConnect.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Data;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates and starts <see cref="RandomDataGenerator"/> instance
|
||||
/// </summary>
|
||||
public static class RandomDataGeneratorProgram
|
||||
{
|
||||
private static readonly IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
|
||||
|
||||
public static void RandomDataGenerator(
|
||||
string startDateString,
|
||||
string endDateString,
|
||||
string symbolCountString,
|
||||
string market,
|
||||
string securityTypeString,
|
||||
string resolutionString,
|
||||
string dataDensityString,
|
||||
string includeCoarseString,
|
||||
string quoteTradeRatioString,
|
||||
string randomSeed,
|
||||
string hasIpoPercentageString,
|
||||
string hasRenamePercentageString,
|
||||
string hasSplitsPercentageString,
|
||||
string hasDividendsPercentageString,
|
||||
string dividendEveryQuarterPercentageString,
|
||||
string optionPriceEngineName,
|
||||
string volatilityModelResolutionString,
|
||||
string chainSymbolCountString,
|
||||
List<string> tickers
|
||||
)
|
||||
{
|
||||
var settings = RandomDataGeneratorSettings.FromCommandLineArguments(
|
||||
startDateString,
|
||||
endDateString,
|
||||
symbolCountString,
|
||||
market,
|
||||
securityTypeString,
|
||||
resolutionString,
|
||||
dataDensityString,
|
||||
includeCoarseString,
|
||||
quoteTradeRatioString,
|
||||
randomSeed,
|
||||
hasIpoPercentageString,
|
||||
hasRenamePercentageString,
|
||||
hasSplitsPercentageString,
|
||||
hasDividendsPercentageString,
|
||||
dividendEveryQuarterPercentageString,
|
||||
optionPriceEngineName,
|
||||
volatilityModelResolutionString,
|
||||
chainSymbolCountString,
|
||||
tickers
|
||||
);
|
||||
|
||||
if (settings.Start.Year < 1998)
|
||||
{
|
||||
Log.Error($"RandomDataGeneratorProgram(): Required parameter --start must be at least 19980101");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
var securityManager = new SecurityManager(new TimeKeeper(settings.Start, new[] { TimeZones.Utc }));
|
||||
var securityService = new SecurityService(
|
||||
new CashBook(),
|
||||
MarketHoursDatabase.FromDataFolder(),
|
||||
SymbolPropertiesDatabase.FromDataFolder(),
|
||||
new SecurityInitializerProvider(new FuncSecurityInitializer(security =>
|
||||
{
|
||||
// init price
|
||||
security.SetMarketPrice(new Tick(settings.Start, security.Symbol, 100, 100));
|
||||
security.SetMarketPrice(new OpenInterest(settings.Start, security.Symbol, 10000));
|
||||
|
||||
// from settings
|
||||
security.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(settings.VolatilityModelResolution);
|
||||
|
||||
// from settings
|
||||
if (security is Option option)
|
||||
{
|
||||
option.PriceModel = OptionPriceModels.QuantLib.Create(settings.OptionPriceEngineName,
|
||||
_interestRateProvider.GetRiskFreeRate(settings.Start, settings.End));
|
||||
}
|
||||
})),
|
||||
RegisteredSecurityDataTypesProvider.Null,
|
||||
new SecurityCacheProvider(
|
||||
new SecurityPortfolioManager(securityManager, new SecurityTransactionManager(null, securityManager), new AlgorithmSettings())),
|
||||
new MapFilePrimaryExchangeProvider(Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>(Config.Get("map-file-provider", "LocalDiskMapFileProvider")))
|
||||
);
|
||||
securityManager.SetSecurityService(securityService);
|
||||
|
||||
var generator = new RandomDataGenerator();
|
||||
generator.Init(settings, securityManager);
|
||||
generator.Run();
|
||||
|
||||
if (settings.IncludeCoarse && settings.SecurityType == SecurityType.Equity)
|
||||
{
|
||||
Log.Trace("RandomDataGeneratorProgram(): Launching coarse data generator...");
|
||||
|
||||
CoarseUniverseGeneratorProgram.CoarseUniverseGenerator();
|
||||
}
|
||||
|
||||
if (!Console.IsInputRedirected)
|
||||
{
|
||||
Log.Trace("RandomDataGeneratorProgram(): Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* 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.Brokerages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
public class RandomDataGeneratorSettings
|
||||
{
|
||||
private static int MarketCode = 100;
|
||||
private static readonly string[] DateFormats = { DateFormat.EightCharacter, DateFormat.YearMonth, "yyyy-MM-dd" };
|
||||
|
||||
public DateTime Start { get; init; }
|
||||
public DateTime End { get; init; }
|
||||
public SecurityType SecurityType { get; init; } = SecurityType.Equity;
|
||||
public DataDensity DataDensity { get; init; } = DataDensity.Dense;
|
||||
public Resolution Resolution { get; init; } = Resolution.Minute;
|
||||
public string Market { get; init; }
|
||||
public bool IncludeCoarse { get; init; } = true;
|
||||
public int SymbolCount { get; set; }
|
||||
public double QuoteTradeRatio { get; init; } = 1;
|
||||
public int RandomSeed { get; init; }
|
||||
public bool RandomSeedSet { get; init; }
|
||||
public double HasIpoPercentage { get; init; }
|
||||
public double HasRenamePercentage { get; init; }
|
||||
public double HasSplitsPercentage { get; init; }
|
||||
public double MonthSplitPercentage { get; init; }
|
||||
public double HasDividendsPercentage { get; init; }
|
||||
public double DividendEveryQuarterPercentage { get; init; }
|
||||
public string OptionPriceEngineName { get; init; }
|
||||
public int ChainSymbolCount { get; init; } = 1;
|
||||
public Resolution VolatilityModelResolution { get; init; } = Resolution.Daily;
|
||||
public List<string> Tickers { get; init; }
|
||||
public static RandomDataGeneratorSettings FromCommandLineArguments(
|
||||
string startDateString,
|
||||
string endDateString,
|
||||
string symbolCountString,
|
||||
string market,
|
||||
string securityTypeString,
|
||||
string resolutionString,
|
||||
string dataDensityString,
|
||||
string includeCoarseString,
|
||||
string quoteTradeRatioString,
|
||||
string randomSeedString,
|
||||
string hasIpoPercentageString,
|
||||
string hasRenamePercentageString,
|
||||
string hasSplitsPercentageString,
|
||||
string hasDividendsPercentageString,
|
||||
string dividendEveryQuarterPercentageString,
|
||||
string optionPriceEngineName,
|
||||
string volatilityModelResolutionString,
|
||||
string chainSymbolCountString,
|
||||
List<string> tickers,
|
||||
double monthSplitPercentage = 5.0
|
||||
)
|
||||
{
|
||||
var randomSeedSet = true;
|
||||
|
||||
int randomSeed;
|
||||
int symbolCount;
|
||||
int chainSymbolCount;
|
||||
bool includeCoarse;
|
||||
Resolution resolution;
|
||||
double quoteTradeRatio;
|
||||
DataDensity dataDensity;
|
||||
SecurityType securityType;
|
||||
DateTime startDate, endDate;
|
||||
double hasIpoPercentage;
|
||||
double hasRenamePercentage;
|
||||
double hasSplitsPercentage;
|
||||
double hasDividendsPercentage;
|
||||
double dividendEveryQuarterPercentage;
|
||||
Resolution volatilityModelResolution;
|
||||
|
||||
var failed = false;
|
||||
// --start
|
||||
if (!DateTime.TryParseExact(startDateString, DateFormats, null, DateTimeStyles.None, out startDate))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Required parameter --from-date was incorrectly formatted. Please specify in yyyyMMdd format. Value provided: '{startDateString}'");
|
||||
}
|
||||
|
||||
// --end
|
||||
if (!DateTime.TryParseExact(endDateString, DateFormats, null, DateTimeStyles.None, out endDate))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Required parameter --to-date was incorrectly formatted. Please specify in yyyyMMdd format. Value provided: '{endDateString}'");
|
||||
}
|
||||
|
||||
// --tickers
|
||||
if (!tickers.IsNullOrEmpty())
|
||||
{
|
||||
symbolCount = tickers.Count;
|
||||
Log.Trace("RandomDataGeneratorSettings(): Ignoring symbol count will use provided tickers");
|
||||
}
|
||||
// --symbol-count
|
||||
else if (!int.TryParse(symbolCountString, out symbolCount) || symbolCount <= 0)
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Required parameter --symbol-count was incorrectly formatted. Please specify a valid integer greater than zero. Value provided: '{symbolCountString}'");
|
||||
}
|
||||
|
||||
// --chain-symbol-count
|
||||
if (!int.TryParse(chainSymbolCountString, out chainSymbolCount) || chainSymbolCount <= 0)
|
||||
{
|
||||
chainSymbolCount = 10;
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Using default value of '{chainSymbolCount}' for --chain-symbol-count");
|
||||
}
|
||||
|
||||
// --resolution
|
||||
if (string.IsNullOrEmpty(resolutionString))
|
||||
{
|
||||
resolution = Resolution.Minute;
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Using default value of '{resolution}' for --resolution");
|
||||
}
|
||||
else if (!Enum.TryParse(resolutionString, true, out resolution))
|
||||
{
|
||||
var validValues = string.Join(", ", Enum.GetValues(typeof(Resolution)).Cast<Resolution>());
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --resolution was incorrectly formatted. Default is Minute. Please specify a valid Resolution. Value provided: '{resolutionString}' Valid values: {validValues}");
|
||||
}
|
||||
|
||||
// --standard deviation volatility period span
|
||||
if (string.IsNullOrEmpty(volatilityModelResolutionString))
|
||||
{
|
||||
volatilityModelResolution = Resolution.Daily;
|
||||
Log.Trace($"RandomDataGeneratorSettings():Using default value of '{resolution}' for --resolution");
|
||||
}
|
||||
else if (!Enum.TryParse(volatilityModelResolutionString, true, out volatilityModelResolution))
|
||||
{
|
||||
var validValues = string.Join(", ", Enum.GetValues(typeof(Resolution)).Cast<Resolution>());
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --volatility-model-resolution was incorrectly formatted. Default is Daily. Please specify a valid Resolution. Value provided: '{volatilityModelResolutionString}' Valid values: {validValues}");
|
||||
}
|
||||
|
||||
// --security-type
|
||||
if (string.IsNullOrEmpty(securityTypeString))
|
||||
{
|
||||
securityType = SecurityType.Equity;
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Using default value of '{securityType}' for --security-type");
|
||||
}
|
||||
else if (!Enum.TryParse(securityTypeString, true, out securityType))
|
||||
{
|
||||
var validValues = string.Join(", ", Enum.GetValues(typeof(SecurityType)).Cast<SecurityType>());
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --security-type is invalid. Default is Equity. Please specify a valid SecurityType. Value provided: '{securityTypeString}' Valid values: {validValues}");
|
||||
}
|
||||
|
||||
if (securityType == SecurityType.Option && resolution != Resolution.Minute)
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): When using --security-type=Option you must specify --resolution=Minute");
|
||||
}
|
||||
|
||||
// --market
|
||||
if (string.IsNullOrEmpty(market))
|
||||
{
|
||||
market = DefaultBrokerageModel.DefaultMarketMap[securityType];
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Using default value of '{market}' for --market and --security-type={securityType}");
|
||||
}
|
||||
else if (QuantConnect.Market.Encode(market) == null)
|
||||
{
|
||||
// be sure to add a reference to the unknown market, otherwise we won't be able to decode it coming out
|
||||
QuantConnect.Market.Add(market, Interlocked.Increment(ref MarketCode));
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Please verify that the specified market value is correct: '{market}' This value is not known has been added to the market value map. If this is an error, stop the application immediately using Ctrl+C");
|
||||
}
|
||||
|
||||
// --include-coarse
|
||||
if (string.IsNullOrEmpty(includeCoarseString))
|
||||
{
|
||||
includeCoarse = securityType == SecurityType.Equity;
|
||||
if (securityType != SecurityType.Equity)
|
||||
{
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Using default value of '{includeCoarse}' for --security-type={securityType}");
|
||||
}
|
||||
}
|
||||
else if (!bool.TryParse(includeCoarseString, out includeCoarse))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --include-coarse was incorrectly formatted. Please specify a valid boolean. Value provided: '{includeCoarseString}'. Valid values: 'true' or 'false'");
|
||||
}
|
||||
else if (includeCoarse && securityType != SecurityType.Equity)
|
||||
{
|
||||
Log.Trace("RandomDataGeneratorSettings(): Optional parameter --include-coarse will be ignored because it only applies to --security-type=Equity");
|
||||
}
|
||||
|
||||
// --data-density
|
||||
if (string.IsNullOrEmpty(dataDensityString))
|
||||
{
|
||||
dataDensity = DataDensity.Dense;
|
||||
if (securityType == SecurityType.Option)
|
||||
{
|
||||
dataDensity = DataDensity.Sparse;
|
||||
}
|
||||
Log.Trace($"RandomDataGeneratorSettings(): Using default value of '{dataDensity}' for --data-density");
|
||||
}
|
||||
else if (!Enum.TryParse(dataDensityString, true, out dataDensity))
|
||||
{
|
||||
var validValues = string.Join(", ", Enum.GetValues(typeof(DataDensity))).Cast<DataDensity>();
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --data-density was incorrectly formatted. Please specify a valid DataDensity. Value provided: '{dataDensityString}'. Valid values: {validValues}");
|
||||
}
|
||||
|
||||
// --quote-trade-ratio
|
||||
if (string.IsNullOrEmpty(quoteTradeRatioString))
|
||||
{
|
||||
quoteTradeRatio = 1;
|
||||
}
|
||||
else if (!double.TryParse(quoteTradeRatioString, out quoteTradeRatio))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --quote-trade-ratio was incorrectly formatted. Please specify a valid double greater than or equal to zero. Value provided: '{quoteTradeRatioString}'");
|
||||
}
|
||||
|
||||
// --random-seed
|
||||
if (string.IsNullOrEmpty(randomSeedString))
|
||||
{
|
||||
randomSeed = 0;
|
||||
randomSeedSet = false;
|
||||
}
|
||||
else if (!int.TryParse(randomSeedString, out randomSeed))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --random-seed was incorrectly formatted. Please specify a valid integer");
|
||||
}
|
||||
|
||||
// --ipo-percentage
|
||||
if (string.IsNullOrEmpty(hasIpoPercentageString))
|
||||
{
|
||||
hasIpoPercentage = 5.0;
|
||||
}
|
||||
else if (!double.TryParse(hasIpoPercentageString, out hasIpoPercentage))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --ipo-percentage was incorrectly formatted. Please specify a valid double greater than or equal to zero. Value provided: '{hasIpoPercentageString}'");
|
||||
}
|
||||
|
||||
// --rename-percentage
|
||||
if (string.IsNullOrEmpty(hasRenamePercentageString))
|
||||
{
|
||||
hasRenamePercentage = 30.0;
|
||||
}
|
||||
else if (!double.TryParse(hasRenamePercentageString, out hasRenamePercentage))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --rename-percentage was incorrectly formatted. Please specify a valid double greater than or equal to zero. Value provided: '{hasRenamePercentageString}'");
|
||||
}
|
||||
|
||||
// --splits-percentage
|
||||
if (string.IsNullOrEmpty(hasSplitsPercentageString))
|
||||
{
|
||||
hasSplitsPercentage = 15.0;
|
||||
}
|
||||
else if (!double.TryParse(hasSplitsPercentageString, out hasSplitsPercentage))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --splits-percentage was incorrectly formatted. Please specify a valid double greater than or equal to zero. Value provided: '{hasSplitsPercentageString}'");
|
||||
}
|
||||
|
||||
// --dividends-percentage
|
||||
if (string.IsNullOrEmpty(hasDividendsPercentageString))
|
||||
{
|
||||
hasDividendsPercentage = 60.0;
|
||||
}
|
||||
else if (!double.TryParse(hasDividendsPercentageString, out hasDividendsPercentage))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --dividends-percentage was incorrectly formatted. Please specify a valid double greater than or equal to zero. Value provided: '{hasDividendsPercentageString}'");
|
||||
}
|
||||
|
||||
// --dividend-every-quarter-percentage
|
||||
if (string.IsNullOrEmpty(dividendEveryQuarterPercentageString))
|
||||
{
|
||||
dividendEveryQuarterPercentage = 30.0;
|
||||
}
|
||||
else if (!double.TryParse(dividendEveryQuarterPercentageString, out dividendEveryQuarterPercentage))
|
||||
{
|
||||
failed = true;
|
||||
Log.Error($"RandomDataGeneratorSettings(): Optional parameter --dividend-ever-quarter-percentage was incorrectly formatted. Please specify a valid double greater than or equal to zero. Value provided: '{dividendEveryQuarterPercentageString}'");
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
Log.Error("RandomDataGeneratorSettings(): Please address the errors and run the application again.");
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
return new RandomDataGeneratorSettings
|
||||
{
|
||||
End = endDate,
|
||||
Start = startDate,
|
||||
|
||||
Market = market,
|
||||
SymbolCount = symbolCount,
|
||||
SecurityType = securityType,
|
||||
QuoteTradeRatio = quoteTradeRatio,
|
||||
ChainSymbolCount = chainSymbolCount,
|
||||
|
||||
Resolution = resolution,
|
||||
|
||||
DataDensity = dataDensity,
|
||||
IncludeCoarse = includeCoarse,
|
||||
RandomSeed = randomSeed,
|
||||
RandomSeedSet = randomSeedSet,
|
||||
|
||||
HasIpoPercentage = hasIpoPercentage,
|
||||
HasRenamePercentage = hasRenamePercentage,
|
||||
HasSplitsPercentage = hasSplitsPercentage,
|
||||
MonthSplitPercentage = monthSplitPercentage,
|
||||
HasDividendsPercentage = hasDividendsPercentage,
|
||||
DividendEveryQuarterPercentage = dividendEveryQuarterPercentage,
|
||||
OptionPriceEngineName = optionPriceEngineName,
|
||||
VolatilityModelResolution = volatilityModelResolution,
|
||||
Tickers = tickers
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Random pricing model used to determine the fair price or theoretical value for a call or a put option
|
||||
/// </summary>
|
||||
public class RandomPriceGenerator : IPriceGenerator
|
||||
{
|
||||
private readonly Security _security;
|
||||
private readonly IRandomValueGenerator _random;
|
||||
|
||||
/// <summary>
|
||||
/// Creates instance of <see cref="RandomPriceGenerator"/>
|
||||
/// </summary>
|
||||
///<param name="security"><see cref="Security"/> object for which to generate price data</param>
|
||||
/// <param name="random"><see cref="IRandomValueGenerator"/> type capable of producing random values</param>
|
||||
public RandomPriceGenerator(Security security, IRandomValueGenerator random)
|
||||
{
|
||||
_security = security;
|
||||
_random = random;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="RandomPriceGenerator"/> is always ready to generate new price values as it does not depend on volatility model
|
||||
/// </summary>
|
||||
public bool WarmedUp => true;
|
||||
|
||||
/// <summary>
|
||||
/// Generates an asset price
|
||||
/// </summary>
|
||||
/// <param name="maximumPercentDeviation">The maximum percent deviation. This value is in percent space,
|
||||
/// so a value of 1m is equal to 1%.</param>
|
||||
/// <param name="referenceDate">date used in price calculation</param>
|
||||
/// <returns>Returns a new decimal as price</returns>
|
||||
public decimal NextValue(decimal maximumPercentDeviation, DateTime referenceDate)
|
||||
=> _random.NextPrice(_security.Symbol.SecurityType, _security.Symbol.ID.Market, _security.Price, maximumPercentDeviation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.Linq;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an implementation of <see cref="IRandomValueGenerator"/> that uses
|
||||
/// <see cref="Random"/> to generate random values
|
||||
/// </summary>
|
||||
public class RandomValueGenerator : IRandomValueGenerator
|
||||
{
|
||||
private readonly Random _random;
|
||||
private readonly MarketHoursDatabase _marketHoursDatabase;
|
||||
private readonly SymbolPropertiesDatabase _symbolPropertiesDatabase;
|
||||
private const decimal _maximumPriceAllowed = 1000000m;
|
||||
|
||||
|
||||
public RandomValueGenerator()
|
||||
: this(new Random())
|
||||
{ }
|
||||
|
||||
public RandomValueGenerator(int seed)
|
||||
: this(new Random(seed))
|
||||
{ }
|
||||
|
||||
public RandomValueGenerator(Random random)
|
||||
: this(random, MarketHoursDatabase.FromDataFolder(), SymbolPropertiesDatabase.FromDataFolder())
|
||||
{ }
|
||||
|
||||
public RandomValueGenerator(
|
||||
int seed,
|
||||
MarketHoursDatabase marketHoursDatabase,
|
||||
SymbolPropertiesDatabase symbolPropertiesDatabase
|
||||
)
|
||||
: this(new Random(seed), marketHoursDatabase, symbolPropertiesDatabase)
|
||||
{ }
|
||||
|
||||
public RandomValueGenerator(Random random, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase)
|
||||
{
|
||||
_random = random;
|
||||
_marketHoursDatabase = marketHoursDatabase;
|
||||
_symbolPropertiesDatabase = symbolPropertiesDatabase;
|
||||
}
|
||||
|
||||
public bool NextBool(double percentOddsForTrue)
|
||||
{
|
||||
return _random.NextDouble() <= percentOddsForTrue / 100;
|
||||
}
|
||||
|
||||
public virtual DateTime NextDate(DateTime minDateTime, DateTime maxDateTime, DayOfWeek? dayOfWeek)
|
||||
{
|
||||
if (maxDateTime < minDateTime)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The maximum date time must be less than or equal to the minimum date time specified"
|
||||
);
|
||||
}
|
||||
|
||||
// compute a random date time value
|
||||
var rangeInDays = (int)maxDateTime.Subtract(minDateTime).TotalDays;
|
||||
var daysOffsetFromMin = _random.Next(0, rangeInDays);
|
||||
var dateTime = minDateTime.AddDays(daysOffsetFromMin);
|
||||
|
||||
var currentDayOfWeek = dateTime.DayOfWeek;
|
||||
if (!dayOfWeek.HasValue || currentDayOfWeek == dayOfWeek.Value)
|
||||
{
|
||||
// either DOW wasn't specified or we got REALLY lucky, although, I suppose it'll happen 1/7 (~14%) of the time
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
var nextDayOfWeek = Enumerable.Range(0, 7)
|
||||
.Select(i => dateTime.AddDays(i))
|
||||
.First(dt => dt.DayOfWeek == dayOfWeek.Value);
|
||||
|
||||
var previousDayOfWeek = Enumerable.Range(0, 7)
|
||||
.Select(i => dateTime.AddDays(-i))
|
||||
.First(dt => dt.DayOfWeek == dayOfWeek.Value);
|
||||
|
||||
// both are valid dates, so chose one randomly
|
||||
if (IsWithinRange(nextDayOfWeek, minDateTime, maxDateTime) &&
|
||||
IsWithinRange(previousDayOfWeek, minDateTime, maxDateTime)
|
||||
)
|
||||
{
|
||||
return _random.Next(0, 1) == 0
|
||||
? previousDayOfWeek
|
||||
: nextDayOfWeek;
|
||||
}
|
||||
|
||||
if (IsWithinRange(nextDayOfWeek, minDateTime, maxDateTime))
|
||||
{
|
||||
return nextDayOfWeek;
|
||||
}
|
||||
|
||||
if (IsWithinRange(previousDayOfWeek, minDateTime, maxDateTime))
|
||||
{
|
||||
return previousDayOfWeek;
|
||||
}
|
||||
|
||||
throw new ArgumentException("The provided min and max dates do not have the requested day of week between them");
|
||||
}
|
||||
|
||||
public double NextDouble() => _random.NextDouble();
|
||||
|
||||
public int NextInt(int minValue, int maxValue) => _random.Next(minValue, maxValue);
|
||||
|
||||
public int NextInt(int maxValue) => _random.Next(maxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="decimal"/> suitable as a price. This should observe minimum price
|
||||
/// variations if available in <see cref="SymbolPropertiesDatabase"/>, and if not, truncating to 2
|
||||
/// decimal places.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">Throw when the <paramref name="referencePrice"/> or <paramref name="maximumPercentDeviation"/>
|
||||
/// is less than or equal to zero.</exception>
|
||||
/// <param name="securityType">The security type the price is being generated for</param>
|
||||
/// <param name="market">The market of the security the price is being generated for</param>
|
||||
/// <param name="referencePrice">The reference price used as the mean of random price generation</param>
|
||||
/// <param name="maximumPercentDeviation">The maximum percent deviation. This value is in percent space,
|
||||
/// so a value of 1m is equal to 1%.</param>
|
||||
/// <returns>A new decimal suitable for usage as price within the specified deviation from the reference price</returns>
|
||||
public virtual decimal NextPrice(SecurityType securityType, string market, decimal referencePrice, decimal maximumPercentDeviation)
|
||||
{
|
||||
if (referencePrice <= 0)
|
||||
{
|
||||
if (securityType == SecurityType.Option && referencePrice == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
throw new ArgumentException("The provided reference price must be a positive number.");
|
||||
}
|
||||
|
||||
if (maximumPercentDeviation <= 0)
|
||||
{
|
||||
throw new ArgumentException("The provided maximum percent deviation must be a positive number");
|
||||
}
|
||||
|
||||
// convert from percent space to decimal space
|
||||
maximumPercentDeviation /= 100m;
|
||||
|
||||
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(market, null, securityType, "USD");
|
||||
var minimumPriceVariation = symbolProperties.MinimumPriceVariation;
|
||||
|
||||
decimal price;
|
||||
var attempts = 0;
|
||||
var increaseProbabilityFactor = 0.5;
|
||||
do
|
||||
{
|
||||
// what follows is a simple model of browning motion that
|
||||
// limits the walk to the specified percent deviation
|
||||
|
||||
var deviation = referencePrice * maximumPercentDeviation * (decimal)(NextDouble() - increaseProbabilityFactor);
|
||||
deviation = Math.Sign(deviation) * Math.Max(Math.Abs(deviation), minimumPriceVariation);
|
||||
price = referencePrice + deviation;
|
||||
price = RoundPrice(price, minimumPriceVariation);
|
||||
|
||||
if (price < 20 * minimumPriceVariation)
|
||||
{
|
||||
// The price should not be to close to the minimum price variation.
|
||||
// Invalidate the price to try again and increase the probability of it to going up
|
||||
price = -1m;
|
||||
increaseProbabilityFactor = Math.Max(increaseProbabilityFactor - 0.05, 0);
|
||||
}
|
||||
|
||||
if (price > (_maximumPriceAllowed / 10m))
|
||||
{
|
||||
// The price should not be too higher
|
||||
// Decrease the probability of it to going up
|
||||
increaseProbabilityFactor = increaseProbabilityFactor + 0.05;
|
||||
}
|
||||
|
||||
if (price > _maximumPriceAllowed)
|
||||
{
|
||||
// The price should not be too higher
|
||||
// Invalidate the price to try again
|
||||
price = -1;
|
||||
}
|
||||
} while (!IsPriceValid(securityType, price) && ++attempts < 10);
|
||||
|
||||
if (!IsPriceValid(securityType, price))
|
||||
{
|
||||
// if still invalid, use the last price
|
||||
price = referencePrice;
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
|
||||
private static decimal RoundPrice(decimal price, decimal minimumPriceVariation)
|
||||
{
|
||||
if (minimumPriceVariation == 0) return minimumPriceVariation;
|
||||
return Math.Round(price / minimumPriceVariation) * minimumPriceVariation;
|
||||
}
|
||||
|
||||
private bool IsWithinRange(DateTime value, DateTime min, DateTime max)
|
||||
{
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
private static bool IsPriceValid(SecurityType securityType, decimal price)
|
||||
{
|
||||
switch (securityType)
|
||||
{
|
||||
case SecurityType.Option:
|
||||
{
|
||||
return price >= 0;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return price > 0 && price < _maximumPriceAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for exceptions thrown by implementations of <see cref="IRandomValueGenerator"/>
|
||||
/// </summary>
|
||||
public class RandomValueGeneratorException : ApplicationException
|
||||
{
|
||||
public RandomValueGeneratorException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
public class SecurityInitializerProvider : ISecurityInitializerProvider
|
||||
{
|
||||
public ISecurityInitializer SecurityInitializer { get; }
|
||||
|
||||
public SecurityInitializerProvider(ISecurityInitializer securityInitializer)
|
||||
{
|
||||
SecurityInitializer = securityInitializer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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.Data.Market;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Logging;
|
||||
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates random tick data according to the settings provided
|
||||
/// </summary>
|
||||
public class TickGenerator : ITickGenerator
|
||||
{
|
||||
private readonly IPriceGenerator _priceGenerator;
|
||||
private Symbol Symbol => Security.Symbol;
|
||||
|
||||
private readonly IRandomValueGenerator _random;
|
||||
private readonly RandomDataGeneratorSettings _settings;
|
||||
private readonly TickType[] _tickTypes;
|
||||
|
||||
private MarketHoursDatabase MarketHoursDatabase { get; }
|
||||
private SymbolPropertiesDatabase SymbolPropertiesDatabase { get; }
|
||||
private Security Security { get; }
|
||||
|
||||
public TickGenerator(RandomDataGeneratorSettings settings, TickType[] tickTypes, Security security, IRandomValueGenerator random)
|
||||
{
|
||||
_random = random;
|
||||
_settings = settings;
|
||||
_tickTypes = tickTypes;
|
||||
Security = security;
|
||||
SymbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder();
|
||||
MarketHoursDatabase = MarketHoursDatabase.FromDataFolder();
|
||||
|
||||
if (Symbol.SecurityType.IsOption())
|
||||
{
|
||||
_priceGenerator = new OptionPriceModelPriceGenerator(security);
|
||||
}
|
||||
else
|
||||
{
|
||||
_priceGenerator = new RandomPriceGenerator(security, random);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Tick> GenerateTicks()
|
||||
{
|
||||
var current = _settings.Start;
|
||||
|
||||
// There is a possibility that even though this succeeds, the DateTime
|
||||
// generated may be the same as the starting DateTime, although the probability
|
||||
// of this happening diminishes the longer the period we're generating data for is
|
||||
if (_random.NextBool(_settings.HasIpoPercentage))
|
||||
{
|
||||
current = _random.NextDate(_settings.Start, _settings.End, null);
|
||||
Log.Trace($"\tSymbol: {Symbol} has delayed IPO at date {current:yyyy MMMM dd}");
|
||||
}
|
||||
|
||||
// creates a max deviation that scales parabolically as resolution decreases (lower frequency)
|
||||
var deviation = GetMaximumDeviation(_settings.Resolution);
|
||||
while (current <= _settings.End)
|
||||
{
|
||||
|
||||
var next = NextTickTime(current, _settings.Resolution, _settings.DataDensity);
|
||||
// The current date can be the last one of the last day before the market closes
|
||||
// so the next date could be beyond de end date
|
||||
if (next > _settings.End)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_tickTypes.Contains(TickType.OpenInterest))
|
||||
{
|
||||
if (next.Date != current.Date)
|
||||
{
|
||||
// 5% deviation in daily OI
|
||||
var openInterest = NextTick(next.Date, TickType.OpenInterest, 5m);
|
||||
yield return openInterest;
|
||||
}
|
||||
}
|
||||
|
||||
Tick nextTick = null;
|
||||
// keeps quotes close to the trades for consistency
|
||||
if (_tickTypes.Contains(TickType.Trade) &&
|
||||
_tickTypes.Contains(TickType.Quote))
|
||||
{
|
||||
// %odds of getting a trade tick, for example, a quote:trade ratio of 2 means twice as likely
|
||||
// to get a quote, which means you have a 33% chance of getting a trade => 1/3
|
||||
var tradeChancePercent = 100 / (1 + _settings.QuoteTradeRatio);
|
||||
nextTick = NextTick(
|
||||
next,
|
||||
_random.NextBool(tradeChancePercent)
|
||||
? TickType.Trade
|
||||
: TickType.Quote,
|
||||
deviation);
|
||||
}
|
||||
else if (_tickTypes.Contains(TickType.Trade))
|
||||
{
|
||||
nextTick = NextTick(next, TickType.Trade, deviation);
|
||||
|
||||
}
|
||||
else if (_tickTypes.Contains(TickType.Quote))
|
||||
{
|
||||
nextTick = NextTick(next, TickType.Quote, deviation);
|
||||
}
|
||||
|
||||
if (nextTick != null && _priceGenerator.WarmedUp)
|
||||
{
|
||||
yield return nextTick;
|
||||
}
|
||||
|
||||
// advance to the next time step
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="Tick"/> that is at most the specified <paramref name="maximumPercentDeviation"/> away from the
|
||||
/// previous price and is of the requested <paramref name="tickType"/>
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The time of the generated tick</param>
|
||||
/// <param name="tickType">The type of <see cref="Tick"/> to be generated</param>
|
||||
/// <param name="maximumPercentDeviation">The maximum percentage to deviate from the
|
||||
/// previous price for example, 1 would indicate a maximum of 1% deviation from the
|
||||
/// previous price. For a previous price of 100, this would yield a price between 99 and 101 inclusive</param>
|
||||
/// <returns>A random <see cref="Tick"/> value that is within the specified <paramref name="maximumPercentDeviation"/>
|
||||
/// from the previous price</returns>
|
||||
public virtual Tick NextTick(DateTime dateTime, TickType tickType, decimal maximumPercentDeviation)
|
||||
{
|
||||
var next = _priceGenerator.NextValue(maximumPercentDeviation, dateTime);
|
||||
var tick = new Tick
|
||||
{
|
||||
Time = dateTime,
|
||||
Symbol = Symbol,
|
||||
TickType = tickType,
|
||||
Value = next
|
||||
};
|
||||
|
||||
switch (tickType)
|
||||
{
|
||||
case TickType.OpenInterest:
|
||||
return NextOpenInterest(dateTime, Security.OpenInterest, maximumPercentDeviation);
|
||||
|
||||
case TickType.Trade:
|
||||
tick.Quantity = _random.NextInt(1, 1500);
|
||||
return tick;
|
||||
|
||||
case TickType.Quote:
|
||||
var bid = _random.NextPrice(Symbol.SecurityType, Symbol.ID.Market, tick.Value, maximumPercentDeviation);
|
||||
if (bid > tick.Value)
|
||||
{
|
||||
bid = tick.Value - (bid - tick.Value);
|
||||
}
|
||||
var ask = _random.NextPrice(Symbol.SecurityType, Symbol.ID.Market, tick.Value, maximumPercentDeviation);
|
||||
if (ask < tick.Value)
|
||||
{
|
||||
ask = tick.Value + (tick.Value - ask);
|
||||
}
|
||||
|
||||
tick.BidPrice = bid;
|
||||
tick.BidSize = _random.NextInt(1, 1500);
|
||||
tick.AskPrice = ask;
|
||||
tick.AskSize = _random.NextInt(1, 1500);
|
||||
return tick;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(tickType), tickType, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="Tick"/> that is at most the specified <paramref name="maximumPercentDeviation"/> away from the
|
||||
/// <paramref name="previousValue"/> and is of the Open Interest
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The time of the generated tick</param>
|
||||
/// <param name="previousValue">The previous price, used as a reference for generating
|
||||
/// new random prices for the next time step</param>
|
||||
/// <param name="maximumPercentDeviation">The maximum percentage to deviate from the
|
||||
/// <paramref name="previousValue"/>, for example, 1 would indicate a maximum of 1% deviation from the
|
||||
/// <paramref name="previousValue"/>. For a previous price of 100, this would yield a price between 99 and 101 inclusive</param>
|
||||
/// <returns>A random <see cref="Tick"/> value that is within the specified <paramref name="maximumPercentDeviation"/>
|
||||
/// from the <paramref name="previousValue"/></returns>
|
||||
public Tick NextOpenInterest(DateTime dateTime, decimal previousValue, decimal maximumPercentDeviation)
|
||||
{
|
||||
var next = (long)_random.NextPrice(Symbol.SecurityType, Symbol.ID.Market, previousValue, maximumPercentDeviation);
|
||||
return new OpenInterest
|
||||
{
|
||||
Time = dateTime,
|
||||
Symbol = Symbol,
|
||||
TickType = TickType.OpenInterest,
|
||||
Value = next,
|
||||
Quantity = next
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random <see cref="DateTime"/> suitable for use as a tick's emit time.
|
||||
/// If the density provided is <see cref="DataDensity.Dense"/>, then at least one tick will be generated per <paramref name="resolution"/> step.
|
||||
/// If the density provided is <see cref="DataDensity.Sparse"/>, then at least one tick will be generated every 5 <paramref name="resolution"/> steps.
|
||||
/// if the density provided is <see cref="DataDensity.VerySparse"/>, then at least one tick will be generated every 50 <paramref name="resolution"/> steps.
|
||||
/// Times returned are guaranteed to be within market hours for the specified Symbol
|
||||
/// </summary>
|
||||
/// <param name="previous">The previous tick time</param>
|
||||
/// <param name="resolution">The requested resolution of data</param>
|
||||
/// <param name="density">The requested data density</param>
|
||||
/// <returns>A new <see cref="DateTime"/> that is after <paramref name="previous"/> according to the specified <paramref name="resolution"/>
|
||||
/// and <paramref name="density"/> specified</returns>
|
||||
public virtual DateTime NextTickTime(DateTime previous, Resolution resolution, DataDensity density)
|
||||
{
|
||||
var increment = resolution.ToTimeSpan();
|
||||
if (increment == TimeSpan.Zero)
|
||||
{
|
||||
increment = TimeSpan.FromMilliseconds(500);
|
||||
}
|
||||
|
||||
double steps;
|
||||
switch (density)
|
||||
{
|
||||
case DataDensity.Dense:
|
||||
steps = 0.5 * _random.NextDouble();
|
||||
break;
|
||||
|
||||
case DataDensity.Sparse:
|
||||
steps = 5 * _random.NextDouble();
|
||||
break;
|
||||
|
||||
case DataDensity.VerySparse:
|
||||
steps = 50 * _random.NextDouble();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(density), density, null);
|
||||
}
|
||||
|
||||
var delta = TimeSpan.FromTicks((long)(steps * increment.Ticks));
|
||||
var tickTime = previous.Add(delta);
|
||||
if (tickTime == previous)
|
||||
{
|
||||
tickTime = tickTime.Add(increment);
|
||||
}
|
||||
|
||||
var barStart = tickTime.Subtract(increment);
|
||||
var marketHours = MarketHoursDatabase.GetExchangeHours(Symbol.ID.Market, Symbol, Symbol.SecurityType);
|
||||
if (!marketHours.IsDateOpen(tickTime) || !marketHours.IsOpen(barStart, tickTime, false))
|
||||
{
|
||||
// we ended up outside of market hours, emit a new tick at market open
|
||||
var nextMarketOpen = marketHours.GetNextMarketOpen(tickTime, false);
|
||||
if (resolution == Resolution.Tick)
|
||||
{
|
||||
resolution = Resolution.Second;
|
||||
}
|
||||
|
||||
// emit a new tick somewhere in the next trading day at a step higher resolution to guarantee a hit
|
||||
return NextTickTime(nextMarketOpen, resolution - 1, density);
|
||||
}
|
||||
|
||||
return tickTime;
|
||||
}
|
||||
|
||||
private static decimal GetMaximumDeviation(Resolution resolution)
|
||||
{
|
||||
var incr = ((int)resolution) + 0.15m;
|
||||
var deviation = incr * incr * 0.1m;
|
||||
return deviation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace QuantConnect.ToolBox.RandomDataGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when multiple attempts to generate a valid random value end in failure
|
||||
/// </summary>
|
||||
public class TooManyFailedAttemptsException : RandomValueGeneratorException
|
||||
{
|
||||
public TooManyFailedAttemptsException(string method, int attempts)
|
||||
: base($"Failed to generate a valid value for '{method}' after {attempts} attempts.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user