Lean  $LEAN_TAG$
CompositeIndicator.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using Python.Runtime;
18 using QuantConnect.Data;
20 
22 {
23  /// <summary>
24  /// This indicator is capable of wiring up two separate indicators into a single indicator
25  /// such that the output of each will be sent to a user specified function.
26  /// </summary>
27  /// <remarks>
28  /// This type is initialized such that there is no need to call the Update function. This indicator
29  /// will have its values automatically updated each time a new piece of data is received from both
30  /// the left and right indicators.
31  /// </remarks>
32  public class CompositeIndicator : IndicatorBase<IndicatorDataPoint>
33  {
34  /// <summary>
35  /// Delegate type used to compose the output of two indicators into a new value.
36  /// </summary>
37  /// <remarks>
38  /// A simple example would be to compute the difference between the two indicators (such as with MACD)
39  /// (left, right) => left - right
40  /// </remarks>
41  /// <param name="left">The left indicator</param>
42  /// <param name="right">The right indicator</param>
43  /// <returns>And indicator result representing the composition of the two indicators</returns>
44  public delegate IndicatorResult IndicatorComposer(IndicatorBase left, IndicatorBase right);
45 
46  /// <summary>function used to compose the individual indicators</summary>
47  private readonly IndicatorComposer _composer;
48 
49  /// <summary>
50  /// Gets the 'left' indicator for the delegate
51  /// </summary>
52  public IndicatorBase Left { get; private set; }
53 
54  /// <summary>
55  /// Gets the 'right' indicator for the delegate
56  /// </summary>
57  public IndicatorBase Right { get; private set; }
58 
59  /// <summary>
60  /// Gets a flag indicating when this indicator is ready and fully initialized
61  /// </summary>
62  public override bool IsReady
63  {
64  get { return Left.IsReady && Right.IsReady; }
65  }
66 
67  /// <summary>
68  /// Resets this indicator to its initial state
69  /// </summary>
70  public override void Reset()
71  {
72  Left.Reset();
73  Right.Reset();
74  base.Reset();
75  }
76 
77  /// <summary>
78  /// Creates a new CompositeIndicator capable of taking the output from the left and right indicators
79  /// and producing a new value via the composer delegate specified
80  /// </summary>
81  /// <param name="name">The name of this indicator</param>
82  /// <param name="left">The left indicator for the 'composer'</param>
83  /// <param name="right">The right indicator for the 'composer'</param>
84  /// <param name="composer">Function used to compose the left and right indicators</param>
85  public CompositeIndicator(string name, IndicatorBase left, IndicatorBase right, IndicatorComposer composer)
86  : base(name)
87  {
88  _composer = composer;
89  Left = left;
90  Right = right;
91  Name ??= $"COMPOSE({Left.Name},{Right.Name})";
92  ConfigureEventHandlers();
93  }
94 
95  /// <summary>
96  /// Creates a new CompositeIndicator capable of taking the output from the left and right indicators
97  /// and producing a new value via the composer delegate specified
98  /// </summary>
99  /// <param name="left">The left indicator for the 'composer'</param>
100  /// <param name="right">The right indicator for the 'composer'</param>
101  /// <param name="composer">Function used to compose the left and right indicators</param>
103  : this(null, left, right, composer)
104  { }
105 
106  /// <summary>
107  /// Initializes a new instance of <see cref="CompositeIndicator"/> using two indicators
108  /// and a custom function.
109  /// </summary>
110  /// <param name="name">The name of the composite indicator.</param>
111  /// <param name="left">The first indicator in the composition.</param>
112  /// <param name="right">The second indicator in the composition.</param>
113  /// <param name="handler">A Python function that processes the indicator values.</param>
114  /// <exception cref="ArgumentException">
115  /// Thrown if the provided left or right indicator is not a valid QuantConnect Indicator object.
116  /// </exception>
117  public CompositeIndicator(string name, PyObject left, PyObject right, PyObject handler)
118  : this(
119  name,
120  (IndicatorBase)left.GetIndicatorAsManagedObject(),
121  (IndicatorBase)right.GetIndicatorAsManagedObject(),
122  new IndicatorComposer(handler.ConvertToDelegate<Func<IndicatorBase, IndicatorBase, IndicatorResult>>())
123  )
124  {
125  }
126 
127  /// <summary>
128  /// Initializes a new instance of <see cref="CompositeIndicator"/> using two indicators
129  /// and a custom function.
130  /// </summary>
131  /// <param name="left">The first indicator in the composition.</param>
132  /// <param name="right">The second indicator in the composition.</param>
133  /// <param name="handler">A Python function that processes the indicator values.</param>
134  public CompositeIndicator(PyObject left, PyObject right, PyObject handler)
135  : this(null, left, right, handler)
136  { }
137 
138  /// <summary>
139  /// Computes the next value of this indicator from the given state
140  /// and returns an instance of the <see cref="IndicatorResult"/> class
141  /// </summary>
142  /// <param name="input">The input given to the indicator</param>
143  /// <returns>An IndicatorResult object including the status of the indicator</returns>
145  {
146  return _composer.Invoke(Left, Right);
147  }
148 
149  /// <summary>
150  /// Computes the next value of this indicator from the given state
151  /// </summary>
152  /// <remarks>
153  /// Since this class overrides <see cref="ValidateAndComputeNextValue"/>, this method is a no-op
154  /// </remarks>
155  /// <param name="_">The input given to the indicator</param>
156  /// <returns>A new value for this indicator</returns>
157  protected override decimal ComputeNextValue(IndicatorDataPoint _)
158  {
159  // this should never actually be invoked
160  return _composer.Invoke(Left, Right).Value;
161  }
162 
163  /// <summary>
164  /// Configures the event handlers for Left.Updated and Right.Updated to update this instance when
165  /// they both have new data.
166  /// </summary>
167  private void ConfigureEventHandlers()
168  {
169  // if either of these are constants then there's no reason
170  bool leftIsConstant = Left.GetType().IsSubclassOfGeneric(typeof(ConstantIndicator<>));
171  bool rightIsConstant = Right.GetType().IsSubclassOfGeneric(typeof(ConstantIndicator<>));
172 
173  // wire up the Updated events such that when we get a new piece of data from both left and right
174  // we'll call update on this indicator. It's important to note that the CompositeIndicator only uses
175  // the timestamp that gets passed into the Update function, his compuation is soley a function
176  // of the left and right indicator via '_composer'
177 
178  IndicatorDataPoint newLeftData = null;
179  IndicatorDataPoint newRightData = null;
180  Left.Updated += (sender, updated) =>
181  {
182  newLeftData = updated;
183 
184  // if we have left and right data (or if right is a constant) then we need to update
185  if (newRightData != null || rightIsConstant)
186  {
187  var dataPoint = new IndicatorDataPoint { Time = MaxTime(updated) };
188  Update(dataPoint);
189  // reset these to null after each update
190  newLeftData = null;
191  newRightData = null;
192  }
193  };
194 
195  Right.Updated += (sender, updated) =>
196  {
197  newRightData = updated;
198 
199  // if we have left and right data (or if left is a constant) then we need to update
200  if (newLeftData != null || leftIsConstant)
201  {
202  var dataPoint = new IndicatorDataPoint { Time = MaxTime(updated) };
203  Update(dataPoint);
204  // reset these to null after each update
205  newLeftData = null;
206  newRightData = null;
207  }
208  };
209  }
210 
211  private DateTime MaxTime(IndicatorDataPoint updated)
212  {
213  return new DateTime(Math.Max(updated.Time.Ticks, Math.Max(Right.Current.Time.Ticks, Left.Current.Time.Ticks)));
214  }
215  }
216 }