FormulaData.cs 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284
  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: DataFormula class provides properties and methods,
  6. // which prepare series data for technical analyses
  7. // and time series and forecasting formulas and prepare
  8. // output data to be displayed as a chart.
  9. //
  10. using System;
  11. using System.Diagnostics.CodeAnalysis;
  12. using FastReport.DataVisualization.Charting.Formulas;
  13. namespace FastReport.DataVisualization.Charting
  14. {
  15. #region Financial Formula Name enumeration
  16. /// <summary>
  17. /// An enumeration of financial formula names.
  18. /// </summary>
  19. public enum FinancialFormula
  20. {
  21. /// <summary>
  22. /// Accumulation Distribution formula. This indicator uses a relationship
  23. /// between volume and prices to estimate the strength of price movements,
  24. /// and if volume is increased, there is a high probability that prices will go up.
  25. /// </summary>
  26. AccumulationDistribution,
  27. /// <summary>
  28. /// Average True Range indicator. It measures commitment and compares
  29. /// the range between the High, Low and Close prices.
  30. /// </summary>
  31. AverageTrueRange,
  32. /// <summary>
  33. /// Bollinger Bands indicators. They are plotted at standard deviation levels
  34. /// above and below a simple moving average.
  35. /// </summary>
  36. BollingerBands,
  37. /// <summary>
  38. /// Chaikin Oscillator indicator. It is the difference between a 3-day
  39. /// exponential moving average and a 10-day exponential moving average
  40. /// applied to the Accumulation Distribution.
  41. /// </summary>
  42. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Chaikin")]
  43. ChaikinOscillator,
  44. /// <summary>
  45. /// Commodity Channel Index. It compares prices with their moving averages.
  46. /// </summary>
  47. CommodityChannelIndex,
  48. /// <summary>
  49. /// Detrended Price Oscillator. It attempts to remove trend from prices.
  50. /// </summary>
  51. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Detrended")]
  52. DetrendedPriceOscillator,
  53. /// <summary>
  54. /// Ease of Movement deals with the relationship between volume and price change,
  55. /// and uses volume to indicate how strong a trend is for prices.
  56. /// </summary>
  57. EaseOfMovement,
  58. /// <summary>
  59. /// Envelopes are plotted above and below a moving average using a specified percentage
  60. /// as the shift.
  61. /// </summary>
  62. Envelopes,
  63. /// <summary>
  64. /// An Exponential Moving Average is an average of data calculated over a period of time
  65. /// where the most recent days have more weight.
  66. /// </summary>
  67. ExponentialMovingAverage,
  68. /// <summary>
  69. /// Forecasting. It predicts future values using historical observations.
  70. /// </summary>
  71. Forecasting,
  72. /// <summary>
  73. /// Moving Average Convergence/Divergence indicator. It compares two
  74. /// moving averages of prices and is used with a 9-day Exponential
  75. /// Moving average as a signal, which indicates buying and selling moments.
  76. /// </summary>
  77. MovingAverageConvergenceDivergence,
  78. /// <summary>
  79. /// The Mass Index is used to predict trend reversal by comparing the
  80. /// difference and range between High and Low prices.
  81. /// </summary>
  82. MassIndex,
  83. /// <summary>
  84. /// Median prices are mid-point values of daily prices and can be used
  85. /// as a filter for trend indicators.
  86. /// </summary>
  87. MedianPrice,
  88. /// <summary>
  89. /// The Money Flow indicator compares upward changes and downward changes
  90. /// of volume-weighted typical prices.
  91. /// </summary>
  92. MoneyFlow,
  93. /// <summary>
  94. /// The Negative Volume Index should be used together with the Positive Volume index,
  95. /// and the Negative Volume Index only changes if the volume decreases from the previous day.
  96. /// </summary>
  97. NegativeVolumeIndex,
  98. /// <summary>
  99. /// The On Balance Volume indicator measures positive and negative volume flow.
  100. /// </summary>
  101. OnBalanceVolume,
  102. /// <summary>
  103. /// The Performance indicator compares a current closing price (or any other price) with
  104. /// the first closing value (from the first time period).
  105. /// </summary>
  106. Performance,
  107. /// <summary>
  108. /// The Positive Volume Index should be used together with the Negative Volume index.
  109. /// The Positive volume index only changes if the volume decreases from the previous day.
  110. /// </summary>
  111. PositiveVolumeIndex,
  112. /// <summary>
  113. /// The Price Volume Trend is a cumulative volume total that is calculated using
  114. /// relative changes of the closing price, and should be used with other indicators.
  115. /// </summary>
  116. PriceVolumeTrend,
  117. /// <summary>
  118. /// The Rate of Change indicator compares a specified closing price with the current price.
  119. /// </summary>
  120. RateOfChange,
  121. /// <summary>
  122. /// The Relative Strength Index is a momentum oscillator that compares upward movements
  123. /// of the closing price with downward movements, and results in values that range from 0 to 100.
  124. /// </summary>
  125. RelativeStrengthIndex,
  126. /// <summary>
  127. /// A Simple Moving Average is an average of data calculated over a period of time.
  128. /// The moving average is the most popular price indicator used in technical analysis,
  129. /// and can be used with any price (e.g. Hi, Low, Open and Close)
  130. /// or it can be applied to other indicators.
  131. /// </summary>
  132. MovingAverage,
  133. /// <summary>
  134. /// Standard deviation is used to indicate volatility, and measures
  135. /// the difference between values (e.g. closing price) and their moving average.
  136. /// </summary>
  137. StandardDeviation,
  138. /// <summary>
  139. /// The Stochastic Indicator helps to find trend reversal by searching in a period for
  140. /// when the closing prices are close to low prices in an upward trending market
  141. /// and for when the closing prices are close to high prices in a downward trending market.
  142. /// </summary>
  143. StochasticIndicator,
  144. /// <summary>
  145. /// A Triangular Moving Average is an average of data calculated over a period of time
  146. /// where the middle portion of data has more weight.
  147. /// </summary>
  148. TriangularMovingAverage,
  149. /// <summary>
  150. /// The Triple Exponential Moving Average is based on a triple moving average of the closing Price.
  151. /// Its purpose is to eliminate short cycles. This indicator keeps the closing price
  152. /// in trends that are shorter than the specified period.
  153. /// </summary>
  154. TripleExponentialMovingAverage,
  155. /// <summary>
  156. /// Typical price is the average value of daily prices, and can be used as a filter for trend indicators.
  157. /// </summary>
  158. TypicalPrice,
  159. /// <summary>
  160. /// The Volatility Chaikins indicator measures the difference between High and Low prices,
  161. /// and is used to indicate tops or bottoms of the market.
  162. /// </summary>
  163. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Chaikins")]
  164. VolatilityChaikins,
  165. /// <summary>
  166. /// The Volume oscillator attempts to identify trends in volume by comparing two moving averages:
  167. /// one with a short period and another with a longer period.
  168. /// </summary>
  169. VolumeOscillator,
  170. /// <summary>
  171. /// The Weighted Close formula calculates the average value of daily prices.
  172. /// The only difference between Typical Price and the Weighted Close is that the closing price
  173. /// has extra weight, and is considered the most important price.
  174. /// </summary>
  175. WeightedClose,
  176. /// <summary>
  177. /// A Weighted Moving Average is an average of data calculated over a period of time,
  178. /// where greater weight is attached to the most recent data.
  179. /// </summary>
  180. WeightedMovingAverage,
  181. /// <summary>
  182. /// William's %R is a momentum indicator, and is used to measure overbought and oversold levels.
  183. /// </summary>
  184. WilliamsR
  185. }
  186. #endregion // Financial Formula Name enumeration
  187. /// <summary>
  188. /// The DataFormula class provides properties and methods, which prepare series
  189. /// data for technical analysis, apply formulas on the series data
  190. /// and prepare output data to be displayed as a chart.
  191. /// </summary>
  192. public class DataFormula
  193. {
  194. #region Data Formulas fields
  195. internal const string IndexedSeriesLabelsSourceAttr = "__IndexedSeriesLabelsSource__";
  196. //***********************************************************
  197. //** Private data members, which store properties values
  198. //***********************************************************
  199. private bool _isEmptyPointIgnored = true;
  200. private string[] _extraParameters;
  201. /// <summary>
  202. /// All X values are zero.
  203. /// </summary>
  204. private bool _zeroXValues = false;
  205. /// <summary>
  206. /// Utility class for Statistical formulas
  207. /// </summary>
  208. private StatisticFormula _statistics;
  209. /// <summary>
  210. /// Reference to the Common elements
  211. /// </summary>
  212. internal CommonElements Common;
  213. #endregion
  214. #region Data Formulas methods
  215. /// <summary>
  216. /// Default constructor
  217. /// </summary>
  218. public DataFormula()
  219. {
  220. _statistics = new StatisticFormula(this);
  221. _extraParameters = new string[1];
  222. _extraParameters[0] = false.ToString(System.Globalization.CultureInfo.InvariantCulture);
  223. }
  224. /// <summary>
  225. /// This method calls a method from a formula module with
  226. /// specified name.
  227. /// </summary>
  228. /// <param name="formulaName">Formula Name</param>
  229. /// <param name="parameters">Formula parameters</param>
  230. /// <param name="inputSeries">Comma separated input data series names and optional X and Y values names.</param>
  231. /// <param name="outputSeries">Comma separated output data series names and optional X and Y values names.</param>
  232. internal void Formula(string formulaName, string parameters, string inputSeries, string outputSeries)
  233. {
  234. // Array of series
  235. Series[] inSeries;
  236. Series[] outSeries;
  237. // Commented out as InsertEmptyDataPoints is currently commented out.
  238. // This field is not used anywhere else, but we might need it if we uncomment all the disabled code parts in this method. (krisztb 4/29/08)
  239. // True if formulas are statistical
  240. //bool statisticalFormulas = false;
  241. // Array of Y value indexes
  242. int[] inValueIndexes;
  243. int[] outValueIndexes;
  244. // Matrix with double values ( used in formula modules )
  245. double[][] inValues;
  246. double[][] inNoEmptyValues;
  247. double[][] outValues = null;
  248. string[][] outLabels = null;
  249. // Array with parameters
  250. string[] parameterList;
  251. // Split comma separated parameter list in the array of strings.
  252. SplitParameters(parameters, out parameterList);
  253. // Split comma separated series and Y values list in the array of
  254. // Series and indexes to Y values.
  255. ConvertToArrays(inputSeries, out inSeries, out inValueIndexes, true);
  256. ConvertToArrays(outputSeries, out outSeries, out outValueIndexes, false);
  257. // Create indexes if all x values are 0
  258. //ConvertZeroXToIndex( ref inSeries );
  259. // Set X value AxisName for output series.
  260. foreach (Series outSeriesItem in outSeries)
  261. {
  262. if (inSeries[0] != null)
  263. {
  264. outSeriesItem.XValueType = inSeries[0].XValueType;
  265. }
  266. }
  267. // This method will convert array of Series and array of Y value
  268. // indexes to matrix of double values.
  269. GetDoubleArray(inSeries, inValueIndexes, out inValues);
  270. // Remove columns with empty values from matrix
  271. if (!DifferentNumberOfSeries(inValues))
  272. {
  273. RemoveEmptyValues(inValues, out inNoEmptyValues);
  274. }
  275. else
  276. {
  277. inNoEmptyValues = inValues;
  278. }
  279. // Call a formula from formula modules
  280. string moduleName = null;
  281. for (int module = 0; module < Common.FormulaRegistry.Count; module++)
  282. {
  283. moduleName = Common.FormulaRegistry.GetModuleName(module);
  284. Common.FormulaRegistry.GetFormulaModule(moduleName).Formula(formulaName, inNoEmptyValues, out outValues, parameterList, _extraParameters, out outLabels);
  285. // Commented out as InsertEmptyDataPoints is currently commented out (see next block).
  286. // It set the statisticalFormulas field that was used to test whether to insert empty data points. (krisztb 4/29/08)
  287. //if( outValues != null )
  288. //{
  289. // if (moduleName == SR.FormulaNameStatisticalAnalysis)
  290. // {
  291. // statisticalFormulas = true;
  292. // }
  293. // break;
  294. //}
  295. // Check if formula was found by detecting output
  296. if (outValues != null)
  297. {
  298. // Exit the loop
  299. break;
  300. }
  301. }
  302. if (outValues == null)
  303. throw new ArgumentException(SR.ExceptionFormulaNotFound(formulaName));
  304. // Insert empty data points
  305. //
  306. // This has been commented out as InsertEmptyDataPoints is currently commented out.
  307. // In its current implementation it didn't do anything other than assign the second
  308. // parameter to the third, so ultimately it was a no op. --magreer 4/21/08
  309. //
  310. //if( !statisticalFormulas )
  311. //{
  312. // InsertEmptyDataPoints( inValues, outValues, out outValues );
  313. //}
  314. // Fill Series with results from matrix with double values using Y value indexes.
  315. SetDoubleArray(outSeries, outValueIndexes, outValues, outLabels);
  316. if (_zeroXValues)
  317. {
  318. // we have indexed series : proceed to align output series.
  319. foreach (Series series in outSeries)
  320. {
  321. if (series.Points.Count > 0)
  322. {
  323. // get the last xValue: the formula processing is
  324. double topXValue = series.Points[series.Points.Count - 1].XValue;
  325. this.Common.Chart.DataManipulator.InsertEmptyPoints(1, IntervalType.Number, 0, IntervalType.Number, 1, topXValue, series);
  326. foreach (DataPoint point in series.Points)
  327. {
  328. point.XValue = 0;
  329. }
  330. }
  331. }
  332. }
  333. // Copy axis labels from the original series into the calculated series
  334. CopyAxisLabels(inSeries, outSeries);
  335. }
  336. /// <summary>
  337. /// Copy axis labels from the original series into the calculated series
  338. /// </summary>
  339. /// <param name="inSeries">array of input series</param>
  340. /// <param name="outSeries">array of output series</param>
  341. private void CopyAxisLabels(Series[] inSeries, Series[] outSeries)
  342. {
  343. //Loop through the pairs of input and output series
  344. int seriesIndex = 0;
  345. while (seriesIndex < inSeries.Length && seriesIndex < outSeries.Length)
  346. {
  347. Series inputSeries = inSeries[seriesIndex];
  348. Series outputSeries = outSeries[seriesIndex];
  349. //Depending on whether or not the source series has X Values we need to use two different search algorithms
  350. if (_zeroXValues)
  351. { //If we have the empty XValues then the source series should have all the AxisLabels
  352. // -- set the indexed series labels source
  353. outputSeries[DataFormula.IndexedSeriesLabelsSourceAttr] = inputSeries.Name;
  354. }
  355. else
  356. { //If the source series has XValues - loop through the input series points looking for the points with AxisLabels set
  357. int outIndex = 0;
  358. foreach (DataPoint inputPoint in inputSeries.Points)
  359. {
  360. if (!String.IsNullOrEmpty(inputPoint.AxisLabel))
  361. {
  362. //If the Axis label is set we need to find the corresponding point by the X value
  363. //Most probably the points are in the same order so lets first try the corresponding point in the output series
  364. if (outIndex < outputSeries.Points.Count && inputPoint.XValue == outputSeries.Points[outIndex].XValue)
  365. { // Yes, the corresponding point in the outputSeries has the same XValue as inputPoint -> copy axis label
  366. outputSeries.Points[outIndex].AxisLabel = inputPoint.AxisLabel;
  367. }
  368. else
  369. {
  370. //The correspong point has a different x value -> lets go through output series and find the value with the same X
  371. outIndex = 0;
  372. foreach (DataPoint outputPoint in outputSeries.Points)
  373. {
  374. if (inputPoint.XValue == outputPoint.XValue)
  375. { //Found the point with the same XValue - copy axis label and break
  376. outputPoint.AxisLabel = inputPoint.AxisLabel;
  377. break;
  378. }
  379. outIndex++;
  380. }
  381. }
  382. }
  383. outIndex++;
  384. }
  385. }
  386. //Sync next pair of input and output series...
  387. seriesIndex++;
  388. }
  389. }
  390. /// <summary>
  391. /// This method will set series X and Y values from matrix of
  392. /// double values.
  393. /// </summary>
  394. /// <param name="outputSeries">Array of output series</param>
  395. /// <param name="valueIndex">Array of Y value indexes</param>
  396. /// <param name="outputValues">Array of doubles which will be used to fill series</param>
  397. /// <param name="outputLabels">Array of labels</param>
  398. internal void SetDoubleArray(Series[] outputSeries, int[] valueIndex, double[][] outputValues, string[][] outputLabels)
  399. {
  400. // Validation
  401. if (outputSeries.Length != valueIndex.Length)
  402. {
  403. throw new ArgumentException(SR.ExceptionFormulaDataItemsNumberMismatch);
  404. }
  405. // Number of output series is not correct
  406. if (outputSeries.Length < outputValues.Length - 1)
  407. {
  408. throw new ArgumentException(SR.ExceptionFormulaDataOutputSeriesNumberYValuesIncorrect);
  409. }
  410. int seriesIndex = 0;
  411. foreach (Series series in outputSeries)
  412. {
  413. if (seriesIndex + 1 > outputValues.Length - 1)
  414. {
  415. break;
  416. }
  417. // If there is different number of data points.
  418. if (series.Points.Count != outputValues[seriesIndex].Length)
  419. {
  420. // Delete all points
  421. series.Points.Clear();
  422. }
  423. // Set the number of y values
  424. if (series.YValuesPerPoint < valueIndex[seriesIndex])
  425. {
  426. series.YValuesPerPoint = valueIndex[seriesIndex];
  427. }
  428. for (int pointIndex = 0; pointIndex < outputValues[0].Length; pointIndex++)
  429. {
  430. // Create a new series and fill data
  431. if (series.Points.Count != outputValues[seriesIndex].Length)
  432. {
  433. // Add data points to series.
  434. series.Points.AddXY(outputValues[0][pointIndex], 0);
  435. // Set Labels
  436. if (outputLabels != null)
  437. {
  438. series.Points[pointIndex].Label = outputLabels[seriesIndex][pointIndex];
  439. }
  440. // Set empty data points or Y values
  441. if (Double.IsNaN(outputValues[seriesIndex + 1][pointIndex]))
  442. series.Points[pointIndex].IsEmpty = true;
  443. else
  444. series.Points[pointIndex].YValues[valueIndex[seriesIndex] - 1] = outputValues[seriesIndex + 1][pointIndex];
  445. }
  446. // Use existing series and set Y values.
  447. else
  448. {
  449. if (series.Points[pointIndex].XValue != outputValues[0][pointIndex] && !_zeroXValues)
  450. {
  451. throw new InvalidOperationException(SR.ExceptionFormulaXValuesNotAligned);
  452. }
  453. // Set empty data points or Y values
  454. if (Double.IsNaN(outputValues[seriesIndex + 1][pointIndex]))
  455. series.Points[pointIndex].IsEmpty = true;
  456. else
  457. {
  458. series.Points[pointIndex].YValues[valueIndex[seriesIndex] - 1] = outputValues[seriesIndex + 1][pointIndex];
  459. // Set Labels
  460. if (outputLabels != null)
  461. {
  462. series.Points[pointIndex].Label = outputLabels[seriesIndex][pointIndex];
  463. }
  464. }
  465. }
  466. }
  467. seriesIndex++;
  468. }
  469. }
  470. /// <summary>
  471. /// This method will convert a string with information about
  472. /// series and y values to two arrays. The first array will
  473. /// contain series and the second array will contain
  474. /// corresponding indexes to y values for every series.
  475. /// The arrays have to have the same number of items.
  476. /// </summary>
  477. /// <param name="inputString">A string with information about series and Y values</param>
  478. /// <param name="seiesArray">Array of Data Series</param>
  479. /// <param name="valueArray">Array of Y value indexes</param>
  480. /// <param name="inputSeries">Do not create new series if input series are used</param>
  481. private void ConvertToArrays(string inputString, out Series[] seiesArray, out int[] valueArray, bool inputSeries)
  482. {
  483. // Split string by comma
  484. string[] subStrings = inputString.Split(',');
  485. // Create array of series
  486. seiesArray = new Series[subStrings.Length];
  487. // Create array of integers - values
  488. valueArray = new int[subStrings.Length];
  489. int index = 0;
  490. foreach (string str in subStrings)
  491. {
  492. string[] parts = str.Split(':');
  493. // There must be at least one and no more than two result strings
  494. if (parts.Length < 1 && parts.Length > 2)
  495. {
  496. throw (new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(str)));
  497. }
  498. // Initialize value index as first Y value (default)
  499. int valueIndex = 1;
  500. // Check specified value type
  501. if (parts.Length == 2)
  502. {
  503. if (parts[1].StartsWith("Y", StringComparison.Ordinal))
  504. {
  505. parts[1] = parts[1].TrimStart('Y');
  506. if (parts[1].Length == 0)
  507. {
  508. valueIndex = 1;
  509. }
  510. else
  511. {
  512. // Try to convert the rest of the string to integer
  513. try
  514. {
  515. valueIndex = Int32.Parse(parts[1], System.Globalization.CultureInfo.InvariantCulture);
  516. }
  517. catch (System.Exception)
  518. {
  519. throw (new ArgumentException(SR.ExceptionFormulaDataFormatInvalid(str)));
  520. }
  521. }
  522. }
  523. else
  524. {
  525. throw (new ArgumentException(SR.ExceptionFormulaDataSeriesNameNotFound(str)));
  526. }
  527. }
  528. // Set Y value indexes
  529. valueArray[index] = valueIndex;
  530. // Set series
  531. try
  532. {
  533. seiesArray[index] = Common.DataManager.Series[parts[0].Trim()];
  534. }
  535. catch (System.Exception)
  536. {
  537. // Series doesn't exist.
  538. if (!inputSeries)
  539. {
  540. // Create a new series if output series
  541. Common.DataManager.Series.Add(new Series(parts[0]));
  542. seiesArray[index] = Common.DataManager.Series[parts[0]];
  543. }
  544. else
  545. throw (new ArgumentException(SR.ExceptionFormulaDataSeriesNameNotFoundInCollection(str)));
  546. }
  547. index++;
  548. }
  549. }
  550. /// <summary>
  551. /// Returns Jagged Arrays of doubles from array of series.
  552. /// A jagged array is merely an array of arrays and
  553. /// it doesn't have to be square. The first item is array of
  554. /// X values from the first series
  555. /// </summary>
  556. /// <param name="inputSeries">Array of Data Series</param>
  557. /// <param name="valueIndex">Array with indexes which represent value from data point: 0 = X, 1 = Y, 2 = Y2, 3 = Y3</param>
  558. /// <param name="output">Jagged Arrays of doubles</param>
  559. private void GetDoubleArray(Series[] inputSeries, int[] valueIndex, out double[][] output)
  560. {
  561. GetDoubleArray(inputSeries, valueIndex, out output, false);
  562. }
  563. /// <summary>
  564. /// Returns Jagged Arrays of doubles from array of series.
  565. /// A jagged array is merely an array of arrays and
  566. /// it doesn't have to be square. The first item is array of
  567. /// X values from the first series
  568. /// </summary>
  569. /// <param name="inputSeries">Array of Data Series</param>
  570. /// <param name="valueIndex">Array with indexes which represent value from data point: 0 = X, 1 = Y, 2 = Y2, 3 = Y3</param>
  571. /// <param name="output">Jagged Arrays of doubles</param>
  572. /// <param name="ignoreZeroX">Ignore Zero X values</param>
  573. private void GetDoubleArray(Series[] inputSeries, int[] valueIndex, out double[][] output, bool ignoreZeroX)
  574. {
  575. // Allocate a memory.
  576. output = new double[inputSeries.Length + 1][];
  577. // Check the length of the array of series and array of value indexes.
  578. if (inputSeries.Length != valueIndex.Length)
  579. {
  580. throw new ArgumentException(SR.ExceptionFormulaDataItemsNumberMismatch2);
  581. }
  582. // Find Maximum number of data points
  583. int maxNumOfPoints = int.MinValue;
  584. Series seriesWidthMaxPoints = null;
  585. foreach (Series series in inputSeries)
  586. {
  587. if (maxNumOfPoints < series.Points.Count)
  588. {
  589. maxNumOfPoints = series.Points.Count;
  590. seriesWidthMaxPoints = series;
  591. }
  592. }
  593. // *********************************************************
  594. // Set X values
  595. // *********************************************************
  596. // Check if all X values are zero
  597. foreach (DataPoint point in inputSeries[0].Points)
  598. {
  599. _zeroXValues = true;
  600. if (point.XValue != 0.0)
  601. {
  602. _zeroXValues = false;
  603. break;
  604. }
  605. }
  606. if (_zeroXValues && !ignoreZeroX)
  607. {
  608. // Check X values input alignment
  609. CheckXValuesAlignment(inputSeries);
  610. }
  611. // Data point index
  612. int indexPoint = 0;
  613. // Allocate memory for X values.
  614. output[0] = new double[maxNumOfPoints];
  615. // Data Points loop
  616. foreach (DataPoint point in seriesWidthMaxPoints.Points)
  617. {
  618. // Set X value
  619. if (_zeroXValues)
  620. output[0][indexPoint] = (double)indexPoint + 1.0;
  621. else
  622. output[0][indexPoint] = point.XValue;
  623. // Increase data point index.
  624. indexPoint++;
  625. }
  626. // *********************************************************
  627. // Set Y values
  628. // *********************************************************
  629. // Data Series Loop
  630. int indexSeries = 1;
  631. foreach (Series series in inputSeries)
  632. {
  633. output[indexSeries] = new double[series.Points.Count];
  634. indexPoint = 0;
  635. // Data Points loop
  636. foreach (DataPoint point in series.Points)
  637. {
  638. // Set Y values
  639. if (point.IsEmpty)
  640. // IsEmpty data point
  641. output[indexSeries][indexPoint] = double.NaN;
  642. else
  643. {
  644. try
  645. {
  646. output[indexSeries][indexPoint] = point.YValues[valueIndex[indexSeries - 1] - 1];
  647. }
  648. catch (System.Exception)
  649. {
  650. throw new ArgumentException(SR.ExceptionFormulaYIndexInvalid);
  651. }
  652. }
  653. // Increase data point index.
  654. indexPoint++;
  655. }
  656. // Increase data series index.
  657. indexSeries++;
  658. }
  659. }
  660. /// <summary>
  661. /// Merge, split or move Y values of the series.
  662. /// </summary>
  663. /// <param name="inputSeries">Comma separated list of input data series names and optional X and Y values names.</param>
  664. /// <param name="outputSeries">Comma separated list of output data series names and optional X and Y values names.</param>
  665. public void CopySeriesValues(string inputSeries, string outputSeries)
  666. {
  667. if (inputSeries == null)
  668. throw new ArgumentNullException("inputSeries");
  669. if (outputSeries == null)
  670. throw new ArgumentNullException("outputSeries");
  671. Series[] inSeries;
  672. Series[] outSeries;
  673. int[] inValueIndexes;
  674. int[] outValueIndexes;
  675. double[][] inValues;
  676. double[][] outValues;
  677. // Convert string with information about series and Y values
  678. // to array of series and indexes to Y values.
  679. ConvertToArrays(inputSeries, out inSeries, out inValueIndexes, true);
  680. ConvertToArrays(outputSeries, out outSeries, out outValueIndexes, false);
  681. // The number of input and output series are different.
  682. if (inSeries.Length != outSeries.Length)
  683. {
  684. throw new ArgumentException(SR.ExceptionFormulaInputOutputSeriesMismatch);
  685. }
  686. // Check if output series points exist. If they do not exist
  687. // create data points which are copy of Input series data points
  688. for (int indexSeries = 0; indexSeries < inSeries.Length; indexSeries++)
  689. {
  690. Series[] series = new Series[2];
  691. series[0] = inSeries[indexSeries];
  692. series[1] = outSeries[indexSeries];
  693. if (series[1].Points.Count == 0)
  694. {
  695. foreach (DataPoint point in series[0].Points)
  696. {
  697. DataPoint clonePoint = point.Clone();
  698. clonePoint.series = series[1];
  699. series[1].Points.Add(clonePoint);
  700. }
  701. }
  702. }
  703. // Check alignment of X values.
  704. for (int indexSeries = 0; indexSeries < inSeries.Length; indexSeries++)
  705. {
  706. Series[] series = new Series[2];
  707. series[0] = inSeries[indexSeries];
  708. series[1] = outSeries[indexSeries];
  709. CheckXValuesAlignment(series);
  710. }
  711. // Covert Series X and Y values to arrays of doubles
  712. GetDoubleArray(inSeries, inValueIndexes, out inValues, true);
  713. outValues = new double[inValues.Length][];
  714. // Copy Series X and Y values.
  715. for (int seriesIndex = 0; seriesIndex < inValues.Length; seriesIndex++)
  716. {
  717. outValues[seriesIndex] = new double[inValues[seriesIndex].Length];
  718. for (int pointIndex = 0; pointIndex < inValues[seriesIndex].Length; pointIndex++)
  719. {
  720. outValues[seriesIndex][pointIndex] = inValues[seriesIndex][pointIndex];
  721. }
  722. }
  723. // Copy Series X and Y value Types.
  724. for (int seriesIndx = 0; seriesIndx < inSeries.Length; seriesIndx++)
  725. {
  726. // X value type
  727. if (outSeries[seriesIndx].XValueType == ChartValueType.Auto)
  728. {
  729. outSeries[seriesIndx].XValueType = inSeries[seriesIndx].XValueType;
  730. outSeries[seriesIndx].autoXValueType = inSeries[seriesIndx].autoXValueType;
  731. }
  732. // Y value type.
  733. if (outSeries[seriesIndx].YValueType == ChartValueType.Auto)
  734. {
  735. outSeries[seriesIndx].YValueType = inSeries[seriesIndx].YValueType;
  736. outSeries[seriesIndx].autoYValueType = inSeries[seriesIndx].autoYValueType;
  737. }
  738. seriesIndx++;
  739. }
  740. SetDoubleArray(outSeries, outValueIndexes, outValues, null);
  741. }
  742. /// <summary>
  743. /// This method will first copy input matrix to output matrix
  744. /// then will remove columns, which have
  745. /// one or more empty values (NaN) from the output matrix. This
  746. /// method will set all values from column of input matrix
  747. /// to be empty (NaN) if one or more values of that column
  748. /// are empty.
  749. /// </summary>
  750. /// <param name="input">Input matrix with empty values</param>
  751. /// <param name="output">Output matrix without empty values</param>
  752. private void RemoveEmptyValues(double[][] input, out double[][] output)
  753. {
  754. // Allocate memory
  755. output = new double[input.Length][];
  756. int seriesIndex = 0;
  757. int numberOfRows = 0;
  758. // Set Nan for all data points with same index in input array
  759. // Data point loop
  760. for (int pointIndex = 0; pointIndex < input[0].Length; pointIndex++)
  761. {
  762. bool isEmpty = false;
  763. // Series loop
  764. // Find empty data point with same point index
  765. for (seriesIndex = 0; seriesIndex < input.Length; seriesIndex++)
  766. {
  767. if (seriesIndex >= input[seriesIndex].Length)
  768. continue;
  769. if (Double.IsNaN(input[seriesIndex][pointIndex]))
  770. isEmpty = true;
  771. }
  772. if (!isEmpty)
  773. {
  774. numberOfRows++;
  775. }
  776. // There is empty data point
  777. if (isEmpty)
  778. {
  779. // Set all points with same index to be empty
  780. for (seriesIndex = 1; seriesIndex < input.Length; seriesIndex++)
  781. {
  782. input[seriesIndex][pointIndex] = Double.NaN;
  783. }
  784. }
  785. }
  786. // Copy input matrix to output matrix without empty columns.
  787. for (seriesIndex = 0; seriesIndex < input.Length; seriesIndex++)
  788. {
  789. output[seriesIndex] = new double[numberOfRows];
  790. int outPointIndex = 0;
  791. for (int pointIndex = 0; pointIndex < input[0].Length; pointIndex++)
  792. {
  793. if (pointIndex >= input[seriesIndex].Length)
  794. continue;
  795. if (!double.IsNaN(input[1][pointIndex]))
  796. {
  797. output[seriesIndex][outPointIndex] = input[seriesIndex][pointIndex];
  798. outPointIndex++;
  799. }
  800. }
  801. }
  802. }
  803. /*
  804. /// <summary>
  805. /// This method will compare a input matrix with empty data
  806. /// points and output matrix without empty data points and
  807. /// add empty data points to output matrix according to
  808. /// input matrix empty data point positions.
  809. /// </summary>
  810. /// <param name="input">Matrix With input data</param>
  811. /// <param name="inputWithoutEmpty">Matrix without empty data points</param>
  812. /// <param name="output">New Matrix with inserted data points</param>
  813. */
  814. //private void InsertEmptyDataPoints( double [][] input, double [][] inputWithoutEmpty, out double [][] output )
  815. //{
  816. // *** NOTE ***
  817. //
  818. //
  819. // This method is only called in one location as of this writing.
  820. // Therefore the entire method is being commented out for now. We wish
  821. // to preserve the code itself as it may be re-implemented in the future.
  822. // --magreer 4/21/08
  823. //
  824. // ************
  825. //output = inputWithoutEmpty;
  826. //return;
  827. //
  828. // NOTE: Inserting empty points in the result data after applying the formula
  829. // causes issues. The algorithm below do not cover most of the common spzces
  830. // and as a result the formula data is completly destroyed.
  831. //
  832. // By removing this code the result data set will have "missing" points instaed
  833. // of empty.
  834. // - AG
  835. //
  836. /*
  837. // Input matrix can have only empty rows. If one value
  838. // is empty all values from a row have to be empty.
  839. // Find the number of empty rows
  840. int NumberOfEmptyRows = 0;
  841. foreach( double val in input[1] )
  842. {
  843. if( Double.IsNaN( val ) )
  844. {
  845. NumberOfEmptyRows++;
  846. }
  847. }
  848. if( NumberOfEmptyRows == 0 ||
  849. inputWithoutEmpty[0].Length > input[0].Length)
  850. {
  851. output = inputWithoutEmpty;
  852. return;
  853. }
  854. output = new double[input.Length][];
  855. // Series loop
  856. for( int seriesIndex = 0; seriesIndex < input.Length; seriesIndex++ )
  857. {
  858. int inputPointIndex = 0;
  859. int emptyPointIndex = 0;
  860. // Skip input index if points are not aligned .
  861. while( input[0][inputPointIndex] != inputWithoutEmpty[0][0] && inputPointIndex < input[0].Length )
  862. {
  863. inputPointIndex++;
  864. }
  865. output[seriesIndex] = new double[inputWithoutEmpty[0].Length + NumberOfEmptyRows - inputPointIndex];
  866. // Data Point loop
  867. for( int pointIndex = 0; pointIndex < output[seriesIndex].Length; pointIndex++ )
  868. {
  869. if( inputPointIndex < input[0].Length &&
  870. inputPointIndex < input[1].Length )
  871. {
  872. // If the point Y value is empty (NaN) insert empty (NaN) for all values.
  873. if( double.IsNaN( input[1][inputPointIndex] ) )
  874. {
  875. output[seriesIndex][pointIndex] = input[seriesIndex][inputPointIndex];
  876. emptyPointIndex--;
  877. }
  878. else if( input[0][inputPointIndex] == inputWithoutEmpty[0][emptyPointIndex] )
  879. {
  880. output[seriesIndex][pointIndex] = inputWithoutEmpty[seriesIndex][emptyPointIndex];
  881. }
  882. else
  883. {
  884. output[0][pointIndex] = inputWithoutEmpty[0][emptyPointIndex];
  885. output[seriesIndex][pointIndex] = inputWithoutEmpty[seriesIndex][emptyPointIndex];
  886. }
  887. }
  888. else
  889. {
  890. output[seriesIndex][pointIndex] = inputWithoutEmpty[seriesIndex][emptyPointIndex];
  891. }
  892. inputPointIndex++;
  893. emptyPointIndex++;
  894. }
  895. }
  896. */
  897. //}
  898. /// <summary>
  899. /// This method splits a string with comma separated
  900. /// parameters to the array of strings with parameters.
  901. /// </summary>
  902. /// <param name="parameters">a string with comma separated parameters</param>
  903. /// <param name="parameterList">the array of strings with parameters</param>
  904. private void SplitParameters(string parameters, out string[] parameterList)
  905. {
  906. // Split string by comma
  907. parameterList = parameters.Split(',');
  908. for (Int32 i = 0; i < parameterList.Length; i++)
  909. {
  910. parameterList[i] = parameterList[i].Trim();
  911. }
  912. }
  913. /// <summary>
  914. /// Check if series have different number of series.
  915. /// </summary>
  916. /// <param name="input">Input series.</param>
  917. /// <returns>true if there is different number of series.</returns>
  918. private static bool DifferentNumberOfSeries(double[][] input)
  919. {
  920. for (int index = 0; index < input.Length - 1; index++)
  921. {
  922. if (input[index].Length != input[index + 1].Length)
  923. {
  924. return true;
  925. }
  926. }
  927. return false;
  928. }
  929. /// <summary>
  930. /// This method will check if X values from different series
  931. /// are aligned.
  932. /// </summary>
  933. /// <param name="series">Array of series</param>
  934. internal void CheckXValuesAlignment(Series[] series)
  935. {
  936. // Check aligment only if more than 1 series provided
  937. if (series.Length > 1)
  938. {
  939. // Series loop
  940. for (int seriesIndex = 0; seriesIndex < series.Length - 1; seriesIndex++)
  941. {
  942. // Check the number of data points
  943. if (series[seriesIndex].Points.Count != series[seriesIndex + 1].Points.Count)
  944. {
  945. throw new ArgumentException(SR.ExceptionFormulaDataSeriesAreNotAlignedDifferentDataPoints(series[seriesIndex].Name, series[seriesIndex + 1].Name));
  946. }
  947. // Data points loop
  948. for (int pointIndex = 0; pointIndex < series[seriesIndex].Points.Count; pointIndex++)
  949. {
  950. // Check X values.
  951. if (series[seriesIndex].Points[pointIndex].XValue != series[seriesIndex + 1].Points[pointIndex].XValue)
  952. throw new ArgumentException(SR.ExceptionFormulaDataSeriesAreNotAlignedDifferentXValues(series[seriesIndex].Name, series[seriesIndex + 1].Name));
  953. }
  954. }
  955. }
  956. }
  957. #endregion
  958. #region Data Formulas Financial methods
  959. /// <summary>
  960. /// This method calls a method from a formula module with
  961. /// specified name.
  962. /// </summary>
  963. /// <param name="formulaName">Formula Name</param>
  964. /// <param name="inputSeries">Input series</param>
  965. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  966. public void FinancialFormula(FinancialFormula formulaName, Series inputSeries)
  967. {
  968. FinancialFormula(formulaName, inputSeries, inputSeries);
  969. }
  970. /// <summary>
  971. /// This method calls a method from a formula module with
  972. /// specified name.
  973. /// </summary>
  974. /// <param name="formulaName">Formula Name</param>
  975. /// <param name="inputSeries">Input series</param>
  976. /// <param name="outputSeries">Output series</param>
  977. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  978. public void FinancialFormula(FinancialFormula formulaName, Series inputSeries, Series outputSeries)
  979. {
  980. FinancialFormula(formulaName, "", inputSeries, outputSeries);
  981. }
  982. /// <summary>
  983. /// This method calls a method from a formula module with
  984. /// specified name.
  985. /// </summary>
  986. /// <param name="formulaName">Formula Name</param>
  987. /// <param name="parameters">Formula parameters</param>
  988. /// <param name="inputSeries">Input series</param>
  989. /// <param name="outputSeries">Output series</param>
  990. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  991. public void FinancialFormula(FinancialFormula formulaName, string parameters, Series inputSeries, Series outputSeries)
  992. {
  993. if (inputSeries == null)
  994. throw new ArgumentNullException("inputSeries");
  995. if (outputSeries == null)
  996. throw new ArgumentNullException("outputSeries");
  997. FinancialFormula(formulaName, parameters, inputSeries.Name, outputSeries.Name);
  998. }
  999. /// <summary>
  1000. /// This method calls a method from a formula module with
  1001. /// specified name.
  1002. /// </summary>
  1003. /// <param name="formulaName">Formula Name</param>
  1004. /// <param name="inputSeries">Comma separated list of input series names and optional X and Y values names.</param>
  1005. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  1006. public void FinancialFormula(FinancialFormula formulaName, string inputSeries)
  1007. {
  1008. FinancialFormula(formulaName, inputSeries, inputSeries);
  1009. }
  1010. /// <summary>
  1011. /// This method calls a method from a formula module with
  1012. /// specified name.
  1013. /// </summary>
  1014. /// <param name="formulaName">Formula Name</param>
  1015. /// <param name="inputSeries">Comma separated list of input series names and optional X and Y values names.</param>
  1016. /// <param name="outputSeries">Comma separated list of output series names and optional X and Y values names.</param>
  1017. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  1018. public void FinancialFormula(FinancialFormula formulaName, string inputSeries, string outputSeries)
  1019. {
  1020. FinancialFormula(formulaName, "", inputSeries, outputSeries);
  1021. }
  1022. /// <summary>
  1023. /// This method calls a method from a formula module with
  1024. /// specified name.
  1025. /// </summary>
  1026. /// <param name="formulaName">Formula Name</param>
  1027. /// <param name="parameters">Formula parameters</param>
  1028. /// <param name="inputSeries">Comma separated list of input series names and optional X and Y values names.</param>
  1029. /// <param name="outputSeries">Comma separated list of output series names and optional X and Y values names.</param>
  1030. public void FinancialFormula(FinancialFormula formulaName, string parameters, string inputSeries, string outputSeries)
  1031. {
  1032. if (inputSeries == null)
  1033. throw new ArgumentNullException("inputSeries");
  1034. if (outputSeries == null)
  1035. throw new ArgumentNullException("outputSeries");
  1036. // Get formula info
  1037. FormulaInfo formulaInfo = FormulaHelper.GetFormulaInfo(formulaName);
  1038. // Provide default parameters if necessary
  1039. if (string.IsNullOrEmpty(parameters))
  1040. {
  1041. parameters = formulaInfo.SaveParametersToString();
  1042. }
  1043. else
  1044. {
  1045. formulaInfo.CheckParameterString(parameters);
  1046. }
  1047. // Fix the InputSeries and Outputseries for cases when the series field names are not provided
  1048. SeriesFieldList inputFields = SeriesFieldList.FromString(this.Common.Chart, inputSeries, formulaInfo.InputFields);
  1049. SeriesFieldList outputFields = SeriesFieldList.FromString(this.Common.Chart, outputSeries, formulaInfo.OutputFields);
  1050. if (inputFields != null) inputSeries = inputFields.ToString();
  1051. if (outputFields != null) outputSeries = outputFields.ToString();
  1052. Formula(formulaName.ToString(), parameters, inputSeries, outputSeries);
  1053. }
  1054. #endregion
  1055. #region Data Formulas properties
  1056. /// <summary>
  1057. /// Gets or sets a flag which indicates whether
  1058. /// empty points are ignored while performing calculations;
  1059. /// otherwise, empty points are treated as zeros.
  1060. /// </summary>
  1061. public bool IsEmptyPointIgnored
  1062. {
  1063. get
  1064. {
  1065. return _isEmptyPointIgnored;
  1066. }
  1067. set
  1068. {
  1069. _isEmptyPointIgnored = value;
  1070. }
  1071. }
  1072. /// <summary>
  1073. /// Gets or sets a flag which indicates whether
  1074. /// to start formulas like rolling average from zero.
  1075. /// </summary>
  1076. public bool IsStartFromFirst
  1077. {
  1078. get
  1079. {
  1080. return bool.Parse(_extraParameters[0]);
  1081. }
  1082. set
  1083. {
  1084. if (value)
  1085. _extraParameters[0] = true.ToString(System.Globalization.CultureInfo.InvariantCulture);
  1086. else
  1087. _extraParameters[0] = false.ToString(System.Globalization.CultureInfo.InvariantCulture);
  1088. }
  1089. }
  1090. /// <summary>
  1091. /// Returns a reference to the statistical utility class.
  1092. /// </summary>
  1093. public StatisticFormula Statistics
  1094. {
  1095. get
  1096. {
  1097. return _statistics;
  1098. }
  1099. }
  1100. #endregion
  1101. }
  1102. }