/* * 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 : IDataConsolidator { private bool _firstTick = true; private RenkoBar _lastWicko; private DataConsolidatedHandler _dataConsolidatedHandler; private RenkoBar _currentBar; private IBaseData _consolidated; /// /// 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 IBaseData WorkingData => _currentBar?.Clone(); /// /// Gets the type consumed by this consolidator /// public Type InputType => typeof(IBaseData); /// /// Gets which is the type emitted in the event. /// public Type OutputType => typeof(RenkoBar); /// /// Gets the most recently consolidated piece of data. This will be null if this consolidator /// has not produced any data yet. /// public IBaseData Consolidated { get { return _consolidated; } private set { _consolidated = value; } } /// /// Event handler that fires when a new piece of data is produced /// public event EventHandler DataConsolidated; /// /// Event handler that fires when a new piece of data is produced /// event DataConsolidatedHandler IDataConsolidator.DataConsolidated { add { _dataConsolidatedHandler += value; } remove { _dataConsolidatedHandler -= value; } } /// /// 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 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 void Scan(DateTime currentLocalTime) { } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// 2 public void Dispose() { DataConsolidated = null; _dataConsolidatedHandler = null; } /// /// Resets the consolidator /// public void Reset() { _firstTick = true; _lastWicko = null; _currentBar = null; _consolidated = null; CloseOn = default; CloseRate = default; HighRate = default; LowRate = default; OpenOn = default; OpenRate = default; } /// /// Event invocator for the DataConsolidated event. This should be invoked /// by derived classes when they have consolidated a new piece of data. /// /// The newly consolidated data protected void OnDataConsolidated(RenkoBar consolidated) { DataConsolidated?.Invoke(this, consolidated); _currentBar = consolidated; _dataConsolidatedHandler?.Invoke(this, consolidated); Consolidated = consolidated; } 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) { } } }