/* * 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 { /// /// Generates random tick data according to the settings provided /// 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 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; } } /// /// Generates a random that is at most the specified away from the /// previous price and is of the requested /// /// The time of the generated tick /// The type of to be generated /// 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 /// A random value that is within the specified /// from the previous price 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); } } /// /// Generates a random that is at most the specified away from the /// and is of the Open Interest /// /// The time of the generated tick /// The previous price, used as a reference for generating /// new random prices for the next time step /// The maximum percentage to deviate from the /// , for example, 1 would indicate a maximum of 1% deviation from the /// . For a previous price of 100, this would yield a price between 99 and 101 inclusive /// A random value that is within the specified /// from the 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 }; } /// /// Generates a random suitable for use as a tick's emit time. /// If the density provided is , then at least one tick will be generated per step. /// If the density provided is , then at least one tick will be generated every 5 steps. /// if the density provided is , then at least one tick will be generated every 50 steps. /// Times returned are guaranteed to be within market hours for the specified Symbol /// /// The previous tick time /// The requested resolution of data /// The requested data density /// A new that is after according to the specified /// and specified 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; } } }