Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -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
*/

Large diffs are not rendered by default.

@@ -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
*/

Large diffs are not rendered by default.

@@ -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
*/