diff --git a/Algorithm/QCAlgorithm.Python.cs b/Algorithm/QCAlgorithm.Python.cs index 19e453f729fb..e1e72d668bac 100644 --- a/Algorithm/QCAlgorithm.Python.cs +++ b/Algorithm/QCAlgorithm.Python.cs @@ -299,7 +299,7 @@ public Universe AddUniverse(PyObject pyObject) return AddUniverse(pyObject, null, null); } // TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved - else if(pyObject.TryConvert(out universe)) + else if (pyObject.TryConvert(out universe)) { return AddUniverse(universe); } @@ -569,6 +569,65 @@ public void AddUniverseOptions(PyObject universe, PyObject optionFilter) } } + /// + /// Creates a new using two indicators and a custom Python function as a handler. + /// + /// The name of the composite indicator. + /// The first indicator used in the composition. + /// The second indicator used in the composition. + /// A Python function that takes two indicator values and returns the computed result. + /// A new instance of . + /// + /// Thrown when the provided left or right indicator is not a valid QuantConnect Indicator object. + /// + [DocumentationAttribute(Universes)] + public CompositeIndicator CompositeIndicator(string name, PyObject left, PyObject right, PyObject handler) + { + var leftIndicator = GetIndicator(left); + var rightIndicator = GetIndicator(right); + if (leftIndicator == null) + { + throw new ArgumentException($"The left argument should be a QuantConnect Indicator object, {left} was provided."); + } + if (rightIndicator == null) + { + throw new ArgumentException($"The right argument should be a QuantConnect Indicator object, {right} was provided."); + } + CompositeIndicator.IndicatorComposer composer = (left, right) => + { + using (Py.GIL()) + { + dynamic result = handler.Invoke(left.Current.Value, right.Current.Value); + return new IndicatorResult(result); + } + }; + return new CompositeIndicator(name, leftIndicator, rightIndicator, composer); + } + + /// + /// Attempts to convert a Python object into a valid QuantConnect indicator. + /// + /// The Python object to convert. + /// + /// A valid instance if conversion is successful; otherwise, null. + /// + public IndicatorBase GetIndicator(PyObject pyObject) + { + if (pyObject.TryConvert(out IndicatorBase indicatorDataPoint)) + { + return indicatorDataPoint; + } + if (pyObject.TryConvert(out IndicatorBase indicatorDataBar)) + { + return indicatorDataBar; + } + if (pyObject.TryConvert(out IndicatorBase indicatorTradeBar)) + { + return indicatorTradeBar; + } + return null; + } + /// /// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates /// from the consolidator. @@ -1768,7 +1827,7 @@ private dynamic[] GetIndicatorArray(PyObject first, PyObject second = null, PyOb { using (Py.GIL()) { - var array = new[] {first, second, third, fourth} + var array = new[] { first, second, third, fourth } .Select( x => {