//+------------------------------------------------------------------+
//|       Finsbury Park2 Market Clock TF Timer FIX.mq4               |
//|  MT4-compatible world clock + symbol switch dashboard            |
//|  Added: previous / current / next timeframe timers               |
//|  Added: live market price immediately after the chart symbol     |
//|  Added: automatic stock-exchange OPEN status on city clocks      |
//|  Modified: all symbol buttons in one horizontal row at bottom    |
//|  Layout tuned to reference screenshot; Market Price closer OPEN  |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 1

double DummyBuffer[];

//--- header layout
input int    TopOffset           = 3;
input int    CityBoxWidth        = 145;
input int    CityBoxHeight       = 36;
input int    CityGap             = 6;
input int    CityFontSize        = 10;
input int    OpenFontSize        = 10;
input int    OpenBorderWidth     = 3;
input color  OpenTextColor       = clrWhite;

//--- symbol and timer layout
input int    SymbolFontSize          = 20;

// Distance in pixels between the bottom of the OPEN label area
// and the EURUSD / MARKET PRICE line.
// Lower value = closer to OPEN.
input int    MarketPriceGapBelowOpen = 5;

input int    TimerRowTopOffset       = 104;
input int    TFLabelFontSize         = 13;
input int    TimerFontSize           = 12;
input int    TimerColumnWidth        = 170;

//--- bottom symbol dashboard
// All FX pairs, commodities and BTC are displayed on ONE horizontal row.
input int    DashboardBottomOffset = 10;
input int    ButtonWidth           = 70;
input int    ButtonMinWidth        = 48;
input int    ButtonHeight          = 22;
input int    ButtonGap             = 4;
input int    ButtonFontSize        = 8;
input int    DashboardSideMargin   = 6;
input string FontName              = "Arial";

//--- colors
input color LondonBg             = clrFireBrick;
input color NewYorkBg            = clrSeaGreen;
input color TokyoBg              = clrRoyalBlue;
input color SydneyBg             = clrDarkOrange;
input color TextColor            = clrWhite;
input color PrevTFColor          = clrLimeGreen;
input color CurrentTFColor       = clrGold;
input color NextTFColor          = clrPeru;
input color ActiveColor          = clrDarkGreen;
input color ButtonColor          = clrDimGray;

//--- broker symbols (edit if your broker uses different names)
input string EURUSD_Symbol       = "EURUSD";
input string GBPUSD_Symbol       = "GBPUSD";
input string USDJPY_Symbol       = "USDJPY";
input string USDCHF_Symbol       = "USDCHF";
input string USDCAD_Symbol       = "USDCAD";
input string AUDUSD_Symbol       = "AUDUSD";
input string NZDUSD_Symbol       = "NZDUSD";
input string WTI_Symbol          = "USOIL";
input string BRENT_Symbol        = "UKOIL";
input string GOLD_Symbol         = "XAUUSD";
input string SILVER_Symbol       = "XAGUSD";
input string BTC_Symbol          = "BTCUSD";

string PFX = "FP2MC_";

//+------------------------------------------------------------------+
int TFArray[9] = {1,5,15,30,60,240,1440,10080,43200};

//+------------------------------------------------------------------+
string TFText(int p)
{
   if(p==1) return "M1";
   if(p==5) return "M5";
   if(p==15) return "M15";
   if(p==30) return "M30";
   if(p==60) return "H1";
   if(p==240) return "H4";
   if(p==1440) return "D1";
   if(p==10080) return "W1";
   if(p==43200) return "MN1";
   return IntegerToString(p);
}

//+------------------------------------------------------------------+
int TFSeconds(int p)
{
   if(p==1) return 60;
   if(p==5) return 300;
   if(p==15) return 900;
   if(p==30) return 1800;
   if(p==60) return 3600;
   if(p==240) return 14400;
   if(p==1440) return 86400;
   if(p==10080) return 604800;
   if(p==43200) return 2592000; // fallback for monthly
   return p*60;
}

//+------------------------------------------------------------------+
int TFIndex(int p)
{
   for(int i=0;i<9;i++)
      if(TFArray[i]==p) return i;
   return 4; // default H1
}

//+------------------------------------------------------------------+
int PrevTF(int p)
{
   int idx=TFIndex(p);
   if(idx<=0) return TFArray[0];
   return TFArray[idx-1];
}

//+------------------------------------------------------------------+
int NextTF(int p)
{
   int idx=TFIndex(p);
   if(idx>=8) return TFArray[8];
   return TFArray[idx+1];
}

//+------------------------------------------------------------------+
datetime NextMonthOpen(datetime cur)
{
   int y = TimeYear(cur);
   int m = TimeMonth(cur);
   int nm = m + 1;
   int ny = y;
   if(nm==13){ nm=1; ny++; }
   string s = IntegerToString(ny) + "." + IntegerToString(nm) + ".01 00:00";
   return StrToTime(s);
}

//+------------------------------------------------------------------+
string CandleTimerTF(int tf)
{
   datetime openBar = iTime(NULL,tf,0);
   if(openBar<=0) return TFText(tf)+": --:--:--";

   int seconds = TFSeconds(tf);
   datetime closeBar;

   if(tf==43200) // MN1
      closeBar = NextMonthOpen(openBar);
   else
      closeBar = openBar + seconds;

   int left = (int)(closeBar - TimeCurrent());
   if(left < 0) left = 0;

   int h = left / 3600;
   int m = (left % 3600) / 60;
   int s = left % 60;

   return TFText(tf)+": "+StringFormat("%02d:%02d:%02d",h,m,s);
}

//+------------------------------------------------------------------+
int LastSunday(int year,int month)
{
   int nm=month+1, ny=year;
   if(nm==13){ nm=1; ny++; }
   datetime firstNext=StrToTime(IntegerToString(ny)+"."+IntegerToString(nm)+".01 00:00");
   datetime lastDay=firstNext-86400;
   return TimeDay(lastDay)-TimeDayOfWeek(lastDay);
}

//+------------------------------------------------------------------+
int NthSunday(int year,int month,int nth)
{
   datetime first=StrToTime(IntegerToString(year)+"."+IntegerToString(month)+".01 00:00");
   int dow=TimeDayOfWeek(first);
   int firstSun=(dow==0 ? 1 : 8-dow);
   return firstSun+7*(nth-1);
}

//+------------------------------------------------------------------+
bool EuropeDST(datetime utc)
{
   int y=TimeYear(utc);
   datetime a=StrToTime(IntegerToString(y)+".03."+IntegerToString(LastSunday(y,3))+" 01:00");
   datetime b=StrToTime(IntegerToString(y)+".10."+IntegerToString(LastSunday(y,10))+" 01:00");
   return (utc>=a && utc<b);
}

//+------------------------------------------------------------------+
bool NewYorkDST(datetime utc)
{
   int y=TimeYear(utc);
   datetime a=StrToTime(IntegerToString(y)+".03."+IntegerToString(NthSunday(y,3,2))+" 07:00");
   datetime b=StrToTime(IntegerToString(y)+".11."+IntegerToString(NthSunday(y,11,1))+" 06:00");
   return (utc>=a && utc<b);
}

//+------------------------------------------------------------------+
datetime SydneyDSTStartUTC(int year)
{
   // First Sunday in October, 02:00 AEST = previous day 16:00 UTC.
   datetime localStart=StrToTime(IntegerToString(year)+".10."+
                                IntegerToString(NthSunday(year,10,1))+" 02:00");
   return(localStart-10*3600);
}

//+------------------------------------------------------------------+
datetime SydneyDSTEndUTC(int year)
{
   // First Sunday in April, 03:00 AEDT = previous day 16:00 UTC.
   datetime localEnd=StrToTime(IntegerToString(year)+".04."+
                              IntegerToString(NthSunday(year,4,1))+" 03:00");
   return(localEnd-11*3600);
}

//+------------------------------------------------------------------+
bool SydneyDST(datetime utc)
{
   int y=TimeYear(utc);
   datetime endThis=SydneyDSTEndUTC(y);
   datetime startThis=SydneyDSTStartUTC(y);

   // Australian daylight saving spans the end of one year and the
   // beginning of the next one.
   if(utc<endThis)
      return(utc>=SydneyDSTStartUTC(y-1));
   if(utc>=startThis)
      return(utc<SydneyDSTEndUTC(y+1));
   return(false);
}

//+------------------------------------------------------------------+
string HHMM(datetime t)
{
   return TimeToString(t,TIME_MINUTES);
}

//+------------------------------------------------------------------+
void MakeRect(string name,int x,int y,int w,int h,color bg)
{
   if(ObjectFind(name)<0) ObjectCreate(name,OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSet(name,OBJPROP_CORNER,0);
   ObjectSet(name,OBJPROP_XDISTANCE,x);
   ObjectSet(name,OBJPROP_YDISTANCE,y);
   ObjectSet(name,OBJPROP_XSIZE,w);
   ObjectSet(name,OBJPROP_YSIZE,h);
   ObjectSet(name,OBJPROP_BGCOLOR,bg);
   ObjectSet(name,OBJPROP_COLOR,clrWhite);
   ObjectSet(name,OBJPROP_BACK,false);
}

//+------------------------------------------------------------------+
void MakeSessionRect(string name,int x,int y,int w,int h,color bg,bool marketOpen)
{
   MakeRect(name,x,y,w,h,bg);
   ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT);
   ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
   ObjectSetInteger(0,name,OBJPROP_COLOR,marketOpen ? clrWhite : bg);
   ObjectSetInteger(0,name,OBJPROP_WIDTH,
                    marketOpen ? (int)MathMax(2,MathMin(5,OpenBorderWidth)) : 1);
}

//+------------------------------------------------------------------+
void MakeLabel(string name,string txt,int x,int y,int fs,color clr)
{
   if(ObjectFind(name)<0) ObjectCreate(name,OBJ_LABEL,0,0,0);
   ObjectSet(name,OBJPROP_CORNER,0);
   ObjectSet(name,OBJPROP_XDISTANCE,x);
   ObjectSet(name,OBJPROP_YDISTANCE,y);
   ObjectSetText(name,txt,fs,FontName,clr);
}

//+------------------------------------------------------------------+
void MakeCenteredLabel(string name,string txt,int xCenter,int y,int fs,color clr)
{
   if(ObjectFind(0,name)<0) ObjectCreate(0,name,OBJ_LABEL,0,0,0);
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xCenter);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
   ObjectSetText(name,txt,fs,FontName,clr);
}

//+------------------------------------------------------------------+
void MakeBottomButton(string name,string txt,int x,int yFromBottom,int width,string symbol)
{
   if(ObjectFind(name)<0) ObjectCreate(name,OBJ_BUTTON,0,0,0);

   // Anchor to the lower-left corner so the dashboard always stays
   // at the bottom of the chart, regardless of chart height.
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yFromBottom);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,width);
   ObjectSetInteger(0,name,OBJPROP_YSIZE,ButtonHeight);
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,true);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);

   ObjectSetText(name,txt,ButtonFontSize,FontName,clrWhite);

   color bg=(Symbol()==symbol ? ActiveColor : ButtonColor);
   ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bg);
   ObjectSetInteger(0,name,OBJPROP_COLOR,clrSilver);
}

//+------------------------------------------------------------------+
void DrawCity(string key,string city,datetime tm,int x,int y,color bg,bool marketOpen)
{
   MakeSessionRect(PFX+key+"_R",x,y,CityBoxWidth,CityBoxHeight,bg,marketOpen);
   MakeLabel(PFX+key+"_T",city+"  "+HHMM(tm),x+10,y+10,CityFontSize,TextColor);

   string openName=PFX+key+"_OPEN";
   if(marketOpen)
      MakeCenteredLabel(openName,"OPEN",x+CityBoxWidth/2,
                        y+CityBoxHeight+2,OpenFontSize,OpenTextColor);
   else if(ObjectFind(0,openName)>=0)
      ObjectDelete(0,openName);
}

//+------------------------------------------------------------------+
bool IsWeekday(datetime localTime)
{
   int day=TimeDayOfWeek(localTime);
   return(day>=1 && day<=5);
}

//+------------------------------------------------------------------+
int LocalSeconds(datetime localTime)
{
   return(TimeHour(localTime)*3600+TimeMinute(localTime)*60+TimeSeconds(localTime));
}

//+------------------------------------------------------------------+
bool InSession(datetime localTime,int openSecond,int closeSecond)
{
   if(!IsWeekday(localTime)) return(false);
   int nowSecond=LocalSeconds(localTime);
   return(nowSecond>=openSecond && nowSecond<closeSecond);
}

//+------------------------------------------------------------------+
bool LondonMarketOpen(datetime localTime)
{
   return(InSession(localTime,8*3600,16*3600+30*60));
}

//+------------------------------------------------------------------+
bool NewYorkMarketOpen(datetime localTime)
{
   return(InSession(localTime,9*3600+30*60,16*3600));
}

//+------------------------------------------------------------------+
bool TokyoMarketOpen(datetime localTime)
{
   // Tokyo Stock Exchange has a lunch break from 11:30 to 12:30.
   return(InSession(localTime,9*3600,11*3600+30*60) ||
          InSession(localTime,12*3600+30*60,15*3600+30*60));
}

//+------------------------------------------------------------------+
bool SydneyMarketOpen(datetime localTime)
{
   // ASX continuous trading begins after the opening auction.
   return(InSession(localTime,9*3600+59*60+45,16*3600));
}

//+------------------------------------------------------------------+
void DrawTFBlock(string key,string text,int xCenter,int y,color clr)
{
   MakeLabel(PFX+key+"_TXT",text,xCenter-55,y,TFLabelFontSize,clr);
   MakeRect(PFX+key+"_LINE",xCenter-12,y+22,26,3,clr);
}

//+------------------------------------------------------------------+
void DrawAll()
{
   long cw=1000;
   ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,0,cw);

   //--- city clocks
   int total=4*CityBoxWidth+3*CityGap;
   int sx=(int)((cw-total)/2);
   if(sx<3) sx=3;

   datetime utc=TimeGMT();
   bool eu=EuropeDST(utc);
   bool us=NewYorkDST(utc);
   bool au=SydneyDST(utc);

   datetime l=utc+(eu?3600:0);
   datetime n=utc+(us?-14400:-18000);
   datetime t=utc+32400;
   datetime s=utc+(au?39600:36000);

   DrawCity("LONDON","London",l,sx,TopOffset,LondonBg,LondonMarketOpen(l));
   DrawCity("NY","New York",n,sx+(CityBoxWidth+CityGap),TopOffset,NewYorkBg,NewYorkMarketOpen(n));
   DrawCity("TOKYO","Tokyo",t,sx+2*(CityBoxWidth+CityGap),TopOffset,TokyoBg,TokyoMarketOpen(t));
   DrawCity("SYDNEY","Sydney",s,sx+3*(CityBoxWidth+CityGap),TopOffset,SydneyBg,SydneyMarketOpen(s));

   //--- big symbol followed immediately by the live bid price
   int center=(int)(cw/2);
   double marketPrice=MarketInfo(Symbol(),MODE_BID);
   int marketDigits=(int)MarketInfo(Symbol(),MODE_DIGITS);
   if(marketPrice<=0.0) marketPrice=Bid;
   if(marketDigits<=0) marketDigits=Digits;
   string symbolPrice=Symbol()+"   MARKET PRICE: "+DoubleToString(marketPrice,marketDigits);

   // OPEN starts at TopOffset + CityBoxHeight + 2.
   // Put the symbol/market-price row immediately underneath it.
   int marketPriceY =
      TopOffset + CityBoxHeight + 2 + OpenFontSize + MarketPriceGapBelowOpen;

   MakeCenteredLabel(PFX+"SYMBOL",symbolPrice,center,marketPriceY,SymbolFontSize,TextColor);

   //--- previous/current/next timeframe timers
   int curTF  = Period();
   int prevTF = PrevTF(curTF);
   int nextTF = NextTF(curTF);

   int rowWidth = 3*TimerColumnWidth;
   int rowStart = center - rowWidth/2;

   DrawTFBlock("PREV", CandleTimerTF(prevTF), rowStart + TimerColumnWidth/2, TimerRowTopOffset, PrevTFColor);
   DrawTFBlock("CURR", CandleTimerTF(curTF),  rowStart + TimerColumnWidth + TimerColumnWidth/2, TimerRowTopOffset, CurrentTFColor);
   DrawTFBlock("NEXT", CandleTimerTF(nextTF), rowStart + 2*TimerColumnWidth + TimerColumnWidth/2, TimerRowTopOffset, NextTFColor);

   //--- bottom dashboard: 12 instruments on ONE horizontal line
   const int totalButtons=12;

   // Fit the row automatically if the chart is narrower than the
   // preferred total width. This keeps every instrument parallel
   // on the same line instead of wrapping to a second row.
   int usableWidth=(int)cw-2*DashboardSideMargin;
   if(usableWidth<totalButtons*ButtonMinWidth)
      usableWidth=totalButtons*ButtonMinWidth;

   int dynamicWidth=ButtonWidth;
   int preferredWidth=totalButtons*ButtonWidth+(totalButtons-1)*ButtonGap;

   if(preferredWidth>usableWidth)
      dynamicWidth=(usableWidth-(totalButtons-1)*ButtonGap)/totalButtons;

   if(dynamicWidth<ButtonMinWidth)
      dynamicWidth=ButtonMinWidth;

   int dashW=totalButtons*dynamicWidth+(totalButtons-1)*ButtonGap;
   int dx=(int)((cw-dashW)/2);
   if(dx<DashboardSideMargin) dx=DashboardSideMargin;

   int yBottom=DashboardBottomOffset;

   MakeBottomButton(PFX+"B_EURUSD","EUR/USD",dx+0*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,EURUSD_Symbol);
   MakeBottomButton(PFX+"B_GBPUSD","GBP/USD",dx+1*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,GBPUSD_Symbol);
   MakeBottomButton(PFX+"B_USDJPY","USD/JPY",dx+2*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,USDJPY_Symbol);
   MakeBottomButton(PFX+"B_USDCHF","USD/CHF",dx+3*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,USDCHF_Symbol);
   MakeBottomButton(PFX+"B_USDCAD","USD/CAD",dx+4*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,USDCAD_Symbol);
   MakeBottomButton(PFX+"B_AUDUSD","AUD/USD",dx+5*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,AUDUSD_Symbol);
   MakeBottomButton(PFX+"B_NZDUSD","NZD/USD",dx+6*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,NZDUSD_Symbol);
   MakeBottomButton(PFX+"B_WTI","WTI",      dx+7*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,WTI_Symbol);
   MakeBottomButton(PFX+"B_BRENT","BRENT",  dx+8*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,BRENT_Symbol);
   MakeBottomButton(PFX+"B_GOLD","GOLD",    dx+9*(dynamicWidth+ButtonGap), yBottom,dynamicWidth,GOLD_Symbol);
   MakeBottomButton(PFX+"B_SILVER","SILVER",dx+10*(dynamicWidth+ButtonGap),yBottom,dynamicWidth,SILVER_Symbol);
   MakeBottomButton(PFX+"B_BTC","BTC",      dx+11*(dynamicWidth+ButtonGap),yBottom,dynamicWidth,BTC_Symbol);

   WindowRedraw();
}

//+------------------------------------------------------------------+
bool ChangeTo(string sym)
{
   if(sym=="") return false;
   if(!SymbolSelect(sym,true))
   {
      Print("Finsbury Park2 Market Clock: symbol not available: ",sym);
      return false;
   }

   bool ok=ChartSetSymbolPeriod(0,sym,Period());
   if(!ok) Print("Finsbury Park2 Market Clock: unable to switch to ",sym);
   return ok;
}

//+------------------------------------------------------------------+
void DeleteAll()
{
   for(int i=ObjectsTotal()-1;i>=0;i--)
   {
      string n=ObjectName(i);
      if(StringFind(n,PFX,0)==0) ObjectDelete(n);
   }
}

//+------------------------------------------------------------------+
int OnInit()
{
   IndicatorShortName("Finsbury Park2 Market Clock - Bottom Symbols V2");
   SetIndexBuffer(0,DummyBuffer);
   SetIndexStyle(0,DRAW_NONE);
   EventSetTimer(1);
   DrawAll();
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   DeleteAll();
}

//+------------------------------------------------------------------+
void OnTimer()
{
   DrawAll();
}

//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
   if(id==CHARTEVENT_CHART_CHANGE)
   {
      DrawAll();
      return;
   }

   if(id!=CHARTEVENT_OBJECT_CLICK) return;

   if(sparam==PFX+"B_EURUSD") ChangeTo(EURUSD_Symbol);
   else if(sparam==PFX+"B_GBPUSD") ChangeTo(GBPUSD_Symbol);
   else if(sparam==PFX+"B_USDJPY") ChangeTo(USDJPY_Symbol);
   else if(sparam==PFX+"B_USDCHF") ChangeTo(USDCHF_Symbol);
   else if(sparam==PFX+"B_USDCAD") ChangeTo(USDCAD_Symbol);
   else if(sparam==PFX+"B_AUDUSD") ChangeTo(AUDUSD_Symbol);
   else if(sparam==PFX+"B_NZDUSD") ChangeTo(NZDUSD_Symbol);
   else if(sparam==PFX+"B_WTI") ChangeTo(WTI_Symbol);
   else if(sparam==PFX+"B_BRENT") ChangeTo(BRENT_Symbol);
   else if(sparam==PFX+"B_GOLD") ChangeTo(GOLD_Symbol);
   else if(sparam==PFX+"B_SILVER") ChangeTo(SILVER_Symbol);
   else if(sparam==PFX+"B_BTC") ChangeTo(BTC_Symbol);
}

//+------------------------------------------------------------------+
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[])
{
   DrawAll();
   return rates_total;
}
//+------------------------------------------------------------------+
