/* * 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 System; using QuantConnect.Data.Market; namespace QuantConnect.Data.Consolidators { /// /// This consolidator can transform a stream of instances into a stream of /// with Renko type . /// /// This implementation replaced the original implementation that was shown to have inaccuracies in its representation /// of Renko charts. The original implementation has been moved to . public class RenkoConsolidator : ConsolidatorBase { private bool _firstTick = true; private RenkoBar _lastWicko; private RenkoBar _currentBar; /// /// Time of consolidated close. /// /// Protected for testing protected DateTime CloseOn { get; set; } /// /// Value of consolidated close. /// /// Protected for testing protected decimal CloseRate { get; set; } /// /// Value of consolidated high. /// /// Protected for testing protected decimal HighRate { get; set; } /// /// Value of consolidated low. /// /// Protected for testing protected decimal LowRate { get; set; } /// /// Time of consolidated open. /// /// Protected for testing protected DateTime OpenOn { get; set; } /// /// Value of consolidate open. /// /// Protected for testing protected decimal OpenRate { get; set; } /// /// Size of the consolidated bar. /// /// Protected for testing protected decimal BarSize { get; set; } /// /// Gets the kind of the bar /// public RenkoType Type => RenkoType.Wicked; /// /// Gets a clone of the data being currently consolidated /// public override IBaseData WorkingData => _currentBar?.Clone(); /// /// Gets the type consumed by this consolidator /// public override Type InputType => typeof(IBaseData); /// /// Gets which is the type emitted in the event. /// public override Type OutputType => typeof(RenkoBar); /// /// Typed event handler that fires when a new piece of data is produced /// public new event EventHandler DataConsolidated; /// /// Initializes a new instance of the class using the specified . /// /// The constant value size of each bar public RenkoConsolidator(decimal barSize) { if (barSize <= 0) { throw new ArgumentException("Renko consolidator BarSize must be strictly greater than zero"); } BarSize = barSize; } /// /// Updates this consolidator with the specified data /// /// The new data for the consolidator public override void Update(IBaseData data) { var rate = data.Price; if (_firstTick) { _firstTick = false; // Round our first rate to the same length as BarSize rate = GetClosestMultiple(rate, BarSize); OpenOn = data.Time; CloseOn = data.Time; OpenRate = rate; HighRate = rate; LowRate = rate; CloseRate = rate; } else { CloseOn = data.Time; if (rate > HighRate) { HighRate = rate; } if (rate < LowRate) { LowRate = rate; } CloseRate = rate; if (CloseRate > OpenRate) { if (_lastWicko == null || _lastWicko.Direction == BarDirection.Rising) { Rising(data); return; } var limit = _lastWicko.Open + BarSize; if (CloseRate > limit) { var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, _lastWicko.Open, limit, LowRate, limit); _lastWicko = wicko; OnDataConsolidated(wicko); OpenOn = CloseOn; OpenRate = limit; LowRate = limit; Rising(data); } } else if (CloseRate < OpenRate) { if (_lastWicko == null || _lastWicko.Direction == BarDirection.Falling) { Falling(data); return; } var limit = _lastWicko.Open - BarSize; if (CloseRate < limit) { var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, _lastWicko.Open, HighRate, limit, limit); _lastWicko = wicko; OnDataConsolidated(wicko); OpenOn = CloseOn; OpenRate = limit; HighRate = limit; Falling(data); } } } } /// /// Scans this consolidator to see if it should emit a bar due to time passing /// /// The current time in the local time zone (same as ) public override void Scan(DateTime currentLocalTime) { } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// 2 public override void Dispose() { DataConsolidated = null; base.Dispose(); } /// /// Resets the consolidator /// public override void Reset() { _firstTick = true; _lastWicko = null; _currentBar = null; CloseOn = default; CloseRate = default; HighRate = default; LowRate = default; OpenOn = default; OpenRate = default; base.Reset(); } /// /// Raises the strongly typed DataConsolidated event /// /// The newly consolidated data protected override void FireDataConsolidated(IBaseData consolidated) { var bar = (RenkoBar)consolidated; // fire the typed event before updating the current bar so handlers reading // WorkingData still see the previous bar, as they did before the rolling window DataConsolidated?.Invoke(this, bar); _currentBar = bar; } private void Rising(IBaseData data) { decimal limit; while (CloseRate > (limit = OpenRate + BarSize)) { var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, limit, LowRate, limit); _lastWicko = wicko; OnDataConsolidated(wicko); OpenOn = CloseOn; OpenRate = limit; LowRate = limit; } } private void Falling(IBaseData data) { decimal limit; while (CloseRate < (limit = OpenRate - BarSize)) { var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, HighRate, limit, limit); _lastWicko = wicko; OnDataConsolidated(wicko); OpenOn = CloseOn; OpenRate = limit; HighRate = limit; } } /// /// Gets the closest BarSize-Multiple to the price. /// /// Based on: The Art of Computer Programming, Vol I, pag 39. Donald E. Knuth /// Price to be rounded to the closest BarSize-Multiple /// The size of the Renko bar /// The closest BarSize-Multiple to the price public static decimal GetClosestMultiple(decimal price, decimal barSize) { if (barSize <= 0) { throw new ArgumentException("BarSize must be strictly greater than zero"); } var modulus = price - barSize * Math.Floor(price / barSize); var round = Math.Round(modulus / barSize); return barSize * (Math.Floor(price / barSize) + round); } } /// /// 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 /// /// public class RenkoConsolidator : RenkoConsolidator where TInput : IBaseData { /// /// Initializes a new instance of the class using the specified . /// /// The constant value size of each bar public RenkoConsolidator(decimal barSize) : base(barSize) { } /// /// Updates this consolidator with the specified data. /// /// /// Type safe shim method. /// /// The new data for the consolidator public void Update(TInput data) { base.Update(data); } } /// /// This consolidator can transform a stream of instances into a stream of /// with Renko type . /// /// /// For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator public class WickedRenkoConsolidator : RenkoConsolidator { /// /// Initializes a new instance of the class using the specified . /// /// The constant value size of each bar public WickedRenkoConsolidator(decimal barSize) : base(barSize) { } } /// /// This consolidator can transform a stream of instances into a stream of /// with Renko type . /// 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 /// /// /// For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator public class WickedRenkoConsolidator : RenkoConsolidator where T : IBaseData { /// /// Initializes a new instance of the class using the specified . /// /// The constant value size of each bar public WickedRenkoConsolidator(decimal barSize) : base(barSize) { } } }