/* * 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; using QuantConnect.Data.Market; using QuantConnect.Indicators; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Python { /// /// Collection of methods that converts lists of objects in pandas.DataFrame /// public class PandasConverter { private static dynamic _pandas; /// /// Creates an instance of . /// public PandasConverter() { if (_pandas == null) { using (Py.GIL()) { _pandas = Py.Import("pandas"); } } } /// /// Converts an enumerable of in a pandas.DataFrame /// /// Enumerable of /// Optional type of bars to add to the data frame /// containing a pandas.DataFrame public PyObject GetDataFrame(IEnumerable data, Type dataType = null) { var maxLevels = 0; var sliceDataDict = new Dictionary(); foreach (var slice in data) { if (dataType == null) { AddSliceDataToDict(slice, sliceDataDict, ref maxLevels); } else { AddSliceDataTypeDataToDict(slice, dataType, sliceDataDict, ref maxLevels); } } using (Py.GIL()) { if (sliceDataDict.Count == 0) { return _pandas.DataFrame(); } var dataFrames = sliceDataDict.Select(x => x.Value.ToPandasDataFrame(maxLevels)); return _pandas.concat(dataFrames.ToArray(), Py.kw("sort", true)); } } /// /// Converts an enumerable of in a pandas.DataFrame /// /// Enumerable of /// containing a pandas.DataFrame public PyObject GetDataFrame(IEnumerable data) where T : IBaseData { PandasData sliceData = null; foreach (var datum in data) { if (sliceData == null) { sliceData = new PandasData(datum); } sliceData.Add(datum); } using (Py.GIL()) { // If sliceData is still null, data is an empty enumerable // returns an empty pandas.DataFrame if (sliceData == null) { return _pandas.DataFrame(); } return sliceData.ToPandasDataFrame(); } } /// /// Converts a dictionary with a list of in a pandas.DataFrame /// /// Dictionary with a list of /// containing a pandas.DataFrame public PyObject GetIndicatorDataFrame(IDictionary> data) { using (Py.GIL()) { var pyDict = new PyDict(); foreach (var kvp in data) { AddSeriesToPyDict(kvp.Key, kvp.Value, pyDict); } return MakeIndicatorDataFrame(pyDict); } } /// /// Converts a dictionary with a list of in a pandas.DataFrame /// /// that should be a dictionary (convertible to PyDict) of string to list of /// containing a pandas.DataFrame public PyObject GetIndicatorDataFrame(PyObject data) { using (Py.GIL()) { using var inputPythonType = data.GetPythonType(); var inputTypeStr = inputPythonType.ToString(); var targetTypeStr = nameof(PyDict); PyObject currentKvp = null; try { using var pyDictData = new PyDict(data); using var seriesPyDict = new PyDict(); targetTypeStr = $"{nameof(String)}: {nameof(List)}"; foreach (var kvp in pyDictData.Items()) { currentKvp = kvp; AddSeriesToPyDict(kvp[0].As(), kvp[1].As>(), seriesPyDict); } return MakeIndicatorDataFrame(seriesPyDict); } catch (Exception e) { if (currentKvp != null) { inputTypeStr = $"{currentKvp[0].GetPythonType()}: {currentKvp[1].GetPythonType()}"; } throw new ArgumentException( $"ConvertToDictionary cannot be used to convert a {inputTypeStr} into {targetTypeStr}. Reason: {e.Message}", e ); } } } /// /// Returns a string that represent the current object /// /// public override string ToString() { return _pandas == null ? "pandas module was not imported." : _pandas.Repr(); } /// /// Creates a series from a list of and adds it to the /// as the value of the given /// /// Key to insert in the /// List of that will make up the resulting series /// where the resulting key-value pair will be inserted into private void AddSeriesToPyDict(string key, List points, PyDict pyDict) { var index = new List(); var values = new List(); foreach (var point in points) { index.Add(point.EndTime); values.Add((double) point.Value); } pyDict.SetItem(key.ToLowerInvariant(), _pandas.Series(values, index)); } /// /// Converts a of string to pandas.Series in a pandas.DataFrame /// /// of string to pandas.Series /// containing a pandas.DataFrame private PyObject MakeIndicatorDataFrame(PyDict pyDict) { return _pandas.DataFrame(pyDict, columns: pyDict.Keys().Select(x => x.As().ToLowerInvariant()).OrderBy(x => x)); } /// /// Gets the for the given symbol if it exists in the dictionary, otherwise it creates a new instance with the /// given base data and adds it to the dictionary /// private PandasData GetPandasDataValue(IDictionary sliceDataDict, Symbol symbol, object data, ref int maxLevels) { PandasData value; if (!sliceDataDict.TryGetValue(symbol, out value)) { sliceDataDict.Add(symbol, value = new PandasData(data)); maxLevels = Math.Max(maxLevels, value.Levels); } return value; } /// /// Adds each slice data to the pandas data dictionary /// private void AddSliceDataToDict(Slice slice, IDictionary sliceDataDict, ref int maxLevels) { foreach (var key in slice.Keys) { var baseData = slice[key]; var value = GetPandasDataValue(sliceDataDict, key, baseData, ref maxLevels); if (value.IsCustomData) { value.Add(baseData); } else { var ticks = slice.Ticks.ContainsKey(key) ? slice.Ticks[key] : null; var tradeBars = slice.Bars.ContainsKey(key) ? slice.Bars[key] : null; var quoteBars = slice.QuoteBars.ContainsKey(key) ? slice.QuoteBars[key] : null; value.Add(ticks, tradeBars, quoteBars); } } } /// /// Adds each slice data corresponding to the requested data type to the pandas data dictionary /// private void AddSliceDataTypeDataToDict(Slice slice, Type dataType, IDictionary sliceDataDict, ref int maxLevels) { var isTick = dataType == typeof(Tick) || dataType == typeof(OpenInterest); // Access ticks directly since slice.Get(typeof(Tick)) and slice.Get(typeof(OpenInterest)) will return only the last tick var sliceData = isTick ? slice.Ticks : slice.Get(dataType); foreach (var key in sliceData.Keys) { var baseData = sliceData[key]; PandasData value = GetPandasDataValue(sliceDataDict, key, baseData, ref maxLevels); if (value.IsCustomData) { value.Add(baseData); } else { var ticks = isTick ? baseData : null; var tradeBars = dataType == typeof(TradeBar) ? baseData : null; var quoteBars = dataType == typeof(QuoteBar) ? baseData : null; value.Add(ticks, tradeBars, quoteBars); } } } } }