/* * 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; 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 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}"); } } 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; } // Format the information in every line var lines = value.Substring(1, value.Length - 1) .Split(new[] { " File " }, StringSplitOptions.RemoveEmptyEntries) .Where(x => x.Split(',').Length > 2) .Select(x => { // Get the directory where the user files are located var baseScript = value.GetStringBetweenChars('\"', '\"'); var length = Math.Max(baseScript.LastIndexOf('/'), baseScript.LastIndexOf('\\')); if (length < 0) { return string.Empty; } var directory = baseScript.Substring(0, 1 + length); var info = x.Replace(directory, string.Empty).Split(','); var line = info[0].GetStringBetweenChars('\"', '\"'); var lineNumber = int.Parse(info[1].Replace("line", string.Empty).Trim()) + ExceptionLineShift; line = $" in {line}: line {lineNumber}"; info = info[2].Split(new[] { "\\n" }, StringSplitOptions.RemoveEmptyEntries); line = $" {info[0].Replace(" in ", " at ")}{line}"; // If we have the exact statement, add it to the error line if (info.Length > 2) line += $" :: {info[1].Trim()}"; return line; }); var errorLine = string.Join(Environment.NewLine, lines); errorLine = Extensions.ClearLeanPaths(errorLine); return string.IsNullOrWhiteSpace(errorLine) ? string.Empty : $"{Environment.NewLine}{errorLine}{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; } } }