/**
* @license Highcharts JS v6.0.4 (2017-12-15)
*
* Indicator series type for Highstock
*
* (c) 2010-2017 Sebastian Bochan
*
* License: www.highcharts.com/license
*/
'use strict';
(function(factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory;
} else {
factory(Highcharts);
}
}(function(Highcharts) {
(function(H) {
var UNDEFINED,
seriesType = H.seriesType,
each = H.each,
merge = H.merge,
color = H.color,
isArray = H.isArray,
defined = H.defined,
SMA = H.seriesTypes.sma;
// Utils:
function maxHigh(arr) {
return arr.reduce(function(max, res) {
return Math.max(max, res[1]);
}, -Infinity);
}
function minLow(arr) {
return arr.reduce(function(min, res) {
return Math.min(min, res[2]);
}, Infinity);
}
function highlowLevel(arr) {
return {
high: maxHigh(arr),
low: minLow(arr)
};
}
function getClosestPointRange(axis) {
var closestDataRange,
loopLength,
distance,
xData,
i;
each(axis.series, function(series) {
if (series.xData) {
xData = series.xData;
loopLength = series.xIncrement ? 1 : xData.length - 1;
for (i = loopLength; i > 0; i--) {
distance = xData[i] - xData[i - 1];
if (closestDataRange === UNDEFINED || distance < closestDataRange) {
closestDataRange = distance;
}
}
}
});
return closestDataRange;
}
/**
* The IKH series type.
*
* @constructor seriesTypes.ikh
* @augments seriesTypes.sma
*/
seriesType('ikh', 'sma',
/**
* Ichimoku Kinko Hyo (IKH). This series requires `linkedTo` option to be set.
*
* @extends {plotOptions.sma}
* @product highstock
* @sample {highstock} stock/indicators/ichimoku-kinko-hyo
* Ichimoku Kinko Hyo indicator
* @since 6.0.0
* @excluding
* allAreas,colorAxis,compare,compareBase,joinBy,keys,stacking,
* showInNavigator,navigatorOptions,pointInterval,pointIntervalUnit,
* pointPlacement,pointRange,pointStart
* @optionparent plotOptions.ikh
*/
{
name: 'IKH (52, 26, 9)',
params: {
period: 26,
/**
* The base period for Tenkan calculations.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
periodTenkan: 9,
/**
* The base period for Senkou Span B calculations
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
periodSenkouSpanB: 52
},
marker: {
enabled: false
},
tooltip: {
pointFormat: '\u25CF {series.name}
' +
'TENKAN SEN: {point.tenkanSen:.3f}
' +
'KIJUN SEN: {point.kijunSen:.3f}
' +
'CHIKOU SPAN: {point.chikouSpan:.3f}
' +
'SENKOU SPAN A: {point.senkouSpanA:.3f}
' +
'SENKOU SPAN B: {point.senkouSpanB:.3f}
'
},
/**
* The styles for Tenkan line
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
tenkanLine: {
styles: {
/**
* Pixel width of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineWidth: 1,
/**
* Color of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineColor: undefined
}
},
/**
* The styles for Kijun line
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
kijunLine: {
styles: {
/**
* Pixel width of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineWidth: 1,
/**
* Color of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineColor: undefined
}
},
/**
* The styles for Chikou line
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
chikouLine: {
styles: {
/**
* Pixel width of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineWidth: 1,
/**
* Color of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineColor: undefined
}
},
/**
* The styles for Senkou Span A line
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
senkouSpanA: {
styles: {
/**
* Pixel width of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineWidth: 1,
/**
* Color of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineColor: undefined
}
},
/**
* The styles for Senkou Span B line
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
senkouSpanB: {
styles: {
/**
* Pixel width of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineWidth: 1,
/**
* Color of the line.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
lineColor: undefined
}
},
/**
* The styles for fill between Senkou Span A and B
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
senkouSpan: {
styles: {
/**
* Color of the area between Senkou Span A and B.
*
* @type {Number}
* @since 6.0.0
* @product highstock
*/
fill: 'rgba(255, 0, 0, 0.5)'
}
},
dataGrouping: {
approximation: 'averages'
}
}, {
pointArrayMap: ['tenkanSen', 'kijunSen', 'chikouSpan', 'senkouSpanA', 'senkouSpanB'],
pointValKey: 'tenkanSen',
init: function() {
SMA.prototype.init.apply(this, arguments);
// Set default color for lines:
this.options = merge({
tenkanLine: {
styles: {
lineColor: this.color
}
},
kijunLine: {
styles: {
lineColor: this.color
}
},
chikouLine: {
styles: {
lineColor: this.color
}
},
senkouSpanA: {
styles: {
lineColor: this.color,
fill: color(this.color).setOpacity(0.5).get()
}
},
senkouSpanB: {
styles: {
lineColor: this.color,
fill: color(this.color).setOpacity(0.5).get()
}
},
senkouSpan: {
styles: {
fill: color(this.color).setOpacity(0.2).get()
}
}
}, this.options);
},
toYData: function(point) {
return [
point.tenkanSen,
point.kijunSen,
point.chikouSpan,
point.senkouSpanA,
point.senkouSpanB
];
},
translate: function() {
var indicator = this;
SMA.prototype.translate.apply(indicator);
each(indicator.points, function(point) {
each(indicator.pointArrayMap, function(value) {
if (defined(point[value])) {
point['plot' + value] = indicator.yAxis.toPixels(
point[value],
true
);
// add extra parameters for support tooltip in moved lines
point.plotY = point['plot' + value];
point.tooltipPos = [point.plotX, point['plot' + value]];
point.isNull = false;
}
});
});
},
// One does not simply
// Render five lines
// And an arearange
// In just one series..
drawGraph: function() {
var indicator = this,
mainLinePoints = indicator.points,
pointsLength = mainLinePoints.length,
mainLineOptions = indicator.options,
mainLinePath = indicator.graph,
mainColor = indicator.color,
gappedExtend = {
options: {
gapSize: mainLineOptions.gapSize
}
},
pointArrayMapLength = indicator.pointArrayMap.length,
allIchimokuPoints = [
[],
[],
[],
[],
[],
[]
],
position,
point,
i;
// Generate points for all lines and spans lines:
while (pointsLength--) {
point = mainLinePoints[pointsLength];
for (i = 0; i < pointArrayMapLength; i++) {
position = indicator.pointArrayMap[i];
if (defined(point[position])) {
allIchimokuPoints[i].push({
plotX: point.plotX,
plotY: point['plot' + position],
isNull: false
});
}
}
}
// Modify options and generate lines:
each(['tenkanLine', 'kijunLine', 'chikouLine', 'senkouSpanA', 'senkouSpanB', 'senkouSpan'], function(lineName, i) {
// First line is rendered by default option
indicator.points = allIchimokuPoints[i];
indicator.options = merge(mainLineOptions[lineName].styles, gappedExtend);
indicator.graph = indicator['graph' + lineName];
// For span, we need an access to the next points, used in getGraphPath()
indicator.nextPoints = allIchimokuPoints[i - 1];
if (i === 5) {
indicator.points = allIchimokuPoints[i - 1];
indicator.options = merge(mainLineOptions[lineName].styles, gappedExtend);
indicator.graph = indicator['graph' + lineName];
indicator.nextPoints = allIchimokuPoints[i - 2];
indicator.fillGraph = true;
indicator.color = indicator.options.fill;
SMA.prototype.drawGraph.call(indicator);
} else {
indicator.fillGraph = false;
indicator.color = mainColor;
SMA.prototype.drawGraph.call(indicator);
}
// Now save lines:
indicator['graph' + lineName] = indicator.graph;
});
// Clean temporary properties:
delete indicator.nextPoints;
delete indicator.fillGraph;
// Restore options and draw the Tenkan line:
indicator.points = mainLinePoints;
indicator.options = mainLineOptions;
indicator.graph = mainLinePath;
},
getGraphPath: function(points) {
var indicator = this,
path = [],
spanA,
fillArray = [],
spanAarr = [];
points = points || this.points;
// Render Senkou Span
if (indicator.fillGraph && indicator.nextPoints) {
spanA = SMA.prototype.getGraphPath.call(
indicator,
// Reverse points, so Senkou Span A will start from the end:
indicator.nextPoints
);
spanA[0] = 'L';
path = SMA.prototype.getGraphPath.call(
indicator,
points
);
spanAarr = spanA.slice(0, path.length);
for (var i = (spanAarr.length - 1); i > 0; i -= 3) {
fillArray.push(
spanAarr[i - 2],
spanAarr[i - 1],
spanAarr[i]
);
}
path = path.concat(fillArray);
} else {
path = SMA.prototype.getGraphPath.apply(indicator, arguments);
}
return path;
},
getValues: function(series, params) {
var period = params.period,
periodTenkan = params.periodTenkan,
periodSenkouSpanB = params.periodSenkouSpanB,
xVal = series.xData,
yVal = series.yData,
xAxis = series.xAxis,
yValLen = (yVal && yVal.length) || 0,
closestPointRange = getClosestPointRange(xAxis),
IKH = [],
xData = [],
dateStart,
date,
slicedTSY,
slicedKSY,
slicedSSBY,
pointTS,
pointKS,
pointSSB,
i,
TS,
KS,
CS,
SSA,
SSB;
// ikh requires close value
if (xVal.length <= period || !isArray(yVal[0]) || yVal[0].length !== 4) {
return false;
}
// add timestamps at the beginning
dateStart = xVal[0] - (period * closestPointRange);
for (i = 0; i < period; i++) {
xData.push(dateStart + i * closestPointRange);
}
for (i = 0; i < yValLen; i++) {
// Tenkan Sen
if (i >= periodTenkan) {
slicedTSY = yVal.slice(i - periodTenkan, i);
pointTS = highlowLevel(slicedTSY);
TS = (pointTS.high + pointTS.low) / 2;
}
if (i >= period) {
slicedKSY = yVal.slice(i - period, i);
pointKS = highlowLevel(slicedKSY);
KS = (pointKS.high + pointKS.low) / 2;
SSA = (TS + KS) / 2;
}
if (i >= periodSenkouSpanB) {
slicedSSBY = yVal.slice(i - periodSenkouSpanB, i);
pointSSB = highlowLevel(slicedSSBY);
SSB = (pointSSB.high + pointSSB.low) / 2;
}
CS = yVal[i][0];
date = xVal[i];
if (IKH[i] === UNDEFINED) {
IKH[i] = [];
}
if (IKH[i + period] === UNDEFINED) {
IKH[i + period] = [];
}
IKH[i + period][0] = TS;
IKH[i + period][1] = KS;
IKH[i + period][2] = UNDEFINED;
if (i >= period) {
IKH[i - period][2] = CS;
} else {
IKH[i + period][3] = UNDEFINED;
IKH[i + period][4] = UNDEFINED;
}
if (IKH[i + 2 * period] === UNDEFINED) {
IKH[i + 2 * period] = [];
}
IKH[i + 2 * period][3] = SSA;
IKH[i + 2 * period][4] = SSB;
xData.push(date);
}
// add timestamps for further points
for (i = 1; i <= period; i++) {
xData.push(date + i * closestPointRange);
}
return {
values: IKH,
xData: xData,
yData: IKH
};
}
});
/**
* A `IKH` series. If the [type](#series.ikh.type) option is not
* specified, it is inherited from [chart.type](#chart.type).
*
* For options that apply to multiple series, it is recommended to add
* them to the [plotOptions.series](#plotOptions.series) options structure.
* To apply to all series of this specific type, apply it to
* [plotOptions.ikh](#plotOptions.ikh).
*
* @type {Object}
* @since 6.0.0
* @extends series,plotOptions.ikh
* @excluding data,dataParser,dataURL
* @product highstock
* @apioption series.ikh
*/
/**
* @since 6.0.0
* @extends series.sma.data
* @product highstock
* @apioption series.ikh.data
*/
}(Highcharts));
}));