/* * 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 Python.Runtime; using QuantConnect.Data.Market; using QuantConnect.Securities; using System; namespace QuantConnect.Data.Consolidators { /// /// This consolidator can transform a stream of instances into a stream of /// public class RangeConsolidator : BaseTimelessConsolidator { private bool _firstTick; private decimal _minimumPriceVariation; /// /// Symbol properties database to use to get the minimum price variation of certain symbol /// private static SymbolPropertiesDatabase _symbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder(); /// /// Bar being created /// protected override RangeBar CurrentBar { get; set; } /// /// Range for each RangeBar, this is, the difference between the High and Low for each /// RangeBar /// public decimal RangeSize { get; private set; } /// /// Number of MinimumPriceVariation units /// public int Range { get; private set; } /// /// Gets which is the type emitted in the event. /// public override Type OutputType => typeof(RangeBar); /// /// Gets a clone of the data being currently consolidated /// public override IBaseData WorkingData => CurrentBar?.Clone(); /// /// Initializes a new instance of the class. /// /// The Range interval sets the range in which the price moves, which in turn initiates the formation of a new bar. /// One range equals to one minimum price change, where this last value is defined depending of the RangeBar's symbol /// Extracts the value from a data instance to be formed into a . The default /// value is (x => x.Value) the property on /// Extracts the volume from a data instance. The default value is null which does /// not aggregate volume per bar, except if the input is a TradeBar. public RangeConsolidator( int range, Func selector = null, Func volumeSelector = null) : base(selector, volumeSelector) { Range = range; _firstTick = true; } /// /// Initializes a new instance of the class. /// /// The Range interval sets the range in which the price moves, which in turn initiates the formation of a new bar. /// One range equals to one minimum price change, where this last value is defined depending of the RangeBar's symbol /// Extracts the value from a data instance to be formed into a . The default /// value is (x => x.Value) the property on /// Extracts the volume from a data instance. The default value is null which does /// not aggregate volume per bar. public RangeConsolidator(int range, PyObject selector, PyObject volumeSelector = null) : base(selector, volumeSelector) { Range = range; _firstTick = true; } /// /// Resets the consolidator /// public override void Reset() { base.Reset(); _firstTick = true; _minimumPriceVariation = 0m; RangeSize = 0m; } /// /// Updates the current RangeBar being created with the given data. /// Additionally, if it's the case, it consolidates the current RangeBar /// /// Time of the given data /// Value of the given data /// Volume of the given data protected override void UpdateBar(DateTime time, decimal currentValue, decimal volume) { bool isRising = default; if (currentValue > CurrentBar.High) { isRising = true; } else if (currentValue < CurrentBar.Low) { isRising = false; } CurrentBar.Update(time, currentValue, volume); while (CurrentBar.IsClosed) { OnDataConsolidated(CurrentBar); CurrentBar = new RangeBar(CurrentBar.Symbol, CurrentBar.EndTime, RangeSize, isRising ? CurrentBar.High + _minimumPriceVariation : CurrentBar.Low - _minimumPriceVariation); CurrentBar.Update(time, currentValue, Math.Abs(CurrentBar.Low - currentValue) > RangeSize ? 0 : volume); // Intermediate/phantom RangeBar's have zero volume } } /// /// Creates a new bar with the given data /// /// The new data for the bar /// The new value for the bar /// The new volume for the bar protected override void CreateNewBar(IBaseData data, decimal currentValue, decimal volume) { var open = currentValue; if (_firstTick) { _minimumPriceVariation = _symbolPropertiesDatabase.GetSymbolProperties(data.Symbol.ID.Market, data.Symbol, data.Symbol.ID.SecurityType, "USD").MinimumPriceVariation; RangeSize = _minimumPriceVariation * Range; open = Math.Ceiling(open / RangeSize) * RangeSize; _firstTick = false; } CurrentBar = new RangeBar(data.Symbol, data.Time, RangeSize, open, volume: volume); } } }