Lean  $LEAN_TAG$
PythonIndicator.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;
19 using QuantConnect.Python;
20 
22 {
23  /// <summary>
24  /// Provides a wrapper for <see cref="IndicatorBase{IBaseData}"/> implementations written in python
25  /// </summary>
27  {
28  private static string _isReadyName = nameof(IsReady).ToSnakeCase();
29  private PyObject _instance;
30  private bool _isReady;
31  private bool _pythonIsReadyProperty;
32  private BasePythonWrapper<IIndicator> _indicatorWrapper;
33 
34  /// <summary>
35  /// Initializes a new instance of the PythonIndicator class using the specified name.
36  /// </summary>
37  /// <remarks>This overload allows inheritance for python classes with no arguments</remarks>
38  public PythonIndicator()
39  : base("")
40  {
41  }
42 
43  /// <summary>
44  /// Initializes a new instance of the PythonIndicator class using the specified name.
45  /// </summary>
46  /// <remarks>This overload allows inheritance for python classes with multiple arguments</remarks>
47  public PythonIndicator(params PyObject[] args)
48  : base(GetIndicatorName(args[0]))
49  {
50  }
51 
52  /// <summary>
53  /// Initializes a new instance of the PythonIndicator class using the specified name.
54  /// </summary>
55  /// <param name="indicator">The python implementation of <see cref="IndicatorBase{IBaseDataBar}"/></param>
56  public PythonIndicator(PyObject indicator)
57  : base(GetIndicatorName(indicator))
58  {
59  SetIndicator(indicator);
60  }
61 
62  /// <summary>
63  /// Sets the python implementation of the indicator
64  /// </summary>
65  /// <param name="indicator">The python implementation of <see cref="IndicatorBase{IBaseDataBar}"/></param>
66  public void SetIndicator(PyObject indicator)
67  {
68  _instance = indicator;
69  _indicatorWrapper = new BasePythonWrapper<IIndicator>(indicator, validateInterface: false);
70  foreach (var attributeName in new[] { "IsReady", "Update", "Value" })
71  {
72  if (!_indicatorWrapper.HasAttr(attributeName))
73  {
74  var name = GetIndicatorName(indicator);
75 
76  var message = $"Indicator.{attributeName.ToSnakeCase()} must be implemented. " +
77  $"Please implement this missing method in {name}";
78 
79  if (attributeName == "IsReady")
80  {
81  message += " or use PythonIndicator as base:" +
82  $"{Environment.NewLine}class {name}(PythonIndicator):";
83  }
84 
85  throw new NotImplementedException(message);
86  }
87 
88  if (attributeName == "IsReady")
89  {
90  using (Py.GIL())
91  {
92  _pythonIsReadyProperty = indicator.GetPythonBoolPropertyWithChecks(_isReadyName) != null;
93  }
94  }
95  }
96 
97  WarmUpPeriod = GetIndicatorWarmUpPeriod();
98  }
99 
100  /// <summary>
101  /// Gets a flag indicating when this indicator is ready and fully initialized
102  /// </summary>
103  public override bool IsReady
104  {
105  get
106  {
107  if (_isReady)
108  {
109  return true;
110  }
111 
112  if (_pythonIsReadyProperty)
113  {
114  using (Py.GIL())
115  {
116  /// We get the property again and convert it to bool
117  var property = _instance.GetPythonBoolPropertyWithChecks(_isReadyName);
118  return BasePythonWrapper<IIndicator>.PythonRuntimeChecker.ConvertAndDispose<bool>(property, _isReadyName, isMethod: false);
119  }
120  }
121 
122  return _isReady;
123  }
124  }
125 
126  /// <summary>
127  /// Required period, in data points, for the indicator to be ready and fully initialized
128  /// </summary>
129  public int WarmUpPeriod { get; protected set; }
130 
131  /// <summary>
132  /// Computes the next value of this indicator from the given state
133  /// </summary>
134  /// <param name="input">The input given to the indicator</param>
135  /// <returns>A new value for this indicator</returns>
136  protected override decimal ComputeNextValue(IBaseData input)
137  {
138  _isReady = _indicatorWrapper.InvokeMethod<bool?>(nameof(Update), input)
139  ?? _indicatorWrapper.GetProperty<bool>(nameof(IsReady));
140  return _indicatorWrapper.GetProperty<decimal>("Value");
141  }
142 
143  /// <summary>
144  /// Get the indicator WarmUpPeriod parameter. If not defined, use 0
145  /// </summary>
146  /// <returns>The WarmUpPeriod of the indicator.</returns>
147  private int GetIndicatorWarmUpPeriod()
148  {
149  return _indicatorWrapper.HasAttr(nameof(WarmUpPeriod)) ? _indicatorWrapper.GetProperty<int>(nameof(WarmUpPeriod)) : 0;
150  }
151 
152  /// <summary>
153  /// Get the indicator Name. If not defined, use the class name
154  /// </summary>
155  /// <param name="indicator">The python implementation of <see cref="IndicatorBase{IBaseDataBar}"/></param>
156  /// <returns>The indicator Name.</returns>
157  private static string GetIndicatorName(PyObject indicator)
158  {
159  using (Py.GIL())
160  {
161  PyObject name;
162  if (indicator.HasAttr("Name"))
163  {
164  name = indicator.GetAttr("Name");
165  }
166  else if (indicator.HasAttr("name"))
167  {
168  name = indicator.GetAttr("name");
169  }
170  else
171  {
172  name = indicator.GetAttr("__class__").GetAttr("__name__");
173  }
174  return name.GetAndDispose<string>();
175  }
176  }
177  }
178 }