/*
* 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);
}
}
}
}
}