Ram
Posts: 41
Joined: Thu May 24, 2018 12:00 pm

Indicator color

Hello Support,

I've implemented a indicator/study called 'Supertrend' in my existing candlestick chart implementation.
The problem is it's picking up random color each time I select that study/reload browser page.
As per standards, 'supertrend' color must be changes to green when the indicator slips below the closing price
And color must be changes to red when the indicator slips above the closing price.

AS BELOW ::
Supertrend ex.PNG
Supertrend ex.PNG (19.19 KiB) Viewed 1450 times
But what it is plotting right now is :
my ex.PNG
my ex.PNG (16.19 KiB) Viewed 1450 times

Please guide me to fix.

Thanks,
Ram
rafalS
Posts: 2665
Joined: Thu Jun 14, 2018 11:40 am

Re: Indicator color

Hi Ram,

We can use zones to color your indicator line: https://api.highcharts.com/highcharts/series.line.zones

For this, I moved your CalculateSuperTrend function to a different place, so we can now use this which refers to indicator series.

Then I created coloredZones array to keep inside zones x cordinates where we change our colors:

Code: Select all

this.start = this.start || false;
		if (!this.start && SuperTrend > CurrentClose[3]) {
			this.coloredZones = this.coloredZones || [];
			this.coloredZones.push(xVal)
			this.start = true;
		} else if (this.start && SuperTrend < CurrentClose[3]) {
			this.coloredZones.push(xVal)
			this.start = false;
		}
Then I created our zones and updated series:

Code: Select all

this.coloredZones.forEach(function(zoneX, i) {
	if (i % 2) {
		zones.push({
			value: zoneX,
			color: 'red'
		})
	} else {
		zones.push({
			value: zoneX,
			color: 'green'
		})
	}
})
zones.push({
	value: this.xData[this.xData.length - 1],
	color: 'red' //last color is green or red, you need to check this
})

if (!updateFlag) {
	updateFlag = true;
	this.update({
		zones: zones
	}, true, true);
}
You can find whole code here:

Code: Select all

/**
 * @license  Highcharts JS v6.1.0 (2018-04-13)
 *
 * 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 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]);
		}

		//Calculate TR
		function getTR(currentPoint, prevPoint) {
		    var pointY = currentPoint, //Current High,Low
		        prevY = prevPoint,    //Previous High,Low
		        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;
		}

		//Calculate ATR
		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];
		}

		//Calculate BASIC UPPERBAND & BASIC LOWERBAND
		function getBasic(yVal,i,ATRValue) {
		    var pointY = yVal[i-1], //Current High,Low
		        ATR = ATRValue,
		        Multiplier = 3,
				BasUp  =  (pointY[1] + pointY[2]) / 2 + Multiplier * ATR,
				BasLow =  (pointY[1] + pointY[2]) / 2 - Multiplier * ATR;

		    return [BasUp,BasLow];
		}

		//Calculate Final UPPERBAND & Final LOWERBAND
		function getFinal(BasUp, BasLow,PrevFinUp,PrevFinLow,yVal,i) {
			//console.log(yVal);
		    var close = yVal[i-1],
				FinUp = (BasUp < PrevFinUp) || (close[3] > PrevFinUp)  ? BasUp : PrevFinUp,
				FinLow =(BasLow > PrevFinLow) || (close[3] < PrevFinLow) ? BasLow : PrevFinLow;
				//if( (BasUp  < PrevFinUp) && (PrevClose > PrevFinUp) ) {FinUp=BasUp} else {FinUp=PrevFinUp}

		    return [FinUp,FinLow];
		}

		var updateFlag = false,
				zones = [];

		//Calculate SuperTrend
		/**
		 * The ATR series type.
		 *
		 * @constructor seriesTypes.supertrend
		 * @augments seriesTypes.sma
		 */
		seriesType('supertrend', 'sma',
		    /**
		     * Average true range indicator (ATR). This series requires `linkedTo`
		     * option to be set.
		     *
		     * @extends {plotOptions.sma}
		     * @product highstock
		     * @sample {highstock} stock/indicators/supertrend ATR indicator
		     * @since 6.0.0
		     * @optionparent plotOptions.supertrend
		     */
		    {
					zoneAxis: 'x',
		        params: {
		            period: 7
		        }
		    }, {
					CalculateSuperTrend: function (xVal,FinUp,FinLow,CurrentClose) {
					    var x = xVal,
							//lastSupertrend=temp[1],
							SuperTrend = (CurrentClose[3]<= FinUp) ? FinUp : FinLow;
							//SuperTrend;

								//if(lastSupertrend == FinUp && CurrentClose[3]<= FinUp){
									//	SuperTrend=FinUp;
								//}else if(lastSupertrend == FinUp && CurrentClose[3] >= FinUp){
									//	SuperTrend=FinLow;
								//}else if(lastSupertrend == FinLow && CurrentClose[3] >= FinLow){
									//	SuperTrend=FinLow;
								//}else if(lastSupertrend == FinLow && CurrentClose[3] <= FinLow){
									//	SuperTrend=FinUp;
								//}else{
								//	SuperTrend=0
								//}
								this.start = this.start || false;
								if (!this.start && SuperTrend > CurrentClose[3]) {
									this.coloredZones = this.coloredZones || [];
									this.coloredZones.push(xVal)
									this.start = true;
								} else if (this.start && SuperTrend < CurrentClose[3]) {
									this.coloredZones.push(xVal)
									this.start = false;
								}


						return [x,SuperTrend];
					},
		        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,
						Basic=[],
						Final=[],
		                BasUp = 0,
		                BasLow = 0,
		                FinUp = 0,
		                FinLow = 0,
		                PrevFinUp = 0,
		                PrevFinLow = 0,
						SuperTrend=[],
						temp=[],
		                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);
							//Basic = getBasic(yVal,i,point[1]);
							//BasUp = Basic[0];
							//BasLow = Basic[1];
							//Final = getFinal(BasUp,BasLow,PrevFinUp,PrevFinLow,yVal,i);
							//PrevFinUp = BasUp;
							//PrevFinLow = BasLow;
							//FinUp = Final[0];
							//FinLow = Final[1];
		                    //SuperTrend.push(CalculateSuperTrend(xVal[i-1],FinUp,FinLow,yVal[i - 1]));
						//xData.push(point[0]);
		                   //yData.push(point[1]);
							//console.log("--Basic"+Basic+"--BasUp"+BasUp+"--BasLow"+BasLow+"--Final"+Final+"--PrevFinUp"+PrevFinUp+"--PrevFinLow"+PrevFinLow+"--FinUp"+FinUp+"--FinLow"+FinLow+"--SuperTrend"+SuperTrend);

		                } 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++;
		                }

						//console.log("===================Super trend cacl start=================");
		                    //prevATR = TR / (i - 1);
		                    //var tempATR=ATR[ATR.length-1];
							Basic = getBasic(yVal,i,prevATR);
							BasUp = Basic[0];
							BasLow = Basic[1];
							Final = getFinal(BasUp,BasLow,PrevFinUp,PrevFinLow,yVal,i);
							PrevFinUp = Final[0];
							PrevFinLow = Final[1];
							FinUp = Final[0];
							FinLow = Final[1];
		                    SuperTrend.push(this.CalculateSuperTrend(xVal[i-1],FinUp,FinLow,yVal[i - 1]));
												//console.log(this)
							temp=SuperTrend[SuperTrend.length-1];
		                    xData.push(xVal[i - 1]);
		                    yData.push(temp[1]);
							//console.log(SuperTrend[SuperTrend.length-1]);
							//console.log("--Basic"+Basic+"--BasUp"+BasUp+"--BasLow"+BasLow+"--Final"+Final+"--PrevFinUp"+PrevFinUp+"--PrevFinLow"+PrevFinLow+"--FinUp"+FinUp+"--FinLow"+FinLow+"--SuperTrend"+SuperTrend);

		            }


								this.coloredZones.forEach(function(zoneX, i) {
									if (i % 2) {
										zones.push({
											value: zoneX,
											color: 'red'
										})
									} else {
										zones.push({
											value: zoneX,
											color: 'green'
										})
									}
								})
								zones.push({
									value: this.xData[this.xData.length - 1],
									color: 'red' //last color is green or red, you need to check this
								})

								if (!updateFlag) {
									updateFlag = true;
									this.update({
										zones: zones
									}, true, true);
								}

		            return {
		                values: SuperTrend,
		                xData: xData,
		                yData: yData
		            };
		        }

		    });

		/**
		 * A `ATR` series. If the [type](#series.supertrend.type) option is not
		 * specified, it is inherited from [chart.type](#chart.type).
		 *
		 * @type {Object}
		 * @since 6.0.0
		 * @extends series,plotOptions.supertrend
		 * @excluding data,dataParser,dataURL
		 * @product highstock
		 * @apioption series.supertrend
		 */

		/**
		 * @type {Array<Object|Array>}
		 * @since 6.0.0
		 * @extends series.sma.data
		 * @product highstock
		 * @apioption series.supertrend.data
		 */

	}(Highcharts));
}));
Unfortunalety, I don't know whole your custom indicator, so you need to check if indicator colors are good, and if not, you have to calculate zones x cordinates on your own. I gave you general idea, so you should be able to finish on your own.
The second problem is last zone - you need to check if the last zone color should be red or green and push the right color to our zones array (I left comment in line 241).

I hope this is helpfull for you. Please, ask if you have additional questions.

Regards,
Rafal
Rafal Sebestjanski,
Highcharts Team Lead
Ram
Posts: 41
Joined: Thu May 24, 2018 12:00 pm

Re: Indicator color

Thank you for all your explanation,
But this is not helpful.
Zones are implementing color only at first time, If I'm deselecting that study/indicator and re-selecting it, it is picking up random color.

Thanks,
Ram
Ram
Posts: 41
Joined: Thu May 24, 2018 12:00 pm

Re: Indicator color

Hello,

Any output?
Please fix this bug.

Thanks,
Ram
rafalS
Posts: 2665
Joined: Thu Jun 14, 2018 11:40 am

Re: Indicator color

Hello Ram,

I don't know why, but your getValues function fires twice. That's why I created a flag, to make sure that update method fires only once, because it can not be fired twice in the same time.

You need to change a flag a bit:

1) in line 76 change "var updateFlag = false," to "var updateFlag = 0,"
2) in line 134 add "updateFlag++;"
3) in line 245 change "if (!updateFlag) {" to "if (updateFlag % 2) {" and remove "updateFlag = true;"

Also, we need to clear zone array every time the CalculateSuperTrend function is fired, so put "zones = [];" to line 107.

New code:

Code: Select all

/**
 * @license  Highcharts JS v6.1.0 (2018-04-13)
 *
 * 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 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]);
      }

      //Calculate TR
      function getTR(currentPoint, prevPoint) {
          var pointY = currentPoint, //Current High,Low
              prevY = prevPoint,    //Previous High,Low
              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;
      }

      //Calculate ATR
      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];
      }

      //Calculate BASIC UPPERBAND & BASIC LOWERBAND
      function getBasic(yVal,i,ATRValue) {
          var pointY = yVal[i-1], //Current High,Low
              ATR = ATRValue,
              Multiplier = 3,
            BasUp  =  (pointY[1] + pointY[2]) / 2 + Multiplier * ATR,
            BasLow =  (pointY[1] + pointY[2]) / 2 - Multiplier * ATR;

          return [BasUp,BasLow];
      }

      //Calculate Final UPPERBAND & Final LOWERBAND
      function getFinal(BasUp, BasLow,PrevFinUp,PrevFinLow,yVal,i) {
         //console.log(yVal);
          var close = yVal[i-1],
            FinUp = (BasUp < PrevFinUp) || (close[3] > PrevFinUp)  ? BasUp : PrevFinUp,
            FinLow =(BasLow > PrevFinLow) || (close[3] < PrevFinLow) ? BasLow : PrevFinLow;
            //if( (BasUp  < PrevFinUp) && (PrevClose > PrevFinUp) ) {FinUp=BasUp} else {FinUp=PrevFinUp}

          return [FinUp,FinLow];
      }

      var updateFlag = 0,
            zones = [];

      //Calculate SuperTrend
      /**
       * The ATR series type.
       *
       * @constructor seriesTypes.supertrend
       * @augments seriesTypes.sma
       */
      seriesType('supertrend', 'sma',
          /**
           * Average true range indicator (ATR). This series requires `linkedTo`
           * option to be set.
           *
           * @extends {plotOptions.sma}
           * @product highstock
           * @sample {highstock} stock/indicators/supertrend ATR indicator
           * @since 6.0.0
           * @optionparent plotOptions.supertrend
           */
          {
               zoneAxis: 'x',
              params: {
                  period: 7
              }
          }, {
               CalculateSuperTrend: function (xVal,FinUp,FinLow,CurrentClose) {
                   var x = xVal,
                     //lastSupertrend=temp[1],
                     SuperTrend = (CurrentClose[3]<= FinUp) ? FinUp : FinLow;
									 zones = [];
                     //SuperTrend;

                        //if(lastSupertrend == FinUp && CurrentClose[3]<= FinUp){
                           //   SuperTrend=FinUp;
                        //}else if(lastSupertrend == FinUp && CurrentClose[3] >= FinUp){
                           //   SuperTrend=FinLow;
                        //}else if(lastSupertrend == FinLow && CurrentClose[3] >= FinLow){
                           //   SuperTrend=FinLow;
                        //}else if(lastSupertrend == FinLow && CurrentClose[3] <= FinLow){
                           //   SuperTrend=FinUp;
                        //}else{
                        //   SuperTrend=0
                        //}
                        this.start = this.start || false;
                        if (!this.start && SuperTrend > CurrentClose[3]) {
                           this.coloredZones = this.coloredZones || [];
                           this.coloredZones.push(xVal)
                           this.start = true;
                        } else if (this.start && SuperTrend < CurrentClose[3]) {
                           this.coloredZones.push(xVal)
                           this.start = false;
                        }


                  return [x,SuperTrend];
               },
              getValues: function (series, params) {
								updateFlag++;
                  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,
                  Basic=[],
                  Final=[],
                      BasUp = 0,
                      BasLow = 0,
                      FinUp = 0,
                      FinLow = 0,
                      PrevFinUp = 0,
                      PrevFinLow = 0,
                  SuperTrend=[],
                  temp=[],
                      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);
                     //Basic = getBasic(yVal,i,point[1]);
                     //BasUp = Basic[0];
                     //BasLow = Basic[1];
                     //Final = getFinal(BasUp,BasLow,PrevFinUp,PrevFinLow,yVal,i);
                     //PrevFinUp = BasUp;
                     //PrevFinLow = BasLow;
                     //FinUp = Final[0];
                     //FinLow = Final[1];
                          //SuperTrend.push(CalculateSuperTrend(xVal[i-1],FinUp,FinLow,yVal[i - 1]));
                  //xData.push(point[0]);
                         //yData.push(point[1]);
                     //console.log("--Basic"+Basic+"--BasUp"+BasUp+"--BasLow"+BasLow+"--Final"+Final+"--PrevFinUp"+PrevFinUp+"--PrevFinLow"+PrevFinLow+"--FinUp"+FinUp+"--FinLow"+FinLow+"--SuperTrend"+SuperTrend);

                      } 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++;
                      }

                  //console.log("===================Super trend cacl start=================");
                          //prevATR = TR / (i - 1);
                          //var tempATR=ATR[ATR.length-1];
                     Basic = getBasic(yVal,i,prevATR);
                     BasUp = Basic[0];
                     BasLow = Basic[1];
                     Final = getFinal(BasUp,BasLow,PrevFinUp,PrevFinLow,yVal,i);
                     PrevFinUp = Final[0];
                     PrevFinLow = Final[1];
                     FinUp = Final[0];
                     FinLow = Final[1];
                          SuperTrend.push(this.CalculateSuperTrend(xVal[i-1],FinUp,FinLow,yVal[i - 1]));
                                    //console.log(this)
                     temp=SuperTrend[SuperTrend.length-1];
                          xData.push(xVal[i - 1]);
                          yData.push(temp[1]);
                     //console.log(SuperTrend[SuperTrend.length-1]);
                     //console.log("--Basic"+Basic+"--BasUp"+BasUp+"--BasLow"+BasLow+"--Final"+Final+"--PrevFinUp"+PrevFinUp+"--PrevFinLow"+PrevFinLow+"--FinUp"+FinUp+"--FinLow"+FinLow+"--SuperTrend"+SuperTrend);

                  }


                        this.coloredZones.forEach(function(zoneX, i) {
                           if (i % 2) {
                              zones.push({
                                 value: zoneX,
                                 color: 'red'
                              })
                           } else {
                              zones.push({
                                 value: zoneX,
                                 color: 'green'
                              })
                           }
                        })
                        zones.push({
                           value: this.xData[this.xData.length - 1],
                           color: 'red' //last color is green or red, you need to check this
                        })

                        if (updateFlag % 2) {
                           this.update({
                              zones: zones
                           }, true, true);
                        }

                  return {
                      values: SuperTrend,
                      xData: xData,
                      yData: yData
                  };
              }

          });

      /**
       * A `ATR` series. If the [type](#series.supertrend.type) option is not
       * specified, it is inherited from [chart.type](#chart.type).
       *
       * @type {Object}
       * @since 6.0.0
       * @extends series,plotOptions.supertrend
       * @excluding data,dataParser,dataURL
       * @product highstock
       * @apioption series.supertrend
       */

      /**
       * @type {Array<Object|Array>}
       * @since 6.0.0
       * @extends series.sma.data
       * @product highstock
       * @apioption series.supertrend.data
       */

   }(Highcharts));
}));
Kind regards!
Rafal Sebestjanski,
Highcharts Team Lead
Ram
Posts: 41
Joined: Thu May 24, 2018 12:00 pm

Re: Indicator color

Hi Rafal,

I've made these changes but still It's calling twice.

Thanks,
Ram
rafalS
Posts: 2665
Joined: Thu Jun 14, 2018 11:40 am

Re: Indicator color

Ram,

Unfortunately, I don't know why it's still calling twice. You asked me to fix an issue with coloring your indicator when you re-selecting it. Forgive me, but I can not debug thousands of lines of your custom code to find a reason of firing your function twice (it's not related to Highcharts, it's your custom code).

You're welcome,
Rafał
Rafal Sebestjanski,
Highcharts Team Lead
Ram
Posts: 41
Joined: Thu May 24, 2018 12:00 pm

Re: Indicator color

Ok thanks, I'll debug and check on that,

But could you please suggest how can I move my series to plot on canle chart instead of in seprate section?

Like in my first comment 1st snapshot indicator is flipping sides of candlestick chart, but in 2nd snapshot supertrend is appearing in separate portion on chart.

TIA,
Ram
rafalS
Posts: 2665
Joined: Thu Jun 14, 2018 11:40 am

Re: Indicator color

Ram,

You have 2 y-axes and probably your indicator is connected to the second one. I prepared an online simplified demo to show you this case: https://jsfiddle.net/BlackLabel/5kjm2rva/

To show the indicator on the same section, you just need to add/remove axes and link your series to it using series.indicator.yAxis property: https://api.highcharts.com/highstock/series.rsi.yAxis (like it's commented in the jsFiddle above).

Best regards!
Rafal Sebestjanski,
Highcharts Team Lead

Return to “Highcharts Stock”