//+------------------------------------------------------------------+
//|                        chaikin-money-flow-trend-ea.mq5           |
//|  A Chaikin Money Flow EA: trades CMF crossing its threshold      |
//|  (custom calculated, no built-in CMF function) only in the       |
//|  direction of a Moving Average trend filter, with ATR-based Stop |
//|  Loss/Take Profit and risk-based position sizing. One position   |
//|  at a time.                                                       |
//|  EDUCATIONAL — test on a demo account first. Past performance     |
//|  does not guarantee future results. This is not financial advice.|
//|  Source: web-forex (educational, free to use and modify)          |
//+------------------------------------------------------------------+
#property copyright "web-forex"
#include <Trade\Trade.mqh>

CTrade trade;

input string       ___Strategy___       = "--- Chaikin Money Flow Trend Strategy ---";
input int          CMFPeriod            = 20;
input double       CMFThreshold         = 0.05;

input string       ___TrendFilter___    = "--- Trend Filter ---";
input bool         UseTrendFilter       = true;
input int          TrendMAPeriod        = 100;

input string       ___RiskManagement___ = "--- Risk Management ---";
input int          ATRPeriod            = 14;
input double       ATRMultiplier        = 2.0;
input double       RiskRewardRatio      = 2.0;
input bool         UseFixedLot          = false;
input double       FixedLotSize         = 0.01;
input double       RiskPercent          = 1.0;

input string       ___Filters___        = "--- Filters ---";
input int          MaxSpreadPoints      = 30;

input string       ___General___        = "--- General ---";
input ulong        MagicNumber          = 20260824;
input bool         EnableTrading        = true;

int atrHandle = INVALID_HANDLE;
int maHandle  = INVALID_HANDLE;
datetime lastBarTime = 0;

int OnInit()
{
   trade.SetExpertMagicNumber(MagicNumber);
   atrHandle = iATR(_Symbol, _Period, ATRPeriod);
   maHandle  = iMA(_Symbol, _Period, TrendMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   IndicatorRelease(atrHandle);
   IndicatorRelease(maHandle);
}

void OnTick()
{
   if (!EnableTrading)
      return;

   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if (currentBarTime == lastBarTime)
      return; // only evaluate once per new bar
   lastBarTime = currentBarTime;

   if (CountOpenPositions() > 0)
      return; // one position at a time

   long spreadPoints = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   if (spreadPoints > MaxSpreadPoints)
      return; // spread too wide right now, skip this bar

   int needed = CMFPeriod + 4;
   double h[], l[], c[];
   long   v[];
   ArraySetAsSeries(h, true);
   ArraySetAsSeries(l, true);
   ArraySetAsSeries(c, true);
   ArraySetAsSeries(v, true);
   if (CopyHigh(_Symbol, _Period, 0, needed, h) < needed) return;
   if (CopyLow(_Symbol, _Period, 0, needed, l) < needed) return;
   if (CopyClose(_Symbol, _Period, 0, needed, c) < needed) return;
   if (CopyTickVolume(_Symbol, _Period, 0, needed, v) < needed) return;

   double cmfLast = CalculateCMF(1, h, l, c, v);
   double cmfPrev = CalculateCMF(2, h, l, c, v);

   double maArr[];
   ArraySetAsSeries(maArr, true);
   if (CopyBuffer(maHandle, 0, 0, 3, maArr) < 3) return;
   double ma = maArr[1];

   bool uptrend   = (!UseTrendFilter || c[1] > ma);
   bool downtrend = (!UseTrendFilter || c[1] < ma);

   double atrArr[];
   ArraySetAsSeries(atrArr, true);
   if (CopyBuffer(atrHandle, 0, 0, 2, atrArr) < 2) return;
   double atr = atrArr[1];

   bool bullishCross = (cmfPrev <= CMFThreshold && cmfLast > CMFThreshold && uptrend);
   bool bearishCross = (cmfPrev >= -CMFThreshold && cmfLast < -CMFThreshold && downtrend);

   if (bullishCross)
      OpenTrade(ORDER_TYPE_BUY, atr);
   else if (bearishCross)
      OpenTrade(ORDER_TYPE_SELL, atr);
}

double CalculateCMF(int shift, const double &h[], const double &l[], const double &c[], const long &v[])
{
   double mfvSum = 0.0;
   double volSum = 0.0;

   for (int j = 0; j < CMFPeriod; j++)
   {
      int idx = shift + j;
      double range = h[idx] - l[idx];
      double mfm   = 0.0;
      if (range > 0.0)
         mfm = ((c[idx] - l[idx]) - (h[idx] - c[idx])) / range;

      double vol = (double)v[idx];
      mfvSum += mfm * vol;
      volSum += vol;
   }

   if (volSum <= 0.0)
      return(0.0);
   return(mfvSum / volSum);
}

int CountOpenPositions()
{
   int count = 0;
   for (int i = 0; i < PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if (ticket > 0 && PositionSelectByTicket(ticket))
      {
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == (long)MagicNumber)
            count++;
      }
   }
   return(count);
}

void OpenTrade(ENUM_ORDER_TYPE type, double atr)
{
   double slDistance = atr * ATRMultiplier;
   double tpDistance = slDistance * RiskRewardRatio;
   double lots       = CalculateLotSize(slDistance);
   double price, sl, tp;

   if (type == ORDER_TYPE_BUY)
   {
      price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      sl = price - slDistance;
      tp = price + tpDistance;
      trade.Buy(lots, _Symbol, price, sl, tp, "CMF Trend EA");
   }
   else
   {
      price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      sl = price + slDistance;
      tp = price - tpDistance;
      trade.Sell(lots, _Symbol, price, sl, tp, "CMF Trend EA");
   }
}

double CalculateLotSize(double slDistance)
{
   if (UseFixedLot || slDistance <= 0)
      return(NormalizeLotSize(FixedLotSize));

   double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
   double tickValue   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if (tickSize <= 0 || tickValue <= 0)
      return(NormalizeLotSize(FixedLotSize));

   double slTicks = slDistance / tickSize;
   double lots    = riskAmount / (slTicks * tickValue);

   return(NormalizeLotSize(lots));
}

double NormalizeLotSize(double lots)
{
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   if (lotStep <= 0)
      return(minLot);

   lots = MathFloor(lots / lotStep) * lotStep;
   if (lots < minLot)
      lots = minLot;
   if (lots > maxLot)
      lots = maxLot;
   return(lots);
}
