diff --git a/Indicators/CompositeIndicator.cs b/Indicators/CompositeIndicator.cs
index d1644c7705fd..eca64cccc001 100644
--- a/Indicators/CompositeIndicator.cs
+++ b/Indicators/CompositeIndicator.cs
@@ -15,8 +15,8 @@
using System;
using Python.Runtime;
+using QuantConnect.Data;
using QuantConnect.Data.Market;
-using QuantConnect.Python;
namespace QuantConnect.Indicators
{
@@ -151,14 +151,14 @@ public CompositeIndicator(PyObject left, PyObject right, PyObject handler)
/// An IndicatorComposer that applies the Python function.
private static IndicatorComposer CreateComposerFromPyObject(PyObject handler)
{
- return (left, right) =>
+ // If the conversion fails, throw an exception
+ if (!handler.TryConvertToDelegate(out Func composer))
{
- using (Py.GIL())
- {
- dynamic result = handler.Invoke(left.Current.Value, right.Current.Value);
- return new IndicatorResult(result);
- }
- };
+ throw new InvalidOperationException("Failed to convert the handler into a valid delegate.");
+ }
+
+ // Return the converted delegate, since it matches the signature of IndicatorComposer
+ return new IndicatorComposer(composer);
}
///
@@ -170,24 +170,24 @@ private static IndicatorComposer CreateComposerFromPyObject(PyObject handler)
/// True if the conversion is successful; otherwise, false.
private static bool TryConvertIndicator(PyObject pyObject, out IndicatorBase indicator)
{
- if (pyObject.TryConvert(out IndicatorBase idp))
+ indicator = null;
+ if (pyObject.TryConvert(out IndicatorBase ibd))
+ {
+ indicator = ibd;
+ }
+ else if (pyObject.TryConvert(out IndicatorBase idp))
{
indicator = idp;
- return true;
}
- if (pyObject.TryConvert(out IndicatorBase idb))
+ else if (pyObject.TryConvert(out IndicatorBase idb))
{
indicator = idb;
- return true;
}
- if (pyObject.TryConvert(out IndicatorBase itb))
+ else if (pyObject.TryConvert(out IndicatorBase itb))
{
indicator = itb;
- return true;
}
-
- indicator = null;
- return false;
+ return indicator != null;
}
///
diff --git a/Tests/Indicators/CompositeIndicatorTests.cs b/Tests/Indicators/CompositeIndicatorTests.cs
index 742b755d036d..c3913fb6a55f 100644
--- a/Tests/Indicators/CompositeIndicatorTests.cs
+++ b/Tests/Indicators/CompositeIndicatorTests.cs
@@ -15,6 +15,7 @@
using System;
using NUnit.Framework;
+using Python.Runtime;
using QuantConnect.Indicators;
namespace QuantConnect.Tests.Indicators
@@ -72,13 +73,14 @@ public void CallsDelegateCorrectly()
}
[Test]
- public virtual void ResetsProperly() {
+ public virtual void ResetsProperly()
+ {
var left = new Maximum("left", 2);
var right = new Minimum("right", 2);
var composite = CreateCompositeIndicator(left, right, (l, r) => l.Current.Value + r.Current.Value);
left.Update(DateTime.Today, 1m);
- right.Update(DateTime.Today,-1m);
+ right.Update(DateTime.Today, -1m);
left.Update(DateTime.Today.AddDays(1), -1m);
right.Update(DateTime.Today.AddDays(1), 1m);
@@ -94,6 +96,55 @@ public virtual void ResetsProperly() {
Assert.AreEqual(right.PeriodsSinceMinimum, 0);
}
+ [TestCase("sum", 5, 10, 15)]
+ [TestCase("min", -12, 52, -12)]
+ public virtual void PythonCompositeIndicatorConstructorValidatesBehavior(string operation, decimal leftValue, decimal rightValue, decimal expectedValue)
+ {
+ var left = new SimpleMovingAverage("SMA", 10);
+ var right = new SimpleMovingAverage("SMA", 10);
+ using (Py.GIL())
+ {
+ var testModule = PyModule.FromString("testModule",
+ @"
+from AlgorithmImports import *
+from QuantConnect.Indicators import *
+
+def create_composite_indicator(left, right, operation):
+ if operation == 'sum':
+ def composer(l, r):
+ return IndicatorResult(l.Current.Value + r.Current.Value)
+ elif operation == 'min':
+ def composer(l, r):
+ return IndicatorResult(min(l.Current.Value, r.Current.Value))
+ return CompositeIndicator(left, right, composer)
+
+def update_indicators(left, right, value_left, value_right):
+ left.Update(IndicatorDataPoint(DateTime.Now, value_left))
+ right.Update(IndicatorDataPoint(DateTime.Now, value_right))
+ ");
+
+ var createCompositeIndicator = testModule.GetAttr("create_composite_indicator");
+ var updateIndicators = testModule.GetAttr("update_indicators");
+
+ var leftPy = left.ToPython();
+ var rightPy = right.ToPython();
+
+ // Create composite indicator using Python logic
+ var composite = createCompositeIndicator.Invoke(leftPy, rightPy, operation.ToPython());
+
+ // Update the indicator with sample values (left, right)
+ updateIndicators.Invoke(leftPy, rightPy, leftValue.ToPython(), rightValue.ToPython());
+
+ // Verify composite indicator name and properties
+ Assert.AreEqual($"COMPOSE({left.Name},{right.Name})", composite.GetAttr("Name").ToString());
+ Assert.AreEqual(left, composite.GetAttr("Left").As());
+ Assert.AreEqual(right, composite.GetAttr("Right").As());
+
+ // Validate the composite indicator computed value
+ Assert.AreEqual(expectedValue, composite.GetAttr("Current").GetAttr("Value").As());
+ }
+ }
+
protected virtual CompositeIndicator CreateCompositeIndicator(IndicatorBase left, IndicatorBase right, QuantConnect.Indicators.CompositeIndicator.IndicatorComposer composer)
{
return new CompositeIndicator(left, right, composer);