Lean  $LEAN_TAG$
RenkoConsolidator.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;
18 
20 {
21  /// <summary>
22  /// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
23  /// with Renko type <see cref="RenkoType.Wicked"/>.
24  /// </summary>
25  /// <remarks>This implementation replaced the original implementation that was shown to have inaccuracies in its representation
26  /// of Renko charts. The original implementation has been moved to <see cref="ClassicRenkoConsolidator"/>.</remarks>
28  {
29  private bool _firstTick = true;
30  private RenkoBar _lastWicko;
31  private DataConsolidatedHandler _dataConsolidatedHandler;
32  private RenkoBar _currentBar;
33  private IBaseData _consolidated;
34 
35  /// <summary>
36  /// Time of consolidated close.
37  /// </summary>
38  /// <remarks>Protected for testing</remarks>
39  protected DateTime CloseOn { get; set; }
40 
41  /// <summary>
42  /// Value of consolidated close.
43  /// </summary>
44  /// <remarks>Protected for testing</remarks>
45  protected decimal CloseRate { get; set; }
46 
47  /// <summary>
48  /// Value of consolidated high.
49  /// </summary>
50  /// <remarks>Protected for testing</remarks>
51  protected decimal HighRate { get; set; }
52 
53  /// <summary>
54  /// Value of consolidated low.
55  /// </summary>
56  /// <remarks>Protected for testing</remarks>
57  protected decimal LowRate { get; set; }
58 
59  /// <summary>
60  /// Time of consolidated open.
61  /// </summary>
62  /// <remarks>Protected for testing</remarks>
63  protected DateTime OpenOn { get; set; }
64 
65  /// <summary>
66  /// Value of consolidate open.
67  /// </summary>
68  /// <remarks>Protected for testing</remarks>
69  protected decimal OpenRate { get; set; }
70 
71  /// <summary>
72  /// Size of the consolidated bar.
73  /// </summary>
74  /// <remarks>Protected for testing</remarks>
75  protected decimal BarSize { get; set; }
76 
77  /// <summary>
78  /// Gets the kind of the bar
79  /// </summary>
80  public RenkoType Type => RenkoType.Wicked;
81 
82  /// <summary>
83  /// Gets a clone of the data being currently consolidated
84  /// </summary>
85  public IBaseData WorkingData => _currentBar?.Clone();
86 
87  /// <summary>
88  /// Gets the type consumed by this consolidator
89  /// </summary>
90  public Type InputType => typeof(IBaseData);
91 
92  /// <summary>
93  /// Gets <see cref="RenkoBar"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
94  /// </summary>
95  public Type OutputType => typeof(RenkoBar);
96 
97  /// <summary>
98  /// Gets the most recently consolidated piece of data. This will be null if this consolidator
99  /// has not produced any data yet.
100  /// </summary>
101  public IBaseData Consolidated
102  {
103  get { return _consolidated; }
104  private set { _consolidated = value; }
105  }
106 
107  /// <summary>
108  /// Event handler that fires when a new piece of data is produced
109  /// </summary>
110  public event EventHandler<RenkoBar> DataConsolidated;
111 
112  /// <summary>
113  /// Event handler that fires when a new piece of data is produced
114  /// </summary>
116  {
117  add { _dataConsolidatedHandler += value; }
118  remove { _dataConsolidatedHandler -= value; }
119  }
120 
121  /// <summary>
122  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
123  /// </summary>
124  /// <param name="barSize">The constant value size of each bar</param>
125  public RenkoConsolidator(decimal barSize)
126  {
127  if (barSize <= 0)
128  {
129  throw new ArgumentException("Renko consolidator BarSize must be strictly greater than zero");
130  }
131 
132  BarSize = barSize;
133  }
134 
135  /// <summary>
136  /// Updates this consolidator with the specified data
137  /// </summary>
138  /// <param name="data">The new data for the consolidator</param>
139  public void Update(IBaseData data)
140  {
141  var rate = data.Price;
142 
143  if (_firstTick)
144  {
145  _firstTick = false;
146 
147  // Round our first rate to the same length as BarSize
148  rate = GetClosestMultiple(rate, BarSize);
149 
150  OpenOn = data.Time;
151  CloseOn = data.Time;
152  OpenRate = rate;
153  HighRate = rate;
154  LowRate = rate;
155  CloseRate = rate;
156  }
157  else
158  {
159  CloseOn = data.Time;
160 
161  if (rate > HighRate)
162  {
163  HighRate = rate;
164  }
165 
166  if (rate < LowRate)
167  {
168  LowRate = rate;
169  }
170 
171  CloseRate = rate;
172 
173  if (CloseRate > OpenRate)
174  {
175  if (_lastWicko == null || _lastWicko.Direction == BarDirection.Rising)
176  {
177  Rising(data);
178  return;
179  }
180 
181  var limit = _lastWicko.Open + BarSize;
182 
183  if (CloseRate > limit)
184  {
185  var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, _lastWicko.Open, limit,
186  LowRate, limit);
187 
188  _lastWicko = wicko;
189 
190  OnDataConsolidated(wicko);
191 
192  OpenOn = CloseOn;
193  OpenRate = limit;
194  LowRate = limit;
195 
196  Rising(data);
197  }
198  }
199  else if (CloseRate < OpenRate)
200  {
201  if (_lastWicko == null || _lastWicko.Direction == BarDirection.Falling)
202  {
203  Falling(data);
204  return;
205  }
206 
207  var limit = _lastWicko.Open - BarSize;
208 
209  if (CloseRate < limit)
210  {
211  var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, _lastWicko.Open, HighRate,
212  limit, limit);
213 
214  _lastWicko = wicko;
215 
216  OnDataConsolidated(wicko);
217 
218  OpenOn = CloseOn;
219  OpenRate = limit;
220  HighRate = limit;
221 
222  Falling(data);
223  }
224  }
225  }
226  }
227 
228  /// <summary>
229  /// Scans this consolidator to see if it should emit a bar due to time passing
230  /// </summary>
231  /// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
232  public void Scan(DateTime currentLocalTime)
233  {
234  }
235 
236  /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
237  /// <filterpriority>2</filterpriority>
238  public void Dispose()
239  {
240  DataConsolidated = null;
241  _dataConsolidatedHandler = null;
242  }
243 
244  /// <summary>
245  /// Resets the consolidator
246  /// </summary>
247  public void Reset()
248  {
249  _firstTick = true;
250  _lastWicko = null;
251  _currentBar = null;
252  _consolidated = null;
253  CloseOn = default;
254  CloseRate = default;
255  HighRate = default;
256  LowRate = default;
257  OpenOn = default;
258  OpenRate = default;
259  }
260 
261  /// <summary>
262  /// Event invocator for the DataConsolidated event. This should be invoked
263  /// by derived classes when they have consolidated a new piece of data.
264  /// </summary>
265  /// <param name="consolidated">The newly consolidated data</param>
266  protected void OnDataConsolidated(RenkoBar consolidated)
267  {
268  DataConsolidated?.Invoke(this, consolidated);
269  _currentBar = consolidated;
270  _dataConsolidatedHandler?.Invoke(this, consolidated);
271  Consolidated = consolidated;
272  }
273 
274  private void Rising(IBaseData data)
275  {
276  decimal limit;
277 
278  while (CloseRate > (limit = OpenRate + BarSize))
279  {
280  var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, limit, LowRate, limit);
281 
282  _lastWicko = wicko;
283 
284  OnDataConsolidated(wicko);
285 
286  OpenOn = CloseOn;
287  OpenRate = limit;
288  LowRate = limit;
289  }
290  }
291 
292  private void Falling(IBaseData data)
293  {
294  decimal limit;
295 
296  while (CloseRate < (limit = OpenRate - BarSize))
297  {
298  var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, HighRate, limit, limit);
299 
300  _lastWicko = wicko;
301 
302  OnDataConsolidated(wicko);
303 
304  OpenOn = CloseOn;
305  OpenRate = limit;
306  HighRate = limit;
307  }
308  }
309 
310  /// <summary>
311  /// Gets the closest BarSize-Multiple to the price.
312  /// </summary>
313  /// <remarks>Based on: The Art of Computer Programming, Vol I, pag 39. Donald E. Knuth</remarks>
314  /// <param name="price">Price to be rounded to the closest BarSize-Multiple</param>
315  /// <param name="barSize">The size of the Renko bar</param>
316  /// <returns>The closest BarSize-Multiple to the price</returns>
317  public static decimal GetClosestMultiple(decimal price, decimal barSize)
318  {
319  if (barSize <= 0)
320  {
321  throw new ArgumentException("BarSize must be strictly greater than zero");
322  }
323 
324  var modulus = price - barSize * Math.Floor(price / barSize);
325  var round = Math.Round(modulus / barSize);
326  return barSize * (Math.Floor(price / barSize) + round);
327  }
328  }
329 
330  /// <summary>
331  /// Provides a type safe wrapper on the RenkoConsolidator class. This just allows us to define our selector functions with the real type they'll be receiving
332  /// </summary>
333  /// <typeparam name="TInput"></typeparam>
334  public class RenkoConsolidator<TInput> : RenkoConsolidator
335  where TInput : IBaseData
336  {
337  /// <summary>
338  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
339  /// </summary>
340  /// <param name="barSize">The constant value size of each bar</param>
341  public RenkoConsolidator(decimal barSize)
342  : base(barSize)
343  {
344  }
345 
346  /// <summary>
347  /// Updates this consolidator with the specified data.
348  /// </summary>
349  /// <remarks>
350  /// Type safe shim method.
351  /// </remarks>
352  /// <param name="data">The new data for the consolidator</param>
353  public void Update(TInput data)
354  {
355  base.Update(data);
356  }
357  }
358 
359  /// <summary>
360  /// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
361  /// with Renko type <see cref="RenkoType.Wicked"/>.
362  /// /// </summary>
363  /// <remarks>For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator</remarks>
365  {
366  /// <summary>
367  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
368  /// </summary>
369  /// <param name="barSize">The constant value size of each bar</param>
370  public WickedRenkoConsolidator(decimal barSize)
371  : base(barSize)
372  {
373  }
374  }
375 
376  /// <summary>
377  /// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
378  /// with Renko type <see cref="RenkoType.Wicked"/>.
379  /// Provides a type safe wrapper on the WickedRenkoConsolidator class. This just allows us to define our selector functions with the real type they'll be receiving
380  /// /// </summary>
381  /// <remarks>For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator</remarks>
383  where T : IBaseData
384  {
385  /// <summary>
386  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
387  /// </summary>
388  /// <param name="barSize">The constant value size of each bar</param>
389  public WickedRenkoConsolidator(decimal barSize)
390  : base(barSize)
391  {
392  }
393  }
394 }