It occured to me that Indicators may not be as 'fresh' as they should be, because the last sample of the indicator may be quite old depending on the resolution.
Consider this scenario:
you have a custom indicator RelativeStrengthIndex() with a period of 14 and resolution 1 hour.
As far as I can see, the Indicator is only updated once an hour, so the last sample could be up to 59 minutes old.
What I would really like to do is update the last sample of the Indicator every minute, and then once per hour increment to the next sample.
This is probably overkill, but ideally the Indicator would be updated every minute, with 14 samples, each exactly 1 hour apart.
Is there an easy way to do this for any Indicator?
or should I subclass RelativeStrengthIndex() to do this?
Keith K
P.S. Right now my algorithm works around this problem by creating a number of overlapping indicators, each with progressively smaller and smaller resolutions. However my algorithms decision tree would be much simpler (and hopefully faster and *better*) if I could combine everything into one super-indicator that did all the work.
Keith K
I'm writing a subclass of RelativeStrengthIndex to do what I need.
However, because IndicatorBase has too many protected members, I have to copy and subclass like 20 moving average indicators to subclass it.
This would be much cleaner and generic if IndicatorBase.ComputeNextValue() and IndicatorBase.previousValue were public, or if my subclass could be added to the QuantConnect.Indicators assembly.
I'm starting to not like C#. Protectionism is good. However, C# offers no built in mechanism for taking off the training wheels. Sometimes you need to write a hack... if the language has no mechanism for writing a hack, then people can't get their work done. Maybe I could write a hack using Reflection?
Keith K
Here's my subclass (and Extension class)
//USAGE: // RelativeStrengthIndex_Fresh rsi = new RelativeStrengthIndex_Fresh(14,MovingAverageType.Wilders); // between periods, call these: // rsi.UpdateFresh( time, value ); // decimal fresh = rsi.GetFresh().Value;; // // at end of each period, call normal Update: // rsi.Update(time, value); using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using QuantConnect.Data; using QuantConnect; using QuantConnect.Indicators; namespace QuantConnect.Indicators { public interface IIndicatorFresh { decimal RecomputeNextValue(IndicatorDataPoint input); // NOTE: I need to override this also, but I can't because its in a different assembly. // previousInput is declared protected // decimal ComputeNextValue(IndicatorDataPoint input); } /// <summary> /// Represents the Relative Strength Index (RSI) developed by K. Welles Wilder. /// You can optionally specified a different moving average type to be used in the computation /// </summary> public class RelativeStrengthIndex_Fresh : RelativeStrengthIndex, IIndicatorFresh { /// <summary>the most recent input that was given to this indicator</summary> IndicatorDataPoint previousInput; /// <summary> /// Computes the next value of this indicator from the given state /// </summary> /// <param name="input">The input given to the indicator</param> /// <returns>A new value for this indicator</returns> protected override decimal ComputeNextValue(IndicatorDataPoint input) { if (previousInput != null && input.Value >= previousInput.Value) { AverageGain.Update(input.Time, input.Value - previousInput.Value); AverageLoss.Update(input.Time, 0m); } else if (previousInput != null && input.Value < previousInput.Value) { AverageGain.Update(input.Time, 0m); AverageLoss.Update(input.Time, previousInput.Value - input.Value); } previousInput = input; if (AverageLoss == 0m) { // all up days is 100 return 100m; } var rs = AverageGain / AverageLoss; return 100m - (100m / (1 + rs)); } /// <summary> /// Recomputes the Fresh value (But not the next value) of this indicator from the given state /// </summary> /// <param name="input">The input given to the indicator</param> /// <returns>A new value for this indicator</returns> public decimal RecomputeNextValue(IndicatorDataPoint input) { if (previousInput != null && input.Value >= previousInput.Value) { //IndicatorBase<BaseData> me = (IndicatorBase<BaseData>) AverageGain; //me.UpdateFresh(input.Time, input.Value - previousInput.Value); AverageGain.UpdateFresh(input.Time, input.Value - previousInput.Value); AverageLoss.UpdateFresh(input.Time, 0m); } else if (previousInput != null && input.Value < previousInput.Value) { AverageGain.UpdateFresh(input.Time, 0m); AverageLoss.UpdateFresh(input.Time, previousInput.Value - input.Value); } // previousInput = input; IndicatorDataPoint lossFresh = AverageLoss.GetFresh(); if (lossFresh == 0m) { // all up days is 100 return 100m; } var rs = AverageGain.GetFresh().Value / lossFresh; return 100m - (100m / (1 + rs)); } /// <summary> /// constcutor /// </summary> /// <param name="period"></param> /// <param name="movingAverageType"></param> public RelativeStrengthIndex_Fresh(int period, MovingAverageType movingAverageType = MovingAverageType.Wilders) : base(period, movingAverageType) { //AverageGain = movingAverageType.AsIndicator(this.Name + "Up", period); //AverageLoss = movingAverageType.AsIndicator(this.Name + "Down", period); } } /// <summary> /// more extenstions for Indicators /// </summary> public static class IndicatorExtensions2 { /// <summary> /// helper function /// </summary> /// <param name="indicator"></param> /// <param name="time"></param> /// <param name="value"></param> /// <returns></returns> public static bool UpdateFresh(this IndicatorBase<IndicatorDataPoint> indicator, DateTime time, decimal value) { return indicator.UpdateFresh(new IndicatorDataPoint(time, value)); } static Dictionary<int, IndicatorDataPoint> sFresh = new Dictionary<int, IndicatorDataPoint>(); /// <summary> /// Updates the Fresh state (as opposed to the Current state) /// of this indicator with the given value and returns true /// if this indicator is ready, false otherwise /// </summary> /// <param name="input">The value to use to update this indicator</param> /// <returns>True if this indicator is ready, false otherwise</returns> public static bool UpdateFresh(this IndicatorBase<IndicatorDataPoint> indicator, IndicatorDataPoint input) { IndicatorDataPoint _freshInput = indicator.GetFresh(); if (_freshInput != null && input.Time < _freshInput.Time) { // if we receive a time in the past, throw throw new ArgumentException(string.Format("This is a forward only indicator: {0} Input: {1} Previous: {2}", indicator.Name, input.Time.ToString("u"), _freshInput.Time.ToString("u"))); } if (!ReferenceEquals(input, _freshInput)) { // compute a new value and update our previous time //Samples++; _freshInput = input; var nextResult = indicator.ValidateAndRecomputeNextValue(input); if (nextResult.Status == IndicatorStatus.Success) { _freshInput = new IndicatorDataPoint(input.Time, nextResult.Value); //Current = new IndicatorDataPoint(input.Time, nextResult.Value); // let others know we've produced a new data point // tbd OnUpdated_Fresh(Current); } } if (_freshInput != null) indicator.SetFresh(_freshInput); return indicator.IsReady; } /// <summary> /// Computes the next value of this indicator from the given state /// </summary> /// <param name="input">The input given to the indicator</param> /// <returns>A new value for this indicator</returns> //static decimal RecomputeNextValue(this IndicatorBase<IndicatorDataPoint> indicator, IndicatorDataPoint input) //{ // return 0; //} /// <summary> /// Computes the next value of this indicator from the given state /// and returns an instance of the <see cref="IndicatorResult"/> class /// </summary> /// <param name="input">The input given to the indicator</param> /// <returns>An IndicatorResult object including the status of the indicator</returns> static IndicatorResult ValidateAndRecomputeNextValue(this IndicatorBase<IndicatorDataPoint> indicator, IndicatorDataPoint input) { if ( indicator is IIndicatorFresh ) { return new IndicatorResult(((IIndicatorFresh)indicator).RecomputeNextValue(input)); } return new IndicatorResult(indicator.ComputeNextValue(input)); // default implementation always returns IndicatorStatus.Success //return new IndicatorResult(indicator.RecomputeNextValue(input)); } /// <summary> /// Gets the freshest state of this indicator. If the state has not been updated /// then the time on the value will equal DateTime.MinValue. /// </summary> public static IndicatorDataPoint GetFresh(this IndicatorBase<IndicatorDataPoint> indicator) { object obj = indicator; int hash = obj.GetHashCode(); if (!sFresh.ContainsKey(hash)) return new IndicatorDataPoint(); return sFresh[hash]; } /// <summary> /// Gets the freshest state of this indicator. If the state has not been updated /// then the time on the value will equal DateTime.MinValue. /// </summary> public static void SetFresh(this IndicatorBase<IndicatorDataPoint> indicator, IndicatorDataPoint input) { object obj = indicator; int hash = obj.GetHashCode(); sFresh[hash] = input; } } }
Keith K
oh wow I may have discovered something unexpected by going through this exercise...
Levitikon
You know what might work - dropping to 1 minute resolution and using a 840 (14 * 60) period RSI. This works for some indicators like MA, but when things like High/Low are factored it, it gets a little weird.
Keith K
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!