// https://forex-station.com/viewtopic.php?p=1295478806#p1295478806 //button code1
// https://forex-station.com/viewtopic.php?p=1295478883#p1295478883 //button code2
//https://www.mql5.com/en/code/39636
#property copyright "Bugscoder Studio"
#property link      "https://www.bugscoder.com/"
#property version   "1.00"
#property strict
#property indicator_separate_window

#property indicator_buffers 10
#property indicator_type1   DRAW_NONE

#property indicator_type2   DRAW_HISTOGRAM
#property indicator_color2  clrLime
#property indicator_width2  2
#property indicator_type3   DRAW_HISTOGRAM
#property indicator_color3  clrGreen
#property indicator_width3  2
#property indicator_type4   DRAW_HISTOGRAM
#property indicator_color4  clrRed
#property indicator_width4  2
#property indicator_type5   DRAW_HISTOGRAM
#property indicator_color5  clrMaroon
#property indicator_width5  2

#property indicator_type6   DRAW_ARROW
#property indicator_color6  clrBlue
#property indicator_width6  2
#property indicator_type7   DRAW_ARROW
#property indicator_color7  clrBlack
#property indicator_width7  2
#property indicator_type8   DRAW_ARROW
#property indicator_color8  clrGray
#property indicator_width8  2

#property indicator_type9   DRAW_NONE
#property indicator_type10   DRAW_NONE

input int    length       = 20; //BB Length
input double mult         = 2.0; //BB MultFactor
input int    lengthKC     = 20; //KC Length
input double multKC       = 1.5; //KC MultFactor
input bool   useTrueRange = true; //Use TrueRange (KC)

//Forex-Station button template start41; copy and paste
extern string             button_note1_         = "------------------------------";
extern int                btn_Subwindow         = 0;                               // What window to put the button on.  If <0, the button will use the same sub-window as the indicator.
extern ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;               // button corner on chart for anchoring
extern string             btn_text              = "BBsqueeze";                     // a button name
extern string             btn_Font              = "Arial";                         // button font name
extern int                btn_FontSize          = 9;                               // button font size               
extern color              btn_text_ON_color     = clrLime;                         // ON color when the button is turned on
extern color              btn_text_OFF_color    = clrRed;                          // OFF color when the button is turned off
extern color              btn_background_color  = clrDimGray;                      // background color of the button
extern color              btn_border_color      = clrBlack;                        // border color the button
extern int                button_x              = 20;                              // x coordinate of the button     
extern int                button_y              = 25;                              // y coordinate of the button     
extern int                btn_Width             = 80;                              // button width
extern int                btn_Height            = 20;                              // button height
extern string             UniqueButtonID        = "SQZMOM";                       // Unique ID for each button        

// The next two lines control whether the sub-window height should be adjusted automatically.
//   If 'false', nothing has changed vs. the usual normal defaults.
//   If 'true', then the initial sub-window height is auto-set, but the user can then (usually) stretch the height of the sub-window as desired.
//   The problem is there are some strange MT4 BUGS and behavior when using IndicatorSetInteger(INDICATOR_HEIGHT,value), where value is the default height, then followed by value "NULL" to restore manual control.
//   The manual control does NOT always work right!  (This is the drawback).  Sometimes it works.  Sometimes you can only make the sub-window bigger and then can not make it smaller.
// Your  choice whether to enable this feature.  Change 'false' to 'true' to enable by default.
extern bool               Auto_Adjust_Window_Height = false;              // Opt. 'true' will auto-set a default sub-window size (which can be changed). However, MT4 might then behave strangely when manually adjusting the window height.
extern int                Default_Active_Window_Height = 250;            // Pick whatever you want to be your default sub-window height when the indicator is "on"
//
extern string             button_note2          = "------------------------------";

bool show_data, recalc=false;
int swin;
string IndicatorObjPrefix, buttonId;
//Forex-Station button template end41; copy and paste

double range[], no[], On[], Off[], linregsrc[];
double upup[], updn[], dndn[], dnup[], linreg[];
string obj_prefix = "SQZMOM_LB_";
//+------------------------------------------------------------------------------------------------------------------+
//Forex-Station button template start42; copy and paste
int OnInit()
{
   IndicatorDigits(Digits);
   IndicatorObjPrefix = "__" + btn_text + "__";
   
   swin = ChartWindowFind(); // This is the real (sub-)window number for the indicator
   if(btn_Subwindow<0) btn_Subwindow = swin;
   
   // The leading "_" gives buttonId a *unique* prefix.  Furthermore, prepending the swin is usually unique unless >2+ of THIS indy are displayed in the SAME sub-window. (But, if >2 used, be sure to shift the buttonId position)
   buttonId = "_" + IntegerToString(swin) + UniqueButtonID + IndicatorObjPrefix + "_BT_";
   if (ObjectFind(buttonId)<0) 
      createButton(buttonId, btn_text, btn_Width, btn_Height, btn_Font, btn_FontSize, btn_background_color, btn_border_color, btn_text_ON_color);
   ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE, button_y);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE, button_x);

   OnInit2();

   show_data = ObjectGetInteger(0, buttonId, OBJPROP_STATE);
   adjustWindowHeight();
   
   if (show_data) ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_ON_color); 
   else ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_OFF_color);
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------------------------------------------------------+
void createButton(string buttonID,string buttonText,int width2,int height,string font,int fontSize,color bgColor,color borderColor,color txtColor)
{
      ObjectDelete    (0,buttonID);
      ObjectCreate    (0,buttonID,OBJ_BUTTON,btn_Subwindow,0,0);
      ObjectSetInteger(0,buttonID,OBJPROP_COLOR,txtColor);
      ObjectSetInteger(0,buttonID,OBJPROP_BGCOLOR,bgColor);
      ObjectSetInteger(0,buttonID,OBJPROP_BORDER_COLOR,borderColor);
      ObjectSetInteger(0,buttonID,OBJPROP_BORDER_TYPE,BORDER_RAISED);
      ObjectSetInteger(0,buttonID,OBJPROP_XSIZE,width2);
      ObjectSetInteger(0,buttonID,OBJPROP_YSIZE,height);
      ObjectSetString (0,buttonID,OBJPROP_FONT,font);
      ObjectSetString (0,buttonID,OBJPROP_TEXT,buttonText);
      ObjectSetInteger(0,buttonID,OBJPROP_FONTSIZE,fontSize);
      ObjectSetInteger(0,buttonID,OBJPROP_SELECTABLE,0);
      ObjectSetInteger(0,buttonID,OBJPROP_CORNER,btn_corner);
      ObjectSetInteger(0,buttonID,OBJPROP_HIDDEN,1);
      ObjectSetInteger(0,buttonID,OBJPROP_XDISTANCE,9999);
      ObjectSetInteger(0,buttonID,OBJPROP_YDISTANCE,9999);
      // Upon creation, set the initial state to "true" which is "on", so one will see the indicator by default
      ObjectSetInteger(0, buttonId, OBJPROP_STATE, true);
}
//+------------------------------------------------------------------------------------------------------------------+
void OnDeinit(const int reason) 
{
   // If just changing a TF', the button need not be deleted, therefore the 'OBJPROP_STATE' is also preserved.
   if(reason != REASON_CHARTCHANGE) ObjectDelete(buttonId);
   OnDeinit2();
}
//+------------------------------------------------------------------------------------------------------------------+
void adjustWindowHeight()
{
   if(Auto_Adjust_Window_Height)
   {
      if(show_data)
      {
         IndicatorSetInteger(INDICATOR_HEIGHT,Default_Active_Window_Height);
         IndicatorSetInteger(INDICATOR_HEIGHT,NULL); // AFTER adjusting to a fixed height, set to "NULL" so that it can then be manually adjusted!
      }
      else
      {
         // If this number (70) is small enough (e.g. 10), the sub-window may disappear when "OFF", HOWEVER, when "ON" again, it may hinder manual adjustments of the height. Strange MT4 behaviors occur.
         IndicatorSetInteger(INDICATOR_HEIGHT,70); 
         IndicatorSetInteger(INDICATOR_HEIGHT,NULL);
      }
   }
}
//+------------------------------------------------------------------------------------------------------------------+
void OnChartEvent(const int id, //don't change anything here
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // If another indy on the same chart has enabled events for create/delete/mouse-move, just skip this events up front because they aren't
   //    needed, AND in the worst case, this indy might cause MT4 to hang!!  Skipping the events seems to help, along with other (major) changes to the code below.
   if(id==CHARTEVENT_OBJECT_CREATE || id==CHARTEVENT_OBJECT_DELETE) return; // This appears to make this indy compatible with other programs that enabled CHART_EVENT_OBJECT_CREATE and/or CHART_EVENT_OBJECT_DELETE
   if(id==CHARTEVENT_MOUSE_MOVE    || id==CHARTEVENT_MOUSE_WHEEL)   return; // If this, or another program, enabled mouse-events, these are not needed below, so skip it unless actually needed. 

   if (id==CHARTEVENT_OBJECT_CLICK && sparam == buttonId)
   {
      show_data = ObjectGetInteger(0, buttonId, OBJPROP_STATE);
      adjustWindowHeight();
      
      if (show_data)
      {
         ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_ON_color); 
         SetIndexStyle(0,DRAW_NONE);
         SetIndexStyle(1,DRAW_HISTOGRAM);
         SetIndexStyle(2,DRAW_HISTOGRAM);
         SetIndexStyle(3,DRAW_HISTOGRAM);
         SetIndexStyle(4,DRAW_HISTOGRAM);
         SetIndexStyle(5,DRAW_ARROW);
         SetIndexStyle(6,DRAW_ARROW);
         SetIndexStyle(7,DRAW_ARROW);
         SetIndexStyle(8,DRAW_NONE);
         SetIndexStyle(9,DRAW_NONE);
         // Is it a problem to call 'start()' ??  Possibly it makes no difference, but now calling "mystart()" instead of "start()"; and "start()" simply runs "mystart()", so should be same as before.
         recalc=true;
         mystart();
      }
      else
      {
         ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_OFF_color);
         for (int ForexStation=0; ForexStation<indicator_buffers; ForexStation++)
              SetIndexStyle(ForexStation,DRAW_NONE);
              OnDeinit2();
      }
   }
}
//Forex-Station button template end42; copy and paste
//+------------------------------------------------------------------------------------------------------------------+

int OnInit2() {
   IndicatorDigits(Digits);
   
   SetIndexLabel(0, "range");
   SetIndexBuffer(0, range);
   
   SetIndexLabel(1, "upup (1)");
   SetIndexBuffer(1, upup);
   SetIndexLabel(2, "updn (2)");
   SetIndexBuffer(2, updn);
   SetIndexLabel(3, "dndn (3)");
   SetIndexBuffer(3, dndn);
   SetIndexLabel(4, "dnup (4)");
   SetIndexBuffer(4, dnup);
   
   SetIndexLabel(5, "noSqz (5)");
   SetIndexBuffer(5, no);
   SetIndexArrow(5, 167);
   SetIndexLabel(6, "sqzOn (6)");
   SetIndexBuffer(6, On);
   SetIndexArrow(6, 167);
   SetIndexLabel(7, "sqzOff (7)");
   SetIndexBuffer(7, Off);
   SetIndexArrow(7, 167);
   
   SetIndexLabel(8, "linregsrc");
   SetIndexBuffer(8, linregsrc);
   SetIndexLabel(9, "linreg");
   SetIndexBuffer(9, linreg);

   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------------------------------------------------------+
//int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[],
//                const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) {
//+------------------------------------------------------------------------------------------------------------------+
int start() {return(mystart()); }
//+------------------------------------------------------------------------------------------------------------------+
int mystart()
  {
   if (show_data)
      {
        int counted_bars=IndicatorCounted();
        if(counted_bars<0) return(-1);
        if(counted_bars>0) counted_bars--;

        if(recalc) 
        {
           // If a button goes from off-to-on, everything must be recalculated.  The 'recalc' variable is used as a trigger to do this.
           counted_bars = 0;
           recalc=false;
        }

        int limit = fmin(Bars-counted_bars-lengthKC-2,Bars-1); 

        for (int pos=limit; pos>=0; pos--)
           {
             // Calculate BB
                           double basis   = iMA(NULL, 0, length, 0, MODE_SMA, PRICE_CLOSE, pos);
             double dev     = mult * iStdDev(NULL, 0, length, 0, MODE_SMA, PRICE_CLOSE, pos);
             double upperBB = basis + dev;
             double lowerBB = basis - dev;
             
             // Calculate KC
             double ma             = iMA(NULL, 0, lengthKC, 0, MODE_SMA, PRICE_CLOSE, pos);
             range[pos]     = useTrueRange ? tr(pos) : (High[pos] - Low[pos]);
             double rangema = iMAOnArray(range, 0, lengthKC, 0, MODE_SMA, pos);
             double upperKC = ma + rangema * multKC;
             double lowerKC = ma - rangema * multKC;

             bool sqzOn  = (lowerBB > lowerKC) && (upperBB < upperKC);
             bool sqzOff = (lowerBB < lowerKC) && (upperBB > upperKC);
             bool noSqz  = (sqzOn == false) && (sqzOff == false);
             
             double highest = High[iHighest(NULL, 0, MODE_HIGH, lengthKC, pos)];
             double lowest  = Low[iLowest(NULL, 0, MODE_LOW, lengthKC, pos)];
             double sma     = iMA(NULL, 0, lengthKC, 0, MODE_SMA, PRICE_CLOSE, pos);
             
             linregsrc[pos] = Close[pos]-(((highest+lowest)/2)+sma)/2;
             linreg[pos]    = linreg(linregsrc, lengthKC, pos);
                          
             upup[pos] = linreg[pos] > 0 && linreg[pos] > nz(linreg[pos+1]) ? linreg[pos] : EMPTY_VALUE;
             updn[pos] = linreg[pos] > 0 && linreg[pos] < nz(linreg[pos+1]) ? linreg[pos] : EMPTY_VALUE;
             dndn[pos] = linreg[pos] < 0 && linreg[pos] < nz(linreg[pos+1]) ? linreg[pos] : EMPTY_VALUE;
             dnup[pos] = linreg[pos] < 0 && linreg[pos] > nz(linreg[pos+1]) ? linreg[pos] : EMPTY_VALUE;
             
             no[pos]  = (noSqz == true)  ? 0 : EMPTY_VALUE;
             On[pos]  = (sqzOn == true)  ? 0 : EMPTY_VALUE;
             Off[pos] = (sqzOff == true) ? 0 : EMPTY_VALUE;
           }     
    } //if (show_data)  

   return(0);
}
//+------------------------------------------------------------------------------------------------------------------+
int OnDeinit2() {
   ObjectsDeleteAll(0, obj_prefix);
   return(0);
}
//+------------------------------------------------------------------------------------------------------------------+
string TimeCleanStr(int pos) {
   string _time = TimeToStr(Time[pos], TIME_DATE|TIME_MINUTES);
   
   StringReplace(_time, ":", "");
   StringReplace(_time, ".", "");
   StringReplace(_time, " ", "");
   
   return _time;
}
//+------------------------------------------------------------------------------------------------------------------+
double tr(int shift) {
   double t1 = High[shift] - Low[shift];
   double t2 = MathAbs(High[shift] - Close[shift+1]);
   double t3 = MathAbs(Low[shift] - Close[shift+1]);
   
   return MathMax(MathMax(t1, t2), t3);
}
//+------------------------------------------------------------------------------------------------------------------+
double linreg(double &src[], int p, int i) {
   double SumY  = 0;
   double Sum1  = 0;
   double Slope = 0;
   double c;
   
   for (int x=0; x<=p-1;x++) {
      c = src[x+i];
      SumY += c;
      Sum1 += x*c;
   }
   
   double SumBars    = p*(p-1)*0.5;
   double SumSqrBars = (p-1)*p*(2*p-1)/6;
	double Sum2       = SumBars*SumY;
	double Num1       = p*Sum1-Sum2;
	double Num2       = SumBars*SumBars-p*SumSqrBars;
	if(Num2!=0) {
	   Slope = Num1/Num2;
   }
	else {
	   Slope = 0;
   }
   
	double Intercept = (SumY-Slope*SumBars)/p;
	double linregval = Intercept+Slope*(p-1);
	
	return(linregval);
}
//+------------------------------------------------------------------------------------------------------------------+
double nz(double check, double val = 0) {
   if (check == EMPTY_VALUE) {
      return val;
   }
   else {
      return check;
   }
}
//+------------------------------------------------------------------------------------------------------------------+
