/* * 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.Python; namespace QuantConnect.Indicators { /// /// Provides a wrapper for implementations written in python /// public class PythonIndicator : IndicatorBase, IIndicatorWarmUpPeriodProvider { private static string _isReadyName = nameof(IsReady).ToSnakeCase(); private PyObject _instance; private bool _isReady; private bool _pythonIsReadyProperty; private BasePythonWrapper _indicatorWrapper; /// /// Initializes a new instance of the PythonIndicator class using the specified name. /// /// This overload allows inheritance for python classes with no arguments public PythonIndicator() : base("") { } /// /// Initializes a new instance of the PythonIndicator class using the specified name. /// /// This overload allows inheritance for python classes with multiple arguments public PythonIndicator(params PyObject[] args) : base(GetIndicatorName(args[0])) { } /// /// Initializes a new instance of the PythonIndicator class using the specified name. /// /// The python implementation of public PythonIndicator(PyObject indicator) : base(GetIndicatorName(indicator)) { SetIndicator(indicator); } /// /// Sets the python implementation of the indicator /// /// The python implementation of public void SetIndicator(PyObject indicator) { _instance = indicator; _indicatorWrapper = new BasePythonWrapper(indicator, validateInterface: false); foreach (var attributeName in new[] { "IsReady", "Update", "Value" }) { if (!_indicatorWrapper.HasAttr(attributeName)) { var name = GetIndicatorName(indicator); var message = $"Indicator.{attributeName.ToSnakeCase()} must be implemented. " + $"Please implement this missing method in {name}"; if (attributeName == "IsReady") { message += " or use PythonIndicator as base:" + $"{Environment.NewLine}class {name}(PythonIndicator):"; } throw new NotImplementedException(message); } if (attributeName == "IsReady") { using (Py.GIL()) { _pythonIsReadyProperty = indicator.GetPythonBoolPropertyWithChecks(_isReadyName) != null; } } } WarmUpPeriod = GetIndicatorWarmUpPeriod(); } /// /// Gets a flag indicating when this indicator is ready and fully initialized /// public override bool IsReady { get { if (_isReady) { return true; } if (_pythonIsReadyProperty) { using (Py.GIL()) { /// We get the property again and convert it to bool var property = _instance.GetPythonBoolPropertyWithChecks(_isReadyName); return BasePythonWrapper.PythonRuntimeChecker.ConvertAndDispose(property, _isReadyName, isMethod: false); } } return _isReady; } } /// /// Required period, in data points, for the indicator to be ready and fully initialized /// public int WarmUpPeriod { get; protected set; } /// /// Computes the next value of this indicator from the given state /// /// The input given to the indicator /// A new value for this indicator protected override decimal ComputeNextValue(IBaseData input) { _isReady = _indicatorWrapper.InvokeMethod(nameof(Update), input) ?? _indicatorWrapper.GetProperty(nameof(IsReady)); return _indicatorWrapper.GetProperty("Value"); } /// /// Get the indicator WarmUpPeriod parameter. If not defined, use 0 /// /// The WarmUpPeriod of the indicator. private int GetIndicatorWarmUpPeriod() { return _indicatorWrapper.HasAttr(nameof(WarmUpPeriod)) ? _indicatorWrapper.GetProperty(nameof(WarmUpPeriod)) : 0; } /// /// Get the indicator Name. If not defined, use the class name /// /// The python implementation of /// The indicator Name. private static string GetIndicatorName(PyObject indicator) { using (Py.GIL()) { PyObject name; if (indicator.HasAttr("Name")) { name = indicator.GetAttr("Name"); } else if (indicator.HasAttr("name")) { name = indicator.GetAttr("name"); } else { name = indicator.GetAttr("__class__").GetAttr("__name__"); } return name.GetAndDispose(); } } } }