Skip to content

Commit

Permalink
Fix warm up indicator bugs (#8279)
Browse files Browse the repository at this point in the history
* Potential solution

* Try a different approach

* Improve unit tests

* Nit change

* Improve implementation

* Fix another bug and improve unit tests
  • Loading branch information
Marinovsky authored Aug 26, 2024
1 parent f312517 commit 3a09c70
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 5 deletions.
11 changes: 6 additions & 5 deletions Algorithm/QCAlgorithm.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3094,7 +3094,7 @@ public void WarmUpIndicator<T>(Symbol symbol, IndicatorBase<T> indicator, TimeSp
private IEnumerable<Slice> GetIndicatorWarmUpHistory(IEnumerable<Symbol> symbols, IIndicator indicator, TimeSpan timeSpan, out bool identityConsolidator)
{
identityConsolidator = false;
if (AssertIndicatorHasWarmupPeriod(indicator))
if (!AssertIndicatorHasWarmupPeriod(indicator))
{
return Enumerable.Empty<Slice>();
}
Expand Down Expand Up @@ -3143,16 +3143,17 @@ private void WarmUpIndicatorImpl<T>(Symbol symbol, TimeSpan period, Action<T> ha
where T : class, IBaseData
{
IDataConsolidator consolidator;
if (identityConsolidator)
{
period = TimeSpan.Zero;
}
if (SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).Count > 0)
{
consolidator = Consolidate(symbol, period, handler);
}
else
{
if (identityConsolidator)
{
period = TimeSpan.Zero;
}

var providedType = typeof(T);
if (providedType.IsAbstract)
{
Expand Down
30 changes: 30 additions & 0 deletions Algorithm/QCAlgorithm.Python.cs
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,36 @@ public void WarmUpIndicator(Symbol symbol, PyObject indicator, Resolution? resol
WarmUpIndicator(symbol, WrapPythonIndicator(indicator), resolution, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}

/// <summary>
/// Warms up a given indicator with historical data
/// </summary>
/// <param name="symbol">The symbol 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 send into the indicator, if null defaults to a cast (x => (T)x)</param>
[DocumentationAttribute(Indicators)]
[DocumentationAttribute(HistoricalData)]
public void WarmUpIndicator(Symbol symbol, PyObject indicator, TimeSpan period, PyObject selector = null)
{
if (indicator.TryConvert(out IndicatorBase<IndicatorDataPoint> indicatorDataPoint))
{
WarmUpIndicator(symbol, indicatorDataPoint, period, selector?.ConvertToDelegate<Func<IBaseData, decimal>>());
return;
}
if (indicator.TryConvert(out IndicatorBase<IBaseDataBar> indicatorDataBar))
{
WarmUpIndicator(symbol, indicatorDataBar, period, selector?.ConvertToDelegate<Func<IBaseData, IBaseDataBar>>());
return;
}
if (indicator.TryConvert(out IndicatorBase<TradeBar> indicatorTradeBar))
{
WarmUpIndicator(symbol, indicatorTradeBar, period, selector?.ConvertToDelegate<Func<IBaseData, TradeBar>>());
return;
}

WarmUpIndicator(symbol, WrapPythonIndicator(indicator), period, selector?.ConvertToDelegate<Func<IBaseData, IBaseData>>());
}

/// <summary>
/// Plot a chart using string series name, with value.
/// </summary>
Expand Down
63 changes: 63 additions & 0 deletions Tests/Algorithm/AlgorithmIndicatorsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,69 @@ public void IndicatorsPassingHistory(Language language)
Assert.IsTrue(indicator.IsReady);
}

[Test]
public void PythonIndicatorCanBeWarmedUpWithTimespan()
{
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
var indicator = new SimpleMovingAverage("SMA", 100);
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
_algorithm.AddEquity(referenceSymbol);
using (Py.GIL())
{
var pythonIndicator = indicator.ToPython();
_algorithm.WarmUpIndicator(referenceSymbol, pythonIndicator, TimeSpan.FromMinutes(60));
Assert.IsTrue(pythonIndicator.GetAttr("is_ready").GetAndDispose<bool>());
Assert.IsTrue(pythonIndicator.GetAttr("samples").GetAndDispose<int>() >= 100);
}
}

[Test]
public void IndicatorCanBeWarmedUpWithTimespan()
{
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
_algorithm.AddEquity(referenceSymbol);
var indicator = new SimpleMovingAverage("SMA", 100);
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
_algorithm.WarmUpIndicator(referenceSymbol, indicator, TimeSpan.FromMinutes(60));
Assert.IsTrue(indicator.IsReady);
Assert.IsTrue(indicator.Samples >= 100);
}

[Test]
public void PythonCustomIndicatorCanBeWarmedUpWithTimespan()
{
var referenceSymbol = Symbol.Create("IBM", SecurityType.Equity, Market.USA);
_algorithm.AddEquity(referenceSymbol);
_algorithm.SetDateTime(new DateTime(2013, 10, 11));
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
from collections import deque
class CustomSimpleMovingAverage(PythonIndicator):
def __init__(self, name, period):
super().__init__()
self.warm_up_period = period
self.name = name
self.value = 0
self.queue = deque(maxlen=period)
# Update method is mandatory
def update(self, input):
self.queue.appendleft(input.value)
count = len(self.queue)
self.value = np.sum(self.queue) / count
return count == self.queue.maxlen");

var customIndicator = testModule.GetAttr("CustomSimpleMovingAverage").Invoke("custom".ToPython(), 100.ToPython());
_algorithm.WarmUpIndicator(referenceSymbol, customIndicator, TimeSpan.FromMinutes(60));
Assert.IsTrue(customIndicator.GetAttr("is_ready").GetAndDispose<bool>());
Assert.IsTrue(customIndicator.GetAttr("samples").GetAndDispose<int>() >= 100);
}
}

[TestCase("count")]
[TestCase("StartAndEndDate")]
public void IndicatorUpdatedWithSymbol(string testCase)
Expand Down

0 comments on commit 3a09c70

Please sign in to comment.