PriceIndicators.cs 40 KB

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