Files
quantconnect--lean/Tests/Algorithm/AlgorithmGetParameterTests.cs
2026-07-13 13:02:50 +08:00

314 lines
14 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 System;
using System.Collections.Generic;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Algorithm;
namespace QuantConnect.Tests.Algorithm
{
[TestFixture]
public class AlgorithmGetParameterTests
{
[TestCase(Language.CSharp, "numeric_parameter", 2, false)]
[TestCase(Language.CSharp, "not_a_parameter", 2, true)]
[TestCase(Language.CSharp, "string_parameter", 2, true)]
[TestCase(Language.Python, "numeric_parameter", 2, false)]
[TestCase(Language.Python, "not_a_parameter", 2, true)]
[TestCase(Language.Python, "string_parameter", 2, true)]
public void GetParameterConvertsToNumericTypes(Language language, string parameterName, int defaultValue, bool shouldReturnDefaultValue)
{
var parameters = new Dictionary<string, string>
{
{ "numeric_parameter", "1" },
{ "string_parameter", "string value" },
};
var doubleDefaultValue = Convert.ToDouble(defaultValue);
var decimalDefaultValue = Convert.ToDecimal(defaultValue);
if (language == Language.CSharp)
{
var algorithm = new QCAlgorithm();
algorithm.SetParameters(parameters);
var intValue = algorithm.GetParameter(parameterName, defaultValue);
var doubleValue = algorithm.GetParameter(parameterName, doubleDefaultValue);
var decimalValue = algorithm.GetParameter(parameterName, decimalDefaultValue);
Assert.AreEqual(typeof(int), intValue.GetType());
Assert.AreEqual(typeof(double), doubleValue.GetType());
Assert.AreEqual(typeof(decimal), decimalValue.GetType());
// If the parameter is not found or is not numeric, the default value should be returned
if (!shouldReturnDefaultValue && parameters.TryGetValue(parameterName, out var parameterValue))
{
Assert.AreEqual(int.Parse(parameterValue), intValue);
Assert.AreEqual(double.Parse(parameterValue), doubleValue);
Assert.AreEqual(decimal.Parse(parameterValue), decimalValue);
}
else
{
Assert.AreEqual(defaultValue, intValue);
Assert.AreEqual(doubleDefaultValue, doubleValue);
Assert.AreEqual(decimalDefaultValue, decimalValue);
}
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def getAlgorithm():
return QCAlgorithm()
def isInt(value):
return isinstance(value, int)
def isFloat(value):
return isinstance(value, float)
");
var getAlgorithm = testModule.GetAttr("getAlgorithm");
var algorithm = getAlgorithm.Invoke();
algorithm.GetAttr("SetParameters").Invoke(PyDict.FromManagedObject(parameters));
var intValue = algorithm.GetAttr("GetParameter").Invoke(parameterName.ToPython(), defaultValue.ToPython());
var doubleValue = algorithm.GetAttr("GetParameter").Invoke(parameterName.ToPython(), doubleDefaultValue.ToPython());
var decimalValue = algorithm.GetAttr("GetParameter").Invoke(parameterName.ToPython(), decimalDefaultValue.ToPython());
Assert.IsTrue(testModule.GetAttr("isInt").Invoke(intValue).As<bool>(),
$"Expected 'intValue' to be of type int but was {intValue.GetPythonType().ToString()} instead");
Assert.IsTrue(testModule.GetAttr("isFloat").Invoke(doubleValue).As<bool>(),
$"Expected 'doubleValue' to be of type float but was {doubleValue.GetPythonType().ToString()} instead");
Assert.IsTrue(testModule.GetAttr("isFloat").Invoke(decimalValue).As<bool>(),
$"Expected 'decimalValue' to be of type float but was {decimalValue.GetPythonType().ToString()} instead");
// If the parameter is not found or is not numeric, the default value should be returned
if (!shouldReturnDefaultValue && parameters.TryGetValue(parameterName, out var parameterValue))
{
Assert.AreEqual(int.Parse(parameterValue), intValue.As<int>());
Assert.AreEqual(double.Parse(parameterValue), doubleValue.As<double>());
Assert.AreEqual(decimal.Parse(parameterValue), decimalValue.As<decimal>());
}
else
{
Assert.AreEqual(defaultValue, intValue.As<int>());
Assert.AreEqual(doubleDefaultValue, doubleValue.As<double>());
Assert.AreEqual(decimalDefaultValue, decimalValue.As<decimal>());
}
}
}
}
[TestCase(Language.CSharp, "numeric_parameter")]
[TestCase(Language.CSharp, "string_parameter")]
[TestCase(Language.CSharp, "not_a_parameter")]
[TestCase(Language.Python, "numeric_parameter")]
[TestCase(Language.Python, "string_parameter")]
[TestCase(Language.Python, "not_a_parameter")]
public void GetsParameterWithoutADefaultValue(Language language, string parameterName)
{
var parameters = new Dictionary<string, string>
{
{ "numeric_parameter", "1" },
{ "string_parameter", "string value" },
};
if (language == Language.CSharp)
{
var algorithm = new QCAlgorithm();
algorithm.SetParameters(parameters);
var parameterWithoutDefault = algorithm.GetParameter(parameterName);
var parameterWithNullDefault = algorithm.GetParameter(parameterName, null);
if (parameters.TryGetValue(parameterName, out var parameterValue))
{
Assert.AreEqual(typeof(string), parameterWithoutDefault.GetType());
Assert.AreEqual(typeof(string), parameterWithNullDefault.GetType());
Assert.AreEqual(parameterValue, parameterWithoutDefault);
Assert.AreEqual(parameterValue, parameterWithNullDefault);
}
else
{
Assert.IsNull(parameterWithoutDefault);
Assert.IsNull(parameterWithNullDefault);
}
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def getAlgorithm():
return QCAlgorithm()
def isString(value):
return isinstance(value, str)
");
dynamic getAlgorithm = testModule.GetAttr("getAlgorithm");
dynamic algorithm = getAlgorithm();
algorithm.SetParameters(PyDict.FromManagedObject(parameters));
dynamic parameterWithoutDefault = algorithm.GetParameter(parameterName.ToPython());
dynamic parameterWithNullDefault = algorithm.GetParameter(parameterName.ToPython(), null);
if (parameters.TryGetValue(parameterName, out var parameterValue))
{
dynamic isString = testModule.GetAttr("isString");
Assert.IsTrue(isString(parameterWithoutDefault).As<bool>(),
$"Expected 'parameterWithoutDefault' to be of type string but was {parameterWithoutDefault.GetPythonType().ToString()} instead");
Assert.IsTrue(isString(parameterWithNullDefault).As<bool>(),
$"Expected 'parameterWithNullDefault' to be of type string but was {parameterWithNullDefault.GetPythonType().ToString()} instead");
Assert.AreEqual(parameterValue, parameterWithoutDefault.As<string>());
Assert.AreEqual(parameterValue, parameterWithNullDefault.As<string>());
}
else
{
Assert.IsNull(parameterWithoutDefault);
Assert.IsNull(parameterWithNullDefault);
}
}
}
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void GetParameterCannotConvertoToNumberFromNonNumericValues(Language language)
{
var parameters = new Dictionary<string, string>
{
{ "date_parameter", "2022-8-8" },
{ "string_parameter", "string value" },
};
int defaultInt = 0;
double defaultDouble = 0;
decimal defaultDecimal = 0;
int intValue;
double doubleValue;
decimal decimalValue;
foreach (var parameterName in parameters.Keys)
{
if (language == Language.CSharp)
{
var algorithm = new QCAlgorithm();
algorithm.SetParameters(parameters);
intValue = algorithm.GetParameter(parameterName, defaultInt);
doubleValue = algorithm.GetParameter(parameterName, defaultDouble);
decimalValue = algorithm.GetParameter(parameterName, defaultDecimal);
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def getAlgorithm():
return QCAlgorithm()
");
var getAlgorithm = testModule.GetAttr("getAlgorithm");
dynamic algorithm = getAlgorithm.Invoke();
algorithm.GetAttr("SetParameters").Invoke(PyDict.FromManagedObject(parameters));
intValue = algorithm.GetParameter(parameterName.ToPython(), defaultInt.ToPython()).As<int>();
doubleValue = algorithm.GetParameter(parameterName.ToPython(), defaultDouble.ToPython()).As<double>();
decimalValue = algorithm.GetParameter(parameterName.ToPython(), defaultDecimal.ToPython()).As<decimal>();
}
}
Assert.AreEqual(defaultInt, intValue, $"Expected '{parameterName}' to be {defaultInt} but was {intValue} instead");
Assert.AreEqual(defaultDouble, doubleValue, $"Expected '{parameterName}' to be {defaultDouble} but was {doubleValue} instead");
Assert.AreEqual(defaultDecimal, decimalValue, $"Expected '{parameterName}' to be {defaultDecimal} but was {decimalValue} instead");
}
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void GetParameterConvertsToFloatingPointTypesFromDifferentFormats(Language language)
{
var parameters = new Dictionary<string, string>
{
{ "int_parameter", "1" },
{ "float_parameter", "1.0" },
{ "comma_parameter", "1,234.56" },
{ "scientific_parameter", "-1.643e6" },
};
var expectedDoubles = new Dictionary<string, double>
{
{ "int_parameter", 1.0 },
{ "float_parameter", 1.0 },
{ "comma_parameter", 1234.56 },
{ "scientific_parameter", -1643000.0 },
};
var expectedDecimals = new Dictionary<string, decimal>
{
{ "int_parameter", 1m },
{ "float_parameter", 1m },
{ "comma_parameter", 1234.56m },
{ "scientific_parameter", -1643000m },
};
double defaultDouble = 0;
decimal defaultDecimal = 0;
double doubleValue;
decimal decimalValue;
foreach (var parameterName in parameters.Keys)
{
if (language == Language.CSharp)
{
var algorithm = new QCAlgorithm();
algorithm.SetParameters(parameters);
doubleValue = algorithm.GetParameter(parameterName, defaultDouble);
decimalValue = algorithm.GetParameter(parameterName, defaultDecimal);
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def getAlgorithm():
return QCAlgorithm()
");
var getAlgorithm = testModule.GetAttr("getAlgorithm");
dynamic algorithm = getAlgorithm.Invoke();
algorithm.GetAttr("SetParameters").Invoke(PyDict.FromManagedObject(parameters));
doubleValue = algorithm.GetParameter(parameterName.ToPython(), defaultDouble.ToPython()).As<double>();
decimalValue = algorithm.GetParameter(parameterName.ToPython(), defaultDecimal.ToPython()).As<decimal>();
}
}
Assert.AreEqual(expectedDoubles[parameterName], doubleValue,
$"Expected '{parameterName}' to be {expectedDoubles[parameterName]} but was {doubleValue} instead");
Assert.AreEqual(expectedDecimals[parameterName], decimalValue,
$"Expected '{parameterName}' to be {expectedDecimals[parameterName]} but was {decimalValue} instead");
}
}
}
}