Lean  $LEAN_TAG$
TickGenerator.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
18 using System;
19 using System.Collections.Generic;
20 using System.Linq;
21 using QuantConnect.Logging;
22 
24 {
25  /// <summary>
26  /// Generates random tick data according to the settings provided
27  /// </summary>
29  {
30  private readonly IPriceGenerator _priceGenerator;
31  private Symbol Symbol => Security.Symbol;
32 
33  private readonly IRandomValueGenerator _random;
34  private readonly RandomDataGeneratorSettings _settings;
35  private readonly TickType[] _tickTypes;
36 
39  private Security Security { get; }
40 
41  public TickGenerator(RandomDataGeneratorSettings settings, TickType[] tickTypes, Security security, IRandomValueGenerator random)
42  {
43  _random = random;
44  _settings = settings;
45  _tickTypes = tickTypes;
46  Security = security;
49 
50  if (Symbol.SecurityType.IsOption())
51  {
52  _priceGenerator = new OptionPriceModelPriceGenerator(security);
53  }
54  else
55  {
56  _priceGenerator = new RandomPriceGenerator(security, random);
57  }
58  }
59 
60  public IEnumerable<Tick> GenerateTicks()
61  {
62  var current = _settings.Start;
63 
64  // There is a possibility that even though this succeeds, the DateTime
65  // generated may be the same as the starting DateTime, although the probability
66  // of this happening diminishes the longer the period we're generating data for is
67  if (_random.NextBool(_settings.HasIpoPercentage))
68  {
69  current = _random.NextDate(_settings.Start, _settings.End, null);
70  Log.Trace($"\tSymbol: {Symbol} has delayed IPO at date {current:yyyy MMMM dd}");
71  }
72 
73  // creates a max deviation that scales parabolically as resolution decreases (lower frequency)
74  var deviation = GetMaximumDeviation(_settings.Resolution);
75  while (current <= _settings.End)
76  {
77 
78  var next = NextTickTime(current, _settings.Resolution, _settings.DataDensity);
79  // The current date can be the last one of the last day before the market closes
80  // so the next date could be beyond de end date
81  if (next > _settings.End)
82  {
83  break;
84  }
85 
86  if (_tickTypes.Contains(TickType.OpenInterest))
87  {
88  if (next.Date != current.Date)
89  {
90  // 5% deviation in daily OI
91  var openInterest = NextTick(next.Date, TickType.OpenInterest, 5m);
92  yield return openInterest;
93  }
94  }
95 
96  Tick nextTick = null;
97  // keeps quotes close to the trades for consistency
98  if (_tickTypes.Contains(TickType.Trade) &&
99  _tickTypes.Contains(TickType.Quote))
100  {
101  // %odds of getting a trade tick, for example, a quote:trade ratio of 2 means twice as likely
102  // to get a quote, which means you have a 33% chance of getting a trade => 1/3
103  var tradeChancePercent = 100 / (1 + _settings.QuoteTradeRatio);
104  nextTick = NextTick(
105  next,
106  _random.NextBool(tradeChancePercent)
107  ? TickType.Trade
108  : TickType.Quote,
109  deviation);
110  }
111  else if (_tickTypes.Contains(TickType.Trade))
112  {
113  nextTick = NextTick(next, TickType.Trade, deviation);
114 
115  }
116  else if (_tickTypes.Contains(TickType.Quote))
117  {
118  nextTick = NextTick(next, TickType.Quote, deviation);
119  }
120 
121  if (nextTick != null && _priceGenerator.WarmedUp)
122  {
123  yield return nextTick;
124  }
125 
126  // advance to the next time step
127  current = next;
128  }
129  }
130 
131  /// <summary>
132  /// Generates a random <see cref="Tick"/> that is at most the specified <paramref name="maximumPercentDeviation"/> away from the
133  /// previous price and is of the requested <paramref name="tickType"/>
134  /// </summary>
135  /// <param name="dateTime">The time of the generated tick</param>
136  /// <param name="tickType">The type of <see cref="Tick"/> to be generated</param>
137  /// <param name="maximumPercentDeviation">The maximum percentage to deviate from the
138  /// previous price for example, 1 would indicate a maximum of 1% deviation from the
139  /// previous price. For a previous price of 100, this would yield a price between 99 and 101 inclusive</param>
140  /// <returns>A random <see cref="Tick"/> value that is within the specified <paramref name="maximumPercentDeviation"/>
141  /// from the previous price</returns>
142  public virtual Tick NextTick(DateTime dateTime, TickType tickType, decimal maximumPercentDeviation)
143  {
144  var next = _priceGenerator.NextValue(maximumPercentDeviation, dateTime);
145  var tick = new Tick
146  {
147  Time = dateTime,
148  Symbol = Symbol,
149  TickType = tickType,
150  Value = next
151  };
152 
153  switch (tickType)
154  {
155  case TickType.OpenInterest:
156  return NextOpenInterest(dateTime, Security.OpenInterest, maximumPercentDeviation);
157 
158  case TickType.Trade:
159  tick.Quantity = _random.NextInt(1, 1500);
160  return tick;
161 
162  case TickType.Quote:
163  var bid = _random.NextPrice(Symbol.SecurityType, Symbol.ID.Market, tick.Value, maximumPercentDeviation);
164  if (bid > tick.Value)
165  {
166  bid = tick.Value - (bid - tick.Value);
167  }
168  var ask = _random.NextPrice(Symbol.SecurityType, Symbol.ID.Market, tick.Value, maximumPercentDeviation);
169  if (ask < tick.Value)
170  {
171  ask = tick.Value + (tick.Value - ask);
172  }
173 
174  tick.BidPrice = bid;
175  tick.BidSize = _random.NextInt(1, 1500);
176  tick.AskPrice = ask;
177  tick.AskSize = _random.NextInt(1, 1500);
178  return tick;
179 
180  default:
181  throw new ArgumentOutOfRangeException(nameof(tickType), tickType, null);
182  }
183  }
184 
185  /// <summary>
186  /// Generates a random <see cref="Tick"/> that is at most the specified <paramref name="maximumPercentDeviation"/> away from the
187  /// <paramref name="previousValue"/> and is of the Open Interest
188  /// </summary>
189  /// <param name="dateTime">The time of the generated tick</param>
190  /// <param name="previousValue">The previous price, used as a reference for generating
191  /// new random prices for the next time step</param>
192  /// <param name="maximumPercentDeviation">The maximum percentage to deviate from the
193  /// <paramref name="previousValue"/>, for example, 1 would indicate a maximum of 1% deviation from the
194  /// <paramref name="previousValue"/>. For a previous price of 100, this would yield a price between 99 and 101 inclusive</param>
195  /// <returns>A random <see cref="Tick"/> value that is within the specified <paramref name="maximumPercentDeviation"/>
196  /// from the <paramref name="previousValue"/></returns>
197  public Tick NextOpenInterest(DateTime dateTime, decimal previousValue, decimal maximumPercentDeviation)
198  {
199  var next = (long)_random.NextPrice(Symbol.SecurityType, Symbol.ID.Market, previousValue, maximumPercentDeviation);
200  return new OpenInterest
201  {
202  Time = dateTime,
203  Symbol = Symbol,
204  TickType = TickType.OpenInterest,
205  Value = next,
206  Quantity = next
207  };
208  }
209 
210  /// <summary>
211  /// Generates a random <see cref="DateTime"/> suitable for use as a tick's emit time.
212  /// If the density provided is <see cref="DataDensity.Dense"/>, then at least one tick will be generated per <paramref name="resolution"/> step.
213  /// If the density provided is <see cref="DataDensity.Sparse"/>, then at least one tick will be generated every 5 <paramref name="resolution"/> steps.
214  /// if the density provided is <see cref="DataDensity.VerySparse"/>, then at least one tick will be generated every 50 <paramref name="resolution"/> steps.
215  /// Times returned are guaranteed to be within market hours for the specified Symbol
216  /// </summary>
217  /// <param name="previous">The previous tick time</param>
218  /// <param name="resolution">The requested resolution of data</param>
219  /// <param name="density">The requested data density</param>
220  /// <returns>A new <see cref="DateTime"/> that is after <paramref name="previous"/> according to the specified <paramref name="resolution"/>
221  /// and <paramref name="density"/> specified</returns>
222  public virtual DateTime NextTickTime(DateTime previous, Resolution resolution, DataDensity density)
223  {
224  var increment = resolution.ToTimeSpan();
225  if (increment == TimeSpan.Zero)
226  {
227  increment = TimeSpan.FromMilliseconds(500);
228  }
229 
230  double steps;
231  switch (density)
232  {
233  case DataDensity.Dense:
234  steps = 0.5 * _random.NextDouble();
235  break;
236 
237  case DataDensity.Sparse:
238  steps = 5 * _random.NextDouble();
239  break;
240 
241  case DataDensity.VerySparse:
242  steps = 50 * _random.NextDouble();
243  break;
244 
245  default:
246  throw new ArgumentOutOfRangeException(nameof(density), density, null);
247  }
248 
249  var delta = TimeSpan.FromTicks((long)(steps * increment.Ticks));
250  var tickTime = previous.Add(delta);
251  if (tickTime == previous)
252  {
253  tickTime = tickTime.Add(increment);
254  }
255 
256  var barStart = tickTime.Subtract(increment);
258  if (!marketHours.IsDateOpen(tickTime) || !marketHours.IsOpen(barStart, tickTime, false))
259  {
260  // we ended up outside of market hours, emit a new tick at market open
261  var nextMarketOpen = marketHours.GetNextMarketOpen(tickTime, false);
262  if (resolution == Resolution.Tick)
263  {
264  resolution = Resolution.Second;
265  }
266 
267  // emit a new tick somewhere in the next trading day at a step higher resolution to guarantee a hit
268  return NextTickTime(nextMarketOpen, resolution - 1, density);
269  }
270 
271  return tickTime;
272  }
273 
274  private static decimal GetMaximumDeviation(Resolution resolution)
275  {
276  var incr = ((int)resolution) + 0.15m;
277  var deviation = incr * incr * 0.1m;
278  return deviation;
279  }
280  }
281 }