//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @owner=alexgor, deliant //================================================================= // File: VolumeIndicator.cs // // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting.Formulas // // Classes: VolumeIndicators // // Purpose: This class is used for calculations of // technical analyses volume indicators. // // Reviewed: GS - August 7, 2002 // AG - August 7, 2002 // //=================================================================== using System; #if Microsoft_CONTROL namespace System.Windows.Forms.DataVisualization.Charting.Formulas #else namespace System.Web.UI.DataVisualization.Charting.Formulas #endif { /// /// This class is used for calculations of /// technical analyses volume indicators. /// internal class VolumeIndicators : PriceIndicators { #region Properties /// /// Formula Module name /// override public string Name { get { return SR.FormulaNameVolumeIndicators; } } #endregion #region Methods /// /// Default Constructor /// public VolumeIndicators() { } /// /// The first method in the module, which converts a formula /// name to the corresponding private method. /// /// String which represent a formula name /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Array of strings - Formula parameters /// Array of strings - Extra Formula parameters from DataManipulator object /// Array of strings - Used for Labels. Description for output results. override public void Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels ) { string name; name = formulaName.ToUpper(System.Globalization.CultureInfo.InvariantCulture); // Not used for these formulas. outLabels = null; try { switch( name ) { case "MONEYFLOW": MoneyFlow( inputValues, out outputValues, parameterList ); break; case "ONBALANCEVOLUME": OnBalanceVolume( inputValues, out outputValues ); break; case "NEGATIVEVOLUMEINDEX": NegativeVolumeIndex( inputValues, out outputValues, parameterList ); break; case "POSITIVEVOLUMEINDEX": PositiveVolumeIndex( inputValues, out outputValues, parameterList ); break; case "PRICEVOLUMETREND": PriceVolumeTrend( inputValues, out outputValues ); break; case "ACCUMULATIONDISTRIBUTION": AccumulationDistribution( inputValues, out outputValues ); break; default: outputValues = null; break; } } catch( IndexOutOfRangeException ) { throw new InvalidOperationException( SR.ExceptionFormulaInvalidPeriod( name ) ); } catch( OverflowException ) { throw new InvalidOperationException( SR.ExceptionFormulaNotEnoughDataPoints( name ) ); } } #endregion #region Formulas /// /// The Money Flow Index ("MFI") is a momentum indicator that /// measures the strength of money flowing in and out of /// a security. It is related to the Relative Strength Index, /// but where the RSI only incorporates prices, the Money Flow /// Index accounts for volume. /// --------------------------------------------------------- /// Input: /// - 4 Y values ( High, Low, Close, Volume ). /// Output: /// - 1 Y value Money Flow Indicator. /// Parameters: /// - Period /// /// Arrays of doubles /// Arrays of doubles /// Array of strings private void MoneyFlow(double [][] inputValues, out double [][] outputValues, string [] parameterList) { int length = inputValues.Length; // There is no enough series if( length != 5 ) throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresFourArrays); // Different number of x and y values CheckNumOfValues( inputValues, 4 ); // Period for 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); // Not enough values for Money Flow. if( inputValues[0].Length < period ) throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints); outputValues = new double [2][]; outputValues[0] = new double [inputValues[0].Length - period + 1]; outputValues[1] = new double [inputValues[0].Length - period + 1]; double [] TypicalPrice = new double [inputValues[1].Length]; double [] MoneyFlow = new double [inputValues[1].Length]; double [] PositiveMoneyFlow = new double [inputValues[1].Length]; double [] NegativeMoneyFlow = new double [inputValues[1].Length]; // Find Money Flow for( int index = 0; index < inputValues[1].Length; index++ ) { // Find Typical Price TypicalPrice[index] = (inputValues[1][index] + inputValues[2][index] + inputValues[3][index])/3.0; // Find Money Flow MoneyFlow[index] = (inputValues[1][index] + inputValues[2][index] + inputValues[3][index])/3.0 * inputValues[4][index]; } // Find Money Flow for( int index = 1; index < inputValues[1].Length; index++ ) { // Positive Typical Price if( TypicalPrice[index] > TypicalPrice[index - 1] ) { PositiveMoneyFlow[index] = MoneyFlow[index]; NegativeMoneyFlow[index] = 0; } // Negative Typical Price if( TypicalPrice[index] < TypicalPrice[index - 1] ) { NegativeMoneyFlow[index] = MoneyFlow[index]; PositiveMoneyFlow[index] = 0; } } double PosMoney = 0; double NegMoney = 0; for( int index = period - 1; index < inputValues[1].Length; index++ ) { PosMoney = 0; NegMoney = 0; // Find Money flow using period for( int periodIndex = index - period + 1; periodIndex <= index; periodIndex++ ) { NegMoney += NegativeMoneyFlow[periodIndex]; PosMoney += PositiveMoneyFlow[periodIndex]; } // X value outputValues[0][index - period + 1] = inputValues[0][index]; // Money Flow Index outputValues[1][index - period + 1] = 100.0 - 100.0 / ( 1.0 + (PosMoney / NegMoney) ); } } /// /// The Price and Volume Trend ("PVT") is similar to /// On Balance Volume ("OBV,") in that it is a cumulative /// total of volume that is adjusted depending on changes /// in closing prices. But where OBV adds all volume on days /// when prices close higher and subtracts all volume on days /// when prices close lower, the PVT adds/subtracts only /// a portion of the daily volume. The amount of volume /// added to the PVT is determined by the amount that prices /// rose or fell relative to the previous day’s close. /// The PVT is calculated by multiplying the day’s volume /// by the percent that the security’s price changed, and /// adding this value to a cumulative total. /// --------------------------------------------------------- /// Input: /// - 2 Y values ( Close, Volume ). /// Output: /// - 1 Y value Price Volume Trend Indicator. /// /// Arrays of doubles - Input values /// Arrays of doubles - Output values private void PriceVolumeTrend(double [][] inputValues, out double [][] outputValues) { // 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 ); outputValues = new double [2][]; outputValues[0] = new double [inputValues[0].Length]; outputValues[1] = new double [inputValues[0].Length]; // Set X and Y zero values outputValues[0][0] = inputValues[0][0]; outputValues[1][0] = 0; double yesterdayClose; double todayClose; // Price Volume Trend Indicator for( int index = 1; index < inputValues[1].Length; index++ ) { // Set X values outputValues[0][index] = inputValues[0][index]; // Set Y values yesterdayClose = inputValues[1][index-1]; todayClose = inputValues[1][index]; // Price Volume Trend for one point outputValues[1][index] = ( todayClose - yesterdayClose ) / yesterdayClose * inputValues[2][index] + outputValues[1][index-1]; } } /// /// On Balance Volume ("OBV") is a momentum indicator that /// relates volume to price change. OBV is one of the most /// popular volume indicators and was developed by /// Joseph Granville. Constructing an OBV line is very /// simple: The total volume for each day is assigned a /// positive or negative value depending on whether prices /// closed higher or lower that day. A higher close results /// in the volume for that day to get a positive value, while /// a lower close results in negative value. A running total /// is kept by adding or subtracting each day's volume based /// on the direction of the close. The direction of the OBV /// line is the thing to watch, not the actual volume numbers. /// --------------------------------------------------------- /// Input: /// - 2 Y values ( Close, Volume ). /// Output: /// - 1 Y value On Balance Volume Indicator. /// /// Arrays of doubles - Input values /// Arrays of doubles - Output values private void OnBalanceVolume(double [][] inputValues, out double [][] outputValues) { // 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 ); outputValues = new double [2][]; outputValues[0] = new double [inputValues[0].Length]; outputValues[1] = new double [inputValues[0].Length]; outputValues[0][0] = inputValues[0][0]; outputValues[1][0] = inputValues[2][0]; // Find On Balance Volume for( int index = 1; index < inputValues[1].Length; index++ ) { // Set X values outputValues[0][index] = inputValues[0][index]; // Set Y Values // If today’s close is greater than yesterday’s close then if( inputValues[1][index - 1] < inputValues[1][index] ) outputValues[1][index] = outputValues[1][index - 1] + inputValues[2][index]; // If today’s close is less than yesterday’s close then else if( inputValues[1][index - 1] > inputValues[1][index] ) outputValues[1][index] = outputValues[1][index - 1] - inputValues[2][index]; // If today’s close is equal to yesterday’s close then else outputValues[1][index] = outputValues[1][index - 1]; } } /// /// The Negative Volume Index ("NVI") focuses on days where /// the volume decreases from the previous day. The premise /// being that the "smart money" takes positions on days when /// volume decreases. /// --------------------------------------------------------- /// Input: /// - 2 Y values ( Close, Volume ). /// Output: /// - 1 Y value Negative Volume index. /// Parameters: /// - StartValue : double /// /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Array of strings - Parameters private void NegativeVolumeIndex(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 ); // Start Value double startValue; try {startValue = double.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );} catch(System.Exception) { throw new InvalidOperationException(SR.ExceptionVolumeIndicatorStartValueMissing); } outputValues = new double [2][]; outputValues[0] = new double [inputValues[0].Length]; outputValues[1] = new double [inputValues[0].Length]; outputValues[0][0] = inputValues[0][0]; outputValues[1][0] = startValue; // Find Negative Volume Index for( int index = 1; index < inputValues[1].Length; index++ ) { // Set X values outputValues[0][index] = inputValues[0][index]; // If today’s volume is less than yesterday’s volume then if( inputValues[2][index] < inputValues[2][index-1] ) { double yesterdayClose = inputValues[1][index-1]; double todayClose = inputValues[1][index]; outputValues[1][index] = ( todayClose - yesterdayClose ) / yesterdayClose * outputValues[1][index-1] + outputValues[1][index-1]; } // If today’s volume is greater than or equal to yesterday’s volume then: else outputValues[1][index] = outputValues[1][index-1]; } } /// /// The Positive Volume Index ("PVI") focuses on days where /// the volume increased from the previous day. The premise /// being that the "crowd" takes positions on days when /// volume increases. Interpretation of the PVI assumes that /// on days when volume increases, the crowd-following /// "uninformed" investors are in the market. Conversely, on /// days with decreased volume, the "smart money" is quietly /// taking positions. Thus, the PVI displays what the /// not-so-smart-money is doing. (The Negative Volume Index, /// displays what the smart money is doing.) Note, however, /// that the PVI is not a contrarian indicator. Even though /// the PVI is supposed to show what the not-so-smart-money /// is doing, it still trends in the same direction as prices. /// --------------------------------------------------------- /// Input: /// - 2 Y values ( Close, Volume ). /// Output: /// - 1 Y value On Positive Volume index. /// Parameters: /// - StartValue : double /// /// Arrays of doubles - Input values /// Arrays of doubles - Output values /// Array of strings - Parameters private void PositiveVolumeIndex(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 ); // Start Value double startValue; try {startValue = double.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );} catch(System.Exception) { throw new InvalidOperationException(SR.ExceptionVolumeIndicatorStartValueMissing); } outputValues = new double [2][]; outputValues[0] = new double [inputValues[0].Length]; outputValues[1] = new double [inputValues[0].Length]; outputValues[0][0] = inputValues[0][0]; outputValues[1][0] = startValue; // Find Negative Volume Index for( int index = 1; index < inputValues[1].Length; index++ ) { // Set X values outputValues[0][index] = inputValues[0][index]; // If today’s volume is greater than yesterday’s volume then if( inputValues[2][index] > inputValues[2][index-1] ) { double yesterdayClose = inputValues[1][index-1]; double todayClose = inputValues[1][index]; outputValues[1][index] = ( todayClose - yesterdayClose ) / yesterdayClose * outputValues[1][index-1] + outputValues[1][index-1]; } // If today’s volume is less than or equal to yesterday’s volume then: else outputValues[1][index] = outputValues[1][index-1]; } } /// /// The Accumulation/Distribution is a momentum indicator that /// associates changes in price and volume. The indicator is /// based on the premise that the more volume that accompanies /// a price move, the more significant the price move. A portion /// of each day’s volume is added or subtracted from /// a cumulative total. The nearer the closing price is to /// the high for the day, the more volume added to /// the cumulative total. The nearer the closing price is to /// the low for the day, the more volume subtracted from the /// cumulative total. If the close is exactly between the high /// and low prices, nothing is added to the cumulative total. /// --------------------------------------------------------- /// Input: /// - 4 Y values ( Hi, Low, Close, Volume ). /// Output: /// - 1 Y value Accumulation Distribution /// /// Arrays of doubles - Input values /// Arrays of doubles - Output values internal void AccumulationDistribution(double [][] inputValues, out double [][] outputValues) { // 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 ); outputValues = new double [2][]; outputValues[0] = new double [inputValues[0].Length]; outputValues[1] = new double [inputValues[0].Length]; double [] distribution = new double [inputValues[0].Length]; // Set X and Y zero values outputValues[0][0] = inputValues[0][0]; outputValues[1][0] = 0; // Accumulation Distribution for( int index = 0; index < inputValues[1].Length; index++ ) { // Set X values outputValues[0][index] = inputValues[0][index]; // Distribution {(Close - Low) - (High - Close)} / (High - Low) * Volume distribution[index] = ((inputValues[3][index] - inputValues[2][index])-(inputValues[1][index] - inputValues[3][index]))/(inputValues[1][index] - inputValues[2][index])*inputValues[4][index]; } // The Accumulation Distribution Index is calculated as a cumulative total of each day's reading double sum = 0; for( int index = 0; index < inputValues[1].Length; index++ ) { sum += distribution[index]; outputValues[1][index] = sum; } } #endregion } }