| @@ -0,0 +1 @@ | ||
| console.error('code.highcharts.local has moved to the /code folder'); // eslint-disable-line no-console |
| @@ -0,0 +1,125 @@ | ||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| 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. | ||
| * | ||
| * @constructor seriesTypes.ad | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| seriesType('ad', 'sma', | ||
| /** | ||
| * Accumulation Distribution (AD). This series requires `linkedTo` option to | ||
| * be set. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/accumulation-distribution | ||
| * Accumulation/Distribution indicator | ||
| * @since 6.0.0 | ||
| * @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. | ||
| * | ||
| * @type {String} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| volumeSeriesID: 'volume' | ||
| } | ||
| }, { | ||
| 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 | ||
| ); | ||
| } | ||
|
|
||
| // 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]; | ||
| ADPoint[1] = ADPoint[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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.ad | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.ad | ||
| */ | ||
|
|
||
| /** | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.sma.data | ||
| * @product highstock | ||
| * @apioption series.ad.data | ||
| */ |
| @@ -0,0 +1,140 @@ | ||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var isArray = H.isArray, | ||
| 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. | ||
| * | ||
| * @constructor seriesTypes.atr | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| seriesType('atr', 'sma', | ||
| /** | ||
| * Average true range indicator (ATR). This series requires `linkedTo` | ||
| * option to be set. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/atr ATR indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.atr | ||
| */ | ||
| { | ||
| params: { | ||
| period: 14 | ||
| } | ||
| }, { | ||
| 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.atr | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.atr | ||
| */ | ||
|
|
||
| /** | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.sma.data | ||
| * @product highstock | ||
| * @apioption series.atr.data | ||
| */ |
| @@ -0,0 +1,286 @@ | ||
| 'use strict'; | ||
|
|
||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var each = H.each, | ||
| merge = H.merge, | ||
| isArray = H.isArray, | ||
| 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; | ||
| } | ||
|
|
||
| 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. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/bollinger-bands | ||
| * Bollinger bands | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.bb | ||
| */ | ||
| { | ||
| name: 'BB (20, 2)', | ||
| params: { | ||
| period: 20, | ||
| /** | ||
| * Standard deviation for top and bottom bands. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| standardDeviation: 2, | ||
| index: 3 | ||
| }, | ||
| /** | ||
| * Bottom line options. | ||
| * | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| bottomLine: { | ||
| /** | ||
| * Styles for a bottom line. | ||
| * | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| styles: { | ||
| /** | ||
| * Pixel width of the line. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| lineWidth: 1, | ||
| /** | ||
| * Color of the line. If not set, it's inherited from | ||
| * [plotOptions.bb.color](#plotOptions.bb.color). | ||
| * | ||
| * @type {String} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| lineColor: undefined | ||
| } | ||
| }, | ||
| /** | ||
| * Top line options. | ||
| * | ||
| * @extends {plotOptions.bb.bottomLine} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| topLine: { | ||
| styles: { | ||
| lineWidth: 1, | ||
| 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.prototype */ { | ||
| pointArrayMap: ['top', 'middle', 'bottom'], | ||
| pointValKey: 'middle', | ||
| nameComponents: ['period', 'standardDeviation'], | ||
| 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, | ||
| translatedBB = ['plotTop', 'plotMiddle', 'plotBottom']; | ||
|
|
||
| SMA.prototype.translate.apply(indicator, arguments); | ||
|
|
||
| each(indicator.points, function (point) { | ||
| each( | ||
| [point.top, point.middle, point.bottom], | ||
| function (value, i) { | ||
| if (value !== null) { | ||
| point[translatedBB[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: | ||
| each(['topLine', 'bottomLine'], 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, | ||
| 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.bb | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.bb | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `bb` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.bb.data | ||
| */ |
| @@ -0,0 +1,121 @@ | ||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var isArray = H.isArray, | ||
| seriesType = H.seriesType; | ||
|
|
||
| // Utils: | ||
| function sumArray(array) { | ||
| return H.reduce(array, 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. | ||
| * | ||
| * @constructor seriesTypes.cci | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| seriesType('cci', 'sma', | ||
| /** | ||
| * Commodity Channel Index (CCI). This series requires `linkedTo` option to | ||
| * be set. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/cci CCI indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.cci | ||
| */ | ||
| { | ||
| params: { | ||
| period: 14 | ||
| } | ||
| }, { | ||
| 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.cci | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.cci | ||
| */ | ||
|
|
||
| /** | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.sma.data | ||
| * @product highstock | ||
| * @apioption series.cci.data | ||
| */ |
| @@ -0,0 +1,233 @@ | ||
| /** | ||
| * (c) 2010-2017 Highsoft AS | ||
| * Author: Sebastian Domas | ||
| * | ||
| * Chaikin Money Flow indicator for Highstock | ||
| * | ||
| * License: www.highcharts.com/license | ||
| */ | ||
|
|
||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
|
|
||
| H.seriesType('cmf', 'sma', | ||
| /** | ||
| * Chaikin Money Flow indicator (cmf). | ||
| * | ||
| * @type {Object} | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/cmf/ | ||
| * Chaikin Money Flow indicator | ||
| * @since 6.0.0 | ||
| * @excluding animationLimit | ||
| * @optionparent plotOptions.cmf | ||
| */ | ||
| { | ||
| params: { | ||
| period: 14, | ||
|
|
||
| /** | ||
| * The id of another series to use its data as volume data for the | ||
| * indiator calculation. | ||
| */ | ||
| volumeSeriesID: 'volume' | ||
| } | ||
| }, { | ||
| 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 | ||
| * @returns {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 | ||
| ); | ||
| }, | ||
|
|
||
| /** | ||
| * @typedef {Object} Values | ||
| * @property {Number[][]} values | ||
| * Combined xData and yData values into a tuple | ||
| * @property {Number[]} xData | ||
| * Values represent x timestamp values | ||
| * @property {Number[]} yData | ||
| * Values represent y values | ||
| **/ | ||
|
|
||
| /** | ||
| * Returns indicator's data | ||
| * @returns {False | Values} | ||
| * 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 | ||
| ); | ||
| }, | ||
|
|
||
| /** | ||
| * @static | ||
| * @param {Number[]} xData x timestamp values | ||
| * @param {Number[]} seriesYData yData of basic series | ||
| * @param {Number[]} volumeSeriesYData yData of volume series | ||
| * @param {Number} period indicator's param | ||
| * @returns {Values} 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 {Number[]} ohlc OHLC point | ||
| * @param {Number} volume Volume point's y value | ||
| * @returns {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 | ||
| * @returns {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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.cmf | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.cmf | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `CMF` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.cmf.data | ||
| */ |
| @@ -0,0 +1,157 @@ | ||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var isArray = H.isArray, | ||
| 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 populateAverage( | ||
| points, | ||
| 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 : | ||
| ((yValue * EMApercent) + (calEMA * (1 - EMApercent))); | ||
|
|
||
| return [x, y]; | ||
| } | ||
| /** | ||
| * The EMA series type. | ||
| * | ||
| * @constructor seriesTypes.ema | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| seriesType('ema', 'sma', | ||
| /** | ||
| * Exponential moving average indicator (EMA). This series requires the | ||
| * `linkedTo` option to be set. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/ema | ||
| * Exponential moving average indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.ema | ||
| */ | ||
| { | ||
| params: { | ||
| index: 0, | ||
| period: 14 | ||
| } | ||
| }, { | ||
| getValues: function (series, params) { | ||
| var period = params.period, | ||
| xVal = series.xData, | ||
| yVal = series.yData, | ||
| yValLen = yVal ? yVal.length : 0, | ||
| EMApercent = (2 / (period + 1)), | ||
| range = 0, | ||
| sum = 0, | ||
| EMA = [], | ||
| xData = [], | ||
| yData = [], | ||
| index = -1, | ||
| points = [], | ||
| SMA = 0, | ||
| calEMA, | ||
| EMAPoint, | ||
| i; | ||
|
|
||
| // Check period, if bigger than points length, skip | ||
| 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) { | ||
| accumulateAverage(points, xVal, yVal, range, index); | ||
| sum += index < 0 ? yVal[range] : yVal[range][index]; | ||
| range++; | ||
| } | ||
|
|
||
| // first point | ||
| SMA = sum / period; | ||
|
|
||
| // Calculate value one-by-one for each period in visible data | ||
| for (i = range; i < yValLen; i++) { | ||
| EMAPoint = populateAverage( | ||
| points, | ||
| xVal, | ||
| yVal, | ||
| i, | ||
| EMApercent, | ||
| calEMA, | ||
| index, | ||
| SMA | ||
| ); | ||
| EMA.push(EMAPoint); | ||
| xData.push(EMAPoint[0]); | ||
| yData.push(EMAPoint[1]); | ||
| calEMA = EMAPoint[1]; | ||
|
|
||
| accumulateAverage(points, xVal, yVal, i, index); | ||
| } | ||
|
|
||
| EMAPoint = populateAverage( | ||
| points, | ||
| xVal, | ||
| yVal, | ||
| i, | ||
| EMApercent, | ||
| calEMA, | ||
| index | ||
| ); | ||
| EMA.push(EMAPoint); | ||
| xData.push(EMAPoint[0]); | ||
| yData.push(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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.ema | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.ema | ||
| */ | ||
|
|
||
| /** | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.sma.data | ||
| * @product highstock | ||
| * @apioption series.ema.data | ||
| */ |
| @@ -0,0 +1,265 @@ | ||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var pick = H.pick, | ||
| each = H.each, | ||
| error = H.error, | ||
| Series = H.Series, | ||
| isArray = H.isArray, | ||
| addEvent = H.addEvent, | ||
| seriesType = H.seriesType; | ||
|
|
||
| /** | ||
| * The SMA series type. | ||
| * | ||
| * @constructor seriesTypes.sma | ||
| * @augments seriesTypes.line | ||
| */ | ||
| seriesType('sma', 'line', | ||
| /** | ||
| * Simple moving average indicator (SMA). This series requires `linkedTo` | ||
| * option to be set. | ||
| * | ||
| * @extends {plotOptions.line} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/sma Simple moving average indicator | ||
| * @since 6.0.0 | ||
| * @excluding | ||
| * allAreas,colorAxis,compare,compareBase,joinBy,keys,stacking, | ||
| * showInNavigator,navigatorOptions,pointInterval, | ||
| * pointIntervalUnit,pointPlacement,pointRange,pointStart,joinBy | ||
| * @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} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| name: undefined, | ||
| tooltip: { | ||
| /** | ||
| * Number of decimals in indicator series. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| valueDecimals: 4 | ||
| }, | ||
| /** | ||
| * The main series ID that indicator will be based on. Required for this | ||
| * indicator. | ||
| * | ||
| * @type {String} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| linkedTo: undefined, | ||
| 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. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| index: 0, | ||
| /** | ||
| * The base period for indicator calculations. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| period: 14 | ||
| } | ||
| }, /** @lends Highcharts.Series.prototype */ { | ||
| bindTo: { | ||
| series: true, | ||
| eventName: 'updatedData' | ||
| }, | ||
| useCommonDataGrouping: true, | ||
| nameComponents: ['period'], | ||
| nameSuffixes: [], // e.g. Zig Zag uses extra '%'' in the legend name | ||
| calculateOn: 'init', | ||
| init: function (chart, options) { | ||
| var indicator = this; | ||
|
|
||
| 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 processedData = indicator.getValues( | ||
| indicator.linkedParent, | ||
| indicator.options.params | ||
| ) || { | ||
| values: [], | ||
| xData: [], | ||
| yData: [] | ||
| }; | ||
|
|
||
| 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`.' | ||
| ); | ||
| } | ||
|
|
||
| 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) { | ||
|
|
||
| each( | ||
| this.nameComponents, | ||
| 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 () { | ||
| each(this.dataEventsToUnbind, 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.sma | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.sma | ||
| */ | ||
|
|
||
|
|
||
| /** | ||
| * An array of data points for the series. For the `SMA` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.sma.data | ||
| */ |
| @@ -0,0 +1,208 @@ | ||
| /** | ||
| * @license @product.name@ JS v@product.version@ (@product.date@) | ||
| * | ||
| * Money Flow Index indicator for Highstock | ||
| * | ||
| * (c) 2010-2017 Grzegorz Blachliński | ||
| * | ||
| * License: www.highcharts.com/license | ||
| */ | ||
|
|
||
| 'use strict'; | ||
|
|
||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var isArray = H.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. | ||
| * | ||
| * @constructor seriesTypes.mfi | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| 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. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/mfi | ||
| * Money Flow Index Indicator | ||
| * @since 6.0.0 | ||
| * @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. | ||
| * | ||
| * @type {String} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| volumeSeriesID: 'volume', | ||
| /** | ||
| * Number of maximum decimals that are used in MFI calculations. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| decimals: 4 | ||
|
|
||
| } | ||
| }, { | ||
| 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 | ||
| ); | ||
| } | ||
|
|
||
| // 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 ? true : false; | ||
| // 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 ? true : false; | ||
| // 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.mfi | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.mfi | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `mfi` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.mfi.data | ||
| */ |
| @@ -0,0 +1,109 @@ | ||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var isArray = H.isArray, | ||
| 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. | ||
| * | ||
| * @constructor seriesTypes.momentum | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| seriesType('momentum', 'sma', | ||
| /** | ||
| * Momentum. This series requires `linkedTo` option to be set. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/momentum Momentum indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.momentum | ||
| */ | ||
| { | ||
| params: { | ||
| period: 14 | ||
| } | ||
| }, { | ||
| 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 perdio 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.momentum | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.momentum | ||
| */ | ||
|
|
||
| /** | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.sma.data | ||
| * @product highstock | ||
| * @apioption series.momentum.data | ||
| */ |
| @@ -0,0 +1,345 @@ | ||
| 'use strict'; | ||
|
|
||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var each = H.each, | ||
| defined = H.defined, | ||
| isArray = H.isArray, | ||
| 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; | ||
| } | ||
| } | ||
|
|
||
| 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. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/pivot-points | ||
| * Pivot points | ||
| * @since 6.0.0 | ||
| * @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'` | ||
| * | ||
| * @type {String} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| algorithm: 'standard' | ||
| }, | ||
| marker: { | ||
| enabled: false | ||
| }, | ||
| enableMouseTracking: false, | ||
| dataLabels: { | ||
| enabled: true, | ||
| format: '{point.pivotLine}' | ||
| }, | ||
| dataGrouping: { | ||
| approximation: 'averages' | ||
| } | ||
| }, { | ||
| 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); | ||
|
|
||
| each(indicator.points, function (point) { | ||
| each(indicator.pointArrayMap, 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; | ||
| } | ||
|
|
||
| each(allPivotPoints, function (pivotPoints) { | ||
| path = path.concat( | ||
| SMA.prototype.getGraphPath.call(indicator, pivotPoints) | ||
| ); | ||
| }); | ||
|
|
||
| return path; | ||
| }, | ||
| 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 | ||
| each(pointMapping.concat([false]), 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; | ||
| } | ||
|
|
||
| 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; | ||
| each(values, 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; | ||
| } | ||
| }, { | ||
| // 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.pivotpoints | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.pivotpoints | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `pivotpoints` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.pivotpoints.data | ||
| */ |
| @@ -0,0 +1,262 @@ | ||
| 'use strict'; | ||
|
|
||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var each = H.each, | ||
| merge = H.merge, | ||
| isArray = H.isArray, | ||
| SMA = H.seriesTypes.sma; | ||
|
|
||
| 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. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/price-envelopes | ||
| * Price envelopes | ||
| * @since 6.0.0 | ||
| * @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. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| topBand: 0.1, | ||
| /** | ||
| * Percentage below the moving average that should be displayed. | ||
| * 0.1 means 90%. Relative to the calculated value. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| bottomBand: 0.1 | ||
| }, | ||
| /** | ||
| * Bottom line options. | ||
| * | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| bottomLine: { | ||
| styles: { | ||
| /** | ||
| * Pixel width of the line. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| lineWidth: 1, | ||
| /** | ||
| * Color of the line. If not set, it's inherited from | ||
| * [plotOptions.priceenvelopes.color]( | ||
| * #plotOptions.priceenvelopes.color). | ||
| * | ||
| * @type {String} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| lineColor: undefined | ||
| } | ||
| }, | ||
| /** | ||
| * Top line options. | ||
| * | ||
| * @extends {plotOptions.priceenvelopes.bottomLine} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| topLine: { | ||
| styles: { | ||
| lineWidth: 1 | ||
| } | ||
| }, | ||
| dataGrouping: { | ||
| approximation: 'averages' | ||
| } | ||
| }, /** @lends Highcharts.Series.prototype */ { | ||
| 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); | ||
|
|
||
| each(indicator.points, function (point) { | ||
| each( | ||
| [point.top, point.middle, point.bottom], | ||
| 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: | ||
| each(['topLine', 'bottomLine'], 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.priceenvelopes | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.priceenvelopes | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `priceenvelopes` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.priceenvelopes.data | ||
| */ |
| @@ -0,0 +1,327 @@ | ||
| /** | ||
| * @license @product.name@ JS v@product.version@ (@product.date@) | ||
| * | ||
| * Parabolic SAR indicator for Highstock | ||
| * | ||
| * (c) 2010-2017 Grzegorz Blachliński | ||
| * | ||
| * License: www.highcharts.com/license | ||
| */ | ||
|
|
||
| 'use strict'; | ||
|
|
||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| // 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); | ||
| } else 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. | ||
| * | ||
| * @constructor seriesTypes.psar | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| H.seriesType('psar', 'sma', | ||
|
|
||
| /** | ||
| * Parabolic SAR. This series requires `linkedTo` | ||
| * option to be set and should be loaded | ||
| * after `stock/indicators/indicators.js` file. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/psar | ||
| * Parabolic SAR Indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.psar | ||
| */ | ||
|
|
||
| { | ||
| lineWidth: 0, | ||
| marker: { | ||
| enabled: true | ||
| }, | ||
| states: { | ||
| hover: { | ||
| lineWidthPlus: 0 | ||
| } | ||
| }, | ||
| /** | ||
| * @excluding index | ||
| * @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. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @excluding period | ||
| * @product highstock | ||
| */ | ||
| initialAccelerationFactor: 0.02, | ||
| /** | ||
| * The Maximum value for acceleration factor. | ||
| * AF can reach a maximum of maxAccelerationFactor, | ||
| * no matter how long the uptrend extends. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| maxAccelerationFactor: 0.2, | ||
| /** | ||
| * Acceleration factor increases by increment each time | ||
| * the extreme point makes a new high. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| increment: 0.02, | ||
| /** | ||
| * Index from which PSAR is starting calculation | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| index: 2, | ||
| /** | ||
| * Number of maximum decimals that are used in PSAR calculations. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| 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; | ||
|
|
||
| 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.psar | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.psar | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `psar` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.psar.data | ||
| */ |
| @@ -0,0 +1,144 @@ | ||
| /** | ||
| * (c) 2010-2017 Kacper Madej | ||
| * | ||
| * License: www.highcharts.com/license | ||
| */ | ||
|
|
||
| 'use strict'; | ||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var seriesType = H.seriesType, | ||
| isArray = H.isArray; | ||
|
|
||
| // 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. | ||
| * | ||
| * @constructor seriesTypes.roc | ||
| * @augments seriesTypes.sma | ||
| */ | ||
| 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. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/roc | ||
| * Rate of change indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.roc | ||
| */ | ||
| { | ||
| name: 'Rate of Change (9)', | ||
| params: { | ||
| index: 3, | ||
| period: 9 | ||
| } | ||
| }, { | ||
| 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. | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.roc | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.roc | ||
| */ | ||
|
|
||
| /** | ||
| * @extends series.sma.data | ||
| * @product highstock | ||
| * @apioption series.roc.data | ||
| */ |
| @@ -0,0 +1,157 @@ | ||
| 'use strict'; | ||
|
|
||
| import H from '../parts/Globals.js'; | ||
| import '../parts/Utilities.js'; | ||
|
|
||
| var isArray = H.isArray; | ||
|
|
||
| // Utils: | ||
| function toFixed(a, n) { | ||
| return parseFloat(a.toFixed(n)); | ||
| } | ||
|
|
||
| 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. | ||
| * | ||
| * @extends {plotOptions.sma} | ||
| * @product highstock | ||
| * @sample {highstock} stock/indicators/rsi | ||
| * RSI indicator | ||
| * @since 6.0.0 | ||
| * @optionparent plotOptions.rsi | ||
| */ | ||
| { | ||
| /** | ||
| * @excluding index | ||
| */ | ||
| params: { | ||
| period: 14, | ||
| /** | ||
| * Number of maximum decimals that are used in RSI calculations. | ||
| * | ||
| * @type {Number} | ||
| * @since 6.0.0 | ||
| * @product highstock | ||
| */ | ||
| decimals: 4 | ||
| } | ||
| }, { | ||
| 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). | ||
| * | ||
| * @type {Object} | ||
| * @since 6.0.0 | ||
| * @extends series,plotOptions.rsi | ||
| * @excluding data,dataParser,dataURL | ||
| * @product highstock | ||
| * @apioption series.rsi | ||
| */ | ||
|
|
||
| /** | ||
| * An array of data points for the series. For the `rsi` series type, | ||
| * points are calculated dynamically. | ||
| * | ||
| * @type {Array<Object|Array>} | ||
| * @since 6.0.0 | ||
| * @extends series.line.data | ||
| * @product highstock | ||
| * @apioption series.rsi.data | ||
| */ |