//+------------------------------------------------------------------+
//|                          KK_MoneyFlow_MT4.mq4                    |
//|                     KK Money Flow Breakout                       |
//|         Signal: where money flows, arrow follows                 |
//+------------------------------------------------------------------+
#property copyright "KK"
#property link      ""
#property version   "1.01"
#property strict
#property indicator_chart_window
#property indicator_buffers 2

//+------------------------------------------------------------------+
//| INPUTS                                                           |
//+------------------------------------------------------------------+

input string ___General___          = "=== GENERAL ===";
input int    BarsToScan             = 2000;

input string ___MF___               = "=== MONEY FLOW ENGINE ===";
input int    CVD_Length             = 10;
input int    VWAP_Length            = 10;
input double FlowThreshold          = 0.1;

input string ___Signal___           = "=== SIGNAL SETTINGS ===";
input int    ConfirmBars            = 1;
input bool   RequirePriceBreak      = false;
input int    PriceBreak_Bars        = 2;

input string ___EMA___              = "=== EMA TREND FILTER ===";
input bool   UseEMA                 = false;
input int    EMA_Period             = 10;
input bool   EMA_PriceAboveBelow    = true;

input string ___Arrows___           = "=== ARROW STYLE ===";
input color  Clr_Buy                = clrAqua;
input color  Clr_Sell               = clrYellow;
input int    Buy_Arrow_Size         = 1;
input int    Sell_Arrow_Size        = 1;
input int    Buy_Arrow_Code         = 233;
input int    Sell_Arrow_Code        = 234;
input int    ArrowOffset_Points     = 5;

input string ___AlertBtn___         = "=== ALERT BUTTON ===";
input int    AlertBtn_X             = 5;    
input int    AlertBtn_Y             = 5;    

input string ___Alerts___           = "=== ALERTS ===";
input bool   EnableAlerts           = true;
input bool   EnablePopupAlert       = true;
input bool   EnableSoundAlert       = true;
input bool   EnablePushNotification = false;
input bool   EnableEmailAlert       = false;
input string SoundFile              = "alert.wav";

//+------------------------------------------------------------------+
//| KONSTANSOK                                                       |
//+------------------------------------------------------------------+

#define BTN_BG   "MF4_BtnBG"
#define BTN_TXT  "MF4_BtnTxt"
#define BTN_W    120
#define BTN_H    22

//+------------------------------------------------------------------+
//| BUFFERS                                                          |
//+------------------------------------------------------------------+
double BuyBuffer[];
double SellBuffer[];

//+------------------------------------------------------------------+
//| GLOBÁLIS VÁLTOZÓK                                                |
//+------------------------------------------------------------------+

datetime LastAlertTime = 0;
int      LastAlertType = 0;

bool     g_alertEnabled = true;
string   g_gvName       = "";      // chart-specifikus GlobalVariable név

// Drag
bool     g_dragging  = false;
int      g_dragOffX  = 0;
int      g_dragOffY  = 0;
bool     g_wasDrag   = false;

// Gomb pozíció
int      g_btnX      = 0;
int      g_btnY      = 0;
bool     g_userMoved = false;

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
{
   // Chart-specifikus GlobalVariable név (ChartID egyedi minden ablakhoz)
   g_gvName = StringFormat("MF4_Alert_%d", ChartID());

   SetIndexBuffer(0, BuyBuffer);
   SetIndexArrow(0, Buy_Arrow_Code);
   SetIndexStyle(0, DRAW_ARROW, STYLE_SOLID, Buy_Arrow_Size, Clr_Buy);
   SetIndexEmptyValue(0, EMPTY_VALUE);
   SetIndexLabel(0, "Buy Signal");

   SetIndexBuffer(1, SellBuffer);
   SetIndexArrow(1, Sell_Arrow_Code);
   SetIndexStyle(1, DRAW_ARROW, STYLE_SOLID, Sell_Arrow_Size, Clr_Sell);
   SetIndexEmptyValue(1, EMPTY_VALUE);
   SetIndexLabel(1, "Sell Signal");

   IndicatorShortName("KK MoneyFlow v1.0");

   // Alert állapot betöltése (TF váltás és újraindítás túléli)
   if(GlobalVariableCheck(g_gvName))
      g_alertEnabled = (GlobalVariableGet(g_gvName) > 0.0);
   else
      g_alertEnabled = true;

   CreateButton();

   // MT4-ben EventSetMillisecondTimer nem létezik, OnTimer 1 másodperces
   EventSetTimer(1);

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   ObjectDelete(0, BTN_BG);
   ObjectDelete(0, BTN_TXT);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| OnTimer – al-ablak pozíció figyelés                              |
//+------------------------------------------------------------------+
void OnTimer()
{
   if(!g_userMoved)
      PositionButton();
}

//+------------------------------------------------------------------+
//| Gomb létrehozása (Rectangle + Label)                             |
//+------------------------------------------------------------------+
void CreateButton()
{
   ObjectDelete(0, BTN_BG);
   ObjectDelete(0, BTN_TXT);

   // Háttér téglalap
   ObjectCreate(0, BTN_BG, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, BTN_BG, OBJPROP_CORNER,      CORNER_LEFT_UPPER);
   ObjectSetInteger(0, BTN_BG, OBJPROP_XDISTANCE,   AlertBtn_X);
   ObjectSetInteger(0, BTN_BG, OBJPROP_YDISTANCE,   AlertBtn_Y);
   ObjectSetInteger(0, BTN_BG, OBJPROP_XSIZE,       BTN_W);
   ObjectSetInteger(0, BTN_BG, OBJPROP_YSIZE,       BTN_H);
   ObjectSetInteger(0, BTN_BG, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, BTN_BG, OBJPROP_WIDTH,       1);
   ObjectSetInteger(0, BTN_BG, OBJPROP_SELECTABLE,  false);
   ObjectSetInteger(0, BTN_BG, OBJPROP_HIDDEN,      false);
   ObjectSetInteger(0, BTN_BG, OBJPROP_ZORDER,      1000);

   // Szöveg label
   ObjectCreate(0, BTN_TXT, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_CORNER,     CORNER_LEFT_UPPER);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_XDISTANCE,  AlertBtn_X + 4);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_YDISTANCE,  AlertBtn_Y + 5);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_FONTSIZE,   9);
   ObjectSetString (0, BTN_TXT, OBJPROP_FONT,       "Arial Bold");
   ObjectSetInteger(0, BTN_TXT, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_HIDDEN,     false);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_ZORDER,     1001);

   UpdateButtonStyle();
}

//+------------------------------------------------------------------+
//| Gomb vizuális frissítése + állapot mentése                       |
//+------------------------------------------------------------------+
void UpdateButtonStyle()
{
   GlobalVariableSet(g_gvName, g_alertEnabled ? 1.0 : 0.0);

   if(g_alertEnabled)
   {
      ObjectSetInteger(0, BTN_BG,  OBJPROP_BGCOLOR,      C'0,145,65');
      ObjectSetInteger(0, BTN_BG,  OBJPROP_BORDER_COLOR, C'0,100,40');
      ObjectSetString (0, BTN_TXT, OBJPROP_TEXT,         "  Alert: ON");
      ObjectSetInteger(0, BTN_TXT, OBJPROP_COLOR,        clrWhite);
   }
   else
   {
      ObjectSetInteger(0, BTN_BG,  OBJPROP_BGCOLOR,      C'185,35,35');
      ObjectSetInteger(0, BTN_BG,  OBJPROP_BORDER_COLOR, C'120,15,15');
      ObjectSetString (0, BTN_TXT, OBJPROP_TEXT,         "  Alert: OFF");
      ObjectSetInteger(0, BTN_TXT, OBJPROP_COLOR,        clrWhite);
   }
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Gomb pozicionálása – al-ablak felett automatikusan               |
//+------------------------------------------------------------------+
void PositionButton()
{
   int subWin = -1;
   int totalWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
   if(totalWin > 1) subWin = 1;

   int yPos;
   if(subWin > 0)
   {
      int winTop = (int)ChartGetInteger(0, CHART_WINDOW_YDISTANCE, subWin);
      yPos = winTop - BTN_H - AlertBtn_Y;
      if(yPos < 5) yPos = 5;
   }
   else
   {
      yPos = AlertBtn_Y;
   }

   g_btnX = AlertBtn_X;
   g_btnY = yPos;
   SetButtonPos(g_btnX, g_btnY);
}

void SetButtonPos(int x, int y)
{
   ObjectSetInteger(0, BTN_BG,  OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, BTN_BG,  OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_XDISTANCE, x + 4);
   ObjectSetInteger(0, BTN_TXT, OBJPROP_YDISTANCE, y + 5);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Egér a gombon van-e?                                             |
//+------------------------------------------------------------------+
bool MouseOverButton(int mx, int my)
{
   int bx = (int)ObjectGetInteger(0, BTN_BG, OBJPROP_XDISTANCE);
   int by = (int)ObjectGetInteger(0, BTN_BG, OBJPROP_YDISTANCE);
   return (mx >= bx && mx <= bx + BTN_W && my >= by && my <= by + BTN_H);
}

//+------------------------------------------------------------------+
//| OnChartEvent – drag + klikk kezelés                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_MOUSE_MOVE)
   {
      int  mx    = (int)lparam;
      int  my    = (int)dparam;
      uint flags = (uint)sparam;
      bool lDown = ((flags & 1) != 0);

      if(lDown)
      {
         if(!g_dragging)
         {
            if(MouseOverButton(mx, my))
            {
               int bx = (int)ObjectGetInteger(0, BTN_BG, OBJPROP_XDISTANCE);
               int by = (int)ObjectGetInteger(0, BTN_BG, OBJPROP_YDISTANCE);
               g_dragging = true;
               g_wasDrag  = false;
               g_dragOffX = mx - bx;
               g_dragOffY = my - by;
            }
         }
         else
         {
            int bx = (int)ObjectGetInteger(0, BTN_BG, OBJPROP_XDISTANCE);
            int by = (int)ObjectGetInteger(0, BTN_BG, OBJPROP_YDISTANCE);
            if(MathAbs(mx - g_dragOffX - bx) > 3 || MathAbs(my - g_dragOffY - by) > 3)
               g_wasDrag = true;

            int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
            int chartH = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
            int newX   = MathMax(0, MathMin(mx - g_dragOffX, chartW - BTN_W));
            int newY   = MathMax(0, MathMin(my - g_dragOffY, chartH - BTN_H));

            g_btnX = newX; g_btnY = newY;
            g_userMoved = true;
            SetButtonPos(g_btnX, g_btnY);
         }
      }
      else
      {
         // Egér felengedve: ha nem drag → toggle
         if(g_dragging && !g_wasDrag && MouseOverButton(mx, my))
         {
            g_alertEnabled = !g_alertEnabled;
            UpdateButtonStyle();
            Print("KK MoneyFlow MT4 [", Symbol(), " #", ChartID(), "] Alert: ",
                  g_alertEnabled ? "BE" : "KI");
         }
         g_dragging = false;
         g_wasDrag  = false;
      }
   }

   if(id == CHARTEVENT_CHART_CHANGE && !g_userMoved)
      PositionButton();
}

//+------------------------------------------------------------------+
//| CVD                                                              |
//+------------------------------------------------------------------+
double GetCVD(int shift, int length)
{
   double totalBuy  = 0;
   double totalSell = 0;

   for(int i = shift; i < shift + length; i++)
   {
      double h   = High[i];
      double l   = Low[i];
      double c   = Close[i];
      double vol = (double)Volume[i];
      double hl  = h - l;

      if(hl == 0) continue;

      double closePos = (c - l) / hl;
      double buyVol   = closePos * vol;
      double sellVol  = (1.0 - closePos) * vol;

      totalBuy  += buyVol;
      totalSell += sellVol;
   }

   double total = totalBuy + totalSell;
   if(total == 0) return 0;

   return (totalBuy - totalSell) / total;
}

//+------------------------------------------------------------------+
//| VWAP deviation                                                   |
//+------------------------------------------------------------------+
double GetVWAPDeviation(int shift, int length)
{
   double sumPV  = 0;
   double sumVol = 0;

   for(int i = shift; i < shift + length; i++)
   {
      double typical = (High[i] + Low[i] + Close[i]) / 3.0;
      double vol     = (double)Volume[i];
      sumPV  += typical * vol;
      sumVol += vol;
   }

   if(sumVol == 0) return 0;

   double vwap = sumPV / sumVol;

   double range = 0;
   for(int i = shift; i < shift + length; i++)
      range += High[i] - Low[i];
   range /= length;

   if(range == 0) return 0;

   double deviation = (Close[shift] - vwap) / range;

   if(deviation >  1.0) deviation =  1.0;
   if(deviation < -1.0) deviation = -1.0;

   return deviation;
}

//+------------------------------------------------------------------+
//| Blended Money Flow Score                                         |
//+------------------------------------------------------------------+
double GetFlowScore(int shift)
{
   double cvd  = GetCVD(shift, CVD_Length);
   double vwap = GetVWAPDeviation(shift, VWAP_Length);
   return (cvd + vwap) * 0.5;
}

//+------------------------------------------------------------------+
//| Flow confirmation                                                |
//+------------------------------------------------------------------+
bool FlowConfirmed(int shift, int direction)
{
   if(ConfirmBars <= 1)
   {
      double score = GetFlowScore(shift);
      if(direction ==  1) return (score >=  FlowThreshold);
      if(direction == -1) return (score <= -FlowThreshold);
      return false;
   }

   for(int i = shift; i < shift + ConfirmBars; i++)
   {
      double score = GetFlowScore(i);
      if(direction ==  1 && score <  FlowThreshold)  return false;
      if(direction == -1 && score > -FlowThreshold)  return false;
   }
   return true;
}

//+------------------------------------------------------------------+
//| Price breakout confirmation                                      |
//+------------------------------------------------------------------+
bool PriceBreakConfirmed(int shift, int direction)
{
   if(!RequirePriceBreak) return true;

   int lookFrom = shift + 1;
   int lookTo   = shift + PriceBreak_Bars;

   double highestHigh = -DBL_MAX;
   double lowestLow   =  DBL_MAX;

   for(int i = lookFrom; i <= lookTo; i++)
   {
      if(High[i] > highestHigh) highestHigh = High[i];
      if(Low[i]  < lowestLow)  lowestLow   = Low[i];
   }

   if(direction ==  1) return (Close[shift] > highestHigh);
   if(direction == -1) return (Close[shift] < lowestLow);
   return false;
}

//+------------------------------------------------------------------+
//| EMA trend filter                                                 |
//+------------------------------------------------------------------+
bool EMAConfirms(int shift, int direction)
{
   if(!UseEMA) return true;

   double ema     = iMA(NULL, 0, EMA_Period, 0, MODE_EMA, PRICE_CLOSE, shift);
   double emaPrev = iMA(NULL, 0, EMA_Period, 0, MODE_EMA, PRICE_CLOSE, shift + 1);

   if(EMA_PriceAboveBelow)
   {
      if(direction ==  1) return (Close[shift] > ema);
      if(direction == -1) return (Close[shift] < ema);
   }
   else
   {
      if(direction ==  1) return (ema > emaPrev);
      if(direction == -1) return (ema < emaPrev);
   }

   return true;
}

//+------------------------------------------------------------------+
//| Main signal                                                      |
//+------------------------------------------------------------------+
int GetSignal(int i, int minBars, int rates_total)
{
   if(i + minBars >= rates_total) return 0;

   double score = GetFlowScore(i);

   int direction = 0;
   if(score >=  FlowThreshold) direction =  1;
   if(score <= -FlowThreshold) direction = -1;
   if(direction == 0) return 0;

   if(!FlowConfirmed(i, direction)) return 0;
   if(!PriceBreakConfirmed(i, direction)) return 0;
   if(!EMAConfirms(i, direction)) return 0;

   return direction;
}

//+------------------------------------------------------------------+
//| Alert küldése                                                    |
//+------------------------------------------------------------------+
void SendAlertMsg(string message, int alertType)
{
   if(!EnableAlerts)   return;
   if(!g_alertEnabled) return;   // Gombbal ki van kapcsolva

   if(Time[0] == LastAlertTime && alertType == LastAlertType) return;
   LastAlertTime = Time[0];
   LastAlertType = alertType;

   string tf = "";
   switch(Period())
   {
      case PERIOD_M1:  tf = "M1";  break;
      case PERIOD_M5:  tf = "M5";  break;
      case PERIOD_M15: tf = "M15"; break;
      case PERIOD_M30: tf = "M30"; break;
      case PERIOD_H1:  tf = "H1";  break;
      case PERIOD_H4:  tf = "H4";  break;
      case PERIOD_D1:  tf = "D1";  break;
      case PERIOD_W1:  tf = "W1";  break;
      case PERIOD_MN1: tf = "MN";  break;
      default:         tf = "TF" + IntegerToString(Period()); break;
   }

   string full = Symbol() + " " + tf + ": " + message;
   if(EnablePopupAlert)       Alert(full);
   if(EnableSoundAlert)       PlaySound(SoundFile);
   if(EnablePushNotification) SendNotification(full);
   if(EnableEmailAlert)       SendMail("KK MoneyFlow Alert", full);
}

//+------------------------------------------------------------------+
//| OnCalculate                                                      |
//+------------------------------------------------------------------+
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 minBars = MathMax(CVD_Length, VWAP_Length) +
                 MathMax(EMA_Period, PriceBreak_Bars) +
                 ConfirmBars + 10;

   if(rates_total < minBars) return(0);

   double offset = ArrowOffset_Points * Point;

   int limit;
   if(prev_calculated == 0)
   {
      limit = MathMin(rates_total - minBars, BarsToScan);
      ArrayInitialize(BuyBuffer,  EMPTY_VALUE);
      ArrayInitialize(SellBuffer, EMPTY_VALUE);
   }
   else
   {
      limit = rates_total - prev_calculated + 1;
      if(limit > BarsToScan)            limit = BarsToScan;
      if(limit > rates_total - minBars) limit = rates_total - minBars;
   }

   for(int i = limit; i >= 0; i--)
   {
      BuyBuffer[i]  = EMPTY_VALUE;
      SellBuffer[i] = EMPTY_VALUE;
   }

   int lastKnownSignal = 0;
   for(int i = limit + 1; i < rates_total; i++)
   {
      if(BuyBuffer[i]  != EMPTY_VALUE) { lastKnownSignal =  1; break; }
      if(SellBuffer[i] != EMPTY_VALUE) { lastKnownSignal = -1; break; }
   }

   for(int i = limit; i >= 0; i--)
   {
      int sig = GetSignal(i, minBars, rates_total);
      if(sig == 0) continue;
      if(sig == lastKnownSignal) continue;

      if(sig == 1)
      {
         BuyBuffer[i] = Low[i] - offset;
         if(i == 0) SendAlertMsg("BUY - Money Flow Signal!", 1);
      }
      else
      {
         SellBuffer[i] = High[i] + offset;
         if(i == 0) SendAlertMsg("SELL - Money Flow Signal!", -1);
      }

      lastKnownSignal = sig;
   }

   return(rates_total);
}
//+------------------------------------------------------------------+
