Skip to content

Commit

Permalink
Fix WarmupIndicator to support multi symbol indicators in Python
Browse files Browse the repository at this point in the history
  • Loading branch information
jhonabreul committed Feb 25, 2025
1 parent 300d96d commit e3398e8
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 69 deletions.
36 changes: 35 additions & 1 deletion Algorithm/QCAlgorithm.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
using Python.Runtime;
using QuantConnect.Util;
using static QuantConnect.StringExtensions;
using QuantConnect.Interfaces;
using QuantConnect.Data.Common;

namespace QuantConnect.Algorithm
Expand Down Expand Up @@ -3166,6 +3165,23 @@ public void WarmUpIndicator(Symbol symbol, IndicatorBase<IndicatorDataPoint> ind
WarmUpIndicatorImpl(symbol, period, onDataConsolidated, history, identityConsolidator);
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
/// <param name="symbols">The symbols whose indicator we want</param>
/// <param name="indicator">The indicator we want to warm up</param>
/// <param name="period">The necessary period to warm up 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>
[DocumentationAttribute(HistoricalData)]
[DocumentationAttribute(Indicators)]
public void WarmUpIndicator(IEnumerable<Symbol> symbols, IndicatorBase<IndicatorDataPoint> indicator, TimeSpan period, Func<IBaseData, decimal> selector = null)
{
if (AssertIndicatorHasWarmupPeriod(indicator))
{
IndicatorHistory(indicator, symbols, period, null, selector);
}
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
Expand Down Expand Up @@ -3199,6 +3215,24 @@ public void WarmUpIndicator<T>(IEnumerable<Symbol> symbols, IndicatorBase<T> ind
}
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
/// <param name="symbols">The symbols whose indicator we want</param>
/// <param name="indicator">The indicator we want to warm up</param>
/// <param name="period">The necessary period to warm up 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>
[DocumentationAttribute(HistoricalData)]
[DocumentationAttribute(Indicators)]
public void WarmUpIndicator<T>(IEnumerable<Symbol> symbols, IndicatorBase<T> indicator, TimeSpan period, Func<IBaseData, T> selector = null)
where T : class, IBaseData
{
if (AssertIndicatorHasWarmupPeriod(indicator))
{
IndicatorHistory(indicator, symbols, period, null, selector);
}
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
Expand Down
226 changes: 159 additions & 67 deletions Algorithm/QCAlgorithm.Python.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,30 +663,36 @@ public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidat
{
// TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved
var convertedIndicator = ConvertPythonIndicator(indicator);
if (convertedIndicator is PythonIndicator pythonIndicator)
{
RegisterIndicator(symbol, pythonIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}
else if (convertedIndicator is IndicatorBase<IndicatorDataPoint> dataPointIndicator)
{
RegisterIndicator(symbol, dataPointIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
}
else if (convertedIndicator is IndicatorBase<IBaseDataBar> dataBarIndicator)
{
RegisterIndicator(symbol, dataBarIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
}
else if (convertedIndicator is IndicatorBase<TradeBar> tradeBarIndicator)
{
RegisterIndicator(symbol, tradeBarIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
}
else if (convertedIndicator is IndicatorBase<IBaseData> baseDataIndicator)
switch (convertedIndicator)
{
RegisterIndicator(symbol, baseDataIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
case PythonIndicator pythonIndicator:
RegisterIndicator(symbol, pythonIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

case IndicatorBase<IndicatorDataPoint> dataPointIndicator:
RegisterIndicator(symbol, dataPointIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
break;

case IndicatorBase<IBaseDataBar> baseDataBarIndicator:
RegisterIndicator(symbol, baseDataBarIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
break;

case IndicatorBase<TradeBar> tradeBarIndicator:
RegisterIndicator(symbol, tradeBarIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
break;

case IndicatorBase<IBaseData> baseDataIndicator:
RegisterIndicator(symbol, baseDataIndicator, consolidator,
selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

default:
// Shouldn't happen, ConvertPythonIndicator will wrap the PyObject in a PythonIndicator instance if it can't convert it
throw new ArgumentException($"Indicator type {indicator.GetPythonType().Name} is not supported.");
}
}

Expand All @@ -702,24 +708,33 @@ public void RegisterIndicator(Symbol symbol, PyObject indicator, IDataConsolidat
public void WarmUpIndicator(Symbol symbol, PyObject indicator, Resolution? resolution = null, PyObject selector = null)
{
// TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved

if (indicator.TryConvert(out IndicatorBase<IndicatorDataPoint> indicatorDataPoint))
{
WarmUpIndicator(symbol, indicatorDataPoint, resolution, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
return;
}
if (indicator.TryConvert(out IndicatorBase<IBaseDataBar> indicatorDataBar))
{
WarmUpIndicator(symbol, indicatorDataBar, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
return;
}
if (indicator.TryConvert(out IndicatorBase<TradeBar> indicatorTradeBar))
var convertedIndicator = ConvertPythonIndicator(indicator);
switch (convertedIndicator)
{
WarmUpIndicator(symbol, indicatorTradeBar, resolution, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
return;
}
case PythonIndicator pythonIndicator:
WarmUpIndicator(symbol, pythonIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

case IndicatorBase<IndicatorDataPoint> dataPointIndicator:
WarmUpIndicator(symbol, dataPointIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
break;

case IndicatorBase<IBaseDataBar> baseDataBarIndicator:
WarmUpIndicator(symbol, baseDataBarIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
break;

case IndicatorBase<TradeBar> tradeBarIndicator:
WarmUpIndicator(symbol, tradeBarIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
break;

case IndicatorBase<IBaseData> baseDataIndicator:
WarmUpIndicator(symbol, baseDataIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

WarmUpIndicator(symbol, WrapPythonIndicator(indicator), resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
default:
// Shouldn't happen, ConvertPythonIndicator will wrap the PyObject in a PythonIndicator instance if it can't convert it
throw new ArgumentException($"Indicator type {indicator.GetPythonType().Name} is not supported.");
}
}

/// <summary>
Expand All @@ -733,23 +748,116 @@ public void WarmUpIndicator(Symbol symbol, PyObject indicator, Resolution? resol
[DocumentationAttribute(HistoricalData)]
public void WarmUpIndicator(Symbol symbol, PyObject indicator, TimeSpan period, PyObject selector = null)
{
if (indicator.TryConvert(out IndicatorBase<IndicatorDataPoint> indicatorDataPoint))
var convertedIndicator = ConvertPythonIndicator(indicator);
switch (convertedIndicator)
{
WarmUpIndicator(symbol, indicatorDataPoint, period, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
return;
case PythonIndicator pythonIndicator:
WarmUpIndicator(symbol, pythonIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

case IndicatorBase<IndicatorDataPoint> dataPointIndicator:
WarmUpIndicator(symbol, dataPointIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
break;

case IndicatorBase<IBaseDataBar> baseDataBarIndicator:
WarmUpIndicator(symbol, baseDataBarIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
break;

case IndicatorBase<TradeBar> tradeBarIndicator:
WarmUpIndicator(symbol, tradeBarIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
break;

case IndicatorBase<IBaseData> baseDataIndicator:
WarmUpIndicator(symbol, baseDataIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

default:
// Shouldn't happen, ConvertPythonIndicator will wrap the PyObject in a PythonIndicator instance if it can't convert it
throw new ArgumentException($"Indicator type {indicator.GetPythonType().Name} is not supported.");
}
if (indicator.TryConvert(out IndicatorBase<IBaseDataBar> indicatorDataBar))
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
/// <param name="symbol">The symbol or symbols to retrieve historical data for</param>
/// <param name="indicator">The indicator we want to warm up</param>
/// <param name="resolution">The resolution</param>
/// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
[DocumentationAttribute(Indicators)]
[DocumentationAttribute(HistoricalData)]
public void WarmUpIndicator(PyObject symbol, PyObject indicator, Resolution? resolution = null, PyObject selector = null)
{
// TODO: to be removed when https://github.com/QuantConnect/pythonnet/issues/62 is solved
var symbols = symbol.ConvertToSymbolEnumerable();
var convertedIndicator = ConvertPythonIndicator(indicator);
switch (convertedIndicator)
{
WarmUpIndicator(symbol, indicatorDataBar, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
return;
case PythonIndicator pythonIndicator:
WarmUpIndicator(symbols, pythonIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

case IndicatorBase<IndicatorDataPoint> dataPointIndicator:
WarmUpIndicator(symbols, dataPointIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
break;

case IndicatorBase<IBaseDataBar> baseDataBarIndicator:
WarmUpIndicator(symbols, baseDataBarIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
break;

case IndicatorBase<TradeBar> tradeBarIndicator:
WarmUpIndicator(symbols, tradeBarIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
break;

case IndicatorBase<IBaseData> baseDataIndicator:
WarmUpIndicator(symbols, baseDataIndicator, resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

default:
// Shouldn't happen, ConvertPythonIndicator will wrap the PyObject in a PythonIndicator instance if it can't convert it
throw new ArgumentException($"Indicator type {indicator.GetPythonType().Name} is not supported.");
}
if (indicator.TryConvert(out IndicatorBase<TradeBar> indicatorTradeBar))
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
/// <param name="symbol">The symbol or symbols to retrieve historical data for</param>
/// <param name="indicator">The indicator we want to warm up</param>
/// <param name="period">The necessary period to warm up the indicator</param>
/// <param name="selector">Selects a value from the BaseData send into the indicator, if null defaults to a cast (x => (T)x)</param>
[DocumentationAttribute(Indicators)]
[DocumentationAttribute(HistoricalData)]
public void WarmUpIndicator(PyObject symbol, PyObject indicator, TimeSpan period, PyObject selector = null)
{
var symbols = symbol.ConvertToSymbolEnumerable();
var convertedIndicator = ConvertPythonIndicator(indicator);
switch (convertedIndicator)
{
WarmUpIndicator(symbol, indicatorTradeBar, period, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
return;
}
case PythonIndicator pythonIndicator:
WarmUpIndicator(symbols, pythonIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

case IndicatorBase<IndicatorDataPoint> dataPointIndicator:
WarmUpIndicator(symbols, dataPointIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
break;

case IndicatorBase<IBaseDataBar> baseDataBarIndicator:
WarmUpIndicator(symbols, baseDataBarIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
break;

case IndicatorBase<TradeBar> tradeBarIndicator:
WarmUpIndicator(symbols, tradeBarIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
break;

case IndicatorBase<IBaseData> baseDataIndicator:
WarmUpIndicator(symbols, baseDataIndicator, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
break;

WarmUpIndicator(symbol, WrapPythonIndicator(indicator), period, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
default:
// Shouldn't happen, ConvertPythonIndicator will wrap the PyObject in a PythonIndicator instance if it can't convert it
throw new ArgumentException($"Indicator type {indicator.GetPythonType().Name} is not supported.");
}
}

/// <summary>
Expand Down Expand Up @@ -1824,23 +1932,7 @@ private IndicatorBase ConvertPythonIndicator(PyObject pyIndicator)
{
convertedIndicator = WrapPythonIndicator(pyIndicator, pythonIndicator);
}
else if (pyIndicator.TryConvert(out IndicatorBase<IndicatorDataPoint> dataPointIndicator))
{
convertedIndicator = dataPointIndicator;
}
else if (pyIndicator.TryConvert(out IndicatorBase<IBaseDataBar> dataBarIndicator))
{
convertedIndicator = dataBarIndicator;
}
else if (pyIndicator.TryConvert(out IndicatorBase<TradeBar> tradeBarIndicator))
{
convertedIndicator = tradeBarIndicator;
}
else if (pyIndicator.TryConvert(out IndicatorBase<IBaseData> baseDataIndicator))
{
convertedIndicator = baseDataIndicator;
}
else
else if (!pyIndicator.TryConvert(out convertedIndicator))
{
convertedIndicator = WrapPythonIndicator(pyIndicator);
}
Expand Down
Loading

0 comments on commit e3398e8

Please sign in to comment.