217 lines
9.5 KiB
C#
217 lines
9.5 KiB
C#
/*
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public class CompositeIndicator : IndicatorBase<IndicatorDataPoint>
|
|
{
|
|
/// <summary>
|
|
/// Delegate type used to compose the output of two indicators into a new value.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// A simple example would be to compute the difference between the two indicators (such as with MACD)
|
|
/// (left, right) => left - right
|
|
/// </remarks>
|
|
/// <param name="left">The left indicator</param>
|
|
/// <param name="right">The right indicator</param>
|
|
/// <returns>And indicator result representing the composition of the two indicators</returns>
|
|
public delegate IndicatorResult IndicatorComposer(IndicatorBase left, IndicatorBase right);
|
|
|
|
/// <summary>function used to compose the individual indicators</summary>
|
|
private readonly IndicatorComposer _composer;
|
|
|
|
/// <summary>
|
|
/// Gets the 'left' indicator for the delegate
|
|
/// </summary>
|
|
public IndicatorBase Left { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the 'right' indicator for the delegate
|
|
/// </summary>
|
|
public IndicatorBase Right { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating when this indicator is ready and fully initialized
|
|
/// </summary>
|
|
public override bool IsReady
|
|
{
|
|
get { return Left.IsReady && Right.IsReady; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets this indicator to its initial state
|
|
/// </summary>
|
|
public override void Reset()
|
|
{
|
|
Left.Reset();
|
|
Right.Reset();
|
|
base.Reset();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="name">The name of this indicator</param>
|
|
/// <param name="left">The left indicator for the 'composer'</param>
|
|
/// <param name="right">The right indicator for the 'composer'</param>
|
|
/// <param name="composer">Function used to compose the left and right indicators</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="left">The left indicator for the 'composer'</param>
|
|
/// <param name="right">The right indicator for the 'composer'</param>
|
|
/// <param name="composer">Function used to compose the left and right indicators</param>
|
|
public CompositeIndicator(IndicatorBase left, IndicatorBase right, IndicatorComposer composer)
|
|
: this(null, left, right, composer)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="CompositeIndicator"/> using two indicators
|
|
/// and a custom function.
|
|
/// </summary>
|
|
/// <param name="name">The name of the composite indicator.</param>
|
|
/// <param name="left">The first indicator in the composition.</param>
|
|
/// <param name="right">The second indicator in the composition.</param>
|
|
/// <param name="handler">A Python function that processes the indicator values.</param>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown if the provided left or right indicator is not a valid QuantConnect Indicator object.
|
|
/// </exception>
|
|
public CompositeIndicator(string name, PyObject left, PyObject right, PyObject handler)
|
|
: this(
|
|
name,
|
|
(IndicatorBase)left.GetIndicatorAsManagedObject(),
|
|
(IndicatorBase)right.GetIndicatorAsManagedObject(),
|
|
new IndicatorComposer(handler.SafeAs<Func<IndicatorBase, IndicatorBase, IndicatorResult>>())
|
|
)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of <see cref="CompositeIndicator"/> using two indicators
|
|
/// and a custom function.
|
|
/// </summary>
|
|
/// <param name="left">The first indicator in the composition.</param>
|
|
/// <param name="right">The second indicator in the composition.</param>
|
|
/// <param name="handler">A Python function that processes the indicator values.</param>
|
|
public CompositeIndicator(PyObject left, PyObject right, PyObject handler)
|
|
: this(null, left, right, handler)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Computes the next value of this indicator from the given state
|
|
/// and returns an instance of the <see cref="IndicatorResult"/> class
|
|
/// </summary>
|
|
/// <param name="input">The input given to the indicator</param>
|
|
/// <returns>An IndicatorResult object including the status of the indicator</returns>
|
|
protected override IndicatorResult ValidateAndComputeNextValue(IndicatorDataPoint input)
|
|
{
|
|
return _composer.Invoke(Left, Right);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computes the next value of this indicator from the given state
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Since this class overrides <see cref="ValidateAndComputeNextValue"/>, this method is a no-op
|
|
/// </remarks>
|
|
/// <param name="_">The input given to the indicator</param>
|
|
/// <returns>A new value for this indicator</returns>
|
|
protected override decimal ComputeNextValue(IndicatorDataPoint _)
|
|
{
|
|
// this should never actually be invoked
|
|
return _composer.Invoke(Left, Right).Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures the event handlers for Left.Updated and Right.Updated to update this instance when
|
|
/// they both have new data.
|
|
/// </summary>
|
|
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)));
|
|
}
|
|
}
|
|
}
|