/* * 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 Python.Runtime; using QuantConnect.Data.Fundamental; using QuantConnect.Data.UniverseSelection; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Util { /// /// Collection of utils for python objects processing /// public class PythonUtil { private static readonly Lazy lazyInspect = new Lazy(() => Py.Import("inspect")); /// /// 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_func"); return method(pyObject, typeof(T1), typeof(T2)).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 /// /// 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; } // 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); // Format the information in every line var lines = value.Substring(1, value.Length - 1) .Split(new[] { "\' File " }, StringSplitOptions.RemoveEmptyEntries) .Where(x => x.Contains(directory)) .Where(x => x.Split(',').Length > 2) .Select(x => { var info = x.Replace(directory, string.Empty).Split(','); var line = info[0].GetStringBetweenChars('\"', '\"'); line = $" in {line}:{info[1].Trim()}"; 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); 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.getargspec(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.getargspec(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 PythonEngine.ModuleFromString("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_func(pyobject, t1, t2):\n" + " return Func[t1, t2](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; // 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; } } }