Skip to content

Commit

Permalink
Add FuncTimeRule and FuncDateRule constructors for PyObject (QuantCon…
Browse files Browse the repository at this point in the history
…nect#8061)

* Initial draft of the solution

* Add more unit tests
  • Loading branch information
Marinovsky authored May 28, 2024
1 parent 158df44 commit d76c2bf
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
15 changes: 15 additions & 0 deletions Common/Scheduling/FuncDateRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*
*/

using Python.Runtime;
using System;
using System.Collections.Generic;

Expand All @@ -37,6 +38,20 @@ public FuncDateRule(string name, Func<DateTime, DateTime, IEnumerable<DateTime>>
_getDatesFunction = getDatesFunction;
}

/// <summary>
/// Initializes a new instance of the <see cref="FuncDateRule"/> class using a Python function
/// </summary>
/// <param name="name">The name of this rule</param>
/// <param name="getDatesFunction">The time applicator function in Python</param>
public FuncDateRule(string name, PyObject getDatesFunction)
{
Name = name;
if (!getDatesFunction.TryConvertToDelegate(out _getDatesFunction))
{
throw new ArgumentException("Python DateRule provided is not a function");
}
}

/// <summary>
/// Gets a name for this rule
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions Common/Scheduling/FuncTimeRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*
*/

using Python.Runtime;
using System;
using System.Collections.Generic;

Expand All @@ -37,6 +38,20 @@ public FuncTimeRule(string name, Func<IEnumerable<DateTime>, IEnumerable<DateTim
_createUtcEventTimesFunction = createUtcEventTimesFunction;
}

/// <summary>
/// Initializes a new instance of the <see cref="FuncTimeRule"/> class using a Python function
/// </summary>
/// <param name="name">The name of the time rule</param>
/// <param name="createUtcEventTimesFunction">Function used to transform dates into event date times in Python</param>
public FuncTimeRule(string name, PyObject createUtcEventTimesFunction)
{
Name = name;
if (!createUtcEventTimesFunction.TryConvertToDelegate(out _createUtcEventTimesFunction))
{
throw new ArgumentException("Python TimeRule provided is not a function");
}
}

/// <summary>
/// Gets a name for this rule
/// </summary>
Expand Down
54 changes: 54 additions & 0 deletions Tests/Common/Scheduling/DateRulesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<DateTime, DateTime, IEnumerable<DateTime>> csharpFunc = (start, end) => { return new List<DateTime>() { new DateTime(2001, 3, 18) }; };
var funcDateRule = getFuncDateRule(csharpFunc);
Assert.AreEqual("CSharp", (funcDateRule.Name as PyObject).GetAndDispose<string>());
Assert.AreEqual(new DateTime(2001, 3, 18),
(funcDateRule.GetDates(new DateTime(2023, 1, 1), new DateTime(2023, 2, 1)) as PyObject).GetAndDispose<List<DateTime>>().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<ArgumentException>(() => new FuncDateRule("PythonFuncDateRule", pythonCustomDateRule));
}
}

private static DateRules GetDateRules()
{
var mhdb = MarketHoursDatabase.FromDataFolder();
Expand Down
54 changes: 54 additions & 0 deletions Tests/Common/Scheduling/TimeRulesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<DateTime>() { 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<DateTime>, IEnumerable<DateTime>> csharpFunc = (dates) => { return new List<DateTime>() { new DateTime(2001, 3, 18) }; };
var funcTimeRule = getFuncTimeRule(csharpFunc);
Assert.AreEqual("CSharp", (funcTimeRule.Name as PyObject).GetAndDispose<string>());
Assert.AreEqual(new DateTime(2001, 3, 18),
(funcTimeRule.CreateUtcEventTimes(new List<DateTime>() { new DateTime(2023, 1, 1) }) as PyObject).GetAndDispose<List<DateTime>>().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<ArgumentException>(() => new FuncTimeRule("PythonFuncTimeRule", pythonCustomTimeRule));
}
}

private static TimeRules GetTimeRules(DateTimeZone dateTimeZone)
{
var timeKeeper = new TimeKeeper(_utcNow, new List<DateTimeZone>());
Expand Down

0 comments on commit d76c2bf

Please sign in to comment.