Page 2 of 2

Re: Reviewing Losing Trades: A Structured Method

Posted: Wed Oct 07, 2026 7:03 pm
by PTScalper
MetaTrader 5 (MQL5)

Code: Select all

//+------------------------------------------------------------------+
//|                                            Discipline Guardrail  |
//|                                     Strict Candle Close & Filter |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2

//--- Plot Valid Long
#property indicator_label1  "Valid Long"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLimeGreen
#property indicator_width1  2

//--- Plot Valid Short
#property indicator_label2  "Valid Short"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_width2  2

//--- Session Inputs
input int InpStartHour   = 8;  // Session Start Hour (Broker Time)
input int InpStartMinute = 0;  // Session Start Minute
input int InpEndHour     = 11; // Session End Hour
input int InpEndMinute   = 30; // Session End Minute

//--- Indicator buffers
double ExtUpBuffer[];
double ExtDownBuffer[];

int OnInit()
  {
   SetIndexBuffer(0, ExtUpBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_ARROW, 233);
   
   SetIndexBuffer(1, ExtDownBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(1, PLOT_ARROW, 234);
   
   return(INIT_SUCCEEDED);
  }

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[])
  {
   // MQL5 requires setting series direction to match MT4/Pine (Bar 0 = Current)
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(ExtUpBuffer, true);
   ArraySetAsSeries(ExtDownBuffer, true);

   int limit = rates_total - prev_calculated;
   if(prev_calculated == 0)
     {
      limit = rates_total - 2; 
      ArrayInitialize(ExtUpBuffer, EMPTY_VALUE);
      ArrayInitialize(ExtDownBuffer, EMPTY_VALUE);
     }

   // 1. STRATEGY LOOP (Strictly evaluating closed candles: i >= 1)
   for(int i = limit; i >= 1; i--)
     {
      ExtUpBuffer[i] = EMPTY_VALUE;
      ExtDownBuffer[i] = EMPTY_VALUE;

      // Time Filter
      MqlDateTime tm;
      TimeToStruct(time[i], tm);
      int timeMins = tm.hour * 60 + tm.min;
      int startMins = InpStartHour * 60 + InpStartMinute;
      int endMins = InpEndHour * 60 + InpEndMinute;
      bool inSession = (timeMins >= startMins && timeMins <= endMins);

      if(!inSession) continue; // Skip out-of-session bars

      // Price Action Logic (Liquidity Sweep Placeholder)
      double o = open[i];
      double c = close[i];
      double h = high[i];
      double l = low[i];

      double totalRange = h - l;
      if(totalRange == 0) continue;

      double upperWick = h - MathMax(o, c);
      double lowerWick = MathMin(o, c) - l;

      bool bullishSweep = (lowerWick > totalRange * 0.5) && (l < low[i+1]) && (c > o);
      bool bearishSweep = (upperWick > totalRange * 0.5) && (h > high[i+1]) && (c < o);

      if(bullishSweep) ExtUpBuffer[i] = l - (10 * _Point);
      if(bearishSweep) ExtDownBuffer[i] = h + (10 * _Point);
     }

   // 2. ALERT TRIGGER (Only fires once exact moment Bar 0 opens & Bar 1 closes)
   static datetime last_time = 0;
   if(time[0] != last_time)
     {
      if(last_time != 0) // Prevent alert on initial compilation load
        {
         if(ExtUpBuffer[1] != EMPTY_VALUE) 
            Alert("VALID LONG: Sweep confirmed, candle closed, inside session.");
         if(ExtDownBuffer[1] != EMPTY_VALUE) 
            Alert("VALID SHORT: Sweep confirmed, candle closed, inside session.");
        }
      last_time = time[0];
     }

   // 3. LIVE DASHBOARD (Comment)
   MqlDateTime curr_tm;
   TimeToStruct(time[0], curr_tm);
   int currMins = curr_tm.hour * 60 + curr_tm.min;
   int sMins = InpStartHour * 60 + InpStartMinute;
   int eMins = InpEndHour * 60 + InpEndMinute;
   bool currInSession = (currMins >= sMins && currMins <= eMins);

   string dash = "\n --- DISCIPLINE GUARDRAIL ---\n";
   dash += " Session: " + (string)(currInSession ? "[ACTIVE]" : "[OUT OF SESSION]") + "\n";
   dash += " Candle:  WAIT FOR CLOSE\n";
   Comment(dash);

   return(rates_total);
  }

Re: Reviewing Losing Trades: A Structured Method

Posted: Wed Oct 07, 2026 7:03 pm
by PTScalper
MetaTrader 4 (MQL4)

Code: Select all

//+------------------------------------------------------------------+
//|                                            Discipline Guardrail  |
//|                                     Strict Candle Close & Filter |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2

//--- Plot Setup
#property indicator_label1  "Valid Long"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLimeGreen
#property indicator_width1  2

#property indicator_label2  "Valid Short"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_width2  2

//--- Session Inputs
input int InpStartHour   = 8;  // Session Start Hour (Broker Time)
input int InpStartMinute = 0;  // Session Start Minute
input int InpEndHour     = 11; // Session End Hour
input int InpEndMinute   = 30; // Session End Minute

double ExtUpBuffer[];
double ExtDownBuffer[];

int OnInit()
  {
   SetIndexBuffer(0, ExtUpBuffer);
   SetIndexArrow(0, 233);
   
   SetIndexBuffer(1, ExtDownBuffer);
   SetIndexArrow(1, 234);
   
   return(INIT_SUCCEEDED);
  }

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 limit = rates_total - prev_calculated;
   if(limit == 0) limit = 1; // Always evaluate at least the last closed bar

   // 1. STRATEGY LOOP (Evaluating Bar 1 backwards)
   for(int i = limit; i >= 1; i--)
     {
      ExtUpBuffer[i] = EMPTY_VALUE;
      ExtDownBuffer[i] = EMPTY_VALUE;

      // Session Check
      datetime t = Time[i];
      int h = TimeHour(t);
      int m = TimeMinute(t);
      int timeMins = h * 60 + m;
      int startMins = InpStartHour * 60 + InpStartMinute;
      int endMins = InpEndHour * 60 + InpEndMinute;
      bool inSession = (timeMins >= startMins && timeMins <= endMins);

      if(!inSession) continue;

      double o = Open[i];
      double c = Close[i];
      double h_val = High[i];
      double l_val = Low[i];

      double totalRange = h_val - l_val;
      if(totalRange == 0) continue;

      double upperWick = h_val - MathMax(o, c);
      double lowerWick = MathMin(o, c) - l_val;

      bool bullishSweep = (lowerWick > totalRange * 0.5) && (l_val < Low[i+1]) && (c > o);
      bool bearishSweep = (upperWick > totalRange * 0.5) && (h_val > High[i+1]) && (c < o);

      if(bullishSweep) ExtUpBuffer[i] = l_val - (10 * Point);
      if(bearishSweep) ExtDownBuffer[i] = h_val + (10 * Point);
     }

   // 2. NEW BAR TRIGGER FOR ALERTS
   static datetime last_time = 0;
   if(Time[0] != last_time)
     {
      if(last_time != 0) 
        {
         if(ExtUpBuffer[1] != EMPTY_VALUE) 
            Alert("VALID LONG: Sweep confirmed, candle closed, inside session.");
         if(ExtDownBuffer[1] != EMPTY_VALUE) 
            Alert("VALID SHORT: Sweep confirmed, candle closed, inside session.");
        }
      last_time = Time[0];
     }

   // 3. DASHBOARD
   datetime curr_t = Time[0];
   int currMins = TimeHour(curr_t) * 60 + TimeMinute(curr_t);
   int sMins = InpStartHour * 60 + InpStartMinute;
   int eMins = InpEndHour * 60 + InpEndMinute;
   bool currInSession = (currMins >= sMins && currMins <= eMins);

   string dash = "\n --- DISCIPLINE GUARDRAIL ---\n";
   dash += " Session: " + (string)(currInSession ? "[ACTIVE]" : "[OUT OF SESSION]") + "\n";
   dash += " Candle:  WAIT FOR CLOSE\n";
   Comment(dash);

   return(rates_total);
  }

Re: Reviewing Losing Trades: A Structured Method

Posted: Wed Oct 07, 2026 7:03 pm
by PTScalper
Key Behavioral Implementation Rules for MT4/5:

Zero Repainting: Because logic never evaluates i = 0 (the active candle) during the loop, the signals will never flash, repaint, or disappear. If an arrow is painted, the conditions were cemented in historical fact.

Timezone Differences: MetaTrader time runs on the Broker's server time (often Eastern European Time / UTC+2 or +3), not your local PC time. You must adjust the InpStartHour and InpEndHour variables to match your broker's representation of the London/NY overlap.

Replacing the Logic: To implement your specific price-action parameters instead of the placeholder sweeps, just swap the equations for bullishSweep and bearishSweep inside the for loop, ensuring you reference Open, Close, etc., to keep the [1] closed-bar logic intact.

Re: Reviewing Losing Trades: A Structured Method

Posted: Wed Oct 07, 2026 7:04 pm
by PTScalper
Because cTrader uses standard C# within the .NET framework, we have access to a much cleaner architectural trigger than MQL provides. We do not need to build manual time-checking loops; we can simply utilize the native OnBarClosed() override to guarantee alerts fire only when the 1-minute or 5-minute candle officially cements.

To physically prevent premature execution, the Calculate() loop immediately aborts if IsLastBar is true. This means the engine absolutely refuses to evaluate or draw anything on a forming candle.

Re: Reviewing Losing Trades: A Structured Method

Posted: Wed Oct 07, 2026 7:04 pm
by PTScalper
Here is the cAlgo implementation:

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class DisciplineGuardrail : Indicator
    {
        [Parameter("Session Start Hour", DefaultValue = 8, Group = "Session Filter (Broker Time)")]
        public int StartHour { get; set; }

        [Parameter("Session Start Minute", DefaultValue = 0, Group = "Session Filter (Broker Time)")]
        public int StartMinute { get; set; }

        [Parameter("Session End Hour", DefaultValue = 11, Group = "Session Filter (Broker Time)")]
        public int EndHour { get; set; }

        [Parameter("Session End Minute", DefaultValue = 30, Group = "Session Filter (Broker Time)")]
        public int EndMinute { get; set; }

        // Keeping DataSeries available so your other cBots can read the strict signals
        [Output("Valid Long", LineColor = "LimeGreen", PlotType = PlotType.Points, Thickness = 4)]
        public IndicatorDataSeries LongSeries { get; set; }

        [Output("Valid Short", LineColor = "Red", PlotType = PlotType.Points, Thickness = 4)]
        public IndicatorDataSeries ShortSeries { get; set; }

        protected override void Initialize()
        {
            UpdateDashboard();
        }

        public override void Calculate(int index)
        {
            // 1. DASHBOARD UPDATE (Updates on live ticks)
            if (IsLastBar)
            {
                UpdateDashboard();
                return; // STRICT CONSTRAINT: Instantly aborts. Cannot evaluate forming candles.
            }

            // 2. SESSION FILTER
            DateTime barTime = Bars.OpenTimes[index];
            TimeSpan currentTime = barTime.TimeOfDay;
            TimeSpan startTime = new TimeSpan(StartHour, StartMinute, 0);
            TimeSpan endTime = new TimeSpan(EndHour, EndMinute, 0);

            bool inSession = (currentTime >= startTime && currentTime <= endTime);
            if (!inSession) return;

            // 3. PRICE ACTION LOGIC (Liquidity Sweep Placeholder)
            double open = Bars.OpenPrices[index];
            double close = Bars.ClosePrices[index];
            double high = Bars.HighPrices[index];
            double low = Bars.LowPrices[index];
            
            // Require index >= 1 to look back at previous candle structure
            if (index < 1) return;
            
            double prevLow = Bars.LowPrices[index - 1];
            double prevHigh = Bars.HighPrices[index - 1];

            double totalRange = high - low;
            if (totalRange == 0) return;

            double upperWick = high - Math.Max(open, close);
            double lowerWick = Math.Min(open, close) - low;

            bool bullishSweep = (lowerWick > totalRange * 0.5) && (low < prevLow) && (close > open);
            bool bearishSweep = (upperWick > totalRange * 0.5) && (high > prevHigh) && (close < open);

            // 4. PLOTTING & BUFFERING
            if (bullishSweep)
            {
                LongSeries[index] = low - (Symbol.PipSize * 2);
                Chart.DrawIcon("Long" + index, ChartIconType.UpArrow, index, low - (Symbol.PipSize * 2), Color.LimeGreen);
            }
            
            if (bearishSweep)
            {
                ShortSeries[index] = high + (Symbol.PipSize * 2);
                Chart.DrawIcon("Short" + index, ChartIconType.DownArrow, index, high + (Symbol.PipSize * 2), Color.Red);
            }
        }

        // 5. ALERT TRIGGER (Native cTrader closed-bar override)
        protected override void OnBarClosed()
        {
            // Bars.Count - 2 safely references the candle that just finalized
            int closedIndex = Bars.Count - 2; 
            
            if (!double.IsNaN(LongSeries[closedIndex]))
            {
                Notifications.PlaySound(SoundType.Ring);
                Print("VALID LONG: Sweep confirmed, candle closed, inside session.");
            }
            
            if (!double.IsNaN(ShortSeries[closedIndex]))
            {
                Notifications.PlaySound(SoundType.Ring);
                Print("VALID SHORT: Sweep confirmed, candle closed, inside session.");
            }
        }

        private void UpdateDashboard()
        {
            TimeSpan serverTime = Server.Time.TimeOfDay;
            TimeSpan startTime = new TimeSpan(StartHour, StartMinute, 0);
            TimeSpan endTime = new TimeSpan(EndHour, EndMinute, 0);
            
            bool inSession = (serverTime >= startTime && serverTime <= endTime);

            string status = inSession ? "ACTIVE" : "OUT OF SESSION";
            Color statusColor = inSession ? Color.LimeGreen : Color.Red;

            string dashText = $"--- DISCIPLINE GUARDRAIL ---\nSession: [{status}]\nCandle: WAIT FOR CLOSE";
            Chart.DrawStaticText("Dashboard", dashText, VerticalAlignment.Top, HorizontalAlignment.Right, statusColor);
        }
    }
}

Re: Reviewing Losing Trades: A Structured Method

Posted: Wed Oct 07, 2026 7:05 pm
by PTScalper
cTrader Integration Notes:

cBot Integration: Because this is built using standard IndicatorDataSeries outputs (LongSeries and ShortSeries), you can easily reference this indicator from within an automated cBot later by calling Indicators.GetIndicator<DisciplineGuardrail>(...) and reading the values, skipping the need to rewrite your price-action logic.

Timezones: cTrader natively defaults to UTC for indicator parameters unless modified. Ensure the StartHour and EndHour inputs match the broker's server time (often UTC+2 or UTC+3 for spot forex).

Visual Placement: Instead of just drawing the standard series dots, this uses Chart.DrawIcon to explicitly print standard arrows 2 pips away from the wick extremes, which keeps the visual setup incredibly clean when evaluating 1-minute chart structures.