Lean  $LEAN_TAG$
BaseTimelessConsolidator.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 Python.Runtime;
17 using System;
19 
21 {
22  /// <summary>
23  /// Represents a timeless consolidator which depends on the given values. This consolidator
24  /// is meant to consolidate data into bars that do not depend on time, e.g., RangeBar's.
25  /// </summary>
26  public abstract class BaseTimelessConsolidator<T> : IDataConsolidator
27  where T : IBaseData
28  {
29  /// <summary>
30  /// Extracts the value from a data instance to be formed into a <see cref="T"/>.
31  /// </summary>
32  protected Func<IBaseData, decimal> Selector { get; set; }
33 
34  /// <summary>
35  /// Extracts the volume from a data instance. The default value is null which does
36  /// not aggregate volume per bar.
37  /// </summary>
38  protected Func<IBaseData, decimal> VolumeSelector { get; set; }
39 
40  /// <summary>
41  /// Event handler type for the IDataConsolidator.DataConsolidated event
42  /// </summary>
44 
45  /// <summary>
46  /// Bar being created
47  /// </summary>
48  protected virtual T CurrentBar { get; set; }
49 
50  /// <summary>
51  /// Gets the most recently consolidated piece of data. This will be null if this consolidator
52  /// has not produced any data yet.
53  /// </summary>
54  public IBaseData Consolidated { get; protected set; }
55 
56  /// <summary>
57  /// Gets a clone of the data being currently consolidated
58  /// </summary>
59  public abstract IBaseData WorkingData { get; }
60 
61  /// <summary>
62  /// Gets the type consumed by this consolidator
63  /// </summary>
64  public Type InputType => typeof(IBaseData);
65 
66  /// <summary>
67  /// Gets <see cref="T"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
68  /// </summary>
69  public virtual Type OutputType => typeof(T);
70 
71  /// <summary>
72  /// Event handler that fires when a new piece of data is produced
73  /// </summary>
74  public event EventHandler<T> DataConsolidated;
75 
76  /// <summary>
77  /// Event handler that fires when a new piece of data is produced
78  /// </summary>
80  {
81  add { DataConsolidatedHandler += value; }
82  remove { DataConsolidatedHandler -= value; }
83  }
84 
85  /// <summary>
86  /// Initializes a new instance of the <see cref="BaseTimelessConsolidator{T}" /> class.
87  /// </summary>
88  /// <param name="selector">Extracts the value from a data instance to be formed into a new bar which inherits from <see cref="IBaseData"/>. The default
89  /// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
90  /// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
91  /// not aggregate volume per bar.</param>
92  protected BaseTimelessConsolidator(Func<IBaseData, decimal> selector = null, Func<IBaseData, decimal> volumeSelector = null)
93  {
94  Selector = selector ?? (x => x.Value);
95  VolumeSelector = volumeSelector ?? (x => x is TradeBar bar ? bar.Volume : (x is Tick tick ? tick.Quantity : 0));
96  }
97 
98  /// <summary>
99  /// Initializes a new instance of the <see cref="BaseTimelessConsolidator{T}" /> class.
100  /// </summary>
101  /// <param name="valueSelector">Extracts the value from a data instance to be formed into a new bar which inherits from <see cref="IBaseData"/>. The default
102  /// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
103  /// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
104  /// not aggregate volume per bar.</param>
105  protected BaseTimelessConsolidator(PyObject valueSelector, PyObject volumeSelector = null)
106  : this (TryToConvertSelector(valueSelector, nameof(valueSelector)), TryToConvertSelector(volumeSelector, nameof(volumeSelector)))
107  {
108  }
109 
110  /// <summary>
111  /// Tries to convert the given python selector to a C# one. If the conversion is not
112  /// possible it returns null
113  /// </summary>
114  /// <param name="selector">The python selector to be converted</param>
115  /// <param name="selectorName">The name of the selector to be used in case an exception is thrown</param>
116  /// <exception cref="ArgumentException">This exception will be thrown if it's not possible to convert the
117  /// given python selector to C#</exception>
118  private static Func<IBaseData, decimal> TryToConvertSelector(PyObject selector, string selectorName)
119  {
120  using (Py.GIL())
121  {
122  Func<IBaseData, decimal> resultSelector;
123  if (selector != null && !selector.IsNone())
124  {
125  if (!selector.TryConvertToDelegate(out resultSelector))
126  {
127  throw new ArgumentException(
128  $"Unable to convert parameter {selectorName} to delegate type Func<IBaseData, decimal>");
129  }
130  }
131  else
132  {
133  resultSelector = null;
134  }
135 
136  return resultSelector;
137  }
138  }
139 
140  /// <summary>
141  /// Updates this consolidator with the specified data
142  /// </summary>
143  /// <param name="data">The new data for the consolidator</param>
144  public void Update(IBaseData data)
145  {
146  var currentValue = Selector(data);
147  var volume = VolumeSelector(data);
148 
149  // If we're already in a bar then update it
150  if (CurrentBar != null)
151  {
152  UpdateBar(data.Time, currentValue, volume);
153  }
154 
155  // The state of the CurrentBar could have changed after UpdateBar(),
156  // then we might need to create a new bar
157  if (CurrentBar == null)
158  {
159  CreateNewBar(data, currentValue, volume);
160  }
161  }
162 
163  /// <summary>
164  /// Updates the current RangeBar being created with the given data.
165  /// Additionally, if it's the case, it consolidates the current RangeBar
166  /// </summary>
167  /// <param name="time">Time of the given data</param>
168  /// <param name="currentValue">Value of the given data</param>
169  /// <param name="volume">Volume of the given data</param>
170  protected abstract void UpdateBar(DateTime time, decimal currentValue, decimal volume);
171 
172  /// <summary>
173  /// Creates a new bar with the given data
174  /// </summary>
175  /// <param name="data">The new data for the bar</param>
176  /// <param name="currentValue">The new value for the bar</param>
177  /// <param name="volume">The new volume to the bar</param>
178  protected abstract void CreateNewBar(IBaseData data, decimal currentValue, decimal volume);
179 
180  /// <summary>
181  /// Event invocator for the DataConsolidated event. This should be invoked
182  /// by derived classes when they have consolidated a new piece of data.
183  /// </summary>
184  /// <param name="consolidated">The newly consolidated data</param>
185  protected void OnDataConsolidated(T consolidated)
186  {
187  DataConsolidated?.Invoke(this, consolidated);
188 
189  DataConsolidatedHandler?.Invoke(this, consolidated);
190 
191  Consolidated = consolidated;
192  }
193 
194  /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
195  /// <filterpriority>2</filterpriority>
196  public virtual void Dispose()
197  {
198  DataConsolidated = null;
200  }
201 
202  /// <summary>
203  /// Scans this consolidator to see if it should emit a bar due to time passing
204  /// </summary>
205  /// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
206  public void Scan(DateTime currentLocalTime)
207  {
208  }
209  }
210 }