/** * @license Highstock JS v7.2.0 (2019-09-03) * * All technical indicators for Highstock * * (c) 2010-2019 Pawel Fus * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/indicators/indicators-all', ['highcharts', 'highcharts/modules/stock'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); } } _registerModule(_modules, 'mixins/indicator-required.js', [_modules['parts/Globals.js']], function (H) { /** * * (c) 2010-2019 Daniel Studencki * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var error = H.error; /* eslint-disable no-invalid-this, valid-jsdoc */ var requiredIndicatorMixin = { /** * Check whether given indicator is loaded, else throw error. * @private * @param {Highcharts.Indicator} indicator * Indicator constructor function. * @param {string} requiredIndicator * Required indicator type. * @param {string} type * Type of indicator where function was called (parent). * @param {Highcharts.IndicatorConstructorFunction} callback * Callback which is triggered if the given indicator is loaded. * Takes indicator as an argument. * @param {string} errMessage * Error message that will be logged in console. * @return {boolean} * Returns false when there is no required indicator loaded. */ isParentLoaded: function (indicator, requiredIndicator, type, callback, errMessage) { if (indicator) { return callback ? callback(indicator) : true; } error(errMessage || this.generateMessage(type, requiredIndicator)); return false; }, /** * @private * @param {string} indicatorType * Indicator type * @param {string} required * Required indicator * @return {string} * Error message */ generateMessage: function (indicatorType, required) { return 'Error: "' + indicatorType + '" indicator type requires "' + required + '" indicator loaded before. Please read docs: ' + 'https://api.highcharts.com/highstock/plotOptions.' + indicatorType; } }; return requiredIndicatorMixin; }); _registerModule(_modules, 'indicators/indicators.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/indicator-required.js']], function (H, U, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray, splat = U.splat; var pick = H.pick, error = H.error, Series = H.Series, addEvent = H.addEvent, seriesType = H.seriesType, seriesTypes = H.seriesTypes, ohlcProto = H.seriesTypes.ohlc.prototype, generateMessage = requiredIndicatorMixin.generateMessage; /** * The parameter allows setting line series type and use OHLC indicators. Data * in OHLC format is required. * * @sample {highstock} stock/indicators/use-ohlc-data * Plot line on Y axis * * @type {boolean} * @product highstock * @apioption plotOptions.line.useOhlcData */ addEvent(H.Series, 'init', function (eventOptions) { var series = this, options = eventOptions.options; if ( options.useOhlcData && options.id !== 'highcharts-navigator-series' ) { H.extend(series, { pointValKey: ohlcProto.pointValKey, keys: ohlcProto.keys, pointArrayMap: ohlcProto.pointArrayMap, toYData: ohlcProto.toYData }); } }); addEvent(Series, 'afterSetOptions', function (e) { var options = e.options, dataGrouping = options.dataGrouping; if ( dataGrouping && options.useOhlcData && options.id !== 'highcharts-navigator-series' ) { dataGrouping.approximation = 'ohlc'; } }); /** * The SMA series type. * * @private * @class * @name Highcharts.seriesTypes.sma * * @augments Highcharts.Series */ seriesType( 'sma', 'line', /** * Simple moving average indicator (SMA). This series requires `linkedTo` * option to be set. * * @sample stock/indicators/sma * Simple moving average indicator * * @extends plotOptions.line * @since 6.0.0 * @excluding allAreas, colorAxis, dragDrop, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking, useOhlcData * @product highstock * @optionparent plotOptions.sma */ { /** * The name of the series as shown in the legend, tooltip etc. If not * set, it will be based on a technical indicator type and default * params. * * @type {string} */ name: undefined, tooltip: { /** * Number of decimals in indicator series. */ valueDecimals: 4 }, /** * The main series ID that indicator will be based on. Required for this * indicator. * * @type {string} */ linkedTo: undefined, /** * Whether to compare indicator to the main series values * or indicator values. * * @sample {highstock} stock/plotoptions/series-comparetomain/ * Difference between comparing SMA values to the main series * and its own values. * * @type {boolean} */ compareToMain: false, /** * Paramters used in calculation of regression series' points. */ params: { /** * The point index which indicator calculations will base. For * example using OHLC data, index=2 means the indicator will be * calculated using Low values. */ index: 0, /** * The base period for indicator calculations. This is the number of * data points which are taken into account for the indicator * calculations. */ period: 14 } }, /** * @lends Highcharts.Series.prototype */ { processData: function () { var series = this, compareToMain = series.options.compareToMain, linkedParent = series.linkedParent; Series.prototype.processData.apply(series, arguments); if (linkedParent && linkedParent.compareValue && compareToMain) { series.compareValue = linkedParent.compareValue; } }, bindTo: { series: true, eventName: 'updatedData' }, hasDerivedData: true, useCommonDataGrouping: true, nameComponents: ['period'], nameSuffixes: [], // e.g. Zig Zag uses extra '%'' in the legend name calculateOn: 'init', // Defines on which other indicators is this indicator based on. requiredIndicators: [], requireIndicators: function () { var obj = { allLoaded: true }; // Check whether all required indicators are loaded, else return // the object with missing indicator's name. this.requiredIndicators.forEach(function (indicator) { if (seriesTypes[indicator]) { seriesTypes[indicator].prototype.requireIndicators(); } else { obj.allLoaded = false; obj.needed = indicator; } }); return obj; }, init: function (chart, options) { var indicator = this, requiredIndicators = indicator.requireIndicators(); // Check whether all required indicators are loaded. if (!requiredIndicators.allLoaded) { return error( generateMessage(indicator.type, requiredIndicators.needed) ); } Series.prototype.init.call( indicator, chart, options ); // Make sure we find series which is a base for an indicator chart.linkSeries(); indicator.dataEventsToUnbind = []; function recalculateValues() { var oldData = indicator.points || [], oldDataLength = (indicator.xData || []).length, processedData = indicator.getValues( indicator.linkedParent, indicator.options.params ) || { values: [], xData: [], yData: [] }, croppedDataValues = [], overwriteData = true, oldFirstPointIndex, oldLastPointIndex, croppedData, min, max, i; // We need to update points to reflect changes in all, // x and y's, values. However, do it only for non-grouped // data - grouping does it for us (#8572) if ( oldDataLength && !indicator.hasGroupedData && indicator.visible && indicator.points ) { // When data is cropped update only avaliable points (#9493) if (indicator.cropped) { if (indicator.xAxis) { min = indicator.xAxis.min; max = indicator.xAxis.max; } croppedData = indicator.cropData( processedData.xData, processedData.yData, min, max ); for (i = 0; i < croppedData.xData.length; i++) { // (#10774) croppedDataValues.push([ croppedData.xData[i] ].concat( splat(croppedData.yData[i]) )); } oldFirstPointIndex = processedData.xData.indexOf( indicator.xData[0] ); oldLastPointIndex = processedData.xData.indexOf( indicator.xData[indicator.xData.length - 1] ); // Check if indicator points should be shifted (#8572) if ( oldFirstPointIndex === -1 && oldLastPointIndex === processedData.xData.length - 2 ) { if (croppedDataValues[0][0] === oldData[0].x) { croppedDataValues.shift(); } } indicator.updateData(croppedDataValues); // Omit addPoint() and removePoint() cases } else if ( processedData.xData.length !== oldDataLength - 1 && processedData.xData.length !== oldDataLength + 1 ) { overwriteData = false; indicator.updateData(processedData.values); } } if (overwriteData) { indicator.xData = processedData.xData; indicator.yData = processedData.yData; indicator.options.data = processedData.values; } // Removal of processedXData property is required because on // first translate processedXData array is empty if (indicator.bindTo.series === false) { delete indicator.processedXData; indicator.isDirty = true; indicator.redraw(); } indicator.isDirtyData = false; } if (!indicator.linkedParent) { return error( 'Series ' + indicator.options.linkedTo + ' not found! Check `linkedTo`.', false, chart ); } indicator.dataEventsToUnbind.push( addEvent( indicator.bindTo.series ? indicator.linkedParent : indicator.linkedParent.xAxis, indicator.bindTo.eventName, recalculateValues ) ); if (indicator.calculateOn === 'init') { recalculateValues(); } else { var unbinder = addEvent( indicator.chart, indicator.calculateOn, function () { recalculateValues(); // Call this just once, on init unbinder(); } ); } return indicator; }, getName: function () { var name = this.name, params = []; if (!name) { (this.nameComponents || []).forEach( function (component, index) { params.push( this.options.params[component] + pick(this.nameSuffixes[index], '') ); }, this ); name = (this.nameBase || this.type.toUpperCase()) + (this.nameComponents ? ' (' + params.join(', ') + ')' : ''); } return name; }, getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal.length, range = 0, sum = 0, SMA = [], xData = [], yData = [], index = -1, i, SMAPoint; if (xVal.length < period) { return false; } // Switch index for OHLC / Candlestick / Arearange if (isArray(yVal[0])) { index = params.index ? params.index : 0; } // Accumulate first N-points while (range < period - 1) { sum += index < 0 ? yVal[range] : yVal[range][index]; range++; } // Calculate value one-by-one for each period in visible data for (i = range; i < yValLen; i++) { sum += index < 0 ? yVal[i] : yVal[i][index]; SMAPoint = [xVal[i], sum / period]; SMA.push(SMAPoint); xData.push(SMAPoint[0]); yData.push(SMAPoint[1]); sum -= index < 0 ? yVal[i - range] : yVal[i - range][index]; } return { values: SMA, xData: xData, yData: yData }; }, destroy: function () { this.dataEventsToUnbind.forEach(function (unbinder) { unbinder(); }); Series.prototype.destroy.call(this); } } ); /** * A `SMA` series. If the [type](#series.sma.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.sma * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL, useOhlcData * @apioption series.sma */ }); _registerModule(_modules, 'indicators/accumulation-distribution.src.js', [_modules['parts/Globals.js']], function (H) { /* * * * License: www.highcharts.com/license * * */ var seriesType = H.seriesType; // Utils: function populateAverage(xVal, yVal, yValVolume, i) { var high = yVal[i][1], low = yVal[i][2], close = yVal[i][3], volume = yValVolume[i], adY = close === high && close === low || high === low ? 0 : ((2 * close - low - high) / (high - low)) * volume, adX = xVal[i]; return [adX, adY]; } /** * The AD series type. * * @private * @class * @name Highcharts.seriesTypes.ad * * @augments Highcharts.Series */ seriesType('ad', 'sma', /** * Accumulation Distribution (AD). This series requires `linkedTo` option to * be set. * * @sample stock/indicators/accumulation-distribution * Accumulation/Distribution indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.ad */ { params: { /** * The id of volume series which is mandatory. * For example using OHLC data, volumeSeriesID='volume' means * the indicator will be calculated using OHLC and volume values. * * @since 6.0.0 */ volumeSeriesID: 'volume' } }, /** * @lends Highcharts.Series# */ { nameComponents: false, nameBase: 'Accumulation/Distribution', getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, volumeSeriesID = params.volumeSeriesID, volumeSeries = series.chart.get(volumeSeriesID), yValVolume = volumeSeries && volumeSeries.yData, yValLen = yVal ? yVal.length : 0, AD = [], xData = [], yData = [], len, i, ADPoint; if (xVal.length <= period && yValLen && yVal[0].length !== 4) { return false; } if (!volumeSeries) { return H.error( 'Series ' + volumeSeriesID + ' not found! Check `volumeSeriesID`.', true, series.chart ); } // i = period <-- skip first N-points // Calculate value one-by-one for each period in visible data for (i = period; i < yValLen; i++) { len = AD.length; ADPoint = populateAverage(xVal, yVal, yValVolume, i, period); if (len > 0) { ADPoint[1] += AD[len - 1][1]; } AD.push(ADPoint); xData.push(ADPoint[0]); yData.push(ADPoint[1]); } return { values: AD, xData: xData, yData: yData }; } }); /** * A `AD` series. If the [type](#series.ad.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ad * @since 6.0.0 * @excluding dataParser, dataURL * @product highstock * @apioption series.ad */ }); _registerModule(_modules, 'indicators/ao.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var correctFloat = H.correctFloat, noop = H.noop; /** * The AO series type * * @private * @class * @name Highcharts.seriesTypes.ao * * @augments Highcharts.Series */ H.seriesType( 'ao', 'sma', /** * Awesome Oscillator. This series requires the `linkedTo` option to * be set and should be loaded after the `stock/indicators/indicators.js` * * @sample {highstock} stock/indicators/ao * Awesome * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, joinBy, keys, navigatorOptions, * params, pointInterval, pointIntervalUnit, pointPlacement, * pointRange, pointStart, showInNavigator, stacking * @optionparent plotOptions.ao */ { /** * Color of the Awesome oscillator series bar that is greater than the * previous one. Note that if a `color` is defined, the `color` * takes precedence and the `greaterBarColor` is ignored. * * @sample {highstock} stock/indicators/ao/ * greaterBarColor * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 7.0.0 */ greaterBarColor: '#06B535', /** * Color of the Awesome oscillator series bar that is lower than the * previous one. Note that if a `color` is defined, the `color` * takes precedence and the `lowerBarColor` is ignored. * * @sample {highstock} stock/indicators/ao/ * lowerBarColor * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 7.0.0 */ lowerBarColor: '#F21313', threshold: 0, groupPadding: 0.2, pointPadding: 0.2, states: { hover: { halo: { size: 0 } } } }, /** * @lends Highcharts.Series# */ { nameBase: 'AO', nameComponents: false, // Columns support: markerAttribs: noop, getColumnMetrics: H.seriesTypes.column.prototype.getColumnMetrics, crispCol: H.seriesTypes.column.prototype.crispCol, translate: H.seriesTypes.column.prototype.translate, drawPoints: H.seriesTypes.column.prototype.drawPoints, drawGraph: function () { var indicator = this, options = indicator.options, points = indicator.points, userColor = indicator.userOptions.color, positiveColor = options.greaterBarColor, negativeColor = options.lowerBarColor, firstPoint = points[0], i; if (!userColor && firstPoint) { firstPoint.color = positiveColor; for (i = 1; i < points.length; i++) { if (points[i].y > points[i - 1].y) { points[i].color = positiveColor; } else if (points[i].y < points[i - 1].y) { points[i].color = negativeColor; } else { points[i].color = points[i - 1].color; } } } }, getValues: function (series) { var shortPeriod = 5, longPeriod = 34, xVal = series.xData || [], yVal = series.yData || [], yValLen = yVal.length, AO = [], // 0- date, 1- Awesome Oscillator xData = [], yData = [], high = 1, low = 2, shortSum = 0, longSum = 0, shortSMA, // Shorter Period SMA longSMA, // Longer Period SMA awesome, shortLastIndex, longLastIndex, price, i, j; if ( xVal.length <= longPeriod || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } for (i = 0; i < longPeriod - 1; i++) { price = (yVal[i][high] + yVal[i][low]) / 2; if (i >= longPeriod - shortPeriod) { shortSum = correctFloat(shortSum + price); } longSum = correctFloat(longSum + price); } for (j = longPeriod - 1; j < yValLen; j++) { price = (yVal[j][high] + yVal[j][low]) / 2; shortSum = correctFloat(shortSum + price); longSum = correctFloat(longSum + price); shortSMA = shortSum / shortPeriod; longSMA = longSum / longPeriod; awesome = correctFloat(shortSMA - longSMA); AO.push([xVal[j], awesome]); xData.push(xVal[j]); yData.push(awesome); shortLastIndex = j + 1 - shortPeriod; longLastIndex = j + 1 - longPeriod; shortSum = correctFloat( shortSum - (yVal[shortLastIndex][high] + yVal[shortLastIndex][low]) / 2 ); longSum = correctFloat( longSum - (yVal[longLastIndex][high] + yVal[longLastIndex][low]) / 2 ); } return { values: AO, xData: xData, yData: yData }; } } ); /** * An `AO` series. If the [type](#series.ao.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ao * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, dataParser, dataURL, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.ao */ }); _registerModule(_modules, 'mixins/multipe-lines.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /** * * (c) 2010-2019 Wojciech Chmiel * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defined = U.defined; var each = H.each, merge = H.merge, error = H.error, SMA = H.seriesTypes.sma; /** * Mixin useful for all indicators that have more than one line. * Merge it with your implementation where you will provide * getValues method appropriate to your indicator and pointArrayMap, * pointValKey, linesApiNames properites. Notice that pointArrayMap * should be consistent with amount of lines calculated in getValues method. * * @private * @mixin multipleLinesMixin */ var multipleLinesMixin = { /* eslint-disable valid-jsdoc */ /** * Lines ids. Required to plot appropriate amount of lines. * Notice that pointArrayMap should have more elements than * linesApiNames, because it contains main line and additional lines ids. * Also it should be consistent with amount of lines calculated in * getValues method from your implementation. * * @private * @name multipleLinesMixin.pointArrayMap * @type {Array<string>} */ pointArrayMap: ['top', 'bottom'], /** * Main line id. * * @private * @name multipleLinesMixin.pointValKey * @type {string} */ pointValKey: 'top', /** * Additional lines DOCS names. Elements of linesApiNames array should * be consistent with DOCS line names defined in your implementation. * Notice that linesApiNames should have decreased amount of elements * relative to pointArrayMap (without pointValKey). * * @private * @name multipleLinesMixin.linesApiNames * @type {Array<string>} */ linesApiNames: ['bottomLine'], /** * Create translatedLines Collection based on pointArrayMap. * * @private * @function multipleLinesMixin.getTranslatedLinesNames * @param {string} [excludedValue] * Main line id * @return {Array<string>} * Returns translated lines names without excluded value. */ getTranslatedLinesNames: function (excludedValue) { var translatedLines = []; each(this.pointArrayMap, function (propertyName) { if (propertyName !== excludedValue) { translatedLines.push('plot' + propertyName.charAt(0).toUpperCase() + propertyName.slice(1)); } }); return translatedLines; }, /** * @private * @function multipleLinesMixin.toYData * @param {Highcharts.Point} point * Indicator point * @return {Array<number>} * Returns point Y value for all lines */ toYData: function (point) { var pointColl = []; each(this.pointArrayMap, function (propertyName) { pointColl.push(point[propertyName]); }); return pointColl; }, /** * Add lines plot pixel values. * * @private * @function multipleLinesMixin.translate * @return {void} */ translate: function () { var indicator = this, pointArrayMap = indicator.pointArrayMap, LinesNames = [], value; LinesNames = indicator.getTranslatedLinesNames(); SMA.prototype.translate.apply(indicator, arguments); each(indicator.points, function (point) { each(pointArrayMap, function (propertyName, i) { value = point[propertyName]; if (value !== null) { point[LinesNames[i]] = indicator.yAxis.toPixels(value, true); } }); }); }, /** * Draw main and additional lines. * * @private * @function multipleLinesMixin.drawGraph * @return {void} */ drawGraph: function () { var indicator = this, pointValKey = indicator.pointValKey, linesApiNames = indicator.linesApiNames, mainLinePoints = indicator.points, pointsLength = mainLinePoints.length, mainLineOptions = indicator.options, mainLinePath = indicator.graph, gappedExtend = { options: { gapSize: mainLineOptions.gapSize } }, // additional lines point place holders: secondaryLines = [], secondaryLinesNames = indicator.getTranslatedLinesNames(pointValKey), point; // Generate points for additional lines: each(secondaryLinesNames, function (plotLine, index) { // create additional lines point place holders secondaryLines[index] = []; while (pointsLength--) { point = mainLinePoints[pointsLength]; secondaryLines[index].push({ x: point.x, plotX: point.plotX, plotY: point[plotLine], isNull: !defined(point[plotLine]) }); } pointsLength = mainLinePoints.length; }); // Modify options and generate additional lines: each(linesApiNames, function (lineName, i) { if (secondaryLines[i]) { indicator.points = secondaryLines[i]; if (mainLineOptions[lineName]) { indicator.options = merge(mainLineOptions[lineName].styles, gappedExtend); } else { error('Error: "There is no ' + lineName + ' in DOCS options declared. Check if linesApiNames' + ' are consistent with your DOCS line names."' + ' at mixin/multiple-line.js:34'); } indicator.graph = indicator['graph' + lineName]; SMA.prototype.drawGraph.call(indicator); // Now save lines: indicator['graph' + lineName] = indicator.graph; } else { error('Error: "' + lineName + ' doesn\'t have equivalent ' + 'in pointArrayMap. To many elements in linesApiNames ' + 'relative to pointArrayMap."'); } }); // Restore options and draw a main line: indicator.points = mainLinePoints; indicator.options = mainLineOptions; indicator.graph = mainLinePath; SMA.prototype.drawGraph.call(indicator); } }; return multipleLinesMixin; }); _registerModule(_modules, 'indicators/aroon.src.js', [_modules['parts/Globals.js'], _modules['mixins/multipe-lines.js']], function (H, multipleLinesMixin) { /* * * * License: www.highcharts.com/license * * */ // Utils // Index of element with extreme value from array (min or max) function getExtremeIndexInArray(arr, extreme) { var extremeValue = arr[0], valueIndex = 0, i; for (i = 1; i < arr.length; i++) { if ( extreme === 'max' && arr[i] >= extremeValue || extreme === 'min' && arr[i] <= extremeValue ) { extremeValue = arr[i]; valueIndex = i; } } return valueIndex; } /** * The Aroon series type. * * @private * @class * @name Highcharts.seriesTypes.aroon * * @augments Highcharts.Series */ H.seriesType( 'aroon', 'sma', /** * Aroon. This series requires the `linkedTo` option to be * set and should be loaded after the `stock/indicators/indicators.js`. * * @sample {highstock} stock/indicators/aroon * Aroon * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @optionparent plotOptions.aroon */ { /** * Paramters used in calculation of aroon series points. * * @excluding periods, index */ params: { /** * Period for Aroon indicator */ period: 25 }, marker: { enabled: false }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span><b> {series.name}</b><br/>Aroon Up: {point.y}<br/>Aroon Down: {point.aroonDown}<br/>' }, /** * aroonDown line options. */ aroonDown: { /** * Styles for an aroonDown line. */ styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. If not set, it's inherited from * [plotOptions.aroon.color](#plotOptions.aroon.color). * * @type {Highcharts.ColorString} */ lineColor: undefined } }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ H.merge(multipleLinesMixin, { nameBase: 'Aroon', pointArrayMap: ['y', 'aroonDown'], pointValKey: 'y', linesApiNames: ['aroonDown'], getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, AR = [], // 0- date, 1- Aroon Up, 2- Aroon Down xData = [], yData = [], slicedY, low = 2, high = 1, aroonUp, aroonDown, xLow, xHigh, i; // For a N-period, we start from N-1 point, to calculate Nth point // That is why we later need to comprehend slice() elements list // with (+1) for (i = period - 1; i < yValLen; i++) { slicedY = yVal.slice(i - period + 1, i + 2); xLow = getExtremeIndexInArray(slicedY.map(function (elem) { return H.pick(elem[low], elem); }), 'min'); xHigh = getExtremeIndexInArray(slicedY.map(function (elem) { return H.pick(elem[high], elem); }), 'max'); aroonUp = (xHigh / period) * 100; aroonDown = (xLow / period) * 100; if (xVal[i + 1]) { AR.push([xVal[i + 1], aroonUp, aroonDown]); xData.push(xVal[i + 1]); yData.push([aroonUp, aroonDown]); } } return { values: AR, xData: xData, yData: yData }; } }) ); /** * A Aroon indicator. If the [type](#series.aroon.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.aroon * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.aroon */ }); _registerModule(_modules, 'indicators/aroon-oscillator.src.js', [_modules['parts/Globals.js'], _modules['mixins/multipe-lines.js'], _modules['mixins/indicator-required.js']], function (H, multipleLinesMixin, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var AROON = H.seriesTypes.aroon, requiredIndicator = requiredIndicatorMixin; /** * The Aroon Oscillator series type. * * @private * @class * @name Highcharts.seriesTypes.aroonoscillator * * @augments Highcharts.Series */ H.seriesType( 'aroonoscillator', 'aroon', /** * Aroon Oscillator. This series requires the `linkedTo` option to be set * and should be loaded after the `stock/indicators/indicators.js` and * `stock/indicators/aroon.js`. * * @sample {highstock} stock/indicators/aroon-oscillator * Aroon Oscillator * * @extends plotOptions.aroon * @since 7.0.0 * @product highstock * @excluding allAreas, aroonDown, colorAxis, compare, compareBase, * joinBy, keys, navigatorOptions, pointInterval, * pointIntervalUnit, pointPlacement, pointRange, pointStart, * showInNavigator, stacking * @optionparent plotOptions.aroonoscillator */ { /** * Paramters used in calculation of aroon oscillator series points. * * @excluding periods, index */ params: { /** * Period for Aroon Oscillator * * @since 7.0.0 * @product highstock */ period: 25 }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span><b> {series.name}</b>: {point.y}' } }, /** * @lends Highcharts.Series# */ H.merge(multipleLinesMixin, { nameBase: 'Aroon Oscillator', pointArrayMap: ['y'], pointValKey: 'y', linesApiNames: [], init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( AROON, 'aroon', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, getValues: function (series, params) { var ARO = [], // 0- date, 1- Aroon Oscillator xData = [], yData = [], aroon, aroonUp, aroonDown, oscillator, i; aroon = AROON.prototype.getValues.call(this, series, params); for (i = 0; i < aroon.yData.length; i++) { aroonUp = aroon.yData[i][0]; aroonDown = aroon.yData[i][1]; oscillator = aroonUp - aroonDown; ARO.push([aroon.xData[i], oscillator]); xData.push(aroon.xData[i]); yData.push(oscillator); } return { values: ARO, xData: xData, yData: yData }; } }) ); /** * An `Aroon Oscillator` series. If the [type](#series.aroonoscillator.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.aroonoscillator * @since 7.0.0 * @product highstock * @excluding allAreas, aroonDown, colorAxis, compare, compareBase, dataParser, * dataURL, joinBy, keys, navigatorOptions, pointInterval, * pointIntervalUnit, pointPlacement, pointRange, pointStart, * showInNavigator, stacking * @apioption series.aroonoscillator */ }); _registerModule(_modules, 'indicators/atr.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType, UNDEFINED; // Utils: function accumulateAverage(points, xVal, yVal, i) { var xValue = xVal[i], yValue = yVal[i]; points.push([xValue, yValue]); } function getTR(currentPoint, prevPoint) { var pointY = currentPoint, prevY = prevPoint, HL = pointY[1] - pointY[2], HCp = prevY === UNDEFINED ? 0 : Math.abs(pointY[1] - prevY[3]), LCp = prevY === UNDEFINED ? 0 : Math.abs(pointY[2] - prevY[3]), TR = Math.max(HL, HCp, LCp); return TR; } function populateAverage(points, xVal, yVal, i, period, prevATR) { var x = xVal[i - 1], TR = getTR(yVal[i - 1], yVal[i - 2]), y; y = (((prevATR * (period - 1)) + TR) / period); return [x, y]; } /** * The ATR series type. * * @private * @class * @name Highcharts.seriesTypes.atr * * @augments Highcharts.Series */ seriesType( 'atr', 'sma', /** * Average true range indicator (ATR). This series requires `linkedTo` * option to be set. * * @sample stock/indicators/atr * ATR indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.atr */ { params: { period: 14 } }, /** * @lends Highcharts.Series# */ { getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, xValue = xVal[0], yValue = yVal[0], range = 1, prevATR = 0, TR = 0, ATR = [], xData = [], yData = [], point, i, points; points = [[xValue, yValue]]; if ( (xVal.length <= period) || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } for (i = 1; i <= yValLen; i++) { accumulateAverage(points, xVal, yVal, i); if (period < range) { point = populateAverage( points, xVal, yVal, i, period, prevATR ); prevATR = point[1]; ATR.push(point); xData.push(point[0]); yData.push(point[1]); } else if (period === range) { prevATR = TR / (i - 1); ATR.push([xVal[i - 1], prevATR]); xData.push(xVal[i - 1]); yData.push(prevATR); range++; } else { TR += getTR(yVal[i - 1], yVal[i - 2]); range++; } } return { values: ATR, xData: xData, yData: yData }; } } ); /** * A `ATR` series. If the [type](#series.atr.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.atr * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.atr */ }); _registerModule(_modules, 'indicators/bollinger-bands.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/multipe-lines.js']], function (H, U, multipleLinesMixin) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var merge = H.merge, SMA = H.seriesTypes.sma; // Utils: function getStandardDeviation(arr, index, isOHLC, mean) { var variance = 0, arrLen = arr.length, std = 0, i = 0, value; for (; i < arrLen; i++) { value = (isOHLC ? arr[i][index] : arr[i]) - mean; variance += value * value; } variance = variance / (arrLen - 1); std = Math.sqrt(variance); return std; } /** * Bollinger Bands series type. * * @private * @class * @name Highcharts.seriesTypes.bb * * @augments Highcharts.Series */ H.seriesType( 'bb', 'sma', /** * Bollinger bands (BB). This series requires the `linkedTo` option to be * set and should be loaded after the `stock/indicators/indicators.js` file. * * @sample stock/indicators/bollinger-bands * Bollinger bands * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.bb */ { params: { period: 20, /** * Standard deviation for top and bottom bands. */ standardDeviation: 2, index: 3 }, /** * Bottom line options. */ bottomLine: { /** * Styles for a bottom line. */ styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. If not set, it's inherited from * [plotOptions.bb.color](#plotOptions.bb.color). * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * Top line options. * * @extends plotOptions.bb.bottomLine */ topLine: { styles: { lineWidth: 1, /** * @type {Highcharts.ColorString} */ lineColor: undefined } }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span><b> {series.name}</b><br/>Top: {point.top}<br/>Middle: {point.middle}<br/>Bottom: {point.bottom}<br/>' }, marker: { enabled: false }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ H.merge(multipleLinesMixin, { pointArrayMap: ['top', 'middle', 'bottom'], pointValKey: 'middle', nameComponents: ['period', 'standardDeviation'], linesApiNames: ['topLine', 'bottomLine'], init: function () { SMA.prototype.init.apply(this, arguments); // Set default color for lines: this.options = merge({ topLine: { styles: { lineColor: this.color } }, bottomLine: { styles: { lineColor: this.color } } }, this.options); }, getValues: function (series, params) { var period = params.period, standardDeviation = params.standardDeviation, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, BB = [], // 0- date, 1-middle line, 2-top line, 3-bottom line ML, TL, BL, // middle line, top line and bottom line date, xData = [], yData = [], slicedX, slicedY, stdDev, isOHLC, point, i; if (xVal.length < period) { return false; } isOHLC = isArray(yVal[0]); for (i = period; i <= yValLen; i++) { slicedX = xVal.slice(i - period, i); slicedY = yVal.slice(i - period, i); point = SMA.prototype.getValues.call( this, { xData: slicedX, yData: slicedY }, params ); date = point.xData[0]; ML = point.yData[0]; stdDev = getStandardDeviation( slicedY, params.index, isOHLC, ML ); TL = ML + standardDeviation * stdDev; BL = ML - standardDeviation * stdDev; BB.push([date, TL, ML, BL]); xData.push(date); yData.push([TL, ML, BL]); } return { values: BB, xData: xData, yData: yData }; } }) ); /** * A bollinger bands indicator. If the [type](#series.bb.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.bb * @since 6.0.0 * @excluding dataParser, dataURL * @product highstock * @apioption series.bb */ }); _registerModule(_modules, 'indicators/cci.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; // Utils: function sumArray(array) { return array.reduce(function (prev, cur) { return prev + cur; }, 0); } function meanDeviation(arr, sma) { var len = arr.length, sum = 0, i; for (i = 0; i < len; i++) { sum += Math.abs(sma - (arr[i])); } return sum; } /** * The CCI series type. * * @private * @class * @name Highcharts.seriesTypes.cci * * @augments Highcharts.Series */ seriesType( 'cci', 'sma', /** * Commodity Channel Index (CCI). This series requires `linkedTo` option to * be set. * * @sample stock/indicators/cci * CCI indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.cci */ { params: { period: 14 } }, /** * @lends Highcharts.Series# */ { getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, TP = [], periodTP = [], range = 1, CCI = [], xData = [], yData = [], CCIPoint, p, len, smaTP, TPtemp, meanDev, i; // CCI requires close value if ( xVal.length <= period || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } // accumulate first N-points while (range < period) { p = yVal[range - 1]; TP.push((p[1] + p[2] + p[3]) / 3); range++; } for (i = period; i <= yValLen; i++) { p = yVal[i - 1]; TPtemp = (p[1] + p[2] + p[3]) / 3; len = TP.push(TPtemp); periodTP = TP.slice(len - period); smaTP = sumArray(periodTP) / period; meanDev = meanDeviation(periodTP, smaTP) / period; CCIPoint = ((TPtemp - smaTP) / (0.015 * meanDev)); CCI.push([xVal[i - 1], CCIPoint]); xData.push(xVal[i - 1]); yData.push(CCIPoint); } return { values: CCI, xData: xData, yData: yData }; } } ); /** * A `CCI` series. If the [type](#series.cci.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.cci * @since 6.0.0 * @excluding dataParser, dataURL * @product highstock * @apioption series.cci */ }); _registerModule(_modules, 'indicators/cmf.src.js', [_modules['parts/Globals.js']], function (H) { /* * * * (c) 2010-2019 Highsoft AS * * Author: Sebastian Domas * * Chaikin Money Flow indicator for Highstock * * License: www.highcharts.com/license * * */ /** * @private * @interface Highcharts.CmfValuesObject *//** * Combined xData and yData values into a tuple. * @name Highcharts.CmfValuesObject#values * @type {Array<Array<number,number>>} *//** * Values represent x timestamp values * @name Highcharts.CmfValuesObject#xData * @type {Array<number>} *//** * Values represent y values * @name Highcharts.CmfValuesObject#yData * @type {Array<number>} */ /** * The CMF series type. * * @private * @class * @name Highcharts.seriesTypes.cmf * * @augments Highcharts.Series */ H.seriesType('cmf', 'sma', /** * Chaikin Money Flow indicator (cmf). * * @sample stock/indicators/cmf/ * Chaikin Money Flow indicator * * @extends plotOptions.sma * @since 6.0.0 * @excluding animationLimit * @product highstock * @optionparent plotOptions.cmf */ { params: { period: 14, /** * The id of another series to use its data as volume data for the * indiator calculation. */ volumeSeriesID: 'volume' } }, /** * @lends Highcharts.Series# */ { nameBase: 'Chaikin Money Flow', /** * Checks if the series and volumeSeries are accessible, number of * points.x is longer than period, is series has OHLC data * @private * @return {boolean} True if series is valid and can be computed, * otherwise false. */ isValid: function () { var chart = this.chart, options = this.options, series = this.linkedParent, volumeSeries = ( this.volumeSeries || ( this.volumeSeries = chart.get(options.params.volumeSeriesID) ) ), isSeriesOHLC = ( series && series.yData && series.yData[0].length === 4 ); function isLengthValid(serie) { return serie.xData && serie.xData.length >= options.params.period; } return !!( series && volumeSeries && isLengthValid(series) && isLengthValid(volumeSeries) && isSeriesOHLC ); }, /** * Returns indicator's data. * @private * @return {boolean|Highcharts.CmfValuesObject} Returns false if the * indicator is not valid, otherwise returns Values object. */ getValues: function (series, params) { if (!this.isValid()) { return false; } return this.getMoneyFlow( series.xData, series.yData, this.volumeSeries.yData, params.period ); }, /** * @private * @param {Array<number>} xData - x timestamp values * @param {Array<number>} seriesYData - yData of basic series * @param {Array<number>} volumeSeriesYData - yData of volume series * @param {number} period - indicator's param * @return {Highcharts.CmfValuesObject} object containing computed money * flow data */ getMoneyFlow: function (xData, seriesYData, volumeSeriesYData, period) { var len = seriesYData.length, moneyFlowVolume = [], sumVolume = 0, sumMoneyFlowVolume = 0, moneyFlowXData = [], moneyFlowYData = [], values = [], i, point, nullIndex = -1; /** * Calculates money flow volume, changes i, nullIndex vars from * upper scope! * @private * @param {Array<number>} ohlc - OHLC point * @param {number} volume - Volume point's y value * @return {number} - volume * moneyFlowMultiplier **/ function getMoneyFlowVolume(ohlc, volume) { var high = ohlc[1], low = ohlc[2], close = ohlc[3], isValid = volume !== null && high !== null && low !== null && close !== null && high !== low; /** * @private * @param {number} h - High value * @param {number} l - Low value * @param {number} c - Close value * @return {number} calculated multiplier for the point **/ function getMoneyFlowMultiplier(h, l, c) { return ((c - l) - (h - c)) / (h - l); } return isValid ? getMoneyFlowMultiplier(high, low, close) * volume : ((nullIndex = i), null); } if (period > 0 && period <= len) { for (i = 0; i < period; i++) { moneyFlowVolume[i] = getMoneyFlowVolume( seriesYData[i], volumeSeriesYData[i] ); sumVolume += volumeSeriesYData[i]; sumMoneyFlowVolume += moneyFlowVolume[i]; } moneyFlowXData.push(xData[i - 1]); moneyFlowYData.push( i - nullIndex >= period && sumVolume !== 0 ? sumMoneyFlowVolume / sumVolume : null ); values.push([moneyFlowXData[0], moneyFlowYData[0]]); for (; i < len; i++) { moneyFlowVolume[i] = getMoneyFlowVolume( seriesYData[i], volumeSeriesYData[i] ); sumVolume -= volumeSeriesYData[i - period]; sumVolume += volumeSeriesYData[i]; sumMoneyFlowVolume -= moneyFlowVolume[i - period]; sumMoneyFlowVolume += moneyFlowVolume[i]; point = [ xData[i], i - nullIndex >= period ? sumMoneyFlowVolume / sumVolume : null ]; moneyFlowXData.push(point[0]); moneyFlowYData.push(point[1]); values.push([point[0], point[1]]); } } return { values: values, xData: moneyFlowXData, yData: moneyFlowYData }; } }); /** * A `CMF` series. If the [type](#series.cmf.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.cmf * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.cmf */ }); _registerModule(_modules, 'indicators/dpo.src.js', [_modules['parts/Globals.js']], function (H) { /* * * * License: www.highcharts.com/license * * */ var correctFloat = H.correctFloat, pick = H.pick; // Utils function accumulatePoints(sum, yVal, i, index, subtract) { var price = pick(yVal[i][index], yVal[i]); if (subtract) { return correctFloat(sum - price); } return correctFloat(sum + price); } /** * The DPO series type. * * @private * @class * @name Highcharts.seriesTypes.dpo * * @augments Highcharts.Series */ H.seriesType( 'dpo', 'sma', /** * Detrended Price Oscillator. This series requires the `linkedTo` option to * be set and should be loaded after the `stock/indicators/indicators.js`. * * @sample {highstock} stock/indicators/dpo * Detrended Price Oscillator * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @optionparent plotOptions.dpo */ { /** * Parameters used in calculation of Detrended Price Oscillator series * points. */ params: { /** * Period for Detrended Price Oscillator */ period: 21 } }, /** * @lends Highcharts.Series# */ { nameBase: 'DPO', getValues: function (series, params) { var period = params.period, index = params.index, offset = Math.floor(period / 2 + 1), range = period + offset, xVal = series.xData || [], yVal = series.yData || [], yValLen = yVal.length, DPO = [], // 0- date, 1- Detrended Price Oscillator xData = [], yData = [], sum = 0, oscillator, periodIndex, rangeIndex, price, i, j; if (xVal.length <= range) { return false; } // Accumulate first N-points for SMA for (i = 0; i < period - 1; i++) { sum = accumulatePoints(sum, yVal, i, index); } // Detrended Price Oscillator formula: // DPO = Price - Simple moving average [from (n / 2 + 1) days ago] for (j = 0; j <= yValLen - range; j++) { periodIndex = j + period - 1; rangeIndex = j + range - 1; // adding the last period point sum = accumulatePoints(sum, yVal, periodIndex, index); price = pick(yVal[rangeIndex][index], yVal[rangeIndex]); oscillator = price - sum / period; // substracting the first period point sum = accumulatePoints(sum, yVal, j, index, true); DPO.push([xVal[rangeIndex], oscillator]); xData.push(xVal[rangeIndex]); yData.push(oscillator); } return { values: DPO, xData: xData, yData: yData }; } } ); /** * A Detrended Price Oscillator. If the [type](#series.dpo.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.dpo * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.dpo */ }); _registerModule(_modules, 'indicators/ema.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType, correctFloat = H.correctFloat; /** * The EMA series type. * * @private * @class * @name Highcharts.seriesTypes.ema * * @augments Highcharts.Series */ seriesType( 'ema', 'sma', /** * Exponential moving average indicator (EMA). This series requires the * `linkedTo` option to be set. * * @sample stock/indicators/ema * Exponential moving average indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.ema */ { params: { /** * The point index which indicator calculations will base. For * example using OHLC data, index=2 means the indicator will be * calculated using Low values. * * By default index value used to be set to 0. Since Highstock 7 * by default index is set to 3 which means that the ema * indicator will be calculated using Close values. */ index: 3, period: 9 // @merge 14 in v6.2 } }, /** * @lends Highcharts.Series# */ { accumulatePeriodPoints: function ( period, index, yVal ) { var sum = 0, i = 0, y = 0; while (i < period) { y = index < 0 ? yVal[i] : yVal[i][index]; sum = sum + y; i++; } return sum; }, calculateEma: function ( xVal, yVal, i, EMApercent, calEMA, index, SMA ) { var x = xVal[i - 1], yValue = index < 0 ? yVal[i - 1] : yVal[i - 1][index], y; y = calEMA === undefined ? SMA : correctFloat((yValue * EMApercent) + (calEMA * (1 - EMApercent))); return [x, y]; }, getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, EMApercent = 2 / (period + 1), sum = 0, EMA = [], xData = [], yData = [], index = -1, SMA = 0, calEMA, EMAPoint, i; // Check period, if bigger than points length, skip if (yValLen < period) { return false; } // Switch index for OHLC / Candlestick / Arearange if (isArray(yVal[0])) { index = params.index ? params.index : 0; } // Accumulate first N-points sum = this.accumulatePeriodPoints( period, index, yVal ); // first point SMA = sum / period; // Calculate value one-by-one for each period in visible data for (i = period; i < yValLen + 1; i++) { EMAPoint = this.calculateEma( xVal, yVal, i, EMApercent, calEMA, index, SMA ); EMA.push(EMAPoint); xData.push(EMAPoint[0]); yData.push(EMAPoint[1]); calEMA = EMAPoint[1]; } return { values: EMA, xData: xData, yData: yData }; } } ); /** * A `EMA` series. If the [type](#series.ema.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ema * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.ema */ }); _registerModule(_modules, 'indicators/chaikin.src.js', [_modules['parts/Globals.js'], _modules['mixins/indicator-required.js']], function (H, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var EMA = H.seriesTypes.ema, AD = H.seriesTypes.ad, error = H.error, correctFloat = H.correctFloat, requiredIndicator = requiredIndicatorMixin; /** * The Chaikin series type. * * @private * @class * @name Highcharts.seriesTypes.chaikin * * @augments Highcharts.Series */ H.seriesType( 'chaikin', 'ema', /** * Chaikin Oscillator. This series requires the `linkedTo` option to * be set and should be loaded after the `stock/indicators/indicators.js` * and `stock/indicators/ema.js`. * * @sample {highstock} stock/indicators/chaikin * Chaikin Oscillator * * @extends plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, joinBy, keys, navigatorOptions, * pointInterval, pointIntervalUnit, pointPlacement, * pointRange, pointStart, showInNavigator, stacking * @optionparent plotOptions.chaikin */ { /** * Paramters used in calculation of Chaikin Oscillator * series points. * * @excluding index, period */ params: { /** * The id of volume series which is mandatory. * For example using OHLC data, volumeSeriesID='volume' means * the indicator will be calculated using OHLC and volume values. */ volumeSeriesID: 'volume', /** * Periods for Chaikin Oscillator calculations. * * @type {Array<number>} * @default [3, 10] */ periods: [3, 10] } }, /** * @lends Highcharts.Series# */ { nameBase: 'Chaikin Osc', nameComponents: ['periods'], init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( EMA, 'ema', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, getValues: function (series, params) { var periods = params.periods, period = params.period, ADL, // Accumulation Distribution Line data CHA = [], // 0- date, 1- Chaikin Oscillator xData = [], yData = [], periodsOffset, SPE, // Shorter Period EMA LPE, // Longer Period EMA oscillator, i; // Check if periods are correct if (periods.length !== 2 || periods[1] <= periods[0]) { error( 'Error: "Chaikin requires two periods. Notice, first ' + 'period should be lower than the second one."' ); return false; } ADL = AD.prototype.getValues.call(this, series, { volumeSeriesID: params.volumeSeriesID, period: period }); // Check if adl is calculated properly, if not skip if (!ADL) { return false; } SPE = EMA.prototype.getValues.call(this, ADL, { period: periods[0] }); LPE = EMA.prototype.getValues.call(this, ADL, { period: periods[1] }); // Check if ema is calculated properly, if not skip if (!SPE || !LPE) { return false; } periodsOffset = periods[1] - periods[0]; for (i = 0; i < LPE.yData.length; i++) { oscillator = correctFloat( SPE.yData[i + periodsOffset] - LPE.yData[i] ); CHA.push([LPE.xData[i], oscillator]); xData.push(LPE.xData[i]); yData.push(oscillator); } return { values: CHA, xData: xData, yData: yData }; } } ); /** * A `Chaikin Oscillator` series. If the [type](#series.chaikin.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.chaikin * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, dataParser, dataURL, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, stacking, showInNavigator * @apioption series.chaikin */ }); _registerModule(_modules, 'indicators/dema.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/indicator-required.js']], function (H, U, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var EMAindicator = H.seriesTypes.ema, requiredIndicator = requiredIndicatorMixin, correctFloat = H.correctFloat; /** * The DEMA series Type * * @private * @class * @name Highcharts.seriesTypes.dema * * @augments Highcharts.Series */ H.seriesType( 'dema', 'ema', /** * Double exponential moving average (DEMA) indicator. This series requires * `linkedTo` option to be set and should be loaded after the * `stock/indicators/indicators.js` and `stock/indicators/ema.js`. * * @sample {highstock} stock/indicators/dema * DEMA indicator * * @extends plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @optionparent plotOptions.dema */ {}, /** * @lends Highcharts.Series# */ { init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( EMAindicator, 'ema', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, getEMA: function ( yVal, prevEMA, SMA, index, i, xVal ) { return EMAindicator.prototype.calculateEma( xVal || [], yVal, i === undefined ? 1 : i, this.chart.series[0].EMApercent, prevEMA, index === undefined ? -1 : index, SMA ); }, getValues: function (series, params) { var period = params.period, doubledPeriod = 2 * period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, index = -1, accumulatePeriodPoints = 0, SMA = 0, DEMA = [], xDataDema = [], yDataDema = [], EMA = 0, // EMA(EMA) EMAlevel2, // EMA of previous point prevEMA, prevEMAlevel2, // EMA values array EMAvalues = [], i, DEMAPoint; series.EMApercent = (2 / (period + 1)); // Check period, if bigger than EMA points length, skip if (yValLen < 2 * period - 1) { return false; } // Switch index for OHLC / Candlestick / Arearange if (isArray(yVal[0])) { index = params.index ? params.index : 0; } // Accumulate first N-points accumulatePeriodPoints = EMAindicator.prototype.accumulatePeriodPoints( period, index, yVal ); // first point SMA = accumulatePeriodPoints / period; accumulatePeriodPoints = 0; // Calculate value one-by-one for each period in visible data for (i = period; i < yValLen + 2; i++) { if (i < yValLen + 1) { EMA = this.getEMA( yVal, prevEMA, SMA, index, i )[1]; EMAvalues.push(EMA); } prevEMA = EMA; // Summing first period points for EMA(EMA) if (i < doubledPeriod) { accumulatePeriodPoints += EMA; } else { // Calculate DEMA // First DEMA point if (i === doubledPeriod) { SMA = accumulatePeriodPoints / period; } EMA = EMAvalues[i - period - 1]; EMAlevel2 = this.getEMA( [EMA], prevEMAlevel2, SMA )[1]; DEMAPoint = [ xVal[i - 2], correctFloat(2 * EMA - EMAlevel2) ]; DEMA.push(DEMAPoint); xDataDema.push(DEMAPoint[0]); yDataDema.push(DEMAPoint[1]); prevEMAlevel2 = EMAlevel2; } } return { values: DEMA, xData: xDataDema, yData: yDataDema }; } } ); /** * A `DEMA` series. If the [type](#series.ema.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.dema */ }); _registerModule(_modules, 'indicators/tema.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/indicator-required.js']], function (H, U, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var EMAindicator = H.seriesTypes.ema, requiredIndicator = requiredIndicatorMixin, correctFloat = H.correctFloat; /** * The TEMA series type. * * @private * @class * @name Highcharts.seriesTypes.tema * * @augments Highcharts.Series */ H.seriesType( 'tema', 'ema', /** * Triple exponential moving average (TEMA) indicator. This series requires * `linkedTo` option to be set and should be loaded after the * `stock/indicators/indicators.js` and `stock/indicators/ema.js`. * * Requires `https://code.highcharts.com/stock/indicators/ema.js`. * * @sample {highstock} stock/indicators/tema * TEMA indicator * * @extends plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @optionparent plotOptions.tema */ {}, /** * @lends Highcharts.Series# */ { init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( EMAindicator, 'ema', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, getEMA: function ( yVal, prevEMA, SMA, index, i, xVal ) { return EMAindicator.prototype.calculateEma( xVal || [], yVal, i === undefined ? 1 : i, this.chart.series[0].EMApercent, prevEMA, index === undefined ? -1 : index, SMA ); }, getTemaPoint: function ( xVal, tripledPeriod, EMAlevels, i ) { var TEMAPoint = [ xVal[i - 3], correctFloat( 3 * EMAlevels.level1 - 3 * EMAlevels.level2 + EMAlevels.level3 ) ]; return TEMAPoint; }, getValues: function (series, params) { var period = params.period, doubledPeriod = 2 * period, tripledPeriod = 3 * period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, index = -1, accumulatePeriodPoints = 0, SMA = 0, TEMA = [], xDataTema = [], yDataTema = [], // EMA of previous point prevEMA, prevEMAlevel2, // EMA values array EMAvalues = [], EMAlevel2values = [], i, TEMAPoint, // This object contains all EMA EMAlevels calculated like below // EMA = level1 // EMA(EMA) = level2, // EMA(EMA(EMA)) = level3, EMAlevels = {}; series.EMApercent = (2 / (period + 1)); // Check period, if bigger than EMA points length, skip if (yValLen < 3 * period - 2) { return false; } // Switch index for OHLC / Candlestick / Arearange if (isArray(yVal[0])) { index = params.index ? params.index : 0; } // Accumulate first N-points accumulatePeriodPoints = EMAindicator.prototype.accumulatePeriodPoints( period, index, yVal ); // first point SMA = accumulatePeriodPoints / period; accumulatePeriodPoints = 0; // Calculate value one-by-one for each period in visible data for (i = period; i < yValLen + 3; i++) { if (i < yValLen + 1) { EMAlevels.level1 = this.getEMA( yVal, prevEMA, SMA, index, i )[1]; EMAvalues.push(EMAlevels.level1); } prevEMA = EMAlevels.level1; // Summing first period points for ema(ema) if (i < doubledPeriod) { accumulatePeriodPoints += EMAlevels.level1; } else { // Calculate dema // First dema point if (i === doubledPeriod) { SMA = accumulatePeriodPoints / period; accumulatePeriodPoints = 0; } EMAlevels.level1 = EMAvalues[i - period - 1]; EMAlevels.level2 = this.getEMA( [EMAlevels.level1], prevEMAlevel2, SMA )[1]; EMAlevel2values.push(EMAlevels.level2); prevEMAlevel2 = EMAlevels.level2; // Summing first period points for ema(ema(ema)) if (i < tripledPeriod) { accumulatePeriodPoints += EMAlevels.level2; } else { // Calculate tema // First tema point if (i === tripledPeriod) { SMA = accumulatePeriodPoints / period; } if (i === yValLen + 1) { // Calculate the last ema and emaEMA points EMAlevels.level1 = EMAvalues[i - period - 1]; EMAlevels.level2 = this.getEMA( [EMAlevels.level1], prevEMAlevel2, SMA )[1]; EMAlevel2values.push(EMAlevels.level2); } EMAlevels.level1 = EMAvalues[i - period - 2]; EMAlevels.level2 = EMAlevel2values[i - 2 * period - 1]; EMAlevels.level3 = this.getEMA( [EMAlevels.level2], EMAlevels.prevLevel3, SMA )[1]; TEMAPoint = this.getTemaPoint( xVal, tripledPeriod, EMAlevels, i ); // Make sure that point exists (for TRIX oscillator) if (TEMAPoint) { TEMA.push(TEMAPoint); xDataTema.push(TEMAPoint[0]); yDataTema.push(TEMAPoint[1]); } EMAlevels.prevLevel3 = EMAlevels.level3; } } } return { values: TEMA, xData: xDataTema, yData: yDataTema }; } } ); /** * A `TEMA` series. If the [type](#series.ema.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.tema */ }); _registerModule(_modules, 'indicators/trix.src.js', [_modules['parts/Globals.js'], _modules['mixins/indicator-required.js']], function (H, requiredIndicator) { /* * * * License: www.highcharts.com/license * * */ var correctFloat = H.correctFloat, TEMA = H.seriesTypes.tema; /** * The TRIX series type. * * @private * @class * @name Highcharts.seriesTypes.trix * * @augments Highcharts.Series */ H.seriesType( 'trix', 'tema', /** * Triple exponential average (TRIX) oscillator. This series requires * `linkedTo` option to be set. * * Requires https://code.highcharts.com/stock/indicators/ema.js * and https://code.highcharts.com/stock/indicators/tema.js. * * @sample {highstock} stock/indicators/trix * TRIX indicator * * @extends plotOptions.tema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @optionparent plotOptions.trix */ {}, /** * @lends Highcharts.Series# */ { init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( TEMA, 'tema', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, // TRIX is calculated using TEMA so we just extend getTemaPoint method. getTemaPoint: function ( xVal, tripledPeriod, EMAlevels, i ) { if (i > tripledPeriod) { var TRIXPoint = [ xVal[i - 3], EMAlevels.prevLevel3 !== 0 ? correctFloat(EMAlevels.level3 - EMAlevels.prevLevel3) / EMAlevels.prevLevel3 * 100 : null ]; } return TRIXPoint; } } ); /** * A `TRIX` series. If the [type](#series.tema.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.tema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.trix */ }); _registerModule(_modules, 'indicators/apo.src.js', [_modules['parts/Globals.js'], _modules['mixins/indicator-required.js']], function (H, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var EMA = H.seriesTypes.ema, error = H.error, requiredIndicator = requiredIndicatorMixin; /** * The APO series type. * * @private * @class * @name Highcharts.seriesTypes.apo * * @augments Highcharts.Series */ H.seriesType( 'apo', 'ema', /** * Absolute Price Oscillator. This series requires the `linkedTo` option to * be set and should be loaded after the `stock/indicators/indicators.js` * and `stock/indicators/ema.js`. * * @sample {highstock} stock/indicators/apo * Absolute Price Oscillator * * @extends plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, joinBy, keys, navigatorOptions, * pointInterval, pointIntervalUnit, pointPlacement, * pointRange, pointStart, showInNavigator, stacking * @optionparent plotOptions.apo */ { /** * Paramters used in calculation of Absolute Price Oscillator * series points. * * @excluding period */ params: { /** * Periods for Absolute Price Oscillator calculations. * * @type {Array<number>} * @default [10, 20] * @since 7.0.0 */ periods: [10, 20] } }, /** * @lends Highcharts.Series.prototype */ { nameBase: 'APO', nameComponents: ['periods'], init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( EMA, 'ema', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, getValues: function (series, params) { var periods = params.periods, index = params.index, APO = [], // 0- date, 1- Absolute price oscillator xData = [], yData = [], periodsOffset, SPE, // Shorter Period EMA LPE, // Longer Period EMA oscillator, i; // Check if periods are correct if (periods.length !== 2 || periods[1] <= periods[0]) { error( 'Error: "APO requires two periods. Notice, first period ' + 'should be lower than the second one."' ); return false; } SPE = EMA.prototype.getValues.call(this, series, { index: index, period: periods[0] }); LPE = EMA.prototype.getValues.call(this, series, { index: index, period: periods[1] }); // Check if ema is calculated properly, if not skip if (!SPE || !LPE) { return false; } periodsOffset = periods[1] - periods[0]; for (i = 0; i < LPE.yData.length; i++) { oscillator = SPE.yData[i + periodsOffset] - LPE.yData[i]; APO.push([LPE.xData[i], oscillator]); xData.push(LPE.xData[i]); yData.push(oscillator); } return { values: APO, xData: xData, yData: yData }; } } ); /** * An `Absolute Price Oscillator` series. If the [type](#series.apo.type) option * is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.apo * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, dataParser, dataURL, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.apo */ }); _registerModule(_modules, 'indicators/ichimoku-kinko-hyo.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var defined = U.defined, isArray = U.isArray, objectEach = U.objectEach; var UNDEFINED, seriesType = H.seriesType, merge = H.merge, color = H.color, SMA = H.seriesTypes.sma; // Utils: function maxHigh(arr) { return arr.reduce(function (max, res) { return Math.max(max, res[1]); }, -Infinity); } function minLow(arr) { return arr.reduce(function (min, res) { return Math.min(min, res[2]); }, Infinity); } function highlowLevel(arr) { return { high: maxHigh(arr), low: minLow(arr) }; } function getClosestPointRange(axis) { var closestDataRange, loopLength, distance, xData, i; axis.series.forEach(function (series) { if (series.xData) { xData = series.xData; loopLength = series.xIncrement ? 1 : xData.length - 1; for (i = loopLength; i > 0; i--) { distance = xData[i] - xData[i - 1]; if ( closestDataRange === UNDEFINED || distance < closestDataRange ) { closestDataRange = distance; } } } }); return closestDataRange; } // Check two lines intersection (line a1-a2 and b1-b2) // Source: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection function checkLineIntersection(a1, a2, b1, b2) { if (a1 && a2 && b1 && b2) { var saX = a2.plotX - a1.plotX, // Auxiliary section a2-a1 X saY = a2.plotY - a1.plotY, // Auxiliary section a2-a1 Y sbX = b2.plotX - b1.plotX, // Auxiliary section b2-b1 X sbY = b2.plotY - b1.plotY, // Auxiliary section b2-b1 Y sabX = a1.plotX - b1.plotX, // Auxiliary section a1-b1 X sabY = a1.plotY - b1.plotY, // Auxiliary section a1-b1 Y // First degree Bézier parameters u, t; u = (-saY * sabX + saX * sabY) / (-sbX * saY + saX * sbY); t = (sbX * sabY - sbY * sabX) / (-sbX * saY + saX * sbY); if (u >= 0 && u <= 1 && t >= 0 && t <= 1) { return { plotX: a1.plotX + (t * saX), plotY: a1.plotY + (t * saY) }; } } return false; } // Parameter opt (indicator options object) include indicator, points, // nextPoints, color, options, gappedExtend and graph properties function drawSenkouSpan(opt) { var indicator = opt.indicator; indicator.points = opt.points; indicator.nextPoints = opt.nextPoints; indicator.color = opt.color; indicator.options = merge(opt.options.senkouSpan.styles, opt.gap); indicator.graph = opt.graph; indicator.fillGraph = true; SMA.prototype.drawGraph.call(indicator); } // Data integrity in Ichimoku is different than default "averages": // Point: [undefined, value, value, ...] is correct // Point: [undefined, undefined, undefined, ...] is incorrect H.approximations['ichimoku-averages'] = function () { var ret = [], isEmptyRange; [].forEach.call(arguments, function (arr, i) { ret.push(H.approximations.average(arr)); isEmptyRange = !isEmptyRange && ret[i] === undefined; }); // Return undefined when first elem. is undefined and let // sum method handle null (#7377) return isEmptyRange ? undefined : ret; }; /** * The IKH series type. * * @private * @class * @name Highcharts.seriesTypes.ikh * * @augments Highcharts.Series */ seriesType( 'ikh', 'sma', /** * Ichimoku Kinko Hyo (IKH). This series requires `linkedTo` option to be * set. * * @sample stock/indicators/ichimoku-kinko-hyo * Ichimoku Kinko Hyo indicator * * @extends plotOptions.sma * @since 6.0.0 * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @product highstock * @optionparent plotOptions.ikh */ { params: { period: 26, /** * The base period for Tenkan calculations. */ periodTenkan: 9, /** * The base period for Senkou Span B calculations */ periodSenkouSpanB: 52 }, marker: { enabled: false }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + 'TENKAN SEN: {point.tenkanSen:.3f}<br/>' + 'KIJUN SEN: {point.kijunSen:.3f}<br/>' + 'CHIKOU SPAN: {point.chikouSpan:.3f}<br/>' + 'SENKOU SPAN A: {point.senkouSpanA:.3f}<br/>' + 'SENKOU SPAN B: {point.senkouSpanB:.3f}<br/>' }, /** * The styles for Tenkan line */ tenkanLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * The styles for Kijun line */ kijunLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * The styles for Chikou line */ chikouLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * The styles for Senkou Span A line */ senkouSpanA: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * The styles for Senkou Span B line */ senkouSpanB: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * The styles for area between Senkou Span A and B. */ senkouSpan: { /** * Color of the area between Senkou Span A and B, * when Senkou Span A is above Senkou Span B. Note that if * a `style.fill` is defined, the `color` takes precedence and * the `style.fill` is ignored. * * @see [senkouSpan.styles.fill](#series.ikh.senkouSpan.styles.fill) * * @sample stock/indicators/ichimoku-kinko-hyo * Ichimoku Kinko Hyo color * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 7.0.0 * @apioption plotOptions.ikh.senkouSpan.color */ /** * Color of the area between Senkou Span A and B, * when Senkou Span A is under Senkou Span B. * * @sample stock/indicators/ikh-negative-color * Ichimoku Kinko Hyo negativeColor * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 7.0.0 * @apioption plotOptions.ikh.senkouSpan.negativeColor */ styles: { /** * Color of the area between Senkou Span A and B. * * @deprecated * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ fill: 'rgba(255, 0, 0, 0.5)' } }, dataGrouping: { approximation: 'ichimoku-averages' } }, /** * @lends Highcharts.Series# */ { pointArrayMap: [ 'tenkanSen', 'kijunSen', 'chikouSpan', 'senkouSpanA', 'senkouSpanB' ], pointValKey: 'tenkanSen', nameComponents: ['periodSenkouSpanB', 'period', 'periodTenkan'], init: function () { SMA.prototype.init.apply(this, arguments); // Set default color for lines: this.options = merge({ tenkanLine: { styles: { lineColor: this.color } }, kijunLine: { styles: { lineColor: this.color } }, chikouLine: { styles: { lineColor: this.color } }, senkouSpanA: { styles: { lineColor: this.color, fill: color(this.color).setOpacity(0.5).get() } }, senkouSpanB: { styles: { lineColor: this.color, fill: color(this.color).setOpacity(0.5).get() } }, senkouSpan: { styles: { fill: color(this.color).setOpacity(0.2).get() } } }, this.options); }, toYData: function (point) { return [ point.tenkanSen, point.kijunSen, point.chikouSpan, point.senkouSpanA, point.senkouSpanB ]; }, translate: function () { var indicator = this; SMA.prototype.translate.apply(indicator); indicator.points.forEach(function (point) { indicator.pointArrayMap.forEach(function (value) { if (defined(point[value])) { point['plot' + value] = indicator.yAxis.toPixels( point[value], true ); // Add extra parameters for support tooltip in moved // lines point.plotY = point['plot' + value]; point.tooltipPos = [point.plotX, point['plot' + value]]; point.isNull = false; } }); }); }, // One does not simply // Render five lines // And an arearange // In just one series.. drawGraph: function () { var indicator = this, mainLinePoints = indicator.points, pointsLength = mainLinePoints.length, mainLineOptions = indicator.options, mainLinePath = indicator.graph, mainColor = indicator.color, gappedExtend = { options: { gapSize: mainLineOptions.gapSize } }, pointArrayMapLength = indicator.pointArrayMap.length, allIchimokuPoints = [[], [], [], [], [], []], ikhMap = { tenkanLine: allIchimokuPoints[0], kijunLine: allIchimokuPoints[1], chikouLine: allIchimokuPoints[2], senkouSpanA: allIchimokuPoints[3], senkouSpanB: allIchimokuPoints[4], senkouSpan: allIchimokuPoints[5] }, intersectIndexColl = [], senkouSpanOptions = indicator.options.senkouSpan, color = senkouSpanOptions.color || senkouSpanOptions.styles.fill, negativeColor = senkouSpanOptions.negativeColor, // Points to create color and negativeColor senkouSpan points = [ [], // Points color [] // Points negative color ], // For span, we need an access to the next points, used in // getGraphPath() nextPoints = [ [], // NextPoints color [] // NextPoints negative color ], lineIndex = 0, position, point, i, startIntersect, endIntersect, sectionPoints, sectionNextPoints, pointsPlotYSum, nextPointsPlotYSum, senkouSpanTempColor, concatArrIndex, j, k; indicator.ikhMap = ikhMap; // Generate points for all lines and spans lines: while (pointsLength--) { point = mainLinePoints[pointsLength]; for (i = 0; i < pointArrayMapLength; i++) { position = indicator.pointArrayMap[i]; if (defined(point[position])) { allIchimokuPoints[i].push({ plotX: point.plotX, plotY: point['plot' + position], isNull: false }); } } if (negativeColor && pointsLength !== mainLinePoints.length - 1) { // Check if lines intersect var index = ikhMap.senkouSpanB.length - 1, intersect = checkLineIntersection( ikhMap.senkouSpanA[index - 1], ikhMap.senkouSpanA[index], ikhMap.senkouSpanB[index - 1], ikhMap.senkouSpanB[index] ), intersectPointObj = { plotX: intersect.plotX, plotY: intersect.plotY, isNull: false, intersectPoint: true }; if (intersect) { // Add intersect point to ichimoku points collection // Create senkouSpan sections ikhMap.senkouSpanA.splice(index, 0, intersectPointObj); ikhMap.senkouSpanB.splice(index, 0, intersectPointObj); intersectIndexColl.push(index); } } } // Modify options and generate lines: objectEach(ikhMap, function (values, lineName) { if (mainLineOptions[lineName] && lineName !== 'senkouSpan') { // First line is rendered by default option indicator.points = allIchimokuPoints[lineIndex]; indicator.options = merge( mainLineOptions[lineName].styles, gappedExtend ); indicator.graph = indicator['graph' + lineName]; indicator.fillGraph = false; indicator.color = mainColor; SMA.prototype.drawGraph.call(indicator); // Now save line indicator['graph' + lineName] = indicator.graph; } lineIndex++; }); // Generate senkouSpan area: // If graphColection exist then remove svg // element and indicator property if (indicator.graphCollection) { indicator.graphCollection.forEach(function (graphName) { indicator[graphName].destroy(); delete indicator[graphName]; }); } // Clean grapCollection or initialize it indicator.graphCollection = []; // When user set negativeColor property if ( negativeColor && ikhMap.senkouSpanA[0] && ikhMap.senkouSpanB[0]) { // Add first and last point to senkouSpan area sections intersectIndexColl.unshift(0); intersectIndexColl.push(ikhMap.senkouSpanA.length - 1); // Populate points and nextPoints arrays for (j = 0; j < intersectIndexColl.length - 1; j++) { startIntersect = intersectIndexColl[j]; endIntersect = intersectIndexColl[j + 1]; sectionPoints = ikhMap.senkouSpanB.slice( startIntersect, endIntersect + 1 ); sectionNextPoints = ikhMap.senkouSpanA.slice( startIntersect, endIntersect + 1 ); // Add points to color or negativeColor arrays // Check the middle point (if exist) if (Math.floor(sectionPoints.length / 2) >= 1) { var x = Math.floor(sectionPoints.length / 2); // When middle points has equal values // Compare all ponints plotY value sum if ( sectionPoints[x].plotY === sectionNextPoints[x].plotY ) { pointsPlotYSum = 0; nextPointsPlotYSum = 0; for (k = 0; k < sectionPoints.length; k++) { pointsPlotYSum += sectionPoints[k].plotY; nextPointsPlotYSum += sectionNextPoints[k].plotY; } concatArrIndex = pointsPlotYSum > nextPointsPlotYSum ? 0 : 1; points[concatArrIndex] = points[concatArrIndex].concat(sectionPoints); nextPoints[concatArrIndex] = nextPoints[concatArrIndex].concat( sectionNextPoints ); } else { // Compare middle point of the section concatArrIndex = sectionPoints[x].plotY > sectionNextPoints[x].plotY ? 0 : 1; points[concatArrIndex] = points[concatArrIndex].concat(sectionPoints); nextPoints[concatArrIndex] = nextPoints[concatArrIndex].concat( sectionNextPoints ); } } else { // Compare first point of the section concatArrIndex = sectionPoints[0].plotY > sectionNextPoints[0].plotY ? 0 : 1; points[concatArrIndex] = points[concatArrIndex].concat(sectionPoints); nextPoints[concatArrIndex] = nextPoints[concatArrIndex].concat( sectionNextPoints ); } } // Render color and negativeColor paths [ 'graphsenkouSpanColor', 'graphsenkouSpanNegativeColor' ].forEach( function (areaName, i) { if (points[i].length && nextPoints[i].length) { senkouSpanTempColor = (i === 0) ? color : negativeColor; drawSenkouSpan({ indicator: indicator, points: points[i], nextPoints: nextPoints[i], color: senkouSpanTempColor, options: mainLineOptions, gap: gappedExtend, graph: indicator[areaName] }); // Now save line indicator[areaName] = indicator.graph; indicator.graphCollection.push(areaName); } } ); } else { // When user set only senkouSpan style.fill property drawSenkouSpan({ indicator: indicator, points: ikhMap.senkouSpanB, nextPoints: ikhMap.senkouSpanA, color: color, options: mainLineOptions, gap: gappedExtend, graph: indicator.graphsenkouSpan }); // Now save line indicator.graphsenkouSpan = indicator.graph; } // Clean temporary properties: delete indicator.nextPoints; delete indicator.fillGraph; // Restore options and draw the Tenkan line: indicator.points = mainLinePoints; indicator.options = mainLineOptions; indicator.graph = mainLinePath; }, getGraphPath: function (points) { var indicator = this, path = [], spanA, fillArray = [], spanAarr = []; points = points || this.points; // Render Senkou Span if (indicator.fillGraph && indicator.nextPoints) { spanA = SMA.prototype.getGraphPath.call( indicator, // Reverse points, so Senkou Span A will start from the end: indicator.nextPoints ); spanA[0] = 'L'; path = SMA.prototype.getGraphPath.call( indicator, points ); spanAarr = spanA.slice(0, path.length); for (var i = (spanAarr.length - 1); i > 0; i -= 3) { fillArray.push( spanAarr[i - 2], spanAarr[i - 1], spanAarr[i] ); } path = path.concat(fillArray); } else { path = SMA.prototype.getGraphPath.apply(indicator, arguments); } return path; }, getValues: function (series, params) { var period = params.period, periodTenkan = params.periodTenkan, periodSenkouSpanB = params.periodSenkouSpanB, xVal = series.xData, yVal = series.yData, xAxis = series.xAxis, yValLen = (yVal && yVal.length) || 0, closestPointRange = getClosestPointRange(xAxis), IKH = [], xData = [], dateStart, date, slicedTSY, slicedKSY, slicedSSBY, pointTS, pointKS, pointSSB, i, TS, KS, CS, SSA, SSB; // Ikh requires close value if ( xVal.length <= period || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } // Add timestamps at the beginning dateStart = xVal[0] - (period * closestPointRange); for (i = 0; i < period; i++) { xData.push(dateStart + i * closestPointRange); } for (i = 0; i < yValLen; i++) { // Tenkan Sen if (i >= periodTenkan) { slicedTSY = yVal.slice(i - periodTenkan, i); pointTS = highlowLevel(slicedTSY); TS = (pointTS.high + pointTS.low) / 2; } if (i >= period) { slicedKSY = yVal.slice(i - period, i); pointKS = highlowLevel(slicedKSY); KS = (pointKS.high + pointKS.low) / 2; SSA = (TS + KS) / 2; } if (i >= periodSenkouSpanB) { slicedSSBY = yVal.slice(i - periodSenkouSpanB, i); pointSSB = highlowLevel(slicedSSBY); SSB = (pointSSB.high + pointSSB.low) / 2; } CS = yVal[i][3]; date = xVal[i]; if (IKH[i] === UNDEFINED) { IKH[i] = []; } if (IKH[i + period] === UNDEFINED) { IKH[i + period] = []; } IKH[i + period][0] = TS; IKH[i + period][1] = KS; IKH[i + period][2] = UNDEFINED; IKH[i][2] = CS; if (i <= period) { IKH[i + period][3] = UNDEFINED; IKH[i + period][4] = UNDEFINED; } if (IKH[i + 2 * period] === UNDEFINED) { IKH[i + 2 * period] = []; } IKH[i + 2 * period][3] = SSA; IKH[i + 2 * period][4] = SSB; xData.push(date); } // Add timestamps for further points for (i = 1; i <= period; i++) { xData.push(date + i * closestPointRange); } return { values: IKH, xData: xData, yData: IKH }; } } ); /** * A `IKH` series. If the [type](#series.ikh.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ikh * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.ikh */ }); _registerModule(_modules, 'indicators/keltner-channels.src.js', [_modules['parts/Globals.js'], _modules['mixins/multipe-lines.js']], function (H, multipleLinesMixin) { /* * * * License: www.highcharts.com/license * * */ var SMA = H.seriesTypes.sma, EMA = H.seriesTypes.ema, ATR = H.seriesTypes.atr, merge = H.merge, correctFloat = H.correctFloat; /** * The Keltner Channels series type. * * @private * @class * @name Highcharts.seriesTypes.keltnerchannels * * @augments Highcharts.Series */ H.seriesType( 'keltnerchannels', 'sma', /** * Keltner Channels. This series requires the `linkedTo` option to be set * and should be loaded after the `stock/indicators/indicators.js`, * `stock/indicators/atr.js`, and `stock/ema/.js`. * * @sample {highstock} stock/indicators/keltner-channels * Keltner Channels * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart,showInNavigator, * stacking * @optionparent plotOptions.keltnerchannels */ { params: { period: 20, /** * The ATR period. */ periodATR: 10, /** * The ATR multiplier. */ multiplierATR: 2 }, /** * Bottom line options. * */ bottomLine: { /** * Styles for a bottom line. * */ styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. If not set, it's inherited from * `plotOptions.keltnerchannels.color` */ lineColor: undefined } }, /** * Top line options. * * @extends plotOptions.keltnerchannels.bottomLine */ topLine: { styles: { lineWidth: 1, lineColor: undefined } }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span><b> {series.name}</b><br/>Upper Channel: {point.top}<br/>EMA({series.options.params.period}): {point.middle}<br/>Lower Channel: {point.bottom}<br/>' }, marker: { enabled: false }, dataGrouping: { approximation: 'averages' }, lineWidth: 1 }, /** * @lends Highcharts.Series# */ merge(multipleLinesMixin, { pointArrayMap: ['top', 'middle', 'bottom'], pointValKey: 'middle', nameBase: 'Keltner Channels', nameComponents: ['period', 'periodATR', 'multiplierATR'], linesApiNames: ['topLine', 'bottomLine'], requiredIndicators: ['ema', 'atr'], init: function () { SMA.prototype.init.apply(this, arguments); // Set default color for lines: this.options = merge({ topLine: { styles: { lineColor: this.color } }, bottomLine: { styles: { lineColor: this.color } } }, this.options); }, getValues: function (series, params) { var period = params.period, periodATR = params.periodATR, multiplierATR = params.multiplierATR, index = params.index, yVal = series.yData, yValLen = yVal ? yVal.length : 0, // Keltner Channels array structure: // 0-date, 1-top line, 2-middle line, 3-bottom line KC = [], ML, TL, BL, // middle line, top line and bottom line date, seriesEMA = EMA.prototype.getValues(series, { period: period, index: index }), seriesATR = ATR.prototype.getValues(series, { period: periodATR }), pointEMA, pointATR, xData = [], yData = [], i; if (yValLen < period) { return false; } for (i = period; i <= yValLen; i++) { pointEMA = seriesEMA.values[i - period]; pointATR = seriesATR.values[i - periodATR]; date = pointEMA[0]; TL = correctFloat(pointEMA[1] + (multiplierATR * pointATR[1])); BL = correctFloat(pointEMA[1] - (multiplierATR * pointATR[1])); ML = pointEMA[1]; KC.push([date, TL, ML, BL]); xData.push(date); yData.push([TL, ML, BL]); } return { values: KC, xData: xData, yData: yData }; } }) ); /** * A Keltner Channels indicator. If the [type](#series.keltnerchannels.type) * option is not specified, it is inherited from[chart.type](#chart.type). * * @extends series,plotOptions.keltnerchannels * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, * pointIntervalUnit, pointPlacement, pointRange, pointStart, * stacking, showInNavigator * @optionparent series.keltnerchannels */ }); _registerModule(_modules, 'indicators/macd.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var defined = U.defined; var seriesType = H.seriesType, noop = H.noop, merge = H.merge, SMA = H.seriesTypes.sma, EMA = H.seriesTypes.ema, correctFloat = H.correctFloat; /** * The MACD series type. * * @private * @class * @name Highcharts.seriesTypes.macd * * @augments Highcharts.Series */ seriesType( 'macd', 'sma', /** * Moving Average Convergence Divergence (MACD). This series requires * `linkedTo` option to be set and should be loaded after the * `stock/indicators/indicators.js` and `stock/indicators/ema.js`. * * @sample stock/indicators/macd * MACD indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.macd */ { params: { /** * The short period for indicator calculations. */ shortPeriod: 12, /** * The long period for indicator calculations. */ longPeriod: 26, /** * The base period for signal calculations. */ signalPeriod: 9, period: 26 }, /** * The styles for signal line */ signalLine: { /** * @sample stock/indicators/macd-zones * Zones in MACD * * @extends plotOptions.macd.zones */ zones: [], styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * The styles for macd line */ macdLine: { /** * @sample stock/indicators/macd-zones * Zones in MACD * * @extends plotOptions.macd.zones */ zones: [], styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: undefined } }, threshold: 0, groupPadding: 0.1, pointPadding: 0.1, states: { hover: { halo: { size: 0 } } }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + 'Value: {point.MACD}<br/>' + 'Signal: {point.signal}<br/>' + 'Histogram: {point.y}<br/>' }, dataGrouping: { approximation: 'averages' }, minPointLength: 0 }, /** * @lends Highcharts.Series# */ { nameComponents: ['longPeriod', 'shortPeriod', 'signalPeriod'], requiredIndicators: ['ema'], // "y" value is treated as Histogram data pointArrayMap: ['y', 'signal', 'MACD'], parallelArrays: ['x', 'y', 'signal', 'MACD'], pointValKey: 'y', // Columns support: markerAttribs: noop, getColumnMetrics: H.seriesTypes.column.prototype.getColumnMetrics, crispCol: H.seriesTypes.column.prototype.crispCol, // Colors and lines: init: function () { SMA.prototype.init.apply(this, arguments); // Check whether series is initialized. It may be not initialized, // when any of required indicators is missing. if (this.options) { // Set default color for a signal line and the histogram: this.options = merge({ signalLine: { styles: { lineColor: this.color } }, macdLine: { styles: { color: this.color } } }, this.options); // Zones have indexes automatically calculated, we need to // translate them to support multiple lines within one indicator this.macdZones = { zones: this.options.macdLine.zones, startIndex: 0 }; this.signalZones = { zones: this.macdZones.zones.concat( this.options.signalLine.zones ), startIndex: this.macdZones.zones.length }; this.resetZones = true; } }, toYData: function (point) { return [point.y, point.signal, point.MACD]; }, translate: function () { var indicator = this, plotNames = ['plotSignal', 'plotMACD']; H.seriesTypes.column.prototype.translate.apply(indicator); indicator.points.forEach(function (point) { [point.signal, point.MACD].forEach(function (value, i) { if (value !== null) { point[plotNames[i]] = indicator.yAxis.toPixels( value, true ); } }); }); }, destroy: function () { // this.graph is null due to removing two times the same SVG element this.graph = null; this.graphmacd = this.graphmacd && this.graphmacd.destroy(); this.graphsignal = this.graphsignal && this.graphsignal.destroy(); SMA.prototype.destroy.apply(this, arguments); }, drawPoints: H.seriesTypes.column.prototype.drawPoints, drawGraph: function () { var indicator = this, mainLinePoints = indicator.points, pointsLength = mainLinePoints.length, mainLineOptions = indicator.options, histogramZones = indicator.zones, gappedExtend = { options: { gapSize: mainLineOptions.gapSize } }, otherSignals = [[], []], point; // Generate points for top and bottom lines: while (pointsLength--) { point = mainLinePoints[pointsLength]; if (defined(point.plotMACD)) { otherSignals[0].push({ plotX: point.plotX, plotY: point.plotMACD, isNull: !defined(point.plotMACD) }); } if (defined(point.plotSignal)) { otherSignals[1].push({ plotX: point.plotX, plotY: point.plotSignal, isNull: !defined(point.plotMACD) }); } } // Modify options and generate smoothing line: ['macd', 'signal'].forEach(function (lineName, i) { indicator.points = otherSignals[i]; indicator.options = merge( mainLineOptions[lineName + 'Line'].styles, gappedExtend ); indicator.graph = indicator['graph' + lineName]; // Zones extension: indicator.currentLineZone = lineName + 'Zones'; indicator.zones = indicator[indicator.currentLineZone].zones; SMA.prototype.drawGraph.call(indicator); indicator['graph' + lineName] = indicator.graph; }); // Restore options: indicator.points = mainLinePoints; indicator.options = mainLineOptions; indicator.zones = histogramZones; indicator.currentLineZone = null; // indicator.graph = null; }, getZonesGraphs: function (props) { var allZones = SMA.prototype.getZonesGraphs.call(this, props), currentZones = allZones; if (this.currentLineZone) { currentZones = allZones.splice( this[this.currentLineZone].startIndex + 1 ); if (!currentZones.length) { // Line has no zones, return basic graph "zone" currentZones = [props[0]]; } else { // Add back basic prop: currentZones.splice(0, 0, props[0]); } } return currentZones; }, applyZones: function () { // Histogram zones are handled by drawPoints method // Here we need to apply zones for all lines var histogramZones = this.zones; // signalZones.zones contains all zones: this.zones = this.signalZones.zones; SMA.prototype.applyZones.call(this); // applyZones hides only main series.graph, hide macd line manually if (this.options.macdLine.zones.length) { this.graphmacd.hide(); } this.zones = histogramZones; }, getValues: function (series, params) { var j = 0, MACD = [], xMACD = [], yMACD = [], signalLine = [], shortEMA, longEMA, i; if (series.xData.length < params.longPeriod + params.signalPeriod) { return false; } // Calculating the short and long EMA used when calculating the MACD shortEMA = EMA.prototype.getValues( series, { period: params.shortPeriod } ); longEMA = EMA.prototype.getValues( series, { period: params.longPeriod } ); shortEMA = shortEMA.values; longEMA = longEMA.values; // Subtract each Y value from the EMA's and create the new dataset // (MACD) for (i = 1; i <= shortEMA.length; i++) { if ( defined(longEMA[i - 1]) && defined(longEMA[i - 1][1]) && defined(shortEMA[i + params.shortPeriod + 1]) && defined(shortEMA[i + params.shortPeriod + 1][0]) ) { MACD.push([ shortEMA[i + params.shortPeriod + 1][0], 0, null, shortEMA[i + params.shortPeriod + 1][1] - longEMA[i - 1][1] ]); } } // Set the Y and X data of the MACD. This is used in calculating the // signal line. for (i = 0; i < MACD.length; i++) { xMACD.push(MACD[i][0]); yMACD.push([0, null, MACD[i][3]]); } // Setting the signalline (Signal Line: X-day EMA of MACD line). signalLine = EMA.prototype.getValues( { xData: xMACD, yData: yMACD }, { period: params.signalPeriod, index: 2 } ); signalLine = signalLine.values; // Setting the MACD Histogram. In comparison to the loop with pure // MACD this loop uses MACD x value not xData. for (i = 0; i < MACD.length; i++) { if (MACD[i][0] >= signalLine[0][0]) { // detect the first point MACD[i][2] = signalLine[j][1]; yMACD[i] = [0, signalLine[j][1], MACD[i][3]]; if (MACD[i][3] === null) { MACD[i][1] = 0; yMACD[i][0] = 0; } else { MACD[i][1] = correctFloat(MACD[i][3] - signalLine[j][1]); yMACD[i][0] = correctFloat(MACD[i][3] - signalLine[j][1]); } j++; } } return { values: MACD, xData: xMACD, yData: yMACD }; } } ); /** * A `MACD` series. If the [type](#series.macd.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.macd * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.macd */ }); _registerModule(_modules, 'indicators/mfi.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * Money Flow Index indicator for Highstock * * (c) 2010-2019 Grzegorz Blachliński * * License: www.highcharts.com/license * * */ var isArray = U.isArray; // Utils: function sumArray(array) { return array.reduce(function (prev, cur) { return prev + cur; }); } function toFixed(a, n) { return parseFloat(a.toFixed(n)); } function calculateTypicalPrice(point) { return (point[1] + point[2] + point[3]) / 3; } function calculateRawMoneyFlow(typicalPrice, volume) { return typicalPrice * volume; } /** * The MFI series type. * * @private * @class * @name Highcharts.seriesTypes.mfi * * @augments Highcharts.Series */ H.seriesType( 'mfi', 'sma', /** * Money Flow Index. This series requires `linkedTo` option to be set and * should be loaded after the `stock/indicators/indicators.js` file. * * @sample stock/indicators/mfi * Money Flow Index Indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.mfi */ { /** * @excluding index */ params: { period: 14, /** * The id of volume series which is mandatory. * For example using OHLC data, volumeSeriesID='volume' means * the indicator will be calculated using OHLC and volume values. */ volumeSeriesID: 'volume', /** * Number of maximum decimals that are used in MFI calculations. */ decimals: 4 } }, /** * @lends Highcharts.Series# */ { nameBase: 'Money Flow Index', getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, decimals = params.decimals, // MFI starts calculations from the second point // Cause we need to calculate change between two points range = 1, volumeSeries = series.chart.get(params.volumeSeriesID), yValVolume = volumeSeries && volumeSeries.yData, MFI = [], isUp = false, xData = [], yData = [], positiveMoneyFlow = [], negativeMoneyFlow = [], newTypicalPrice, oldTypicalPrice, rawMoneyFlow, negativeMoneyFlowSum, positiveMoneyFlowSum, moneyFlowRatio, MFIPoint, i; if (!volumeSeries) { return H.error( 'Series ' + params.volumeSeriesID + ' not found! Check `volumeSeriesID`.', true, series.chart ); } // MFI requires high low and close values if ( (xVal.length <= period) || !isArray(yVal[0]) || yVal[0].length !== 4 || !yValVolume ) { return false; } // Calculate first typical price newTypicalPrice = calculateTypicalPrice(yVal[range]); // Accumulate first N-points while (range < period + 1) { // Calculate if up or down oldTypicalPrice = newTypicalPrice; newTypicalPrice = calculateTypicalPrice(yVal[range]); isUp = newTypicalPrice >= oldTypicalPrice; // Calculate raw money flow rawMoneyFlow = calculateRawMoneyFlow( newTypicalPrice, yValVolume[range] ); // Add to array positiveMoneyFlow.push(isUp ? rawMoneyFlow : 0); negativeMoneyFlow.push(isUp ? 0 : rawMoneyFlow); range++; } for (i = range - 1; i < yValLen; i++) { if (i > range - 1) { // Remove first point from array positiveMoneyFlow.shift(); negativeMoneyFlow.shift(); // Calculate if up or down oldTypicalPrice = newTypicalPrice; newTypicalPrice = calculateTypicalPrice(yVal[i]); isUp = newTypicalPrice > oldTypicalPrice; // Calculate raw money flow rawMoneyFlow = calculateRawMoneyFlow( newTypicalPrice, yValVolume[i] ); // Add to array positiveMoneyFlow.push(isUp ? rawMoneyFlow : 0); negativeMoneyFlow.push(isUp ? 0 : rawMoneyFlow); } // Calculate sum of negative and positive money flow: negativeMoneyFlowSum = sumArray(negativeMoneyFlow); positiveMoneyFlowSum = sumArray(positiveMoneyFlow); moneyFlowRatio = positiveMoneyFlowSum / negativeMoneyFlowSum; MFIPoint = toFixed( 100 - (100 / (1 + moneyFlowRatio)), decimals ); MFI.push([xVal[i], MFIPoint]); xData.push(xVal[i]); yData.push(MFIPoint); } return { values: MFI, xData: xData, yData: yData }; } } ); /** * A `MFI` series. If the [type](#series.mfi.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.mfi * @since 6.0.0 * @excluding dataParser, dataURL * @product highstock * @apioption series.mfi */ }); _registerModule(_modules, 'indicators/momentum.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; function populateAverage(points, xVal, yVal, i, period) { var mmY = yVal[i - 1][3] - yVal[i - period - 1][3], mmX = xVal[i - 1]; points.shift(); // remove point until range < period return [mmX, mmY]; } /** * The Momentum series type. * * @private * @class * @name Highcharts.seriesTypes.momentum * * @augments Highcharts.Series */ seriesType( 'momentum', 'sma', /** * Momentum. This series requires `linkedTo` option to be set. * * @sample stock/indicators/momentum * Momentum indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.momentum */ { params: { period: 14 } }, /** * @lends Highcharts.Series# */ { nameBase: 'Momentum', getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, xValue = xVal[0], yValue = yVal[0], MM = [], xData = [], yData = [], index, i, points, MMPoint; if (xVal.length <= period) { return false; } // Switch index for OHLC / Candlestick / Arearange if (isArray(yVal[0])) { yValue = yVal[0][3]; } else { return false; } // Starting point points = [ [xValue, yValue] ]; // Calculate value one-by-one for each period in visible data for (i = (period + 1); i < yValLen; i++) { MMPoint = populateAverage(points, xVal, yVal, i, period, index); MM.push(MMPoint); xData.push(MMPoint[0]); yData.push(MMPoint[1]); } MMPoint = populateAverage(points, xVal, yVal, i, period, index); MM.push(MMPoint); xData.push(MMPoint[0]); yData.push(MMPoint[1]); return { values: MM, xData: xData, yData: yData }; } } ); /** * A `Momentum` series. If the [type](#series.momentum.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.momentum * @since 6.0.0 * @excluding dataParser, dataURL * @product highstock * @apioption series.momentum */ }); _registerModule(_modules, 'indicators/natr.src.js', [_modules['parts/Globals.js']], function (H) { /* * * * License: www.highcharts.com/license * * */ var ATR = H.seriesTypes.atr; /** * The NATR series type. * * @private * @class * @name Highcharts.seriesTypes.natr * * @augments Highcharts.Series */ H.seriesType('natr', 'sma', /** * Normalized average true range indicator (NATR). This series requires * `linkedTo` option to be set and should be loaded after the * `stock/indicators/indicators.js` and `stock/indicators/atr.js`. * * @sample {highstock} stock/indicators/natr * NATR indicator * * @extends plotOptions.atr * @since 7.0.0 * @product highstock * @optionparent plotOptions.natr */ { tooltip: { valueSuffix: '%' } }, /** * @lends Highcharts.Series# */ { requiredIndicators: ['atr'], getValues: function (series, params) { var atrData = ATR.prototype.getValues.apply(this, arguments), atrLength = atrData.values.length, period = params.period - 1, yVal = series.yData, i = 0; for (; i < atrLength; i++) { atrData.yData[i] = atrData.values[i][1] / yVal[period][3] * 100; atrData.values[i][1] = atrData.yData[i]; period++; } return atrData; } }); /** * A `NATR` series. If the [type](#series.natr.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.natr * @since 7.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.natr */ }); _registerModule(_modules, 'indicators/pivot-points.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var defined = U.defined, isArray = U.isArray; var SMA = H.seriesTypes.sma; function destroyExtraLabels(point, functionName) { var props = point.series.pointArrayMap, prop, i = props.length; SMA.prototype.pointClass.prototype[functionName].call(point); while (i--) { prop = 'dataLabel' + props[i]; // S4 dataLabel could be removed by parent method: if (point[prop] && point[prop].element) { point[prop].destroy(); } point[prop] = null; } } /** * The Pivot Points series type. * * @private * @class * @name Highcharts.seriesTypes.pivotpoints * * @augments Highcharts.Series */ H.seriesType( 'pivotpoints', 'sma', /** * Pivot points indicator. This series requires the `linkedTo` option to be * set and should be loaded after `stock/indicators/indicators.js` file. * * @sample stock/indicators/pivot-points * Pivot points * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.pivotpoints */ { /** * @excluding index */ params: { period: 28, /** * Algorithm used to calculate ressistance and support lines based * on pivot points. Implemented algorithms: `'standard'`, * `'fibonacci'` and `'camarilla'` */ algorithm: 'standard' }, marker: { enabled: false }, enableMouseTracking: false, dataLabels: { /** @ignore-option */ enabled: true, /** @ignore-option */ format: '{point.pivotLine}' }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ { nameBase: 'Pivot Points', pointArrayMap: ['R4', 'R3', 'R2', 'R1', 'P', 'S1', 'S2', 'S3', 'S4'], pointValKey: 'P', toYData: function (point) { return [point.P]; // The rest should not affect extremes }, translate: function () { var indicator = this; SMA.prototype.translate.apply(indicator); indicator.points.forEach(function (point) { indicator.pointArrayMap.forEach(function (value) { if (defined(point[value])) { point['plot' + value] = indicator.yAxis.toPixels( point[value], true ); } }); }); // Pivot points are rendered as horizontal lines // And last point start not from the next one (as it's the last one) // But from the approximated last position in a given range indicator.plotEndPoint = indicator.xAxis.toPixels( indicator.endPoint, true ); }, getGraphPath: function (points) { var indicator = this, pointsLength = points.length, allPivotPoints = [[], [], [], [], [], [], [], [], []], path = [], endPoint = indicator.plotEndPoint, pointArrayMapLength = indicator.pointArrayMap.length, position, point, i; while (pointsLength--) { point = points[pointsLength]; for (i = 0; i < pointArrayMapLength; i++) { position = indicator.pointArrayMap[i]; if (defined(point[position])) { allPivotPoints[i].push({ // Start left: plotX: point.plotX, plotY: point['plot' + position], isNull: false }, { // Go to right: plotX: endPoint, plotY: point['plot' + position], isNull: false }, { // And add null points in path to generate breaks: plotX: endPoint, plotY: null, isNull: true }); } } endPoint = point.plotX; } allPivotPoints.forEach(function (pivotPoints) { path = path.concat( SMA.prototype.getGraphPath.call(indicator, pivotPoints) ); }); return path; }, // TODO: Rewrite this logic to use multiple datalabels drawDataLabels: function () { var indicator = this, pointMapping = indicator.pointArrayMap, currentLabel, pointsLength, point, i; if (indicator.options.dataLabels.enabled) { pointsLength = indicator.points.length; // For every Ressitance/Support group we need to render labels. // Add one more item, which will just store dataLabels from // previous iteration pointMapping.concat([false]).forEach(function (position, k) { i = pointsLength; while (i--) { point = indicator.points[i]; if (!position) { // Store S4 dataLabel too: point['dataLabel' + pointMapping[k - 1]] = point.dataLabel; } else { point.y = point[position]; point.pivotLine = position; point.plotY = point['plot' + position]; currentLabel = point['dataLabel' + position]; // Store previous label if (k) { point['dataLabel' + pointMapping[k - 1]] = point.dataLabel; } if (!point.dataLabels) { point.dataLabels = []; } point.dataLabels[0] = point.dataLabel = currentLabel = currentLabel && currentLabel.element ? currentLabel : null; } } SMA.prototype.drawDataLabels.apply(indicator, arguments); }); } }, getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, placement = this[params.algorithm + 'Placement'], PP = [], // 0- from, 1- to, 2- R1, 3- R2, 4- pivot, 5- S1 etc. endTimestamp, xData = [], yData = [], slicedXLen, slicedX, slicedY, lastPP, pivot, avg, i; // Pivot Points requires high, low and close values if ( xVal.length < period || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } for (i = period + 1; i <= yValLen + period; i += period) { slicedX = xVal.slice(i - period - 1, i); slicedY = yVal.slice(i - period - 1, i); slicedXLen = slicedX.length; endTimestamp = slicedX[slicedXLen - 1]; pivot = this.getPivotAndHLC(slicedY); avg = placement(pivot); lastPP = PP.push( [endTimestamp] .concat(avg) ); xData.push(endTimestamp); yData.push(PP[lastPP - 1].slice(1)); } // We don't know exact position in ordinal axis // So we use simple logic: // Get first point in last range, calculate visible average range // and multiply by period this.endPoint = slicedX[0] + ((endTimestamp - slicedX[0]) / slicedXLen) * period; return { values: PP, xData: xData, yData: yData }; }, getPivotAndHLC: function (values) { var high = -Infinity, low = Infinity, close = values[values.length - 1][3], pivot; values.forEach(function (p) { high = Math.max(high, p[1]); low = Math.min(low, p[2]); }); pivot = (high + low + close) / 3; return [pivot, high, low, close]; }, standardPlacement: function (values) { var diff = values[1] - values[2], avg = [ null, null, values[0] + diff, values[0] * 2 - values[2], values[0], values[0] * 2 - values[1], values[0] - diff, null, null ]; return avg; }, camarillaPlacement: function (values) { var diff = values[1] - values[2], avg = [ values[3] + diff * 1.5, values[3] + diff * 1.25, values[3] + diff * 1.1666, values[3] + diff * 1.0833, values[0], values[3] - diff * 1.0833, values[3] - diff * 1.1666, values[3] - diff * 1.25, values[3] - diff * 1.5 ]; return avg; }, fibonacciPlacement: function (values) { var diff = values[1] - values[2], avg = [ null, values[0] + diff, values[0] + diff * 0.618, values[0] + diff * 0.382, values[0], values[0] - diff * 0.382, values[0] - diff * 0.618, values[0] - diff, null ]; return avg; } }, /** * @lends Highcharts.Point# */ { // Destroy labels: // This method is called when cropping data: destroyElements: function () { destroyExtraLabels(this, 'destroyElements'); }, // This method is called when removing points, e.g. series.update() destroy: function () { destroyExtraLabels(this, 'destroyElements'); } } ); /** * A pivot points indicator. If the [type](#series.pivotpoints.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.pivotpoints * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.pivotpoints */ }); _registerModule(_modules, 'indicators/ppo.src.js', [_modules['parts/Globals.js'], _modules['mixins/indicator-required.js']], function (H, requiredIndicatorMixin) { /* * * * License: www.highcharts.com/license * * */ var EMA = H.seriesTypes.ema, error = H.error, correctFloat = H.correctFloat, requiredIndicator = requiredIndicatorMixin; /** * The PPO series type. * * @private * @class * @name Highcharts.seriesTypes.ppo * * @augments Highcharts.Series */ H.seriesType( 'ppo', 'ema', /** * Percentage Price Oscillator. This series requires the * `linkedTo` option to be set and should be loaded after the * `stock/indicators/indicators.js` and `stock/indicators/ema.js`. * * @sample {highstock} stock/indicators/ppo * Percentage Price Oscillator * * @extends plotOptions.ema * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, joinBy, keys, navigatorOptions, * pointInterval, pointIntervalUnit, pointPlacement, * pointRange, pointStart, showInNavigator, stacking * @optionparent plotOptions.ppo */ { /** * Paramters used in calculation of Percentage Price Oscillator series * points. * * @excluding period */ params: { /** * Periods for Percentage Price Oscillator calculations. * * @type {Array<number>} * @default [12, 26] */ periods: [12, 26] } }, /** * @lends Highcharts.Series.prototype */ { nameBase: 'PPO', nameComponents: ['periods'], init: function () { var args = arguments, ctx = this; requiredIndicator.isParentLoaded( EMA, 'ema', ctx.type, function (indicator) { indicator.prototype.init.apply(ctx, args); } ); }, getValues: function (series, params) { var periods = params.periods, index = params.index, PPO = [], // 0- date, 1- Percentage Price Oscillator xData = [], yData = [], periodsOffset, SPE, // Shorter Period EMA LPE, // Longer Period EMA oscillator, i; // Check if periods are correct if (periods.length !== 2 || periods[1] <= periods[0]) { error( 'Error: "PPO requires two periods. Notice, first period ' + 'should be lower than the second one."' ); return false; } SPE = EMA.prototype.getValues.call(this, series, { index: index, period: periods[0] }); LPE = EMA.prototype.getValues.call(this, series, { index: index, period: periods[1] }); // Check if ema is calculated properly, if not skip if (!SPE || !LPE) { return false; } periodsOffset = periods[1] - periods[0]; for (i = 0; i < LPE.yData.length; i++) { oscillator = correctFloat( (SPE.yData[i + periodsOffset] - LPE.yData[i]) / LPE.yData[i] * 100 ); PPO.push([LPE.xData[i], oscillator]); xData.push(LPE.xData[i]); yData.push(oscillator); } return { values: PPO, xData: xData, yData: yData }; } } ); /** * A `Percentage Price Oscillator` series. If the [type](#series.ppo.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ppo * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, dataParser, dataURL, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.ppo */ }); _registerModule(_modules, 'mixins/reduce-array.js', [_modules['parts/Globals.js']], function (H) { /** * * (c) 2010-2019 Pawel Fus & Daniel Studencki * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var reduce = H.reduce; var reduceArrayMixin = { /** * Get min value of array filled by OHLC data. * @privagte * @param {Array<Highcharts.OHLCPoint>} arr Array of OHLC points (arrays). * @param {string} index Index of "low" value in point array. * @return {number} Returns min value. */ minInArray: function (arr, index) { return reduce(arr, function (min, target) { return Math.min(min, target[index]); }, Number.MAX_VALUE); }, /** * Get max value of array filled by OHLC data. * @private * @param {Array<Highcharts.OHLCPoint>} arr Array of OHLC points (arrays). * @param {string} index Index of "high" value in point array. * @return {number} Returns max value. */ maxInArray: function (arr, index) { return reduce(arr, function (max, target) { return Math.max(max, target[index]); }, -Number.MAX_VALUE); }, /** * Get extremes of array filled by OHLC data. * @private * @param {Array<Highcharts.OHLCPoint>} arr Array of OHLC points (arrays). * @param {string} minIndex Index of "low" value in point array. * @param {string} maxIndex Index of "high" value in point array. * @return {Array<number,number>} Returns array with min and max value. */ getArrayExtremes: function (arr, minIndex, maxIndex) { return reduce(arr, function (prev, target) { return [ Math.min(prev[0], target[minIndex]), Math.max(prev[1], target[maxIndex]) ]; }, [Number.MAX_VALUE, -Number.MAX_VALUE]); } }; return reduceArrayMixin; }); _registerModule(_modules, 'indicators/price-channel.src.js', [_modules['parts/Globals.js'], _modules['mixins/reduce-array.js'], _modules['mixins/multipe-lines.js']], function (H, reduceArrayMixin, multipleLinesMixin) { /* * * * License: www.highcharts.com/license * * */ var getArrayExtremes = reduceArrayMixin.getArrayExtremes, merge = H.merge; /** * The Price Channel series type. * * @private * @class * @name Highcharts.seriesTypes.pc * * @augments Highcharts.Series */ H.seriesType( 'pc', 'sma', /** * Price channel (PC). This series requires the `linkedTo` option to be * set and should be loaded after the `stock/indicators/indicators.js`. * * @sample {highstock} stock/indicators/price-channel * Price Channel * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking * @optionparent plotOptions.pc */ { /** * @excluding index */ params: { period: 20 }, lineWidth: 1, topLine: { styles: { /** * Color of the top line. If not set, it's inherited from * [plotOptions.pc.color](#plotOptions.pc.color). * * @type {Highcharts.ColorString} */ lineColor: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' ')[2], /** * Pixel width of the line. */ lineWidth: 1 } }, bottomLine: { styles: { /** * Color of the bottom line. If not set, it's inherited from * [plotOptions.pc.color](#plotOptions.pc.color). * * @type {Highcharts.ColorString} */ lineColor: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' ')[8], /** * Pixel width of the line. */ lineWidth: 1 } }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ merge(multipleLinesMixin, { pointArrayMap: ['top', 'middle', 'bottom'], pointValKey: 'middle', nameBase: 'Price Channel', nameComponents: ['period'], linesApiNames: ['topLine', 'bottomLine'], getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, PC = [], // 0- date, 1-top line, 2-middle line, 3-bottom line ML, TL, BL, // middle line, top line and bottom line date, low = 2, high = 1, xData = [], yData = [], slicedY, extremes, i; if (yValLen < period) { return false; } for (i = period; i <= yValLen; i++) { date = xVal[i - 1]; slicedY = yVal.slice(i - period, i); extremes = getArrayExtremes(slicedY, low, high); TL = extremes[1]; BL = extremes[0]; ML = (TL + BL) / 2; PC.push([date, TL, ML, BL]); xData.push(date); yData.push([TL, ML, BL]); } return { values: PC, xData: xData, yData: yData }; } }) ); /** * A Price channel indicator. If the [type](#series.pc.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.pc * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, * pointIntervalUnit, pointPlacement, pointRange, pointStart, * showInNavigator, stacking * @optionparent series.pc */ }); _registerModule(_modules, 'indicators/price-envelopes.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var merge = H.merge, SMA = H.seriesTypes.sma; /** * The Price Envelopes series type. * * @private * @class * @name Highcharts.seriesTypes.priceenvelopes * * @augments Highcharts.Series */ H.seriesType( 'priceenvelopes', 'sma', /** * Price envelopes indicator based on [SMA](#plotOptions.sma) calculations. * This series requires the `linkedTo` option to be set and should be loaded * after the `stock/indicators/indicators.js` file. * * @sample stock/indicators/price-envelopes * Price envelopes * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.priceenvelopes */ { marker: { enabled: false }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span><b> {series.name}</b><br/>Top: {point.top}<br/>Middle: {point.middle}<br/>Bottom: {point.bottom}<br/>' }, params: { period: 20, /** * Percentage above the moving average that should be displayed. * 0.1 means 110%. Relative to the calculated value. */ topBand: 0.1, /** * Percentage below the moving average that should be displayed. * 0.1 means 90%. Relative to the calculated value. */ bottomBand: 0.1 }, /** * Bottom line options. */ bottomLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. If not set, it's inherited from * [plotOptions.priceenvelopes.color]( * #plotOptions.priceenvelopes.color). * * @type {Highcharts.ColorString} */ lineColor: undefined } }, /** * Top line options. * * @extends plotOptions.priceenvelopes.bottomLine */ topLine: { styles: { lineWidth: 1 } }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ { nameComponents: ['period', 'topBand', 'bottomBand'], nameBase: 'Price envelopes', pointArrayMap: ['top', 'middle', 'bottom'], parallelArrays: ['x', 'y', 'top', 'bottom'], pointValKey: 'middle', init: function () { SMA.prototype.init.apply(this, arguments); // Set default color for lines: this.options = merge({ topLine: { styles: { lineColor: this.color } }, bottomLine: { styles: { lineColor: this.color } } }, this.options); }, toYData: function (point) { return [point.top, point.middle, point.bottom]; }, translate: function () { var indicator = this, translatedEnvelopes = ['plotTop', 'plotMiddle', 'plotBottom']; SMA.prototype.translate.apply(indicator); indicator.points.forEach(function (point) { [point.top, point.middle, point.bottom].forEach( function (value, i) { if (value !== null) { point[translatedEnvelopes[i]] = indicator.yAxis.toPixels(value, true); } } ); }); }, drawGraph: function () { var indicator = this, middleLinePoints = indicator.points, pointsLength = middleLinePoints.length, middleLineOptions = indicator.options, middleLinePath = indicator.graph, gappedExtend = { options: { gapSize: middleLineOptions.gapSize } }, deviations = [[], []], // top and bottom point place holders point; // Generate points for top and bottom lines: while (pointsLength--) { point = middleLinePoints[pointsLength]; deviations[0].push({ plotX: point.plotX, plotY: point.plotTop, isNull: point.isNull }); deviations[1].push({ plotX: point.plotX, plotY: point.plotBottom, isNull: point.isNull }); } // Modify options and generate lines: ['topLine', 'bottomLine'].forEach(function (lineName, i) { indicator.points = deviations[i]; indicator.options = merge( middleLineOptions[lineName].styles, gappedExtend ); indicator.graph = indicator['graph' + lineName]; SMA.prototype.drawGraph.call(indicator); // Now save lines: indicator['graph' + lineName] = indicator.graph; }); // Restore options and draw a middle line: indicator.points = middleLinePoints; indicator.options = middleLineOptions; indicator.graph = middleLinePath; SMA.prototype.drawGraph.call(indicator); }, getValues: function (series, params) { var period = params.period, topPercent = params.topBand, botPercent = params.bottomBand, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, PE = [], // 0- date, 1-top line, 2-middle line, 3-bottom line ML, TL, BL, // middle line, top line and bottom line date, xData = [], yData = [], slicedX, slicedY, point, i; // Price envelopes requires close value if ( xVal.length < period || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } for (i = period; i <= yValLen; i++) { slicedX = xVal.slice(i - period, i); slicedY = yVal.slice(i - period, i); point = SMA.prototype.getValues.call(this, { xData: slicedX, yData: slicedY }, params); date = point.xData[0]; ML = point.yData[0]; TL = ML * (1 + topPercent); BL = ML * (1 - botPercent); PE.push([date, TL, ML, BL]); xData.push(date); yData.push([TL, ML, BL]); } return { values: PE, xData: xData, yData: yData }; } } ); /** * A price envelopes indicator. If the [type](#series.priceenvelopes.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.priceenvelopes * @since 6.0.0 * @excluding dataParser, dataURL * @product highstock * @apioption series.priceenvelopes */ }); _registerModule(_modules, 'indicators/psar.src.js', [_modules['parts/Globals.js']], function (H) { /* * * * Parabolic SAR indicator for Highstock * * (c) 2010-2019 Grzegorz Blachliński * * License: www.highcharts.com/license * * */ // Utils: function toFixed(a, n) { return parseFloat(a.toFixed(n)); } function calculateDirection(previousDirection, low, high, PSAR) { if ( (previousDirection === 1 && low > PSAR) || (previousDirection === -1 && high > PSAR) ) { return 1; } return -1; } /* * * Method for calculating acceleration factor * dir - direction * pDir - previous Direction * eP - extreme point * pEP - previous extreme point * inc - increment for acceleration factor * maxAcc - maximum acceleration factor * initAcc - initial acceleration factor */ function getAccelerationFactor(dir, pDir, eP, pEP, pAcc, inc, maxAcc, initAcc) { if (dir === pDir) { if (dir === 1 && (eP > pEP)) { return (pAcc === maxAcc) ? maxAcc : toFixed(pAcc + inc, 2); } if (dir === -1 && (eP < pEP)) { return (pAcc === maxAcc) ? maxAcc : toFixed(pAcc + inc, 2); } return pAcc; } return initAcc; } function getExtremePoint(high, low, previousDirection, previousExtremePoint) { if (previousDirection === 1) { return (high > previousExtremePoint) ? high : previousExtremePoint; } return (low < previousExtremePoint) ? low : previousExtremePoint; } function getEPMinusPSAR(EP, PSAR) { return EP - PSAR; } function getAccelerationFactorMultiply(accelerationFactor, EPMinusSAR) { return accelerationFactor * EPMinusSAR; } /* * * Method for calculating PSAR * pdir - previous direction * sDir - second previous Direction * PSAR - previous PSAR * pACCMultiply - previous acceleration factor multiply * sLow - second previous low * pLow - previous low * sHigh - second previous high * pHigh - previous high * pEP - previous extreme point */ function getPSAR(pdir, sDir, PSAR, pACCMulti, sLow, pLow, pHigh, sHigh, pEP) { if (pdir === sDir) { if (pdir === 1) { return (PSAR + pACCMulti < Math.min(sLow, pLow)) ? PSAR + pACCMulti : Math.min(sLow, pLow); } return (PSAR + pACCMulti > Math.max(sHigh, pHigh)) ? PSAR + pACCMulti : Math.max(sHigh, pHigh); } return pEP; } /** * The Parabolic SAR series type. * * @private * @class * @name Highcharts.seriesTypes.psar * * @augments Highcharts.Series */ H.seriesType( 'psar', 'sma', /** * Parabolic SAR. This series requires `linkedTo` * option to be set and should be loaded * after `stock/indicators/indicators.js` file. * * @sample stock/indicators/psar * Parabolic SAR Indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.psar */ { lineWidth: 0, marker: { enabled: true }, states: { hover: { lineWidthPlus: 0 } }, /** * @excluding period */ params: { /** * The initial value for acceleration factor. * Acceleration factor is starting with this value * and increases by specified increment each time * the extreme point makes a new high. * AF can reach a maximum of maxAccelerationFactor, * no matter how long the uptrend extends. */ initialAccelerationFactor: 0.02, /** * The Maximum value for acceleration factor. * AF can reach a maximum of maxAccelerationFactor, * no matter how long the uptrend extends. */ maxAccelerationFactor: 0.2, /** * Acceleration factor increases by increment each time * the extreme point makes a new high. * * @since 6.0.0 */ increment: 0.02, /** * Index from which PSAR is starting calculation * * @since 6.0.0 */ index: 2, /** * Number of maximum decimals that are used in PSAR calculations. * * @since 6.0.0 */ decimals: 4 } }, { nameComponents: false, getValues: function (series, params) { var xVal = series.xData, yVal = series.yData, // Extreme point is the lowest low for falling and highest high // for rising psar - and we are starting with falling extremePoint = yVal[0][1], accelerationFactor = params.initialAccelerationFactor, maxAccelerationFactor = params.maxAccelerationFactor, increment = params.increment, // Set initial acc factor (for every new trend!) initialAccelerationFactor = params.initialAccelerationFactor, PSAR = yVal[0][2], decimals = params.decimals, index = params.index, PSARArr = [], xData = [], yData = [], previousDirection = 1, direction, EPMinusPSAR, accelerationFactorMultiply, newDirection, prevLow, prevPrevLow, prevHigh, prevPrevHigh, newExtremePoint, high, low, ind; if (index >= yVal.length) { return false; } for (ind = 0; ind < index; ind++) { extremePoint = Math.max(yVal[ind][1], extremePoint); PSAR = Math.min(yVal[ind][2], toFixed(PSAR, decimals)); } direction = (yVal[ind][1] > PSAR) ? 1 : -1; EPMinusPSAR = getEPMinusPSAR(extremePoint, PSAR); accelerationFactor = params.initialAccelerationFactor; accelerationFactorMultiply = getAccelerationFactorMultiply( accelerationFactor, EPMinusPSAR ); PSARArr.push([xVal[index], PSAR]); xData.push(xVal[index]); yData.push(toFixed(PSAR, decimals)); for (ind = index + 1; ind < yVal.length; ind++) { prevLow = yVal[ind - 1][2]; prevPrevLow = yVal[ind - 2][2]; prevHigh = yVal[ind - 1][1]; prevPrevHigh = yVal[ind - 2][1]; high = yVal[ind][1]; low = yVal[ind][2]; // Null points break PSAR if ( prevPrevLow !== null && prevPrevHigh !== null && prevLow !== null && prevHigh !== null && high !== null && low !== null ) { PSAR = getPSAR( direction, previousDirection, PSAR, accelerationFactorMultiply, prevPrevLow, prevLow, prevHigh, prevPrevHigh, extremePoint ); newExtremePoint = getExtremePoint( high, low, direction, extremePoint ); newDirection = calculateDirection( previousDirection, low, high, PSAR ); accelerationFactor = getAccelerationFactor( newDirection, direction, newExtremePoint, extremePoint, accelerationFactor, increment, maxAccelerationFactor, initialAccelerationFactor ); EPMinusPSAR = getEPMinusPSAR(newExtremePoint, PSAR); accelerationFactorMultiply = getAccelerationFactorMultiply( accelerationFactor, EPMinusPSAR ); PSARArr.push([xVal[ind], toFixed(PSAR, decimals)]); xData.push(xVal[ind]); yData.push(toFixed(PSAR, decimals)); previousDirection = direction; direction = newDirection; extremePoint = newExtremePoint; } } return { values: PSARArr, xData: xData, yData: yData }; } } ); /** * A `PSAR` series. If the [type](#series.psar.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.psar * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.psar */ }); _registerModule(_modules, 'indicators/roc.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2010-2019 Kacper Madej * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; // Utils: function populateAverage(xVal, yVal, i, period, index) { /* Calculated as: (Closing Price [today] - Closing Price [n days ago]) / Closing Price [n days ago] * 100 Return y as null when avoiding division by zero */ var nDaysAgoY, rocY; if (index < 0) { // y data given as an array of values nDaysAgoY = yVal[i - period]; rocY = nDaysAgoY ? (yVal[i] - nDaysAgoY) / nDaysAgoY * 100 : null; } else { // y data given as an array of arrays and the index should be used nDaysAgoY = yVal[i - period][index]; rocY = nDaysAgoY ? (yVal[i][index] - nDaysAgoY) / nDaysAgoY * 100 : null; } return [xVal[i], rocY]; } /** * The ROC series type. * * @private * @class * @name Highcharts.seriesTypes.roc * * @augments Highcharts.Series */ seriesType( 'roc', 'sma', /** * Rate of change indicator (ROC). The indicator value for each point * is defined as: * * `(C - Cn) / Cn * 100` * * where: `C` is the close value of the point of the same x in the * linked series and `Cn` is the close value of the point `n` periods * ago. `n` is set through [period](#plotOptions.roc.params.period). * * This series requires `linkedTo` option to be set. * * @sample stock/indicators/roc * Rate of change indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.roc */ { params: { index: 3, period: 9 } }, /** * @lends Highcharts.Series# */ { nameBase: 'Rate of Change', getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, ROC = [], xData = [], yData = [], i, index = -1, ROCPoint; // Period is used as a number of time periods ago, so we need more // (at least 1 more) data than the period value if (xVal.length <= period) { return false; } // Switch index for OHLC / Candlestick / Arearange if (isArray(yVal[0])) { index = params.index; } // i = period <-- skip first N-points // Calculate value one-by-one for each period in visible data for (i = period; i < yValLen; i++) { ROCPoint = populateAverage(xVal, yVal, i, period, index); ROC.push(ROCPoint); xData.push(ROCPoint[0]); yData.push(ROCPoint[1]); } return { values: ROC, xData: xData, yData: yData }; } } ); /** * A `ROC` series. If the [type](#series.wma.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * Rate of change indicator (ROC). The indicator value for each point * is defined as: * * `(C - Cn) / Cn * 100` * * where: `C` is the close value of the point of the same x in the * linked series and `Cn` is the close value of the point `n` periods * ago. `n` is set through [period](#series.roc.params.period). * * This series requires `linkedTo` option to be set. * * @extends series,plotOptions.roc * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.roc */ }); _registerModule(_modules, 'indicators/rsi.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; // Utils: function toFixed(a, n) { return parseFloat(a.toFixed(n)); } /** * The RSI series type. * * @private * @class * @name Highcharts.seriesTypes.rsi * * @augments Highcharts.Series */ H.seriesType( 'rsi', 'sma', /** * Relative strength index (RSI) technical indicator. This series * requires the `linkedTo` option to be set and should be loaded after * the `stock/indicators/indicators.js` file. * * @sample stock/indicators/rsi * RSI indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.rsi */ { /** * @excluding index */ params: { period: 14, /** * Number of maximum decimals that are used in RSI calculations. */ decimals: 4 } }, /** * @lends Highcharts.Series# */ { getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, decimals = params.decimals, // RSI starts calculations from the second point // Cause we need to calculate change between two points range = 1, RSI = [], xData = [], yData = [], index = 3, gain = 0, loss = 0, RSIPoint, change, avgGain, avgLoss, i; // RSI requires close value if ( (xVal.length < period) || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } // Calculate changes for first N points while (range < period) { change = toFixed( yVal[range][index] - yVal[range - 1][index], decimals ); if (change > 0) { gain += change; } else { loss += Math.abs(change); } range++; } // Average for first n-1 points: avgGain = toFixed(gain / (period - 1), decimals); avgLoss = toFixed(loss / (period - 1), decimals); for (i = range; i < yValLen; i++) { change = toFixed(yVal[i][index] - yVal[i - 1][index], decimals); if (change > 0) { gain = change; loss = 0; } else { gain = 0; loss = Math.abs(change); } // Calculate smoothed averages, RS, RSI values: avgGain = toFixed( (avgGain * (period - 1) + gain) / period, decimals ); avgLoss = toFixed( (avgLoss * (period - 1) + loss) / period, decimals ); // If average-loss is equal zero, then by definition RSI is set // to 100: if (avgLoss === 0) { RSIPoint = 100; // If average-gain is equal zero, then by definition RSI is set // to 0: } else if (avgGain === 0) { RSIPoint = 0; } else { RSIPoint = toFixed( 100 - (100 / (1 + (avgGain / avgLoss))), decimals ); } RSI.push([xVal[i], RSIPoint]); xData.push(xVal[i]); yData.push(RSIPoint); } return { values: RSI, xData: xData, yData: yData }; } } ); /** * A `RSI` series. If the [type](#series.rsi.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.rsi * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.rsi */ }); _registerModule(_modules, 'indicators/stochastic.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/reduce-array.js'], _modules['mixins/multipe-lines.js']], function (H, U, reduceArrayMixin, multipleLinesMixin) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var merge = H.merge, SMA = H.seriesTypes.sma, getArrayExtremes = reduceArrayMixin.getArrayExtremes; /** * The Stochastic series type. * * @private * @class * @name Highcharts.seriesTypes.stochastic * * @augments Highcharts.Series */ H.seriesType( 'stochastic', 'sma', /** * Stochastic oscillator. This series requires the `linkedTo` option to be * set and should be loaded after the `stock/indicators/indicators.js` file. * * @sample stock/indicators/stochastic * Stochastic oscillator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @excluding allAreas, colorAxis, joinBy, keys, navigatorOptions, * pointInterval, pointIntervalUnit, pointPlacement, * pointRange, pointStart, showInNavigator, stacking * @optionparent plotOptions.stochastic */ { /** * @excluding index, period */ params: { /** * Periods for Stochastic oscillator: [%K, %D]. * * @type {Array<number,number>} * @default [14, 3] */ periods: [14, 3] }, marker: { enabled: false }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span><b> {series.name}</b><br/>%K: {point.y}<br/>%D: {point.smoothed}<br/>' }, /** * Smoothed line options. */ smoothedLine: { /** * Styles for a smoothed line. */ styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. If not set, it's inherited from * [plotOptions.stochastic.color * ](#plotOptions.stochastic.color). * * @type {Highcharts.ColorString} */ lineColor: undefined } }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ H.merge(multipleLinesMixin, { nameComponents: ['periods'], nameBase: 'Stochastic', pointArrayMap: ['y', 'smoothed'], parallelArrays: ['x', 'y', 'smoothed'], pointValKey: 'y', linesApiNames: ['smoothedLine'], init: function () { SMA.prototype.init.apply(this, arguments); // Set default color for lines: this.options = merge({ smoothedLine: { styles: { lineColor: this.color } } }, this.options); }, getValues: function (series, params) { var periodK = params.periods[0], periodD = params.periods[1], xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, SO = [], // 0- date, 1-%K, 2-%D xData = [], yData = [], slicedY, close = 3, low = 2, high = 1, CL, HL, LL, K, D = null, points, extremes, i; // Stochastic requires close value if ( yValLen < periodK || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } // For a N-period, we start from N-1 point, to calculate Nth point // That is why we later need to comprehend slice() elements list // with (+1) for (i = periodK - 1; i < yValLen; i++) { slicedY = yVal.slice(i - periodK + 1, i + 1); // Calculate %K extremes = getArrayExtremes(slicedY, low, high); LL = extremes[0]; // Lowest low in %K periods CL = yVal[i][close] - LL; HL = extremes[1] - LL; K = CL / HL * 100; xData.push(xVal[i]); yData.push([K, null]); // Calculate smoothed %D, which is SMA of %K if (i >= (periodK - 1) + (periodD - 1)) { points = SMA.prototype.getValues.call(this, { xData: xData.slice(-periodD), yData: yData.slice(-periodD) }, { period: periodD }); D = points.yData[0]; } SO.push([xVal[i], K, D]); yData[yData.length - 1][1] = D; } return { values: SO, xData: xData, yData: yData }; } }) ); /** * A Stochastic indicator. If the [type](#series.stochastic.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.stochastic * @since 6.0.0 * @product highstock * @excluding allAreas, colorAxis, dataParser, dataURL, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.stochastic */ }); _registerModule(_modules, 'indicators/supertrend.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray, objectEach = U.objectEach; var ATR = H.seriesTypes.atr, SMA = H.seriesTypes.sma, merge = H.merge, correctFloat = H.correctFloat; // Utils: function createPointObj(mainSeries, index, close) { return { index: index, close: mainSeries.yData[index][close], x: mainSeries.xData[index] }; } /** * The Supertrend series type. * * @private * @class * @name Highcharts.seriesTypes.supertrend * * @augments Highcharts.Series */ H.seriesType( 'supertrend', 'sma', /** * Supertrend indicator. This series requires the `linkedTo` option to be * set and should be loaded after the `stock/indicators/indicators.js` and * `stock/indicators/sma.js`. * * @sample {highstock} stock/indicators/supertrend * Supertrend indicator * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, color, cropThreshold, negativeColor, colorAxis, * joinBy, keys, navigatorOptions, pointInterval, * pointIntervalUnit, pointPlacement, pointRange, pointStart, * showInNavigator, stacking, threshold * @optionparent plotOptions.supertrend */ { /** * Paramters used in calculation of Supertrend indicator series points. * * @excluding index */ params: { /** * Multiplier for Supertrend Indicator. */ multiplier: 3, /** * The base period for indicator Supertrend Indicator calculations. * This is the number of data points which are taken into account * for the indicator calculations. */ period: 10 }, /** * Color of the Supertrend series line that is beneath the main series. * * @sample {highstock} stock/indicators/supertrend/ * Example with risingTrendColor * * @type {Highcharts.ColorString} */ risingTrendColor: '#06B535', /** * Color of the Supertrend series line that is above the main series. * * @sample {highstock} stock/indicators/supertrend/ * Example with fallingTrendColor * * @type {Highcharts.ColorString} */ fallingTrendColor: '#F21313', /** * The styles for the Supertrend line that intersect main series. * * @sample {highstock} stock/indicators/supertrend/ * Example with changeTrendLine */ changeTrendLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1, /** * Color of the line. * * @type {Highcharts.ColorString} */ lineColor: '#333333', /** * The dash or dot style of the grid lines. For possible * values, see * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). * * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/ * Long dashes * @sample {highstock} stock/xaxis/gridlinedashstyle/ * Long dashes * * @type {Highcharts.DashStyleValue} * @since 7.0.0 */ dashStyle: 'LongDash' } } }, /** * @lends Highcharts.Series.prototype */ { nameBase: 'Supertrend', nameComponents: ['multiplier', 'period'], requiredIndicators: ['atr'], init: function () { var options, parentOptions; SMA.prototype.init.apply(this, arguments); options = this.options; parentOptions = this.linkedParent.options; // Indicator cropThreshold has to be equal linked series one // reduced by period due to points comparison in drawGraph method // (#9787) options.cropThreshold = parentOptions.cropThreshold - (options.params.period - 1); }, drawGraph: function () { var indicator = this, indicOptions = indicator.options, // Series that indicator is linked to mainSeries = indicator.linkedParent, mainLinePoints = mainSeries ? mainSeries.points : [], indicPoints = indicator.points, indicPath = indicator.graph, indicPointsLen = indicPoints.length, // Points offset between lines tempOffset = mainLinePoints.length - indicPointsLen, offset = tempOffset > 0 ? tempOffset : 0, gappedExtend = { options: { gapSize: indicOptions.gapSize } }, // Sorted supertrend points array groupedPoitns = { top: [], // Rising trend line points bottom: [], // Falling trend line points intersect: [] // Change trend line points }, // Options for trend lines supertrendLineOptions = { top: { styles: { lineWidth: indicOptions.lineWidth, lineColor: indicOptions.fallingTrendColor, dashStyle: indicOptions.dashStyle } }, bottom: { styles: { lineWidth: indicOptions.lineWidth, lineColor: indicOptions.risingTrendColor, dashStyle: indicOptions.dashStyle } }, intersect: indicOptions.changeTrendLine }, close = 3, // Supertrend line point point, // Supertrend line next point (has smaller x pos than point) nextPoint, // Main series points mainPoint, nextMainPoint, // Used when supertrend and main points are shifted // relative to each other prevMainPoint, prevPrevMainPoint, // Used when particular point color is set pointColor, // Temporary points that fill groupedPoitns array newPoint, newNextPoint; // Loop which sort supertrend points while (indicPointsLen--) { point = indicPoints[indicPointsLen]; nextPoint = indicPoints[indicPointsLen - 1]; mainPoint = mainLinePoints[indicPointsLen - 1 + offset]; nextMainPoint = mainLinePoints[indicPointsLen - 2 + offset]; prevMainPoint = mainLinePoints[indicPointsLen + offset]; prevPrevMainPoint = mainLinePoints[indicPointsLen + offset + 1]; pointColor = point.options.color; newPoint = { x: point.x, plotX: point.plotX, plotY: point.plotY, isNull: false }; // When mainPoint is the last one (left plot area edge) // but supertrend has additional one if ( !nextMainPoint && mainPoint && mainSeries.yData[mainPoint.index - 1] ) { nextMainPoint = createPointObj( mainSeries, mainPoint.index - 1, close ); } // When prevMainPoint is the last one (right plot area edge) // but supertrend has additional one (and points are shifted) if ( !prevPrevMainPoint && prevMainPoint && mainSeries.yData[prevMainPoint.index + 1] ) { prevPrevMainPoint = createPointObj( mainSeries, prevMainPoint.index + 1, close ); } // When points are shifted (right or left plot area edge) if ( !mainPoint && nextMainPoint && mainSeries.yData[nextMainPoint.index + 1] ) { mainPoint = createPointObj( mainSeries, nextMainPoint.index + 1, close ); } else if ( !mainPoint && prevMainPoint && mainSeries.yData[prevMainPoint.index - 1] ) { mainPoint = createPointObj( mainSeries, prevMainPoint.index - 1, close ); } // Check if points are shifted relative to each other if ( point && mainPoint && prevMainPoint && nextMainPoint && point.x !== mainPoint.x ) { if (point.x === prevMainPoint.x) { nextMainPoint = mainPoint; mainPoint = prevMainPoint; } else if (point.x === nextMainPoint.x) { mainPoint = nextMainPoint; nextMainPoint = { close: mainSeries.yData[mainPoint.index - 1][close], x: mainSeries.xData[mainPoint.index - 1] }; } else if ( prevPrevMainPoint && point.x === prevPrevMainPoint.x ) { mainPoint = prevPrevMainPoint; nextMainPoint = prevMainPoint; } } if (nextPoint && nextMainPoint && mainPoint) { newNextPoint = { x: nextPoint.x, plotX: nextPoint.plotX, plotY: nextPoint.plotY, isNull: false }; if ( point.y >= mainPoint.close && nextPoint.y >= nextMainPoint.close ) { point.color = pointColor || indicOptions.fallingTrendColor; groupedPoitns.top.push(newPoint); } else if ( point.y < mainPoint.close && nextPoint.y < nextMainPoint.close ) { point.color = pointColor || indicOptions.risingTrendColor; groupedPoitns.bottom.push(newPoint); } else { groupedPoitns.intersect.push(newPoint); groupedPoitns.intersect.push(newNextPoint); // Additional null point to make a gap in line groupedPoitns.intersect.push(merge(newNextPoint, { isNull: true })); if ( point.y >= mainPoint.close && nextPoint.y < nextMainPoint.close ) { point.color = pointColor || indicOptions.fallingTrendColor; nextPoint.color = pointColor || indicOptions.risingTrendColor; groupedPoitns.top.push(newPoint); groupedPoitns.top.push(merge(newNextPoint, { isNull: true })); } else if ( point.y < mainPoint.close && nextPoint.y >= nextMainPoint.close ) { point.color = pointColor || indicOptions.risingTrendColor; nextPoint.color = pointColor || indicOptions.fallingTrendColor; groupedPoitns.bottom.push(newPoint); groupedPoitns.bottom.push(merge(newNextPoint, { isNull: true })); } } } else if (mainPoint) { if (point.y >= mainPoint.close) { point.color = pointColor || indicOptions.fallingTrendColor; groupedPoitns.top.push(newPoint); } else { point.color = pointColor || indicOptions.risingTrendColor; groupedPoitns.bottom.push(newPoint); } } } // Generate lines: objectEach(groupedPoitns, function (values, lineName) { indicator.points = values; indicator.options = merge( supertrendLineOptions[lineName].styles, gappedExtend ); indicator.graph = indicator['graph' + lineName + 'Line']; SMA.prototype.drawGraph.call(indicator); // Now save line indicator['graph' + lineName + 'Line'] = indicator.graph; }); // Restore options: indicator.points = indicPoints; indicator.options = indicOptions; indicator.graph = indicPath; }, // Supertrend (Multiplier, Period) Formula: // BASIC UPPERBAND = (HIGH + LOW) / 2 + Multiplier * ATR(Period) // BASIC LOWERBAND = (HIGH + LOW) / 2 - Multiplier * ATR(Period) // FINAL UPPERBAND = // IF( // Current BASICUPPERBAND < Previous FINAL UPPERBAND AND // Previous Close > Previous FINAL UPPERBAND // ) THEN (Current BASIC UPPERBAND) // ELSE (Previous FINALUPPERBAND) // FINAL LOWERBAND = // IF( // Current BASIC LOWERBAND > Previous FINAL LOWERBAND AND // Previous Close < Previous FINAL LOWERBAND // ) THEN (Current BASIC LOWERBAND) // ELSE (Previous FINAL LOWERBAND) // SUPERTREND = // IF( // Previous Supertrend == Previous FINAL UPPERBAND AND // Current Close < Current FINAL UPPERBAND // ) THAN Current FINAL UPPERBAND // ELSE IF( // Previous Supertrend == Previous FINAL LOWERBAND AND // Current Close < Current FINAL LOWERBAND // ) THAN Current FINAL UPPERBAND // ELSE IF( // Previous Supertrend == Previous FINAL UPPERBAND AND // Current Close > Current FINAL UPPERBAND // ) THAN Current FINAL LOWERBAND // ELSE IF( // Previous Supertrend == Previous FINAL LOWERBAND AND // Current Close > Current FINAL LOWERBAND // ) THAN Current FINAL LOWERBAND getValues: function (series, params) { var period = params.period, multiplier = params.multiplier, xVal = series.xData, yVal = series.yData, ATRData = [], ST = [], // 0- date, 1- Supertrend indicator xData = [], yData = [], close = 3, low = 2, high = 1, periodsOffset = (period === 0) ? 0 : period - 1, basicUp, basicDown, finalUp = [], finalDown = [], supertrend, prevFinalUp, prevFinalDown, prevST, // previous Supertrend prevY, y, i; if ( (xVal.length <= period) || !isArray(yVal[0]) || yVal[0].length !== 4 || period < 0 ) { return false; } ATRData = ATR.prototype.getValues.call(this, series, { period: period }).yData; for (i = 0; i < ATRData.length; i++) { y = yVal[periodsOffset + i]; prevY = yVal[periodsOffset + i - 1] || []; prevFinalUp = finalUp[i - 1]; prevFinalDown = finalDown[i - 1]; prevST = yData[i - 1]; if (i === 0) { prevFinalUp = prevFinalDown = prevST = 0; } basicUp = correctFloat( (y[high] + y[low]) / 2 + multiplier * ATRData[i] ); basicDown = correctFloat( (y[high] + y[low]) / 2 - multiplier * ATRData[i] ); if ( (basicUp < prevFinalUp) || (prevY[close] > prevFinalUp) ) { finalUp[i] = basicUp; } else { finalUp[i] = prevFinalUp; } if ( (basicDown > prevFinalDown) || (prevY[close] < prevFinalDown) ) { finalDown[i] = basicDown; } else { finalDown[i] = prevFinalDown; } if (prevST === prevFinalUp && y[close] < finalUp[i] || prevST === prevFinalDown && y[close] < finalDown[i] ) { supertrend = finalUp[i]; } else if ( prevST === prevFinalUp && y[close] > finalUp[i] || prevST === prevFinalDown && y[close] > finalDown[i] ) { supertrend = finalDown[i]; } ST.push([xVal[periodsOffset + i], supertrend]); xData.push(xVal[periodsOffset + i]); yData.push(supertrend); } return { values: ST, xData: xData, yData: yData }; } } ); /** * A `Supertrend indicator` series. If the [type](#series.supertrend.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.supertrend * @since 7.0.0 * @product highstock * @excluding allAreas, color, colorAxis, cropThreshold, data, dataParser, * dataURL, joinBy, keys, navigatorOptions, negativeColor, * pointInterval, pointIntervalUnit, pointPlacement, pointRange, * pointStart, showInNavigator, stacking, threshold * @apioption series.supertrend */ }); _registerModule(_modules, 'indicators/volume-by-price.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2010-2019 Paweł Dalek * * Volume By Price (VBP) indicator for Highstock * * License: www.highcharts.com/license * * */ var isArray = U.isArray; // Utils function arrayExtremesOHLC(data) { var dataLength = data.length, min = data[0][3], max = min, i = 1, currentPoint; for (; i < dataLength; i++) { currentPoint = data[i][3]; if (currentPoint < min) { min = currentPoint; } if (currentPoint > max) { max = currentPoint; } } return { min: min, max: max }; } var abs = Math.abs, noop = H.noop, addEvent = H.addEvent, correctFloat = H.correctFloat, seriesType = H.seriesType, columnPrototype = H.seriesTypes.column.prototype; /** * The Volume By Price (VBP) series type. * * @private * @class * @name Highcharts.seriesTypes.vbp * * @augments Highcharts.Series */ seriesType( 'vbp', 'sma', /** * Volume By Price indicator. * * This series requires `linkedTo` option to be set. * * @sample stock/indicators/volume-by-price * Volume By Price indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.vbp */ { /** * @excluding index, period */ params: { /** * The number of price zones. */ ranges: 12, /** * The id of volume series which is mandatory. For example using * OHLC data, volumeSeriesID='volume' means the indicator will be * calculated using OHLC and volume values. */ volumeSeriesID: 'volume' }, /** * The styles for lines which determine price zones. */ zoneLines: { /** * Enable/disable zone lines. */ enabled: true, /** * Specify the style of zone lines. * * @type {Highcharts.CSSObject} * @default {"color": "#0A9AC9", "dashStyle": "LongDash", "lineWidth": 1} */ styles: { /** @ignore-options */ color: '#0A9AC9', /** @ignore-options */ dashStyle: 'LongDash', /** @ignore-options */ lineWidth: 1 } }, /** * The styles for bars when volume is divided into positive/negative. */ volumeDivision: { /** * Option to control if volume is divided. */ enabled: true, styles: { /** * Color of positive volume bars. * * @type {Highcharts.ColorString} */ positiveColor: 'rgba(144, 237, 125, 0.8)', /** * Color of negative volume bars. * * @type {Highcharts.ColorString} */ negativeColor: 'rgba(244, 91, 91, 0.8)' } }, // To enable series animation; must be animationLimit > pointCount animationLimit: 1000, enableMouseTracking: false, pointPadding: 0, zIndex: -1, crisp: true, dataGrouping: { enabled: false }, dataLabels: { /** @ignore-option */ allowOverlap: true, /** @ignore-option */ enabled: true, /** @ignore-option */ format: 'P: {point.volumePos:.2f} | N: {point.volumeNeg:.2f}', /** @ignore-option */ padding: 0, /** @ignore-option */ style: { fontSize: '7px' }, /** @ignore-option */ verticalAlign: 'top' } }, /** * @lends Highcharts.Series# */ { nameBase: 'Volume by Price', bindTo: { series: false, eventName: 'afterSetExtremes' }, calculateOn: 'render', markerAttribs: noop, drawGraph: noop, getColumnMetrics: columnPrototype.getColumnMetrics, crispCol: columnPrototype.crispCol, init: function (chart) { var indicator = this, params, baseSeries, volumeSeries; H.seriesTypes.sma.prototype.init.apply(indicator, arguments); params = indicator.options.params; baseSeries = indicator.linkedParent; volumeSeries = chart.get(params.volumeSeriesID); indicator.addCustomEvents(baseSeries, volumeSeries); return indicator; }, // Adds events related with removing series addCustomEvents: function (baseSeries, volumeSeries) { var indicator = this; function toEmptyIndicator() { indicator.chart.redraw(); indicator.setData([]); indicator.zoneStarts = []; if (indicator.zoneLinesSVG) { indicator.zoneLinesSVG.destroy(); delete indicator.zoneLinesSVG; } } // If base series is deleted, indicator series data is filled with // an empty array indicator.dataEventsToUnbind.push( addEvent(baseSeries, 'remove', function () { toEmptyIndicator(); }) ); // If volume series is deleted, indicator series data is filled with // an empty array if (volumeSeries) { indicator.dataEventsToUnbind.push( addEvent(volumeSeries, 'remove', function () { toEmptyIndicator(); }) ); } return indicator; }, // Initial animation animate: function (init) { var series = this, attr = {}; if (H.svg && !init) { attr.translateX = series.yAxis.pos; series.group.animate( attr, H.extend(H.animObject(series.options.animation), { step: function (val, fx) { series.group.attr({ scaleX: Math.max(0.001, fx.pos) }); } }) ); // Delete this function to allow it only once series.animate = null; } }, drawPoints: function () { var indicator = this; if (indicator.options.volumeDivision.enabled) { indicator.posNegVolume(true, true); columnPrototype.drawPoints.apply(indicator, arguments); indicator.posNegVolume(false, false); } columnPrototype.drawPoints.apply(indicator, arguments); }, // Function responsible for dividing volume into positive and negative posNegVolume: function (initVol, pos) { var indicator = this, signOrder = pos ? ['positive', 'negative'] : ['negative', 'positive'], volumeDivision = indicator.options.volumeDivision, pointLength = indicator.points.length, posWidths = [], negWidths = [], i = 0, pointWidth, priceZone, wholeVol, point; if (initVol) { indicator.posWidths = posWidths; indicator.negWidths = negWidths; } else { posWidths = indicator.posWidths; negWidths = indicator.negWidths; } for (; i < pointLength; i++) { point = indicator.points[i]; point[signOrder[0] + 'Graphic'] = point.graphic; point.graphic = point[signOrder[1] + 'Graphic']; if (initVol) { pointWidth = point.shapeArgs.width; priceZone = indicator.priceZones[i]; wholeVol = priceZone.wholeVolumeData; if (wholeVol) { posWidths.push( pointWidth / wholeVol * priceZone.positiveVolumeData ); negWidths.push( pointWidth / wholeVol * priceZone.negativeVolumeData ); } else { posWidths.push(0); negWidths.push(0); } } point.color = pos ? volumeDivision.styles.positiveColor : volumeDivision.styles.negativeColor; point.shapeArgs.width = pos ? indicator.posWidths[i] : indicator.negWidths[i]; point.shapeArgs.x = pos ? point.shapeArgs.x : indicator.posWidths[i]; } }, translate: function () { var indicator = this, options = indicator.options, chart = indicator.chart, yAxis = indicator.yAxis, yAxisMin = yAxis.min, zoneLinesOptions = indicator.options.zoneLines, priceZones = indicator.priceZones, yBarOffset = 0, indicatorPoints, volumeDataArray, maxVolume, primalBarWidth, barHeight, barHeightP, oldBarHeight, barWidth, pointPadding, chartPlotTop, barX, barY; columnPrototype.translate.apply(indicator); indicatorPoints = indicator.points; // Do translate operation when points exist if (indicatorPoints.length) { pointPadding = options.pointPadding < 0.5 ? options.pointPadding : 0.1; volumeDataArray = indicator.volumeDataArray; maxVolume = H.arrayMax(volumeDataArray); primalBarWidth = chart.plotWidth / 2; chartPlotTop = chart.plotTop; barHeight = abs(yAxis.toPixels(yAxisMin) - yAxis.toPixels(yAxisMin + indicator.rangeStep)); oldBarHeight = abs(yAxis.toPixels(yAxisMin) - yAxis.toPixels(yAxisMin + indicator.rangeStep)); if (pointPadding) { barHeightP = abs(barHeight * (1 - 2 * pointPadding)); yBarOffset = abs((barHeight - barHeightP) / 2); barHeight = abs(barHeightP); } indicatorPoints.forEach(function (point, index) { barX = point.barX = point.plotX = 0; barY = point.plotY = ( yAxis.toPixels(priceZones[index].start) - chartPlotTop - ( yAxis.reversed ? (barHeight - oldBarHeight) : barHeight ) - yBarOffset ); barWidth = correctFloat( primalBarWidth * priceZones[index].wholeVolumeData / maxVolume ); point.pointWidth = barWidth; point.shapeArgs = indicator.crispCol.apply( // eslint-disable-line no-useless-call indicator, [barX, barY, barWidth, barHeight] ); point.volumeNeg = priceZones[index].negativeVolumeData; point.volumePos = priceZones[index].positiveVolumeData; point.volumeAll = priceZones[index].wholeVolumeData; }); if (zoneLinesOptions.enabled) { indicator.drawZones( chart, yAxis, indicator.zoneStarts, zoneLinesOptions.styles ); } } }, getValues: function (series, params) { var indicator = this, xValues = series.processedXData, yValues = series.processedYData, chart = indicator.chart, ranges = params.ranges, VBP = [], xData = [], yData = [], isOHLC, volumeSeries, priceZones; // Checks if base series exists if (!series.chart) { return H.error( 'Base series not found! In case it has been removed, add ' + 'a new one.', true, chart ); } // Checks if volume series exists if (!(volumeSeries = chart.get(params.volumeSeriesID))) { return H.error( 'Series ' + params.volumeSeriesID + ' not found! Check `volumeSeriesID`.', true, chart ); } // Checks if series data fits the OHLC format isOHLC = isArray(yValues[0]); if (isOHLC && yValues[0].length !== 4) { return H.error( 'Type of ' + series.name + ' series is different than line, OHLC or candlestick.', true, chart ); } // Price zones contains all the information about the zones (index, // start, end, volumes, etc.) priceZones = indicator.priceZones = indicator.specifyZones( isOHLC, xValues, yValues, ranges, volumeSeries ); priceZones.forEach(function (zone, index) { VBP.push([zone.x, zone.end]); xData.push(VBP[index][0]); yData.push(VBP[index][1]); }); return { values: VBP, xData: xData, yData: yData }; }, // Specifing where each zone should start ans end specifyZones: function ( isOHLC, xValues, yValues, ranges, volumeSeries ) { var indicator = this, rangeExtremes = isOHLC ? arrayExtremesOHLC(yValues) : false, lowRange = rangeExtremes ? rangeExtremes.min : H.arrayMin(yValues), highRange = rangeExtremes ? rangeExtremes.max : H.arrayMax(yValues), zoneStarts = indicator.zoneStarts = [], priceZones = [], i = 0, j = 1, rangeStep, zoneStartsLength; if (!lowRange || !highRange) { if (this.points.length) { this.setData([]); this.zoneStarts = []; this.zoneLinesSVG.destroy(); } return []; } rangeStep = indicator.rangeStep = correctFloat(highRange - lowRange) / ranges; zoneStarts.push(lowRange); for (; i < ranges - 1; i++) { zoneStarts.push(correctFloat(zoneStarts[i] + rangeStep)); } zoneStarts.push(highRange); zoneStartsLength = zoneStarts.length; // Creating zones for (; j < zoneStartsLength; j++) { priceZones.push({ index: j - 1, x: xValues[0], start: zoneStarts[j - 1], end: zoneStarts[j] }); } return indicator.volumePerZone( isOHLC, priceZones, volumeSeries, xValues, yValues ); }, // Calculating sum of volume values for a specific zone volumePerZone: function ( isOHLC, priceZones, volumeSeries, xValues, yValues ) { var indicator = this, volumeXData = volumeSeries.processedXData, volumeYData = volumeSeries.processedYData, lastZoneIndex = priceZones.length - 1, baseSeriesLength = yValues.length, volumeSeriesLength = volumeYData.length, previousValue, startFlag, endFlag, value, i; // Checks if each point has a corresponding volume value if (abs(baseSeriesLength - volumeSeriesLength)) { // If the first point don't have volume, add 0 value at the // beggining of the volume array if (xValues[0] !== volumeXData[0]) { volumeYData.unshift(0); } // If the last point don't have volume, add 0 value at the end // of the volume array if ( xValues[baseSeriesLength - 1] !== volumeXData[volumeSeriesLength - 1] ) { volumeYData.push(0); } } indicator.volumeDataArray = []; priceZones.forEach(function (zone) { zone.wholeVolumeData = 0; zone.positiveVolumeData = 0; zone.negativeVolumeData = 0; for (i = 0; i < baseSeriesLength; i++) { startFlag = false; endFlag = false; value = isOHLC ? yValues[i][3] : yValues[i]; previousValue = i ? (isOHLC ? yValues[i - 1][3] : yValues[i - 1]) : value; // Checks if this is the point with the lowest close value // and if so, adds it calculations if (value <= zone.start && zone.index === 0) { startFlag = true; } // Checks if this is the point with the highest close value // and if so, adds it calculations if (value >= zone.end && zone.index === lastZoneIndex) { endFlag = true; } if ( (value > zone.start || startFlag) && (value < zone.end || endFlag) ) { zone.wholeVolumeData += volumeYData[i]; if (previousValue > value) { zone.negativeVolumeData += volumeYData[i]; } else { zone.positiveVolumeData += volumeYData[i]; } } } indicator.volumeDataArray.push(zone.wholeVolumeData); }); return priceZones; }, // Function responsoble for drawing additional lines indicating zones drawZones: function (chart, yAxis, zonesValues, zonesStyles) { var indicator = this, renderer = chart.renderer, zoneLinesSVG = indicator.zoneLinesSVG, zoneLinesPath = [], leftLinePos = 0, rightLinePos = chart.plotWidth, verticalOffset = chart.plotTop, verticalLinePos; zonesValues.forEach(function (value) { verticalLinePos = yAxis.toPixels(value) - verticalOffset; zoneLinesPath = zoneLinesPath.concat(chart.renderer.crispLine([ 'M', leftLinePos, verticalLinePos, 'L', rightLinePos, verticalLinePos ], zonesStyles.lineWidth)); }); // Create zone lines one path or update it while animating if (zoneLinesSVG) { zoneLinesSVG.animate({ d: zoneLinesPath }); } else { zoneLinesSVG = indicator.zoneLinesSVG = renderer.path(zoneLinesPath).attr({ 'stroke-width': zonesStyles.lineWidth, 'stroke': zonesStyles.color, 'dashstyle': zonesStyles.dashStyle, 'zIndex': indicator.group.zIndex + 0.1 }) .add(indicator.group); } } }, /** * @lends Highcharts.Point# */ { // Required for destroying negative part of volume destroy: function () { if (this.negativeGraphic) { this.negativeGraphic = this.negativeGraphic.destroy(); } return H.Point.prototype.destroy.apply(this, arguments); } } ); /** * A `Volume By Price (VBP)` series. If the [type](#series.vbp.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.vbp * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.vbp */ }); _registerModule(_modules, 'indicators/vwap.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2010-2019 Paweł Dalek * * Volume Weighted Average Price (VWAP) indicator for Highstock * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; /** * The Volume Weighted Average Price (VWAP) series type. * * @private * @class * @name Highcharts.seriesTypes.vwap * * @augments Highcharts.Series */ seriesType('vwap', 'sma', /** * Volume Weighted Average Price indicator. * * This series requires `linkedTo` option to be set. * * @sample stock/indicators/vwap * Volume Weighted Average Price indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.vwap */ { /** * @excluding index */ params: { period: 30, /** * The id of volume series which is mandatory. For example using * OHLC data, volumeSeriesID='volume' means the indicator will be * calculated using OHLC and volume values. */ volumeSeriesID: 'volume' } }, /** * @lends Highcharts.Series# */ { /** * Returns the final values of the indicator ready to be presented on a * chart * @private * @param {Highcharts.Series} series - series for indicator * @param {object} params - params * @return {object} - computed VWAP **/ getValues: function (series, params) { var indicator = this, chart = series.chart, xValues = series.xData, yValues = series.yData, period = params.period, isOHLC = true, volumeSeries; // Checks if volume series exists if (!(volumeSeries = chart.get(params.volumeSeriesID))) { return H.error( 'Series ' + params.volumeSeriesID + ' not found! Check `volumeSeriesID`.', true, chart ); } // Checks if series data fits the OHLC format if (!(isArray(yValues[0]))) { isOHLC = false; } return indicator.calculateVWAPValues( isOHLC, xValues, yValues, volumeSeries, period ); }, /** * Main algorithm used to calculate Volume Weighted Average Price (VWAP) * values * @private * @param {boolean} isOHLC - says if data has OHLC format * @param {Array<number>} xValues - array of timestamps * @param {Array<number|Array<number,number,number,number>>} yValues - * array of yValues, can be an array of a four arrays (OHLC) or array of * values (line) * @param {Array<*>} volumeSeries - volume series * @param {number} period - number of points to be calculated * @return {object} - Object contains computed VWAP **/ calculateVWAPValues: function ( isOHLC, xValues, yValues, volumeSeries, period ) { var volumeValues = volumeSeries.yData, volumeLength = volumeSeries.xData.length, pointsLength = xValues.length, cumulativePrice = [], cumulativeVolume = [], xData = [], yData = [], VWAP = [], commonLength, typicalPrice, cPrice, cVolume, i, j; if (pointsLength <= volumeLength) { commonLength = pointsLength; } else { commonLength = volumeLength; } for (i = 0, j = 0; i < commonLength; i++) { // Depending on whether series is OHLC or line type, price is // average of the high, low and close or a simple value typicalPrice = isOHLC ? ((yValues[i][1] + yValues[i][2] + yValues[i][3]) / 3) : yValues[i]; typicalPrice *= volumeValues[i]; cPrice = j ? (cumulativePrice[i - 1] + typicalPrice) : typicalPrice; cVolume = j ? (cumulativeVolume[i - 1] + volumeValues[i]) : volumeValues[i]; cumulativePrice.push(cPrice); cumulativeVolume.push(cVolume); VWAP.push([xValues[i], (cPrice / cVolume)]); xData.push(VWAP[i][0]); yData.push(VWAP[i][1]); j++; if (j === period) { j = 0; } } return { values: VWAP, xData: xData, yData: yData }; } }); /** * A `Volume Weighted Average Price (VWAP)` series. If the * [type](#series.vwap.type) option is not specified, it is inherited from * [chart.type](#chart.type). * * @extends series,plotOptions.vwap * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.vwap */ }); _registerModule(_modules, 'indicators/williams-r.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js'], _modules['mixins/reduce-array.js']], function (H, U, reduceArrayMixin) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var getArrayExtremes = reduceArrayMixin.getArrayExtremes; /** * The Williams %R series type. * * @private * @class * @name Highcharts.seriesTypes.williamsr * * @augments Highcharts.Series */ H.seriesType( 'williamsr', 'sma', /** * Williams %R. This series requires the `linkedTo` option to be * set and should be loaded after the `stock/indicators/indicators.js`. * * @sample {highstock} stock/indicators/williams-r * Williams %R * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, joinBy, keys, navigatorOptions, * pointInterval, pointIntervalUnit, pointPlacement, * pointRange, pointStart, showInNavigator, stacking * @optionparent plotOptions.williamsr */ { /** * Paramters used in calculation of Williams %R series points. * @excluding index */ params: { /** * Period for Williams %R oscillator */ period: 14 } }, /** * @lends Highcharts.Series# */ { nameBase: 'Williams %R', getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, WR = [], // 0- date, 1- Williams %R xData = [], yData = [], slicedY, close = 3, low = 2, high = 1, extremes, R, HH, // Highest high value in period LL, // Lowest low value in period CC, // Current close value i; // Williams %R requires close value if ( xVal.length < period || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } // For a N-period, we start from N-1 point, to calculate Nth point // That is why we later need to comprehend slice() elements list // with (+1) for (i = period - 1; i < yValLen; i++) { slicedY = yVal.slice(i - period + 1, i + 1); extremes = getArrayExtremes(slicedY, low, high); LL = extremes[0]; HH = extremes[1]; CC = yVal[i][close]; R = ((HH - CC) / (HH - LL)) * -100; if (xVal[i]) { WR.push([xVal[i], R]); xData.push(xVal[i]); yData.push(R); } } return { values: WR, xData: xData, yData: yData }; } } ); /** * A `Williams %R Oscillator` series. If the [type](#series.williamsr.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.williamsr * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, dataParser, dataURL, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, stacking * @apioption series.williamsr */ }); _registerModule(_modules, 'indicators/wma.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2010-2019 Kacper Madej * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; // Utils: function accumulateAverage(points, xVal, yVal, i, index) { var xValue = xVal[i], yValue = index < 0 ? yVal[i] : yVal[i][index]; points.push([xValue, yValue]); } function weightedSumArray(array, pLen) { // The denominator is the sum of the number of days as a triangular number. // If there are 5 days, the triangular numbers are 5, 4, 3, 2, and 1. // The sum is 5 + 4 + 3 + 2 + 1 = 15. var denominator = (pLen + 1) / 2 * pLen; // reduce VS loop => reduce return array.reduce(function (prev, cur, i) { return [null, prev[1] + cur[1] * (i + 1)]; })[1] / denominator; } function populateAverage(points, xVal, yVal, i) { var pLen = points.length, wmaY = weightedSumArray(points, pLen), wmaX = xVal[i - 1]; points.shift(); // remove point until range < period return [wmaX, wmaY]; } /** * The SMA series type. * * @private * @class * @name Highcharts.seriesTypes.wma * * @augments Highcharts.Series */ seriesType( 'wma', 'sma', /** * Weighted moving average indicator (WMA). This series requires `linkedTo` * option to be set. * * @sample stock/indicators/wma * Weighted moving average indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.wma */ { params: { index: 3, period: 9 } }, /** * @lends Highcharts.Series# */ { getValues: function (series, params) { var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, range = 1, xValue = xVal[0], yValue = yVal[0], WMA = [], xData = [], yData = [], index = -1, i, points, WMAPoint; if (xVal.length < period) { return false; } // Switch index for OHLC / Candlestick if (isArray(yVal[0])) { index = params.index; yValue = yVal[0][index]; } // Starting point points = [[xValue, yValue]]; // Accumulate first N-points while (range !== period) { accumulateAverage(points, xVal, yVal, range, index); range++; } // Calculate value one-by-one for each period in visible data for (i = range; i < yValLen; i++) { WMAPoint = populateAverage(points, xVal, yVal, i); WMA.push(WMAPoint); xData.push(WMAPoint[0]); yData.push(WMAPoint[1]); accumulateAverage(points, xVal, yVal, i, index); } WMAPoint = populateAverage(points, xVal, yVal, i); WMA.push(WMAPoint); xData.push(WMAPoint[0]); yData.push(WMAPoint[1]); return { values: WMA, xData: xData, yData: yData }; } } ); /** * A `WMA` series. If the [type](#series.wma.type) option is not specified, it * is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.wma * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.wma */ }); _registerModule(_modules, 'indicators/zigzag.src.js', [_modules['parts/Globals.js']], function (H) { /* * * * (c) 2010-2019 Kacper Madej * * License: www.highcharts.com/license * * */ var seriesType = H.seriesType, UNDEFINED; /** * The Zig Zag series type. * * @private * @class * @name Highcharts.seriesTypes.zigzag * * @augments Highcharts.Series */ seriesType( 'zigzag', 'sma', /** * Zig Zag indicator. * * This series requires `linkedTo` option to be set. * * @sample stock/indicators/zigzag * Zig Zag indicator * * @extends plotOptions.sma * @since 6.0.0 * @product highstock * @optionparent plotOptions.zigzag */ { /** * @excluding index, period */ params: { /** * The point index which indicator calculations will base - low * value. * * For example using OHLC data, index=2 means the indicator will be * calculated using Low values. */ lowIndex: 2, /** * The point index which indicator calculations will base - high * value. * * For example using OHLC data, index=1 means the indicator will be * calculated using High values. */ highIndex: 1, /** * The threshold for the value change. * * For example deviation=1 means the indicator will ignore all price * movements less than 1%. */ deviation: 1 } }, /** * @lends Highcharts.Series# */ { nameComponents: ['deviation'], nameSuffixes: ['%'], nameBase: 'Zig Zag', getValues: function (series, params) { var lowIndex = params.lowIndex, highIndex = params.highIndex, deviation = params.deviation / 100, deviations = { 'low': 1 + deviation, 'high': 1 - deviation }, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, Zigzag = [], xData = [], yData = [], i, j, ZigzagPoint, firstZigzagLow, firstZigzagHigh, directionUp, zigZagLen, exitLoop = false, yIndex = false; // Exit if not enught points or no low or high values if ( xVal.length <= 1 || ( yValLen && ( yVal[0][lowIndex] === UNDEFINED || yVal[0][highIndex] === UNDEFINED ) ) ) { return false; } // Set first zigzag point candidate firstZigzagLow = yVal[0][lowIndex]; firstZigzagHigh = yVal[0][highIndex]; // Search for a second zigzag point candidate, // this will also set first zigzag point for (i = 1; i < yValLen; i++) { // requried change to go down if (yVal[i][lowIndex] <= firstZigzagHigh * deviations.high) { Zigzag.push([xVal[0], firstZigzagHigh]); // second zigzag point candidate ZigzagPoint = [xVal[i], yVal[i][lowIndex]]; // next line will be going up directionUp = true; exitLoop = true; // requried change to go up } else if ( yVal[i][highIndex] >= firstZigzagLow * deviations.low ) { Zigzag.push([xVal[0], firstZigzagLow]); // second zigzag point candidate ZigzagPoint = [xVal[i], yVal[i][highIndex]]; // next line will be going down directionUp = false; exitLoop = true; } if (exitLoop) { xData.push(Zigzag[0][0]); yData.push(Zigzag[0][1]); j = i++; i = yValLen; } } // Search for next zigzags for (i = j; i < yValLen; i++) { if (directionUp) { // next line up // lower when going down -> change zigzag candidate if (yVal[i][lowIndex] <= ZigzagPoint[1]) { ZigzagPoint = [xVal[i], yVal[i][lowIndex]]; } // requried change to go down -> new zigzagpoint and // direction change if (yVal[i][highIndex] >= ZigzagPoint[1] * deviations.low) { yIndex = highIndex; } } else { // next line down // higher when going up -> change zigzag candidate if (yVal[i][highIndex] >= ZigzagPoint[1]) { ZigzagPoint = [xVal[i], yVal[i][highIndex]]; } // requried change to go down -> new zigzagpoint and // direction change if (yVal[i][lowIndex] <= ZigzagPoint[1] * deviations.high) { yIndex = lowIndex; } } if (yIndex !== false) { // new zigzag point and direction change Zigzag.push(ZigzagPoint); xData.push(ZigzagPoint[0]); yData.push(ZigzagPoint[1]); ZigzagPoint = [xVal[i], yVal[i][yIndex]]; directionUp = !directionUp; yIndex = false; } } zigZagLen = Zigzag.length; // no zigzag for last point if ( zigZagLen !== 0 && Zigzag[zigZagLen - 1][0] < xVal[yValLen - 1] ) { // set last point from zigzag candidate Zigzag.push(ZigzagPoint); xData.push(ZigzagPoint[0]); yData.push(ZigzagPoint[1]); } return { values: Zigzag, xData: xData, yData: yData }; } } ); /** * A `Zig Zag` series. If the [type](#series.zigzag.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.zigzag * @since 6.0.0 * @product highstock * @excluding dataParser, dataURL * @apioption series.zigzag */ }); _registerModule(_modules, 'indicators/regressions.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * (c) 2010-2019 Kamil Kulig * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; /** * Linear regression series type. * * @private * @class * @name Highcharts.seriesTypes.linearregression * * @augments Highcharts.Series */ seriesType( 'linearRegression', 'sma', /** * Linear regression indicator. This series requires `linkedTo` option to be * set. * * @sample {highstock} stock/indicators/linear-regression * Linear regression indicator * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @optionparent plotOptions.linearregression */ { params: { /** * Unit (in milliseconds) for the x axis distances used to compute * the regression line paramters (slope & intercept) for every * range. In Highstock the x axis values are always represented in * milliseconds which may cause that distances between points are * "big" integer numbers. * * Highstock's linear regression algorithm (least squares method) * will utilize these "big" integers for finding the slope and the * intercept of the regression line for each period. In consequence, * this value may be a very "small" decimal number that's hard to * interpret by a human. * * For instance: `xAxisUnit` equealed to `86400000` ms (1 day) * forces the algorithm to treat `86400000` as `1` while computing * the slope and the intercept. This may enchance the legiblitity of * the indicator's values. * * Default value is the closest distance between two data points. * * @sample {highstock} stock/plotoptions/linear-regression-xaxisunit * xAxisUnit set to 1 minute * * @example * // In Liniear Regression Slope Indicator series `xAxisUnit` is * // `86400000` (1 day) and period is `3`. There're 3 points in the * // base series: * * data: [ * [Date.UTC(2019, 0, 1), 1], * [Date.UTC(2019, 0, 2), 3], * [Date.UTC(2019, 0, 3), 5] * ] * * // This will produce one point in the indicator series that has a * // `y` value of `2` (slope of the regression line). If we change * // the `xAxisUnit` to `1` (ms) the value of the indicator's point * // will be `2.3148148148148148e-8` which is harder to interpert * // for a human. * * @type {number} * @product highstock */ xAxisUnit: undefined }, tooltip: { valueDecimals: 4 } }, /** * @lends Highcharts.Series# */ { nameBase: 'Linear Regression Indicator', /** * Return the slope and intercept of a straight line function. * @private * @param {Array<number>} - list of all x coordinates in a period * @param {Array<number>} - list of all y coordinates in a period * @return {object} - object that contains the slope and the intercept * of a straight line function */ getRegressionLineParameters: function (xData, yData) { // least squares method var yIndex = this.options.params.index, getSingleYValue = function (yValue, yIndex) { return isArray(yValue) ? yValue[yIndex] : yValue; }, xSum = xData.reduce(function (accX, val) { return val + accX; }, 0), ySum = yData.reduce(function (accY, val) { return getSingleYValue(val, yIndex) + accY; }, 0), xMean = xSum / xData.length, yMean = ySum / yData.length, xError, yError, formulaNumerator = 0, formulaDenominator = 0, i, slope; for (i = 0; i < xData.length; i++) { xError = xData[i] - xMean; yError = getSingleYValue(yData[i], yIndex) - yMean; formulaNumerator += xError * yError; formulaDenominator += Math.pow(xError, 2); } slope = formulaDenominator ? formulaNumerator / formulaDenominator : 0; // don't divide by 0 return { slope: slope, intercept: yMean - slope * xMean }; }, /** * Return the y value on a straight line. * @private * @param {object} - object that contains the slope and the intercept * of a straight line function * @param {number} - x coordinate of the point * @return {number} - y value of the point that lies on the line */ getEndPointY: function (lineParameters, endPointX) { return lineParameters.slope * endPointX + lineParameters.intercept; }, /** * Transform the coordinate system so that x values start at 0 and * apply xAxisUnit. * @private * @param {Array<number>} - list of all x coordinates in a period * @param {number} - xAxisUnit option (see the API) * @return {Array<number>} - array of transformed x data */ transformXData: function (xData, xAxisUnit) { var xOffset = xData[0]; return xData.map(function (xValue) { return (xValue - xOffset) / xAxisUnit; }); }, /** * Find the closest distance between points in the base series. * @private * @param {Array<number>} - list of all x coordinates in the base series * @return {number} - closest distance between points in the base series */ findClosestDistance: function (xData) { var distance, closestDistance, i; for (i = 1; i < xData.length - 1; i++) { distance = xData[i] - xData[i - 1]; if (distance > 0 && (closestDistance === undefined || distance < closestDistance)) { closestDistance = distance; } } return closestDistance; }, // Required to be implemented - starting point for indicator's logic getValues: function (baseSeries, regressionSeriesParams) { var xData = baseSeries.xData, yData = baseSeries.yData, period = regressionSeriesParams.period, lineParameters, i, periodStart, periodEnd, indicatorData = { // format required to be returned xData: [], // by getValues() method yData: [], values: [] }, endPointX, endPointY, periodXData, periodYData, periodTransformedXData, xAxisUnit = this.options.params.xAxisUnit || this.findClosestDistance(xData); // Iteration logic: x value of the last point within the period // (end point) is used to represent the y value (regression) // of the entire period. for (i = period - 1; i <= xData.length - 1; i++) { periodStart = i - period + 1; // adjusted for slice() function periodEnd = i + 1; // (as above) endPointX = xData[i]; periodXData = xData.slice(periodStart, periodEnd); periodYData = yData.slice(periodStart, periodEnd); periodTransformedXData = this.transformXData(periodXData, xAxisUnit); lineParameters = this.getRegressionLineParameters( periodTransformedXData, periodYData ); endPointY = this.getEndPointY(lineParameters, periodTransformedXData[periodTransformedXData.length - 1]); indicatorData.values.push({ regressionLineParameters: lineParameters, x: endPointX, y: endPointY }); indicatorData.xData.push(endPointX); indicatorData.yData.push(endPointY); } return indicatorData; } } ); /** * A linear regression series. If the [type](#series.linearregression.type) * option is not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.linearregression * @since 7.0.0 * @product highstock * @excluding dataParser,dataURL * @apioption series.linearregression */ /* ************************************************************************** */ /** * The Linear Regression Slope series type. * * @private * @class * @name Highcharts.seriesTypes.linearRegressionSlope * * @augments Highcharts.Series */ seriesType( 'linearRegressionSlope', 'linearRegression', /** * Linear regression slope indicator. This series requires `linkedTo` * option to be set. * * @sample {highstock} stock/indicators/linear-regression-slope * Linear regression slope indicator * * @extends plotOptions.linearregression * @since 7.0.0 * @product highstock * @optionparent plotOptions.linearregressionslope */ {}, /** * @lends Highcharts.Series# */ { nameBase: 'Linear Regression Slope Indicator', getEndPointY: function (lineParameters) { return lineParameters.slope; } } ); /** * A linear regression slope series. If the * [type](#series.linearregressionslope.type) option is not specified, it is * inherited from [chart.type](#chart.type). * * @extends series,plotOptions.linearregressionslope * @since 7.0.0 * @product highstock * @excluding dataParser,dataURL * @apioption series.linearregressionslope */ /* ************************************************************************** */ /** * The Linear Regression Intercept series type. * * @private * @class * @name Highcharts.seriesTypes.linearRegressionIntercept * * @augments Highcharts.Series */ seriesType( 'linearRegressionIntercept', 'linearRegression', /** * Linear regression intercept indicator. This series requires `linkedTo` * option to be set. * * @sample {highstock} stock/indicators/linear-regression-intercept * Linear intercept slope indicator * * @extends plotOptions.linearregression * @since 7.0.0 * @product highstock * @optionparent plotOptions.linearregressionintercept */ {}, /** * @lends Highcharts.Series# */ { nameBase: 'Linear Regression Intercept Indicator', getEndPointY: function (lineParameters) { return lineParameters.intercept; } } ); /** * A linear regression intercept series. If the * [type](#series.linearregressionintercept.type) option is not specified, it is * inherited from [chart.type](#chart.type). * * @extends series,plotOptions.linearregressionintercept * @since 7.0.0 * @product highstock * @excluding dataParser,dataURL * @apioption series.linearregressionintercept */ /* ************************************************************************** */ /** * The Linear Regression Angle series type. * * @private * @class * @name Highcharts.seriesTypes.linearRegressionAngle * * @augments Highcharts.Series */ seriesType( 'linearRegressionAngle', 'linearRegression', /** * Linear regression angle indicator. This series requires `linkedTo` * option to be set. * * @sample {highstock} stock/indicators/linear-regression-angle * Linear intercept angle indicator * * @extends plotOptions.linearregression * @since 7.0.0 * @product highstock * @optionparent plotOptions.linearregressionangle */ { tooltip: { // add a degree symbol pointFormat: '<span style="color:{point.color}">\u25CF</span>' + '{series.name}: <b>{point.y}°</b><br/>' } }, /** * @lends Highcharts.Series# */ { nameBase: 'Linear Regression Angle Indicator', /** * Convert a slope of a line to angle (in degrees) between * the line and x axis * @private * @param {number} - slope of the straight line function * @return {number} */ slopeToAngle: function (slope) { return Math.atan(slope) * (180 / Math.PI); // rad to deg }, getEndPointY: function (lineParameters) { return this.slopeToAngle(lineParameters.slope); } } ); /** * A linear regression intercept series. If the * [type](#series.linearregressionangle.type) option is not specified, it is * inherited from [chart.type](#chart.type). * * @extends series,plotOptions.linearregressionangle * @since 7.0.0 * @product highstock * @excluding dataParser,dataURL * @apioption series.linearregressionangle */ }); _registerModule(_modules, 'indicators/acceleration-bands.src.js', [_modules['parts/Globals.js'], _modules['mixins/multipe-lines.js']], function (H, multipleLinesMixin) { /* * * * License: www.highcharts.com/license * * */ var SMA = H.seriesTypes.sma, merge = H.merge, correctFloat = H.correctFloat; function getBaseForBand(low, high, factor) { return (( (correctFloat(high - low)) / ((correctFloat(high + low)) / 2) ) * 1000) * factor; } function getPointUB(high, base) { return high * (correctFloat(1 + 2 * base)); } function getPointLB(low, base) { return low * (correctFloat(1 - 2 * base)); } /** * The ABands series type * * @private * @class * @name Highcharts.seriesTypes.abands * * @augments Highcharts.Series */ H.seriesType( 'abands', 'sma', /** * Acceleration bands (ABANDS). This series requires the `linkedTo` option * to be set and should be loaded after the * `stock/indicators/indicators.js`. * * @sample {highstock} stock/indicators/acceleration-bands * Acceleration Bands * * @extends plotOptions.sma * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, joinBy, keys, * navigatorOptions, pointInterval, pointIntervalUnit, * pointPlacement, pointRange, pointStart, showInNavigator, * stacking, * @optionparent plotOptions.abands */ { params: { period: 20, /** * The algorithms factor value used to calculate bands. * * @product highstock */ factor: 0.001, index: 3 }, lineWidth: 1, topLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1 } }, bottomLine: { styles: { /** * Pixel width of the line. */ lineWidth: 1 } }, dataGrouping: { approximation: 'averages' } }, /** * @lends Highcharts.Series# */ merge(multipleLinesMixin, { pointArrayMap: ['top', 'middle', 'bottom'], pointValKey: 'middle', nameBase: 'Acceleration Bands', nameComponents: ['period', 'factor'], linesApiNames: ['topLine', 'bottomLine'], getValues: function (series, params) { var period = params.period, factor = params.factor, index = params.index, xVal = series.xData, yVal = series.yData, yValLen = yVal ? yVal.length : 0, UB = [], // Upperbands LB = [], // Lowerbands // ABANDS array structure: // 0-date, 1-top line, 2-middle line, 3-bottom line ABANDS = [], ML, TL, BL, // middle line, top line and bottom line date, bandBase, pointSMA, ubSMA, lbSMA, low = 2, high = 1, xData = [], yData = [], slicedX, slicedY, i; if (yValLen < period) { return false; } for (i = 0; i <= yValLen; i++) { // Get UB and LB values of every point. This condition // is necessary, because there is a need to calculate current // UB nad LB values simultaneously with given period SMA // in one for loop. if (i < yValLen) { bandBase = getBaseForBand( yVal[i][low], yVal[i][high], factor ); UB.push(getPointUB(yVal[i][high], bandBase)); LB.push(getPointLB(yVal[i][low], bandBase)); } if (i >= period) { slicedX = xVal.slice(i - period, i); slicedY = yVal.slice(i - period, i); ubSMA = SMA.prototype.getValues.call(this, { xData: slicedX, yData: UB.slice(i - period, i) }, { period: period }); lbSMA = SMA.prototype.getValues.call(this, { xData: slicedX, yData: LB.slice(i - period, i) }, { period: period }); pointSMA = SMA.prototype.getValues.call(this, { xData: slicedX, yData: slicedY }, { period: period, index: index }); date = pointSMA.xData[0]; TL = ubSMA.yData[0]; BL = lbSMA.yData[0]; ML = pointSMA.yData[0]; ABANDS.push([date, TL, ML, BL]); xData.push(date); yData.push([TL, ML, BL]); } } return { values: ABANDS, xData: xData, yData: yData }; } }) ); /** * An Acceleration bands indicator. If the [type](#series.pc.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.abands * @since 7.0.0 * @product highstock * @excluding allAreas, colorAxis, compare, compareBase, dataParser, dataURL, * joinBy, keys, navigatorOptions, pointInterval, * pointIntervalUnit, pointPlacement, pointRange, pointStart, * stacking, showInNavigator, * @optionparent series.abands */ }); _registerModule(_modules, 'indicators/trendline.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) { /* * * * License: www.highcharts.com/license * * */ var isArray = U.isArray; var seriesType = H.seriesType; /** * The Trend line series type. * * @private * @class * @name Highcharts.seriesTypes.trendline * * @augments Highcharts.Series */ seriesType( 'trendline', 'sma', /** * Trendline (linear regression) fits a straight line to the selected data * using a method called the Sum Of Least Squares. This series requires the * `linkedTo` option to be set. * * @sample stock/indicators/trendline * Trendline indicator * * @extends plotOptions.sma * @since 7.1.3 * @product highstock * @optionparent plotOptions.trendline */ { /** * @excluding period */ params: { /** * The point index which indicator calculations will base. For * example using OHLC data, index=2 means the indicator will be * calculated using Low values. * * @default 3 */ index: 3 } }, /** * @lends Highcharts.Series# */ { nameBase: 'Trendline', nameComponents: false, getValues: function (series, params) { var xVal = series.xData, yVal = series.yData, LR = [], xData = [], yData = [], sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, xValLength = xVal.length, index = params.index, alpha, beta, i, x, y; // Get sums: for (i = 0; i < xValLength; i++) { x = xVal[i]; y = isArray(yVal[i]) ? yVal[i][index] : yVal[i]; sumX += x; sumY += y; sumXY += x * y; sumX2 += x * x; } // Get slope and offset: alpha = (xValLength * sumXY - sumX * sumY) / (xValLength * sumX2 - sumX * sumX); if (isNaN(alpha)) { alpha = 0; } beta = (sumY - alpha * sumX) / xValLength; // Calculate linear regression: for (i = 0; i < xValLength; i++) { x = xVal[i]; y = alpha * x + beta; // Prepare arrays required for getValues() method LR[i] = [x, y]; xData[i] = x; yData[i] = y; } return { xData: xData, yData: yData, values: LR }; } } ); /** * A `TrendLine` series. If the [type](#series.trendline.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.trendline * @since 7.1.3 * @product highstock * @excluding dataParser, dataURL * @apioption series.trendline */ }); _registerModule(_modules, 'masters/indicators/indicators-all.src.js', [], function () { }); }));