diff --git a/Algorithm.Framework/Alphas/BasePairsTradingAlphaModel.py b/Algorithm.Framework/Alphas/BasePairsTradingAlphaModel.py
index cfd9bee2f88a..477f6396fb57 100644
--- a/Algorithm.Framework/Alphas/BasePairsTradingAlphaModel.py
+++ b/Algorithm.Framework/Alphas/BasePairsTradingAlphaModel.py
@@ -47,7 +47,7 @@ def __init__(self, lookback = 1,
self.Securities = list()
resolutionString = Extensions.GetEnumString(resolution, Resolution)
- self.Name = f'{self.__class__.__name__}({self.lookback},{resolutionString},{Extensions.Normalize(threshold)})'
+ self.Name = f'{self.__class__.__name__}({self.lookback},{resolutionString},{Extensions.NormalizeToStr(threshold)})'
def Update(self, algorithm, data):
diff --git a/Algorithm.Framework/Execution/StandardDeviationExecutionModel.py b/Algorithm.Framework/Execution/StandardDeviationExecutionModel.py
index 78b4c2e3228b..4cfcc58f5c74 100644
--- a/Algorithm.Framework/Execution/StandardDeviationExecutionModel.py
+++ b/Algorithm.Framework/Execution/StandardDeviationExecutionModel.py
@@ -80,8 +80,16 @@ def Execute(self, algorithm, targets):
maxOrderSize = OrderSizing.Value(data.Security, self.MaximumOrderValue)
orderSize = np.min([maxOrderSize, np.abs(unorderedQuantity)])
+ remainder = orderSize % data.Security.SymbolProperties.LotSize
+ missingForLotSize = data.Security.SymbolProperties.LotSize - remainder
+ # if the amount we are missing for +1 lot size is 1M part of a lot size
+ # we suppose its due to floating point error and round up
+ # Note: this is required to avoid a diff with C# equivalent
+ if missingForLotSize < (data.Security.SymbolProperties.LotSize / 1000000):
+ remainder -= data.Security.SymbolProperties.LotSize
+
# round down to even lot size
- orderSize -= orderSize % data.Security.SymbolProperties.LotSize
+ orderSize -= remainder
if orderSize != 0:
algorithm.MarketOrder(symbol, np.sign(unorderedQuantity) * orderSize)
diff --git a/Algorithm.Framework/Execution/VolumeWeightedAveragePriceExecutionModel.py b/Algorithm.Framework/Execution/VolumeWeightedAveragePriceExecutionModel.py
index 4046245250d4..68824384b51a 100644
--- a/Algorithm.Framework/Execution/VolumeWeightedAveragePriceExecutionModel.py
+++ b/Algorithm.Framework/Execution/VolumeWeightedAveragePriceExecutionModel.py
@@ -72,8 +72,16 @@ def Execute(self, algorithm, targets):
maxOrderSize = OrderSizing.PercentVolume(data.Security, self.MaximumOrderQuantityPercentVolume)
orderSize = np.min([maxOrderSize, np.abs(unorderedQuantity)])
+ remainder = orderSize % data.Security.SymbolProperties.LotSize
+ missingForLotSize = data.Security.SymbolProperties.LotSize - remainder
+ # if the amount we are missing for +1 lot size is 1M part of a lot size
+ # we suppose its due to floating point error and round up
+ # Note: this is required to avoid a diff with C# equivalent
+ if missingForLotSize < (data.Security.SymbolProperties.LotSize / 1000000):
+ remainder -= data.Security.SymbolProperties.LotSize
+
# round down to even lot size
- orderSize -= orderSize % data.Security.SymbolProperties.LotSize
+ orderSize -= remainder
if orderSize != 0:
algorithm.MarketOrder(symbol, np.sign(unorderedQuantity) * orderSize)
diff --git a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
index 8a2c264dbded..361a0daa071e 100644
--- a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
+++ b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj
@@ -41,6 +41,7 @@
+
diff --git a/Algorithm.Python/decimal.py b/Algorithm.Python/decimal.py
new file mode 100644
index 000000000000..b7b4f3e14dbe
--- /dev/null
+++ b/Algorithm.Python/decimal.py
@@ -0,0 +1,18 @@
+# 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.
+
+class Decimal(float):
+ '''This is required for backwards compatibility with users performing operations over expected decimal types.
+ NOTE: previously we converted C# decimal into python Decimal, but now its converted to python float due to performance.'''
+ def __init__(self, obj):
+ self = obj
\ No newline at end of file
diff --git a/Common/Extensions.cs b/Common/Extensions.cs
index 205fdbecc2a0..6135dbd17841 100644
--- a/Common/Extensions.cs
+++ b/Common/Extensions.cs
@@ -235,12 +235,31 @@ public static decimal SafeDecimalCast(this double input)
return (decimal) input;
}
+ ///
+ /// Will remove any trailing zeros for the provided decimal input
+ ///
+ /// The to remove trailing zeros from
+ /// Provided input with no trailing zeros
+ /// Will not have the expected behavior when called from Python,
+ /// since the returned will be converted to python float,
+ ///
public static decimal Normalize(this decimal input)
{
// http://stackoverflow.com/a/7983330/1582922
return input / 1.000000000000000000000000000000000m;
}
+ ///
+ /// Will remove any trailing zeros for the provided decimal and convert to string.
+ /// Uses .
+ ///
+ /// The to convert to
+ /// Input converted to with no trailing zeros
+ public static string NormalizeToStr(this decimal input)
+ {
+ return Normalize(input).ToString(CultureInfo.InvariantCulture);
+ }
+
///
/// Extension method for faster string to decimal conversion.
///
diff --git a/Tests/Common/Exceptions/UnsupportedOperandPythonExceptionInterpreterTests.cs b/Tests/Common/Exceptions/UnsupportedOperandPythonExceptionInterpreterTests.cs
index 6a2e843e9a26..31563ed4de0f 100644
--- a/Tests/Common/Exceptions/UnsupportedOperandPythonExceptionInterpreterTests.cs
+++ b/Tests/Common/Exceptions/UnsupportedOperandPythonExceptionInterpreterTests.cs
@@ -37,7 +37,7 @@ public void Setup()
try
{
- // x = decimal.Decimal(1) * 1.1
+ // x = None + "Pepe Grillo"
algorithm.unsupported_operand();
}
catch (PythonException pythonException)
@@ -80,7 +80,7 @@ public void VerifyMessageContainsStackTraceInformation()
var assembly = typeof(PythonExceptionInterpreter).Assembly;
var interpreter = StackExceptionInterpreter.CreateFromAssemblies(new[] { assembly });
exception = interpreter.Interpret(exception, NullExceptionInterpreter.Instance);
- Assert.True(exception.Message.Contains("x = decimal.Decimal(1) * 1.1"));
+ Assert.True(exception.Message.Contains("x = None + \"Pepe Grillo\""));
}
private Exception CreateExceptionFromType(Type type) => type == typeof(PythonException) ? _pythonException : (Exception)Activator.CreateInstance(type);
diff --git a/Tests/Common/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs
index 035f252abe74..3d305491f0c1 100644
--- a/Tests/Common/Util/ExtensionsTests.cs
+++ b/Tests/Common/Util/ExtensionsTests.cs
@@ -700,7 +700,7 @@ public void PyObjectTryConvertToAction2()
}
catch (PythonException e)
{
- Assert.AreEqual($"ValueError : {6}", e.Message);
+ Assert.AreEqual("ValueError : 6.0", e.Message);
}
}
diff --git a/Tests/RegressionAlgorithms/Test_PythonExceptionInterpreter.py b/Tests/RegressionAlgorithms/Test_PythonExceptionInterpreter.py
index 7df6e29c3178..5f6ef80f1a59 100644
--- a/Tests/RegressionAlgorithms/Test_PythonExceptionInterpreter.py
+++ b/Tests/RegressionAlgorithms/Test_PythonExceptionInterpreter.py
@@ -38,7 +38,7 @@ def no_method_match(self):
self.SetCash('SPY')
def unsupported_operand(self):
- x = decimal.Decimal(1) * 1.1
+ x = None + "Pepe Grillo"
def zero_division_error(self):
x = 1 / 0
\ No newline at end of file