highcharts
Version:
JavaScript charting framework
1,326 lines (1,193 loc) • 357 kB
JavaScript
/**
* @license Highcharts JS v7.1.2 (2019-06-04)
*
* 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
*/
var error = H.error;
var requiredIndicatorMixin = {
/**
* Check whether given indicator is loaded, else throw error.
* @param {function} indicator Indicator constructor function.
* @param {string} requiredIndicator required indicator type.
* @param {string} type Type of indicator where function was called (parent).
* @param {function} 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.
* @returns {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;
},
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['mixins/indicator-required.js']], function (H, requiredIndicatorMixin) {
/* *
*
* License: www.highcharts.com/license
*
* */
var pick = H.pick,
error = H.error,
Series = H.Series,
isArray = H.isArray,
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, 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(
H.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']], function (H) {
/* *
*
* License: www.highcharts.com/license
*
* */
var correctFloat = H.correctFloat,
isArray = H.isArray,
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']], function (H) {
/**
*
* (c) 2010-2019 Wojciech Chmiel
*
* License: www.highcharts.com/license
*
* */
var each = H.each,
merge = H.merge,
error = H.error,
defined = H.defined,
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 = {
/**
* 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
* pointValKey - 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 {string} 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
*/
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
*/
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
}
},
secondaryLines = [], // additional lines point place holders
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,
aroonDo