From bc4124930750df909bb8f7b136577e8f67badd44 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 21 Feb 2025 03:24:23 -0500 Subject: [PATCH] Add new test case --- Indicators/CompositeIndicator.cs | 33 +-------- Indicators/IndicatorExtensions.cs | 38 +++++++--- Tests/Indicators/CompositeIndicatorTests.cs | 78 +++++++++++++++++---- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/Indicators/CompositeIndicator.cs b/Indicators/CompositeIndicator.cs index eca64cccc001..dd86ce600da0 100644 --- a/Indicators/CompositeIndicator.cs +++ b/Indicators/CompositeIndicator.cs @@ -116,11 +116,11 @@ public CompositeIndicator(IndicatorBase left, IndicatorBase right, IndicatorComp public CompositeIndicator(string name, PyObject left, PyObject right, PyObject handler) : base(name) { - if (!TryConvertIndicator(left, out var leftIndicator)) + if (!left.TryConvertIndicator(out var leftIndicator)) { throw new ArgumentException($"The left argument should be a QuantConnect Indicator object, {left} was provided."); } - if (!TryConvertIndicator(right, out var rightIndicator)) + if (!right.TryConvertIndicator(out var rightIndicator)) { throw new ArgumentException($"The right argument should be a QuantConnect Indicator object, {right} was provided."); } @@ -161,35 +161,6 @@ private static IndicatorComposer CreateComposerFromPyObject(PyObject handler) return new IndicatorComposer(composer); } - /// - /// Attempts to convert a into an . - /// Supports indicators based on , , and . - /// - /// The Python object to convert. - /// The converted indicator if successful; otherwise, null. - /// True if the conversion is successful; otherwise, false. - private static bool TryConvertIndicator(PyObject pyObject, out IndicatorBase indicator) - { - indicator = null; - if (pyObject.TryConvert(out IndicatorBase ibd)) - { - indicator = ibd; - } - else if (pyObject.TryConvert(out IndicatorBase idp)) - { - indicator = idp; - } - else if (pyObject.TryConvert(out IndicatorBase idb)) - { - indicator = idb; - } - else if (pyObject.TryConvert(out IndicatorBase itb)) - { - indicator = itb; - } - return indicator != null; - } - /// /// Computes the next value of this indicator from the given state /// and returns an instance of the class diff --git a/Indicators/IndicatorExtensions.cs b/Indicators/IndicatorExtensions.cs index 2d21c2f35ac7..a3cf62e28e8e 100644 --- a/Indicators/IndicatorExtensions.cs +++ b/Indicators/IndicatorExtensions.cs @@ -19,6 +19,7 @@ using QuantConnect.Data; using Python.Runtime; using QuantConnect.Util; +using QuantConnect.Data.Market; namespace QuantConnect.Indicators { @@ -98,7 +99,8 @@ public static CompositeIndicator WeightedBy(this IndicatorBase va denominator.Update(consolidated); }; - var resetCompositeIndicator = new ResetCompositeIndicator(numerator, denominator, GetOverIndicatorComposer(), () => { + var resetCompositeIndicator = new ResetCompositeIndicator(numerator, denominator, GetOverIndicatorComposer(), () => + { x.Reset(); y.Reset(); }); @@ -132,7 +134,7 @@ public static CompositeIndicator Plus(this IndicatorBase left, decimal constant) /// The sum of the left and right indicators public static CompositeIndicator Plus(this IndicatorBase left, IndicatorBase right) { - return new (left, right, (l, r) => l.Current.Value + r.Current.Value); + return new(left, right, (l, r) => l.Current.Value + r.Current.Value); } /// @@ -147,7 +149,7 @@ public static CompositeIndicator Plus(this IndicatorBase left, IndicatorBase rig /// The sum of the left and right indicators public static CompositeIndicator Plus(this IndicatorBase left, IndicatorBase right, string name) { - return new (name, left, right, (l, r) => l.Current.Value + r.Current.Value); + return new(name, left, right, (l, r) => l.Current.Value + r.Current.Value); } /// @@ -176,7 +178,7 @@ public static CompositeIndicator Minus(this IndicatorBase left, decimal constant /// The difference of the left and right indicators public static CompositeIndicator Minus(this IndicatorBase left, IndicatorBase right) { - return new (left, right, (l, r) => l.Current.Value - r.Current.Value); + return new(left, right, (l, r) => l.Current.Value - r.Current.Value); } /// @@ -191,7 +193,7 @@ public static CompositeIndicator Minus(this IndicatorBase left, IndicatorBase ri /// The difference of the left and right indicators public static CompositeIndicator Minus(this IndicatorBase left, IndicatorBase right, string name) { - return new (name, left, right, (l, r) => l.Current.Value - r.Current.Value); + return new(name, left, right, (l, r) => l.Current.Value - r.Current.Value); } /// @@ -220,7 +222,7 @@ public static CompositeIndicator Over(this IndicatorBase left, decimal constant) /// The ratio of the left to the right indicator public static CompositeIndicator Over(this IndicatorBase left, IndicatorBase right) { - return new (left, right, GetOverIndicatorComposer()); + return new(left, right, GetOverIndicatorComposer()); } /// @@ -235,7 +237,7 @@ public static CompositeIndicator Over(this IndicatorBase left, IndicatorBase rig /// The ratio of the left to the right indicator public static CompositeIndicator Over(this IndicatorBase left, IndicatorBase right, string name) { - return new (name, left, right, GetOverIndicatorComposer()); + return new(name, left, right, GetOverIndicatorComposer()); } /// @@ -264,7 +266,7 @@ public static CompositeIndicator Times(this IndicatorBase left, decimal constant /// The product of the left to the right indicators public static CompositeIndicator Times(this IndicatorBase left, IndicatorBase right) { - return new (left, right, (l, r) => l.Current.Value * r.Current.Value); + return new(left, right, (l, r) => l.Current.Value * r.Current.Value); } /// @@ -279,7 +281,23 @@ public static CompositeIndicator Times(this IndicatorBase left, IndicatorBase ri /// The product of the left to the right indicators public static CompositeIndicator Times(this IndicatorBase left, IndicatorBase right, string name) { - return new (name, left, right, (l, r) => l.Current.Value * r.Current.Value); + return new(name, left, right, (l, r) => l.Current.Value * r.Current.Value); + } + + /// + /// Attempts to convert a into an . + /// + /// The Python object to convert. + /// The resulting indicator if successful; otherwise, null. + /// true if the conversion succeeds; otherwise, false. + public static bool TryConvertIndicator(this PyObject pyObject, out IndicatorBase indicator) + { + indicator = null; + + return pyObject.TryConvert(out IndicatorBase ibd) && (indicator = ibd) != null || + pyObject.TryConvert(out IndicatorBase idp) && (indicator = idp) != null || + pyObject.TryConvert(out IndicatorBase idb) && (indicator = idb) != null || + pyObject.TryConvert(out IndicatorBase itb) && (indicator = itb) != null; } /// Creates a new ExponentialMovingAverage indicator with the specified period and smoothingFactor from the left indicator @@ -418,7 +436,7 @@ public static SimpleMovingAverage SMA(PyObject left, int period, bool waitForFir return SMA(indicator, period, waitForFirstToReady); } - /// + /// /// Creates a new CompositeIndicator such that the result will be the ratio of the left to the constant /// /// diff --git a/Tests/Indicators/CompositeIndicatorTests.cs b/Tests/Indicators/CompositeIndicatorTests.cs index c3913fb6a55f..f774ff9c15d0 100644 --- a/Tests/Indicators/CompositeIndicatorTests.cs +++ b/Tests/Indicators/CompositeIndicatorTests.cs @@ -96,12 +96,24 @@ 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) + [TestCase("sum", 5, 10, 15, false)] + [TestCase("min", -12, 52, -12, false)] + [TestCase("sum", 5, 10, 15, true)] + [TestCase("min", -12, 52, -12, true)] + public virtual void PythonCompositeIndicatorConstructorValidatesBehavior(string operation, decimal leftValue, decimal rightValue, decimal expectedValue, bool usePythonIndicator) { - var left = new SimpleMovingAverage("SMA", 10); - var right = new SimpleMovingAverage("SMA", 10); + IndicatorBase left; + IndicatorBase right; + if (usePythonIndicator) + { + left = new PythonIndicator(CreatePyObjectIndicator(10)); + right = new PythonIndicator(CreatePyObjectIndicator(10)); + } + else + { + left = new SimpleMovingAverage("SMA", 10); + right = new SimpleMovingAverage("SMA", 10); + } using (Py.GIL()) { var testModule = PyModule.FromString("testModule", @@ -123,25 +135,63 @@ def update_indicators(left, right, value_left, value_right): right.Update(IndicatorDataPoint(DateTime.Now, value_right)) "); - var createCompositeIndicator = testModule.GetAttr("create_composite_indicator"); - var updateIndicators = testModule.GetAttr("update_indicators"); + using var createCompositeIndicator = testModule.GetAttr("create_composite_indicator"); + using var updateIndicators = testModule.GetAttr("update_indicators"); - var leftPy = left.ToPython(); - var rightPy = right.ToPython(); + using var leftPy = left.ToPython(); + using var rightPy = right.ToPython(); // Create composite indicator using Python logic - var composite = createCompositeIndicator.Invoke(leftPy, rightPy, operation.ToPython()); + using 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()); + using var name = composite.GetAttr("Name"); + using var typeLeft = composite.GetAttr("Left"); + using var typeRight = composite.GetAttr("Right"); + Assert.AreEqual($"COMPOSE({left.Name},{right.Name})", name.ToString()); + Assert.AreEqual(left, typeLeft.As()); + Assert.AreEqual(right, typeRight.As()); // Validate the composite indicator computed value - Assert.AreEqual(expectedValue, composite.GetAttr("Current").GetAttr("Value").As()); + using var value = composite.GetAttr("Current").GetAttr("Value"); + Assert.AreEqual(expectedValue, value.As()); + } + } + + + private static PyObject CreatePyObjectIndicator(int period) + { + using (Py.GIL()) + { + var module = PyModule.FromString( + "custom_indicator", + @" +from AlgorithmImports import * +from collections import deque + +class CustomSimpleMovingAverage(PythonIndicator): + def __init__(self, period): + self.Name = 'CustomSMA' + self.Value = 0 + self.Period = period + self.WarmUpPeriod = period + self.queue = deque(maxlen=period) + + def Update(self, input): + self.queue.appendleft(input.Value) + count = len(self.queue) + self.Value = sum(self.queue) / count + return count == self.queue.maxlen +" + ); + + var indicator = module.GetAttr("CustomSimpleMovingAverage") + .Invoke(period.ToPython()); + + return indicator; } }