diff --git a/Common/Scheduling/FuncDateRule.cs b/Common/Scheduling/FuncDateRule.cs index 500ad115e87c..05e6f078c4c3 100644 --- a/Common/Scheduling/FuncDateRule.cs +++ b/Common/Scheduling/FuncDateRule.cs @@ -14,6 +14,7 @@ * */ +using Python.Runtime; using System; using System.Collections.Generic; @@ -37,6 +38,20 @@ public FuncDateRule(string name, Func> _getDatesFunction = getDatesFunction; } + /// + /// Initializes a new instance of the class using a Python function + /// + /// The name of this rule + /// The time applicator function in Python + public FuncDateRule(string name, PyObject getDatesFunction) + { + Name = name; + if (!getDatesFunction.TryConvertToDelegate(out _getDatesFunction)) + { + throw new ArgumentException("Python DateRule provided is not a function"); + } + } + /// /// Gets a name for this rule /// diff --git a/Common/Scheduling/FuncTimeRule.cs b/Common/Scheduling/FuncTimeRule.cs index 095046d27fd6..2620e8b8cd9e 100644 --- a/Common/Scheduling/FuncTimeRule.cs +++ b/Common/Scheduling/FuncTimeRule.cs @@ -14,6 +14,7 @@ * */ +using Python.Runtime; using System; using System.Collections.Generic; @@ -37,6 +38,20 @@ public FuncTimeRule(string name, Func, IEnumerable + /// Initializes a new instance of the class using a Python function + /// + /// The name of the time rule + /// Function used to transform dates into event date times in Python + public FuncTimeRule(string name, PyObject createUtcEventTimesFunction) + { + Name = name; + if (!createUtcEventTimesFunction.TryConvertToDelegate(out _createUtcEventTimesFunction)) + { + throw new ArgumentException("Python TimeRule provided is not a function"); + } + } + /// /// Gets a name for this rule /// diff --git a/Tests/Common/Scheduling/DateRulesTests.cs b/Tests/Common/Scheduling/DateRulesTests.cs index 927848731235..e85658d1dd1b 100644 --- a/Tests/Common/Scheduling/DateRulesTests.cs +++ b/Tests/Common/Scheduling/DateRulesTests.cs @@ -20,6 +20,7 @@ using System.Linq; using NodaTime; using NUnit.Framework; +using Python.Runtime; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Logging; @@ -602,6 +603,59 @@ public void SetTimeZone() Assert.AreEqual(nowUtc.Date, nowNewYork.AddDays(1)); } + [Test] + public void SetFuncDateRuleInPythonWorksAsExpected() + { + using (Py.GIL()) + { + var pythonModule = PyModule.FromString("testModule", @" +from AlgorithmImports import * + +def CustomDateRule(start, end): + return [start + (end - start)/2] +"); + dynamic pythonCustomDateRule = pythonModule.GetAttr("CustomDateRule"); + var funcDateRule = new FuncDateRule("PythonFuncDateRule", pythonCustomDateRule); + Assert.AreEqual("PythonFuncDateRule", funcDateRule.Name); + Assert.AreEqual(new DateTime(2023, 1, 16, 12, 0, 0), funcDateRule.GetDates(new DateTime(2023, 1, 1), new DateTime(2023, 2, 1)).First()); + } + } + + [Test] + public void SetFuncDateRuleInPythonWorksAsExpectedWithCSharpFunc() + { + using (Py.GIL()) + { + var pythonModule = PyModule.FromString("testModule", @" +from AlgorithmImports import * + +def GetFuncDateRule(csharpFunc): + return FuncDateRule(""CSharp"", csharpFunc) +"); + dynamic getFuncDateRule = pythonModule.GetAttr("GetFuncDateRule"); + Func> csharpFunc = (start, end) => { return new List() { new DateTime(2001, 3, 18) }; }; + var funcDateRule = getFuncDateRule(csharpFunc); + Assert.AreEqual("CSharp", (funcDateRule.Name as PyObject).GetAndDispose()); + Assert.AreEqual(new DateTime(2001, 3, 18), + (funcDateRule.GetDates(new DateTime(2023, 1, 1), new DateTime(2023, 2, 1)) as PyObject).GetAndDispose>().First()); + } + } + + [Test] + public void SetFuncDateRuleInPythonFailsWhenDateRuleIsInvalid() + { + using (Py.GIL()) + { + var pythonModule = PyModule.FromString("testModule", @" +from AlgorithmImports import * + +wrongCustomDateRule = 1 +"); + dynamic pythonCustomDateRule = pythonModule.GetAttr("wrongCustomDateRule"); + Assert.Throws(() => new FuncDateRule("PythonFuncDateRule", pythonCustomDateRule)); + } + } + private static DateRules GetDateRules() { var mhdb = MarketHoursDatabase.FromDataFolder(); diff --git a/Tests/Common/Scheduling/TimeRulesTests.cs b/Tests/Common/Scheduling/TimeRulesTests.cs index a8263dfbf085..eee0f0050964 100644 --- a/Tests/Common/Scheduling/TimeRulesTests.cs +++ b/Tests/Common/Scheduling/TimeRulesTests.cs @@ -19,6 +19,7 @@ using System.Linq; using NodaTime; using NUnit.Framework; +using Python.Runtime; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Scheduling; @@ -328,6 +329,59 @@ public void SetTimeZone() Assert.AreEqual(nowUtc, nowNewYork); } + [Test] + public void SetFuncTimeRuleInPythonWorksAsExpected() + { + using (Py.GIL()) + { + var pythonModule = PyModule.FromString("testModule", @" +from AlgorithmImports import * + +def CustomTimeRule(dates): + return [dates[0] + timedelta(days=1)] +"); + dynamic pythonCustomTimeRule = pythonModule.GetAttr("CustomTimeRule"); + var funcTimeRule = new FuncTimeRule("PythonFuncTimeRule", pythonCustomTimeRule); + Assert.AreEqual("PythonFuncTimeRule", funcTimeRule.Name); + Assert.AreEqual(new DateTime(2023, 1, 2, 0, 0, 0), funcTimeRule.CreateUtcEventTimes(new List() { new DateTime(2023, 1, 1) }).First()); + } + } + + [Test] + public void SetFuncTimeRuleInPythonWorksAsExpectedWithCSharpFunc() + { + using (Py.GIL()) + { + var pythonModule = PyModule.FromString("testModule", @" +from AlgorithmImports import * + +def GetFuncTimeRule(csharpFunc): + return FuncTimeRule(""CSharp"", csharpFunc) +"); + dynamic getFuncTimeRule = pythonModule.GetAttr("GetFuncTimeRule"); + Func, IEnumerable> csharpFunc = (dates) => { return new List() { new DateTime(2001, 3, 18) }; }; + var funcTimeRule = getFuncTimeRule(csharpFunc); + Assert.AreEqual("CSharp", (funcTimeRule.Name as PyObject).GetAndDispose()); + Assert.AreEqual(new DateTime(2001, 3, 18), + (funcTimeRule.CreateUtcEventTimes(new List() { new DateTime(2023, 1, 1) }) as PyObject).GetAndDispose>().First()); + } + } + + [Test] + public void SetFuncTimeRuleInPythonFailsWhenInvalidTimeRule() + { + using (Py.GIL()) + { + var pythonModule = PyModule.FromString("testModule", @" +from AlgorithmImports import * + +wrongCustomTimeRule = ""hello"" +"); + dynamic pythonCustomTimeRule = pythonModule.GetAttr("wrongCustomTimeRule"); + Assert.Throws(() => new FuncTimeRule("PythonFuncTimeRule", pythonCustomTimeRule)); + } + } + private static TimeRules GetTimeRules(DateTimeZone dateTimeZone) { var timeKeeper = new TimeKeeper(_utcNow, new List());