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