/* * 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; using QuantConnect.Data; using System.Diagnostics; using QuantConnect.Logging; using System.Collections.Generic; using QuantConnect.Data.Consolidators; using System.Collections; namespace QuantConnect.Indicators { /// /// Abstract Indicator base, meant to contain non-generic fields of indicator base to support non-typed inputs /// public abstract partial class IndicatorBase : IIndicator, IEnumerable { /// /// The data consolidators associated with this indicator if any /// /// These references allow us to unregister an indicator from getting future data updates through it's consolidators. /// We need multiple consolitadors because some indicators consume data from multiple different symbols public ISet Consolidators { get; } = new HashSet(); /// /// Gets the current state of this indicator. If the state has not been updated /// then the time on the value will equal DateTime.MinValue. /// public IndicatorDataPoint Current { get { return Window[0]; } protected set { Window.Add(value); } } /// /// Gets the previous state of this indicator. If the state has not been updated /// then the time on the value will equal DateTime.MinValue. /// public IndicatorDataPoint Previous { get { return Window.Count > 1 ? Window[1] : new IndicatorDataPoint(DateTime.MinValue, 0); } } /// /// Gets a name for this indicator /// public string Name { get; protected set; } /// /// Gets the number of samples processed by this indicator /// public long Samples { get; internal set; } /// /// Gets a flag indicating when this indicator is ready and fully initialized /// public abstract bool IsReady { get; } /// /// Event handler that fires after this indicator is updated /// public event IndicatorUpdatedHandler Updated; /// /// A rolling window keeping a history of the indicator values of a given period /// public RollingWindow Window { get; } /// /// Resets this indicator to its initial state /// public abstract void Reset(); /// /// Initializes a new instance of the Indicator class. /// protected IndicatorBase() { Window = new RollingWindow(Indicator.DefaultWindowSize); Current = new IndicatorDataPoint(DateTime.MinValue, 0m); } /// /// Initializes a new instance of the Indicator class using the specified name. /// /// The name of this indicator protected IndicatorBase(string name) : this() { Name = name; } /// /// Event invocator for the Updated event /// /// This is the new piece of data produced by this indicator protected virtual void OnUpdated(IndicatorDataPoint consolidated) { Updated?.Invoke(this, consolidated); } /// /// Updates the state of this indicator with the given value and returns true /// if this indicator is ready, false otherwise /// /// The value to use to update this indicator /// True if this indicator is ready, false otherwise public abstract bool Update(IBaseData input); /// /// Indexes the history windows, where index 0 is the most recent indicator value. /// If index is greater or equal than the current count, it returns null. /// If the index is greater or equal than the window size, it returns null and resizes the windows to i + 1. /// /// The index /// the ith most recent indicator value public IndicatorDataPoint this[int i] { get { return Window[i]; } } /// /// Returns an enumerator that iterates through the history window. /// /// /// A that can be used to iterate through the history window. /// /// 1 public IEnumerator GetEnumerator() { return Window.GetEnumerator(); } /// /// Returns an enumerator that iterates through the history window. /// /// /// An object that can be used to iterate through the history window. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// ToString Overload for Indicator Base /// /// String representation of the indicator public override string ToString() { return Current.Value.ToStringInvariant("#######0.0####"); } /// /// Provides a more detailed string of this indicator in the form of {Name} - {Value} /// /// A detailed string of this indicator's current state public string ToDetailedString() { return $"{Name} - {this}"; } /// /// Compares the current object with another object of the same type. /// /// /// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the parameter.Zero This object is equal to . Greater than zero This object is greater than . /// /// An object to compare with this object. public int CompareTo(IIndicator other) { if (ReferenceEquals(other, null)) { // everything is greater than null via MSDN return 1; } return Current.CompareTo(other.Current); } /// /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. /// /// An object to compare with this instance. is not the same type as this instance. 2 public int CompareTo(object obj) { var other = obj as IndicatorBase; if (other == null) { throw new ArgumentException("Object must be of type " + GetType().GetBetterTypeName()); } return CompareTo(other); } } /// /// Provides a base type for all indicators /// /// The type of data input into this indicator [DebuggerDisplay("{ToDetailedString()}")] public abstract class IndicatorBase : IndicatorBase where T : IBaseData { private bool _loggedForwardOnlyIndicatorError; /// the most recent input that was given to this indicator private Dictionary _previousInput = new Dictionary(); /// /// Initializes a new instance of the Indicator class using the specified name. /// /// The name of this indicator protected IndicatorBase(string name) : base(name) { } /// /// Updates the state of this indicator with the given value and returns true /// if this indicator is ready, false otherwise /// /// The value to use to update this indicator /// True if this indicator is ready, false otherwise public override bool Update(IBaseData input) { T _previousSymbolInput = default(T); if (_previousInput.TryGetValue(input.Symbol.ID, out _previousSymbolInput) && input.EndTime < _previousSymbolInput.EndTime) { if (!_loggedForwardOnlyIndicatorError) { _loggedForwardOnlyIndicatorError = true; // if we receive a time in the past, log once and return Log.Error($"IndicatorBase.Update(): This is a forward only indicator: {Name} Input: {input.EndTime:u} Previous: {_previousSymbolInput.EndTime:u}. It will not be updated with this input."); } return IsReady; } if (!ReferenceEquals(input, _previousSymbolInput)) { // compute a new value and update our previous time Samples++; if (!(input is T)) { if (typeof(T) == typeof(IndicatorDataPoint)) { input = new IndicatorDataPoint(input.Symbol, input.EndTime, input.Value); } else { throw new ArgumentException($"IndicatorBase.Update() 'input' expected to be of type {typeof(T)} but is of type {input.GetType()}"); } } _previousInput[input.Symbol.ID] = (T)input; var nextResult = ValidateAndComputeNextValue((T)input); if (nextResult.Status == IndicatorStatus.Success) { Current = new IndicatorDataPoint(input.Symbol, input.EndTime, nextResult.Value); // let others know we've produced a new data point OnUpdated(Current); } } return IsReady; } /// /// Updates the state of this indicator with the given value and returns true /// if this indicator is ready, false otherwise /// /// The time associated with the value /// The value to use to update this indicator /// True if this indicator is ready, false otherwise public bool Update(DateTime time, decimal value) { if (typeof(T) == typeof(IndicatorDataPoint)) { return Update((T)(object)new IndicatorDataPoint(time, value)); } var suggestions = new List { "Update(TradeBar)", "Update(QuoteBar)" }; if (typeof(T) == typeof(IBaseData)) { suggestions.Add("Update(Tick)"); } throw new NotSupportedException($"{GetType().Name} does not support the `Update(DateTime, decimal)` method. Use one of the following methods instead: {string.Join(", ", suggestions)}"); } /// /// Resets this indicator to its initial state /// public override void Reset() { Samples = 0; _previousInput.Clear(); Window.Reset(); Current = new IndicatorDataPoint(DateTime.MinValue, default(decimal)); } /// /// Computes the next value of this indicator from the given state /// /// The input given to the indicator /// A new value for this indicator protected abstract decimal ComputeNextValue(T input); /// /// Computes the next value of this indicator from the given state /// and returns an instance of the class /// /// The input given to the indicator /// An IndicatorResult object including the status of the indicator protected virtual IndicatorResult ValidateAndComputeNextValue(T input) { // default implementation always returns IndicatorStatus.Success return new IndicatorResult(ComputeNextValue(input)); } /// /// Determines whether the specified object is equal to the current object. /// /// /// true if the specified object is equal to the current object; otherwise, false. /// /// The object to compare with the current object. public override bool Equals(object obj) { // this implementation acts as a liason to prevent inconsistency between the operators // == and != against primitive types. the core impl for equals between two indicators // is still reference equality, however, when comparing value types (floats/int, ect..) // we'll use value type semantics on Current.Value // because of this, we shouldn't need to override GetHashCode as well since we're still // solely relying on reference semantics (think hashset/dictionary impls) if (ReferenceEquals(obj, null)) return false; var type = obj.GetType(); while (type != null && type != typeof(object)) { var cur = type.IsGenericType ? type.GetGenericTypeDefinition() : type; if (typeof(IndicatorBase<>) == cur) { return ReferenceEquals(this, obj); } type = type.BaseType; } try { // the obj is not an indicator, so let's check for value types, try converting to decimal var converted = obj.ConvertInvariant(); return Current.Value == converted; } catch (InvalidCastException) { // conversion failed, return false return false; } } /// /// Get Hash Code for this Object /// /// Integer Hash Code public override int GetHashCode() { return base.GetHashCode(); } } }