klinger.src.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /**
  2. * @license Highcharts Stock JS v9.1.1 (2021-06-04)
  3. *
  4. * Indicator series type for Highcharts Stock
  5. *
  6. * (c) 2010-2021 Karol Kolodziej
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/indicators/klinger', ['highcharts', 'highcharts/modules/stock'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'Mixins/IndicatorRequired.js', [_modules['Core/Utilities.js']], function (U) {
  32. /**
  33. *
  34. * (c) 2010-2021 Daniel Studencki
  35. *
  36. * License: www.highcharts.com/license
  37. *
  38. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  39. *
  40. * */
  41. var error = U.error;
  42. /* eslint-disable no-invalid-this, valid-jsdoc */
  43. var requiredIndicatorMixin = {
  44. /**
  45. * Check whether given indicator is loaded,
  46. else throw error.
  47. * @private
  48. * @param {Highcharts.Indicator} indicator
  49. * Indicator constructor function.
  50. * @param {string} requiredIndicator
  51. * Required indicator type.
  52. * @param {string} type
  53. * Type of indicator where function was called (parent).
  54. * @param {Highcharts.IndicatorCallbackFunction} callback
  55. * Callback which is triggered if the given indicator is loaded.
  56. * Takes indicator as an argument.
  57. * @param {string} errMessage
  58. * Error message that will be logged in console.
  59. * @return {boolean}
  60. * Returns false when there is no required indicator loaded.
  61. */
  62. isParentLoaded: function (indicator,
  63. requiredIndicator,
  64. type,
  65. callback,
  66. errMessage) {
  67. if (indicator) {
  68. return callback ? callback(indicator) : true;
  69. }
  70. error(errMessage || this.generateMessage(type, requiredIndicator));
  71. return false;
  72. },
  73. /**
  74. * @private
  75. * @param {string} indicatorType
  76. * Indicator type
  77. * @param {string} required
  78. * Required indicator
  79. * @return {string}
  80. * Error message
  81. */
  82. generateMessage: function (indicatorType, required) {
  83. return 'Error: "' + indicatorType +
  84. '" indicator type requires "' + required +
  85. '" indicator loaded before. Please read docs: ' +
  86. 'https://api.highcharts.com/highstock/plotOptions.' +
  87. indicatorType;
  88. }
  89. };
  90. return requiredIndicatorMixin;
  91. });
  92. _registerModule(_modules, 'Mixins/MultipleLines.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) {
  93. /**
  94. *
  95. * (c) 2010-2021 Wojciech Chmiel
  96. *
  97. * License: www.highcharts.com/license
  98. *
  99. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  100. *
  101. * */
  102. var defined = U.defined,
  103. error = U.error,
  104. merge = U.merge;
  105. var SMA = H.seriesTypes.sma;
  106. /**
  107. * Mixin useful for all indicators that have more than one line.
  108. * Merge it with your implementation where you will provide
  109. * getValues method appropriate to your indicator and pointArrayMap,
  110. * pointValKey, linesApiNames properites. Notice that pointArrayMap
  111. * should be consistent with amount of lines calculated in getValues method.
  112. *
  113. * @private
  114. * @mixin multipleLinesMixin
  115. */
  116. var multipleLinesMixin = {
  117. /* eslint-disable valid-jsdoc */
  118. /**
  119. * Lines ids. Required to plot appropriate amount of lines.
  120. * Notice that pointArrayMap should have more elements than
  121. * linesApiNames, because it contains main line and additional lines ids.
  122. * Also it should be consistent with amount of lines calculated in
  123. * getValues method from your implementation.
  124. *
  125. * @private
  126. * @name multipleLinesMixin.pointArrayMap
  127. * @type {Array<string>}
  128. */
  129. pointArrayMap: ['top', 'bottom'],
  130. /**
  131. * Main line id.
  132. *
  133. * @private
  134. * @name multipleLinesMixin.pointValKey
  135. * @type {string}
  136. */
  137. pointValKey: 'top',
  138. /**
  139. * Additional lines DOCS names. Elements of linesApiNames array should
  140. * be consistent with DOCS line names defined in your implementation.
  141. * Notice that linesApiNames should have decreased amount of elements
  142. * relative to pointArrayMap (without pointValKey).
  143. *
  144. * @private
  145. * @name multipleLinesMixin.linesApiNames
  146. * @type {Array<string>}
  147. */
  148. linesApiNames: ['bottomLine'],
  149. /**
  150. * Create translatedLines Collection based on pointArrayMap.
  151. *
  152. * @private
  153. * @function multipleLinesMixin.getTranslatedLinesNames
  154. * @param {string} [excludedValue]
  155. * Main line id
  156. * @return {Array<string>}
  157. * Returns translated lines names without excluded value.
  158. */
  159. getTranslatedLinesNames: function (excludedValue) {
  160. var translatedLines = [];
  161. (this.pointArrayMap || []).forEach(function (propertyName) {
  162. if (propertyName !== excludedValue) {
  163. translatedLines.push('plot' +
  164. propertyName.charAt(0).toUpperCase() +
  165. propertyName.slice(1));
  166. }
  167. });
  168. return translatedLines;
  169. },
  170. /**
  171. * @private
  172. * @function multipleLinesMixin.toYData
  173. * @param {Highcharts.Point} point
  174. * Indicator point
  175. * @return {Array<number>}
  176. * Returns point Y value for all lines
  177. */
  178. toYData: function (point) {
  179. var pointColl = [];
  180. (this.pointArrayMap || []).forEach(function (propertyName) {
  181. pointColl.push(point[propertyName]);
  182. });
  183. return pointColl;
  184. },
  185. /**
  186. * Add lines plot pixel values.
  187. *
  188. * @private
  189. * @function multipleLinesMixin.translate
  190. * @return {void}
  191. */
  192. translate: function () {
  193. var indicator = this,
  194. pointArrayMap = indicator.pointArrayMap,
  195. LinesNames = [],
  196. value;
  197. LinesNames = indicator.getTranslatedLinesNames();
  198. SMA.prototype.translate.apply(indicator, arguments);
  199. indicator.points.forEach(function (point) {
  200. pointArrayMap.forEach(function (propertyName, i) {
  201. value = point[propertyName];
  202. if (value !== null) {
  203. point[LinesNames[i]] = indicator.yAxis.toPixels(value, true);
  204. }
  205. });
  206. });
  207. },
  208. /**
  209. * Draw main and additional lines.
  210. *
  211. * @private
  212. * @function multipleLinesMixin.drawGraph
  213. * @return {void}
  214. */
  215. drawGraph: function () {
  216. var indicator = this,
  217. pointValKey = indicator.pointValKey,
  218. linesApiNames = indicator.linesApiNames,
  219. mainLinePoints = indicator.points,
  220. pointsLength = mainLinePoints.length,
  221. mainLineOptions = indicator.options,
  222. mainLinePath = indicator.graph,
  223. gappedExtend = {
  224. options: {
  225. gapSize: mainLineOptions.gapSize
  226. }
  227. },
  228. // additional lines point place holders:
  229. secondaryLines = [],
  230. secondaryLinesNames = indicator.getTranslatedLinesNames(pointValKey),
  231. point;
  232. // Generate points for additional lines:
  233. secondaryLinesNames.forEach(function (plotLine, index) {
  234. // create additional lines point place holders
  235. secondaryLines[index] = [];
  236. while (pointsLength--) {
  237. point = mainLinePoints[pointsLength];
  238. secondaryLines[index].push({
  239. x: point.x,
  240. plotX: point.plotX,
  241. plotY: point[plotLine],
  242. isNull: !defined(point[plotLine])
  243. });
  244. }
  245. pointsLength = mainLinePoints.length;
  246. });
  247. // Modify options and generate additional lines:
  248. linesApiNames.forEach(function (lineName, i) {
  249. if (secondaryLines[i]) {
  250. indicator.points = secondaryLines[i];
  251. if (mainLineOptions[lineName]) {
  252. indicator.options = merge(mainLineOptions[lineName].styles, gappedExtend);
  253. }
  254. else {
  255. error('Error: "There is no ' + lineName +
  256. ' in DOCS options declared. Check if linesApiNames' +
  257. ' are consistent with your DOCS line names."' +
  258. ' at mixin/multiple-line.js:34');
  259. }
  260. indicator.graph = indicator['graph' + lineName];
  261. SMA.prototype.drawGraph.call(indicator);
  262. // Now save lines:
  263. indicator['graph' + lineName] = indicator.graph;
  264. }
  265. else {
  266. error('Error: "' + lineName + ' doesn\'t have equivalent ' +
  267. 'in pointArrayMap. To many elements in linesApiNames ' +
  268. 'relative to pointArrayMap."');
  269. }
  270. });
  271. // Restore options and draw a main line:
  272. indicator.points = mainLinePoints;
  273. indicator.options = mainLineOptions;
  274. indicator.graph = mainLinePath;
  275. SMA.prototype.drawGraph.call(indicator);
  276. }
  277. };
  278. return multipleLinesMixin;
  279. });
  280. _registerModule(_modules, 'Stock/Indicators/Klinger/KlingerIndicator.js', [_modules['Mixins/IndicatorRequired.js'], _modules['Mixins/MultipleLines.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (RequiredIndicatorMixin, MultipleLinesMixin, SeriesRegistry, U) {
  281. /* *
  282. *
  283. * License: www.highcharts.com/license
  284. *
  285. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  286. *
  287. * */
  288. var __extends = (this && this.__extends) || (function () {
  289. var extendStatics = function (d,
  290. b) {
  291. extendStatics = Object.setPrototypeOf ||
  292. ({ __proto__: [] } instanceof Array && function (d,
  293. b) { d.__proto__ = b; }) ||
  294. function (d,
  295. b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  296. return extendStatics(d, b);
  297. };
  298. return function (d, b) {
  299. extendStatics(d, b);
  300. function __() { this.constructor = d; }
  301. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  302. };
  303. })();
  304. var _a = SeriesRegistry.seriesTypes,
  305. SMAIndicator = _a.sma,
  306. EMAIndicator = _a.ema;
  307. var correctFloat = U.correctFloat,
  308. error = U.error,
  309. extend = U.extend,
  310. isArray = U.isArray,
  311. merge = U.merge;
  312. /**
  313. * The Klinger oscillator series type.
  314. *
  315. * @private
  316. * @class
  317. * @name Highcharts.seriesTypes.klinger
  318. *
  319. * @augments Highcharts.Series
  320. */
  321. var KlingerIndicator = /** @class */ (function (_super) {
  322. __extends(KlingerIndicator, _super);
  323. function KlingerIndicator() {
  324. var _this = _super !== null && _super.apply(this,
  325. arguments) || this;
  326. /* *
  327. *
  328. * Properties
  329. *
  330. * */
  331. _this.data = void 0;
  332. _this.points = void 0;
  333. _this.options = void 0;
  334. _this.volumeSeries = void 0;
  335. return _this;
  336. }
  337. /* *
  338. *
  339. * Functions
  340. *
  341. * */
  342. KlingerIndicator.prototype.init = function () {
  343. var args = arguments,
  344. ctx = this;
  345. // Check if the EMA module is added.
  346. RequiredIndicatorMixin.isParentLoaded(EMAIndicator, 'ema', ctx.type, function (indicator) {
  347. indicator.prototype.init.apply(ctx, args);
  348. return;
  349. });
  350. };
  351. KlingerIndicator.prototype.calculateTrend = function (yVal, i) {
  352. var isUpward = yVal[i][1] + yVal[i][2] + yVal[i][3] >
  353. yVal[i - 1][1] + yVal[i - 1][2] + yVal[i - 1][3];
  354. return isUpward ? 1 : -1;
  355. };
  356. // Checks if the series and volumeSeries are accessible, number of
  357. // points.x is longer than period, is series has OHLC data
  358. KlingerIndicator.prototype.isValidData = function (firstYVal) {
  359. var chart = this.chart,
  360. options = this.options,
  361. series = this.linkedParent,
  362. isSeriesOHLC = isArray(firstYVal) &&
  363. firstYVal.length === 4,
  364. volumeSeries = this.volumeSeries ||
  365. (this.volumeSeries =
  366. chart.get(options.params.volumeSeriesID));
  367. if (!volumeSeries) {
  368. error('Series ' +
  369. options.params.volumeSeriesID +
  370. ' not found! Check `volumeSeriesID`.', true, series.chart);
  371. }
  372. var isLengthValid = [series,
  373. volumeSeries].every(function (series) {
  374. return series && series.xData && series.xData.length >=
  375. options.params.slowAvgPeriod;
  376. });
  377. return !!(isLengthValid && isSeriesOHLC);
  378. };
  379. KlingerIndicator.prototype.getCM = function (previousCM, DM, trend, previousTrend, prevoiusDM) {
  380. return correctFloat(DM + (trend === previousTrend ? previousCM : prevoiusDM));
  381. };
  382. KlingerIndicator.prototype.getDM = function (high, low) {
  383. return correctFloat(high - low);
  384. };
  385. KlingerIndicator.prototype.getVolumeForce = function (yVal) {
  386. var volumeForce = [];
  387. var CM = 0, // cumulative measurement
  388. DM, // daily measurement
  389. force,
  390. i = 1, // start from second point
  391. previousCM = 0,
  392. previousDM = yVal[0][1] - yVal[0][2], // initial DM
  393. previousTrend = 0,
  394. trend;
  395. for (i; i < yVal.length; i++) {
  396. trend = this.calculateTrend(yVal, i);
  397. DM = this.getDM(yVal[i][1], yVal[i][2]);
  398. // For the first iteration when the previousTrend doesn't exist,
  399. // previousCM doesn't exist either, but it doesn't matter becouse
  400. // it's filltered out in the getCM method in else statement,
  401. // (in this iteration, previousCM can be raplaced with the DM).
  402. CM = this.getCM(previousCM, DM, trend, previousTrend, previousDM);
  403. force = this.volumeSeries.yData[i] *
  404. trend * Math.abs(2 * ((DM / CM) - 1)) * 100;
  405. volumeForce.push([force]);
  406. // Before next iteration, assign the current as the previous.
  407. previousTrend = trend;
  408. previousCM = CM;
  409. previousDM = DM;
  410. }
  411. return volumeForce;
  412. };
  413. KlingerIndicator.prototype.getEMA = function (yVal, prevEMA, SMA, EMApercent, index, i, xVal) {
  414. return EMAIndicator.prototype.calculateEma(xVal || [], yVal, typeof i === 'undefined' ? 1 : i, EMApercent, prevEMA, typeof index === 'undefined' ? -1 : index, SMA);
  415. };
  416. KlingerIndicator.prototype.getSMA = function (period, index, values) {
  417. return EMAIndicator.prototype
  418. .accumulatePeriodPoints(period, index, values) / period;
  419. };
  420. KlingerIndicator.prototype.getValues = function (series, params) {
  421. var Klinger = [],
  422. xVal = series.xData,
  423. yVal = series.yData,
  424. xData = [],
  425. yData = [],
  426. calcSingal = [];
  427. var KO,
  428. i = 0,
  429. fastEMA = 0,
  430. slowEMA,
  431. // signalEMA: number|undefined = void 0,
  432. previousFastEMA = void 0,
  433. previousSlowEMA = void 0,
  434. signal = null;
  435. // If the necessary conditions are not fulfilled, don't proceed.
  436. if (!this.isValidData(yVal[0])) {
  437. return;
  438. }
  439. // Calculate the Volume Force array.
  440. var volumeForce = this.getVolumeForce(yVal);
  441. // Calculate SMA for the first points.
  442. var SMAFast = this.getSMA(params.fastAvgPeriod, 0,
  443. volumeForce),
  444. SMASlow = this.getSMA(params.slowAvgPeriod, 0,
  445. volumeForce);
  446. // Calculate EMApercent for the first points.
  447. var fastEMApercent = 2 / (params.fastAvgPeriod + 1),
  448. slowEMApercent = 2 / (params.slowAvgPeriod + 1);
  449. // Calculate KO
  450. for (i; i < yVal.length; i++) {
  451. // Get EMA for fast period.
  452. if (i >= params.fastAvgPeriod) {
  453. fastEMA = this.getEMA(volumeForce, previousFastEMA, SMAFast, fastEMApercent, 0, i, xVal)[1];
  454. previousFastEMA = fastEMA;
  455. }
  456. // Get EMA for slow period.
  457. if (i >= params.slowAvgPeriod) {
  458. slowEMA = this.getEMA(volumeForce, previousSlowEMA, SMASlow, slowEMApercent, 0, i, xVal)[1];
  459. previousSlowEMA = slowEMA;
  460. KO = correctFloat(fastEMA - slowEMA);
  461. calcSingal.push(KO);
  462. // Calculate signal SMA
  463. if (calcSingal.length >= params.signalPeriod) {
  464. signal = calcSingal.slice(-params.signalPeriod)
  465. .reduce(function (prev, curr) {
  466. return prev + curr;
  467. }) / params.signalPeriod;
  468. }
  469. Klinger.push([xVal[i], KO, signal]);
  470. xData.push(xVal[i]);
  471. yData.push([KO, signal]);
  472. }
  473. }
  474. return {
  475. values: Klinger,
  476. xData: xData,
  477. yData: yData
  478. };
  479. };
  480. /**
  481. * Klinger oscillator. This series requires the `linkedTo` option to be set
  482. * and should be loaded after the `stock/indicators/indicators.js` file.
  483. *
  484. * @sample stock/indicators/klinger
  485. * Klinger oscillator
  486. *
  487. * @extends plotOptions.sma
  488. * @since 9.1.0
  489. * @product highstock
  490. * @requires stock/indicators/indicators
  491. * @requires stock/indicators/klinger
  492. * @optionparent plotOptions.klinger
  493. */
  494. KlingerIndicator.defaultOptions = merge(SMAIndicator.defaultOptions, {
  495. /**
  496. * Paramters used in calculation of Klinger Oscillator.
  497. *
  498. * @excluding index, period
  499. */
  500. params: {
  501. /**
  502. * The fast period for indicator calculations.
  503. */
  504. fastAvgPeriod: 34,
  505. /**
  506. * The slow period for indicator calculations.
  507. */
  508. slowAvgPeriod: 55,
  509. /**
  510. * The base period for signal calculations.
  511. */
  512. signalPeriod: 13,
  513. /**
  514. * The id of another series to use its data as volume data for the
  515. * indiator calculation.
  516. */
  517. volumeSeriesID: 'volume'
  518. },
  519. signalLine: {
  520. /**
  521. * Styles for a signal line.
  522. */
  523. styles: {
  524. /**
  525. * Pixel width of the line.
  526. */
  527. lineWidth: 1,
  528. /**
  529. * Color of the line. If not set, it's inherited from
  530. * [plotOptions.klinger.color
  531. * ](#plotOptions.klinger.color).
  532. *
  533. * @type {Highcharts.ColorString}
  534. */
  535. lineColor: '#ff0000'
  536. }
  537. },
  538. dataGrouping: {
  539. approximation: 'averages'
  540. },
  541. tooltip: {
  542. pointFormat: '<span style="color: {point.color}">\u25CF</span><b> {series.name}</b><br/>' +
  543. '<span style="color: {point.color}">Klinger</span>: {point.y}<br/>' +
  544. '<span style="color: {point.series.options.signalLine.styles.lineColor}">Signal</span>' +
  545. ': {point.signal}<br/>'
  546. }
  547. });
  548. return KlingerIndicator;
  549. }(SMAIndicator));
  550. extend(KlingerIndicator.prototype, {
  551. linesApiNames: ['signalLine'],
  552. nameBase: 'Klinger',
  553. nameComponents: ['fastAvgPeriod', 'slowAvgPeriod'],
  554. pointArrayMap: ['y', 'signal'],
  555. parallelArrays: ['x', 'y', 'signal'],
  556. pointValKey: 'y',
  557. drawGraph: MultipleLinesMixin.drawGraph,
  558. getTranslatedLinesNames: MultipleLinesMixin.getTranslatedLinesNames,
  559. translate: MultipleLinesMixin.translate,
  560. toYData: MultipleLinesMixin.toYData
  561. });
  562. SeriesRegistry.registerSeriesType('klinger', KlingerIndicator);
  563. /* *
  564. *
  565. * Default Export
  566. *
  567. * */
  568. /**
  569. * A Klinger oscillator. If the [type](#series.klinger.type)
  570. * option is not specified, it is inherited from [chart.type](#chart.type).
  571. *
  572. * @extends series,plotOptions.klinger
  573. * @since 9.1.0
  574. * @product highstock
  575. * @requires stock/indicators/indicators
  576. * @requires stock/indicators/ema
  577. * @requires stock/indicators/klinger
  578. * @apioption series.klinger
  579. */
  580. ''; // to include the above in the js output
  581. return KlingerIndicator;
  582. });
  583. _registerModule(_modules, 'masters/indicators/klinger.src.js', [], function () {
  584. });
  585. }));