Hi all, very new to QC, just sharing a example of an Alpha indicator using RollingWindows under QC Framework. Basically using an EMA crossover of EMA values 1 day earlier (e.g. _fastEmaWin[1] instead of _fastEmaWin[0]). Hope for any feedback/comments, on whether there's a better way of doing this. Are many of you also using the QC Framework model, or not?
Something to note is that in the QC example RollingWindowAlgorithm.cs file, the event handler/delegate is set-up before the RollingWindow is declared, however when I ran the program i encountered bugs, and had to swap the order (create RollingWindow before adding event handler. Wonder why is that?
From QC example:
SMA("SPY", 5).Updated += (sender, updated) => _smaWin.Add(updated);
_smaWin = new RollingWindow<IndicatorDataPoint>(5);
From my alpha example:
_fastEmaWin = new RollingWindow<IndicatorDataPoint>(5);
algorithm.EMA(added.Symbol, _fastPeriod).Updated += (sender, updated) => _fastEmaWin.Add(updated);
Full alphas code (EMAWindowAlphaModel.cs):
/*
* 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 System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.Framework.Alphas
{
/// <summary>
/// Alpha model that uses an EMA cross to create insights
/// </summary>
public class EMAWindowAlphaModel : AlphaModel
{
private readonly int _fastPeriod;
private readonly int _slowPeriod;
private readonly Resolution _resolution;
private readonly int _predictionInterval;
private readonly Dictionary<Symbol, SymbolData> _symbolData;
/// <summary>
/// Initializes a new instance of the <see cref="EmaCrossAlphaModel"/> class
/// </summary>
/// <param name="fastPeriod">The fast EMA period</param>
/// <param name="slowPeriod">The slow EMA period</param>
/// <param name="resolution">The resolution of data sent into the EMA indicators</param>
public EMAWindowAlphaModel(
int fastPeriod = 12,
int slowPeriod = 30,
Resolution resolution = Resolution.Daily
)
{
_fastPeriod = fastPeriod;
_slowPeriod = slowPeriod;
_resolution = resolution;
_predictionInterval = fastPeriod;
_symbolData = new Dictionary<Symbol, SymbolData>();
Name = $"{nameof(EMAWindowAlphaModel)}({fastPeriod},{slowPeriod},{resolution})";
}
/// <summary>
/// Updates this alpha model with the latest data from the algorithm.
/// This is called each time the algorithm receives data for subscribed securities
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="data">The new data available</param>
/// <returns>The new insights generated</returns>
public override IEnumerable<Insight> Update(QCAlgorithmFramework algorithm, Slice data)
{
//var insights = new List<Insight>();
foreach (var sd in _symbolData.Values)
{
if (sd._FastEmaWin.IsReady && sd._SlowEmaWin.IsReady)
{
var direction = InsightDirection.Flat;
if (sd._FastEmaWin[1] > sd._SlowEmaWin[1])
{
//insights.Add(Insight.Price(sd.Symbol, insightPeriod, InsightDirection.Up));
direction = InsightDirection.Up;
}
else if (sd._FastEmaWin[1] < sd._SlowEmaWin[1])
{
//insights.Add(Insight.Price(sd.Symbol, insightPeriod, InsightDirection.Down));
direction = InsightDirection.Down;
}
if (direction == sd.PreviousDirection)
{
continue;
}
var insightPeriod = _resolution.ToTimeSpan().Multiply(_predictionInterval);
var insight = Insight.Price(sd.Symbol, insightPeriod, direction);
sd.PreviousDirection = direction;
yield return insight;
}
}
// return insights;
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public override void OnSecuritiesChanged(QCAlgorithmFramework algorithm, SecurityChanges changes)
{
foreach (var added in changes.AddedSecurities)
{
SymbolData symbolData;
if (!_symbolData.TryGetValue(added.Symbol, out symbolData))
{
// var myEma = algorithm.EMA(added.Symbol, _emaPeriod, _resolution);
RollingWindow<IndicatorDataPoint> _fastEmaWin;
RollingWindow<IndicatorDataPoint> _slowEmaWin;
_fastEmaWin = new RollingWindow<IndicatorDataPoint>(5);
algorithm.EMA(added.Symbol, _fastPeriod).Updated += (sender, updated) => _fastEmaWin.Add(updated);
_slowEmaWin = new RollingWindow<IndicatorDataPoint>(5);
algorithm.EMA(added.Symbol, _slowPeriod).Updated += (sender, updated) => _slowEmaWin.Add(updated);
_symbolData[added.Symbol] = new SymbolData
{
Security = added,
_FastEmaWin = _fastEmaWin,
_SlowEmaWin = _slowEmaWin
};
}
else
{
// a security that was already initialized was re-added, reset the indicators
symbolData._FastEmaWin.Reset();
symbolData._SlowEmaWin.Reset();
}
}
}
/// <summary>
/// Contains data specific to a symbol required by this model
/// </summary>
private class SymbolData
{
public InsightDirection? PreviousDirection { get; set; }
public Security Security { get; set; }
public Symbol Symbol => Security.Symbol;
//public ExponentialMovingAverage MyEma { get; set; }
public RollingWindow<IndicatorDataPoint> _FastEmaWin;
public RollingWindow<IndicatorDataPoint> _SlowEmaWin;
}
}
}
Michael Handschuh
Welcome to QuantConnect Joe!
Overall things looks great, but you'll want to also reset your EMA indicator and not just the rollowing windows. First, I think you'll want to save your indicators to your SymbolData class so that you can call Reset on them along with the rolling windows. The issue is that if you don't reset the indicator, then they'll still have data from the last time the security was in the universe. Resetting the underlying indicator will fix this and set them up to receive fresh data. Something like this should work:
public override void OnSecuritiesChanged(QCAlgorithmFramework algorithm, SecurityChanges changes) { foreach (var added in changes.AddedSecurities) { SymbolData symbolData; if (!_symbolDataBySymbol.TryGetValue(added.Symbol, out symbolData)) { // create fast/slow EMAs and windows var fastWindow = new RollingWindow<IndicatorDataPoint>(5); var fast = algorithm.EMA(added.Symbol, _fastPeriod, _resolution); fast.Updated += (sender, updated) => fastWindow.Add(updated); var slowWindow = new RollingWindow<IndicatorDataPoint>(5); var slow = algorithm.EMA(added.Symbol, _slowPeriod, _resolution); slow.Updated += (sender, updated) => slowWindow.Add(updated); _symbolDataBySymbol[added.Symbol] = new SymbolData { Security = added, Fast = fast, FastWindow = fastWindow, Slow = slow, SlowWindow = slowWindow }; } else { // a security that was already initialized was re-added, reset the indicators and windows symbolData.Fast.Reset(); symbolData.FastWindow.Reset(); symbolData.Slow.Reset(); symbolData.SlowWindow.Reset(); } } } private class SymbolData { public Security Security { get; set; } public Symbol Symbol => Security.Symbol; public ExponentialMovingAverage Fast { get; set; } public ExponentialMovingAverage Slow { get; set; } public RollingWindow<IndicatorDataPoint> FastWindow { get; set; } public RollingWindow<IndicatorDataPoint> SlowWindow { get; set; } public bool FastIsOverSlow { get; set; } public bool SlowIsOverFast => !FastIsOverSlow; }
Joe Ng
Hi Michael, thanks for the tip!
Joe Ng
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!