//+------------------------------------------------------------------+
//|                                                  P4L BoxFibo.mq4 |
//|                               A significant rewrite by pips4life |
//|                 Original BoxFibo.mq4 by hanover + mods by others |
//+------------------------------------------------------------------+

#property copyright "v2.00+ by pips4life"
#property link      "https://www.forexfactory.com/thread/662529-modified-useful-mq4-utilities-indicators-and-related-tips" // TAG: v2_05  Update if posted in new thread
#property indicator_chart_window
#property strict

#property description "This indicator draws boxes around *completed* time-windows from:\n StartTime (inclusive) to EndTime (exclusive)"
#property description "The indicator is intended for Timeframes <= H1."
#property description "\nA Fibo is drawn from the Low-to-High of the box.  The 'FibPercentLevels' are marked, with optional Description/Percent/Price"
#property description "On/Off button toggles the display."
#property description "One may add more-than-one of this indicator so long as either the StartTime and/or EndTime differs."

#property version "2.05"  // TAG: v2_05
extern string _VERSION_v2_05___2025_Nov_03 = "By pips4life. (Orig: hanover)";


/*
VERSION HISTORY:

2025-Nov-03 v2_05 by pips4life
   Added support for an EndTime can be an hour BEFORE the StartTime, e.g. start=21:00, end = 00:00.
   Added an adjustment of dt1 to cross the weekend gap, else some boxes might not draw properly.
   Because "strict" mode is used, cleaned up the extern comments to clarify what the inputs do.
   
 
2025-Feb-01 v2_04 by pips4life    
   Add HIDDEN=true, and SELECTABLE=false properties to created objects.
   
2025-Jan-26 v2_03 by pips4life    
   Fixed bug with the button.  It toggled on/off fine, but if off, changing the TF should keep it off, yet it drew the objects.
      Also, when off, a simple "Refresh" of the chart would actually draw the objects!
      Added a 'buttonStatus' conditional in OnInit() before it runs 'plot_obj()'
      Also added a 'buttonStatus' conditional in start() so that it exits immediately if the button is off.
      With the fix, changing the TF (or a Refresh) honors the buttonStatus (on/off) setting, whatever it is. 

2023-Apr-28 v2_02 by pips4life    
   Replaced 'SupressIndicatorForTFsAbove_minutes'(60) with 'SupressIndicatorIfAbove_TF' (default PERIOD_H1)

2023-Apr-26 v2_01 by pips4life
   Changed internal 'debugmicro' value from "TRUE" to "false", as was intended for a production release.
   
2023-Apr-25 v2_00 by pips4life
   NOTE: These are major changes, and it would not be unusual if there are still bugs!  Please report any you find.
   Optimized performance.   Previous version ran *every* tick, and depending up TF, might take 400 - 1000 microseconds (which is a problem during high-tick news events!)
     The previous version used very slow (wasteful) functions StrToTime and TimeToStr far too often. Replaced with faster equivalent calculations. 
     The previous version also recalculated ALL the previous boxes, for every tick, which is a total waste of CPU!  
     This version runs only once-per-second; OR, if a new bar; OR, if old bars have refreshed.  It does not recalculate old boxes unnecessarily.  Most ticks are 1-5 microseconds!
     (This version is not completely optimized to prevent ALL unnecessary re-calculations, but it's far better than before and good enough.)
   Because this version is much faster, the NumDays default is now 30 instead of 10 (Users may customize as desired).
   Added on/off button.
   Eliminated 'identity' variable, and replaced with internal *unique* 'createdObjectPrefix' (so long as either StartTime or EndTime are unique!)
   The default BoxColor & FiboColor are best for lightBG charts.  Added 'AutoFlipColorsIfDarkBG' (true) which will 'flip' the colors automatically
      if used on a dark background chart.  (*IF* the user wants specific colors for a dark BG chart, choose your colors AND make this var 'false'!)
   New var 'FibPercentLevels' (0 50 100 200 -100).  The user may customize the Fibo levels as desired.
   New var 'EXTRA_Corresponding_FibLevelDesc'(",,,,") to specify a prefix description for each Fibo level, as desired.
   New vars 'ShowPricePerlevel'(true) and 'ShowPercentPerLevel'(true) which determine what is displayed on each Fibo line (e.g. with both, "100 1.34502")
   New var 'DisplayPercentWithNumberOfDigits'(0) will display the %# level with specified accuracy, e.g. 0 displays "50", 1 displays "50.0"
   New var 'ShiftFiboTime2By_seconds'(3600) affects how far to the right the Fibo lines extend. 3600 is fairly consistent across <= H1 timeframes.
   New var 'SupressIndicatorForTFsAbove_minutes' (60) will suppress the BoxFibo's for TF's > 60 minutes (H1). (This indicator is really only 
      intended for <= H1 timeframes anyway). Note: Toggling the button will *ignore* this variable, and force-display on <= H4 timeframes.
   The previous version suffered from a few major bugs which might be infrequent, but would nevertheless occur:  
      * New boxes would overdraw old boxes, which would make them completely invisible!
      * The StartTime might include the previous bar on certain Symbols (e.g. XAUUSD (Gold) often does not have a bar at 00:00. The old version included the "23:00" bar.
   The previous version offered unique Fibo level text labels for specific Fibo % levels.  The same may be achieved using the
     comma-separated 'EXTRA_Corresponding_FibLevelDesc', which adds any non-blank text as a prefix to the corresponding Fibo level.
     To restore the original levels, comment out the new 2 lines and uncomment out the old 2 lines with the desired 'FibPercentLevels' and 'EXTRA_Corresponding_FibLevelDesc'

2009-2023 Various mods by others.

2009-Jun-07  Orig. by hanover. https://www.forexfactory.com/thread/post/2785607#post2785607

FUTURE PLANS/ENHANCEMENTS/ETC.:
   NEXT(2023-APR) : Rewrite the (pre-b600+) 'stringSplitBy*' functions to take advantage of the 'StringSplit()' function (available in MQL4 since b600+).
      A basic example to split by commas: fibCount = StringSplit(FibPercentLevels,StringGetChar(",",0),fibLevelStr);
   NEXT(2023-APR) : In the code there is the line: "if (dt2 != Time[i])  continue;"    For most ticks, "limit=1", which means this
      portion of the code repeats the drawing of the most recent Box and Fibo, over-and-over.  It would improve performance to draw that Box & Fibo only once, not repetitively.

*/


string createdObjectPrefix = "_BoxFibo_"; // This var (replaces 'identity' is modified in OnInit(), and will be unique so long as StartTime & EndTime are unique.

extern string StartTime                           = "00:00"; // StartTime (inclusive)
extern string EndTime                             = "09:00"; // EndTime (exclusive)
extern int    NumDays                             = 30;
extern string FibPercentLevels                    = "0, 50, 100, 200, -100";
extern string EXTRA_Corresponding_FibLevelDesc    = ",,,,";
// These next 2 lines were the levels & labels used in previous versions:
//extern string FibPercentLevels                    = "  0,        50,  100, -23.6,    -38.2,        -61.8, -100, 123.6,    138.2,        161.8, 200, -138.2, 238.2, -161.8, 261.8";
//extern string EXTRA_Corresponding_FibLevelDesc    = "Low, Mid Price, High, Entry, TP2 Stop, TP1/TP3 Stop,  TP2, Entry, TP2 Stop, TP1/TP3 Stop, TP2,    TP3,   TP3,    TP4,   TP4";
//
extern bool   ShowPercentPerLevel                 = true;
extern bool   ShowPricePerlevel                   = true;
// DisplayPercentWithNumberOfDigits: E.g. 0 displays '50', 1 displays '50.0'
extern int    DisplayPercentWithNumberOfDigits    = 0; 
extern color  BoxColor                            = clrLightBlue;
extern color  FiboColor                           = clrBlack;
extern bool   AutoFlipColorsIfDarkBG              = true;
extern ENUM_TIMEFRAMES SupressIndicatorIfAbove_TF = PERIOD_H1;
//extern int    SupressIndicatorForTFsAbove_minutes = 60;
extern int    ShiftFiboTime2By_seconds            = 3600; 
// ShiftFiboTime2By_seconds: The larger the number, the farther to the right the Fibo lines extend (depending upon TimeFrame). 3600 is consistent across <=H1

//Forex-Station button template start41; copy and paste
extern string             button_note1_          = "------------------------------";
       int                btn_Subwindow          = 0;                               // btn_Subwindow (If <0, same sub-win as indicator)
extern ENUM_BASE_CORNER   btn_corner             = CORNER_LEFT_UPPER;               // 
extern string             btn_text               = "BoxFibo";                       // 
extern string             btn_Font               = "Arial";                         // 
extern int                btn_FontSize           = 9;                               //  
extern color              btn_text_OFF_color     = clrPink;                         // 
extern color              btn_text_ON_color      = clrLime;                         //  
extern color              btn_background_color   = clrDimGray;                      //  
extern color              btn_border_color       = clrBlack;                        //  
extern int                button_x               = 20;                             // 
//                                                                                  //      FF:20  For personal use: 600  // TAG: v2_05
extern int                button_y               = 45;                             // 
//                                                                                  //    FF:45  For personal use: 160
extern int                btn_Width              = 60;                              // 
extern int                btn_Height             = 20;                              // 
extern bool               btn_Initial_Default_ON = true;                            // 
extern string             button_note2           = "------------------------------";
string buttonId;
bool buttonStatus;
//


datetime dtStartTime;
datetime dtEndTime;
string fibLevelText = "";
string fibLevelsStr[];
string fibLevelsPrefix[];
double fibLevels[];
int fibCount;
bool debugmicro = false;
string shortName;

//+------------------------------------------------------------------+
int OnInit()
  {
   createdObjectPrefix = StringConcatenate(createdObjectPrefix,StartTime,"_",EndTime,"_");
   //===========  Determine if duplicate Indicator exists
   shortName = createdObjectPrefix; // Must NOT be simply the same as 'WindowExpertName()', else it will see itself as a duplicate.
   int winFind = WindowFind(shortName);
   IndicatorShortName(shortName);
   if(winFind >= 0)
   {  
      Alert("ERROR(?).  The chart may already have this indicator with identical values for StartTime & EndTime."); // Because of false errors, do not remove.  //This duplicate is auto-removed.");
      //return(INIT_FAILED); // Due to possible false errors, commented out to keep going.
   }  
   //============  End of check, and adjustment, if duplicate Indicator added.
  
   FibPercentLevels = stringReplaceEveryMatch(FibPercentLevels,","," ");
   FibPercentLevels = stringReplaceEveryMatch(FibPercentLevels,"%"," ");
   FibPercentLevels = stringReplaceEveryMatch(FibPercentLevels,";"," ");
   FibPercentLevels = StringTrimLeft(StringTrimRight(FibPercentLevels));
   fibCount = stringSplitBySpaces(fibLevelsStr,FibPercentLevels);
   ArrayResize(fibLevels,fibCount);
   for(int c=0;c<fibCount; c++)
   {
      fibLevels[c] = StringToDouble(fibLevelsStr[c])/100.0;
   }
   
   if(ShowPricePerlevel) fibLevelText = " %$";
   
   stringSplitByDelimiters(fibLevelsPrefix, EXTRA_Corresponding_FibLevelDesc, ",", true, fibCount, "");
   
   
   dtStartTime = StrToTime("1970.01.01 "+StartTime);
   dtEndTime = StrToTime("1970.01.01 "+EndTime);
   if(dtEndTime <= dtStartTime) dtStartTime -= 86400; // If the EndTime is before the StartTime, adjust the StartTime to be 1 day earlier.
   //Alert("DEBUG dtStartTime=",dtStartTime,"  dtEndTime=",dtEndTime);
   
   // --------------- Button related ----------------- //
   //if(btn_Subwindow<0) btn_Subwindow = swin;
   // The leading "_" gives buttonId a *unique* prefix so it is not deleted if/when any other chart objects are deleted.
   buttonId = "_" + createdObjectPrefix + "_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);
      if(!btn_Initial_Default_ON) ObjectSetInteger(0,buttonId, OBJPROP_STATE,false); // This can force the initially created button to start as "off".  (But just changing TF's keeps current status!)
      
      string tooltipDesc = "P4L BoxFibo: Click to toggle ON/OFF "+
                               "";
      ObjectSetString(0,buttonId,OBJPROP_TOOLTIP,tooltipDesc);
   }
   ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE, button_y);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE, button_x);
   buttonStatus = ObjectGetInteger(0, buttonId, OBJPROP_STATE);   
   if (buttonStatus) ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_ON_color); 
   else ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_OFF_color);
   // --------------- -------------- ----------------- //
   
   if(AutoFlipColorsIfDarkBG && !isBackgroundLight() )
   {
      BoxColor = colorToVisibleRGBflip(BoxColor);
      FiboColor = colorToVisibleRGBflip(FiboColor);
      //BoxColor = colorToRGBflip(BoxColor);
      //FiboColor = colorToRGBflip(FiboColor);
   }
   
   // NOTE: It is unusual/strange to run plot_obj() in OnInit() because start() will also run it.
   if(Period() <= SupressIndicatorIfAbove_TF && buttonStatus) plot_obj();
   return(INIT_SUCCEEDED);
}//end of OnInit()

//+------------------------------------------------------------------+
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
     && reason != REASON_CLOSE
     // && reason != REASON_RECOMPILE  // Leave commented, if personal preference is to delete it when Re-Compiled.
      ) ObjectDelete(buttonId);

   if(reason != REASON_REMOVE) IndicatorShortName("any_random_name_just_before_deinit_is_done");
   del_obj();
   return;
}//end of OnDeinit()

//+------------------------------------------------------------------------------------------------------------------+
void OnChartEvent(const int id, //don't change anything here
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   
   //// Method 1: At the very least, skip the bulk of what are usually unwanted event id's.  NOTE that these
   //// events might have been enabled not by this indicator but by some OTHER indicator on the same chart!
   //// 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_OBJECT_CREATE || id==CHARTEVENT_OBJECT_DELETE) return; 
   //// If this, or another program, enabled mouse-events, these are usually not needed, so skip unless actually needed.:
   //if(id==CHARTEVENT_MOUSE_MOVE    || id==CHARTEVENT_MOUSE_WHEEL)   return; 

   // Method 2 (Preferred):  Check for a specific id and a specific sparam (object name) of interest, and ONLY act on that event!
   if (id==CHARTEVENT_OBJECT_CLICK && sparam == buttonId)
   {
      
      buttonStatus = ObjectGetInteger(0, buttonId, OBJPROP_STATE);
      
      if (buttonStatus) // on
      {
         ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_ON_color); 
         //Call a function (preferably not the entire start() function though it might work) to do whatever the indicator does here:
         del_obj();
         if(Period() < PERIOD_D1) plot_obj(); // The button *ignores* 'SupressIndicatorIfAbove_TF' and can therefore force H4 (but not >= D1)         
      }
      else // off
      {
         ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_OFF_color);
         // If this indy had created objects (besides the buttonId), this would be the place to remove them one time only, now that the indy is "off"
         // deleteCreatedObjects(IndicatorObjPrefix);
         del_obj();
      }
      ChartRedraw();
   }
}//end of OnChartEvent()

//+------------------------------------------------------------------------------------------------------------------+
// Normally one does not need to change anything here
void createButton(string buttonID,string buttonText,int width2,int height,string font,int fontSize,color bgColor,color borderColor,color txtColor, bool initial_state=true)
{
      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'  If "true" which is "on" (the default), one will see the indicator by default
      ObjectSetInteger(0, buttonId, OBJPROP_STATE, initial_state);
}//end of createButton()

//+------------------------------------------------------------------+
int start()  
{  
   if(Period() > SupressIndicatorIfAbove_TF || !buttonStatus) return(0);  // User can control whether to suppress ABOVE some TF, like > H1. OR, if buttonStatus is OFF, return.
   if(Period() >= PERIOD_D1) return(0);  // Indicator does not apply at all to D1 and above (and is not really intended for H4 either)
      
   int limit;
   int counted_bars = IndicatorCounted();
   //---- check for possible errors 
   if(counted_bars<0) return(-1); 
   ////---- the last counted bar will be recounted 
   //if(counted_bars>0) counted_bars--; 
   limit=Bars-counted_bars; 
   
   static datetime last_timecurrent = 0;
   bool isNewBar = IsNewBar(); 
   if(TimeCurrent() == last_timecurrent && !isNewBar && limit<=1 ) return(0);  // Run no more than once per second. 
   last_timecurrent = TimeCurrent();
   
   
   ulong microstart = 0;
   if(debugmicro) microstart = GetMicrosecondCount();
   plot_obj(limit);
//+------------------------------------------------------------------+
   if(debugmicro)
   {
      static int nn=0;
      static int uslimit = 25;
      ulong time_us = GetMicrosecondCount() - microstart;
      if(time_us > 400) 
      {
         if (nn > uslimit) 
         {
            nn=uslimit;
            uslimit = uslimit + 2;
         }
      }
      nn++;
      if (nn<=uslimit || isNewBar) Print("Box Fibo: Pass n=",nn," took: ",time_us," micro-seconds.  limit=",limit,"  Period()=",Period());
   }
  return(0);
}//end of start()

//+------------------------------------------------------------------+
void plot_obj(int limit=INT_MAX)  {
//+------------------------------------------------------------------+
   
   //datetime dt1 = StrToTime(TimeToStr(TimeCurrent(),TIME_DATE) + " " + StartTime + ":00"); // TimeToStr then StrToTime are VERY SLOW functions!
   //datetime dt2 = StrToTime(TimeToStr(TimeCurrent(),TIME_DATE) + " " + EndTime + ":00");
   datetime timecurrent = TimeCurrent();
   timecurrent -= (datetime) MathMod(timecurrent,86400);
   datetime dt1 = timecurrent + dtStartTime;
   datetime dt2 = timecurrent + dtEndTime;
   int      dys = 0, NumBars = (int) (dt2-dt1)/PeriodSeconds();
   limit = MathMin(limit,Bars-NumBars);
   for (int i=0; i<limit; i++)  
   {
      //dt1 = StrToTime(TimeToStr(Time[i],TIME_DATE) + " "  + StartTime + ":00");
      //dt2 = StrToTime(TimeToStr(Time[i],TIME_DATE) + " "  + EndTime + ":00");
      datetime timei = Time[i] - (datetime) MathMod(Time[i],86400);
      dt1 = timei + dtStartTime;
      dt2 = timei + dtEndTime;
      int ib1 = iBarShift(NULL,0,dt1);
      int ib2 = iBarShift(NULL,0,dt2);
      datetime timeIb1Ib2 = Time[ib2] - Time[ib1];
      if( timeIb1Ib2 > 97200) // If gap > 72hrs, assume there's a 1,2 or 3-day data gap and adjust accordingly. 
      {
         if( timeIb1Ib2 > 248400) dt1 -= 259200; // If gap > 2days+21 hrs (69hrs), adjust by 3 days,
         else if ( timeIb1Ib2 < 129600 ) dt1 -= 86400; // else if gap < 1.5 days, adjust by 1 day,
         else dt1 -= 172800; // else adjust by 2 days (normal weekend)
         ib1 = iBarShift(NULL,0,dt1);
      }
      if(Time[ib1] < dt1 && ib1>=1) { ib1--; dt1 = Time[ib1]; } // If not an EXACT match for the bar time, the *next* bar is correct, so shift 1 bar forward.
      if(Time[ib2] < dt2 && ib2>=1) { ib2--; dt2 = Time[ib2]; }
      if (dt2 != Time[i])  continue;
      // This old 'for' loop is a little slower than using iHighest & iLowest
      ////double vHigh = 0, vLow = 999;
      //double vHigh = 0.0, vLow = DBL_MAX; 
      //for (int j=ib1; j>ib2; j--)  {
      //  vHigh = MathMax(vHigh,High[j]);
      //  vLow  = MathMin(vLow,Low[j]);
      //}
      int barHigh = iHighest(NULL,0,MODE_HIGH,ib1-ib2,ib2+1);
      int barLow = iLowest(NULL,0,MODE_LOW,ib1-ib2,ib2+1);
      double vHigh = High[barHigh];
      double vLow = Low[barLow];
      
      string objname = createdObjectPrefix+IntegerToString(dt1)+"_R";
      if( ObjectFind(objname) < 0) 
      {
         ObjectCreate(objname,OBJ_RECTANGLE,0,dt1,vHigh,dt2-60,vLow);
         
         ObjectSet(objname,OBJPROP_COLOR,BoxColor);
         ObjectSet(objname,OBJPROP_SELECTABLE,false); 
         ObjectSet(objname,OBJPROP_HIDDEN,true); 
      }
      else
      {
         ObjectSetInteger(0,objname,OBJPROP_TIME1,dt1);
         ObjectSetInteger(0,objname,OBJPROP_TIME2,dt2-60);
         ObjectSetDouble(0,objname,OBJPROP_PRICE1,vHigh);
         ObjectSetDouble(0,objname,OBJPROP_PRICE2,vLow);
      }
      
      if(fibCount > 0)
      {
         objname = createdObjectPrefix+IntegerToString(dt1)+"_F";
         if( ObjectFind(objname) < 0) 
         {
            ObjectCreate(objname,OBJ_FIBO,0,dt2-ShiftFiboTime2By_seconds,vHigh,dt2,vLow);
            
            ObjectSet(objname,OBJPROP_RAY,false);
            ObjectSet(objname,OBJPROP_SELECTABLE,false);
            ObjectSet(objname,OBJPROP_HIDDEN,true); 
            ObjectSet(objname,OBJPROP_COLOR,BoxColor);
            ObjectSet(objname,OBJPROP_LEVELCOLOR,FiboColor);
            ObjectSet(objname,OBJPROP_LEVELWIDTH,2);
              
            ObjectSet(objname,OBJPROP_FIBOLEVELS,fibCount);
            ObjectSet(objname,OBJPROP_LEVELSTYLE,STYLE_SOLID);
            for(int c=0; c<fibCount; c++)
            {
               string use_fibLevelText = fibLevelText;
               ObjectSet(objname,OBJPROP_FIRSTLEVEL+c,fibLevels[c]);
               if(ShowPercentPerLevel) use_fibLevelText = DoubleToStr(fibLevels[c]*100.0,DisplayPercentWithNumberOfDigits)+" "+use_fibLevelText;
               if(!stringIsBlank(fibLevelsPrefix[c])) use_fibLevelText = fibLevelsPrefix[c]+"  "+use_fibLevelText;
               ObjectSetFiboDescription(objname,c,use_fibLevelText);
            }
         }
         else
         {
            ObjectSetInteger(0,objname,OBJPROP_TIME1,dt2-ShiftFiboTime2By_seconds);
            ObjectSetInteger(0,objname,OBJPROP_TIME2,dt2);
            ObjectSetDouble(0,objname,OBJPROP_PRICE1,vHigh);
            ObjectSetDouble(0,objname,OBJPROP_PRICE2,vLow);
         }
      }
      dys++; if (dys >= NumDays)   break;
   }
}//end of plot_obj()

//+------------------------------------------------------------------+
void del_obj(string prefix="")  
{
   if(StringLen(prefix)==0) prefix = createdObjectPrefix;
   int limit = ObjectsTotal() - 1;
   for(int k=limit; k>= 0; k--) // When deleting objects, must *decrement*.
   {
      string objname = ObjectName(k);
      if (StringFind(objname,prefix,0) == 0)  
      ObjectDelete(objname);
   }    
}//end of del_obj()

//+------------------------------------------------------------------+ 
int stringSplitBySpaces(string& output[], string s_input) export
{
   int pos, arraysize;
   ArrayResize(output, 0);
   s_input = StringTrimLeft(StringTrimRight(s_input));  // Leading/trailing spaces must be stripped off first.
   while (true) 
   {
      pos = StringFind(s_input, " ");
      arraysize = ArraySize(output);
      ArrayResize(output, arraysize + 1);
      if (pos != -1) 
      {
         output[arraysize] = StringTrimLeft(StringTrimRight(StringSubstr(s_input, 0, pos)));
         s_input = StringTrimLeft(StringTrimRight(StringSubstr(s_input, pos + 1)));
      } else {
         output[arraysize] = StringTrimLeft(StringTrimRight(s_input));
         break;
      } // if
   } // while
   return(ArraySize(output));
}//end of stringSplitBySpaces()

//+------------------------------------------------------------------+
// Splits s_input string into output array according to *multiple* single-single character delimiters.
int stringSplitByDelimiters(string& output[], string s_input, string delimiters, bool trimLeftRightSpaces=true, int finalSize=0, string defaultValue=NULL) export
{
   int p, d, len, pos, size, cycles=0;
   int countDelim = StringLen(delimiters);
   if(countDelim == 0) return(-1); // Error, no delimiter specified.  (Should I assume... " "(space), or "/" or ... ?)
   int delim[];
   ArrayResize(delim,countDelim);
   //Note: A space is not a delimiter unless it is included as a character in the delimiters
   for(p=0; p<countDelim; p++) {delim[p] = (uchar) StringGetChar(delimiters,p);} //if(delim[p]== a space, do anything different?? Force trimLeftRightSpaces=true, or ??
   
   if(trimLeftRightSpaces) s_input = StringTrimRight(s_input); // Left is always trimmed in the loop
   ArrayResize(output, 0);
   
   //arrayPrint(delim,true);

   while (true) 
   {  //find each array element
      cycles++; if(cycles>10000) return(-1);
      
      if(trimLeftRightSpaces) s_input = StringTrimLeft(s_input);
      pos=0;
      len=StringLen(s_input);
      while(true)
      { //process each character
         cycles++; if(cycles>10000) return(-1);
         ushort asciiCode = StringGetCharacter(s_input,pos);
         bool delimFound=false;
         for(d=0; d<countDelim; d++)
         {
            if(pos>=len || asciiCode == delim[d])
            {
               //found a delim or EOS at pos
               delimFound=true;
               break;
            }
         }
         if(delimFound) break;
         pos++;
      }
      size = ArraySize(output);
      ArrayResize(output, size + 1);
      //if (pos == 0) output[size] = ""; //empty element 
      if (pos == 0) output[size] = defaultValue; //empty element
      else 
      {
         output[size] = StringSubstr(s_input, 0, pos);
         if(trimLeftRightSpaces) output[size] = StringTrimLeft(StringTrimRight(output[size]));
      }
      if(pos>=len) break;
      s_input = StringSubstr(s_input, pos + 1); // was len_token which is always 1
      
      //Alert("DEBUG (pre)size=",size,"  output[size]='",output[size],"'","  Remaining s_input='",s_input,"'  d=",d,"  pos=",pos);
   }
   
   int arSize = ArraySize(output);
   if(finalSize>arSize)
   {
      ArrayResize(output,finalSize);
      for(int k=arSize; k<finalSize; k++) {output[k] = defaultValue;}
      arSize = finalSize;
   }
   return(arSize);

   return(ArraySize(output));       
}//end of stringSplitByDelimiters()

//+------------------------------------------------------------------+
string stringReplaceEveryMatch(string str, string toFind, string toReplace, bool ignoreCaseOf_toFind=false) export {
   //Updated v3_15 to support ignoreCaseOf_toFind
   int len_toReplace = StringLen(toReplace);
   int len_toFind = StringLen(toFind);
   //int len = StringLen(toFind);
   int pos = 0;
   string leftPart, rightPart, result = str, TOFIND = toFind;
   if(ignoreCaseOf_toFind) StringToUpper(TOFIND);

   if (len_toFind == 0 || (!ignoreCaseOf_toFind && toFind == toReplace)) return (result); // Cannot find "", and replacing same-with-same is a waste of time.
   while (true) {
       // Careful, (pos or result) must change each loop or it's an infinite loop.
       if(ignoreCaseOf_toFind) pos = StringFind(stringToUC(result), TOFIND, pos);
       else pos = StringFind(result, toFind, pos);
       if (pos == -1) {
           break;
       }
       if (pos == 0) {
           leftPart = "";
       } else {
           leftPart = StringSubstr(result, 0, pos);
       }
       rightPart = StringSubstr(result, pos+len_toFind); 
       result = StringConcatenate(leftPart,toReplace,rightPart);
       pos = pos + len_toReplace;
   }    
   return (result);
}//end of stringReplaceEveryMatch()
//+------------------------------------------------------------------+
string stringToUC(string str) export {
   // Convert str to upper-case
   int lS = 97, lE = 122, uS = 65, uE = 90, diff = lS - uS;
   for (int i = 0; i < StringLen(str); i++) {
       int code = StringGetChar(str, i);
       if (code >= lS && code <= lE) {
           code -= diff;
           str = StringSetChar(str, i, (ushort) code);
       }
   }
   return (str);
}//end of stringToUC()
//+------------------------------------------------------------------+
bool IsNewBar() export // DEV to add: string symbol=_Symbol, int period=_Period)
{
   static datetime lastbar; // DEV Q: Would a single lastbar var work with multiple different calls and/or arguments to function? If no, how? Pass the &lastbar?
   datetime curbar = (datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_LASTBAR_DATE);
   if(lastbar != curbar)
   {
      lastbar = curbar;
      return true;
   }
   return false;
}//end of IsNewBar()

//+------------------------------------------------------------------+
color colorToRGBflip(color c_mycolor) export { return( (color) (~c_mycolor & 0xFFFFFF) ); }
color colorToVisibleRGBflip(color c_mycolor) export 
{
   if(isBackgroundLight())
   {
      //Print("DEBUGC LIGHT orig=",IntegerToHexString(c_mycolor),"   pureFlip=",IntegerToHexString( (color) (~c_mycolor & 0xFFFFFF)),"  modFlip=",IntegerToHexString( (color) ((~c_mycolor & 0xFFFFFF) & 0xDDDDDD))); // 0xBBBBBB  0xDDDDDD
      return( (color) (~c_mycolor & 0xBBBBBB) );  // 0xBB = 1011 1011   This forces off a couple of bits each for RGB, so it won't be "too bright" for each of the RGB components of the color.
      //return( (color) ((~c_mycolor & 0xFFFFFF) & 0xCCCCCC) );
   }
   else
   {
      //Print("DEBUGC DARK orig=",IntegerToHexString(c_mycolor),"   pureFlip=",IntegerToHexString( (color) (~c_mycolor))," FlipAND=",IntegerToHexString( (color) ((~c_mycolor) & 0xFFFFFF)),
      //   "   pureFlip=",IntegerToHexString( (color) (~c_mycolor & 0xFFFFFF)),"  modFlip=",IntegerToHexString( (color) ((~c_mycolor & 0xFFFFFF) | 0x444444)));
      return( (color) ((~c_mycolor & 0xFFFFFF) | 0x444444) );
   }
   //return( (color) (~c_mycolor & 0xFFFFFF) ); 
}//end of colorToVisibleRGBflip()
//+------------------------------------------------------------------+
int   colorToRGBsum(color c_mycolor) export { return( (c_mycolor & 0xFF) + ((c_mycolor>>8) & 0xFF) + ((c_mycolor>>16) & 0xFF) ); }
bool  isBackgroundLight() export { if(colorToRGBsum( (int) ChartGetInteger(0,CHART_COLOR_BACKGROUND)) >382 ) return(true); else return(false); }
//+------------------------------------------------------------------+
bool stringIsBlank(string str, bool do_StringTrim_LR=true) export
{
   //NO Not reliable. if(str == "") return(false);
   //if(StringLen(str) > 0) return(false);
   //if(str == NULL) return(false); // MAYBE ok for build>=600
   if(do_StringTrim_LR) str = StringTrimLeft(StringTrimRight(str));
   //if(StringFind(str,"NULL",0) == 0) return(true); // Ok to check, but likely unnecessary
   if(StringGetChar(str,0) > 0) return(false);
   return(true);
}//end of stringIsBlank()
//+------------------------------------------------------------------+


// These previous functions to delete objects were flawed.  When deleting objects, one must decrement the counter; never increment.
////+------------------------------------------------------------------+
//void del_obj1()  {
////+------------------------------------------------------------------+
//  int k=0;
//  while (k<ObjectsTotal())   {
//    string objname = ObjectName(k);
//    if (StringSubstr(objname,0,3) == "BFB")  
//      ObjectDelete(objname);
//    else
//      k++;  // NEVER increment and ObjectDelete like this!  Only *decrement* the counter, starting from the max, down to 0.
//  }    
//}
//
//void del_obj2()  {
////+------------------------------------------------------------------+
//  int k=0;
//  while (k<ObjectsTotal())   {
//    string objname = ObjectName(k);
//    if (StringSubstr(objname,0,3) == "BFF")  
//      ObjectDelete(objname);
//    else
//      k++;
//  }    
//}


