536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
528 lines
19 KiB
C#
528 lines
19 KiB
C#
//-------------------------------------------------------------
|
||
// <copyright company=’Microsoft Corporation’>
|
||
// Copyright © Microsoft Corporation. All Rights Reserved.
|
||
// </copyright>
|
||
//-------------------------------------------------------------
|
||
// @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
|
||
{
|
||
/// <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
|
||
}
|
||
}
|