Lean  $LEAN_TAG$
QuoteBar.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 
16 using System;
17 using System.Globalization;
18 using System.IO;
19 using System.Runtime.CompilerServices;
20 using ProtoBuf;
21 using QuantConnect.Logging;
22 using QuantConnect.Util;
23 using static QuantConnect.StringExtensions;
24 
26 {
27  /// <summary>
28  /// QuoteBar class for second and minute resolution data:
29  /// An OHLC implementation of the QuantConnect BaseData class with parameters for candles.
30  /// </summary>
31  [ProtoContract(SkipConstructor = true)]
32  public class QuoteBar : BaseData, IBaseDataBar
33  {
34  // scale factor used in QC equity/option/indexOption data files
35  private const decimal _scaleFactor = 1 / 10000m;
36 
37  /// <summary>
38  /// Average bid size
39  /// </summary>
40  [ProtoMember(201)]
41  public decimal LastBidSize { get; set; }
42 
43  /// <summary>
44  /// Average ask size
45  /// </summary>
46  [ProtoMember(202)]
47  public decimal LastAskSize { get; set; }
48 
49  /// <summary>
50  /// Bid OHLC
51  /// </summary>
52  [ProtoMember(203)]
53  public Bar Bid { get; set; }
54 
55  /// <summary>
56  /// Ask OHLC
57  /// </summary>
58  [ProtoMember(204)]
59  public Bar Ask { get; set; }
60 
61  /// <summary>
62  /// Opening price of the bar: Defined as the price at the start of the time period.
63  /// </summary>
64  public decimal Open
65  {
66  get
67  {
68  if (Bid != null && Ask != null)
69  {
70  if (Bid.Open != 0m && Ask.Open != 0m)
71  return (Bid.Open + Ask.Open) / 2m;
72 
73  if (Bid.Open != 0)
74  return Bid.Open;
75 
76  if (Ask.Open != 0)
77  return Ask.Open;
78 
79  return 0m;
80  }
81  if (Bid != null)
82  {
83  return Bid.Open;
84  }
85  if (Ask != null)
86  {
87  return Ask.Open;
88  }
89  return 0m;
90  }
91  }
92 
93  /// <summary>
94  /// High price of the QuoteBar during the time period.
95  /// </summary>
96  public decimal High
97  {
98  get
99  {
100  if (Bid != null && Ask != null)
101  {
102  if (Bid.High != 0m && Ask.High != 0m)
103  return (Bid.High + Ask.High) / 2m;
104 
105  if (Bid.High != 0)
106  return Bid.High;
107 
108  if (Ask.High != 0)
109  return Ask.High;
110 
111  return 0m;
112  }
113  if (Bid != null)
114  {
115  return Bid.High;
116  }
117  if (Ask != null)
118  {
119  return Ask.High;
120  }
121  return 0m;
122  }
123  }
124 
125  /// <summary>
126  /// Low price of the QuoteBar during the time period.
127  /// </summary>
128  public decimal Low
129  {
130  get
131  {
132  if (Bid != null && Ask != null)
133  {
134  if (Bid.Low != 0m && Ask.Low != 0m)
135  return (Bid.Low + Ask.Low) / 2m;
136 
137  if (Bid.Low != 0)
138  return Bid.Low;
139 
140  if (Ask.Low != 0)
141  return Ask.Low;
142 
143  return 0m;
144  }
145  if (Bid != null)
146  {
147  return Bid.Low;
148  }
149  if (Ask != null)
150  {
151  return Ask.Low;
152  }
153  return 0m;
154  }
155  }
156 
157  /// <summary>
158  /// Closing price of the QuoteBar. Defined as the price at Start Time + TimeSpan.
159  /// </summary>
160  public decimal Close
161  {
162  get
163  {
164  if (Bid != null && Ask != null)
165  {
166  if (Bid.Close != 0m && Ask.Close != 0m)
167  return (Bid.Close + Ask.Close) / 2m;
168 
169  if (Bid.Close != 0)
170  return Bid.Close;
171 
172  if (Ask.Close != 0)
173  return Ask.Close;
174 
175  return 0m;
176  }
177  if (Bid != null)
178  {
179  return Bid.Close;
180  }
181  if (Ask != null)
182  {
183  return Ask.Close;
184  }
185  return Value;
186  }
187  }
188 
189  /// <summary>
190  /// The closing time of this bar, computed via the Time and Period
191  /// </summary>
192  public override DateTime EndTime
193  {
194  get { return Time + Period; }
195  set { Period = value - Time; }
196  }
197 
198  /// <summary>
199  /// The period of this quote bar, (second, minute, daily, ect...)
200  /// </summary>
201  [ProtoMember(205)]
202  public TimeSpan Period { get; set; }
203 
204  /// <summary>
205  /// Default initializer to setup an empty quotebar.
206  /// </summary>
207  public QuoteBar()
208  {
209  Symbol = Symbol.Empty;
210  Time = new DateTime();
211  Bid = new Bar();
212  Ask = new Bar();
213  Value = 0;
215  DataType = MarketDataType.QuoteBar;
216  }
217 
218  /// <summary>
219  /// Initialize Quote Bar with Bid(OHLC) and Ask(OHLC) Values:
220  /// </summary>
221  /// <param name="time">DateTime Timestamp of the bar</param>
222  /// <param name="symbol">Market MarketType Symbol</param>
223  /// <param name="bid">Bid OLHC bar</param>
224  /// <param name="lastBidSize">Average bid size over period</param>
225  /// <param name="ask">Ask OLHC bar</param>
226  /// <param name="lastAskSize">Average ask size over period</param>
227  /// <param name="period">The period of this bar, specify null for default of 1 minute</param>
228  public QuoteBar(DateTime time, Symbol symbol, IBar bid, decimal lastBidSize, IBar ask, decimal lastAskSize, TimeSpan? period = null)
229  {
230  Symbol = symbol;
231  Time = time;
232  Bid = bid == null ? null : new Bar(bid.Open, bid.High, bid.Low, bid.Close);
233  Ask = ask == null ? null : new Bar(ask.Open, ask.High, ask.Low, ask.Close);
234  if (Bid != null) LastBidSize = lastBidSize;
235  if (Ask != null) LastAskSize = lastAskSize;
236  Value = Close;
237  Period = period ?? QuantConnect.Time.OneMinute;
238  DataType = MarketDataType.QuoteBar;
239  }
240 
241  /// <summary>
242  /// Update the quotebar - build the bar from this pricing information:
243  /// </summary>
244  /// <param name="lastTrade">The last trade price</param>
245  /// <param name="bidPrice">Current bid price</param>
246  /// <param name="askPrice">Current asking price</param>
247  /// <param name="volume">Volume of this trade</param>
248  /// <param name="bidSize">The size of the current bid, if available, if not, pass 0</param>
249  /// <param name="askSize">The size of the current ask, if available, if not, pass 0</param>
250  [MethodImpl(MethodImplOptions.AggressiveInlining)]
251  public override void Update(decimal lastTrade, decimal bidPrice, decimal askPrice, decimal volume, decimal bidSize, decimal askSize)
252  {
253  // update our bid and ask bars - handle null values, this is to give good values for midpoint OHLC
254  if (Bid == null && bidPrice != 0) Bid = new Bar(bidPrice, bidPrice, bidPrice, bidPrice);
255  else if (Bid != null) Bid.Update(ref bidPrice);
256 
257  if (Ask == null && askPrice != 0) Ask = new Bar(askPrice, askPrice, askPrice, askPrice);
258  else if (Ask != null) Ask.Update(ref askPrice);
259 
260  if (bidSize > 0)
261  {
262  LastBidSize = bidSize;
263  }
264 
265  if (askSize > 0)
266  {
267  LastAskSize = askSize;
268  }
269 
270  // be prepared for updates without trades
271  if (lastTrade != 0) Value = lastTrade;
272  else if (askPrice != 0) Value = askPrice;
273  else if (bidPrice != 0) Value = bidPrice;
274  }
275 
276  /// <summary>
277  /// QuoteBar Reader: Fetch the data from the QC storage and feed it line by line into the engine.
278  /// </summary>
279  /// <param name="config">Symbols, Resolution, DataType, </param>
280  /// <param name="stream">The file data stream</param>
281  /// <param name="date">Date of this reader request</param>
282  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
283  /// <returns>Enumerable iterator for returning each line of the required data.</returns>
284  public override BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode)
285  {
286  try
287  {
288  switch (config.SecurityType)
289  {
290  case SecurityType.Equity:
291  return ParseEquity(config, stream, date);
292 
293  case SecurityType.Forex:
294  case SecurityType.Crypto:
295  case SecurityType.CryptoFuture:
296  return ParseForex(config, stream, date);
297 
298  case SecurityType.Cfd:
299  return ParseCfd(config, stream, date);
300 
301  case SecurityType.Option:
302  case SecurityType.FutureOption:
303  case SecurityType.IndexOption:
304  return ParseOption(config, stream, date);
305 
306  case SecurityType.Future:
307  return ParseFuture(config, stream, date);
308 
309  }
310  }
311  catch (Exception err)
312  {
313  Log.Error(Invariant($"QuoteBar.Reader(): Error parsing stream, Symbol: {config.Symbol.Value}, SecurityType: {config.SecurityType}, ") +
314  Invariant($"Resolution: {config.Resolution}, Date: {date.ToStringInvariant("yyyy-MM-dd")}, Message: {err}")
315  );
316  }
317 
318  // we need to consume a line anyway, to advance the stream
319  stream.ReadLine();
320 
321  // if we couldn't parse it above return a default instance
322  return new QuoteBar { Symbol = config.Symbol, Period = config.Increment };
323  }
324 
325  /// <summary>
326  /// QuoteBar Reader: Fetch the data from the QC storage and feed it line by line into the engine.
327  /// </summary>
328  /// <param name="config">Symbols, Resolution, DataType, </param>
329  /// <param name="line">Line from the data file requested</param>
330  /// <param name="date">Date of this reader request</param>
331  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
332  /// <returns>Enumerable iterator for returning each line of the required data.</returns>
333  public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
334  {
335  try
336  {
337  switch (config.SecurityType)
338  {
339  case SecurityType.Equity:
340  return ParseEquity(config, line, date);
341 
342  case SecurityType.Forex:
343  case SecurityType.Crypto:
344  case SecurityType.CryptoFuture:
345  return ParseForex(config, line, date);
346 
347  case SecurityType.Cfd:
348  return ParseCfd(config, line, date);
349 
350  case SecurityType.Option:
351  case SecurityType.FutureOption:
352  case SecurityType.IndexOption:
353  return ParseOption(config, line, date);
354 
355  case SecurityType.Future:
356  return ParseFuture(config, line, date);
357 
358  }
359  }
360  catch (Exception err)
361  {
362  Log.Error(Invariant($"QuoteBar.Reader(): Error parsing line: '{line}', Symbol: {config.Symbol.Value}, SecurityType: {config.SecurityType}, ") +
363  Invariant($"Resolution: {config.Resolution}, Date: {date.ToStringInvariant("yyyy-MM-dd")}, Message: {err}")
364  );
365  }
366 
367  // if we couldn't parse it above return a default instance
368  return new QuoteBar { Symbol = config.Symbol, Period = config.Increment };
369  }
370 
371  private static bool HasShownWarning;
372 
373  /// <summary>
374  /// "Scaffold" code - If the data being read is formatted as a TradeBar, use this method to deserialize it
375  /// TODO: Once all Forex data refactored to use QuoteBar formatted data, remove this method
376  /// </summary>
377  /// <param name="config">Symbols, Resolution, DataType, </param>
378  /// <param name="line">Line from the data file requested</param>
379  /// <param name="date">Date of this reader request</param>
380  /// <returns><see cref="QuoteBar"/> with the bid/ask prices set to same values</returns>
381  [Obsolete("All Forex data should use Quotes instead of Trades.")]
382  private QuoteBar ParseTradeAsQuoteBar(SubscriptionDataConfig config, DateTime date, string line)
383  {
384  if (!HasShownWarning)
385  {
386  Logging.Log.Error("QuoteBar.ParseTradeAsQuoteBar(): Data formatted as Trade when Quote format was expected. Support for this will disappear June 2017.");
387  HasShownWarning = true;
388  }
389 
390  var quoteBar = new QuoteBar
391  {
392  Period = config.Increment,
393  Symbol = config.Symbol
394  };
395 
396  var csv = line.ToCsv(5);
397  if (config.Resolution == Resolution.Daily || config.Resolution == Resolution.Hour)
398  {
399  // hourly and daily have different time format, and can use slow, robust c# parser.
400  quoteBar.Time = DateTime.ParseExact(csv[0], DateFormat.TwelveCharacter, CultureInfo.InvariantCulture).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
401  }
402  else
403  {
404  //Fast decimal conversion
405  quoteBar.Time = date.Date.AddMilliseconds(csv[0].ToInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
406  }
407 
408  // the Bid/Ask bars were already create above, we don't need to recreate them but just set their values
409  quoteBar.Bid.Open = csv[1].ToDecimal();
410  quoteBar.Bid.High = csv[2].ToDecimal();
411  quoteBar.Bid.Low = csv[3].ToDecimal();
412  quoteBar.Bid.Close = csv[4].ToDecimal();
413 
414  quoteBar.Ask.Open = csv[1].ToDecimal();
415  quoteBar.Ask.High = csv[2].ToDecimal();
416  quoteBar.Ask.Low = csv[3].ToDecimal();
417  quoteBar.Ask.Close = csv[4].ToDecimal();
418 
419  quoteBar.Value = quoteBar.Close;
420 
421  return quoteBar;
422  }
423 
424  /// <summary>
425  /// Parse a quotebar representing a future with a scaling factor
426  /// </summary>
427  /// <param name="config">Symbols, Resolution, DataType</param>
428  /// <param name="line">Line from the data file requested</param>
429  /// <param name="date">Date of this reader request</param>
430  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
431  public QuoteBar ParseFuture(SubscriptionDataConfig config, string line, DateTime date)
432  {
433  return ParseQuote(config, date, line, false);
434  }
435 
436  /// <summary>
437  /// Parse a quotebar representing a future with a scaling factor
438  /// </summary>
439  /// <param name="config">Symbols, Resolution, DataType</param>
440  /// <param name="streamReader">The data stream of the requested file</param>
441  /// <param name="date">Date of this reader request</param>
442  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
443  public QuoteBar ParseFuture(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
444  {
445  return ParseQuote(config, date, streamReader, false);
446  }
447 
448  /// <summary>
449  /// Parse a quotebar representing an option with a scaling factor
450  /// </summary>
451  /// <param name="config">Symbols, Resolution, DataType</param>
452  /// <param name="line">Line from the data file requested</param>
453  /// <param name="date">Date of this reader request</param>
454  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
455  public QuoteBar ParseOption(SubscriptionDataConfig config, string line, DateTime date)
456  {
457  return ParseQuote(config, date, line, OptionUseScaleFactor(config.Symbol));
458  }
459 
460  /// <summary>
461  /// Parse a quotebar representing an option with a scaling factor
462  /// </summary>
463  /// <param name="config">Symbols, Resolution, DataType</param>
464  /// <param name="streamReader">The data stream of the requested file</param>
465  /// <param name="date">Date of this reader request</param>
466  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
467  public QuoteBar ParseOption(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
468  {
469  // scale factor only applies for equity and index options
470  return ParseQuote(config, date, streamReader, useScaleFactor: OptionUseScaleFactor(config.Symbol));
471  }
472 
473  /// <summary>
474  /// Helper method that defines the types of options that should use scale factor
475  /// </summary>
476  /// <param name="symbol"></param>
477  /// <returns></returns>
478  private static bool OptionUseScaleFactor(Symbol symbol)
479  {
480  return symbol.SecurityType == SecurityType.Option ||
481  symbol.SecurityType == SecurityType.IndexOption;
482  }
483 
484  /// <summary>
485  /// Parse a quotebar representing a cfd without a scaling factor
486  /// </summary>
487  /// <param name="config">Symbols, Resolution, DataType</param>
488  /// <param name="line">Line from the data file requested</param>
489  /// <param name="date">Date of this reader request</param>
490  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
491  public QuoteBar ParseCfd(SubscriptionDataConfig config, string line, DateTime date)
492  {
493  return ParseQuote(config, date, line, false);
494  }
495 
496  /// <summary>
497  /// Parse a quotebar representing a cfd without a scaling factor
498  /// </summary>
499  /// <param name="config">Symbols, Resolution, DataType</param>
500  /// <param name="streamReader">The data stream of the requested file</param>
501  /// <param name="date">Date of this reader request</param>
502  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
503  public QuoteBar ParseCfd(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
504  {
505  return ParseQuote(config, date, streamReader, false);
506  }
507 
508  /// <summary>
509  /// Parse a quotebar representing a forex without a scaling factor
510  /// </summary>
511  /// <param name="config">Symbols, Resolution, DataType</param>
512  /// <param name="line">Line from the data file requested</param>
513  /// <param name="date">Date of this reader request</param>
514  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
515  public QuoteBar ParseForex(SubscriptionDataConfig config, string line, DateTime date)
516  {
517  return ParseQuote(config, date, line, false);
518  }
519 
520  /// <summary>
521  /// Parse a quotebar representing a forex without a scaling factor
522  /// </summary>
523  /// <param name="config">Symbols, Resolution, DataType</param>
524  /// <param name="streamReader">The data stream of the requested file</param>
525  /// <param name="date">Date of this reader request</param>
526  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
527  public QuoteBar ParseForex(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
528  {
529  return ParseQuote(config, date, streamReader, false);
530  }
531 
532  /// <summary>
533  /// Parse a quotebar representing an equity with a scaling factor
534  /// </summary>
535  /// <param name="config">Symbols, Resolution, DataType</param>
536  /// <param name="line">Line from the data file requested</param>
537  /// <param name="date">Date of this reader request</param>
538  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
539  public QuoteBar ParseEquity(SubscriptionDataConfig config, string line, DateTime date)
540  {
541  return ParseQuote(config, date, line, true);
542  }
543 
544  /// <summary>
545  /// Parse a quotebar representing an equity with a scaling factor
546  /// </summary>
547  /// <param name="config">Symbols, Resolution, DataType</param>
548  /// <param name="streamReader">The data stream of the requested file</param>
549  /// <param name="date">Date of this reader request</param>
550  /// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
551  public QuoteBar ParseEquity(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
552  {
553  return ParseQuote(config, date, streamReader, true);
554  }
555 
556  /// <summary>
557  /// "Scaffold" code - If the data being read is formatted as a QuoteBar, use this method to deserialize it
558  /// </summary>
559  /// <param name="config">Symbols, Resolution, DataType, </param>
560  /// <param name="streamReader">The data stream of the requested file</param>
561  /// <param name="date">Date of this reader request</param>
562  /// <param name="useScaleFactor">Whether the data has a scaling factor applied</param>
563  /// <returns><see cref="QuoteBar"/> with the bid/ask prices set appropriately</returns>
564  private QuoteBar ParseQuote(SubscriptionDataConfig config, DateTime date, StreamReader streamReader, bool useScaleFactor)
565  {
566  // Non-equity asset classes will not use scaling, including options that have a non-equity underlying asset class.
567  var scaleFactor = useScaleFactor
568  ? _scaleFactor
569  : 1;
570 
571  var quoteBar = new QuoteBar
572  {
573  Period = config.Increment,
574  Symbol = config.Symbol
575  };
576 
577  if (config.Resolution == Resolution.Daily || config.Resolution == Resolution.Hour)
578  {
579  // hourly and daily have different time format, and can use slow, robust c# parser.
580  quoteBar.Time = streamReader.GetDateTime().ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
581  }
582  else
583  {
584  // Using custom int conversion for speed on high resolution data.
585  quoteBar.Time = date.Date.AddMilliseconds(streamReader.GetInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
586  }
587 
588  var open = streamReader.GetDecimal();
589  var high = streamReader.GetDecimal();
590  var low = streamReader.GetDecimal();
591  var close = streamReader.GetDecimal();
592  var lastSize = streamReader.GetDecimal();
593  // only create the bid if it exists in the file
594  if (open != 0 || high != 0 || low != 0 || close != 0)
595  {
596  // the Bid/Ask bars were already create above, we don't need to recreate them but just set their values
597  quoteBar.Bid.Open = open * scaleFactor;
598  quoteBar.Bid.High = high * scaleFactor;
599  quoteBar.Bid.Low = low * scaleFactor;
600  quoteBar.Bid.Close = close * scaleFactor;
601  quoteBar.LastBidSize = lastSize;
602  }
603  else
604  {
605  quoteBar.Bid = null;
606  }
607 
608  open = streamReader.GetDecimal();
609  high = streamReader.GetDecimal();
610  low = streamReader.GetDecimal();
611  close = streamReader.GetDecimal();
612  lastSize = streamReader.GetDecimal();
613  // only create the ask if it exists in the file
614  if (open != 0 || high != 0 || low != 0 || close != 0)
615  {
616  // the Bid/Ask bars were already create above, we don't need to recreate them but just set their values
617  quoteBar.Ask.Open = open * scaleFactor;
618  quoteBar.Ask.High = high * scaleFactor;
619  quoteBar.Ask.Low = low * scaleFactor;
620  quoteBar.Ask.Close = close * scaleFactor;
621  quoteBar.LastAskSize = lastSize;
622  }
623  else
624  {
625  quoteBar.Ask = null;
626  }
627 
628  quoteBar.Value = quoteBar.Close;
629 
630  return quoteBar;
631  }
632 
633  /// <summary>
634  /// "Scaffold" code - If the data being read is formatted as a QuoteBar, use this method to deserialize it
635  /// TODO: Once all Forex data refactored to use QuoteBar formatted data, use only this method
636  /// </summary>
637  /// <param name="config">Symbols, Resolution, DataType, </param>
638  /// <param name="line">Line from the data file requested</param>
639  /// <param name="date">Date of this reader request</param>
640  /// <param name="useScaleFactor">Whether the data has a scaling factor applied</param>
641  /// <returns><see cref="QuoteBar"/> with the bid/ask prices set appropriately</returns>
642  private QuoteBar ParseQuote(SubscriptionDataConfig config, DateTime date, string line, bool useScaleFactor)
643  {
644  var scaleFactor = useScaleFactor
645  ? _scaleFactor
646  : 1;
647 
648  var quoteBar = new QuoteBar
649  {
650  Period = config.Increment,
651  Symbol = config.Symbol
652  };
653 
654  var csv = line.ToCsv(10);
655  if (config.Resolution == Resolution.Daily || config.Resolution == Resolution.Hour)
656  {
657  // hourly and daily have different time format, and can use slow, robust c# parser.
658  quoteBar.Time = DateTime.ParseExact(csv[0], DateFormat.TwelveCharacter, CultureInfo.InvariantCulture).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
659  }
660  else
661  {
662  // Using custom "ToDecimal" conversion for speed on high resolution data.
663  quoteBar.Time = date.Date.AddMilliseconds((double)csv[0].ToDecimal()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
664  }
665 
666  // only create the bid if it exists in the file
667  if (csv[1].Length != 0 || csv[2].Length != 0 || csv[3].Length != 0 || csv[4].Length != 0)
668  {
669  // the Bid/Ask bars were already create above, we don't need to recreate them but just set their values
670  quoteBar.Bid.Open = csv[1].ToDecimal() * scaleFactor;
671  quoteBar.Bid.High = csv[2].ToDecimal() * scaleFactor;
672  quoteBar.Bid.Low = csv[3].ToDecimal() * scaleFactor;
673  quoteBar.Bid.Close = csv[4].ToDecimal() * scaleFactor;
674  quoteBar.LastBidSize = csv[5].ToDecimal();
675  }
676  else
677  {
678  quoteBar.Bid = null;
679  }
680 
681  // only create the ask if it exists in the file
682  if (csv[6].Length != 0 || csv[7].Length != 0 || csv[8].Length != 0 || csv[9].Length != 0)
683  {
684  // the Bid/Ask bars were already create above, we don't need to recreate them but just set their values
685  quoteBar.Ask.Open = csv[6].ToDecimal() * scaleFactor;
686  quoteBar.Ask.High = csv[7].ToDecimal() * scaleFactor;
687  quoteBar.Ask.Low = csv[8].ToDecimal() * scaleFactor;
688  quoteBar.Ask.Close = csv[9].ToDecimal() * scaleFactor;
689  quoteBar.LastAskSize = csv[10].ToDecimal();
690  }
691  else
692  {
693  quoteBar.Ask = null;
694  }
695 
696  quoteBar.Value = quoteBar.Close;
697 
698  return quoteBar;
699  }
700 
701  /// <summary>
702  /// Get Source for Custom Data File
703  /// >> What source file location would you prefer for each type of usage:
704  /// </summary>
705  /// <param name="config">Configuration object</param>
706  /// <param name="date">Date of this source request if source spread across multiple files</param>
707  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
708  /// <returns>String source location of the file</returns>
709  public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
710  {
711  if (isLiveMode)
712  {
713  // this data type is streamed in live mode
714  return new SubscriptionDataSource(string.Empty, SubscriptionTransportMedium.Streaming);
715  }
716 
717  var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
718  if (config.SecurityType == SecurityType.Future || config.SecurityType.IsOption())
719  {
720  source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
721  }
722  return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
723  }
724 
725  /// <summary>
726  /// Return a new instance clone of this quote bar, used in fill forward
727  /// </summary>
728  /// <returns>A clone of the current quote bar</returns>
729  public override BaseData Clone()
730  {
731  return new QuoteBar
732  {
733  Ask = Ask == null ? null : Ask.Clone(),
734  Bid = Bid == null ? null : Bid.Clone(),
737  Symbol = Symbol,
738  Time = Time,
739  Period = Period,
740  Value = Value,
742  };
743  }
744 
745  /// <summary>
746  /// Collapses QuoteBars into TradeBars object when
747  /// algorithm requires FX data, but calls OnData(<see cref="TradeBars"/>)
748  /// TODO: (2017) Remove this method in favor of using OnData(<see cref="Slice"/>)
749  /// </summary>
750  /// <returns><see cref="TradeBars"/></returns>
752  {
753  return new TradeBar(Time, Symbol, Open, High, Low, Close, 0, Period);
754  }
755 
756  /// <summary>
757  /// Convert this <see cref="QuoteBar"/> to string form.
758  /// </summary>
759  /// <returns>String representation of the <see cref="QuoteBar"/></returns>
760  public override string ToString()
761  {
762  return $"{Symbol}: " +
763  $"Bid: O: {Bid?.Open.SmartRounding()} " +
764  $"Bid: H: {Bid?.High.SmartRounding()} " +
765  $"Bid: L: {Bid?.Low.SmartRounding()} " +
766  $"Bid: C: {Bid?.Close.SmartRounding()} " +
767  $"Ask: O: {Ask?.Open.SmartRounding()} " +
768  $"Ask: H: {Ask?.High.SmartRounding()} " +
769  $"Ask: L: {Ask?.Low.SmartRounding()} " +
770  $"Ask: C: {Ask?.Close.SmartRounding()} ";
771  }
772  }
773 }