VolumeIndicator.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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 for calculations of
  6. // technical analyses volume indicators.
  7. //
  8. using System;
  9. namespace FastReport.DataVisualization.Charting.Formulas
  10. {
  11. /// <summary>
  12. /// This class is used for calculations of
  13. /// technical analyses volume indicators.
  14. /// </summary>
  15. internal class VolumeIndicators : PriceIndicators
  16. {
  17. #region Properties
  18. /// <summary>
  19. /// Formula Module name
  20. /// </summary>
  21. override public string Name { get { return SR.FormulaNameVolumeIndicators; } }
  22. #endregion
  23. #region Methods
  24. /// <summary>
  25. /// Default Constructor
  26. /// </summary>
  27. public VolumeIndicators()
  28. {
  29. }
  30. /// <summary>
  31. /// The first method in the module, which converts a formula
  32. /// name to the corresponding private method.
  33. /// </summary>
  34. /// <param name="formulaName">String which represent a formula name</param>
  35. /// <param name="inputValues">Arrays of doubles - Input values</param>
  36. /// <param name="outputValues">Arrays of doubles - Output values</param>
  37. /// <param name="parameterList">Array of strings - Formula parameters</param>
  38. /// <param name="extraParameterList">Array of strings - Extra Formula parameters from DataManipulator object</param>
  39. /// <param name="outLabels">Array of strings - Used for Labels. Description for output results.</param>
  40. override public void Formula( string formulaName, double [][] inputValues, out double [][] outputValues, string [] parameterList, string [] extraParameterList, out string [][] outLabels )
  41. {
  42. string name;
  43. name = formulaName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
  44. // Not used for these formulas.
  45. outLabels = null;
  46. try
  47. {
  48. switch( name )
  49. {
  50. case "MONEYFLOW":
  51. MoneyFlow( inputValues, out outputValues, parameterList );
  52. break;
  53. case "ONBALANCEVOLUME":
  54. OnBalanceVolume( inputValues, out outputValues );
  55. break;
  56. case "NEGATIVEVOLUMEINDEX":
  57. NegativeVolumeIndex( inputValues, out outputValues, parameterList );
  58. break;
  59. case "POSITIVEVOLUMEINDEX":
  60. PositiveVolumeIndex( inputValues, out outputValues, parameterList );
  61. break;
  62. case "PRICEVOLUMETREND":
  63. PriceVolumeTrend( inputValues, out outputValues );
  64. break;
  65. case "ACCUMULATIONDISTRIBUTION":
  66. AccumulationDistribution( inputValues, out outputValues );
  67. break;
  68. default:
  69. outputValues = null;
  70. break;
  71. }
  72. }
  73. catch( IndexOutOfRangeException )
  74. {
  75. throw new InvalidOperationException( SR.ExceptionFormulaInvalidPeriod( name ) );
  76. }
  77. catch( OverflowException )
  78. {
  79. throw new InvalidOperationException( SR.ExceptionFormulaNotEnoughDataPoints( name ) );
  80. }
  81. }
  82. #endregion
  83. #region Formulas
  84. /// <summary>
  85. /// The Money Flow Index ("MFI") is a momentum indicator that
  86. /// measures the strength of money flowing in and out of
  87. /// a security. It is related to the Relative Strength Index,
  88. /// but where the RSI only incorporates prices, the Money Flow
  89. /// Index accounts for volume.
  90. /// ---------------------------------------------------------
  91. /// Input:
  92. /// - 4 Y values ( High, Low, Close, Volume ).
  93. /// Output:
  94. /// - 1 Y value Money Flow Indicator.
  95. /// Parameters:
  96. /// - Period
  97. /// </summary>
  98. /// <param name="inputValues">Arrays of doubles</param>
  99. /// <param name="outputValues">Arrays of doubles</param>
  100. /// <param name="parameterList">Array of strings</param>
  101. private void MoneyFlow(double [][] inputValues, out double [][] outputValues, string [] parameterList)
  102. {
  103. int length = inputValues.Length;
  104. // There is no enough series
  105. if( length != 5 )
  106. throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresFourArrays);
  107. // Different number of x and y values
  108. CheckNumOfValues( inputValues, 4 );
  109. // Period for moving average
  110. int period;
  111. try
  112. {period = int.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
  113. catch( Exception e )
  114. {
  115. if (e.Message == SR.ExceptionObjectReferenceIsNull)
  116. throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing);
  117. else
  118. throw new InvalidOperationException(SR.ExceptionPriceIndicatorsPeriodMissing + e.Message);
  119. }
  120. if( period <= 0 )
  121. throw new InvalidOperationException(SR.ExceptionPeriodParameterIsNegative);
  122. // Not enough values for Money Flow.
  123. if( inputValues[0].Length < period )
  124. throw new ArgumentException(SR.ExceptionPriceIndicatorsNotEnoughPoints);
  125. outputValues = new double [2][];
  126. outputValues[0] = new double [inputValues[0].Length - period + 1];
  127. outputValues[1] = new double [inputValues[0].Length - period + 1];
  128. double [] TypicalPrice = new double [inputValues[1].Length];
  129. double [] MoneyFlow = new double [inputValues[1].Length];
  130. double [] PositiveMoneyFlow = new double [inputValues[1].Length];
  131. double [] NegativeMoneyFlow = new double [inputValues[1].Length];
  132. // Find Money Flow
  133. for( int index = 0; index < inputValues[1].Length; index++ )
  134. {
  135. // Find Typical Price
  136. TypicalPrice[index] = (inputValues[1][index] + inputValues[2][index] + inputValues[3][index])/3.0;
  137. // Find Money Flow
  138. MoneyFlow[index] = (inputValues[1][index] + inputValues[2][index] + inputValues[3][index])/3.0 * inputValues[4][index];
  139. }
  140. // Find Money Flow
  141. for( int index = 1; index < inputValues[1].Length; index++ )
  142. {
  143. // Positive Typical Price
  144. if( TypicalPrice[index] > TypicalPrice[index - 1] )
  145. {
  146. PositiveMoneyFlow[index] = MoneyFlow[index];
  147. NegativeMoneyFlow[index] = 0;
  148. }
  149. // Negative Typical Price
  150. if( TypicalPrice[index] < TypicalPrice[index - 1] )
  151. {
  152. NegativeMoneyFlow[index] = MoneyFlow[index];
  153. PositiveMoneyFlow[index] = 0;
  154. }
  155. }
  156. double PosMoney = 0;
  157. double NegMoney = 0;
  158. for( int index = period - 1; index < inputValues[1].Length; index++ )
  159. {
  160. PosMoney = 0;
  161. NegMoney = 0;
  162. // Find Money flow using period
  163. for( int periodIndex = index - period + 1; periodIndex <= index; periodIndex++ )
  164. {
  165. NegMoney += NegativeMoneyFlow[periodIndex];
  166. PosMoney += PositiveMoneyFlow[periodIndex];
  167. }
  168. // X value
  169. outputValues[0][index - period + 1] = inputValues[0][index];
  170. // Money Flow Index
  171. outputValues[1][index - period + 1] = 100.0 - 100.0 / ( 1.0 + (PosMoney / NegMoney) );
  172. }
  173. }
  174. /// <summary>
  175. /// The Price and Volume Trend ("PVT") is similar to
  176. /// On Balance Volume ("OBV,") in that it is a cumulative
  177. /// total of volume that is adjusted depending on changes
  178. /// in closing prices. But where OBV adds all volume on days
  179. /// when prices close higher and subtracts all volume on days
  180. /// when prices close lower, the PVT adds/subtracts only
  181. /// a portion of the daily volume. The amount of volume
  182. /// added to the PVT is determined by the amount that prices
  183. /// rose or fell relative to the previous day’s close.
  184. /// The PVT is calculated by multiplying the day’s volume
  185. /// by the percent that the security’s price changed, and
  186. /// adding this value to a cumulative total.
  187. /// ---------------------------------------------------------
  188. /// Input:
  189. /// - 2 Y values ( Close, Volume ).
  190. /// Output:
  191. /// - 1 Y value Price Volume Trend Indicator.
  192. /// </summary>
  193. /// <param name="inputValues">Arrays of doubles - Input values</param>
  194. /// <param name="outputValues">Arrays of doubles - Output values</param>
  195. private void PriceVolumeTrend(double [][] inputValues, out double [][] outputValues)
  196. {
  197. // There is no enough input series
  198. if( inputValues.Length != 3 )
  199. throw new ArgumentException(SR.ExceptionPriceIndicatorsFormulaRequiresTwoArrays);
  200. // Different number of x and y values
  201. CheckNumOfValues( inputValues, 2 );
  202. outputValues = new double [2][];
  203. outputValues[0] = new double [inputValues[0].Length];
  204. outputValues[1] = new double [inputValues[0].Length];
  205. // Set X and Y zero values
  206. outputValues[0][0] = inputValues[0][0];
  207. outputValues[1][0] = 0;
  208. double yesterdayClose;
  209. double todayClose;
  210. // Price Volume Trend Indicator
  211. for( int index = 1; index < inputValues[1].Length; index++ )
  212. {
  213. // Set X values
  214. outputValues[0][index] = inputValues[0][index];
  215. // Set Y values
  216. yesterdayClose = inputValues[1][index-1];
  217. todayClose = inputValues[1][index];
  218. // Price Volume Trend for one point
  219. outputValues[1][index] = ( todayClose - yesterdayClose ) / yesterdayClose * inputValues[2][index] + outputValues[1][index-1];
  220. }
  221. }
  222. /// <summary>
  223. /// On Balance Volume ("OBV") is a momentum indicator that
  224. /// relates volume to price change. OBV is one of the most
  225. /// popular volume indicators and was developed by
  226. /// Joseph Granville. Constructing an OBV line is very
  227. /// simple: The total volume for each day is assigned a
  228. /// positive or negative value depending on whether prices
  229. /// closed higher or lower that day. A higher close results
  230. /// in the volume for that day to get a positive value, while
  231. /// a lower close results in negative value. A running total
  232. /// is kept by adding or subtracting each day's volume based
  233. /// on the direction of the close. The direction of the OBV
  234. /// line is the thing to watch, not the actual volume numbers.
  235. /// ---------------------------------------------------------
  236. /// Input:
  237. /// - 2 Y values ( Close, Volume ).
  238. /// Output:
  239. /// - 1 Y value On Balance Volume Indicator.
  240. /// </summary>
  241. /// <param name="inputValues">Arrays of doubles - Input values</param>
  242. /// <param name="outputValues">Arrays of doubles - Output values</param>
  243. private void OnBalanceVolume(double [][] inputValues, out double [][] outputValues)
  244. {
  245. // There is no enough input series
  246. if( inputValues.Length != 3 )
  247. throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresTwoArrays);
  248. // Different number of x and y values
  249. CheckNumOfValues( inputValues, 2 );
  250. outputValues = new double [2][];
  251. outputValues[0] = new double [inputValues[0].Length];
  252. outputValues[1] = new double [inputValues[0].Length];
  253. outputValues[0][0] = inputValues[0][0];
  254. outputValues[1][0] = inputValues[2][0];
  255. // Find On Balance Volume
  256. for( int index = 1; index < inputValues[1].Length; index++ )
  257. {
  258. // Set X values
  259. outputValues[0][index] = inputValues[0][index];
  260. // Set Y Values
  261. // If today’s close is greater than yesterday’s close then
  262. if( inputValues[1][index - 1] < inputValues[1][index] )
  263. outputValues[1][index] = outputValues[1][index - 1] + inputValues[2][index];
  264. // If today’s close is less than yesterday’s close then
  265. else if( inputValues[1][index - 1] > inputValues[1][index] )
  266. outputValues[1][index] = outputValues[1][index - 1] - inputValues[2][index];
  267. // If today’s close is equal to yesterday’s close then
  268. else
  269. outputValues[1][index] = outputValues[1][index - 1];
  270. }
  271. }
  272. /// <summary>
  273. /// The Negative Volume Index ("NVI") focuses on days where
  274. /// the volume decreases from the previous day. The premise
  275. /// being that the "smart money" takes positions on days when
  276. /// volume decreases.
  277. /// ---------------------------------------------------------
  278. /// Input:
  279. /// - 2 Y values ( Close, Volume ).
  280. /// Output:
  281. /// - 1 Y value Negative Volume index.
  282. /// Parameters:
  283. /// - StartValue : double
  284. /// </summary>
  285. /// <param name="inputValues">Arrays of doubles - Input values</param>
  286. /// <param name="outputValues">Arrays of doubles - Output values</param>
  287. /// <param name="parameterList">Array of strings - Parameters</param>
  288. private void NegativeVolumeIndex(double [][] inputValues, out double [][] outputValues, string [] parameterList)
  289. {
  290. // There is no enough input series
  291. if( inputValues.Length != 3 )
  292. throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresTwoArrays);
  293. // Different number of x and y values
  294. CheckNumOfValues( inputValues, 2 );
  295. // Start Value
  296. double startValue;
  297. try
  298. {startValue = double.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
  299. catch(System.Exception)
  300. { throw new InvalidOperationException(SR.ExceptionVolumeIndicatorStartValueMissing); }
  301. outputValues = new double [2][];
  302. outputValues[0] = new double [inputValues[0].Length];
  303. outputValues[1] = new double [inputValues[0].Length];
  304. outputValues[0][0] = inputValues[0][0];
  305. outputValues[1][0] = startValue;
  306. // Find Negative Volume Index
  307. for( int index = 1; index < inputValues[1].Length; index++ )
  308. {
  309. // Set X values
  310. outputValues[0][index] = inputValues[0][index];
  311. // If today’s volume is less than yesterday’s volume then
  312. if( inputValues[2][index] < inputValues[2][index-1] )
  313. {
  314. double yesterdayClose = inputValues[1][index-1];
  315. double todayClose = inputValues[1][index];
  316. outputValues[1][index] = ( todayClose - yesterdayClose ) / yesterdayClose * outputValues[1][index-1] + outputValues[1][index-1];
  317. }
  318. // If today’s volume is greater than or equal to yesterday’s volume then:
  319. else
  320. outputValues[1][index] = outputValues[1][index-1];
  321. }
  322. }
  323. /// <summary>
  324. /// The Positive Volume Index ("PVI") focuses on days where
  325. /// the volume increased from the previous day. The premise
  326. /// being that the "crowd" takes positions on days when
  327. /// volume increases. Interpretation of the PVI assumes that
  328. /// on days when volume increases, the crowd-following
  329. /// "uninformed" investors are in the market. Conversely, on
  330. /// days with decreased volume, the "smart money" is quietly
  331. /// taking positions. Thus, the PVI displays what the
  332. /// not-so-smart-money is doing. (The Negative Volume Index,
  333. /// displays what the smart money is doing.) Note, however,
  334. /// that the PVI is not a contrarian indicator. Even though
  335. /// the PVI is supposed to show what the not-so-smart-money
  336. /// is doing, it still trends in the same direction as prices.
  337. /// ---------------------------------------------------------
  338. /// Input:
  339. /// - 2 Y values ( Close, Volume ).
  340. /// Output:
  341. /// - 1 Y value On Positive Volume index.
  342. /// Parameters:
  343. /// - StartValue : double
  344. /// </summary>
  345. /// <param name="inputValues">Arrays of doubles - Input values</param>
  346. /// <param name="outputValues">Arrays of doubles - Output values</param>
  347. /// <param name="parameterList">Array of strings - Parameters</param>
  348. private void PositiveVolumeIndex(double [][] inputValues, out double [][] outputValues, string [] parameterList)
  349. {
  350. // There is no enough input series
  351. if( inputValues.Length != 3 )
  352. throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresTwoArrays);
  353. // Different number of x and y values
  354. CheckNumOfValues( inputValues, 2 );
  355. // Start Value
  356. double startValue;
  357. try
  358. {startValue = double.Parse( parameterList[0], System.Globalization.CultureInfo.InvariantCulture );}
  359. catch(System.Exception)
  360. { throw new InvalidOperationException(SR.ExceptionVolumeIndicatorStartValueMissing); }
  361. outputValues = new double [2][];
  362. outputValues[0] = new double [inputValues[0].Length];
  363. outputValues[1] = new double [inputValues[0].Length];
  364. outputValues[0][0] = inputValues[0][0];
  365. outputValues[1][0] = startValue;
  366. // Find Negative Volume Index
  367. for( int index = 1; index < inputValues[1].Length; index++ )
  368. {
  369. // Set X values
  370. outputValues[0][index] = inputValues[0][index];
  371. // If today’s volume is greater than yesterday’s volume then
  372. if( inputValues[2][index] > inputValues[2][index-1] )
  373. {
  374. double yesterdayClose = inputValues[1][index-1];
  375. double todayClose = inputValues[1][index];
  376. outputValues[1][index] = ( todayClose - yesterdayClose ) / yesterdayClose * outputValues[1][index-1] + outputValues[1][index-1];
  377. }
  378. // If today’s volume is less than or equal to yesterday’s volume then:
  379. else
  380. outputValues[1][index] = outputValues[1][index-1];
  381. }
  382. }
  383. /// <summary>
  384. /// The Accumulation/Distribution is a momentum indicator that
  385. /// associates changes in price and volume. The indicator is
  386. /// based on the premise that the more volume that accompanies
  387. /// a price move, the more significant the price move. A portion
  388. /// of each day’s volume is added or subtracted from
  389. /// a cumulative total. The nearer the closing price is to
  390. /// the high for the day, the more volume added to
  391. /// the cumulative total. The nearer the closing price is to
  392. /// the low for the day, the more volume subtracted from the
  393. /// cumulative total. If the close is exactly between the high
  394. /// and low prices, nothing is added to the cumulative total.
  395. /// ---------------------------------------------------------
  396. /// Input:
  397. /// - 4 Y values ( Hi, Low, Close, Volume ).
  398. /// Output:
  399. /// - 1 Y value Accumulation Distribution
  400. /// </summary>
  401. /// <param name="inputValues">Arrays of doubles - Input values</param>
  402. /// <param name="outputValues">Arrays of doubles - Output values</param>
  403. internal void AccumulationDistribution(double [][] inputValues, out double [][] outputValues)
  404. {
  405. // There is no enough input series
  406. if( inputValues.Length != 5 )
  407. throw new ArgumentException( SR.ExceptionPriceIndicatorsFormulaRequiresFourArrays);
  408. // Different number of x and y values
  409. CheckNumOfValues( inputValues, 4 );
  410. outputValues = new double [2][];
  411. outputValues[0] = new double [inputValues[0].Length];
  412. outputValues[1] = new double [inputValues[0].Length];
  413. double [] distribution = new double [inputValues[0].Length];
  414. // Set X and Y zero values
  415. outputValues[0][0] = inputValues[0][0];
  416. outputValues[1][0] = 0;
  417. // Accumulation Distribution
  418. for( int index = 0; index < inputValues[1].Length; index++ )
  419. {
  420. // Set X values
  421. outputValues[0][index] = inputValues[0][index];
  422. // Distribution {(Close - Low) - (High - Close)} / (High - Low) * Volume
  423. distribution[index] = ((inputValues[3][index] - inputValues[2][index])-(inputValues[1][index] - inputValues[3][index]))/(inputValues[1][index] - inputValues[2][index])*inputValues[4][index];
  424. }
  425. // The Accumulation Distribution Index is calculated as a cumulative total of each day's reading
  426. double sum = 0;
  427. for( int index = 0; index < inputValues[1].Length; index++ )
  428. {
  429. sum += distribution[index];
  430. outputValues[1][index] = sum;
  431. }
  432. }
  433. #endregion
  434. }
  435. }