/* * 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 System; using QuantConnect.Data.Market; namespace QuantConnect.Data.Consolidators { /// /// Represents a timeless consolidator which depends on the given values. This consolidator /// is meant to consolidate data into bars that do not depend on time, e.g., RangeBar's. /// public abstract class BaseTimelessConsolidator : ConsolidatorBase where T : IBaseData { /// /// Extracts the value from a data instance to be formed into a . /// protected Func Selector { get; set; } /// /// Extracts the volume from a data instance. The default value is null which does /// not aggregate volume per bar. /// protected Func VolumeSelector { get; set; } /// /// Bar being created /// protected virtual T CurrentBar { get; set; } /// /// Gets a clone of the data being currently consolidated /// public abstract override IBaseData WorkingData { get; } /// /// 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(T); /// /// 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. /// /// Extracts the value from a data instance to be formed into a new bar which inherits from . 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. protected BaseTimelessConsolidator(Func selector = null, Func volumeSelector = null) { Selector = selector ?? (x => x.Value); VolumeSelector = volumeSelector ?? (x => x is TradeBar bar ? bar.Volume : (x is Tick tick ? tick.Quantity : 0)); } /// /// Initializes a new instance of the class. /// /// Extracts the value from a data instance to be formed into a new bar which inherits from . 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. protected BaseTimelessConsolidator(PyObject valueSelector, PyObject volumeSelector = null) : this (TryToConvertSelector(valueSelector, nameof(valueSelector)), TryToConvertSelector(volumeSelector, nameof(volumeSelector))) { } /// /// Tries to convert the given python selector to a C# one. If the conversion is not /// possible it returns null /// /// The python selector to be converted /// The name of the selector to be used in case an exception is thrown /// This exception will be thrown if it's not possible to convert the /// given python selector to C# private static Func TryToConvertSelector(PyObject selector, string selectorName) { using (Py.GIL()) { Func resultSelector; if (selector != null && !selector.IsNone()) { if (!selector.TrySafeAs(out resultSelector)) { throw new ArgumentException( $"Unable to convert parameter {selectorName} to delegate type Func"); } } else { resultSelector = null; } return resultSelector; } } /// /// Updates this consolidator with the specified data /// /// The new data for the consolidator public override void Update(IBaseData data) { var currentValue = Selector(data); var volume = VolumeSelector(data); // If we're already in a bar then update it if (CurrentBar != null) { UpdateBar(data.Time, currentValue, volume); } // The state of the CurrentBar could have changed after UpdateBar(), // then we might need to create a new bar if (CurrentBar == null) { CreateNewBar(data, currentValue, volume); } } /// /// 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 abstract void UpdateBar(DateTime time, decimal currentValue, decimal volume); /// /// Creates a new bar with the given data /// /// The new data for the bar /// The new value for the bar /// The new volume to the bar protected abstract void CreateNewBar(IBaseData data, decimal currentValue, decimal volume); /// /// Raises the strongly typed DataConsolidated event /// /// The newly consolidated data protected override void FireDataConsolidated(IBaseData consolidated) { DataConsolidated?.Invoke(this, (T)consolidated); } /// 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() { CurrentBar = default(T); base.Reset(); } /// /// 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) { } } }