Skip to content

Commit 3a8e48a

Browse files
committed
Add new initialization path for Custom Python Indicators
1 parent 7735917 commit 3a8e48a

File tree

5 files changed

+45
-22
lines changed

5 files changed

+45
-22
lines changed

Algorithm/QCAlgorithm.Python.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2046,7 +2046,7 @@ private PythonIndicator WrapPythonIndicator(PyObject pyObject, PythonIndicator c
20462046

20472047
if (pythonIndicator == null)
20482048
{
2049-
pythonIndicator = new PythonIndicator(pyObject);
2049+
pythonIndicator = new PythonIndicator(pyObject, false);
20502050
}
20512051
else
20522052
{

Indicators/PythonIndicator.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public class PythonIndicator : IndicatorBase<IBaseData>, IIndicatorWarmUpPeriodP
2929
private PyObject _instance;
3030
private bool _isReady;
3131
private bool _pythonIsReadyProperty;
32+
private bool _useNewInitialization;
33+
private bool _isInstanceSet;
3234
private BasePythonWrapper<IIndicator> _indicatorWrapper;
3335

3436
/// <summary>
@@ -53,10 +55,12 @@ public PythonIndicator(params PyObject[] args)
5355
/// Initializes a new instance of the PythonIndicator class using the specified name.
5456
/// </summary>
5557
/// <param name="indicator">The python implementation of <see cref="IndicatorBase{IBaseDataBar}"/></param>
56-
public PythonIndicator(PyObject indicator)
58+
/// <param name="useNewInitialization">Whether to use the new initialization method</param>
59+
public PythonIndicator(PyObject indicator, bool useNewInitialization = true)
5760
: base(GetIndicatorName(indicator))
5861
{
59-
SetIndicator(indicator);
62+
_useNewInitialization = useNewInitialization;
63+
_instance = indicator;
6064
}
6165

6266
/// <summary>
@@ -67,19 +71,20 @@ public void SetIndicator(PyObject indicator)
6771
{
6872
_instance = indicator;
6973
_indicatorWrapper = new BasePythonWrapper<IIndicator>(indicator, validateInterface: false);
70-
foreach (var attributeName in new[] { "IsReady", "Update", "Value" })
74+
var requiredAttributes = new[] { "IsReady", _useNewInitialization ? "ComputeNextValue" : "Update", "Value" };
75+
76+
foreach (var attributeName in requiredAttributes)
7177
{
7278
if (!_indicatorWrapper.HasAttr(attributeName))
7379
{
7480
var name = GetIndicatorName(indicator);
75-
7681
var message = $"Indicator.{attributeName.ToSnakeCase()} must be implemented. " +
77-
$"Please implement this missing method in {name}";
82+
$"Please implement this missing method in {name}";
7883

7984
if (attributeName == "IsReady")
8085
{
8186
message += " or use PythonIndicator as base:" +
82-
$"{Environment.NewLine}class {name}(PythonIndicator):";
87+
$"{Environment.NewLine}class {name}(PythonIndicator):";
8388
}
8489

8590
throw new NotImplementedException(message);
@@ -93,8 +98,18 @@ public void SetIndicator(PyObject indicator)
9398
}
9499
}
95100
}
96-
97101
WarmUpPeriod = GetIndicatorWarmUpPeriod();
102+
_isInstanceSet = true;
103+
}
104+
105+
private bool CheckInstance()
106+
{
107+
if (_instance != null && !_isInstanceSet)
108+
{
109+
SetIndicator(_instance);
110+
}
111+
112+
return _isInstanceSet;
98113
}
99114

100115
/// <summary>
@@ -113,7 +128,7 @@ public override bool IsReady
113128
{
114129
using (Py.GIL())
115130
{
116-
/// We get the property again and convert it to bool
131+
// We get the property again and convert it to bool
117132
var property = _instance.GetPythonBoolPropertyWithChecks(_isReadyName);
118133
return BasePythonWrapper<IIndicator>.PythonRuntimeChecker.ConvertAndDispose<bool>(property, _isReadyName, isMethod: false);
119134
}
@@ -135,9 +150,17 @@ public override bool IsReady
135150
/// <returns>A new value for this indicator</returns>
136151
protected override decimal ComputeNextValue(IBaseData input)
137152
{
138-
_isReady = _indicatorWrapper.InvokeMethod<bool?>(nameof(Update), input)
153+
CheckInstance();
154+
if (_useNewInitialization)
155+
{
156+
return _indicatorWrapper.InvokeMethod<decimal>("ComputeNextValue", input);
157+
}
158+
else
159+
{
160+
_isReady = _indicatorWrapper.InvokeMethod<bool?>(nameof(Update), input)
139161
?? _indicatorWrapper.GetProperty<bool>(nameof(IsReady));
140-
return _indicatorWrapper.GetProperty<decimal>("Value");
162+
return _indicatorWrapper.GetProperty<decimal>("Value");
163+
}
141164
}
142165

143166
/// <summary>

Tests/Indicators/PythonIndicatorNoinheritanceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, name, period):
6767
var indicator = module.GetAttr("CustomSimpleMovingAverage")
6868
.Invoke("custom".ToPython(), 14.ToPython());
6969

70-
return new PythonIndicator(indicator);
70+
return new PythonIndicator(indicator, false);
7171
}
7272
}
7373

Tests/Indicators/PythonIndicatorNoinheritanceTestsLegacy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(self, name, period):
6666
var indicator = module.GetAttr("CustomSimpleMovingAverage")
6767
.Invoke("custom".ToPython(), 14.ToPython());
6868

69-
return new PythonIndicator(indicator);
69+
return new PythonIndicator(indicator, false);
7070
}
7171
}
7272

Tests/Indicators/PythonIndicatorTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def __init__(self, name, period):
8080

8181
protected override IndicatorBase<IBaseData> CreateIndicator()
8282
{
83-
return new PythonIndicator(CreatePythonIndicator());
83+
return new PythonIndicator(CreatePythonIndicator(), false);
8484
}
8585

8686
protected override string TestFileName => "spy_with_indicators.txt";
@@ -135,13 +135,13 @@ protected override void RunTestIndicator(IndicatorBase<IBaseData> indicator)
135135
}
136136

137137
protected override Action<IndicatorBase<IBaseData>, double> Assertion => (indicator, expected) =>
138-
Assert.AreEqual(expected, (double) indicator.Current.Value, 1e-2);
138+
Assert.AreEqual(expected, (double)indicator.Current.Value, 1e-2);
139139

140140
[Test]
141141
public void SmaComputesCorrectly()
142142
{
143143
var sma = new SimpleMovingAverage(4);
144-
var data = new[] {1m, 10m, 100m, 1000m, 10000m, 1234m, 56789m};
144+
var data = new[] { 1m, 10m, 100m, 1000m, 10000m, 1234m, 56789m };
145145

146146
var seen = new List<decimal>();
147147
for (int i = 0; i < data.Length; i++)
@@ -327,9 +327,9 @@ public void AllPythonRegisterIndicatorBadCases(string consolidatorName, bool nee
327327
Consolidator = Consolidator.Invoke();
328328
}
329329

330-
#pragma warning disable CS0618
330+
#pragma warning disable CS0618
331331
var exception = Assert.Throws<ArgumentException>(() => algorithm.RegisterIndicator(spy, PyIndicator, Consolidator));
332-
#pragma warning restore CS0618
332+
#pragma warning restore CS0618
333333
Assert.That(exception.Message, Is.EqualTo(expectedMessage));
334334
}
335335
}
@@ -362,7 +362,7 @@ def Update(self, input):
362362
);
363363
var pythonIndicator = module.GetAttr("CustomSimpleMovingAverage")
364364
.Invoke("custom".ToPython(), 14.ToPython());
365-
var SMAWithWarmUpPeriod = new PythonIndicator(pythonIndicator);
365+
var SMAWithWarmUpPeriod = new PythonIndicator(pythonIndicator, false);
366366
var reference = new DateTime(2000, 1, 1, 0, 0, 0);
367367
var period = ((IIndicatorWarmUpPeriodProvider)SMAWithWarmUpPeriod).WarmUpPeriod;
368368

@@ -404,7 +404,7 @@ def Update(self, input):
404404
);
405405
var pythonIndicator = module.GetAttr("CustomSimpleMovingAverage")
406406
.Invoke("custom".ToPython(), 14.ToPython());
407-
var indicator = new PythonIndicator(pythonIndicator);
407+
var indicator = new PythonIndicator(pythonIndicator, false);
408408

409409
Assert.AreEqual(0, indicator.WarmUpPeriod);
410410
}
@@ -421,7 +421,7 @@ public void PythonIndicatorDoesntRequireWrappingToWork()
421421
using (Py.GIL())
422422
{
423423
using dynamic customSma = CreatePythonIndicator(period);
424-
var wrapper = new PythonIndicator(customSma);
424+
var wrapper = new PythonIndicator(customSma, false);
425425

426426
for (int i = 0; i < data.Length; i++)
427427
{
@@ -486,7 +486,7 @@ public void PythonIndicatorExtensionInRegressionAlgorithm()
486486
{
487487
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters(
488488
"CustomIndicatorWithExtensionAlgorithm",
489-
new (),
489+
new(),
490490
Language.Python,
491491
AlgorithmStatus.Completed);
492492

0 commit comments

Comments
 (0)