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