/*
* 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 : IDataConsolidator
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; }
///
/// Event handler type for the IDataConsolidator.DataConsolidated event
///
protected DataConsolidatedHandler DataConsolidatedHandler { get; set; }
///
/// Bar being created
///
protected virtual T CurrentBar { get; set; }
///
/// 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; protected set; }
///
/// Gets a clone of the data being currently consolidated
///
public abstract IBaseData WorkingData { get; }
///
/// Gets the type consumed by this consolidator
///
public Type InputType => typeof(IBaseData);
///
/// Gets which is the type emitted in the event.
///
public virtual Type OutputType => typeof(T);
///
/// 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.
///
/// 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.TryConvertToDelegate(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 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);
///
/// 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(T consolidated)
{
DataConsolidated?.Invoke(this, consolidated);
DataConsolidatedHandler?.Invoke(this, consolidated);
Consolidated = consolidated;
}
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// 2
public virtual void Dispose()
{
DataConsolidated = null;
DataConsolidatedHandler = null;
}
///
/// Resets the consolidator
///
public virtual void Reset()
{
Consolidated = null;
CurrentBar = default(T);
}
///
/// 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)
{
}
}
}