/* * 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.Linq; using Python.Runtime; using System.Collections.Generic; using QuantConnect.Data.Fundamental; using System.Text.RegularExpressions; using QuantConnect.Data.UniverseSelection; using System.IO; using System.Globalization; namespace QuantConnect.Util { /// /// Collection of utils for python objects processing /// public class PythonUtil { private static Regex LineRegex = new Regex("line (\\d+)", RegexOptions.Compiled); private static Regex StackTraceFileLineRegex = new Regex("\"(.+)\", line (\\d+), in (.+)", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Lazy lazyInspect = new Lazy(() => Py.Import("inspect")); /// /// The python exception stack trace line shift to use /// public static int ExceptionLineShift { get; set; } = 0; /// /// Encapsulates a python method with a /// /// The input type /// The python method /// A that encapsulates the python method public static Action ToAction(PyObject pyObject) { using (Py.GIL()) { long count = 0; if (!TryGetArgLength(pyObject, out count) || count != 1) { return null; } dynamic method = GetModule().GetAttr("to_action1"); return method(pyObject, typeof(T1)).AsManagedObject(typeof(Action)); } } /// /// Encapsulates a python method with a /// /// The first input type /// The second input type type /// The python method /// A that encapsulates the python method public static Action ToAction(PyObject pyObject) { using (Py.GIL()) { long count = 0; if (!TryGetArgLength(pyObject, out count) || count != 2) { return null; } dynamic method = GetModule().GetAttr("to_action2"); return method(pyObject, typeof(T1), typeof(T2)).AsManagedObject(typeof(Action)); } } /// /// Encapsulates a python method with a /// /// The data type /// The output type /// The python method /// A that encapsulates the python method public static Func ToFunc(PyObject pyObject) { using (Py.GIL()) { long count = 0; if (!TryGetArgLength(pyObject, out count) || count != 1) { return null; } dynamic method = GetModule().GetAttr("to_func1"); return method(pyObject, typeof(T1), typeof(T2)).AsManagedObject(typeof(Func)); } } /// /// Encapsulates a python method with a /// /// The first argument's type /// The first argument's type /// The output type /// The python method /// A that encapsulates the python method public static Func ToFunc(PyObject pyObject) { using (Py.GIL()) { long count = 0; if (!TryGetArgLength(pyObject, out count) || count != 2) { return null; } dynamic method = GetModule().GetAttr("to_func2"); return method(pyObject, typeof(T1), typeof(T2), typeof(T3)).AsManagedObject(typeof(Func)); } } /// /// Encapsulates a python method in coarse fundamental universe selector. /// /// The python method /// A (parameter is , return value is ) that encapsulates the python method public static Func, IEnumerable> ToCoarseFundamentalSelector(PyObject pyObject) { var selector = ToFunc, Symbol[]>(pyObject); if (selector == null) { using (Py.GIL()) { throw new ArgumentException($"{pyObject.Repr()} is not a valid coarse fundamental universe selector method."); } } return selector; } /// /// Encapsulates a python method in fine fundamental universe selector. /// /// The python method /// A (parameter is , return value is ) that encapsulates the python method public static Func, IEnumerable> ToFineFundamentalSelector(PyObject pyObject) { var selector = ToFunc, Symbol[]>(pyObject); if (selector == null) { using (Py.GIL()) { throw new ArgumentException($"{pyObject.Repr()} is not a valid fine fundamental universe selector method."); } } return selector; } /// /// Parsers into a readable message /// /// The exception to parse /// String with relevant part of the stacktrace public static string PythonExceptionParser(PythonException pythonException) { return PythonExceptionMessageParser(pythonException.Message) + PythonExceptionStackParser(pythonException.StackTrace); } /// /// Parsers into a readable message /// /// The python exception message /// String with relevant part of the stacktrace public static string PythonExceptionMessageParser(string message) { var match = LineRegex.Match(message); if (match.Success) { foreach (Match lineCapture in match.Captures) { var newLineNumber = int.Parse(lineCapture.Groups[1].Value) + ExceptionLineShift; message = Regex.Replace(message, lineCapture.ToString(), $"line {newLineNumber}"); } } else if (message.Contains(" value cannot be converted to ", StringComparison.InvariantCulture)) { message += ": This error is often encountered when assigning to a member defined in the base QCAlgorithm class. For example, self.universe conflicts with 'QCAlgorithm.Universe' but can be fixed by prefixing private variables with an underscore, self._universe."; } return message; } /// /// Parsers into a readable message /// /// String with the stacktrace information /// String with relevant part of the stacktrace public static string PythonExceptionStackParser(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } // The stack trace info before "at Python.Runtime." is the trace we want, // which is for user Python code. var endIndex = value.IndexOf("at Python.Runtime.", StringComparison.InvariantCulture); var neededStackTrace = endIndex > 0 ? value.Substring(0, endIndex) : value; // The stack trace is separated in blocks by file var blocks = neededStackTrace.Split(" File ", StringSplitOptions.RemoveEmptyEntries) .Select(fileTrace => { var trimedTrace = fileTrace.Trim(); if (string.IsNullOrWhiteSpace(trimedTrace)) { return string.Empty; } var match = StackTraceFileLineRegex.Match(trimedTrace); if (!match.Success) { return string.Empty; } var capture = match.Captures[0] as Match; var filePath = capture.Groups[1].Value; var lastFileSeparatorIndex = Math.Max(filePath.LastIndexOf('/'), filePath.LastIndexOf('\\')); if (lastFileSeparatorIndex < 0) { return string.Empty; } var fileName = filePath.Substring(lastFileSeparatorIndex + 1); var lineNumber = int.Parse(capture.Groups[2].Value, CultureInfo.InvariantCulture) + ExceptionLineShift; var locationAndInfo = capture.Groups[3].Value.Trim(); return $" at {locationAndInfo}{Environment.NewLine} in {fileName}: line {lineNumber}"; }) .Where(x => !string.IsNullOrWhiteSpace(x)); var result = string.Join(Environment.NewLine, blocks); result = Logging.Log.ClearLeanPaths(result); return string.IsNullOrWhiteSpace(result) ? string.Empty : $"{Environment.NewLine}{result}{Environment.NewLine}"; } /// /// Try to get the length of arguments of a method /// /// Object representing a method /// Lenght of arguments /// True if pyObject is a method private static bool TryGetArgLength(PyObject pyObject, out long length) { using (Py.GIL()) { var inspect = lazyInspect.Value; if (inspect.isfunction(pyObject)) { var args = inspect.getfullargspec(pyObject).args as PyObject; var pyList = new PyList(args); length = pyList.Length(); pyList.Dispose(); args.Dispose(); return true; } if (inspect.ismethod(pyObject)) { var args = inspect.getfullargspec(pyObject).args as PyObject; var pyList = new PyList(args); length = pyList.Length() - 1; pyList.Dispose(); args.Dispose(); return true; } } length = 0; return false; } /// /// Creates a python module with utils methods /// /// PyObject with a python module private static PyObject GetModule() { return PyModule.FromString("x", "from clr import AddReference\n" + "AddReference(\"System\")\n" + "from System import Action, Func\n" + "def to_action1(pyobject, t1):\n" + " return Action[t1](pyobject)\n" + "def to_action2(pyobject, t1, t2):\n" + " return Action[t1, t2](pyobject)\n" + "def to_func1(pyobject, t1, t2):\n" + " return Func[t1, t2](pyobject)\n" + "def to_func2(pyobject, t1, t2, t3):\n" + " return Func[t1, t2, t3](pyobject)"); } /// /// Convert Python input to a list of Symbols /// /// Object with the desired property /// List of Symbols public static IEnumerable ConvertToSymbols(PyObject input) { List symbolsList; Symbol symbol; using (Py.GIL()) { // Handle the possible types of conversions if (PyList.IsListType(input)) { List symbolsStringList; //Check if an entry in the list is a string type, if so then try and convert the whole list if (PyString.IsStringType(input[0]) && input.TryConvert(out symbolsStringList)) { symbolsList = new List(); foreach (var stringSymbol in symbolsStringList) { symbol = QuantConnect.Symbol.Create(stringSymbol, SecurityType.Equity, Market.USA); symbolsList.Add(symbol); } } //Try converting it to list of symbols, if it fails throw exception else if (!input.TryConvert(out symbolsList)) { throw new ArgumentException($"Cannot convert list {input.Repr()} to symbols"); } } else { //Check if its a single string, and try and convert it string symbolString; if (PyString.IsStringType(input) && input.TryConvert(out symbolString)) { symbol = QuantConnect.Symbol.Create(symbolString, SecurityType.Equity, Market.USA); symbolsList = new List { symbol }; } else if (input.TryConvert(out symbol)) { symbolsList = new List { symbol }; } else { throw new ArgumentException($"Cannot convert object {input.Repr()} to symbol"); } } } return symbolsList; } } }