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;
40 
41  /// <summary>
42  /// Value of consolidated close.
43  /// </summary>
44  /// <remarks>Protected for testing</remarks>
45  protected decimal CloseRate;
46 
47  /// <summary>
48  /// Value of consolidated high.
49  /// </summary>
50  /// <remarks>Protected for testing</remarks>
51  protected decimal HighRate;
52 
53  /// <summary>
54  /// Value of consolidated low.
55  /// </summary>
56  /// <remarks>Protected for testing</remarks>
57  protected decimal LowRate;
58 
59  /// <summary>
60  /// Time of consolidated open.
61  /// </summary>
62  /// <remarks>Protected for testing</remarks>
63  protected DateTime OpenOn;
64 
65  /// <summary>
66  /// Value of consolidate open.
67  /// </summary>
68  /// <remarks>Protected for testing</remarks>
69  protected decimal OpenRate;
70 
71  /// <summary>
72  /// Size of the consolidated bar.
73  /// </summary>
74  /// <remarks>Protected for testing</remarks>
75  protected decimal BarSize;
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  /// Event invocator for the DataConsolidated event. This should be invoked
246  /// by derived classes when they have consolidated a new piece of data.
247  /// </summary>
248  /// <param name="consolidated">The newly consolidated data</param>
249  protected void OnDataConsolidated(RenkoBar consolidated)
250  {
251  DataConsolidated?.Invoke(this, consolidated);
252  _currentBar = consolidated;
253  _dataConsolidatedHandler?.Invoke(this, consolidated);
254  Consolidated = consolidated;
255  }
256 
257  private void Rising(IBaseData data)
258  {
259  decimal limit;
260 
261  while (CloseRate > (limit = OpenRate + BarSize))
262  {
263  var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, limit, LowRate, limit);
264 
265  _lastWicko = wicko;
266 
267  OnDataConsolidated(wicko);
268 
269  OpenOn = CloseOn;
270  OpenRate = limit;
271  LowRate = limit;
272  }
273  }
274 
275  private void Falling(IBaseData data)
276  {
277  decimal limit;
278 
279  while (CloseRate < (limit = OpenRate - BarSize))
280  {
281  var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, HighRate, limit, limit);
282 
283  _lastWicko = wicko;
284 
285  OnDataConsolidated(wicko);
286 
287  OpenOn = CloseOn;
288  OpenRate = limit;
289  HighRate = limit;
290  }
291  }
292 
293  /// <summary>
294  /// Gets the closest BarSize-Multiple to the price.
295  /// </summary>
296  /// <remarks>Based on: The Art of Computer Programming, Vol I, pag 39. Donald E. Knuth</remarks>
297  /// <param name="price">Price to be rounded to the closest BarSize-Multiple</param>
298  /// <param name="barSize">The size of the Renko bar</param>
299  /// <returns>The closest BarSize-Multiple to the price</returns>
300  public static decimal GetClosestMultiple(decimal price, decimal barSize)
301  {
302  if (barSize <= 0)
303  {
304  throw new ArgumentException("BarSize must be strictly greater than zero");
305  }
306 
307  var modulus = price - barSize * Math.Floor(price / barSize);
308  var round = Math.Round(modulus / barSize);
309  return barSize * (Math.Floor(price / barSize) + round);
310  }
311  }
312 
313  /// <summary>
314  /// 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
315  /// </summary>
316  /// <typeparam name="TInput"></typeparam>
317  public class RenkoConsolidator<TInput> : RenkoConsolidator
318  where TInput : IBaseData
319  {
320  /// <summary>
321  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
322  /// </summary>
323  /// <param name="barSize">The constant value size of each bar</param>
324  public RenkoConsolidator(decimal barSize)
325  : base(barSize)
326  {
327  }
328 
329  /// <summary>
330  /// Updates this consolidator with the specified data.
331  /// </summary>
332  /// <remarks>
333  /// Type safe shim method.
334  /// </remarks>
335  /// <param name="data">The new data for the consolidator</param>
336  public void Update(TInput data)
337  {
338  base.Update(data);
339  }
340  }
341 
342  /// <summary>
343  /// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
344  /// with Renko type <see cref="RenkoType.Wicked"/>.
345  /// /// </summary>
346  /// <remarks>For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator</remarks>
348  {
349  /// <summary>
350  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
351  /// </summary>
352  /// <param name="barSize">The constant value size of each bar</param>
353  public WickedRenkoConsolidator(decimal barSize)
354  : base(barSize)
355  {
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  /// 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
363  /// /// </summary>
364  /// <remarks>For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator</remarks>
366  where T : IBaseData
367  {
368  /// <summary>
369  /// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
370  /// </summary>
371  /// <param name="barSize">The constant value size of each bar</param>
372  public WickedRenkoConsolidator(decimal barSize)
373  : base(barSize)
374  {
375  }
376  }
377 }