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