123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- //
- // Purpose: This class is used for calculations of
- // technical analyses volume indicators.
- //
- using System;
- namespace FastReport.DataVisualization.Charting.Formulas
- {
- /// <summary>
- /// This class is used for calculations of
- /// technical analyses volume indicators.
- /// </summary>
- internal class VolumeIndicators : PriceIndicators
- {
- #region Properties
- /// <summary>
- /// Formula Module name
- /// </summary>
- override public string Name { get { return SR.FormulaNameVolumeIndicators; } }
- #endregion
- #region Methods
- /// <summary>
- /// Default Constructor
- /// </summary>
- public VolumeIndicators()
- {
-
- }
- /// <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;
- 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
-
- /// <summary>
- /// 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
- /// </summary>
- /// <param name="inputValues">Arrays of doubles</param>
- /// <param name="outputValues">Arrays of doubles</param>
- /// <param name="parameterList">Array of strings</param>
- 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) );
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="inputValues">Arrays of doubles - Input values</param>
- /// <param name="outputValues">Arrays of doubles - Output values</param>
- 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];
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="inputValues">Arrays of doubles - Input values</param>
- /// <param name="outputValues">Arrays of doubles - Output values</param>
- 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];
- }
- }
- /// <summary>
- /// 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
- /// </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 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];
-
- }
- }
- /// <summary>
- /// 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
- /// </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 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];
- }
- }
- /// <summary>
- /// 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
- /// </summary>
- /// <param name="inputValues">Arrays of doubles - Input values</param>
- /// <param name="outputValues">Arrays of doubles - Output values</param>
- 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
- }
- }
|