1 //-------------------------------------------------------------
2 // <copyright company=�Microsoft Corporation�>
3 //   Copyright � Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:		PriceIndicators.cs
9 //
10 //  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting.Formulas
11 //
12 //	Classes:	PriceIndicators
13 //
14 //  Purpose:	This class is used to calculate Price
15 //				indicators used in Technical Analyses.
16 //
17 //	Reviewed:	GS - August 7, 2002
18 //				AG - August 7, 2002
19 //
20 //===================================================================
21 
22 
23 using System;
24 
25 
26 #if Microsoft_CONTROL
27     namespace System.Windows.Forms.DataVisualization.Charting.Formulas
28 #else
29 	namespace System.Web.UI.DataVisualization.Charting.Formulas
30 #endif
31 {
32 	/// <summary>
33 	/// Price indicator is module with mathematical calculations
34 	/// that apply to a security's price.
35 	/// </summary>
36 	internal class PriceIndicators : IFormula
37 	{
38 		#region Error strings
39 
40 		// Error strings
41 		//internal string inputArrayStart = "Formula requires";
42 		//internal string inputArrayEnd = "arrays";
43 		//internal string SR.ExceptionPriceIndicatorsSameYNumber = "Formula requires the same number of Y values for each input data point";
44 		//internal string SR.ExceptionPriceIndicatorsFormulaRequiresFourArrays = "Formula requires the same number of X and Y values for each input data point";
45 		//internal string periodMissing = "Formula error - Period parameter is missing. ";
46 		//internal string SR.ExceptionPriceIndicatorsFormulaRequiresFourArrays = "Formula error - There are not enough data points for the Period. ";
47 
48 		#endregion
49 
50 		#region Properties
51 
52 		/// <summary>
53 		/// Formula Module name
54 		/// </summary>
55         virtual public string Name { get { return SR.FormulaNamePriceIndicators; } }
56 
57 		#endregion
58 
59 		#region Formulas
60 
61 		/// <summary>
62 		/// A Moving Average is an indicator that shows the average
63 		/// value of a security's price over a period of time. When
64 		/// calculating a moving average, a mathematical analysis of
65 		/// the security's average value over a predetermined time
66 		/// period is made. As the security's price changes,
67 		/// its average price moves up or down.
68 		/// A simple, or arithmetic, moving average is calculated by
69 		/// adding the closing price of the security for a number of
70 		/// time periods (e.g., 12 days) and then dividing this total
71 		/// by the number of time periods. The result is the average
72 		/// price of the security over the time period. Simple moving
73 		/// averages give equal weight to each daily price.
74 		/// ---------------------------------------------------------
75 		/// Input:
76 		///		- Y values.
77 		/// Output:
78 		///		- Moving Average.
79 		/// Parameters:
80 		///		- Period
81 		///	Extra Parameters:
82 		///		- Start from First
83 		///
84 		/// </summary>
85 		/// <param name="inputValues">Array of doubles: Y values</param>
86 		/// <param name="outputValues">Arrays of doubles: Moving average</param>
87 		/// <param name="period">Period</param>
88 		/// <param name="FromFirst">Start from first value</param>
MovingAverage(double [] inputValues, out double [] outputValues, int period, bool FromFirst )89 		internal void MovingAverage(double [] inputValues, out double [] outputValues, int period, bool FromFirst )
90 		{
91 			double [][] tempInput = new double [2][];
92 			double [][] tempOutput = new double [2][];
93 			string [] parList = new string [1];
94 			string [] extList = new string [1];
95 
96 			parList[0] = period.ToString(System.Globalization.CultureInfo.InvariantCulture);
97 			extList[0] = FromFirst.ToString(System.Globalization.CultureInfo.InvariantCulture);
98 
99 			tempInput[0] = new double[inputValues.Length];
100 			tempInput[1] = inputValues;
101 
102 			MovingAverage( tempInput, out tempOutput, parList, extList );
103 
104 			outputValues = tempOutput[1];
105 		}
106 
107 		/// <summary>
108 		/// A Moving Average is an indicator that shows the average
109 		/// value of a security's price over a period of time. When
110 		/// calculating a moving average, a mathematical analysis of
111 		/// the security's average value over a predetermined time
112 		/// period is made. As the security's price changes,
113 		/// its average price moves up or down.
114 		/// A simple, or arithmetic, moving average is calculated by
115 		/// adding the closing price of the security for a number of
116 		/// time periods (e.g., 12 days) and then dividing this total
117 		/// by the number of time periods. The result is the average
118 		/// price of the security over the time period. Simple moving
119 		/// averages give equal weight to each daily price.
120 		/// ---------------------------------------------------------
121 		/// Input:
122 		///		- Y values.
123 		/// Output:
124 		///		- Moving Average.
125 		/// Parameters:
126 		///		- Period
127 		///	Extra Parameters:
128 		///		- Start from First
129 		///
130 		/// </summary>
131 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values</param>
132 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Moving average</param>
133 		/// <param name="parameterList">Array of strings: 1. Period</param>
134 		/// <param name="extraParameterList">Array of strings: 1. Start from zero</param>
MovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)135 		private void MovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
136 		{
137 			int length = inputValues.Length;
138 
139 			// Period for moving average
140 			int period;
141 			try
142 			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
143 			catch( Exception e )
144 			{
145                 if (e.Message == SR.ExceptionObjectReferenceIsNull)
146                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
147 				else
148                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
149 			}
150 
151 			if( period <= 0 )
152                 throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
153 
154 			// Starting average from the first data point or after period.
155 			bool startFromFirst = bool.Parse( extraParameterList[0]);
156 
157 			// There is no enough series
158 			if( length != 2 )
159                 throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
160 
161 			// Different number of x and y values
162 			if( inputValues[0].Length != inputValues[1].Length )
163                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
164 
165 			// Not enough values for moving average.
166 			if( inputValues[0].Length < period )
167                 throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints);
168 
169 			outputValues = new double [2][];
170 
171 			if( startFromFirst )
172 			{
173 				// X values
174 				outputValues[0] = new double [inputValues[0].Length];
175 
176 				// Y values
177 				outputValues[1] = new double [inputValues[1].Length];
178 
179 				for( int point = 0; point < inputValues[0].Length; point++ )
180 				{
181 					// Set X value
182 					outputValues[0][point] = inputValues[0][point];
183 
184 					// Find sum of Y values
185 					double sum = 0;
186 					int startSum = 0;
187 
188 					// Find the begining of the period
189 					if( point - period + 1 > 0 )
190 					{
191 						startSum = point - period + 1;
192 					}
193 
194 					// Find sum fro real period.
195 					for( int pointSum = startSum; pointSum <= point; pointSum++ )
196 					{
197 						sum += inputValues[1][pointSum];
198 					}
199 
200 					// Find real period if start from first data point.
201 					int realPeriod = period;
202 					if( period > point + 1 )
203 					{
204 						realPeriod = point + 1;
205 					}
206 
207 					outputValues[1][point] = sum / realPeriod;
208 				}
209 			}
210 			else
211 			{
212 				// X values
213 				outputValues[0] = new double [inputValues[0].Length - period + 1];
214 
215 				// Y values
216 				outputValues[1] = new double [inputValues[1].Length - period + 1];
217 
218 				// Find sum of Y values for the period
219 				double sum = 0;
220 				for( int pointSum = 0; pointSum < period; pointSum++ )
221 				{
222 					sum += inputValues[1][pointSum];
223 				}
224 
225 				for( int point = 0; point < outputValues[0].Length; point++ )
226 				{
227 					// Set X value
228 					outputValues[0][point] = inputValues[0][point + period - 1];
229 
230 					outputValues[1][point] = sum / period;
231 
232 					// Change Sum
233 					if( point < outputValues[0].Length - 1 )
234 					{
235 						sum -= inputValues[1][point];
236 						sum += inputValues[1][point + period];
237 					}
238 				}
239 			}
240 		}
241 
242 		/// <summary>
243 		/// An exponential (or exponentially weighted) moving average
244 		/// is calculated by applying a percentage of today�s closing
245 		/// price to yesterday�s moving average value. Exponential
246 		/// moving averages place more weight on recent prices.	For
247 		/// example, to calculate a 9% exponential moving average
248 		/// of IBM, you would first take today�s closing price and
249 		/// multiply it by 9%. Next, you would add this product to
250 		/// the value of yesterday�s moving average multiplied by
251 		/// 91% (100% - 9% = 91%).
252 		/// ---------------------------------------------------------
253 		/// Input:
254 		///		- Y values.
255 		/// Output:
256 		///		- Exponential Moving Average.
257 		/// Parameters:
258 		///		- Period
259 		///	Extra Parameters:
260 		///		- Start from First
261 		///
262 		/// </summary>
263 		/// <param name="inputValues">Array of doubles: Y values</param>
264 		/// <param name="outputValues">Arrays of doubles: Exponential Moving average</param>
265 		/// <param name="period">Period</param>
266 		/// <param name="startFromFirst">Start from first value</param>
ExponentialMovingAverage(double []inputValues, out double []outputValues, int period, bool startFromFirst)267 		internal void ExponentialMovingAverage(double []inputValues, out double []outputValues, int period, bool startFromFirst)
268 		{
269 			double [][] tempInput = new double [2][];
270 			double [][] tempOutput = new double [2][];
271 			string [] parList = new string [1];
272 			string [] extList = new string [1];
273 
274 			parList[0] = period.ToString(System.Globalization.CultureInfo.InvariantCulture);
275 			extList[0] = startFromFirst.ToString(System.Globalization.CultureInfo.InvariantCulture);
276 
277 			tempInput[0] = new double[inputValues.Length];
278 			tempInput[1] = inputValues;
279 
280 			ExponentialMovingAverage( tempInput, out tempOutput, parList, extList );
281 
282 			outputValues = tempOutput[1];
283 		}
284 
285 		/// <summary>
286 		/// An exponential (or exponentially weighted) moving average
287 		/// is calculated by applying a percentage of today�s closing
288 		/// price to yesterday�s moving average value. Exponential
289 		/// moving averages place more weight on recent prices.	For
290 		/// example, to calculate a 9% exponential moving average
291 		/// of IBM, you would first take today�s closing price and
292 		/// multiply it by 9%. Next, you would add this product to
293 		/// the value of yesterday�s moving average multiplied by
294 		/// 91% (100% - 9% = 91%).
295 		/// ---------------------------------------------------------
296 		/// Input:
297 		///		- Y values.
298 		/// Output:
299 		///		- Exponential Moving Average.
300 		/// Parameters:
301 		///		- Period
302 		///	Extra Parameters:
303 		///		- Start from First
304 		///
305 		/// </summary>
306 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values</param>
307 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Moving average</param>
308 		/// <param name="parameterList">Array of strings: 1. Period</param>
309 		/// <param name="extraParameterList">Array of strings: 1. Start from zero</param>
ExponentialMovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)310 		private void ExponentialMovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
311 		{
312 			int length = inputValues.Length;
313 
314 			// Period for moving average
315 			int period;
316 			try
317 			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
318 			catch( Exception e )
319 			{
320                 if (e.Message == SR.ExceptionObjectReferenceIsNull)
321                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
322 				else
323                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
324 			}
325 
326 			if( period <= 0 )
327                 throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
328 
329 			// Formula for converting period to percentage
330 			double exponentialPercentage = 2.0 / ( period + 1.0 );
331 
332 			// Starting average from the first data point or after period.
333 			bool startFromFirst = bool.Parse( extraParameterList[0] );
334 
335 			// There is no enough series
336 			if( length != 2 )
337                 throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
338 
339 			// Different number of x and y values
340 			if( inputValues[0].Length != inputValues[1].Length )
341                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
342 
343 			// Not enough values for moving average.
344 			if( inputValues[0].Length < period )
345                 throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints);
346 
347 			outputValues = new double [2][];
348 
349 			if( startFromFirst )
350 			{
351 				// X values
352 				outputValues[0] = new double [inputValues[0].Length];
353 
354 				// Y values
355 				outputValues[1] = new double [inputValues[1].Length];
356 
357 				for( int point = 0; point < inputValues[0].Length; point++ )
358 				{
359 					// Set X value
360 					outputValues[0][point] = inputValues[0][point];
361 
362 					// Find sum of Y values
363 					double sum = 0;
364 					int startSum = 0;
365 
366 					if( point - period + 1 > 0 )
367 					{
368 						startSum = point - period + 1;
369 					}
370 
371 					for( int pointSum = startSum; pointSum < point; pointSum++ )
372 					{
373 						sum += inputValues[1][pointSum];
374 					}
375 
376 					int realPeriod = period;
377 					if( period > point + 1 )
378 						realPeriod = point + 1;
379 
380 					double movingAvr;
381 
382 					// Find real period if start from first data point.
383 					if( realPeriod <= 1 )
384 						movingAvr = 0;
385 					else
386 						movingAvr = sum / ( realPeriod - 1 );
387 
388 					// Formula for converting period to percentage
389 					exponentialPercentage = 2.0 / ( realPeriod + 1.0 );
390 
391 					// Exponential influence
392 					outputValues[1][point] = movingAvr * (1 - exponentialPercentage ) + inputValues[1][point] * exponentialPercentage;
393 
394 				}
395 			}
396 			else
397 			{
398 				// X values
399 				outputValues[0] = new double [inputValues[0].Length - period + 1];
400 
401 				// Y values
402 				outputValues[1] = new double [inputValues[1].Length - period + 1];
403 
404 				for( int point = 0; point < outputValues[0].Length; point++ )
405 				{
406 					// Set X value
407 					outputValues[0][point] = inputValues[0][point + period - 1];
408 
409 					double movingAvr;
410 					// if point is less than period calulate simple moving average
411 					if(  point == 0  )
412 					{
413 						// Find sum of Y values
414 						double sum = 0;
415 						for( int pointSum = point; pointSum < point + period; pointSum++ )
416 						{
417 							sum += inputValues[1][pointSum];
418 						}
419 
420 						movingAvr = sum / ( period );
421 					}
422 						// else use previos day exponential moving average
423 					else
424 						movingAvr = outputValues[1][point-1];
425 
426 					// Exponential influence
427 					outputValues[1][point] = movingAvr * (1 - exponentialPercentage ) + inputValues[1][point + period - 1] * exponentialPercentage;
428 
429 				}
430 			}
431 		}
432 
433 		/// <summary>
434 		/// Triangular moving averages place the majority of the weight
435 		/// on the middle portion of the price series. They are actually
436 		/// double-smoothed simple moving averages. The periods used
437 		/// in the simple moving averages varies depending on if you
438 		/// specify an odd or even number of time periods.
439 		/// ---------------------------------------------------------
440 		/// Input:
441 		///		- Y values.
442 		/// Output:
443 		///		- Moving Average.
444 		/// Parameters:
445 		///		- Period
446 		///	Extra Parameters:
447 		///		- Start from First
448 		///
449 		/// </summary>
450 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values</param>
451 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Moving average</param>
452 		/// <param name="parameterList">Array of strings: 1. Period</param>
453 		/// <param name="extraParameterList">Array of strings: 1. Start from zero</param>
TriangularMovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)454 		private void TriangularMovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
455 		{
456 			int length = inputValues.Length;
457 
458 			// Period for moving average
459 			int period;
460 			try
461 			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
462 			catch( Exception e )
463 			{
464                 if (e.Message == SR.ExceptionObjectReferenceIsNull)
465                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
466 				else
467                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
468 			}
469 
470 			if( period <= 0 )
471                 throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
472 
473 			// Starting average from the first data point or after period.
474 			bool startFromFirst = bool.Parse( extraParameterList[0] );
475 
476 			// There is no enough series
477 			if( length != 2 )
478                 throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
479 
480 			// Different number of x and y values
481 			if( inputValues[0].Length != inputValues[1].Length )
482                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
483 
484 			// Not enough values for moving average.
485 			if( inputValues[0].Length < period )
486                 throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints);
487 
488 			outputValues = new double [2][];
489 
490 			// Find triangular period
491 			double tempPeriod = ((double)period + 1.0) / 2.0;
492 			tempPeriod = Math.Round(tempPeriod);
493 			double [] tempOut;
494 			double [] tempIn = inputValues[1];
495 
496 			// Call moving averages first time
497 			MovingAverage( tempIn, out tempOut, (int)tempPeriod, startFromFirst );
498 			// Call moving averages second time (Moving average of moving average)
499 			MovingAverage( tempOut, out tempOut, (int)tempPeriod, startFromFirst );
500 
501 			outputValues[1] = tempOut;
502 
503 			// X values
504 			outputValues[0] = new double [outputValues[1].Length];
505 
506 			// Set X values
507 			if( startFromFirst )
508 				outputValues[0] = inputValues[0];
509 			else
510 			{
511 				for( int index = 0; index < outputValues[1].Length; index++ )
512 					outputValues[0][index] = inputValues[0][((int)(tempPeriod)-1) * 2 + index];
513 			}
514 		}
515 
516 		/// <summary>
517 		/// A weighted moving average is designed to put more weight on
518 		/// recent data and less weight on past data. A weighted moving
519 		/// average is calculated by multiplying each of the previous
520 		/// day�s data by a weight. The following table shows the calculation
521 		/// of a 5-day weighted moving average.
522 		/// ---------------------------------------------------------
523 		/// Input:
524 		///		- Y values.
525 		/// Output:
526 		///		- Moving Average.
527 		/// Parameters:
528 		///		- Period
529 		///	Extra Parameters:
530 		///		- Start from First
531 		///
532 		/// </summary>
533 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values</param>
534 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Moving average</param>
535 		/// <param name="parameterList">Array of strings: 1. Period</param>
536 		/// <param name="extraParameterList">Array of strings: 1. Start from zero</param>
WeightedMovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)537 		private void WeightedMovingAverage(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
538 		{
539 			int length = inputValues.Length;
540 
541 			// Period for moving average
542 			int period;
543 			try
544 			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
545 			catch( Exception e )
546 			{
547                 if (e.Message == SR.ExceptionObjectReferenceIsNull)
548                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
549 				else
550                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
551 			}
552 
553 			if( period <= 0 )
554                 throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
555 
556 			// Starting average from the first data point or after period.
557 			bool startFromFirst = bool.Parse( extraParameterList[0] );
558 
559 			// There is no enough series
560 			if( length != 2 )
561                 throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
562 
563 			// Different number of x and y values
564 			if( inputValues[0].Length != inputValues[1].Length )
565                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
566 
567 			// Not enough values for moving average.
568 			if( inputValues[0].Length < period )
569                 throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints);
570 
571 			outputValues = new double [2][];
572 
573 			if( startFromFirst )
574 			{
575 				// X values
576 				outputValues[0] = new double [inputValues[0].Length];
577 
578 				// Y values
579 				outputValues[1] = new double [inputValues[1].Length];
580 
581 				for( int point = 0; point < inputValues[0].Length; point++ )
582 				{
583 					// Set X value
584 					outputValues[0][point] = inputValues[0][point];
585 
586 					// Find sum of Y values
587 					double sum = 0;
588 					int startSum = 0;
589 
590 					if( point - period + 1 > 0 )
591 					{
592 						startSum = point - period + 1;
593 					}
594 
595 					int index = 1;
596 					int indexSum = 0;
597 					for( int pointSum = startSum; pointSum <= point; pointSum++ )
598 					{
599 						sum += inputValues[1][pointSum] * index;
600 						indexSum += index;
601 						index++;
602 					}
603 
604 					double movingAvr;
605 
606 					// Avoid division by zero.
607 					if( point == 0 )
608 						movingAvr = inputValues[1][0];
609 					else
610 						movingAvr = sum / indexSum;
611 
612 					// Weighted average
613 					outputValues[1][point] = movingAvr;
614 
615 				}
616 			}
617 			else
618 			{
619 				// X values
620 				outputValues[0] = new double [inputValues[0].Length - period + 1];
621 
622 				// Y values
623 				outputValues[1] = new double [inputValues[1].Length - period + 1];
624 
625 				for( int point = 0; point < outputValues[0].Length; point++ )
626 				{
627 					// Set X value
628 					outputValues[0][point] = inputValues[0][point + period - 1];
629 
630 					// Find sum of Y values
631 					double sum = 0;
632 
633 					int index = 1;
634 					int indexSum = 0;
635 					for( int pointSum = point; pointSum < point + period; pointSum++ )
636 					{
637 						sum += inputValues[1][pointSum] * index;
638 						indexSum += index;
639 						index++;
640 					}
641 
642 					double movingAvr = sum / indexSum;
643 
644 					// Weighted average
645 					outputValues[1][point] = movingAvr;
646 
647 				}
648 			}
649 		}
650 
651 		/// <summary>
652 		/// Bollinger Bands plot trading bands above and below
653 		/// a simple moving average. The standard deviation of
654 		/// closing prices for a period equal to the moving
655 		/// average employed is used to determine the band width.
656 		/// This causes the bands to tighten in quiet markets and
657 		/// loosen in volatile markets. The bands can be used to
658 		/// determine overbought and oversold levels, locate
659 		/// reversal areas, project targets for market moves, and
660 		/// determine appropriate stop levels. The bands are used
661 		/// in conjunction with indicators such as RSI, MovingAverageConvergenceDivergence
662 		/// histogram, CCI and Rate of Change. Divergences between
663 		/// Bollinger bands and other indicators show potential
664 		/// action points.
665 		/// ---------------------------------------------------------
666 		/// Input:
667 		///		- 1 Y value.
668 		/// Output:
669 		///		- 2 Y values (Bollinger Band Hi and Low).
670 		/// Parameters:
671 		///		- period
672 		///	Extra Parameters:
673 		///		- startFromFirst
674 		///
675 		/// </summary>
676 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values</param>
677 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Bollinger Band Up, 3. row - Bollinger Band Down</param>
678 		/// <param name="parameterList">Array of strings: 1. Period</param>
679 		/// <param name="extraParameterList">Array of strings: 1. Start from zero</param>
BollingerBands(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)680 		private void BollingerBands(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
681 		{
682 			int length = inputValues.Length;
683 
684 			// Period for moving average
685 			int period;
686 			try
687 			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
688 			catch( Exception e )
689 			{
690                 if (e.Message == SR.ExceptionObjectReferenceIsNull)
691                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
692 				else
693                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
694 			}
695 
696 			if( period <= 0 )
697                 throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
698 
699 			// Standard deviation
700 			double deviation;
701 			try
702 			{deviation = double.Parse( parameterList[1], System.Globalization.CultureInfo.InvariantCulture );}
703 			catch(System.Exception)
704             { throw new InvalidOperationException(SR.ExceptionIndicatorsDeviationMissing); }
705 
706 			// Starting average from the first data point or after period.
707 			bool startFromFirst = bool.Parse( extraParameterList[0] );
708 
709 			// There is no enough series
710 			if( length != 2 )
711                 throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
712 
713 			// Different number of x and y values
714 			if( inputValues[0].Length != inputValues[1].Length )
715                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
716 
717 			// Not enough values for moving average.
718 			if( inputValues[0].Length < period )
719                 throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints);
720 
721 			outputValues = new double [3][];
722 
723 			if( startFromFirst )
724 			{
725 				// X values
726 				outputValues[0] = new double [inputValues[0].Length];
727 
728 				// Y values
729 				outputValues[1] = new double [inputValues[1].Length];
730 				outputValues[2] = new double [inputValues[1].Length];
731 
732 				// average
733 				double [] average = new double [inputValues[1].Length];
734 
735 				MovingAverage( inputValues[1], out average, period, true );
736 
737 				for( int point = 0; point < outputValues[0].Length; point++ )
738 				{
739 					// Set X value
740 					outputValues[0][point] = inputValues[0][point];
741 
742 					// Find sum of Y values
743 					double sum = 0;
744 					int startSum = 0;
745 
746 					// Find the begining of the period
747 					if( point - period + 1 > 0 )
748 					{
749 						startSum = point - period + 1;
750 					}
751 
752 					for( int pointSum = startSum; pointSum <= point; pointSum++ )
753 					{
754 						sum += ((inputValues[1][pointSum] - average[point])*(inputValues[1][pointSum] - average[point]));
755 					}
756 
757 					outputValues[1][point] = average[point] + Math.Sqrt(sum / period) * deviation;
758 					outputValues[2][point] = average[point] - Math.Sqrt(sum / period) * deviation;
759 				}
760 			}
761 			else
762 			{
763 				// X values
764 				outputValues[0] = new double [inputValues[0].Length - period + 1];
765 
766 				// Y values
767 				outputValues[1] = new double [inputValues[1].Length - period + 1];
768 				outputValues[2] = new double [inputValues[1].Length - period + 1];
769 
770 				// average
771 				double [] average = new double [inputValues[1].Length - period + 1];
772 
773 				MovingAverage( inputValues[1], out average, period, false );
774 
775 				for( int point = 0; point < outputValues[0].Length; point++ )
776 				{
777 					// Set X value
778 					outputValues[0][point] = inputValues[0][point + period - 1];
779 
780 					// Find sum of Y values
781 					double sum = 0;
782 
783 					for( int pointSum = point; pointSum < point + period; pointSum++ )
784 					{
785 						sum += ((inputValues[1][pointSum] - average[point])*(inputValues[1][pointSum] - average[point]));
786 					}
787 
788 					outputValues[1][point] = average[point] + Math.Sqrt(sum / period) * deviation;
789 					outputValues[2][point] = average[point] - Math.Sqrt(sum / period) * deviation;
790 				}
791 			}
792 		}
793 
794 		/// <summary>
795 		/// The Typical Price indicator is simply an average of each
796 		/// day's price. The Median Price and Weighted Close are
797 		/// similar indicators. The Typical Price indicator provides
798 		/// a simple, single-line plot of the day's average price.
799 		/// Some investors use the Typical Price rather than the
800 		/// closing price when creating moving average ----ion
801 		/// systems.
802 		/// ---------------------------------------------------------
803 		/// Input:
804 		///		- 3 Y values ( Close, High, Low ).
805 		/// Output:
806 		///		- 1 Y value Weighted Close Indicator.
807 		/// </summary>
808 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values (Close), 3. row - Y values (High), 4. row - Y values (Low)</param>
809 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Weighted Close</param>
TypicalPrice(double [][] inputValues, out double [][] outputValues)810 		private void TypicalPrice(double [][] inputValues, out double [][] outputValues)
811 		{
812 			int length = inputValues.Length;
813 
814 			// There is no enough series
815 			if( length != 4 )
816 				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresThreeArrays);
817 
818 			// Different number of x and y values
819 			CheckNumOfValues( inputValues, 3 );
820 
821 			outputValues = new double [2][];
822 
823 			outputValues[0] = new double [inputValues[0].Length];
824 			outputValues[1] = new double [inputValues[1].Length];
825 
826 			for( int index = 0; index < inputValues[1].Length; index++ )
827 			{
828 				// Set X values
829 				outputValues[0][index] = inputValues[0][index];
830 
831 				// Set median price
832 				outputValues[1][index] = (inputValues[1][index] + inputValues[2][index] + inputValues[3][index])/3.0;
833 			}
834 
835 		}
836 
837 		/// <summary>
838 		/// The Median Price indicator is simply the midpoint of each
839 		/// day's price. The Typical Price and Weighted Close are
840 		/// similar indicators. The Median Price indicator provides
841 		/// a simple, single-line chart of the day's "average price."
842 		/// This average price is useful when you want a simpler
843 		/// scaleView of prices.
844 		/// ---------------------------------------------------------
845 		/// Input:
846 		///		- 2 Y values ( High, Low ).
847 		/// Output:
848 		///		- 1 Y value Median Price Indicator.
849 		/// </summary>
850 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values (High), 3. row - Y values (Low)</param>
851 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Median Price</param>
MedianPrice(double [][] inputValues, out double [][] outputValues)852 		private void MedianPrice(double [][] inputValues, out double [][] outputValues)
853 		{
854 			int length = inputValues.Length;
855 
856 			// There is no enough series
857 			if( length != 3 )
858 				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresTwoArrays);
859 
860 			// Different number of x and y values
861 			CheckNumOfValues( inputValues, 2 );
862 
863 			outputValues = new double [2][];
864 
865 			outputValues[0] = new double [inputValues[0].Length];
866 			outputValues[1] = new double [inputValues[1].Length];
867 
868 			for( int index = 0; index < inputValues[1].Length; index++ )
869 			{
870 				// Set X values
871 				outputValues[0][index] = inputValues[0][index];
872 
873 				// Set median price
874 				outputValues[1][index] = (inputValues[1][index] + inputValues[2][index])/2.0;
875 			}
876 
877 		}
878 
879 		/// <summary>
880 		/// The Weighted Close indicator is simply an average of each day's
881 		/// price. It gets its name from the fact that extra weight is
882 		/// given to the closing price. The Median Price and Typical Price
883 		/// are similar indicators. When plotting and back-testing moving
884 		/// averages, indicators, trendlines, etc, some investors like
885 		/// the simplicity that a line chart offers. However, line
886 		/// charts that only show the closing price can be misleading
887 		/// since they ignore the high and low price. A Weighted Close
888 		/// chart combines the simplicity of the line chart with the
889 		/// scope of a bar chart, by plotting a single point for each
890 		/// day that includes the high, low, and closing price.
891 		/// ---------------------------------------------------------
892 		/// Input:
893 		///		- 3 Y values ( Close, High, Low ).
894 		/// Output:
895 		///		- 1 Y value Weighted Close Indicator.
896 		/// </summary>
897 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values (Close), 3. row - Y values (High), 4. row - Y values (Low)</param>
898 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Weighted Close</param>
WeightedClose(double [][] inputValues, out double [][] outputValues)899 		private void WeightedClose(double [][] inputValues, out double [][] outputValues)
900 		{
901 			int length = inputValues.Length;
902 
903 			// There is no enough series
904 			if( length != 4 )
905 				throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresThreeArrays);
906 
907 			// Different number of x and y values
908 			CheckNumOfValues( inputValues, 3 );
909 
910 			outputValues = new double [2][];
911 
912 			outputValues[0] = new double [inputValues[0].Length];
913 			outputValues[1] = new double [inputValues[1].Length];
914 
915 			for( int index = 0; index < inputValues[1].Length; index++ )
916 			{
917 				// Set X values
918 				outputValues[0][index] = inputValues[0][index];
919 
920 				// Set median price
921 				outputValues[1][index] = (inputValues[1][index] + inputValues[2][index] + inputValues[3][index] * 2)/4.0;
922 			}
923 
924 		}
925 
926 		/// <summary>
927 		/// An envelope is comprised of two moving averages. One moving
928 		/// average is shifted upward and the second moving average
929 		/// is shifted downward. Envelopes define the upper and lower
930 		/// boundaries of a security's normal trading range. A sell
931 		/// signal is generated when the security reaches the upper
932 		/// band whereas a buy signal is generated at the lower band.
933 		/// The optimum percentage shift depends on the volatility of
934 		/// the security--the more volatile, the larger the percentage.
935 		/// The logic behind envelopes is that overzealous buyers and
936 		/// sellers push the price to the extremes (i.e., the upper
937 		/// and lower bands), at which point the prices often stabilize
938 		/// by moving to more realistic levels. This is similar to the
939 		/// interpretation of Bollinger Bands.
940 		/// ---------------------------------------------------------
941 		/// Input:
942 		///		- 1 Y value.
943 		/// Output:
944 		///		- 2 Y values (Envelope Hi and Low).
945 		/// Parameters:
946 		///		- period
947 		///		- shift in percentages
948 		///	Extra Parameters:
949 		///		- startFromFirst
950 		///
951 		/// </summary>
952 		/// <param name="inputValues">Arrays of doubles: 1. row - X values, 2. row - Y values</param>
953 		/// <param name="outputValues">Arrays of doubles: 1. row - X values, 2. row - Envelopes Up, 3. row - Envelopes Down</param>
954 		/// <param name="parameterList">Array of strings: parameters</param>
955 		/// <param name="extraParameterList">Array of strings: Extra parameters </param>
Envelopes(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)956 		private void Envelopes(double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList)
957 		{
958 			int length = inputValues.Length;
959 
960 			// Period for moving average
961 			int period;
962 			try
963 			{period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
964 			catch( Exception e )
965 			{
966                 if (e.Message == SR.ExceptionObjectReferenceIsNull)
967                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
968 				else
969                     throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
970 			}
971 
972 			if( period <= 0 )
973                 throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
974 
975 			// Shift
976 			double shift;
977 			try
978 			{shift = double.Parse( parameterList[1], System.Globalization.CultureInfo.InvariantCulture );}
979 			catch(System.Exception)
980             { throw new InvalidOperationException(SR.ExceptionPriceIndicatorsShiftParameterMissing); }
981 
982 			// There is no enough series
983 			if( length != 2 )
984                 throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresOneArray);
985 
986 			// Different number of x and y values
987 			if( inputValues[0].Length != inputValues[1].Length )
988                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
989 
990 			double [][] movingAverage;
991 
992 			MovingAverage( inputValues, out movingAverage, parameterList, extraParameterList );
993 
994 			outputValues = new double[3][];
995 			outputValues[0] = new double[movingAverage[0].Length];
996 			outputValues[1] = new double[movingAverage[0].Length];
997 			outputValues[2] = new double[movingAverage[0].Length];
998 
999 			for( int index = 0; index < movingAverage[0].Length; index++ )
1000 			{
1001 				outputValues[0][index] = movingAverage[0][index];
1002 				outputValues[1][index] = movingAverage[1][index] + shift*movingAverage[1][index]/100.0;
1003 				outputValues[2][index] = movingAverage[1][index] - shift*movingAverage[1][index]/100.0;
1004 
1005 			}
1006 		}
1007 
1008 		/// <summary>
1009 		/// Standard Deviation is a statistical measure of volatility.
1010 		/// Standard Deviation is typically used as a component of
1011 		/// other indicators, rather than as a stand-alone indicator.
1012 		/// For example, Bollinger Bands are calculated by adding
1013 		/// a security's Standard Deviation to a moving average.
1014 		/// High Standard Deviation values occur when the data item
1015 		/// being analyzed (e.g., prices or an indicator) is changing
1016 		/// dramatically. Similarly, low Standard Deviation values
1017 		/// occur when prices are stable.
1018 		/// </summary>
1019 		/// <param name="inputValues">Input Y values</param>
1020 		/// <param name="outputValues">Output standard deviation</param>
1021 		/// <param name="period">Period</param>
1022 		/// <param name="startFromFirst">Start calculation from the first Y value</param>
StandardDeviation(double [] inputValues, out double [] outputValues, int period, bool startFromFirst )1023 		internal void StandardDeviation(double [] inputValues, out double [] outputValues, int period, bool startFromFirst )
1024 		{
1025 			double [] movingOut;
1026 
1027 			// Start calculation from the first Y value
1028 			if( startFromFirst )
1029 			{
1030 				outputValues = new double[inputValues.Length];
1031 				double sum;
1032 				MovingAverage( inputValues, out movingOut, period, startFromFirst );
1033 				int outIndex = 0;
1034 				for( int index = 0; index < inputValues.Length; index++ )
1035 				{
1036 					sum = 0;
1037 					int startSum = 0;
1038 
1039 					// Find the begining of the period
1040 					if( index - period + 1 > 0 )
1041 					{
1042 						startSum = index - period + 1;
1043 					}
1044 					for( int indexDev = startSum; indexDev <= index; indexDev++ )
1045 					{
1046 						sum += (inputValues[indexDev] - movingOut[outIndex])*(inputValues[indexDev] - movingOut[outIndex]);
1047 					}
1048 					outputValues[outIndex] = Math.Sqrt( sum / period );
1049 					outIndex++;
1050 				}
1051 			}
1052 				// Do not start calculation from the first Y value
1053 			else
1054 			{
1055 				outputValues = new double[inputValues.Length - period + 1];
1056 				double sum;
1057 				MovingAverage( inputValues, out movingOut, period, startFromFirst );
1058 				int outIndex = 0;
1059 				for( int index = period - 1; index < inputValues.Length; index++ )
1060 				{
1061 					sum = 0;
1062 					for( int indexDev = index - period + 1; indexDev <= index; indexDev++ )
1063 					{
1064 						sum += (inputValues[indexDev] - movingOut[outIndex])*(inputValues[indexDev] - movingOut[outIndex]);
1065 					}
1066 					outputValues[outIndex] = Math.Sqrt( sum / period );
1067 					outIndex++;
1068 				}
1069 			}
1070 		}
1071 
1072 		#endregion
1073 
1074 		#region Methods
1075 
1076 		/// <summary>
1077 		/// Default constructor
1078 		/// </summary>
PriceIndicators()1079 		public PriceIndicators()
1080 		{
1081 		}
1082 
1083 		/// <summary>
1084 		/// This methods checks the number of X and Y values and
1085 		/// fire exception if the numbers are different.
1086 		/// </summary>
1087 		/// <param name="inputValues">Input X and Y values</param>
1088 		/// <param name="numOfYValues">The number of Y values</param>
CheckNumOfValues( double [][] inputValues, int numOfYValues )1089 		public void CheckNumOfValues( double [][] inputValues, int numOfYValues )
1090 		{
1091 			// Different number of x and y values
1092 			if( inputValues[0].Length != inputValues[1].Length )
1093 			{
1094                 throw new ArgumentException(SR.ExceptionPriceIndicatorsSameXYNumber);
1095 			}
1096 
1097 			// Different number of y values
1098 			for( int index = 1; index < numOfYValues; index++ )
1099 			{
1100 				if( inputValues[index].Length != inputValues[index+1].Length )
1101 				{
1102 					throw new ArgumentException( SR.ExceptionPriceIndicatorsSameYNumber );
1103 				}
1104 			}
1105 		}
1106 
1107         /// <summary>
1108         /// The first method in the module, which converts a formula
1109         /// name to the corresponding private method.
1110         /// </summary>
1111         /// <param name="formulaName">String which represent a formula name</param>
1112         /// <param name="inputValues">Arrays of doubles - Input values</param>
1113         /// <param name="outputValues">Arrays of doubles - Output values</param>
1114         /// <param name="parameterList">Array of strings - Formula parameters</param>
1115         /// <param name="extraParameterList">Array of strings - Extra Formula parameters from DataManipulator object</param>
1116         /// <param name="outLabels">Array of strings - Used for Labels. Description for output results.</param>
Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels )1117 		virtual public void Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels )
1118 		{
1119 			string name;
1120 
1121 			name = formulaName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
1122 
1123 			// Not used for these formulas.
1124 			outLabels = null;
1125 
1126 			try
1127 			{
1128 				switch( name )
1129 				{
1130 					case "MOVINGAVERAGE":
1131 						MovingAverage( inputValues, out outputValues, parameterList, extraParameterList );
1132 						break;
1133 					case "EXPONENTIALMOVINGAVERAGE":
1134 						ExponentialMovingAverage( inputValues, out outputValues, parameterList, extraParameterList );
1135 						break;
1136 					case "TRIANGULARMOVINGAVERAGE":
1137 						TriangularMovingAverage( inputValues, out outputValues, parameterList, extraParameterList );
1138 						break;
1139 					case "WEIGHTEDMOVINGAVERAGE":
1140 						WeightedMovingAverage( inputValues, out outputValues, parameterList, extraParameterList );
1141 						break;
1142 					case "BOLLINGERBANDS":
1143 						BollingerBands( inputValues, out outputValues, parameterList, extraParameterList );
1144 						break;
1145 					case "MEDIANPRICE":
1146 						MedianPrice( inputValues, out outputValues );
1147 						break;
1148 					case "TYPICALPRICE":
1149 						TypicalPrice( inputValues, out outputValues );
1150 						break;
1151 					case "WEIGHTEDCLOSE":
1152 						WeightedClose( inputValues, out outputValues );
1153 						break;
1154 					case "ENVELOPES":
1155 						Envelopes( inputValues, out outputValues, parameterList, extraParameterList );
1156 						break;
1157 					default:
1158 						outputValues = null;
1159 						break;
1160 				}
1161 			}
1162 			catch( IndexOutOfRangeException )
1163 			{
1164                 throw new InvalidOperationException(SR.ExceptionFormulaInvalidPeriod( name ) );
1165 			}
1166 			catch( OverflowException )
1167 			{
1168 				throw new InvalidOperationException( SR.ExceptionFormulaNotEnoughDataPoints( name ) );
1169 			}
1170 		}
1171 
1172 		#endregion
1173 	}
1174 }
1175