/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System;
using QuantConnect.Securities;
using NodaTime;
using System.Collections.Generic;
using QuantConnect.Python;
using Python.Runtime;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Fundamental;
using System.Linq;
using QuantConnect.Brokerages;
using QuantConnect.Scheduling;
using QuantConnect.Util;
namespace QuantConnect.Algorithm
{
public partial class QCAlgorithm
{
private readonly Dictionary _pythonIndicators = new Dictionary();
public PandasConverter PandasConverter { get; private set; }
///
/// Sets pandas converter
///
public void SetPandasConverter()
{
PandasConverter = new PandasConverter();
}
///
/// AddData a new user defined data source, requiring only the minimum config options.
/// The data is added with a default time zone of NewYork (Eastern Daylight Savings Time).
/// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
/// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
///
/// Data source type
/// Key/Ticker for data
/// Resolution of the data
/// The new
public Security AddData(PyObject type, string ticker, Resolution? resolution = null)
{
return AddData(type, ticker, resolution, null, false, 1m);
}
///
/// AddData a new user defined data source, requiring only the minimum config options.
/// The data is added with a default time zone of NewYork (Eastern Daylight Savings Time).
/// This adds a Symbol to the `Underlying` property in the custom data Symbol object.
/// Use this method when adding custom data with a ticker from the past, such as "AOL"
/// before it became "TWX", or if you need to filter using custom data and place trades on the
/// Symbol associated with the custom data.
///
/// Data source type
/// The underlying symbol for the custom data
/// Resolution of the data
/// The new
///
/// We include three optional unused object parameters so that pythonnet chooses the intended method
/// correctly. Previously, calling the overloaded method that accepts a string would instead call this method.
/// Adding the three unused parameters makes it choose the correct method when using a string or Symbol. This is
/// due to pythonnet's method precedence, as viewable here: https://github.com/QuantConnect/pythonnet/blob/9e29755c54e6008cb016e3dd9d75fbd8cd19fcf7/src/runtime/methodbinder.cs#L215
///
public Security AddData(PyObject type, Symbol underlying, Resolution? resolution = null)
{
return AddData(type, underlying, resolution, null, false, 1m);
}
///
/// AddData a new user defined data source, requiring only the minimum config options.
/// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
/// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
///
/// Data source type
/// Key/Ticker for data
/// Resolution of the Data Required
/// Specifies the time zone of the raw data
/// When no data available on a tradebar, return the last data that was generated
/// Custom leverage per security
/// The new
public Security AddData(PyObject type, string ticker, Resolution? resolution, DateTimeZone timeZone, bool fillDataForward = false, decimal leverage = 1.0m)
{
return AddData(type.CreateType(), ticker, resolution, timeZone, fillDataForward, leverage);
}
///
/// AddData a new user defined data source, requiring only the minimum config options.
/// This adds a Symbol to the `Underlying` property in the custom data Symbol object.
/// Use this method when adding custom data with a ticker from the past, such as "AOL"
/// before it became "TWX", or if you need to filter using custom data and place trades on the
/// Symbol associated with the custom data.
///
/// Data source type
/// The underlying symbol for the custom data
/// Resolution of the Data Required
/// Specifies the time zone of the raw data
/// When no data available on a tradebar, return the last data that was generated
/// Custom leverage per security
/// The new
///
/// We include three optional unused object parameters so that pythonnet chooses the intended method
/// correctly. Previously, calling the overloaded method that accepts a string would instead call this method.
/// Adding the three unused parameters makes it choose the correct method when using a string or Symbol. This is
/// due to pythonnet's method precedence, as viewable here: https://github.com/QuantConnect/pythonnet/blob/9e29755c54e6008cb016e3dd9d75fbd8cd19fcf7/src/runtime/methodbinder.cs#L215
///
public Security AddData(PyObject type, Symbol underlying, Resolution? resolution, DateTimeZone timeZone, bool fillDataForward = false, decimal leverage = 1.0m)
{
return AddData(type.CreateType(), underlying, resolution, timeZone, fillDataForward, leverage);
}
///
/// AddData a new user defined data source, requiring only the minimum config options.
/// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
/// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
///
/// Data source type
/// Key/Ticker for data
/// Resolution of the Data Required
/// Specifies the time zone of the raw data
/// When no data available on a tradebar, return the last data that was generated
/// Custom leverage per security
/// The new
public Security AddData(Type dataType, string ticker, Resolution? resolution, DateTimeZone timeZone, bool fillDataForward = false, decimal leverage = 1.0m)
{
// NOTE: Invoking methods on BaseData w/out setting the symbol may provide unexpected behavior
var baseInstance = dataType.GetBaseDataInstance();
if (!baseInstance.RequiresMapping())
{
var symbol = new Symbol(
SecurityIdentifier.GenerateBase(dataType, ticker, Market.USA, baseInstance.RequiresMapping()),
ticker);
return AddDataImpl(dataType, symbol, resolution, timeZone, fillDataForward, leverage);
}
// If we need a mappable ticker and we can't find one in the SymbolCache, throw
Symbol underlying;
if (!SymbolCache.TryGetSymbol(ticker, out underlying))
{
throw new InvalidOperationException($"The custom data type {dataType.Name} requires mapping, but the provided ticker is not in the cache. " +
$"Please add this custom data type using a Symbol or perform this call after " +
$"a Security has been added using AddEquity, AddForex, AddCfd, AddCrypto, AddFuture, AddOption or AddSecurity. " +
$"An example use case can be found in CustomDataAddDataRegressionAlgorithm");
}
return AddData(dataType, underlying, resolution, timeZone, fillDataForward, leverage);
}
///
/// AddData a new user defined data source, requiring only the minimum config options.
/// This adds a Symbol to the `Underlying` property in the custom data Symbol object.
/// Use this method when adding custom data with a ticker from the past, such as "AOL"
/// before it became "TWX", or if you need to filter using custom data and place trades on the
/// Symbol associated with the custom data.
///
/// Data source type
///
/// Resolution of the Data Required
/// Specifies the time zone of the raw data
/// When no data available on a tradebar, return the last data that was generated
/// Custom leverage per security
/// The new
///
/// We include three optional unused object parameters so that pythonnet chooses the intended method
/// correctly. Previously, calling the overloaded method that accepts a string would instead call this method.
/// Adding the three unused parameters makes it choose the correct method when using a string or Symbol. This is
/// due to pythonnet's method precedence, as viewable here: https://github.com/QuantConnect/pythonnet/blob/9e29755c54e6008cb016e3dd9d75fbd8cd19fcf7/src/runtime/methodbinder.cs#L215
///
public Security AddData(Type dataType, Symbol underlying, Resolution? resolution = null, DateTimeZone timeZone = null, bool fillDataForward = false, decimal leverage = 1.0m)
{
var symbol = QuantConnect.Symbol.CreateBase(dataType, underlying, Market.USA);
return AddDataImpl(dataType, symbol, resolution, timeZone, fillDataForward, leverage);
}
///
/// Creates and adds a new Future Option contract to the algorithm.
///
/// The canonical symbol (i.e. Symbol returned from )
/// Filter to apply to option contracts loaded as part of the universe
/// The new security, containing a as its underlying.
/// The symbol provided is not canonical.
public void AddFutureOption(Symbol futureSymbol, PyObject optionFilter)
{
Func optionFilterUniverse;
if (!optionFilter.TryConvertToDelegate(out optionFilterUniverse))
{
throw new ArgumentException("Option contract universe filter provided is not a function");
}
AddFutureOption(futureSymbol, optionFilterUniverse);
}
///
/// Adds the provided final Symbol with/without underlying set to the algorithm.
/// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
/// Examples of data sources that meet this criteria are U.S. Treasury Yield Curve Rates and Trading Economics data
///
/// Data source type
/// Final symbol that includes underlying (if any)
/// Resolution of the Data required
/// Specifies the time zone of the raw data
/// When no data available on a tradebar, return the last data that was generated
/// Custom leverage per security
/// The new
private Security AddDataImpl(Type dataType, Symbol symbol, Resolution? resolution, DateTimeZone timeZone, bool fillDataForward, decimal leverage)
{
var alias = symbol.ID.Symbol;
SymbolCache.Set(alias, symbol);
if (timeZone != null)
{
// user set time zone
MarketHoursDatabase.SetEntryAlwaysOpen(Market.USA, alias, SecurityType.Base, timeZone);
}
//Add this new generic data as a tradeable security:
var config = SubscriptionManager.SubscriptionDataConfigService.Add(
dataType,
symbol,
resolution,
fillDataForward,
isCustomData: true,
extendedMarketHours: true);
var security = Securities.CreateSecurity(symbol, config, leverage, addToSymbolCache: false);
AddToUserDefinedUniverse(security, new List { config });
return security;
}
///
/// Creates a new universe and adds it to the algorithm. This is for coarse fundamental US Equity data and
/// will be executed on day changes in the NewYork time zone (
///
/// Defines an initial coarse selection
public Universe AddUniverse(PyObject pyObject)
{
Func, object> coarseFunc;
Universe universe;
if (pyObject.TryConvert(out universe))
{
return AddUniverse(universe);
}
else if (pyObject.TryConvert(out universe, allowPythonDerivative: true))
{
return AddUniverse(new UniversePythonWrapper(pyObject));
}
else if (pyObject.TryConvertToDelegate(out coarseFunc))
{
return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate());
}
else
{
using (Py.GIL())
{
throw new ArgumentException($"QCAlgorithm.AddUniverse: {pyObject.Repr()} is not a valid argument.");
}
}
}
///
/// Creates a new universe and adds it to the algorithm. This is for coarse and fine fundamental US Equity data and
/// will be executed on day changes in the NewYork time zone (
///
/// Defines an initial coarse selection or a universe
/// Defines a more detailed selection with access to more data
public Universe AddUniverse(PyObject pyObject, PyObject pyfine)
{
Func, object> coarseFunc;
Func, object> fineFunc;
Universe universe;
if (pyObject.TryConvert(out universe) && pyfine.TryConvertToDelegate(out fineFunc))
{
return AddUniverse(universe, fineFunc.ConvertToUniverseSelectionSymbolDelegate());
}
else if (pyObject.TryConvertToDelegate(out coarseFunc) && pyfine.TryConvertToDelegate(out fineFunc))
{
return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate(),
fineFunc.ConvertToUniverseSelectionSymbolDelegate());
}
else
{
using (Py.GIL())
{
throw new ArgumentException($"QCAlgorithm.AddUniverse: {pyObject.Repr()} or {pyfine.Repr()} is not a valid argument.");
}
}
}
///
/// Creates a new universe and adds it to the algorithm. This can be used to return a list of string
/// symbols retrieved from anywhere and will loads those symbols under the US Equity market.
///
/// A unique name for this universe
/// The resolution this universe should be triggered on
/// Function delegate that accepts a DateTime and returns a collection of string symbols
public Universe AddUniverse(string name, Resolution resolution, PyObject pySelector)
{
var selector = pySelector.ConvertToDelegate>();
return AddUniverse(name, resolution, selector.ConvertToUniverseSelectionStringDelegate());
}
///
/// Creates a new universe and adds it to the algorithm. This can be used to return a list of string
/// symbols retrieved from anywhere and will loads those symbols under the US Equity market.
///
/// A unique name for this universe
/// Function delegate that accepts a DateTime and returns a collection of string symbols
public Universe AddUniverse(string name, PyObject pySelector)
{
var selector = pySelector.ConvertToDelegate>();
return AddUniverse(name, selector.ConvertToUniverseSelectionStringDelegate());
}
///
/// Creates a new user defined universe that will fire on the requested resolution during market hours.
///
/// The security type of the universe
/// A unique name for this universe
/// The resolution this universe should be triggered on
/// The market of the universe
/// The subscription settings used for securities added from this universe
/// Function delegate that accepts a DateTime and returns a collection of string symbols
public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
{
var selector = pySelector.ConvertToDelegate>();
return AddUniverse(securityType, name, resolution, market, universeSettings, selector.ConvertToUniverseSelectionStringDelegate());
}
///
/// Creates a new universe and adds it to the algorithm. This will use the default universe settings
/// specified via the property. This universe will use the defaults
/// of SecurityType.Equity, Resolution.Daily, Market.USA, and UniverseSettings
///
/// The data type
/// A unique name for this universe
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(PyObject T, string name, PyObject selector)
{
return AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
}
///
/// Creates a new universe and adds it to the algorithm. This will use the default universe settings
/// specified via the property. This universe will use the defaults
/// of SecurityType.Equity, Market.USA and UniverseSettings
///
/// The data type
/// A unique name for this universe
/// The epected resolution of the universe data
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(PyObject T, string name, Resolution resolution, PyObject selector)
{
return AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
}
///
/// Creates a new universe and adds it to the algorithm. This will use the default universe settings
/// specified via the property. This universe will use the defaults
/// of SecurityType.Equity, and Market.USA
///
/// The data type
/// A unique name for this universe
/// The epected resolution of the universe data
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(PyObject T, string name, Resolution resolution, UniverseSettings universeSettings, PyObject selector)
{
return AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
}
///
/// Creates a new universe and adds it to the algorithm. This will use the default universe settings
/// specified via the property. This universe will use the defaults
/// of SecurityType.Equity, Resolution.Daily, and Market.USA
///
/// The data type
/// A unique name for this universe
/// The settings used for securities added by this universe
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(PyObject T, string name, UniverseSettings universeSettings, PyObject selector)
{
return AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
}
///
/// Creates a new universe and adds it to the algorithm. This will use the default universe settings
/// specified via the property.
///
/// The data type
/// The security type the universe produces
/// A unique name for this universe
/// The epected resolution of the universe data
/// The market for selected symbols
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, PyObject selector)
{
return AddUniverse(T.CreateType(), securityType, name, resolution, market, UniverseSettings, selector);
}
///
/// Creates a new universe and adds it to the algorithm
///
/// The data type
/// The security type the universe produces
/// A unique name for this universe
/// The epected resolution of the universe data
/// The market for selected symbols
/// The subscription settings to use for newly created subscriptions
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject selector)
{
return AddUniverse(T.CreateType(), securityType, name, resolution, market, universeSettings, selector);
}
///
/// Creates a new universe and adds it to the algorithm
///
/// The data type
/// The security type the universe produces
/// A unique name for this universe
/// The epected resolution of the universe data
/// The market for selected symbols
/// The subscription settings to use for newly created subscriptions
/// Function delegate that performs selection on the universe data
public Universe AddUniverse(Type dataType, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
{
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
var symbol = QuantConnect.Symbol.Create(name, securityType, market, baseDataType: dataType);
var config = new SubscriptionDataConfig(dataType, symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, true, isFilteredSubscription: false);
var selector = pySelector.ConvertToDelegate, object>>();
return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, baseDatas =>
{
var result = selector(baseDatas);
return ReferenceEquals(result, Universe.Unchanged)
? Universe.Unchanged : ((object[])result)
.Select(x => x is Symbol ? (Symbol)x : QuantConnect.Symbol.Create((string)x, securityType, market, baseDataType: dataType));
}
));
}
///
/// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
/// changes of a given selection output and create a new for each of them
///
/// The universe we want to chain an option universe selection model too
/// The option filter universe to use
public void AddUniverseOptions(PyObject universe, PyObject optionFilter)
{
Func convertedOptionChain;
Universe universeToChain;
if (universe.TryConvert(out universeToChain) && optionFilter.TryConvertToDelegate(out convertedOptionChain))
{
AddUniverseOptions(universeToChain, convertedOptionChain);
}
else
{
using (Py.GIL())
{
throw new ArgumentException($"QCAlgorithm.AddChainedEquityOptionUniverseSelectionModel: {universe.Repr()} or {optionFilter.Repr()} is not a valid argument.");
}
}
}
///
/// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
/// from the consolidator.
///
/// The symbol to register against
/// The indicator to receive data from the consolidator
/// The resolution at which to send data to the indicator, null to use the same resolution as the subscription
/// Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)
public void RegisterIndicator(Symbol symbol, PyObject indicator, Resolution? resolution = null, PyObject selector = null)
{
RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution), selector);
}
///
/// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
/// from the consolidator.
///
/// The symbol to register against
/// The indicator to receive data from the consolidator
/// The resolution at which to send data to the indicator, null to use the same resolution as the subscription
/// Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)
public void RegisterIndicator(Symbol symbol, PyObject indicator, TimeSpan? resolution = null, PyObject selector = null)
{
RegisterIndicator(symbol, indicator, ResolveConsolidator(symbol, resolution), selector);
}
///
/// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
/// from the consolidator.
///
/// The symbol to register against
/// The indicator to receive data from the consolidator
/// The python object that it is trying to register with, could be consolidator or a timespan
/// Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)
public void RegisterIndicator(Symbol symbol, PyObject indicator, PyObject pyObject, PyObject selector = null)
{
try
{
// First check if this is just a regular IDataConsolidator
IDataConsolidator dataConsolidator;
if (!pyObject.TryConvert(out dataConsolidator))
{
// If not then try and wrap it as a custom Python consolidator
dataConsolidator = new DataConsolidatorPythonWrapper(pyObject);
}
RegisterIndicator(symbol, indicator, dataConsolidator, selector);
return;
}
catch
{
}
// Finally, since above didn't work, just try it as a timespan
// Issue #4668 Fix
using (Py.GIL())
{
try
{
// tryConvert does not work for timespan
TimeSpan? timeSpan = pyObject.As();
if (timeSpan != default(TimeSpan))
{
RegisterIndicator(symbol, indicator, timeSpan, selector);
}
}
catch
{
throw new ArgumentException("Invalid third argument, should be either a valid consolidator or timedelta object");
}
}
}
///
/// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
/// from the consolidator.
///
/// The symbol to register against
/// The indicator to receive data from the consolidator
/// The consolidator to receive raw subscription data
/// Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)
public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidator consolidator, PyObject selector = null)
{
IndicatorBase indicatorDataPoint;
IndicatorBase indicatorDataBar;
IndicatorBase indicatorTradeBar;
if (indicator.TryConvert(out indicatorDataPoint))
{
RegisterIndicator(symbol, indicatorDataPoint, consolidator, selector?.ConvertToDelegate>());
return;
}
else if (indicator.TryConvert(out indicatorDataBar))
{
RegisterIndicator(symbol, indicatorDataBar, consolidator, selector?.ConvertToDelegate>());
return;
}
else if (indicator.TryConvert(out indicatorTradeBar))
{
RegisterIndicator(symbol, indicatorTradeBar, consolidator, selector?.ConvertToDelegate>());
return;
}
RegisterIndicator(symbol, WrapPythonIndicator(indicator), consolidator, selector?.ConvertToDelegate>());
}
///
/// Plot a chart using string series name, with value.
///
/// Name of the plot series
/// PyObject with the value to plot
///
public void Plot(string series, PyObject pyObject)
{
using (Py.GIL())
{
try
{
var value = (((dynamic)pyObject).Current.Value as PyObject).GetAndDispose();
Plot(series, value);
}
catch
{
var pythonType = pyObject.GetPythonType().Repr();
throw new ArgumentException($"QCAlgorithm.Plot(): The last argument should be a QuantConnect Indicator object, {pythonType} was provided.");
}
}
}
///
/// Plots the value of each indicator on the chart
///
/// The chart's name
/// The first indicator to plot
/// The second indicator to plot
/// The third indicator to plot
/// The fourth indicator to plot
///
public void Plot(string chart, Indicator first, Indicator second = null, Indicator third = null, Indicator fourth = null)
{
Plot(chart, new[] { first, second, third, fourth }.Where(x => x != null).ToArray());
}
///
/// Plots the value of each indicator on the chart
///
/// The chart's name
/// The first indicator to plot
/// The second indicator to plot
/// The third indicator to plot
/// The fourth indicator to plot
///
public void Plot(string chart, BarIndicator first, BarIndicator second = null, BarIndicator third = null, BarIndicator fourth = null)
{
Plot(chart, new[] { first, second, third, fourth }.Where(x => x != null).ToArray());
}
///
/// Plots the value of each indicator on the chart
///
/// The chart's name
/// The first indicator to plot
/// The second indicator to plot
/// The third indicator to plot
/// The fourth indicator to plot
///
public void Plot(string chart, TradeBarIndicator first, TradeBarIndicator second = null, TradeBarIndicator third = null, TradeBarIndicator fourth = null)
{
Plot(chart, new[] { first, second, third, fourth }.Where(x => x != null).ToArray());
}
///
/// Automatically plots each indicator when a new value is available
///
public void PlotIndicator(string chart, PyObject first, PyObject second = null, PyObject third = null, PyObject fourth = null)
{
var array = GetIndicatorArray(first, second, third, fourth);
PlotIndicator(chart, array[0], array[1], array[2], array[3]);
}
///
/// Automatically plots each indicator when a new value is available
///
public void PlotIndicator(string chart, bool waitForReady, PyObject first, PyObject second = null, PyObject third = null, PyObject fourth = null)
{
var array = GetIndicatorArray(first, second, third, fourth);
PlotIndicator(chart, waitForReady, array[0], array[1], array[2], array[3]);
}
///
/// Creates a new FilteredIdentity indicator for the symbol The indicator will be automatically
/// updated on the symbol's subscription resolution
///
/// The symbol whose values we want as an indicator
/// Selects a value from the BaseData, if null defaults to the .Value property (x => x.Value)
/// Filters the IBaseData send into the indicator, if null defaults to true (x => true) which means no filter
/// The name of the field being selected
/// A new FilteredIdentity indicator for the specified symbol and selector
public FilteredIdentity FilteredIdentity(Symbol symbol, PyObject selector = null, PyObject filter = null, string fieldName = null)
{
var resolution = GetSubscription(symbol).Resolution;
return FilteredIdentity(symbol, resolution, selector, filter, fieldName);
}
///
/// Creates a new FilteredIdentity indicator for the symbol The indicator will be automatically
/// updated on the symbol's subscription resolution
///
/// The symbol whose values we want as an indicator
/// The desired resolution of the data
/// Selects a value from the BaseData, if null defaults to the .Value property (x => x.Value)
/// Filters the IBaseData send into the indicator, if null defaults to true (x => true) which means no filter
/// The name of the field being selected
/// A new FilteredIdentity indicator for the specified symbol and selector
public FilteredIdentity FilteredIdentity(Symbol symbol, Resolution resolution, PyObject selector = null, PyObject filter = null, string fieldName = null)
{
var name = CreateIndicatorName(symbol, fieldName ?? "close", resolution);
var pyselector = PythonUtil.ToFunc(selector);
var pyfilter = PythonUtil.ToFunc(filter);
var filteredIdentity = new FilteredIdentity(name, pyfilter);
RegisterIndicator(symbol, filteredIdentity, resolution, pyselector);
return filteredIdentity;
}
///
/// Creates a new FilteredIdentity indicator for the symbol The indicator will be automatically
/// updated on the symbol's subscription resolution
///
/// The symbol whose values we want as an indicator
/// The desired resolution of the data
/// Selects a value from the BaseData, if null defaults to the .Value property (x => x.Value)
/// Filters the IBaseData send into the indicator, if null defaults to true (x => true) which means no filter
/// The name of the field being selected
/// A new FilteredIdentity indicator for the specified symbol and selector
public FilteredIdentity FilteredIdentity(Symbol symbol, TimeSpan resolution, PyObject selector = null, PyObject filter = null, string fieldName = null)
{
var name = $"{symbol}({fieldName ?? "close"}_{resolution.ToStringInvariant(null)})";
var pyselector = PythonUtil.ToFunc(selector);
var pyfilter = PythonUtil.ToFunc(filter);
var filteredIdentity = new FilteredIdentity(name, pyfilter);
RegisterIndicator(symbol, filteredIdentity, ResolveConsolidator(symbol, resolution), pyselector);
return filteredIdentity;
}
///
/// Gets the historical data for the specified symbol. The exact number of bars will be returned.
/// The symbol must exist in the Securities collection.
///
/// The symbols to retrieve historical data for
/// The number of bars to request
/// The resolution to request
/// A python dictionary with pandas DataFrame containing the requested historical data
public PyObject History(PyObject tickers, int periods, Resolution? resolution = null)
{
var symbols = tickers.ConvertToSymbolEnumerable();
return PandasConverter.GetDataFrame(History(symbols, periods, resolution));
}
///
/// Gets the historical data for the specified symbols over the requested span.
/// The symbols must exist in the Securities collection.
///
/// The symbols to retrieve historical data for
/// The span over which to retrieve recent historical data
/// The resolution to request
/// A python dictionary with pandas DataFrame containing the requested historical data
public PyObject History(PyObject tickers, TimeSpan span, Resolution? resolution = null)
{
var symbols = tickers.ConvertToSymbolEnumerable();
return PandasConverter.GetDataFrame(History(symbols, span, resolution));
}
///
/// Gets the historical data for the specified symbol between the specified dates. The symbol must exist in the Securities collection.
///
/// The symbols to retrieve historical data for
/// The start time in the algorithm's time zone
/// The end time in the algorithm's time zone
/// The resolution to request
/// A python dictionary with pandas DataFrame containing the requested historical data
public PyObject History(PyObject tickers, DateTime start, DateTime end, Resolution? resolution = null)
{
var symbols = tickers.ConvertToSymbolEnumerable();
return PandasConverter.GetDataFrame(History(symbols, start, end, resolution));
}
///
/// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
///
/// The data type of the symbols
/// The symbols to retrieve historical data for
/// The start time in the algorithm's time zone
/// The end time in the algorithm's time zone
/// The resolution to request
/// pandas.DataFrame containing the requested historical data
public PyObject History(PyObject type, PyObject tickers, DateTime start, DateTime end, Resolution? resolution = null)
{
var symbols = tickers.ConvertToSymbolEnumerable();
var requestedType = type.CreateType();
var requests = symbols.Select(x =>
{
var security = Securities[x];
var config = security.Subscriptions.OrderByDescending(s => s.Resolution)
.FirstOrDefault(s => s.Type.BaseType == requestedType.BaseType);
if (config == null) return null;
return _historyRequestFactory.CreateHistoryRequest(config, start, end, GetExchangeHours(x), resolution);
});
return PandasConverter.GetDataFrame(History(requests.Where(x => x != null)).Memoize());
}
///
/// Gets the historical data for the specified symbols. The exact number of bars will be returned for
/// each symbol. This may result in some data start earlier/later than others due to when various
/// exchanges are open. The symbols must exist in the Securities collection.
///
/// The data type of the symbols
/// The symbols to retrieve historical data for
/// The number of bars to request
/// The resolution to request
/// pandas.DataFrame containing the requested historical data
public PyObject History(PyObject type, PyObject tickers, int periods, Resolution? resolution = null)
{
var symbols = tickers.ConvertToSymbolEnumerable();
var requestedType = type.CreateType();
var requests = symbols.Select(x =>
{
var security = Securities[x];
var config = security.Subscriptions.OrderByDescending(s => s.Resolution)
.FirstOrDefault(s => s.Type.BaseType == requestedType.BaseType);
if (config == null) return null;
var res = GetResolution(x, resolution);
var exchange = GetExchangeHours(x);
var start = _historyRequestFactory.GetStartTimeAlgoTz(x, periods, res, exchange, config.DataTimeZone);
return _historyRequestFactory.CreateHistoryRequest(config, start, Time, exchange, res);
});
return PandasConverter.GetDataFrame(History(requests.Where(x => x != null)).Memoize());
}
///
/// Gets the historical data for the specified symbols over the requested span.
/// The symbols must exist in the Securities collection.
///
/// The data type of the symbols
/// The symbols to retrieve historical data for
/// The span over which to retrieve recent historical data
/// The resolution to request
/// pandas.DataFrame containing the requested historical data
public PyObject History(PyObject type, PyObject tickers, TimeSpan span, Resolution? resolution = null)
{
return History(type, tickers, Time - span, Time, resolution);
}
///
/// Gets the historical data for the specified symbols between the specified dates. The symbols must exist in the Securities collection.
///
/// The data type of the symbols
/// The symbol to retrieve historical data for
/// The start time in the algorithm's time zone
/// The end time in the algorithm's time zone
/// The resolution to request
/// pandas.DataFrame containing the requested historical data
public PyObject History(PyObject type, Symbol symbol, DateTime start, DateTime end, Resolution? resolution = null)
{
var security = Securities[symbol];
// verify the types match
var requestedType = type.CreateType();
var config = security.Subscriptions.OrderByDescending(s => s.Resolution)
.FirstOrDefault(s => s.Type.BaseType == requestedType.BaseType);
if (config == null)
{
var actualType = security.Subscriptions.Select(x => x.Type.Name).DefaultIfEmpty("[None]").FirstOrDefault();
throw new ArgumentException("The specified security is not of the requested type. Symbol: " + symbol.ToString() + " Requested Type: " + requestedType.Name + " Actual Type: " + actualType);
}
var request = _historyRequestFactory.CreateHistoryRequest(config, start, end, GetExchangeHours(symbol), resolution);
return PandasConverter.GetDataFrame(History(request).Memoize());
}
///
/// Gets the historical data for the specified symbols. The exact number of bars will be returned for
/// each symbol. This may result in some data start earlier/later than others due to when various
/// exchanges are open. The symbols must exist in the Securities collection.
///
/// The data type of the symbols
/// The symbol to retrieve historical data for
/// The number of bars to request
/// The resolution to request
/// pandas.DataFrame containing the requested historical data
public PyObject History(PyObject type, Symbol symbol, int periods, Resolution? resolution = null)
{
if (resolution == Resolution.Tick) throw new ArgumentException("History functions that accept a 'periods' parameter can not be used with Resolution.Tick");
var res = GetResolution(symbol, resolution);
var marketHours = GetMarketHours(symbol);
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, res, marketHours.ExchangeHours, marketHours.DataTimeZone);
return History(type, symbol, start, Time, resolution);
}
///
/// Gets the historical data for the specified symbols over the requested span.
/// The symbols must exist in the Securities collection.
///
/// The data type of the symbols
/// The symbol to retrieve historical data for
/// The span over which to retrieve recent historical data
/// The resolution to request
/// pandas.DataFrame containing the requested historical data
public PyObject History(PyObject type, Symbol symbol, TimeSpan span, Resolution? resolution = null)
{
return History(type, symbol, Time - span, Time, resolution);
}
///
/// Sets the specified function as the benchmark, this function provides the value of
/// the benchmark at each date/time requested
///
/// The benchmark producing function
public void SetBenchmark(PyObject benchmark)
{
using (Py.GIL())
{
var pyBenchmark = PythonUtil.ToFunc(benchmark);
if (pyBenchmark != null)
{
SetBenchmark(pyBenchmark);
return;
}
SetBenchmark((Symbol)benchmark.AsManagedObject(typeof(Symbol)));
}
}
///
/// Sets the brokerage to emulate in backtesting or paper trading.
/// This can be used to set a custom brokerage model.
///
/// The brokerage model to use
public void SetBrokerageModel(PyObject model)
{
IBrokerageModel brokerageModel;
if (!model.TryConvert(out brokerageModel))
{
brokerageModel = new BrokerageModelPythonWrapper(model);
}
SetBrokerageModel(brokerageModel);
}
///
/// Sets the security initializer function, used to initialize/configure securities after creation
///
/// The security initializer function or class
public void SetSecurityInitializer(PyObject securityInitializer)
{
var securityInitializer1 = PythonUtil.ToAction(securityInitializer);
if (securityInitializer1 != null)
{
SetSecurityInitializer(securityInitializer1);
return;
}
SetSecurityInitializer(new SecurityInitializerPythonWrapper(securityInitializer));
}
///
/// Downloads the requested resource as a .
/// The resource to download is specified as a containing the URI.
///
/// A string containing the URI to download
/// Defines header values to add to the request
/// The requested resource as a
public string Download(string address, PyObject headers) => Download(address, headers, null, null);
///
/// Downloads the requested resource as a .
/// The resource to download is specified as a containing the URI.
///
/// A string containing the URI to download
/// Defines header values to add to the request
/// The user name associated with the credentials
/// The password for the user name associated with the credentials
/// The requested resource as a
public string Download(string address, PyObject headers, string userName, string password)
{
var dict = new Dictionary();
if (headers != null)
{
using (Py.GIL())
{
// In python algorithms, headers must be a python dictionary
// In order to convert it into a C# Dictionary
if (PyDict.IsDictType(headers))
{
foreach (PyObject pyKey in headers)
{
var key = (string)pyKey.AsManagedObject(typeof(string));
var value = (string)headers.GetItem(pyKey).AsManagedObject(typeof(string));
dict.Add(key, value);
}
}
else
{
throw new ArgumentException($"QCAlgorithm.Fetch(): Invalid argument. {headers.Repr()} is not a dict");
}
}
}
return Download(address, dict, userName, password);
}
///
/// Send a debug message to the web console:
///
/// Message to send to debug console
///
///
public void Debug(PyObject message)
{
Debug(message.ToSafeString());
}
///
/// Send a string error message to the Console.
///
/// Message to display in errors grid
///
///
public void Error(PyObject message)
{
Error(message.ToSafeString());
}
///
/// Added another method for logging if user guessed.
///
/// String message to log.
///
///
public void Log(PyObject message)
{
Log(message.ToSafeString());
}
///
/// Terminate the algorithm after processing the current event handler.
///
/// Exit message to display on quitting
public void Quit(PyObject message)
{
Quit(message.ToSafeString());
}
///
/// Registers the to receive consolidated data for the specified symbol
///
/// The symbol who's data is to be consolidated
/// The consolidation period
/// Data handler receives new consolidated data when generated
/// A new consolidator matching the requested parameters with the handler already registered
public IDataConsolidator Consolidate(Symbol symbol, Resolution period, PyObject handler)
{
return Consolidate(symbol, period.ToTimeSpan(), null, handler);
}
///
/// Registers the to receive consolidated data for the specified symbol
///
/// The symbol who's data is to be consolidated
/// The consolidation period
/// The tick type of subscription used as data source for consolidator. Specify null to use first subscription found.
/// Data handler receives new consolidated data when generated
/// A new consolidator matching the requested parameters with the handler already registered
public IDataConsolidator Consolidate(Symbol symbol, Resolution period, TickType? tickType, PyObject handler)
{
return Consolidate(symbol, period.ToTimeSpan(), tickType, handler);
}
///
/// Registers the to receive consolidated data for the specified symbol
///
/// The symbol who's data is to be consolidated
/// The consolidation period
/// Data handler receives new consolidated data when generated
/// A new consolidator matching the requested parameters with the handler already registered
public IDataConsolidator Consolidate(Symbol symbol, TimeSpan period, PyObject handler)
{
return Consolidate(symbol, period, null, handler);
}
///
/// Registers the to receive consolidated data for the specified symbol
///
/// The symbol who's data is to be consolidated
/// The consolidation period
/// The tick type of subscription used as data source for consolidator. Specify null to use first subscription found.
/// Data handler receives new consolidated data when generated
/// A new consolidator matching the requested parameters with the handler already registered
public IDataConsolidator Consolidate(Symbol symbol, TimeSpan period, TickType? tickType, PyObject handler)
{
// resolve consolidator input subscription
var type = GetSubscription(symbol, tickType).Type;
if (type == typeof(TradeBar))
{
return Consolidate(symbol, period, tickType, handler.ConvertToDelegate>());
}
if (type == typeof(QuoteBar))
{
return Consolidate(symbol, period, tickType, handler.ConvertToDelegate>());
}
return Consolidate(symbol, period, null, handler.ConvertToDelegate>());
}
///
/// Registers the to receive consolidated data for the specified symbol
///
/// The symbol who's data is to be consolidated
/// The consolidation calendar
/// Data handler receives new consolidated data when generated
/// A new consolidator matching the requested parameters with the handler already registered
public IDataConsolidator Consolidate(Symbol symbol, Func calendar, PyObject handler)
{
return Consolidate(symbol, calendar, null, handler);
}
///
/// Schedules the provided training code to execute immediately
///
/// The training code to be invoked
public ScheduledEvent Train(PyObject trainingCode)
{
return Schedule.TrainingNow(trainingCode);
}
///
/// Schedules the training code to run using the specified date and time rules
///
/// Specifies what dates the event should run
/// Specifies the times on those dates the event should run
/// The training code to be invoked
public ScheduledEvent Train(IDateRule dateRule, ITimeRule timeRule, PyObject trainingCode)
{
return Schedule.Training(dateRule, timeRule, trainingCode);
}
///
/// Registers the to receive consolidated data for the specified symbol
///
/// The symbol who's data is to be consolidated
/// The consolidation calendar
/// The tick type of subscription used as data source for consolidator. Specify null to use first subscription found.
/// Data handler receives new consolidated data when generated
/// A new consolidator matching the requested parameters with the handler already registered
private IDataConsolidator Consolidate(Symbol symbol, Func calendar, TickType? tickType, PyObject handler)
{
// resolve consolidator input subscription
var type = GetSubscription(symbol, tickType).Type;
if (type == typeof(TradeBar))
{
return Consolidate(symbol, calendar, tickType, handler.ConvertToDelegate>());
}
if (type == typeof(QuoteBar))
{
return Consolidate(symbol, calendar, tickType, handler.ConvertToDelegate>());
}
return Consolidate(symbol, calendar, tickType, handler.ConvertToDelegate>());
}
///
/// Gets indicator base type
///
/// Indicator type
/// Indicator base type
private Type GetIndicatorBaseType(Type type)
{
if (type.BaseType == typeof(object))
{
return type;
}
return GetIndicatorBaseType(type.BaseType);
}
///
/// Converts the sequence of PyObject objects into an array of dynamic objects that represent indicators of the same type
///
/// Array of dynamic objects with indicator
private dynamic[] GetIndicatorArray(PyObject first, PyObject second = null, PyObject third = null, PyObject fourth = null)
{
using (Py.GIL())
{
var array = new[] {first, second, third, fourth}
.Select(
x =>
{
if (x == null) return null;
Type type;
return x.GetPythonType().TryConvert(out type)
? x.AsManagedObject(type)
: WrapPythonIndicator(x);
}
).ToArray();
var types = array.Where(x => x != null).Select(x => GetIndicatorBaseType(x.GetType())).Distinct();
if (types.Count() > 1)
{
throw new Exception("QCAlgorithm.GetIndicatorArray(). All indicators must be of the same type: data point, bar or tradebar.");
}
return array;
}
}
///
/// Wraps a custom python indicator and save its reference to _pythonIndicators dictionary
///
/// The python implementation of
/// that wraps the python implementation
private PythonIndicator WrapPythonIndicator(PyObject pyObject)
{
PythonIndicator pythonIndicator;
if (!_pythonIndicators.TryGetValue(pyObject.Handle, out pythonIndicator))
{
pyObject.TryConvert(out pythonIndicator);
pythonIndicator?.SetIndicator(pyObject);
if (pythonIndicator == null)
{
pythonIndicator = new PythonIndicator(pyObject);
}
// Save to prevent future additions
_pythonIndicators.Add(pyObject.Handle, pythonIndicator);
}
return pythonIndicator;
}
}
}