//+------------------------------------------------------------------+
//|                                        CleanTrendEA_FTMO.mq5     |
//|                                                                  |
//|  FTMO 2-Step Challenge Edition                                   |
//|  Built specifically for $100k (or any size) FTMO challenge.      |
//|                                                                  |
//|  FTMO RULE TRACKING BUILT IN:                                    |
//|    1. Daily Loss Guard   - stops at 4.5% (FTMO limit is 5%)     |
//|    2. Total Loss Floor   - stops at 9.0% (FTMO limit is 10%)    |
//|    3. Profit Target Lock - stops new entries at 9.5% profit      |
//|       (FTMO target is 10% Phase 1, 5% Phase 2)                  |
//|    4. Trading Day Counter - tracks minimum 4 days requirement    |
//|                                                                  |
//|  CRITICAL — DAILY LOSS RULE:                                     |
//|    FTMO calculates daily loss as: Balance-at-midnight MINUS your |
//|    current equity (which includes ALL open floating losses,      |
//|    commissions and swaps). This is the #1 reason challenges fail.|
//|    This EA tracks it correctly.                                  |
//|                                                                  |
//|  STRATEGY:                                                       |
//|    Trend filter (200 EMA) + EMA cross entry (20/50)             |
//|    ATR-based stops, targets and trailing stop                    |
//|    Risk-based position sizing (% of balance per trade)           |
//|                                                                  |
//|  HOW TO USE:                                                     |
//|    1. Set InpInitialBalance to your exact challenge balance      |
//|    2. Set InpTimeframe and attach to a chart of that timeframe   |
//|    3. Keep InpRiskPercent at 1.0% - do NOT increase it          |
//|    4. Run in Strategy Tester BEFORE the real challenge           |
//|    5. One chart per symbol. Use different InpMagic per chart.    |
//+------------------------------------------------------------------+
#property copyright "Educational template - FTMO Edition"
#property version   "1.00"
#property description "Transparent FTMO-rule-aware trend EA."

#include <Trade/Trade.mqh>

//==================================================================
//  INPUTS
//==================================================================
input group "=== FTMO Challenge Settings ==="
input double InpInitialBalance     = 100000.0;  // Challenge starting balance ($)
input double InpDailyLossLimitPct  = 4.5;       // Daily loss limit % (FTMO=5%, use 4.5 as buffer)
input double InpTotalLossFloorPct  = 9.0;       // Max total loss % (FTMO=10%, use 9.0 as buffer)
input double InpProfitTargetPct    = 9.5;       // Stop new entries at this profit % (Phase1=10%)

input group "=== Strategy ==="
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1;   // Timeframe (attach chart to match!)
input int    InpFastEMA            = 20;           // Fast EMA period
input int    InpSlowEMA            = 50;           // Slow EMA period
input int    InpTrendEMA           = 200;          // Long-term trend filter EMA
input int    InpATRPeriod          = 14;           // ATR period

input group "=== Risk Management ==="
input double InpRiskPercent        = 1.0;          // Risk per trade (% of balance) — keep at 1%
input double InpATR_SL_Mult        = 2.0;          // Stop loss = ATR x this
input double InpATR_TP_Mult        = 3.0;          // Take profit = ATR x this
input double InpATR_Trail_Mult     = 2.0;          // Trailing stop = ATR x this
input bool   InpUseTrailing        = true;         // Use ATR trailing stop

input group "=== Misc ==="
input long   InpMagic              = 20260601;     // Magic number (change per chart/symbol)
input int    InpMaxSlippage        = 30;           // Max slippage in points

//==================================================================
//  GLOBALS
//==================================================================
CTrade   trade;

int      hFastEMA  = INVALID_HANDLE;
int      hSlowEMA  = INVALID_HANDLE;
int      hTrendEMA = INVALID_HANDLE;
int      hATR      = INVALID_HANDLE;

// --- Day tracking ---
datetime g_currentDay       = 0;
double   g_dayStartBalance  = 0.0;  // BALANCE (closed trades only) at midnight = FTMO daily reference
bool     g_tradingDisabled  = false;
string   g_disableReason    = "";

// --- FTMO metrics ---
double   g_dailyLossPct     = 0.0;
double   g_totalLossPct     = 0.0;
double   g_profitPct        = 0.0;
bool     g_profitTargetHit  = false;

// --- Trading day counter ---
int      g_tradingDays      = 0;
datetime g_lastTradingDate  = 0;

// --- Heartbeat ---
datetime g_lastBarTime      = 0;
datetime g_lastTickLocal    = 0;

//==================================================================
//  INIT / DEINIT
//==================================================================
int OnInit()
{
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpMaxSlippage);
   trade.SetTypeFillingBySymbol(_Symbol);

   hFastEMA  = iMA(_Symbol, InpTimeframe, InpFastEMA,  0, MODE_EMA, PRICE_CLOSE);
   hSlowEMA  = iMA(_Symbol, InpTimeframe, InpSlowEMA,  0, MODE_EMA, PRICE_CLOSE);
   hTrendEMA = iMA(_Symbol, InpTimeframe, InpTrendEMA, 0, MODE_EMA, PRICE_CLOSE);
   hATR      = iATR(_Symbol, InpTimeframe, InpATRPeriod);

   if(hFastEMA==INVALID_HANDLE || hSlowEMA==INVALID_HANDLE ||
      hTrendEMA==INVALID_HANDLE || hATR==INVALID_HANDLE)
   {
      Print("ERROR: Could not create indicator handles.");
      return(INIT_FAILED);
   }

   // Set day reference using BALANCE (not equity) at the current moment.
   // This matches FTMO's rule: daily loss reference = balance at midnight.
   g_currentDay      = StartOfDay(TimeCurrent());
   g_dayStartBalance = AccountInfoDouble(ACCOUNT_BALANCE);
   g_tradingDisabled = false;
   g_disableReason   = "";
   g_lastTickLocal   = TimeLocal();

   Print("FTMO EA started. Initial Balance: ", DoubleToString(InpInitialBalance, 2),
         " | Daily Limit: ", DoubleToString(InpDailyLossLimitPct, 1), "%",
         " | Total Floor: ", DoubleToString(InpTotalLossFloorPct, 1), "%",
         " | Profit Target: ", DoubleToString(InpProfitTargetPct, 1), "%");

   EventSetTimer(1);
   UpdateDashboard();
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   if(hFastEMA  != INVALID_HANDLE) IndicatorRelease(hFastEMA);
   if(hSlowEMA  != INVALID_HANDLE) IndicatorRelease(hSlowEMA);
   if(hTrendEMA != INVALID_HANDLE) IndicatorRelease(hTrendEMA);
   if(hATR      != INVALID_HANDLE) IndicatorRelease(hATR);
   Comment("");
}

//==================================================================
//  MAIN TICK HANDLER
//==================================================================
void OnTick()
{
   g_lastTickLocal = TimeLocal();

   // --- 1. New day rollover ---
   datetime today = StartOfDay(TimeCurrent());
   if(today != g_currentDay)
   {
      g_currentDay      = today;
      // Record BALANCE (not equity) as the new day's reference — matches FTMO rule
      g_dayStartBalance = AccountInfoDouble(ACCOUNT_BALANCE);
      // Only re-enable if we were shut down for daily loss (not total loss / profit target)
      if(g_disableReason == "DAILY_LOSS")
      {
         g_tradingDisabled = false;
         g_disableReason   = "";
         Print("New trading day. Daily loss counter reset. Trading re-enabled.");
      }
   }

   // --- 2. Calculate all FTMO metrics ---
   double equity   = AccountInfoDouble(ACCOUNT_EQUITY);
   double balance  = AccountInfoDouble(ACCOUNT_BALANCE);

   // Daily loss: (day-start BALANCE) minus (current EQUITY) — includes all floating + commissions
   double dailyLossDollars = g_dayStartBalance - equity;
   g_dailyLossPct = (g_dayStartBalance > 0.0)
                    ? dailyLossDollars / g_dayStartBalance * 100.0
                    : 0.0;

   // Total loss from initial balance
   double totalLossDollars = InpInitialBalance - equity;
   g_totalLossPct = (InpInitialBalance > 0.0)
                    ? totalLossDollars / InpInitialBalance * 100.0
                    : 0.0;

   // Profit from initial balance
   g_profitPct = (InpInitialBalance > 0.0)
                 ? (equity - InpInitialBalance) / InpInitialBalance * 100.0
                 : 0.0;

   // --- 3. FTMO kill switches (checked every tick) ---
   if(!g_tradingDisabled)
   {
      // Daily loss guard (4.5% buffer under 5% FTMO limit)
      if(g_dailyLossPct >= InpDailyLossLimitPct)
      {
         CloseAllPositions();
         g_tradingDisabled = true;
         g_disableReason   = "DAILY_LOSS";
         Print("DAILY LOSS LIMIT HIT: ", DoubleToString(g_dailyLossPct, 2),
               "%. Closing all trades. Will reset on next calendar day.");
      }

      // Total loss floor (9% buffer under 10% FTMO limit)
      else if(g_totalLossPct >= InpTotalLossFloorPct)
      {
         CloseAllPositions();
         g_tradingDisabled = true;
         g_disableReason   = "MAX_LOSS";
         Print("TOTAL LOSS FLOOR HIT: ", DoubleToString(g_totalLossPct, 2),
               "%. Closing all trades. PERMANENT — challenge at risk.");
      }

      // Profit target reached — stop new entries, protect the gain
      else if(g_profitPct >= InpProfitTargetPct && !g_profitTargetHit)
      {
         g_profitTargetHit = true;
         Print("PROFIT TARGET REACHED: ", DoubleToString(g_profitPct, 2),
               "%. Stopping new entries. Let existing trades close naturally.");
      }
   }

   // --- 4. Manage open trades (trailing stop) ---
   if(InpUseTrailing)
      ManageTrailing();

   UpdateDashboard();

   if(g_tradingDisabled || g_profitTargetHit)
      return;

   // --- 5. Entry logic: once per new closed bar ---
   datetime barTime = iTime(_Symbol, InpTimeframe, 0);
   if(barTime == g_lastBarTime)
      return;
   g_lastBarTime = barTime;

   CheckForEntry();
}

void OnTimer()
{
   UpdateDashboard();
}

//==================================================================
//  ENTRY LOGIC
//==================================================================
void CheckForEntry()
{
   if(PositionExistsForThisEA())
      return;

   double fast[], slow[], trend[], atr[];
   ArraySetAsSeries(fast,  true);
   ArraySetAsSeries(slow,  true);
   ArraySetAsSeries(trend, true);
   ArraySetAsSeries(atr,   true);

   if(CopyBuffer(hFastEMA, 0, 0, 3, fast)  < 3) return;
   if(CopyBuffer(hSlowEMA, 0, 0, 3, slow)  < 3) return;
   if(CopyBuffer(hTrendEMA,0, 0, 2, trend) < 2) return;
   if(CopyBuffer(hATR,     0, 0, 2, atr)   < 2) return;

   double atrVal   = atr[1];
   if(atrVal <= 0.0) return;

   double price    = iClose(_Symbol, InpTimeframe, 1);
   double trendVal = trend[1];

   // Cross on CLOSED bars only
   bool crossUp   = (fast[2] <= slow[2]) && (fast[1] >  slow[1]);
   bool crossDown = (fast[2] >= slow[2]) && (fast[1] <  slow[1]);
   bool trendUp   = (price > trendVal);
   bool trendDown = (price < trendVal);

   if(crossUp && trendUp)
      OpenTrade(ORDER_TYPE_BUY, atrVal);
   else if(crossDown && trendDown)
      OpenTrade(ORDER_TYPE_SELL, atrVal);
}

void OpenTrade(ENUM_ORDER_TYPE type, double atrVal)
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   double slDist = atrVal * InpATR_SL_Mult;
   double tpDist = atrVal * InpATR_TP_Mult;

   double entry, sl, tp;
   if(type == ORDER_TYPE_BUY)
   { entry = ask; sl = entry - slDist; tp = entry + tpDist; }
   else
   { entry = bid; sl = entry + slDist; tp = entry - tpDist; }

   double lots = CalcLotSize(slDist);
   if(lots <= 0.0) return;

   sl = NormalizeDouble(sl, _Digits);
   tp = NormalizeDouble(tp, _Digits);

   bool ok = (type == ORDER_TYPE_BUY)
             ? trade.Buy(lots, _Symbol, 0.0, sl, tp, "CleanTrendEA_FTMO")
             : trade.Sell(lots, _Symbol, 0.0, sl, tp, "CleanTrendEA_FTMO");

   if(ok)
   {
      // Count trading day
      datetime tradeDate = StartOfDay(TimeCurrent());
      if(tradeDate != g_lastTradingDate)
      {
         g_tradingDays++;
         g_lastTradingDate = tradeDate;
      }
   }
   else
      Print("Order failed: ", trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription());
}

//==================================================================
//  POSITION SIZING
//==================================================================
double CalcLotSize(double slDistPrice)
{
   double balance   = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskMoney = balance * InpRiskPercent / 100.0;

   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tickSize <= 0.0 || tickValue <= 0.0) return(0.0);

   double lossPerLot = (slDistPrice / tickSize) * tickValue;
   if(lossPerLot <= 0.0) return(0.0);

   double lots    = riskMoney / lossPerLot;
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(lotStep > 0.0)
      lots = MathFloor(lots / lotStep) * lotStep;

   if(lots < minLot)
   {
      Print("Risk lot (", DoubleToString(lots,4), ") < broker minimum (",
            DoubleToString(minLot,2), "). Skipping trade.");
      return(0.0);
   }
   if(lots > maxLot) lots = maxLot;

   return(NormalizeDouble(lots, 2));
}

//==================================================================
//  TRADE MANAGEMENT
//==================================================================
void ManageTrailing()
{
   double atr[];
   ArraySetAsSeries(atr, true);
   if(CopyBuffer(hATR, 0, 0, 2, atr) < 2) return;
   double atrVal = atr[1];
   if(atrVal <= 0.0) return;
   double trailDist = atrVal * InpATR_Trail_Mult;

   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetInteger(POSITION_MAGIC) != InpMagic)  continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)   continue;

      long   pType = PositionGetInteger(POSITION_TYPE);
      double openP = PositionGetDouble(POSITION_PRICE_OPEN);
      double curSL = PositionGetDouble(POSITION_SL);
      double curTP = PositionGetDouble(POSITION_TP);
      double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      if(pType == POSITION_TYPE_BUY)
      {
         double newSL = NormalizeDouble(bid - trailDist, _Digits);
         if(newSL > curSL && newSL > openP)
            trade.PositionModify(ticket, newSL, curTP);
      }
      else if(pType == POSITION_TYPE_SELL)
      {
         double newSL = NormalizeDouble(ask + trailDist, _Digits);
         if((curSL == 0.0 || newSL < curSL) && newSL < openP)
            trade.PositionModify(ticket, newSL, curTP);
      }
   }
}

bool PositionExistsForThisEA()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetInteger(POSITION_MAGIC) == InpMagic &&
         PositionGetString(POSITION_SYMBOL) == _Symbol)
         return(true);
   }
   return(false);
}

void CloseAllPositions()
{
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(PositionGetInteger(POSITION_MAGIC) == InpMagic &&
         PositionGetString(POSITION_SYMBOL) == _Symbol)
         trade.PositionClose(ticket);
   }
}

//==================================================================
//  DASHBOARD
//==================================================================
void UpdateDashboard()
{
   double equity   = AccountInfoDouble(ACCOUNT_EQUITY);
   double balance  = AccountInfoDouble(ACCOUNT_BALANCE);
   double floatPL  = equity - balance;
   int    secsSinceTick = (int)(TimeLocal() - g_lastTickLocal);

   // FTMO dollar amounts
   double dailyLossDollars = g_dayStartBalance - equity;
   double dailyAllowance   = InpInitialBalance * InpDailyLossLimitPct / 100.0;
   double totalLossDollars = InpInitialBalance - equity;
   double totalAllowance   = InpInitialBalance * InpTotalLossFloorPct / 100.0;
   double profitDollars    = equity - InpInitialBalance;
   double profitTarget$    = InpInitialBalance * InpProfitTargetPct / 100.0;

   // Status line
   string status;
   if(g_tradingDisabled && g_disableReason == "DAILY_LOSS")
      status = "!!! DAILY LOSS LIMIT — RESETS TOMORROW !!!";
   else if(g_tradingDisabled && g_disableReason == "MAX_LOSS")
      status = "!!! MAX TOTAL LOSS — CHECK ACCOUNT NOW !!!";
   else if(g_profitTargetHit)
      status = "*** PROFIT TARGET HIT — NO NEW ENTRIES ***";
   else
      status = "ACTIVE";

   // Build the dashboard
   string txt =
      "========= CleanTrendEA — FTMO EDITION =========\n" +
      "Symbol:        " + _Symbol + "\n" +
      "Timeframe:     " + TimeframeToString(InpTimeframe) + "  <-- attach chart to THIS\n" +
      "Status:        " + status + "\n" +
      "------------------------------------------------\n" +
      "  FTMO SCORECARD\n" +
      "  Balance:        $" + DoubleToString(balance, 2) + "\n" +
      "  Equity:         $" + DoubleToString(equity, 2) + "  (float: " + DoubleToString(floatPL, 2) + ")\n" +
      "------------------------------------------------\n" +
      "  DAILY LOSS:     $" + DoubleToString(MathMax(dailyLossDollars, 0.0), 2) +
                         "  (" + DoubleToString(MathMax(g_dailyLossPct, 0.0), 2) + "%)" +
                         "  LIMIT: $" + DoubleToString(dailyAllowance, 0) + "\n" +
      "  TOTAL LOSS:     $" + DoubleToString(MathMax(totalLossDollars, 0.0), 2) +
                         "  (" + DoubleToString(MathMax(g_totalLossPct, 0.0), 2) + "%)" +
                         "  LIMIT: $" + DoubleToString(totalAllowance, 0) + "\n" +
      "  PROFIT:         $" + DoubleToString(profitDollars, 2) +
                         "  (" + DoubleToString(g_profitPct, 2) + "%)" +
                         "  TARGET: $" + DoubleToString(profitTarget$, 0) + "\n" +
      "------------------------------------------------\n" +
      "  Trading days this session: " + IntegerToString(g_tradingDays) + " / 4 min required\n" +
      "  Risk per trade:            " + DoubleToString(InpRiskPercent, 1) + "% of balance\n" +
      "  Initial balance set to:    $" + DoubleToString(InpInitialBalance, 0) + "\n" +
      "------------------------------------------------\n" +
      "  Heartbeat: " + IntegerToString(secsSinceTick) + "s since last tick\n" +
      "================================================";

   Comment(txt);
}

//==================================================================
//  HELPERS
//==================================================================
datetime StartOfDay(datetime t)
{
   MqlDateTime st;
   TimeToStruct(t, st);
   st.hour = 0; st.min = 0; st.sec = 0;
   return(StructToTime(st));
}

string TimeframeToString(ENUM_TIMEFRAMES tf)
{
   switch(tf)
   {
      case PERIOD_M1:  return("M1");
      case PERIOD_M5:  return("M5");
      case PERIOD_M15: return("M15");
      case PERIOD_M30: return("M30");
      case PERIOD_H1:  return("H1");
      case PERIOD_H4:  return("H4");
      case PERIOD_D1:  return("D1");
      case PERIOD_W1:  return("W1");
      case PERIOD_MN1: return("MN1");
      default:         return(EnumToString(tf));
   }
}
//+------------------------------------------------------------------+
