csharp/ABTSoftware/SciChart.Wpf.Examples/Examples/SciChart.Examples.ExternalDependencies/Data/MovingAverage.cs

MovingAverage.cs
// *************************************************************************************
// SCICHART� Copyright SciChart Ltd. 2011-2021. All rights reserved.
//  
// Web: http://www.scichart.com
//   Support: [email protected]
//   Sales:   [email protected]
// 
// MovingAverage.cs is part of SCICHART�, High Performance Scientific Charts
// For full terms and conditions of the license, see http://www.scichart.com/scichart-eula/
// 
// This source code is protected by international copyright law. Unauthorized
// reproduction, reverse-engineering, or distribution of all or any portion of
// this source code is strictly prohibited.
// 
// This source code contains confidential and proprietary trade secrets of
// SciChart Ltd., and should at no time be copied, transferred, sold,
// distributed or made available without express written permission.
// *************************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;

namespace SciChart.Examples.ExternalDependencies.Data
{
    public static clast MovingAverageExtensions
    {
        /// 
        /// Fast MovingAverage LINQ Extension method provided for example purposes on an AS IS BASIS ONLY. ACCURACY OF CALCULATION NOT GAURANTEED
        /// 
        public static IEnumerable MovingAverage(this IEnumerable inputStream, Func selector, int period)
        {
            var ma = new MovingAverage(period);
            foreach (var item in inputStream)
            {
                ma.Push(selector(item));
                yield return ma.Current;
            }
        }

        /// 
        /// Fast MovingAverage LINQ Extension method provided for example purposes on an AS IS BASIS ONLY. ACCURACY OF CALCULATION NOT GAURANTEED
        /// 
        public static IEnumerable MovingAverage(this IEnumerable inputStream, int period)
        {
            var ma = new MovingAverage(period);
            foreach (var item in inputStream)
            {
                ma.Push(item);
                yield return ma.Current;
            }
        }        

        /// 
        /// Fast Macd LINQ Extension method provided for example purposes on an AS IS BASIS ONLY. ACCURACY OF CALCULATION NOT GAURANTEED
        /// 
        public static IEnumerable Macd(this IEnumerable inputStream, int slow, int fast, int signal)
        {
            var maSlow = new MovingAverage(slow);
            var maFast = new MovingAverage(fast);
            var maSignal = new MovingAverage(signal);

            foreach (var item in inputStream)
            {
                double macd = maSlow.Push(item).Current - maFast.Push(item).Current;
                double signalLine = double.IsNaN(macd) ? double.NaN : maSignal.Push(macd).Current;
                double divergence = double.IsNaN(macd) || double.IsNaN(signalLine) ? double.NaN : macd - signalLine;

                yield return new MacdPoint() { Macd = macd, Signal = signalLine, Divergence = divergence };
            }
        }

        /// 
        /// Fast Rsi LINQ Extension method provided for example purposes on an AS IS BASIS ONLY. ACCURACY OF CALCULATION NOT GAURANTEED
        /// 
        public static IEnumerable Rsi(this IEnumerable inputStream, int period)
        {
            var averageGain = new MovingAverage(period);
            var averageLoss = new MovingAverage(period);

            var previous = inputStream.First();
            foreach (var item in inputStream)
            {
                double gain = item.Close > previous.Close ? item.Close - previous.Close : 0.0;
                double loss = previous.Close > item.Close ? previous.Close - item.Close : 0.0;

                averageGain.Push(gain);
                averageLoss.Push(loss);

                double relativeStrength = double.IsNaN(averageGain.Current) || double.IsNaN(averageLoss.Current) ? double.NaN : averageGain.Current/averageLoss.Current;

                previous = item;
                yield return double.IsNaN(relativeStrength) ? double.NaN : 100.0 - (100.0/(1.0 + relativeStrength));
            }
        }
    }

    public struct MacdPoint
    {
        public double Macd;
        public double Signal;
        public double Divergence;
    }

    /// 
    /// Fast Moving Average clast provided for example purposes on an AS IS BASIS ONLY. ACCURACY OF CALCULATION NOT GAURANTEED
    /// 
    public clast MovingAverage
    {
        private readonly int _length;
        private int _circIndex = -1;
        private bool _filled;
        private double _current = double.NaN;        
        private readonly double _oneOverLength;
        private readonly double[] _circularBuffer;
        private double _total;

        public MovingAverage(int length)
        {
            _length = length;
            _oneOverLength = 1.0/length;
            _circularBuffer = new double[length];
        }

        public MovingAverage Update(double value)
        { 
            double lostValue = _circularBuffer[_circIndex];
            _circularBuffer[_circIndex] = value;

            // Maintain totals for Push function
            // skip NaN 
            _total += double.IsNaN(value) ? 0d :value;
            _total -= lostValue;

            // If not yet filled, just return. Current value should be double.NaN
            if (!_filled)
            {
                _current = double.NaN;
                return this;
            }

            // Compute the average
            double average = 0.0;
            for (int i = 0; i < _circularBuffer.Length; i++)
            {
                average += _circularBuffer[i];
            }

            _current = average * _oneOverLength;

            return this;
        }

        public MovingAverage Push(double value)
        {
            // Apply the circular buffer
            if (++_circIndex == _length)
            {
                _circIndex = 0;
            }

            double lostValue = _circularBuffer[_circIndex];
            _circularBuffer[_circIndex] = value;

            // Compute the average
            // Skip NaN 
            _total += double.IsNaN(value) ? 0d : value;
            _total -= lostValue;
      
            // If not yet filled, just return. Current value should be double.NaN
            if (!_filled && _circIndex != _length -1)
            {
                _current = double.NaN;
                return this;
            }
            else
            {
                // Set a flag to indicate this is the first time the buffer has been filled
                _filled = true;
            }

            _current = _total * _oneOverLength;

            return this;
        }

        public int Length { get { return _length; }}
        public double Current { get { return _current; } }
    }
}