593 lines
30 KiB
C#
593 lines
30 KiB
C#
/*
|
|
* 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.Algorithm;
|
|
using QuantConnect.Configuration;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Fundamental;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Indicators;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Lean.Engine;
|
|
using QuantConnect.Lean.Engine.DataFeeds;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Securities.Future;
|
|
using QuantConnect.Securities.Option;
|
|
using QuantConnect.Statistics;
|
|
using QuantConnect.Util;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using QuantConnect.Packets;
|
|
|
|
namespace QuantConnect.Jupyter
|
|
{
|
|
/// <summary>
|
|
/// Provides access to data for quantitative analysis
|
|
/// </summary>
|
|
public class QuantBook : QCAlgorithm
|
|
{
|
|
private dynamic _pandas;
|
|
private IDataCacheProvider _dataCacheProvider;
|
|
|
|
/// <summary>
|
|
/// <see cref = "QuantBook" /> constructor.
|
|
/// Provides access to data for quantitative analysis
|
|
/// </summary>
|
|
public QuantBook() : base()
|
|
{
|
|
try
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
_pandas = Py.Import("pandas");
|
|
}
|
|
|
|
// By default, set start date to end data which is yesterday
|
|
SetStartDate(EndDate);
|
|
|
|
// Sets PandasConverter
|
|
SetPandasConverter();
|
|
|
|
// Initialize History Provider
|
|
var composer = new Composer();
|
|
var algorithmHandlers = LeanEngineAlgorithmHandlers.FromConfiguration(composer);
|
|
var systemHandlers = LeanEngineSystemHandlers.FromConfiguration(composer);
|
|
systemHandlers.LeanManager.Initialize(systemHandlers,
|
|
algorithmHandlers,
|
|
new BacktestNodePacket(),
|
|
new AlgorithmManager(false));
|
|
systemHandlers.LeanManager.SetAlgorithm(this);
|
|
|
|
_dataCacheProvider = new ZipDataCacheProvider(algorithmHandlers.DataProvider);
|
|
|
|
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
|
|
var securityService = new SecurityService(Portfolio.CashBook, MarketHoursDatabase, symbolPropertiesDataBase, this);
|
|
Securities.SetSecurityService(securityService);
|
|
SubscriptionManager.SetDataManager(
|
|
new DataManager(new NullDataFeed(),
|
|
new UniverseSelection(this, securityService),
|
|
this,
|
|
TimeKeeper,
|
|
MarketHoursDatabase,
|
|
false));
|
|
|
|
var mapFileProvider = algorithmHandlers.MapFileProvider;
|
|
HistoryProvider = composer.GetExportedValueByTypeName<IHistoryProvider>(Config.Get("history-provider", "SubscriptionDataReaderHistoryProvider"));
|
|
HistoryProvider.Initialize(
|
|
new HistoryProviderInitializeParameters(
|
|
null,
|
|
null,
|
|
algorithmHandlers.DataProvider,
|
|
_dataCacheProvider,
|
|
mapFileProvider,
|
|
algorithmHandlers.FactorFileProvider,
|
|
null
|
|
)
|
|
);
|
|
|
|
SetOptionChainProvider(new CachingOptionChainProvider(new BacktestingOptionChainProvider()));
|
|
SetFutureChainProvider(new CachingFutureChainProvider(new BacktestingFutureChainProvider()));
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
throw new Exception("QuantBook.Main(): " + exception);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get fundamental data from given symbols
|
|
/// </summary>
|
|
/// <param name="pyObject">The symbols to retrieve fundamental data for</param>
|
|
/// <param name="selector">Selects a value from the Fundamental data to filter the request output</param>
|
|
/// <param name="start">The start date of selected data</param>
|
|
/// <param name="end">The end date of selected data</param>
|
|
/// <returns></returns>
|
|
public PyObject GetFundamental(PyObject tickers, string selector, DateTime? start = null, DateTime? end = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(selector))
|
|
{
|
|
return "Invalid selector. Cannot be None, empty or consist only of white-space characters".ToPython();
|
|
}
|
|
|
|
using (Py.GIL())
|
|
{
|
|
// If tickers are not a PyList, we create one
|
|
if (!PyList.IsListType(tickers))
|
|
{
|
|
var tmp = new PyList();
|
|
tmp.Append(tickers);
|
|
tickers = tmp;
|
|
}
|
|
|
|
var list = new List<Tuple<Symbol, DateTime, object>>();
|
|
|
|
foreach (var ticker in tickers)
|
|
{
|
|
var symbol = QuantConnect.Symbol.Create(ticker.ToString(), SecurityType.Equity, Market.USA);
|
|
var dir = new DirectoryInfo(Path.Combine(Globals.DataFolder, "equity", symbol.ID.Market, "fundamental", "fine", symbol.Value.ToLower()));
|
|
if (!dir.Exists) continue;
|
|
|
|
var config = new SubscriptionDataConfig(typeof(FineFundamental), symbol, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
|
|
|
|
foreach (var fileName in dir.EnumerateFiles())
|
|
{
|
|
var date = DateTime.ParseExact(fileName.Name.Substring(0, 8), DateFormat.EightCharacter, CultureInfo.InvariantCulture);
|
|
if (date < start || date > end) continue;
|
|
|
|
var factory = new TextSubscriptionDataSourceReader(_dataCacheProvider, config, date, false);
|
|
var source = new SubscriptionDataSource(fileName.FullName, SubscriptionTransportMedium.LocalFile);
|
|
var value = factory.Read(source).Select(x => GetPropertyValue(x, selector)).First();
|
|
|
|
list.Add(Tuple.Create(symbol, date, value));
|
|
}
|
|
}
|
|
|
|
var data = new PyDict();
|
|
foreach (var item in list.GroupBy(x => x.Item1))
|
|
{
|
|
var index = item.Select(x => x.Item2);
|
|
data.SetItem(item.Key, _pandas.Series(item.Select(x => x.Item3).ToList(), index));
|
|
}
|
|
|
|
return _pandas.DataFrame(data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets <see cref="OptionHistory"/> object for a given symbol, date and resolution
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical option data for</param>
|
|
/// <param name="date">Date of the data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>A <see cref="OptionHistory"/> object that contains historical option data.</returns>
|
|
public OptionHistory GetOptionHistory(Symbol symbol, DateTime start, DateTime? end = null, Resolution? resolution = null)
|
|
{
|
|
if (!end.HasValue || end.Value.Date == start.Date)
|
|
{
|
|
end = start.AddDays(1);
|
|
}
|
|
|
|
var option = Securities[symbol] as Option;
|
|
var resolutionToUseForUnderlying = resolution ?? SubscriptionManager.SubscriptionDataConfigService
|
|
.GetSubscriptionDataConfigs(symbol)
|
|
.GetHighestResolution();
|
|
var underlying = AddEquity(symbol.Underlying.Value, resolutionToUseForUnderlying);
|
|
|
|
var allSymbols = new List<Symbol>();
|
|
for (var date = start; date < end; date = date.AddDays(1))
|
|
{
|
|
if (option.Exchange.DateIsOpen(date))
|
|
{
|
|
allSymbols.AddRange(OptionChainProvider.GetOptionContractList(symbol.Underlying, date));
|
|
}
|
|
}
|
|
var symbols = base.History(symbol.Underlying, start, end.Value, resolution)
|
|
.SelectMany(x => option.ContractFilter.Filter(new OptionFilterUniverse(allSymbols.Distinct(), x)))
|
|
.Distinct().Concat(new[] { symbol.Underlying });
|
|
|
|
return new OptionHistory(History(symbols, start, end.Value, resolution));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets <see cref="FutureHistory"/> object for a given symbol, date and resolution
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical future data for</param>
|
|
/// <param name="start">Date of the data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <returns>A <see cref="FutureHistory"/> object that contains historical future data.</returns>
|
|
public FutureHistory GetFutureHistory(Symbol symbol, DateTime start, DateTime? end = null, Resolution? resolution = null)
|
|
{
|
|
if (!end.HasValue || end.Value.Date == start.Date)
|
|
{
|
|
end = start.AddDays(1);
|
|
}
|
|
|
|
var future = Securities[symbol] as Future;
|
|
|
|
var allSymbols = new List<Symbol>();
|
|
for (var date = start; date < end; date = date.AddDays(1))
|
|
{
|
|
if (future.Exchange.DateIsOpen(date))
|
|
{
|
|
allSymbols.AddRange(FutureChainProvider.GetFutureContractList(future.Symbol, date));
|
|
}
|
|
}
|
|
var symbols = allSymbols.Distinct();
|
|
|
|
return new FutureHistory(History(symbols, start, end.Value, resolution));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of an indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, Symbol symbol, int period, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, period, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<IBaseDataBar> indicator, Symbol symbol, int period, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, period, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="periods">The number of bars to request</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<TradeBar> indicator, Symbol symbol, int period, Resolution? resolution = null, Func<IBaseData, TradeBar> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, period, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of an indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, span, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<IBaseDataBar> indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, span, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="span">The span over which to retrieve recent historical data</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<TradeBar> indicator, Symbol symbol, TimeSpan span, Resolution? resolution = null, Func<IBaseData, TradeBar> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, span, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of an indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of an indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, start, end, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<IBaseDataBar> indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, start, end, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of a bar indicator for the specified symbol. The exact number of bars will be returned.
|
|
/// The symbol must exist in the Securities collection.
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="symbol">The symbol to retrieve historical data for</param>
|
|
/// <param name="start">The start time in the algorithm's time zone</param>
|
|
/// <param name="end">The end time in the algorithm's time zone</param>
|
|
/// <param name="resolution">The resolution to request</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame of historical data of a bar indicator</returns>
|
|
public PyObject Indicator(IndicatorBase<TradeBar> indicator, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null, Func<IBaseData, TradeBar> selector = null)
|
|
{
|
|
var history = History(new[] { symbol }, start, end, resolution);
|
|
return Indicator(indicator, history, selector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets Portfolio Statistics from a pandas.DataFrame with equity and benchmark values
|
|
/// </summary>
|
|
/// <param name="dataFrame">pandas.DataFrame with the information required to compute the Portfolio statistics</param>
|
|
/// <returns><see cref="PortfolioStatistics"/> object wrapped in a <see cref="PyDict"/> with the portfolio statistics.</returns>
|
|
public PyDict GetPortfolioStatistics(PyObject dataFrame)
|
|
{
|
|
var dictBenchmark = new SortedDictionary<DateTime, double>();
|
|
var dictEquity = new SortedDictionary<DateTime, double>();
|
|
var dictPL = new SortedDictionary<DateTime, double>();
|
|
|
|
using (Py.GIL())
|
|
{
|
|
var result = new PyDict();
|
|
|
|
try
|
|
{
|
|
// Converts the data from pandas.DataFrame into dictionaries keyed by time
|
|
var df = ((dynamic)dataFrame).dropna();
|
|
dictBenchmark = GetDictionaryFromSeries((PyObject)df["benchmark"]);
|
|
dictEquity = GetDictionaryFromSeries((PyObject)df["equity"]);
|
|
dictPL = GetDictionaryFromSeries((PyObject)df["equity"].pct_change());
|
|
}
|
|
catch (PythonException e)
|
|
{
|
|
result.SetItem("Runtime Error", e.Message.ToPython());
|
|
return result;
|
|
}
|
|
|
|
// Convert the double into decimal
|
|
var equity = new SortedDictionary<DateTime, decimal>(dictEquity.ToDictionary(kvp => kvp.Key, kvp => (decimal)kvp.Value));
|
|
var profitLoss = new SortedDictionary<DateTime, decimal>(dictPL.ToDictionary(kvp => kvp.Key, kvp => double.IsNaN(kvp.Value) ? 0 : (decimal)kvp.Value));
|
|
|
|
// Gets the last value of the day of the benchmark and equity
|
|
var listBenchmark = CalculateDailyRateOfChange(dictBenchmark);
|
|
var listPerformance = CalculateDailyRateOfChange(dictEquity);
|
|
|
|
// Gets the startting capital
|
|
var startingCapital = Convert.ToDecimal(dictEquity.FirstOrDefault().Value);
|
|
|
|
// Compute portfolio statistics
|
|
var stats = new PortfolioStatistics(profitLoss, equity, listPerformance, listBenchmark, startingCapital);
|
|
|
|
result.SetItem("Average Win (%)", Convert.ToDouble(stats.AverageWinRate * 100).ToPython());
|
|
result.SetItem("Average Loss (%)", Convert.ToDouble(stats.AverageLossRate * 100).ToPython());
|
|
result.SetItem("Compounding Annual Return (%)", Convert.ToDouble(stats.CompoundingAnnualReturn * 100m).ToPython());
|
|
result.SetItem("Drawdown (%)", Convert.ToDouble(stats.Drawdown * 100).ToPython());
|
|
result.SetItem("Expectancy", Convert.ToDouble(stats.Expectancy).ToPython());
|
|
result.SetItem("Net Profit (%)", Convert.ToDouble(stats.TotalNetProfit * 100).ToPython());
|
|
result.SetItem("Sharpe Ratio", Convert.ToDouble(stats.SharpeRatio).ToPython());
|
|
result.SetItem("Win Rate (%)", Convert.ToDouble(stats.WinRate * 100).ToPython());
|
|
result.SetItem("Loss Rate (%)", Convert.ToDouble(stats.LossRate * 100).ToPython());
|
|
result.SetItem("Profit-Loss Ratio", Convert.ToDouble(stats.ProfitLossRatio).ToPython());
|
|
result.SetItem("Alpha", Convert.ToDouble(stats.Alpha).ToPython());
|
|
result.SetItem("Beta", Convert.ToDouble(stats.Beta).ToPython());
|
|
result.SetItem("Annual Standard Deviation", Convert.ToDouble(stats.AnnualStandardDeviation).ToPython());
|
|
result.SetItem("Annual Variance", Convert.ToDouble(stats.AnnualVariance).ToPython());
|
|
result.SetItem("Information Ratio", Convert.ToDouble(stats.InformationRatio).ToPython());
|
|
result.SetItem("Tracking Error", Convert.ToDouble(stats.TrackingError).ToPython());
|
|
result.SetItem("Treynor Ratio", Convert.ToDouble(stats.TreynorRatio).ToPython());
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a pandas.Series into a <see cref="SortedDictionary{DateTime, Double}"/>
|
|
/// </summary>
|
|
/// <param name="series">pandas.Series to be converted</param>
|
|
/// <returns><see cref="SortedDictionary{DateTime, Double}"/> with pandas.Series information</returns>
|
|
private SortedDictionary<DateTime, double> GetDictionaryFromSeries(PyObject series)
|
|
{
|
|
var dictionary = new SortedDictionary<DateTime, double>();
|
|
|
|
var pyDict = new PyDict(((dynamic)series).to_dict());
|
|
foreach (PyObject item in pyDict.Items())
|
|
{
|
|
var key = (DateTime)item[0].AsManagedObject(typeof(DateTime));
|
|
var value = (double)item[1].AsManagedObject(typeof(double));
|
|
dictionary.Add(key, value);
|
|
}
|
|
|
|
return dictionary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the daily rate of change
|
|
/// </summary>
|
|
/// <param name="dictionary"><see cref="IDictionary{DateTime, Double}"/> with prices keyed by time</param>
|
|
/// <returns><see cref="List{Double}"/> with daily rate of change</returns>
|
|
private List<double> CalculateDailyRateOfChange(IDictionary<DateTime, double> dictionary)
|
|
{
|
|
var daily = dictionary.GroupBy(kvp => kvp.Key.Date)
|
|
.ToDictionary(x => x.Key, v => v.LastOrDefault().Value)
|
|
.Values.ToArray();
|
|
|
|
var rocp = new double[daily.Length];
|
|
for (var i = 1; i < daily.Length; i++)
|
|
{
|
|
rocp[i] = (daily[i] - daily[i - 1]) / daily[i - 1];
|
|
}
|
|
rocp[0] = 0;
|
|
|
|
return rocp.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of an indicator and convert it into pandas.DataFrame
|
|
/// </summary>
|
|
/// <param name="indicator">Indicator</param>
|
|
/// <param name="history">Historical data used to calculate the indicator</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame containing the historical data of <param name="indicator"></returns>
|
|
private PyObject Indicator(IndicatorBase<IndicatorDataPoint> indicator, IEnumerable<Slice> history, Func<IBaseData, decimal> selector = null)
|
|
{
|
|
// Reset the indicator
|
|
indicator.Reset();
|
|
|
|
// Create a dictionary of the properties
|
|
var name = indicator.GetType().Name;
|
|
|
|
var properties = indicator.GetType().GetProperties()
|
|
.Where(x => x.PropertyType.IsGenericType)
|
|
.ToDictionary(x => x.Name, y => new List<IndicatorDataPoint>());
|
|
properties.Add(name, new List<IndicatorDataPoint>());
|
|
|
|
indicator.Updated += (s, e) =>
|
|
{
|
|
if (!indicator.IsReady)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var kvp in properties)
|
|
{
|
|
var dataPoint = kvp.Key == name ? e : GetPropertyValue(s, kvp.Key + ".Current");
|
|
kvp.Value.Add((IndicatorDataPoint)dataPoint);
|
|
}
|
|
};
|
|
|
|
selector = selector ?? (x => x.Value);
|
|
|
|
history.PushThrough(bar =>
|
|
{
|
|
var value = selector(bar);
|
|
indicator.Update(bar.EndTime, value);
|
|
});
|
|
|
|
return PandasConverter.GetIndicatorDataFrame(properties);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the historical data of an bar indicator and convert it into pandas.DataFrame
|
|
/// </summary>
|
|
/// <param name="indicator">Bar indicator</param>
|
|
/// <param name="history">Historical data used to calculate the indicator</param>
|
|
/// <param name="selector">Selects a value from the BaseData to send into the indicator, if null defaults to the Value property of BaseData (x => x.Value)</param>
|
|
/// <returns>pandas.DataFrame containing the historical data of <param name="indicator"></returns>
|
|
private PyObject Indicator<T>(IndicatorBase<T> indicator, IEnumerable<Slice> history, Func<IBaseData, T> selector = null)
|
|
where T : IBaseData
|
|
{
|
|
// Reset the indicator
|
|
indicator.Reset();
|
|
|
|
// Create a dictionary of the properties
|
|
var name = indicator.GetType().Name;
|
|
|
|
var properties = indicator.GetType().GetProperties()
|
|
.Where(x => x.PropertyType.IsGenericType)
|
|
.ToDictionary(x => x.Name, y => new List<IndicatorDataPoint>());
|
|
properties.Add(name, new List<IndicatorDataPoint>());
|
|
|
|
indicator.Updated += (s, e) =>
|
|
{
|
|
if (!indicator.IsReady)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var kvp in properties)
|
|
{
|
|
var dataPoint = kvp.Key == name ? e : GetPropertyValue(s, kvp.Key + ".Current");
|
|
kvp.Value.Add((IndicatorDataPoint)dataPoint);
|
|
}
|
|
};
|
|
|
|
selector = selector ?? (x => (T)x);
|
|
|
|
history.PushThrough(bar => indicator.Update(selector(bar)));
|
|
|
|
return PandasConverter.GetIndicatorDataFrame(properties);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value of a property
|
|
/// </summary>
|
|
/// <param name="baseData">Object with the desired property</param>
|
|
/// <param name="fullName">Property name</param>
|
|
/// <returns>Property value</returns>
|
|
private object GetPropertyValue(object baseData, string fullName)
|
|
{
|
|
foreach (var name in fullName.Split('.'))
|
|
{
|
|
if (baseData == null) return null;
|
|
|
|
var info = baseData.GetType().GetProperty(name);
|
|
|
|
baseData = info?.GetValue(baseData, null);
|
|
}
|
|
|
|
return baseData;
|
|
}
|
|
}
|
|
} |