347 lines
16 KiB
C#
347 lines
16 KiB
C#
/*
|
|
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using NUnit.Framework;
|
|
using Python.Runtime;
|
|
using QuantConnect.Python;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Util;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace QuantConnect.Tests.Common.Util
|
|
{
|
|
[TestFixture]
|
|
public class PythonUtilTests
|
|
{
|
|
[TestCase(false)]
|
|
[TestCase(true)]
|
|
public void ToActionFailure(bool typeAnnotations)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var action = PyModule.FromString("ToAction", @"
|
|
from AlgorithmImports import *
|
|
|
|
def Test1():
|
|
pass
|
|
def Test2() -> None:
|
|
pass
|
|
");
|
|
var testMethod = action.GetAttr(typeAnnotations ? "Test2" : "Test1");
|
|
var result = PythonUtil.ToAction<SecurityType>(testMethod);
|
|
Assert.IsNull(result);
|
|
}
|
|
}
|
|
[TestCase(false)]
|
|
[TestCase(true)]
|
|
public void ToActionSuccess(bool typeAnnotations)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var action = PyModule.FromString("ToAction", @"
|
|
from AlgorithmImports import *
|
|
|
|
def Test1(securityType):
|
|
if securityType != SecurityType.Equity:
|
|
raise ValueError('Unexpected SecurityType!')
|
|
|
|
def Test2(securityType: SecurityType) -> None:
|
|
if securityType != SecurityType.Equity:
|
|
raise ValueError('Unexpected SecurityType!')
|
|
");
|
|
var testMethod = action.GetAttr(typeAnnotations ? "Test2" : "Test1");
|
|
var result = PythonUtil.ToAction<SecurityType>(testMethod);
|
|
|
|
Assert.IsNotNull(action);
|
|
Assert.DoesNotThrow(() => result(SecurityType.Equity));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void ConvertToSymbolsTest()
|
|
{
|
|
var expected = new List<Symbol>
|
|
{
|
|
Symbol.Create("AIG", SecurityType.Equity, Market.USA),
|
|
Symbol.Create("BAC", SecurityType.Equity, Market.USA),
|
|
Symbol.Create("IBM", SecurityType.Equity, Market.USA),
|
|
Symbol.Create("GOOG", SecurityType.Equity, Market.USA)
|
|
};
|
|
|
|
using (Py.GIL())
|
|
{
|
|
// Test Python String
|
|
using var pyString = new PyString("AIG");
|
|
var test1 = PythonUtil.ConvertToSymbols(pyString);
|
|
Assert.IsTrue(typeof(List<Symbol>) == test1.GetType());
|
|
Assert.AreEqual(expected.FirstOrDefault(), test1.FirstOrDefault());
|
|
|
|
// Test Python List of Strings
|
|
var list = (new List<string> { "AIG", "BAC", "IBM", "GOOG" }).ToPyList();
|
|
var test2 = PythonUtil.ConvertToSymbols(list);
|
|
Assert.IsTrue(typeof(List<Symbol>) == test2.GetType());
|
|
Assert.IsTrue(test2.SequenceEqual(expected));
|
|
|
|
// Test Python Symbol
|
|
var test3 = PythonUtil.ConvertToSymbols(expected.FirstOrDefault().ToPython());
|
|
Assert.IsTrue(typeof(List<Symbol>) == test3.GetType());
|
|
Assert.AreEqual(expected.FirstOrDefault(), test3.FirstOrDefault());
|
|
|
|
// Test Python List of Symbols
|
|
var test4 = PythonUtil.ConvertToSymbols(expected.ToPyList());
|
|
Assert.IsTrue(typeof(List<Symbol>) == test4.GetType());
|
|
Assert.IsTrue(test4.SequenceEqual(expected));
|
|
}
|
|
}
|
|
|
|
[TestCase("SyntaxError : invalid syntax (BasicTemplateAlgorithm.py, line 33)", "SyntaxError : invalid syntax (BasicTemplateAlgorithm.py, line 32)", 1)]
|
|
[TestCase("SyntaxError : invalid syntax (BasicTemplateAlgorithm.py, line 1)", "SyntaxError : invalid syntax (BasicTemplateAlgorithm.py, line 32)", -31)]
|
|
[TestCase("NameError : name 's' is not defined", "NameError : name 's' is not defined", -31)]
|
|
public void ParsesPythonExceptionMessage(string expected, string original, int shift)
|
|
{
|
|
var originalShiftValue = PythonUtil.ExceptionLineShift;
|
|
PythonUtil.ExceptionLineShift = shift;
|
|
var result = PythonUtil.PythonExceptionMessageParser(original);
|
|
|
|
PythonUtil.ExceptionLineShift = originalShiftValue;
|
|
Assert.AreEqual(expected, result);
|
|
}
|
|
|
|
[TestCase(@"
|
|
at Initialize
|
|
s
|
|
===
|
|
in BasicTemplateAlgorithm.py: line 20
|
|
",
|
|
@" File ""D:/QuantConnect/MyLean/Lean/Algorithm.Python\BasicTemplateAlgorithm.py"", line 30, in Initialize
|
|
s
|
|
===
|
|
at Python.Runtime.PyObject.Invoke(PyTuple args, PyDict kw)
|
|
at Python.Runtime.PyObject.InvokeMethod(String name, PyTuple args, PyDict kw)
|
|
at Python.Runtime.PyObject.TryInvokeMember(InvokeMemberBinder binder, Object[] args, Object& result)
|
|
at CallSite.Target(Closure , CallSite , Object )
|
|
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
|
|
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.Initialize() in D:\QuantConnect\MyLean\Lean\AlgorithmFactory\Python\Wrappers\AlgorithmPythonWrapper.cs:line 528
|
|
at QuantConnect.Lean.Engine.Setup.BacktestingSetupHandler.<>c__DisplayClass27_0.<Setup>b__0() in D:\QuantConnect\MyLean\Lean\Engine\Setup\BacktestingSetupHandler.cs:line 186",
|
|
-10)]
|
|
[TestCase(@"
|
|
at Initialize
|
|
self.SetEndDate(201, 1)
|
|
===
|
|
in BasicTemplateAlgorithm.py: line 40
|
|
",
|
|
@" File ""D:/QuantConnect/MyLean/Lean/Algorithm.Python\BasicTemplateAlgorithm.py"", line 30, in Initialize
|
|
self.SetEndDate(201, 1)
|
|
===
|
|
at Python.Runtime.PyObject.Invoke(PyTuple args, PyDict kw)
|
|
at Python.Runtime.PyObject.InvokeMethod(String name, PyTuple args, PyDict kw)
|
|
at Python.Runtime.PyObject.TryInvokeMember(InvokeMemberBinder binder, Object[] args, Object& result)
|
|
at CallSite.Target(Closure , CallSite , Object )
|
|
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
|
|
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.Initialize() in D:\QuantConnect\MyLean\Lean\AlgorithmFactory\Python\Wrappers\AlgorithmPythonWrapper.cs:line 528
|
|
at QuantConnect.Lean.Engine.Setup.BacktestingSetupHandler.<>c__DisplayClass27_0.<Setup>b__0() in D:\QuantConnect\MyLean\Lean\Engine\Setup\BacktestingSetupHandler.cs:line 186",
|
|
10)]
|
|
[TestCase(@"
|
|
at <module>
|
|
class BasicTemplateAlgorithm(QCAlgorithm):
|
|
in BasicTemplateAlgorithm.py: line 23
|
|
",
|
|
@" File ""D:/QuantConnect/MyLean/Lean/Algorithm.Python\BasicTemplateAlgorithm.py"", line 23, in <module>
|
|
class BasicTemplateAlgorithm(QCAlgorithm):
|
|
at Python.Runtime.PythonException.ThrowLastAsClrException()
|
|
at Python.Runtime.NewReferenceExtensions.BorrowOrThrow(NewReference& reference)
|
|
at Python.Runtime.PyModule.Import(String name)
|
|
at Python.Runtime.Py.Import(String name)
|
|
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper..ctor(String moduleName) in D:\QuantConnect\MyLean\Lean\AlgorithmFactory\Python\Wrappers\AlgorithmPythonWrapper.cs:line 74",
|
|
0)]
|
|
[TestCase(@"
|
|
at wrapped_function
|
|
raise KeyError(f""No key found for either mapped or original key. Mapped Key: {mKey}; Original Key: {oKey}"")
|
|
in PandasMapper.py: line 76
|
|
at HistoryCall
|
|
history.loc['QQQ'] # <--- raises Key Error
|
|
in TestAlgorithm.py: line 27
|
|
at OnData
|
|
self.HistoryCall()
|
|
in TestAlgorithm.py: line 23
|
|
",
|
|
@" File ""/home/user/QuantConnect/Lean/Launcher/bin/Debug/PandasMapper.py"", line 76, in wrapped_function
|
|
raise KeyError(f""No key found for either mapped or original key. Mapped Key: {mKey}; Original Key: {oKey}"")
|
|
File ""/home/user/QuantConnect/Lean/Algorithm.Python/TestAlgorithm.py"", line 27, in HistoryCall
|
|
history.loc['QQQ'] # <--- raises Key Error
|
|
File ""/home/user/QuantConnect/Lean/Algorithm.Python/TestAlgorithm.py"", line 23, in OnData
|
|
self.HistoryCall()
|
|
at Python.Runtime.PythonException.ThrowLastAsClrException()
|
|
at Python.Runtime.PyObject.Invoke(PyTuple args, PyDict kw)
|
|
at Python.Runtime.PyObject.TryInvoke(InvokeBinder binder, Object[] args, Object& result)
|
|
at CallSite.Target(Closure , CallSite , Object , PythonSlice )
|
|
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
|
|
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.OnData(Slice slice) in /home/user/QuantConnect/Lean/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs:line 587
|
|
at QuantConnect.Lean.Engine.AlgorithmManager.Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, IAlphaHandler alphas, CancellationToken token) in /home/user/QuantConnect/Lean/Engine/AlgorithmManager.cs:line 523",
|
|
0)]
|
|
[TestCase(@"
|
|
at OnData
|
|
raise ValueError(""""ASD"""")
|
|
in BasicTemplateAlgorithm.py: line 43
|
|
",
|
|
@" File ""D:\QuantConnect/MyLean/Lean/Algorithm.Python\BasicTemplateAlgorithm.py"", line 43, in OnData
|
|
raise ValueError(""""ASD"""")
|
|
at Python.Runtime.PythonException.ThrowLastAsClrException() in D:\QuantConnect\MyLean\pythonnet\src\runtime\PythonException.cs:line 52
|
|
at Python.Runtime.PyObject.Invoke(PyTuple args, PyDict kw) in D:\QuantConnect\MyLean\pythonnet\src\runtime\PythonTypes\PyObject.cs:line 837
|
|
at Python.Runtime.PyObject.TryInvoke(InvokeBinder binder, Object[] args, Object& result) in D:\QuantConnect\MyLean\pythonnet\src\runtime\PythonTypes\PyObject.cs:line 1320
|
|
at CallSite.Target(Closure , CallSite , Object , PythonSlice )
|
|
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
|
|
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.OnData(Slice slice) in D:\QuantConnect\MyLean\Lean\AlgorithmFactory\Python\Wrappers\AlgorithmPythonWrapper.cs:line 693
|
|
at QuantConnect.Lean.Engine.AlgorithmManager.Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, CancellationToken token) in D:\QuantConnect\MyLean\Lean\Engine\AlgorithmManager.cs:line 526",
|
|
0)]
|
|
[TestCase(@"
|
|
at on_data
|
|
self.set_holdings(""SPY"", 1 / None)
|
|
~~^~~~~~
|
|
in BasicTemplateAlgorithm.py: line 46
|
|
",
|
|
@" File ""C:\Users/user/QuantConnect/Lean/Algorithm.Python\BasicTemplateAlgorithm.py"", line 46, in on_data
|
|
self.set_holdings(""SPY"", 1 / None)
|
|
~~^~~~~~
|
|
at Python.Runtime.PythonException.ThrowLastAsClrException()
|
|
at Python.Runtime.PyObject.Invoke(PyTuple args, PyDict kw)
|
|
at Python.Runtime.PyObject.TryInvoke(InvokeBinder binder, Object[] args, Object& result)
|
|
at CallSite.Target(Closure , CallSite , Object , Slice )
|
|
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
|
|
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.OnData(Slice slice) in C:\Users\user\QuantConnect\Lean\AlgorithmFactory\Python\Wrappers\AlgorithmPythonWrapper.cs:line 759
|
|
at QuantConnect.Lean.Engine.AlgorithmManager.Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, CancellationToken token) in C:\Users\user\QuantConnect\Lean\Engine\AlgorithmManager.cs:line 524",
|
|
0)]
|
|
public void ParsesPythonExceptionStackTrace(string expected, string original, int shift)
|
|
{
|
|
var originalShiftValue = PythonUtil.ExceptionLineShift;
|
|
PythonUtil.ExceptionLineShift = shift;
|
|
var result = PythonUtil.PythonExceptionStackParser(original);
|
|
|
|
PythonUtil.ExceptionLineShift = originalShiftValue;
|
|
Assert.AreEqual(expected, result);
|
|
}
|
|
|
|
[TestCase(true)]
|
|
[TestCase(false)]
|
|
public void BothInheritedAndNonInheritedClassesWork(bool inherited)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
string pythonCode = @"
|
|
from AlgorithmImports import *
|
|
|
|
class PurePythonBuyingPowerModel:
|
|
def GetMaximumOrderQuantityForTargetBuyingPower(self, parameters):
|
|
return GetMaximumOrderQuantityResult(100)
|
|
|
|
def GetMaximumOrderQuantityForDeltaBuyingPower(self, parameters):
|
|
return GetMaximumOrderQuantityResult(200)
|
|
|
|
def HasSufficientBuyingPowerForOrder(self, parameters):
|
|
return HasSufficientBuyingPowerForOrderResult(True)
|
|
|
|
def GetReservedBuyingPowerForPosition(self, parameters):
|
|
return ReservedBuyingPowerForPosition(0)
|
|
|
|
def GetLeverage(self, security):
|
|
return 1.0
|
|
|
|
def GetBuyingPower(self, parameters):
|
|
return BuyingPower(1000)
|
|
|
|
def SetLeverage(self, security, leverage):
|
|
pass
|
|
|
|
def GetMaintenanceMargin(self, parameters):
|
|
return None
|
|
|
|
def GetInitialMarginRequirement(self, parameters):
|
|
return None
|
|
|
|
def GetInitialMarginRequiredForOrder(self, parameters):
|
|
return None
|
|
|
|
class InheritedBuyingPowerModel(SecurityMarginModel):
|
|
def GetMaximumOrderQuantityForTargetBuyingPower(self, parameters):
|
|
return GetMaximumOrderQuantityResult(200)
|
|
";
|
|
var module = PyModule.FromString("TestModels", pythonCode);
|
|
PyObject pyObject = null;
|
|
if (inherited)
|
|
{
|
|
pyObject = module.GetAttr("InheritedBuyingPowerModel").Invoke();
|
|
}
|
|
else
|
|
{
|
|
pyObject = module.GetAttr("PurePythonBuyingPowerModel").Invoke();
|
|
}
|
|
|
|
var result = PythonUtil.CreateInstanceOrWrapper<IBuyingPowerModel>(
|
|
pyObject,
|
|
py => new BuyingPowerModelPythonWrapper(py)
|
|
);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.IsInstanceOf<BuyingPowerModelPythonWrapper>(result);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void MissingRequiredMethodThrowsException()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
string pythonCode = @"
|
|
class IncompleteBuyingPowerModel:
|
|
def GetMaximumOrderQuantityForTargetBuyingPower(self, parameters):
|
|
return GetMaximumOrderQuantityResult(100)
|
|
|
|
def HasSufficientBuyingPowerForOrder(self, parameters):
|
|
return HasSufficientBuyingPowerForOrderResult(True)
|
|
";
|
|
var module = PyModule.FromString("TestModels", pythonCode);
|
|
var purePython = module.GetAttr("IncompleteBuyingPowerModel").Invoke();
|
|
|
|
Assert.Throws<NotImplementedException>(() =>
|
|
PythonUtil.CreateInstanceOrWrapper<IBuyingPowerModel>(purePython, py => new BuyingPowerModelPythonWrapper(py)));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void PureCSharpClassReturnsDirectInstanceWithoutWrapper()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
// Create pure C# instance
|
|
var csharpObject = new BuyingPowerModel();
|
|
var pyObject = csharpObject.ToPython();
|
|
|
|
// Should return the same C# instance, not a wrapper
|
|
var result = PythonUtil.CreateInstanceOrWrapper<IBuyingPowerModel>(
|
|
pyObject,
|
|
py => new BuyingPowerModelPythonWrapper(py)
|
|
);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreSame(csharpObject, result);
|
|
Assert.IsNotInstanceOf<BuyingPowerModelPythonWrapper>(result);
|
|
Assert.IsInstanceOf<BuyingPowerModel>(result);
|
|
}
|
|
}
|
|
}
|
|
}
|