/*
* 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 Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Indicators
{
///
/// This indicator is capable of wiring up two separate indicators into a single indicator
/// such that the output of each will be sent to a user specified function.
///
///
/// This type is initialized such that there is no need to call the Update function. This indicator
/// will have its values automatically updated each time a new piece of data is received from both
/// the left and right indicators.
///
public class CompositeIndicator : IndicatorBase
{
///
/// Delegate type used to compose the output of two indicators into a new value.
///
///
/// A simple example would be to compute the difference between the two indicators (such as with MACD)
/// (left, right) => left - right
///
/// The left indicator
/// The right indicator
/// And indicator result representing the composition of the two indicators
public delegate IndicatorResult IndicatorComposer(IndicatorBase left, IndicatorBase right);
/// function used to compose the individual indicators
private readonly IndicatorComposer _composer;
///
/// Gets the 'left' indicator for the delegate
///
public IndicatorBase Left { get; private set; }
///
/// Gets the 'right' indicator for the delegate
///
public IndicatorBase Right { get; private set; }
///
/// Gets a flag indicating when this indicator is ready and fully initialized
///
public override bool IsReady
{
get { return Left.IsReady && Right.IsReady; }
}
///
/// Resets this indicator to its initial state
///
public override void Reset()
{
Left.Reset();
Right.Reset();
base.Reset();
}
///
/// Creates a new CompositeIndicator capable of taking the output from the left and right indicators
/// and producing a new value via the composer delegate specified
///
/// The name of this indicator
/// The left indicator for the 'composer'
/// The right indicator for the 'composer'
/// Function used to compose the left and right indicators
public CompositeIndicator(string name, IndicatorBase left, IndicatorBase right, IndicatorComposer composer)
: base(name)
{
_composer = composer;
Left = left;
Right = right;
Name ??= $"COMPOSE({Left.Name},{Right.Name})";
ConfigureEventHandlers();
}
///
/// Creates a new CompositeIndicator capable of taking the output from the left and right indicators
/// and producing a new value via the composer delegate specified
///
/// The left indicator for the 'composer'
/// The right indicator for the 'composer'
/// Function used to compose the left and right indicators
public CompositeIndicator(IndicatorBase left, IndicatorBase right, IndicatorComposer composer)
: this(null, left, right, composer)
{ }
///
/// Initializes a new instance of using two indicators
/// and a custom function.
///
/// The name of the composite indicator.
/// The first indicator in the composition.
/// The second indicator in the composition.
/// A Python function that processes the indicator values.
///
/// Thrown if the provided left or right indicator is not a valid QuantConnect Indicator object.
///
public CompositeIndicator(string name, PyObject left, PyObject right, PyObject handler)
: this(
name,
(IndicatorBase)left.GetIndicatorAsManagedObject(),
(IndicatorBase)right.GetIndicatorAsManagedObject(),
new IndicatorComposer(handler.ConvertToDelegate>())
)
{
}
///
/// Initializes a new instance of using two indicators
/// and a custom function.
///
/// The first indicator in the composition.
/// The second indicator in the composition.
/// A Python function that processes the indicator values.
public CompositeIndicator(PyObject left, PyObject right, PyObject handler)
: this(null, left, right, handler)
{ }
///
/// Computes the next value of this indicator from the given state
/// and returns an instance of the class
///
/// The input given to the indicator
/// An IndicatorResult object including the status of the indicator
protected override IndicatorResult ValidateAndComputeNextValue(IndicatorDataPoint input)
{
return _composer.Invoke(Left, Right);
}
///
/// Computes the next value of this indicator from the given state
///
///
/// Since this class overrides , this method is a no-op
///
/// The input given to the indicator
/// A new value for this indicator
protected override decimal ComputeNextValue(IndicatorDataPoint _)
{
// this should never actually be invoked
return _composer.Invoke(Left, Right).Value;
}
///
/// Configures the event handlers for Left.Updated and Right.Updated to update this instance when
/// they both have new data.
///
private void ConfigureEventHandlers()
{
// if either of these are constants then there's no reason
bool leftIsConstant = Left.GetType().IsSubclassOfGeneric(typeof(ConstantIndicator<>));
bool rightIsConstant = Right.GetType().IsSubclassOfGeneric(typeof(ConstantIndicator<>));
// wire up the Updated events such that when we get a new piece of data from both left and right
// we'll call update on this indicator. It's important to note that the CompositeIndicator only uses
// the timestamp that gets passed into the Update function, his compuation is soley a function
// of the left and right indicator via '_composer'
IndicatorDataPoint newLeftData = null;
IndicatorDataPoint newRightData = null;
Left.Updated += (sender, updated) =>
{
newLeftData = updated;
// if we have left and right data (or if right is a constant) then we need to update
if (newRightData != null || rightIsConstant)
{
var dataPoint = new IndicatorDataPoint { Time = MaxTime(updated) };
Update(dataPoint);
// reset these to null after each update
newLeftData = null;
newRightData = null;
}
};
Right.Updated += (sender, updated) =>
{
newRightData = updated;
// if we have left and right data (or if left is a constant) then we need to update
if (newLeftData != null || leftIsConstant)
{
var dataPoint = new IndicatorDataPoint { Time = MaxTime(updated) };
Update(dataPoint);
// reset these to null after each update
newLeftData = null;
newRightData = null;
}
};
}
private DateTime MaxTime(IndicatorDataPoint updated)
{
return new DateTime(Math.Max(updated.Time.Ticks, Math.Max(Right.Current.Time.Ticks, Left.Current.Time.Ticks)));
}
}
}