File: Common\Formulas\Oscillator.cs
Project: ndp\fx\src\DataVisualization\System.Windows.Forms.DataVisualization.csproj (System.Windows.Forms.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		Oscillator.cs
//
//  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting.Formulas
//
//	Classes:	Oscillators
//
//  Purpose:	This class is used to calculate oscillator 
//				indicators used in Technical Analyses.
//
//	Reviewed:	GS - August 7, 2002
//				AG - August 7, 2002
//
//===================================================================
 
 
using System;
using System.Globalization;
 
 
#if Microsoft_CONTROL
	namespace System.Windows.Forms.DataVisualization.Charting.Formulas
#else
	namespace System.Web.UI.DataVisualization.Charting.Formulas
#endif
{
	/// <summary>
	/// This class is used to calculate oscillator 
	///	indicators used in Technical Analyses.
	/// </summary>
	internal class Oscillators : PriceIndicators
	{
		#region Properties
 
		/// <summary>
		/// Formula Module name
		/// </summary>
		override public string Name			{ get{ return "Oscillators";}}
 
		#endregion
 
		#region Formulas
 
		/// <summary>
		/// The Chaikin Oscillator is created by subtracting a 10 period 
		/// exponential moving average of the Accumulation/Distribution 
		/// line from a 3 period moving average of the 
		/// Accumulation/Distribution Line. 
		/// ---------------------------------------------------------
		/// Input: 
		///		- 4 Y values ( Hi, Low, Close, Volume ).
		/// Output: 
		///		- 1 Y value Chaikin Oscillator
		/// Parameters: 
		///		- Short Period for Exponential Moving average (default=3)
		///		- Int64 Period for Exponential Moving average (default=10)
		///	Extra Parameters: 
		///		- Start from First
		/// </summary>
		/// <param name="inputValues">Arrays of doubles - Input values</param>
		/// <param name="outputValues">Arrays of doubles - Output values</param>
		/// <param name="parameterList">Array of strings - Parameters</param>
		/// <param name="extraParameterList">Array of strings - Extra parameters</param>
		private void ChaikinOscillator(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
		{
			// There is no enough input series
			if( inputValues.Length != 5 )
			{
				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresFourArrays);
			}
						
			// Different number of x and y values
			CheckNumOfValues( inputValues, 4 );
 
			// Short Period for Exp moving average
			int shortPeriod;
            if (parameterList.Length < 1 || 
                !int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out shortPeriod))
            {
                shortPeriod = 3;
            }
			
			// Int64 Period for Exp moving average
			int longPeriod;
            if (parameterList.Length < 2 || 
                !int.TryParse(parameterList[1], NumberStyles.Any, CultureInfo.InvariantCulture, out longPeriod))
            {
                longPeriod = 10;
            }
 
			if( shortPeriod > longPeriod || longPeriod <= 0 || shortPeriod <= 0 )
			{
                throw new ArgumentException(SR.ExceptionOscillatorObjectInvalidPeriod);
			}
 
			// Starting average from the first data point or after period.
			bool startFromFirst = bool.Parse( extraParameterList[0] );
 
			VolumeIndicators volume = new VolumeIndicators();
 
			double [][] outputDistribution = new double [2][];
 
			// Accumulation Distribution
			volume.AccumulationDistribution( inputValues, out outputDistribution );
 
			double [] ExpAvgDistribution;
 
			// Exponential Moving average of Accumulation Distribution
			ExponentialMovingAverage(outputDistribution[1],out ExpAvgDistribution,longPeriod,startFromFirst);
 
			double [] ExpAvg;
 
			// Exponential Moving average of close
			ExponentialMovingAverage(outputDistribution[1],out ExpAvg,shortPeriod,startFromFirst);
 
			outputValues = new double [2][];
 
			int period = Math.Min(ExpAvg.Length,ExpAvgDistribution.Length);
 
			outputValues[0] = new double [period];
			outputValues[1] = new double [period];
 
			// Accumulation Distribution
			int expIndex = 0;
			for( int index = inputValues[1].Length - period; index < inputValues[1].Length; index++ )
			{
				// Set X values
				outputValues[0][expIndex] = inputValues[0][index];
 
				// Set Y values
				if(startFromFirst)
				{
					// Number of items in all arays is the same and they are aligned by time.
					outputValues[1][expIndex] = ExpAvg[expIndex] - ExpAvgDistribution[expIndex];
				}
				else if( (expIndex + longPeriod - shortPeriod) < ExpAvg.Length)
				{
					// Number of items in MovingAverages arrays is different and requires adjustment.
					outputValues[1][expIndex] = ExpAvg[expIndex + longPeriod - shortPeriod] - ExpAvgDistribution[expIndex];
				}
				else
				{
					outputValues[1][expIndex] = Double.NaN;
				}
				expIndex++;
			}
		}
 
		/// <summary>
		/// The Detrended Price Oscillator ("DPO") attempts to 
		/// eliminate the trend in prices. Detrended prices allow 
		/// you to more easily identify cycles and overbought/oversold 
		/// levels. To calculate the DPO, you specify a time period. 
		/// Cycles longer than this time period are removed from 
		/// prices, leaving the shorter-term cycles. 
		/// ---------------------------------------------------------
		/// Input: 
		///		- 1 Y value ( Close ).
		/// Output: 
		///		- 1 Y value Detrended Price Oscillator
		/// Parameters: 
		///		- Period
		/// </summary>
		/// <param name="inputValues">Arrays of doubles - Input values</param>
		/// <param name="outputValues">Arrays of doubles - Output values</param>
		/// <param name="parameterList">Array of strings - Parameters</param>
		private void DetrendedPriceOscillator(double [][] inputValues, out double [][] outputValues, string [] parameterList)
		{
			// There is no enough input series
			if( inputValues.Length != 2 )
			{
                throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
			}
			
			// Different number of x and y values
			CheckNumOfValues( inputValues, 1 );
			
			// Short Period for Exp moving average
			int period;
			try
			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
			catch( Exception e )
			{
                if (e.Message == SR.ExceptionObjectReferenceIsNull)
                    throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
				else
                    throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
			}
 
			if( period <= 0 )
                throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
 
			double [] outputAverage;
 
			// Moving Average
			MovingAverage( inputValues[1], out outputAverage, period, false );
			
			outputValues = new double [2][];
 
			outputValues[0] = new double [inputValues[0].Length - period*3/2];
			outputValues[1] = new double [inputValues[1].Length - period*3/2];
 
			// Detrended Price Oscillator
			for( int index = 0; index < outputValues[1].Length; index++ )
			{
				// Set X values
				outputValues[0][index] = inputValues[0][index + period + period/2];
 
				// Set Y values
				outputValues[1][index] = inputValues[1][index + period + period/2] - outputAverage[index];
			}
		}
 
		/// <summary>
		/// Chaikin's Volatility indicator compares the spread 
		/// between a security's high and low prices. 
		/// It quantifies volatility as a widening of the range 
		/// between the high and the low price. There are two ways 
		/// to interpret this measure of volatility. One method 
		/// assumes that market tops are generally accompanied by 
		/// increased volatility (as investors get nervous and 
		/// indecisive) and that the latter stages of a market 
		/// bottom are generally accompanied by decreased volatility 
		/// (as investors get bored). Another method (Mr. Chaikin's) 
		/// assumes that an increase in the Volatility indicator over 
		/// a relatively short time period indicates that a bottom is 
		/// near (e.g., a panic sell-off) and that a decrease in 
		/// volatility over a longer time period indicates an 
		/// approaching top (e.g., a mature bull market). As with 
		/// almost all experienced investors, Mr. Chaikin recommends 
		/// that you do not rely on any one indicator. He suggests 
		/// using a moving average ----ion or trading band system 
		/// to confirm this (or any) indicator.
		/// ---------------------------------------------------------
		/// Input: 
		///		- 2 Y values ( Hi, Low ).
		/// Output: 
		///		- 1 Y value Volatility Chaikins
		/// Parameters: 
		///		- Periods (default 10)- is used to specify the Shift days, By default this property is set to 10.
		///     - SignalPeriod (default 10)- is used to calculate Exponential Moving Avg of the Signal line, By default this property is set to 10.
		/// </summary>
		/// <param name="inputValues">Arrays of doubles - Input values</param>
		/// <param name="outputValues">Arrays of doubles - Output values</param>
		/// <param name="parameterList">Array of strings - Parameters</param>
		private void VolatilityChaikins(double [][] inputValues, out double [][] outputValues, string [] parameterList)
		{
			// There is no enough input series
			if( inputValues.Length != 3 )
			{
				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresTwoArrays);
			}
			
			// Different number of x and y values
			CheckNumOfValues( inputValues, 2 );
						
			// Period
			int period;
            if (parameterList.Length < 1 ||
                !int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out period))
            {
                period = 10;
            }
 
			if( period <= 0 )
                throw new InvalidOperationException(SR.ExceptionOscillatorNegativePeriodParameter);
 
			// Signal Period for Exp moving average
			int signalPeriod;
            if (parameterList.Length < 2 ||
                !int.TryParse(parameterList[1], NumberStyles.Any, CultureInfo.InvariantCulture, out signalPeriod))
            {
                signalPeriod = 10;
            }
 
			if( signalPeriod <= 0 )
                throw new InvalidOperationException(SR.ExceptionOscillatorNegativeSignalPeriod);
 
			double [] outputAverage;
 
			double [] hiLowInput = new double[inputValues[1].Length];
 
			// Find Hi - Low
			for( int index = 0; index < inputValues[1].Length; index++ )
			{
				hiLowInput[index] = inputValues[1][index] - inputValues[2][index];
			}
            
			// Exponential Moving Average
			ExponentialMovingAverage( hiLowInput, out outputAverage, signalPeriod, false );
			
			outputValues = new double [2][];
 
			outputValues[0] = new double [outputAverage.Length - period];
			outputValues[1] = new double [outputAverage.Length - period];
 
			// Volatility Chaikins
			for( int index = 0; index < outputValues[1].Length; index++ )
			{
				// Set X values
				outputValues[0][index] = inputValues[0][index + period + signalPeriod - 1];
 
				// Set Y values
				if( outputAverage[index] != 0.0 )
					outputValues[1][index] = ( outputAverage[index + period] - outputAverage[index] ) / outputAverage[index] * 100.0;
				else
					// Div with zero error.
					outputValues[1][index] = 0.0;
			}
		}
 
		/// <summary>
		/// The Volume Oscillator displays the difference between two 
		/// moving averages of a security's volume. The difference 
		/// between the moving averages can be expressed in either 
		/// points or percentages. You can use the difference between 
		/// two moving averages of volume to determine if the overall 
		/// volume trend is increasing or decreasing. When the Volume 
		/// Oscillator rises above zero, it signifies that the 
		/// shorter-term volume moving average has risen above 
		/// the longer-term volume moving average, and thus, that 
		/// the short-term volume trend is higher (i.e., more volume) 
		/// than the longer-term volume trend.
		/// ---------------------------------------------------------
		/// Input: 
		///		- 1 Y values ( Volume ).
		/// Output: 
		///		- 1 Y value VolumeOscillator
		/// Parameters: 
		///		- ShortPeriod (Default 5)= is used to configure the short period.
		///		- LongPeriod (Default 10)= is used to configure the Int64 period.
		///		- Percentage (Default true)= The Volume Oscillator can display the difference between the two moving averages as either points or percentages.
		/// </summary>
		/// <param name="inputValues">Arrays of doubles - Input values</param>
		/// <param name="outputValues">Arrays of doubles - Output values</param>
		/// <param name="parameterList">Array of strings - Parameters</param>
		private void VolumeOscillator(double [][] inputValues, out double [][] outputValues, string [] parameterList)
		{
			// There is no enough input series
			if( inputValues.Length != 2 )
			{
                throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
			}
			
			// Different number of x and y values
			CheckNumOfValues( inputValues, 1 );
						
			// Short Period
			int shortPeriod;
            if (parameterList.Length < 1 || 
                !int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out shortPeriod))
            {
                shortPeriod = 5;
            }
 
			// Int64 Period
			int longPeriod;
            if (parameterList.Length < 2 || 
                !int.TryParse(parameterList[1], NumberStyles.Any, CultureInfo.InvariantCulture, out longPeriod))
            {
                longPeriod = 10;
            }
 
			if( shortPeriod > longPeriod || longPeriod <= 0 || shortPeriod <= 0 )
                throw new ArgumentException(SR.ExceptionOscillatorObjectInvalidPeriod);
 
			// percentage
			bool percentage;
            if (parameterList.Length < 3 || 
                !bool.TryParse(parameterList[2], out percentage))
            {
                percentage = true;
            }
			
			double [] shortAverage;
			double [] longAverage;
 
			// Find Short moving average
			MovingAverage( inputValues[1], out shortAverage, shortPeriod, false );
 
			// Find Int64 moving average
			MovingAverage( inputValues[1], out longAverage, longPeriod, false );
 
			outputValues = new double [2][];
 
			outputValues[0] = new double [longAverage.Length];
			outputValues[1] = new double [longAverage.Length];
			
			// Volume Oscillator
			for( int index = 0; index < longAverage.Length; index++ )
			{
				// Set X values
				outputValues[0][index] = inputValues[0][index + longPeriod-1];
 
				// Set Y values
				outputValues[1][index] = shortAverage[index + shortPeriod] - longAverage[index];
 
				// RecalculateAxesScale difference in %
				if( percentage )
				{
					// Div by zero error.
					if( longAverage[index] == 0.0 )
						outputValues[1][index]=0.0;
					else
						outputValues[1][index] = outputValues[1][index] / shortAverage[index + shortPeriod] * 100;
				}
			}
		}
 
		/// <summary>
		/// The Stochastic Indicator is based on the observation that 
		/// as prices increase, closing prices tend to accumulate ever 
		/// closer to the highs for the period. Conversely, as prices 
		/// decrease, closing prices tend to accumulate ever closer to 
		/// the lows for the period. Trading decisions are made with 
		/// respect to divergence between % of "D" (one of the two 
		/// lines generated by the study) and the item's price. For 
		/// example, when a commodity or stock makes a high, reacts, 
		/// and subsequently moves to a higher high while corresponding 
		/// peaks on the % of "D" line make a high and then a lower 
		/// high, a bearish divergence is indicated. When a commodity 
		/// or stock has established a new low, reacts, and moves to a 
		/// lower low while the corresponding low points on the % of 
		/// "D" line make a low and then a higher low, a bullish 
		/// divergence is indicated. Traders act upon this divergence 
		/// when the other line generated by the study (K) crosses on 
		/// the right-hand side of the peak of the % of "D" line in the 
		/// case of a top, or on the right-hand side of the low point 
		/// of the % of "D" line in the case of a bottom. The Stochastic 
		/// Oscillator is displayed as two lines. The main line is 
		/// called "%K." The second line, called "%D," is a moving 
		/// average of %K. 
		/// ---------------------------------------------------------
		/// Input: 
		///		- 3 Y values ( Hi, Low, Close ).
		/// Output: 
		///		- 2 Y value ( %K, %D )
		/// Parameters: 
		///		- PeriodD (Default 10) = is used for %D calculation as SMA of %K.
		///     - PeriodK (Default 10) = is used to calculate %K.
		/// </summary>
		/// <param name="inputValues">Arrays of doubles - Input values</param>
		/// <param name="outputValues">Arrays of doubles - Output values</param>
		/// <param name="parameterList">Array of strings - Parameters</param>
		internal void StochasticIndicator(double [][] inputValues, out double [][] outputValues, string [] parameterList)
		{
			// There is no enough input series
			if( inputValues.Length != 4 )
			{
				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresThreeArrays);
			}
			
			// Different number of x and y values
			CheckNumOfValues( inputValues, 3 );
			
			// PeriodD for moving average
			int periodD;
            if (parameterList.Length < 2 || 
                !int.TryParse(parameterList[1], NumberStyles.Any, CultureInfo.InvariantCulture, out periodD))
            {
                periodD = 10;
            }
 
			if( periodD <= 0 )
                throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
 
			// PeriodK for moving average
			int periodK;
            if (parameterList.Length < 1 || 
                !int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out periodK))
            {
                periodK = 10;
            }
 
			if( periodK <= 0 )
                throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
 
			// Output arrays
			outputValues = new double [3][];
 
			// X
			outputValues[0] = new double [inputValues[0].Length - periodK - periodD + 2];
 
			// K%
			outputValues[1] = new double [inputValues[0].Length - periodK - periodD + 2];
 
			// D%
			outputValues[2] = new double [inputValues[0].Length - periodK - periodD + 2];
 
			double [] K = new double [inputValues[0].Length - periodK + 1];
 
			// Find K%
			for( int index = periodK - 1; index < inputValues[0].Length; index++ )
			{
				// Find Lowest Low and Highest High
				double minLow = double.MaxValue;
				double maxHi = double.MinValue;
				for( int indexHL = index - periodK + 1; indexHL <= index; indexHL++ )
				{
					if( minLow > inputValues[2][indexHL] )
						minLow = inputValues[2][indexHL];
 
					if( maxHi < inputValues[1][indexHL] )
						maxHi = inputValues[1][indexHL];
				}
				// Find K%
				K[index - periodK + 1] = ( inputValues[3][index] - minLow ) / ( maxHi - minLow ) * 100;
 
				// Set X and Y K output
				if( index >= periodK + periodD - 2 )
				{
					outputValues[0][index - periodK - periodD + 2] = inputValues[0][index];
					outputValues[1][index - periodK - periodD + 2] = K[index - periodK + 1];
				}
			}
 
			// Find D%
			MovingAverage( K, out outputValues[2], periodD, false );
		}
 
		/// <summary>
		/// Williams’ %R (pronounced "percent R") is a momentum 
		/// indicator that measures overbought/oversold levels. 
		/// Williams’ %R was developed by Larry Williams. The 
		/// interpretation of Williams' %R is very similar to that 
		/// of the Stochastic Oscillator except that %R is plotted 
		/// upside-down and the Stochastic Oscillator has internal 
		/// smoothing. To display the Williams’ %R indicator on an 
		/// upside-down scale, it is usually plotted using negative 
		/// values (e.g., -20%). Readings in the range of 80 to 100% 
		/// indicate that the security is oversold while readings in 
		/// the 0 to 20% range suggest that it is overbought.
		/// As with all overbought/oversold indicators, it is best to 
		/// wait for the security's price to change direction before 
		/// placing your trades. For example, if an overbought/oversold 
		/// indicator (such as the Stochastic Oscillator or Williams' 
		/// %R) is showing an overbought condition, it is wise to wait 
		/// for the security's price to turn down before selling the 
		/// security. (The MovingAverageConvergenceDivergence is a good indicator to monitor change 
		/// in a security's price.) It is not unusual for 
		/// overbought/oversold indicators to remain in an 
		/// overbought/oversold condition for a long time period as 
		/// the security's price continues to climb/fall. Selling 
		/// simply because the security appears overbought may take 
		/// you out of the security long before its price shows signs 
		/// of deterioration.
		/// ---------------------------------------------------------
		/// Input: 
		///		- 3 Y values ( Hi, Low, Close ).
		/// Output: 
		///		- 2 Y value ( %R )
		/// Parameters: 
		///		- Period (Default 14) = is used to configure the number of periods to calculate the WilliamsR
		/// </summary>
		/// <param name="inputValues">Arrays of doubles - Input values</param>
		/// <param name="outputValues">Arrays of doubles - Output values</param>
		/// <param name="parameterList">Array of strings - Parameters</param>
		internal void WilliamsR(double [][] inputValues, out double [][] outputValues, string [] parameterList)
		{
			// There is no enough input series
			if( inputValues.Length != 4 )
				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresThreeArrays);
			
			// Different number of x and y values
			CheckNumOfValues( inputValues, 3 );
			
			// PeriodD for moving average
			int period;
            if (parameterList.Length < 1 || 
                !int.TryParse(parameterList[0], NumberStyles.Any, CultureInfo.InvariantCulture, out period))
            {
                period = 14;
            }
 
			if( period <= 0 )
                throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
 
			// Output arrays
			outputValues = new double [2][];
 
			// X
			outputValues[0] = new double [inputValues[0].Length - period + 1];
 
			// R%
			outputValues[1] = new double [inputValues[0].Length - period + 1];
 
			// Find R%
			for( int index = period - 1; index < inputValues[0].Length; index++ )
			{
				// Find Lowest Low and Highest High
				double minLow = double.MaxValue;
				double maxHi = double.MinValue;
				for( int indexHL = index - period + 1; indexHL <= index; indexHL++ )
				{
					if( minLow > inputValues[2][indexHL] )
						minLow = inputValues[2][indexHL];
 
					if( maxHi < inputValues[1][indexHL] )
						maxHi = inputValues[1][indexHL];
				}
				// Set X value
				outputValues[0][index - period + 1] = inputValues[0][index];
 
				// Find R%
				outputValues[1][index - period + 1] = ( maxHi - inputValues[3][index] ) / ( maxHi - minLow ) * (-100.0);
			}
		}
 
		#endregion
 
		#region Methods
 
		/// <summary>
		/// Constructor
		/// </summary>
		public Oscillators()
		{
		}
 
 
        /// <summary>
        /// The first method in the module, which converts a formula 
        /// name to the corresponding private method.
        /// </summary>
        /// <param name="formulaName">String which represent a formula name</param>
        /// <param name="inputValues">Arrays of doubles - Input values</param>
        /// <param name="outputValues">Arrays of doubles - Output values</param>
        /// <param name="parameterList">Array of strings - Formula parameters</param>
        /// <param name="extraParameterList">Array of strings - Extra Formula parameters from DataManipulator object</param>
        /// <param name="outLabels">Array of strings - Used for Labels. Description for output results.</param>
		override public void Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels )
		{
			string name;
			outputValues = null;
 
			// Not used for these formulas.
			outLabels = null;
 
			name = formulaName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
			try
			{
				switch( name )
				{
					case "STOCHASTICINDICATOR":
						StochasticIndicator( inputValues, out outputValues, parameterList );
						break;
					case "CHAIKINOSCILLATOR":
						ChaikinOscillator( inputValues, out outputValues, parameterList, extraParameterList );
						break;
					case "DETRENDEDPRICEOSCILLATOR":
						DetrendedPriceOscillator( inputValues, out outputValues, parameterList );
						break;
					case "VOLATILITYCHAIKINS":
						VolatilityChaikins( inputValues, out outputValues, parameterList );
						break;
					case "VOLUMEOSCILLATOR":
						VolumeOscillator( inputValues, out outputValues, parameterList );
						break;
					case "WILLIAMSR":
						WilliamsR( inputValues, out outputValues, parameterList );
						break;
					default:
						outputValues = null;
						break;
				}
			}
			catch( IndexOutOfRangeException )
			{
				throw new InvalidOperationException( SR.ExceptionFormulaInvalidPeriod( name ) );
			}
			catch( OverflowException )
			{
				throw new InvalidOperationException( SR.ExceptionFormulaNotEnoughDataPoints( name ) );
			}
		}
 
		#endregion
		
	}
}