//+------------------------------------------------------------------+
//|  DailyBreakerLatch_Example.mq5                                    |
//|  Companion to the blog post "Your guard fired, and the challenge   |
//|  failed anyway".                                                   |
//|                                                                    |
//|  Shows the one pattern the post argues for: latch the daily-loss   |
//|  breaker on the ACCOUNT STATE, never on the return code of a close |
//|  request, and keep holding the scope flat while the latch is set.  |
//|                                                                    |
//|  Illustrative and deliberately minimal. It opens no trades and     |
//|  carries no signal. Not a product, not a drop-in guard: a real one |
//|  also needs the persisted day anchor, symbol/magic scoping, a      |
//|  timer, and a reset grace window. Test on demo.                    |
//|                                                                    |
//|  TechnoTrader · technotrader.net                                   |
//+------------------------------------------------------------------+
#property copyright "TechnoTrader"
#property link      "https://technotrader.net"
#property version   "1.00"
#property strict

#include <Trade\Trade.mqh>

input double InpFirmDailyPct = 5.0;   // the firm's published daily loss limit, %
input double InpBufferPct    = 0.5;   // stop this far ahead of it, %
input int    InpDeviation    = 30;    // slippage room for the closing trades, points

CTrade  g_trade;
double  g_dayStartBal = 0.0;          // a real guard persists this; see the Risk Sentinel post
bool    g_tripped     = false;

//--- effective limit, with a floor so a tight custom limit minus the
//--- buffer can never land on zero and trip the guard on attach
double EffectiveLimitPct()
{
   return MathMax(0.05, InpFirmDailyPct - InpBufferPct);
}

//--- measure EQUITY, not balance: a floating loss counts against you now
double DailyLossPct()
{
   if(g_dayStartBal <= 0.0)
      return 0.0;
   return 100.0 * (g_dayStartBal - AccountInfoDouble(ACCOUNT_EQUITY)) / g_dayStartBal;
}

int ManagedCount()
{
   int n = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
      if(PositionGetSymbol(i) != "")
         n++;
   return n;
}

void FlattenManaged()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      string sym = PositionGetSymbol(i);
      if(sym == "")
         continue;
      if(!SymbolSelect(sym, true))      // a symbol out of Market Watch has no price
         continue;
      g_trade.PositionClose(sym);       // a true return means ACCEPTED, not closed
   }
}

int OnInit()
{
   g_trade.SetDeviationInPoints(InpDeviation);
   g_dayStartBal = AccountInfoDouble(ACCOUNT_BALANCE);
   return INIT_SUCCEEDED;
}

void OnTick()
{
   //--- LIE TWO: a latch is a STATE, not an event. While it is set, keep
   //--- holding the scope flat, or a copied signal reopens after the trip.
   if(g_tripped)
   {
      if(ManagedCount() > 0)
         FlattenManaged();
      return;
   }

   if(DailyLossPct() < EffectiveLimitPct())
      return;

   //--- LIE ONE: latch on what the account IS, not on what we asked for.
   //--- If a close was rejected, nothing latches and the next tick retries.
   FlattenManaged();
   if(ManagedCount() == 0)
      g_tripped = true;
}
//+------------------------------------------------------------------+
