IC Markets

Tagging A+/B/C setups: what my distribution taught me after 100 trades

Document your personal trading journey. Track daily equity curves, review winning and losing streaks, share trade screenshots, and get constructive feedback.
Fairman
Posts: 606
Joined: Tue Jul 21, 2026 7:11 am
Location: Abuja

Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by Fairman »

After I tagged 100 live scalps as A+, B, or C — and forced myself to keep the grade even when P&L disagreed — the distribution taught me more than any win-rate screenshot.

Grade definitions I locked before the sample:

- A+: session permission, H1/M15 alignment, clear invalidation, trigger at the level, risk pre-sized, no news conflict. I would take it again tomorrow under the same rules.
- B: most pieces present, one soft miss (slightly early trigger, thinner location, mild fatigue). Still rules-legal.
- C: missing a hard gate, FOMO, revenge, chasing, or sizing from comfort. Even if it won.

Rules for tagging:

1. Grade within two minutes of exit — before equity narrative rewrites memory.
2. P&L does not upgrade a C. A green C stays C.
3. A red A+ stays A+. Process score and R are separate columns.
4. Ambiguous? Default to the lower grade. Generosity in journals creates fiction.

What 100 trades showed (order of magnitude, not a sales pitch):

- A+ was a minority — often under 30% of attempts. Most of my week was B and “should have been no-trade.”
- Expectancy clustered in A+. B was near flat to mildly positive after costs. C destroyed weeks even when win rate looked “okay” on C alone.
- My worst weeks were not low A+ count; they were high C count after a first loss.

Actions that followed the distribution:

1. Cap C attempts: if I tag two C’s in a session, soft mental stop — done for that window.
2. Promote only A+ and clean B into the playbook shortlist. C patterns become “forbidden list” examples, not “almost setups.”
3. Review A+ losers monthly: if they share a structural miss, tighten the A+ definition. If they are clean variance, leave the grade alone.

Common trap: renaming C to B after a win. Another: refusing to call A+ when it loses because it “feels wrong.” Grades are descriptions of process fidelity, not trophies.

If you have not graded 50–100 trades this way, start this week. Keep the definitions written. Sort by grade, then by expectancy after costs. Your distribution will tell you whether you have an edge problem or a selection problem — most scalpers have the second.
Attachments
26-abc-distribution-plain.png
26-abc-distribution-plain.png (30.31 KiB) Viewed 20 times
It’s Fairman :geek:
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

Fairman wrote: Sat Sep 05, 2026 5:02 pm After I tagged 100 live scalps as A+, B, or C — and forced myself to keep the grade even when P&L disagreed — the distribution taught me more than any win-rate screenshot.

Grade definitions I locked before the sample:

- A+: session permission, H1/M15 alignment, clear invalidation, trigger at the level, risk pre-sized, no news conflict. I would take it again tomorrow under the same rules.
- B: most pieces present, one soft miss (slightly early trigger, thinner location, mild fatigue). Still rules-legal.
- C: missing a hard gate, FOMO, revenge, chasing, or sizing from comfort. Even if it won.

Rules for tagging:

1. Grade within two minutes of exit — before equity narrative rewrites memory.
2. P&L does not upgrade a C. A green C stays C.
3. A red A+ stays A+. Process score and R are separate columns.
4. Ambiguous? Default to the lower grade. Generosity in journals creates fiction.

What 100 trades showed (order of magnitude, not a sales pitch):

- A+ was a minority — often under 30% of attempts. Most of my week was B and “should have been no-trade.”
- Expectancy clustered in A+. B was near flat to mildly positive after costs. C destroyed weeks even when win rate looked “okay” on C alone.
- My worst weeks were not low A+ count; they were high C count after a first loss.

Actions that followed the distribution:

1. Cap C attempts: if I tag two C’s in a session, soft mental stop — done for that window.
2. Promote only A+ and clean B into the playbook shortlist. C patterns become “forbidden list” examples, not “almost setups.”
3. Review A+ losers monthly: if they share a structural miss, tighten the A+ definition. If they are clean variance, leave the grade alone.

Common trap: renaming C to B after a win. Another: refusing to call A+ when it loses because it “feels wrong.” Grades are descriptions of process fidelity, not trophies.

If you have not graded 50–100 trades this way, start this week. Keep the definitions written. Sort by grade, then by expectancy after costs. Your distribution will tell you whether you have an edge problem or a selection problem — most scalpers have the second.
Hi Fairman, hi traders.

After years of executing high-volume scalps, the data always points to the exact same conclusion: most traders don't lack an edge, they lack selection discipline. C-grade setups—driven by FOMO, boredom, or chasing—are what actually destroy weeks of disciplined execution, regardless of the win rate. Separating process fidelity from the raw P&L outcome is the only way to objectively track performance. Great breakdown.

Pine Script: A+ Alignment Filter

Since the original post explicitly defines an A+ setup as having session permission and H1/M15 alignment, here is a Pine Script v5 snippet you can share in the thread.

It acts as a mechanical guardrail against C-grade trades by highlighting the chart background only when the 1-Hour and 15-Minute trends align during the allowed session window.

Code: Select all

//@version=5
indicator("A+ Setup Alignment Filter", overlay=true)

// --- Inputs ---
sessionTime = input.session("0800-1100", title="Trading Session (A+ Permission)")
emaFastLen  = input.int(9, title="Fast EMA Length")
emaSlowLen  = input.int(21, title="Slow EMA Length")

// --- HTF (1 Hour) Alignment ---
// Using request.security to pull H1 trend data
htfFast = request.security(syminfo.tickerid, "60", ta.ema(close, emaFastLen))
htfSlow = request.security(syminfo.tickerid, "60", ta.ema(close, emaSlowLen))

htfBullish = htfFast > htfSlow
htfBearish = htfFast < htfSlow

// --- LTF (Current Chart) Alignment ---
ltfFast = ta.ema(close, emaFastLen)
ltfSlow = ta.ema(close, emaSlowLen)

ltfBullish = ltfFast > ltfSlow
ltfBearish = ltfFast < ltfSlow

// --- Session Check ---
inSession = not na(time(timeframe.period, sessionTime))

// --- A+ Conditions ---
// Requires session permission + HTF/LTF alignment
isAPlusLong  = inSession and htfBullish and ltfBullish
isAPlusShort = inSession and htfBearish and ltfBearish

// --- Visuals ---
// Highlights the background green for Long A+ permission, red for Short A+ permission
bgcolor(isAPlusLong ? color.new(color.green, 90) : na, title="A+ Long Zone")
bgcolor(isAPlusShort ? color.new(color.red, 90) : na, title="A+ Short Zone")

// Plot LTF EMAs for visual reference
plot(ltfFast, color=color.blue, title="LTF Fast")
plot(ltfSlow, color=color.orange, title="LTF Slow")
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

Plus i prepared for you MT4, MT5 and Ctrader versions.

Here are the complete, ready-to-compile implementations for MetaTrader 4 (MQL4) and MetaTrader 5 (MQL5).

Both versions replicate the logic:

Session Gate: Verifies server time falls within your specified trading window.

H1 / M15 Alignment: Confirms both Higher Timeframe (H1) and Intermediate Timeframe (M15) Fast EMAs are stacked in the same direction.

Optimized Chart Shading: Draws filled background rectangles (OBJ_RECTANGLE set to the background) grouped into continuous blocks, with an active lookback limit so high-frequency tick updates won't lag your terminal.

Trigger Overlay: Plots the Fast (9) and Slow (21) EMAs directly on your execution chart.

1. MetaTrader 4 (MQL4)
Save as APlusAlignmentFilter.mq4 in your MQL4/Indicators/ folder.

Code: Select all

//+------------------------------------------------------------------+
//|                                        APlusAlignmentFilter.mq4  |
//|                                    A+ Setup Alignment Gatekeeper |
//+------------------------------------------------------------------+
#property copyright "Forex Scalping"
#property link      ""
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1  clrDodgerBlue
#property indicator_color2  clrDarkOrange
#property indicator_width1  2
#property indicator_width2  2

//--- Inputs
input string          InpSessionTime  = "08:00-11:00";   // Trading Session (HH:MM-HH:MM)
input ENUM_TIMEFRAMES InpHTF          = PERIOD_H1;       // Higher Timeframe (HTF)
input ENUM_TIMEFRAMES InpLTF          = PERIOD_M15;      // Intermediate Timeframe (LTF)
input int             InpFastEMALen   = 9;               // Fast EMA Period
input int             InpSlowEMALen   = 21;              // Slow EMA Period
input int             InpMaxBars      = 1500;            // Max Bars for Zone Shading
input color           InpLongColor    = C'20, 45, 30';   // A+ Long Shading (Dark Theme)
input color           InpShortColor   = C'50, 20, 25';   // A+ Short Shading (Dark Theme)

//--- Buffers
double FastEMABuffer[];
double SlowEMABuffer[];

//--- Internal State
int    startMinutes = 0;
int    endMinutes   = 0;
const string PREFIX = "APLUS_";

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, FastEMABuffer);
   SetIndexBuffer(1, SlowEMABuffer);
   SetIndexLabel(0, "Fast EMA");
   SetIndexLabel(1, "Slow EMA");
   SetIndexStyle(0, DRAW_LINE);
   SetIndexStyle(1, DRAW_LINE);

   if(!ParseSession(InpSessionTime, startMinutes, endMinutes))
   {
      Print("Invalid session format. Use HH:MM-HH:MM (e.g. 08:00-11:00)");
      return INIT_PARAMETERS_INCORRECT;
   }

   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, PREFIX);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Session Validator                                                |
//+------------------------------------------------------------------+
bool ParseSession(string sessionStr, int &sMin, int &eMin)
{
   string parts[];
   if(StringSplit(sessionStr, '-', parts) != 2) return false;

   string sParts[], eParts[];
   if(StringSplit(parts[0], ':', sParts) != 2 || StringSplit(parts[1], ':', eParts) != 2) return false;

   sMin = (int)StringToInteger(sParts[0]) * 60 + (int)StringToInteger(sParts[1]);
   eMin = (int)StringToInteger(eParts[0]) * 60 + (int)StringToInteger(eParts[1]);
   return true;
}

bool IsInSession(datetime t)
{
   MqlDateTime dt;
   TimeToStruct(t, dt);
   int curMin = dt.hour * 60 + dt.min;
   if(startMinutes <= endMinutes)
      return (curMin >= startMinutes && curMin < endMinutes);
   else
      return (curMin >= startMinutes || curMin < endMinutes);
}

//+------------------------------------------------------------------+
//| Background Zone Management                                       |
//+------------------------------------------------------------------+
void DrawZoneRect(string name, datetime t1, datetime t2, color clr)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, -100000.0, t2, 2000000.0);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_FILL, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   }
   else
   {
      ObjectSetInteger(0, name, OBJPROP_TIME, 0, t1);
      ObjectSetInteger(0, name, OBJPROP_TIME, 1, t2);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
   if(rates_total <= InpSlowEMALen) return 0;

   // 1. Calculate Chart EMAs
   int limit = rates_total - prev_calculated;
   if(limit > 1) limit = rates_total - 1;

   for(int i = limit; i >= 0; i--)
   {
      FastEMABuffer[i] = iMA(NULL, 0, InpFastEMALen, 0, MODE_EMA, PRICE_CLOSE, i);
      SlowEMABuffer[i] = iMA(NULL, 0, InpSlowEMALen, 0, MODE_EMA, PRICE_CLOSE, i);
   }

   // 2. Rebuild Zones on new bar or first run
   static datetime lastBarTime = 0;
   if(Time[0] != lastBarTime || prev_calculated == 0)
   {
      lastBarTime = Time[0];
      ObjectsDeleteAll(0, PREFIX);

      int scanLimit = MathMin(rates_total - 1, InpMaxBars);
      int activeState = 0; // 1 = Long, -1 = Short, 0 = None
      datetime zoneStart = 0;
      int zoneIdx = 0;

      for(int i = scanLimit; i >= 0; i--)
      {
         datetime barTime = Time[i];
         bool inSession = IsInSession(barTime);

         int htfShift = iBarShift(NULL, InpHTF, barTime, false);
         double htfFast = iMA(NULL, InpHTF, InpFastEMALen, 0, MODE_EMA, PRICE_CLOSE, htfShift);
         double htfSlow = iMA(NULL, InpHTF, InpSlowEMALen, 0, MODE_EMA, PRICE_CLOSE, htfShift);

         int ltfShift = iBarShift(NULL, InpLTF, barTime, false);
         double ltfFast = iMA(NULL, InpLTF, InpFastEMALen, 0, MODE_EMA, PRICE_CLOSE, ltfShift);
         double ltfSlow = iMA(NULL, InpLTF, InpSlowEMALen, 0, MODE_EMA, PRICE_CLOSE, ltfShift);

         int state = 0;
         if(inSession && (htfFast > htfSlow) && (ltfFast > ltfSlow))
            state = 1;
         else if(inSession && (htfFast < htfSlow) && (ltfFast < ltfSlow))
            state = -1;

         if(state != activeState)
         {
            if(activeState != 0)
            {
               string name = PREFIX + IntegerToString(zoneIdx++);
               datetime zoneEnd = Time[i + 1];
               color c = (activeState == 1) ? InpLongColor : InpShortColor;
               DrawZoneRect(name, zoneStart, zoneEnd, c);
            }
            activeState = state;
            zoneStart = barTime;
         }
      }

      // Draw current live zone up to projected bar close
      if(activeState != 0)
      {
         string liveName = PREFIX + "LIVE";
         datetime zoneEnd = Time[0] + PeriodSeconds();
         color c = (activeState == 1) ? InpLongColor : InpShortColor;
         DrawZoneRect(liveName, zoneStart, zoneEnd, c);
      }
      ChartRedraw(0);
   }

   return rates_total;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

2. MetaTrader 5 (MQL5)

Save as APlusAlignmentFilter.mq5 in your MQL5/Indicators/ folder.

Code: Select all

//+------------------------------------------------------------------+
//|                                        APlusAlignmentFilter.mq5  |
//|                                    A+ Setup Alignment Gatekeeper |
//+------------------------------------------------------------------+
#property copyright "Forex Scalping"
#property link      ""
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2
#property indicator_label1  "Fast EMA"

#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDarkOrange
#property indicator_width2  2
#property indicator_label2  "Slow EMA"

//--- Inputs
input string          InpSessionTime  = "08:00-11:00";   // Trading Session (HH:MM-HH:MM)
input ENUM_TIMEFRAMES InpHTF          = PERIOD_H1;       // Higher Timeframe (HTF)
input ENUM_TIMEFRAMES InpLTF          = PERIOD_M15;      // Intermediate Timeframe (LTF)
input int             InpFastEMALen   = 9;               // Fast EMA Period
input int             InpSlowEMALen   = 21;              // Slow EMA Period
input int             InpMaxBars      = 1500;            // Max Bars for Zone Shading
input color           InpLongColor    = C'20, 45, 30';   // A+ Long Shading (Dark Theme)
input color           InpShortColor   = C'50, 20, 25';   // A+ Short Shading (Dark Theme)

//--- Buffers
double FastEMABuffer[];
double SlowEMABuffer[];

//--- Indicator Handles
int hChartFast = INVALID_HANDLE;
int hChartSlow = INVALID_HANDLE;
int hHtfFast   = INVALID_HANDLE;
int hHtfSlow   = INVALID_HANDLE;
int hLtfFast   = INVALID_HANDLE;
int hLtfSlow   = INVALID_HANDLE;

int    startMinutes = 0;
int    endMinutes   = 0;
const string PREFIX = "APLUS5_";

//+------------------------------------------------------------------+
//| Session Parser                                                   |
//+------------------------------------------------------------------+
bool ParseSession(string sessionStr, int &sMin, int &eMin)
{
   string parts[];
   if(StringSplit(sessionStr, '-', parts) != 2) return false;

   string sParts[], eParts[];
   if(StringSplit(parts[0], ':', sParts) != 2 || StringSplit(parts[1], ':', eParts) != 2) return false;

   sMin = (int)StringToInteger(sParts[0]) * 60 + (int)StringToInteger(sParts[1]);
   eMin = (int)StringToInteger(eParts[0]) * 60 + (int)StringToInteger(eParts[1]);
   return true;
}

bool IsInSession(datetime t)
{
   MqlDateTime dt;
   TimeToStruct(t, dt);
   int curMin = dt.hour * 60 + dt.min;
   if(startMinutes <= endMinutes)
      return (curMin >= startMinutes && curMin < endMinutes);
   else
      return (curMin >= startMinutes || curMin < endMinutes);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, FastEMABuffer, INDICATOR_DATA);
   SetIndexBuffer(1, SlowEMABuffer, INDICATOR_DATA);

   if(!ParseSession(InpSessionTime, startMinutes, endMinutes))
   {
      Print("Invalid session format. Use HH:MM-HH:MM");
      return INIT_PARAMETERS_INCORRECT;
   }

   // Initialize Indicator Handles
   hChartFast = iMA(_Symbol, _Period, InpFastEMALen, 0, MODE_EMA, PRICE_CLOSE);
   hChartSlow = iMA(_Symbol, _Period, InpSlowEMALen, 0, MODE_EMA, PRICE_CLOSE);
   hHtfFast   = iMA(_Symbol, InpHTF, InpFastEMALen, 0, MODE_EMA, PRICE_CLOSE);
   hHtfSlow   = iMA(_Symbol, InpHTF, InpSlowEMALen, 0, MODE_EMA, PRICE_CLOSE);
   hLtfFast   = iMA(_Symbol, InpLTF, InpFastEMALen, 0, MODE_EMA, PRICE_CLOSE);
   hLtfSlow   = iMA(_Symbol, InpLTF, InpSlowEMALen, 0, MODE_EMA, PRICE_CLOSE);

   if(hChartFast == INVALID_HANDLE || hChartSlow == INVALID_HANDLE ||
      hHtfFast   == INVALID_HANDLE || hHtfSlow   == INVALID_HANDLE ||
      hLtfFast   == INVALID_HANDLE || hLtfSlow   == INVALID_HANDLE)
   {
      Print("Error creating indicator handles.");
      return INIT_FAILED;
   }

   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, PREFIX);
   IndicatorRelease(hChartFast);
   IndicatorRelease(hChartSlow);
   IndicatorRelease(hHtfFast);
   IndicatorRelease(hHtfSlow);
   IndicatorRelease(hLtfFast);
   IndicatorRelease(hLtfSlow);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Rectangle Drawer                                                 |
//+------------------------------------------------------------------+
void DrawZoneRect(string name, datetime t1, datetime t2, color clr)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, -100000.0, t2, 2000000.0);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_FILL, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   }
   else
   {
      ObjectSetInteger(0, name, OBJPROP_TIME, 0, t1);
      ObjectSetInteger(0, name, OBJPROP_TIME, 1, t2);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
   if(rates_total <= InpSlowEMALen) return 0;

   // 1. Sync Chart Overlay EMAs
   int copyCount = rates_total - prev_calculated + 1;
   if(prev_calculated == 0) copyCount = rates_total;

   if(CopyBuffer(hChartFast, 0, 0, copyCount, FastEMABuffer) <= 0 ||
      CopyBuffer(hChartSlow, 0, 0, copyCount, SlowEMABuffer) <= 0)
      return 0;

   // 2. Rebuild Zones on New Bar or Initial Load
   static datetime lastBarTime = 0;
   if(time[rates_total - 1] != lastBarTime || prev_calculated == 0)
   {
      lastBarTime = time[rates_total - 1];
      ObjectsDeleteAll(0, PREFIX);

      int startIdx = MathMax(0, rates_total - InpMaxBars);
      int activeState = 0;
      datetime zoneStart = 0;
      int zoneIdx = 0;

      double hFast[1], hSlow[1], lFast[1], lSlow[1];

      for(int i = startIdx; i < rates_total; i++)
      {
         datetime barTime = time[i];
         bool inSession = IsInSession(barTime);

         if(CopyBuffer(hHtfFast, 0, barTime, 1, hFast) <= 0) continue;
         if(CopyBuffer(hHtfSlow, 0, barTime, 1, hSlow) <= 0) continue;
         if(CopyBuffer(hLtfFast, 0, barTime, 1, lFast) <= 0) continue;
         if(CopyBuffer(hLtfSlow, 0, barTime, 1, lSlow) <= 0) continue;

         int state = 0;
         if(inSession && (hFast[0] > hSlow[0]) && (lFast[0] > lSlow[0]))
            state = 1;
         else if(inSession && (hFast[0] < hSlow[0]) && (lFast[0] < lSlow[0]))
            state = -1;

         if(state != activeState)
         {
            if(activeState != 0)
            {
               string name = PREFIX + IntegerToString(zoneIdx++);
               datetime zoneEnd = time[i];
               color c = (activeState == 1) ? InpLongColor : InpShortColor;
               DrawZoneRect(name, zoneStart, zoneEnd, c);
            }
            activeState = state;
            zoneStart = barTime;
         }
      }

      // Draw active bar zone
      if(activeState != 0)
      {
         string liveName = PREFIX + "LIVE";
         datetime zoneEnd = time[rates_total - 1] + PeriodSeconds();
         color c = (activeState == 1) ? InpLongColor : InpShortColor;
         DrawZoneRect(liveName, zoneStart, zoneEnd, c);
      }
      ChartRedraw(0);
   }

   return rates_total;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

Usage Notes

Color Palette: The default zone colors (C'20, 45, 30' and C'50, 20, 25') are calibrated for dark chart backgrounds so candle wicks remain crisp. If you trade on a light background, switch them in the properties dialog to soft pastels such as C'225, 245, 225' and C'255, 225, 225'.

Execution Timeframes: Attach either indicator directly to your preferred trigger chart (e.g., M1, M2, or M5). It queries H1 and M15 in the background regardless of what timeframe is active on your screen.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

Here is the complete cTrader (cAlgo API) implementation.

This version runs highly optimized. To keep the platform fast when handling multiple timeframes, it caches the background shading zones and updates them seamlessly. It also hooks into the chart's scrolling and zooming events to dynamically stretch the shaded areas so they don't break your chart's auto-scale settings.

cTrader / C# Implementation

Save this directly in cTrader's Automate tab as a new Indicator named APlusAlignmentFilter.

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class APlusAlignmentFilter : Indicator
    {
        [Parameter("Session Start (HH:mm)", DefaultValue = "08:00", Group = "Session")]
        public string SessionStart { get; set; }

        [Parameter("Session End (HH:mm)", DefaultValue = "11:00", Group = "Session")]
        public string SessionEnd { get; set; }

        [Parameter("Higher Timeframe (HTF)", DefaultValue = "Hour", Group = "Timeframes")]
        public TimeFrame Htf { get; set; }

        [Parameter("Intermediate Timeframe (LTF)", DefaultValue = "Minute15", Group = "Timeframes")]
        public TimeFrame Ltf { get; set; }

        [Parameter("Fast EMA Length", DefaultValue = 9, Group = "Moving Averages")]
        public int FastEmaLength { get; set; }

        [Parameter("Slow EMA Length", DefaultValue = 21, Group = "Moving Averages")]
        public int SlowEmaLength { get; set; }

        [Parameter("Max Bars for Shading", DefaultValue = 1500, Group = "Visuals")]
        public int MaxBars { get; set; }

        [Parameter("Long Zone Color", DefaultValue = "SeaGreen", Group = "Visuals")]
        public string LongColorStr { get; set; }

        [Parameter("Short Zone Color", DefaultValue = "Firebrick", Group = "Visuals")]
        public string ShortColorStr { get; set; }

        [Parameter("Zone Opacity (0-255)", DefaultValue = 40, MinValue = 0, MaxValue = 255, Group = "Visuals")]
        public int ZoneOpacity { get; set; }

        [Output("Fast EMA", LineColor = "DodgerBlue", Thickness = 2)]
        public IndicatorDataSeries ChartFast { get; set; }

        [Output("Slow EMA", LineColor = "DarkOrange", Thickness = 2)]
        public IndicatorDataSeries ChartSlow { get; set; }

        // Internals
        private ExponentialMovingAverage _chartFast;
        private ExponentialMovingAverage _chartSlow;

        private ExponentialMovingAverage _htfFast;
        private ExponentialMovingAverage _htfSlow;
        private ExponentialMovingAverage _ltfFast;
        private ExponentialMovingAverage _ltfSlow;

        private Bars _htfBars;
        private Bars _ltfBars;

        private Color _longColor;
        private Color _shortColor;
        private TimeSpan _sessionStart;
        private TimeSpan _sessionEnd;

        private int _drawnZonesCount = 0;
        private int _lastTickState = 0;
        private DateTime _lastBarTime;

        protected override void Initialize()
        {
            // 1. Parse Session Times
            if (!TimeSpan.TryParse(SessionStart, out _sessionStart) || !TimeSpan.TryParse(SessionEnd, out _sessionEnd))
            {
                Print("Invalid session time format. Use HH:mm (e.g., 08:00)");
            }

            // 2. Parse Colors safely
            Color parsedLong = Color.FromName(LongColorStr);
            _longColor = Color.FromArgb(ZoneOpacity, parsedLong.R, parsedLong.G, parsedLong.B);

            Color parsedShort = Color.FromName(ShortColorStr);
            _shortColor = Color.FromArgb(ZoneOpacity, parsedShort.R, parsedShort.G, parsedShort.B);

            // 3. Request Multi-Timeframe Data
            _htfBars = MarketData.GetBars(Htf);
            _ltfBars = MarketData.GetBars(Ltf);

            // 4. Initialize EMAs
            _chartFast = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastEmaLength);
            _chartSlow = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowEmaLength);

            _htfFast = Indicators.ExponentialMovingAverage(_htfBars.ClosePrices, FastEmaLength);
            _htfSlow = Indicators.ExponentialMovingAverage(_htfBars.ClosePrices, SlowEmaLength);

            _ltfFast = Indicators.ExponentialMovingAverage(_ltfBars.ClosePrices, FastEmaLength);
            _ltfSlow = Indicators.ExponentialMovingAverage(_ltfBars.ClosePrices, SlowEmaLength);

            // 5. Subscribe to chart changes so rectangles always span top to bottom
            Chart.ScrollChanged += Chart_VisualsChanged;
            Chart.ZoomChanged += Chart_VisualsChanged;
            Chart.SizeChanged += Chart_VisualsChanged;
        }

        public override void Calculate(int index)
        {
            // Standard chart EMAs
            ChartFast[index] = _chartFast.Result[index];
            ChartSlow[index] = _chartSlow.Result[index];

            if (index < SlowEmaLength) return;

            if (IsHistorical)
            {
                // Defer background drawing until history is fully loaded for instant startup
                if (index == Bars.Count - 1)
                {
                    RebuildZones(index);
                }
            }
            else
            {
                // Live tick optimization: only scan when the state flips or a new bar prints
                int state = GetState(Bars.OpenTimes[index], out bool valid);
                if (state != _lastTickState || Bars.OpenTimes[index] != _lastBarTime)
                {
                    RebuildZones(index);
                    _lastTickState = state;
                    _lastBarTime = Bars.OpenTimes[index];
                }
            }
        }

        private void RebuildZones(int currentIndex)
        {
            int scanLimit = Math.Max(0, currentIndex - MaxBars);
            int activeState = 0;
            DateTime zoneStart = DateTime.MinValue;
            int zoneIdx = 0;

            for (int i = scanLimit; i <= currentIndex; i++)
            {
                var barTime = Bars.OpenTimes[i];
                int state = GetState(barTime, out bool valid);
                
                if (!valid) continue;

                if (state != activeState)
                {
                    if (activeState != 0)
                    {
                        string name = "APLUS_" + zoneIdx;
                        DrawZone(name, zoneStart, barTime, activeState == 1 ? _longColor : _shortColor);
                        zoneIdx++;
                    }
                    activeState = state;
                    zoneStart = barTime;
                }
            }

            if (activeState != 0)
            {
                // Project the live active zone forward so it highlights the forming bar completely
                string name = "APLUS_LIVE";
                DateTime endTime = Bars.OpenTimes[currentIndex].Add(Bars.TimeFrame.TimeSpan);
                DrawZone(name, zoneStart, endTime, activeState == 1 ? _longColor : _shortColor);
            }
            else
            {
                Chart.RemoveObject("APLUS_LIVE");
            }

            // Cleanup trailing zones if they successfully merged together
            for (int i = zoneIdx; i < _drawnZonesCount; i++)
            {
                Chart.RemoveObject("APLUS_" + i);
            }
            
            _drawnZonesCount = zoneIdx;
        }

        private void DrawZone(string name, DateTime start, DateTime end, Color color)
        {
            var rect = Chart.DrawRectangle(name, start, Chart.BottomY, end, Chart.TopY, color);
            rect.IsFilled = true;
            rect.Color = color;
            rect.LineColor = Color.Transparent;
            rect.IsInteractive = false;
        }

        private int GetState(DateTime barTime, out bool valid)
        {
            valid = true;
            if (!IsInSession(barTime)) return 0;

            int htfIndex = _htfBars.OpenTimes.GetIndexByTime(barTime);
            int ltfIndex = _ltfBars.OpenTimes.GetIndexByTime(barTime);

            // Gate check: Has the higher timeframe generated enough data yet?
            if (htfIndex < 0 || ltfIndex < 0) 
            {
                valid = false;
                return 0;
            }

            double hFast = _htfFast.Result[htfIndex];
            double hSlow = _htfSlow.Result[htfIndex];
            double lFast = _ltfFast.Result[ltfIndex];
            double lSlow = _ltfSlow.Result[ltfIndex];

            if (double.IsNaN(hFast) || double.IsNaN(hSlow) || double.IsNaN(lFast) || double.IsNaN(lSlow)) 
            {
                valid = false;
                return 0;
            }

            if (hFast > hSlow && lFast > lSlow) return 1;
            if (hFast < hSlow && lFast < lSlow) return -1;
            
            return 0; // Alignment broken
        }

        private bool IsInSession(DateTime time)
        {
            var timeOfDay = time.TimeOfDay;
            if (_sessionStart <= _sessionEnd)
                return timeOfDay >= _sessionStart && timeOfDay <= _sessionEnd;
            else
                // Cross-midnight handling (e.g. 22:00 to 02:00)
                return timeOfDay >= _sessionStart || timeOfDay <= _sessionEnd; 
        }

        private void Chart_VisualsChanged(ChartEventArgs obj)
        {
            // Ensures the background zones resize natively with your price scaling
            var bottom = Chart.BottomY;
            var top = Chart.TopY;
            
            foreach (var rect in Chart.FindAllObjects<ChartRectangle>())
            {
                if (rect.Name.StartsWith("APLUS_"))
                {
                    rect.Y1 = bottom;
                    rect.Y2 = top;
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

Here is a production-grade Pine Script v5 implementation.

To rebuild this "like a pro," the code needs to move past basic scripting and incorporate best practices used in institutional scripts:

Tuple-Based Security Calls: Calling request.security multiple times for the same timeframe drains server resources and hits Pine’s limits. We package the fast and slow EMAs into a tuple array to fetch both in a single query.

Heads-Up Display (HUD): A responsive, non-intrusive table in the corner of the screen so you don't have to guess why the background isn't shaded (e.g., checking if it's the HTF or LTF that is out of alignment).

Decoupled Execution Timeframe: It explicitly separates the HTF (e.g., 1H) and LTF (e.g., 15m) from your execution chart. You can scalp on the 1m or 3m chart, and it will dynamically reference the correct macro timeframes in the background.

State-Change Alerts: Built-in alertcondition triggers that fire precisely when your session permission opens and timeframes align, or when alignment is lost.

Pine Script v5 Pro Implementation

Code: Select all

//@version=5
indicator("A+ Alignment Filter [PRO]", shorttitle="A+ Filter", overlay=true, timeframe="", timeframe_gaps=true)

// ==============================================================================
// 1. CONSTANTS & GROUPS
// ==============================================================================
var string GRP_TIME = "Session & Timeframes (Gatekeepers)"
var string GRP_MA   = "Moving Averages"
var string GRP_VIS  = "Visuals & UI"

// ==============================================================================
// 2. INPUTS
// ==============================================================================
i_session   = input.session("0800-1100", title="A+ Trading Session", group=GRP_TIME, tooltip="Time window where trades are permitted.")
i_htf       = input.timeframe("60", title="Higher Timeframe (HTF)", group=GRP_TIME)
i_ltf       = input.timeframe("15", title="Intermediate Timeframe (LTF)", group=GRP_TIME)

i_fastEma   = input.int(9, title="Fast EMA", minval=1, group=GRP_MA, inline="ema")
i_slowEma   = input.int(21, title="Slow EMA", minval=1, group=GRP_MA, inline="ema")

i_bgLong    = input.color(color.new(color.teal, 85), title="Long Zone", group=GRP_VIS, inline="bg")
i_bgShort   = input.color(color.new(color.maroon, 85), title="Short Zone", group=GRP_VIS, inline="bg")
i_showTable = input.bool(true, title="Show HUD Dashboard", group=GRP_VIS)

// ==============================================================================
// 3. FUNCTIONS & MTF DATA FETCHING
// ==============================================================================
// Calculate EMAs and return as a tuple. 
// Passed directly into request.security to halve the required server calls.
f_emas() =>
    fast = ta.ema(close, i_fastEma)
    slow = ta.ema(close, i_slowEma)
    [fast, slow]

// Fetch HTF and LTF using lookahead_ignore to prevent historical repainting
[htfFast, htfSlow] = request.security(syminfo.tickerid, i_htf, f_emas(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[ltfFast, ltfSlow] = request.security(syminfo.tickerid, i_ltf, f_emas(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)

// Fetch execution chart EMAs for plotting
[chartFast, chartSlow] = f_emas()

// ==============================================================================
// 4. CORE LOGIC & ALIGNMENT
// ==============================================================================
htfBull = htfFast > htfSlow
htfBear = htfFast < htfSlow

ltfBull = ltfFast > ltfSlow
ltfBear = ltfFast < ltfSlow

// Session Validation (syncs with the chart's active timezone to prevent offset bugs)
inSession = not na(time(timeframe.period, i_session, syminfo.timezone))

// Final A+ States
isAPlusLong  = inSession and htfBull and ltfBull
isAPlusShort = inSession and htfBear and ltfBear

// ==============================================================================
// 5. VISUALS & DRAWING
// ==============================================================================
// Background Shading
bgcolor(isAPlusLong ? i_bgLong : isAPlusShort ? i_bgShort : na, title="A+ Zone Shading")

// Plot Execution Chart EMAs
plot(chartFast, title="Chart Fast EMA", color=color.new(color.aqua, 0), linewidth=2)
plot(chartSlow, title="Chart Slow EMA", color=color.new(color.orange, 0), linewidth=2)

// ==============================================================================
// 6. HUD DASHBOARD (Real-Time State Table)
// ==============================================================================
var table hud = table.new(position.top_right, 2, 4, border_width=1, border_color=color.new(color.gray, 80), frame_color=color.new(color.gray, 80), frame_width=1)

if barstate.islast and i_showTable
    // Row 0: Headers
    table.cell(hud, 0, 0, "A+ GATE", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    table.cell(hud, 1, 0, "STATE", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    
    // Row 1: Session Clock
    table.cell(hud, 0, 1, "Session", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 1, inSession ? "ACTIVE" : "CLOSED", text_color=inSession ? color.lime : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    // Row 2: HTF Alignment
    table.cell(hud, 0, 2, "HTF (" + i_htf + ")", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 2, htfBull ? "BULL" : htfBear ? "BEAR" : "FLAT", text_color=htfBull ? color.lime : htfBear ? color.red : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    // Row 3: LTF Alignment
    table.cell(hud, 0, 3, "LTF (" + i_ltf + ")", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 3, ltfBull ? "BULL" : ltfBear ? "BEAR" : "FLAT", text_color=ltfBull ? color.lime : ltfBear ? color.red : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)

// ==============================================================================
// 7. ALERTS
// ==============================================================================
alertcondition(isAPlusLong and not isAPlusLong[1], title="A+ Long Opened", message="A+ Long alignment achieved. Session active.")
alertcondition(isAPlusShort and not isAPlusShort[1], title="A+ Short Opened", message="A+ Short alignment achieved. Session active.")
alertcondition((not isAPlusLong and isAPlusLong[1]) or (not isAPlusShort and isAPlusShort[1]), title="A+ Alignment Lost", message="Alignment broken or session closed. C-grade zone.")
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

To elevate the framework to an institutional execution standard, we must mechanize the remaining qualitative rules from your grading system: Risk Pre-Sizing, Anti-Chasing (Location), and Trigger at the Level.

This Pine Script v5 Pro+ architecture introduces three critical upgrades:

The Extension Guard: Measures real-time distance from the EMA. If price is extended beyond a calculated ATR threshold, it immediately flags the setup as "Chasing" and blocks the A+ grade.

The Value Pocket Trigger: Requires price to pull back and touch the space between the Fast and Slow EMAs (the value zone) before signaling a valid entry.

Live Risk Engine: Calculates precise position sizing (in units) directly on the chart based on your account equity, risk percentage, and dynamic ATR stop-loss distance.

Pine Script v5: Institutional Execution Framework

Code: Select all

//@version=5
indicator("Institutional Execution Framework [PRO+]", shorttitle="A+ Exec PRO+", overlay=true, timeframe="", timeframe_gaps=true)

// ==============================================================================
// 1. INPUT GROUPS & PARAMETERS
// ==============================================================================
var string GRP_TIME = "1. Session & Timeframes"
var string GRP_MA   = "2. Trend Alignment"
var string GRP_FILT = "3. Location & Anti-Chasing"
var string GRP_RISK = "4. Risk Pre-Sizing Engine"

i_sessTime  = input.session("0800-1100", "A+ Session Window", group=GRP_TIME)
i_htf       = input.timeframe("60", "Higher Timeframe", group=GRP_TIME)
i_ltf       = input.timeframe("15", "Lower Timeframe", group=GRP_TIME)

i_emaFast   = input.int(9, "Fast EMA", group=GRP_MA)
i_emaSlow   = input.int(21, "Slow EMA", group=GRP_MA)

i_maxAtrExt = input.float(2.0, "Max ATR Extension", step=0.1, group=GRP_FILT, tooltip="Disqualifies trades if price is further than X ATRs from the Slow EMA (Prevents FOMO).")

i_accSize   = input.float(50000, "Account Balance ($)", group=GRP_RISK)
i_riskPct   = input.float(1.0, "Risk Per Trade (%)", step=0.1, group=GRP_RISK)
i_slAtrMult = input.float(1.5, "Stop Loss (ATR Multiplier)", step=0.1, group=GRP_RISK)

// ==============================================================================
// 2. DATA FETCHING (TUPLE OPTIMIZED)
// ==============================================================================
f_ema() => [ta.ema(close, i_emaFast), ta.ema(close, i_emaSlow)]

[htfF, htfS] = request.security(syminfo.tickerid, i_htf, f_ema(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[ltfF, ltfS] = request.security(syminfo.tickerid, i_ltf, f_ema(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[chartF, chartS] = f_ema()

// ==============================================================================
// 3. CORE LOGIC (ALIGNMENT, LOCATION, PULLBACK)
// ==============================================================================
inSess = not na(time(timeframe.period, i_sessTime, syminfo.timezone))
atr = ta.atr(14)
distToSlow = math.abs(close - chartS)

// Trend States
htfBull = htfF > htfS, ltfBull = ltfF > ltfS, chartBull = chartF > chartS
htfBear = htfF < htfS, ltfBear = ltfF < ltfS, chartBear = chartF < chartS

bullAligned = htfBull and ltfBull and chartBull
bearAligned = htfBear and ltfBear and chartBear

// Location & Chasing Guard
isExtended = distToSlow > (atr * i_maxAtrExt)

// Trigger at Level (Price must test the EMA Value Pocket)
inValueLong  = low <= chartF and close > chartS
inValueShort = high >= chartF and close < chartS

// Final Grading
isAPlusLong  = inSess and bullAligned and not isExtended and inValueLong
isAPlusShort = inSess and bearAligned and not isExtended and inValueShort

// ==============================================================================
// 4. RISK & POSITION SIZING CALCULATOR
// ==============================================================================
riskDollars = i_accSize * (i_riskPct / 100)
slDistance  = atr * i_slAtrMult

// Calculates standard units based on ticker point value
calcUnits = riskDollars / (slDistance * syminfo.pointvalue)
units = na(calcUnits) or calcUnits == 0 ? 0 : calcUnits

// ==============================================================================
// 5. VISUALS & HUD
// ==============================================================================
bgcolor(isAPlusLong ? color.new(color.teal, 85) : isAPlusShort ? color.new(color.maroon, 85) : na, title="A+ Zone Shading")

plot(chartF, "Fast EMA", color=color.new(color.aqua, 0), linewidth=2)
plot(chartS, "Slow EMA", color=color.new(color.orange, 0), linewidth=2)

var table hud = table.new(position.top_right, 2, 7, border_width=1, border_color=color.new(color.gray, 80), frame_color=color.new(color.gray, 80), frame_width=1)

if barstate.islast
    // Headers
    table.cell(hud, 0, 0, "EXECUTION METRIC", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    table.cell(hud, 1, 0, "LIVE STATUS", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    
    // Gate 1: Session
    table.cell(hud, 0, 1, "Session Window", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 1, inSess ? "ACTIVE" : "CLOSED", text_color=inSess ? color.lime : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    // Gate 2: Trend Alignment
    table.cell(hud, 0, 2, "MTF Alignment", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 2, bullAligned ? "BULLISH" : bearAligned ? "BEARISH" : "MIXED", text_color=bullAligned ? color.lime : bearAligned ? color.red : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    // Gate 3: Location / Chasing
    table.cell(hud, 0, 3, "Location Guard", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 3, isExtended ? "CHASING (EXTENDED)" : "WITHIN VALUE", text_color=isExtended ? color.red : color.lime, bgcolor=color.new(color.black, 60), text_size=size.small)

    // Grade Output
    string gradeTxt = isAPlusLong or isAPlusShort ? "A+ TRIGGER READY" : (bullAligned or bearAligned) and not isExtended ? "WAITING PULLBACK" : isExtended ? "BLOCKED (C-RISK)" : "NO SETUP"
    color gradeClr = isAPlusLong or isAPlusShort ? color.yellow : (bullAligned or bearAligned) and not isExtended ? color.orange : color.gray
    table.cell(hud, 0, 4, "SYSTEM GRADE", text_color=color.white, bgcolor=color.new(color.blue, 70), text_size=size.small)
    table.cell(hud, 1, 4, gradeTxt, text_color=gradeClr, bgcolor=color.new(color.blue, 70), text_size=size.small)

    // Risk Parameters
    table.cell(hud, 0, 5, "Risk Allocation", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 5, "$" + str.tostring(riskDollars, "#.##") + " | " + str.tostring(slDistance / syminfo.mintick, "#") + " ticks", text_color=color.white, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    // Final Sizing Output
    table.cell(hud, 0, 6, "PRE-SIZED UNITS", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 6, str.tostring(units, "#.##"), text_color=color.aqua, bgcolor=color.new(color.black, 60), text_size=size.normal)

// ==============================================================================
// 6. JSON WEBHOOK ALERTS
// ==============================================================================
string jsonLong  = '{"ticker": "' + syminfo.ticker + '", "action": "buy", "units": "' + str.tostring(units, "#.##") + '", "sl_dist": "' + str.tostring(slDistance, "#.#####") + '"}'
string jsonShort = '{"ticker": "' + syminfo.ticker + '", "action": "sell", "units": "' + str.tostring(units, "#.##") + '", "sl_dist": "' + str.tostring(slDistance, "#.#####") + '"}'

alertcondition(isAPlusLong and not isAPlusLong[1], "A+ Long Trigger", message=jsonLong)
alertcondition(isAPlusShort and not isAPlusShort[1], "A+ Short Trigger", message=jsonShort)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

We have to solve a unique logic problem: the indicator naturally filters out C-grade setups, so it can't automatically know if you took a bad trade off-script.

To fix this, we introduce Circuit Breakers.

The Virtual Loss Tracker (Automated): The script acts as a virtual broker. When an A+ signal fires, it opens a "virtual trade" and tracks the price. If price hits the ATR stop-loss, it increments your daily loss count.

The Confessional Tilt Counter (Manual): An input setting in the indicator properties. If you break your rules and take a C-grade trade (like chasing an extended move), you manually increment this input.

If either the daily A+ losses or the C-grade tilt counter hits your max threshold, the script triggers the circuit breaker. It halts all alerts, shades the dashboard red, and forces the "soft mental stop" mentioned in your forum rules.

Pine Script v5: Circuit Breaker Update

Replace your previous code with this complete, updated version.

Code: Select all

//@version=5
indicator("Institutional Execution Framework [PRO+]", shorttitle="A+ Exec PRO+", overlay=true, timeframe="", timeframe_gaps=true)

// ==============================================================================
// 1. INPUT GROUPS & PARAMETERS
// ==============================================================================
var string GRP_TIME = "1. Session & Timeframes"
var string GRP_MA   = "2. Trend Alignment"
var string GRP_FILT = "3. Location & Anti-Chasing"
var string GRP_RISK = "4. Risk Pre-Sizing Engine"
var string GRP_CB   = "5. Circuit Breakers (Tilt & Loss)"

i_sessTime  = input.session("0800-1100", "A+ Session Window", group=GRP_TIME)
i_htf       = input.timeframe("60", "Higher Timeframe", group=GRP_TIME)
i_ltf       = input.timeframe("15", "Lower Timeframe", group=GRP_TIME)

i_emaFast   = input.int(9, "Fast EMA", group=GRP_MA)
i_emaSlow   = input.int(21, "Slow EMA", group=GRP_MA)

i_maxAtrExt = input.float(2.0, "Max ATR Extension", step=0.1, group=GRP_FILT, tooltip="Disqualifies trades if price is further than X ATRs from the Slow EMA.")

i_accSize   = input.float(50000, "Account Balance ($)", group=GRP_RISK)
i_riskPct   = input.float(1.0, "Risk Per Trade (%)", step=0.1, group=GRP_RISK)
i_slAtrMult = input.float(1.5, "Stop Loss (ATR Multiplier)", step=0.1, group=GRP_RISK)

i_maxLosses = input.int(2, "Max Daily A+ Losses", group=GRP_CB)
i_maxCGrade = input.int(2, "Max C-Grade Tilt Limit", group=GRP_CB)
i_cGrades   = input.int(0, "Confessional: C-Grades Taken Today", group=GRP_CB, tooltip="Increment this manually if you took a FOMO trade off-script. Trips the breaker if it hits the limit.")
i_virtRR    = input.float(1.5, "Virtual TP (For Tracker Reset)", step=0.1, group=GRP_CB, tooltip="Reward-to-Risk ratio used to clear winning virtual trades so the script can track the next signal.")

// ==============================================================================
// 2. DATA FETCHING (TUPLE OPTIMIZED)
// ==============================================================================
f_ema() => [ta.ema(close, i_emaFast), ta.ema(close, i_emaSlow)]

[htfF, htfS] = request.security(syminfo.tickerid, i_htf, f_ema(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[ltfF, ltfS] = request.security(syminfo.tickerid, i_ltf, f_ema(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[chartF, chartS] = f_ema()

// ==============================================================================
// 3. CORE LOGIC (ALIGNMENT, LOCATION, PULLBACK)
// ==============================================================================
inSess = not na(time(timeframe.period, i_sessTime, syminfo.timezone))
atr = ta.atr(14)
distToSlow = math.abs(close - chartS)
slDistance = atr * i_slAtrMult

htfBull = htfF > htfS, ltfBull = ltfF > ltfS, chartBull = chartF > chartS
htfBear = htfF < htfS, ltfBear = ltfF < ltfS, chartBear = chartF < chartS

bullAligned = htfBull and ltfBull and chartBull
bearAligned = htfBear and ltfBear and chartBear

isExtended = distToSlow > (atr * i_maxAtrExt)

inValueLong  = low <= chartF and close > chartS
inValueShort = high >= chartF and close < chartS

// Base A+ Conditions
baseAPlusLong  = inSess and bullAligned and not isExtended and inValueLong
baseAPlusShort = inSess and bearAligned and not isExtended and inValueShort

// ==============================================================================
// 4. CIRCUIT BREAKER & VIRTUAL TRADE MANAGER
// ==============================================================================
var int dailyLosses = 0
if ta.change(time("D"))
    dailyLosses := 0 // Reset at midnight

var int vTradeDir = 0 // 1 = Long, -1 = Short, 0 = Flat
var float vSL = na
var float vTP = na

// Check Exits BEFORE Entries on current bar
if vTradeDir == 1
    if low <= vSL
        dailyLosses += 1
        vTradeDir := 0 // Stopped out
    else if high >= vTP
        vTradeDir := 0 // Target hit, clear state

if vTradeDir == -1
    if high >= vSL
        dailyLosses += 1
        vTradeDir := 0 // Stopped out
    else if low <= vTP
        vTradeDir := 0 // Target hit, clear state

// Circuit Breaker Evaluation
breakerTripped = (dailyLosses >= i_maxLosses) or (i_cGrades >= i_maxCGrade)

// Final Executable Signals
triggerLong  = baseAPlusLong and not breakerTripped and vTradeDir == 0
triggerShort = baseAPlusShort and not breakerTripped and vTradeDir == 0

// Execute Virtual Entry
if triggerLong
    vTradeDir := 1
    vSL := close - slDistance
    vTP := close + (slDistance * i_virtRR)
if triggerShort
    vTradeDir := -1
    vSL := close + slDistance
    vTP := close - (slDistance * i_virtRR)

// ==============================================================================
// 5. RISK & POSITION SIZING CALCULATOR
// ==============================================================================
riskDollars = i_accSize * (i_riskPct / 100)
calcUnits = riskDollars / (slDistance * syminfo.pointvalue)
units = na(calcUnits) or calcUnits == 0 ? 0 : calcUnits

// ==============================================================================
// 6. VISUALS & HUD
// ==============================================================================
bgcolor(breakerTripped ? color.new(color.red, 95) : triggerLong ? color.new(color.teal, 85) : triggerShort ? color.new(color.maroon, 85) : na, title="A+ Zone Shading")

plot(chartF, "Fast EMA", color=color.new(color.aqua, 0), linewidth=2)
plot(chartS, "Slow EMA", color=color.new(color.orange, 0), linewidth=2)

var table hud = table.new(position.top_right, 2, 8, border_width=1, border_color=color.new(color.gray, 80), frame_color=color.new(color.gray, 80), frame_width=1)

if barstate.islast
    // Headers
    table.cell(hud, 0, 0, "EXECUTION METRIC", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    table.cell(hud, 1, 0, "LIVE STATUS", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    
    // Status Gates
    table.cell(hud, 0, 1, "Session Window", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 1, inSess ? "ACTIVE" : "CLOSED", text_color=inSess ? color.lime : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    table.cell(hud, 0, 2, "MTF Alignment", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 2, bullAligned ? "BULLISH" : bearAligned ? "BEARISH" : "MIXED", text_color=bullAligned ? color.lime : bearAligned ? color.red : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    table.cell(hud, 0, 3, "Location Guard", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 3, isExtended ? "CHASING (EXTENDED)" : "WITHIN VALUE", text_color=isExtended ? color.red : color.lime, bgcolor=color.new(color.black, 60), text_size=size.small)

    // Circuit Breakers Tracker
    string cbStatus = "Loss: " + str.tostring(dailyLosses) + "/" + str.tostring(i_maxLosses) + " | C-Grade: " + str.tostring(i_cGrades) + "/" + str.tostring(i_maxCGrade)
    table.cell(hud, 0, 4, "Circuit Breakers", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 4, cbStatus, text_color=breakerTripped ? color.red : color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)

    // Grade Output
    string gradeTxt = breakerTripped ? "HALTED (BREAKER TRIPPED)" : vTradeDir != 0 ? "IN VIRTUAL TRADE" : (triggerLong or triggerShort) ? "A+ TRIGGER READY" : (bullAligned or bearAligned) and not isExtended ? "WAITING PULLBACK" : isExtended ? "BLOCKED (C-RISK)" : "NO SETUP"
    color gradeClr = breakerTripped ? color.red : vTradeDir != 0 ? color.yellow : (triggerLong or triggerShort) ? color.lime : (bullAligned or bearAligned) and not isExtended ? color.orange : color.gray
    table.cell(hud, 0, 5, "SYSTEM GRADE", text_color=color.white, bgcolor=breakerTripped ? color.new(color.red, 70) : color.new(color.blue, 70), text_size=size.small)
    table.cell(hud, 1, 5, gradeTxt, text_color=gradeClr, bgcolor=breakerTripped ? color.new(color.red, 70) : color.new(color.blue, 70), text_size=size.small)

    // Risk Parameters
    table.cell(hud, 0, 6, "Risk Allocation", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 6, "$" + str.tostring(riskDollars, "#.##") + " | " + str.tostring(slDistance / syminfo.mintick, "#") + " ticks", text_color=color.white, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    // Final Sizing Output
    table.cell(hud, 0, 7, "PRE-SIZED UNITS", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 7, str.tostring(units, "#.##"), text_color=color.aqua, bgcolor=color.new(color.black, 60), text_size=size.normal)

// ==============================================================================
// 7. WEBHOOK ALERTS (Disabled Automatically if Breaker Trips)
// ==============================================================================
string jsonLong  = '{"ticker": "' + syminfo.ticker + '", "action": "buy", "units": "' + str.tostring(units, "#.##") + '", "sl_dist": "' + str.tostring(slDistance, "#.#####") + '"}'
string jsonShort = '{"ticker": "' + syminfo.ticker + '", "action": "sell", "units": "' + str.tostring(units, "#.##") + '", "sl_dist": "' + str.tostring(slDistance, "#.#####") + '"}'

alertcondition(triggerLong, "A+ Long Trigger", message=jsonLong)
alertcondition(triggerShort, "A+ Short Trigger", message=jsonShort)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 1089
Joined: Mon Jul 20, 2026 1:28 pm

Re: Tagging A+/B/C setups: what my distribution taught me after 100 trades

Post by PTScalper »

To convert this indicator into a fully functional backtesting strategy, we need to make three fundamental changes to the Pine Script architecture:

Change the Declaration: Swap indicator() for strategy(). This unlocks Pine's built-in backtesting engine, equity tracking, and performance metrics.

Replace Virtual Tracking with Native Orders: We strip out the custom virtual trade manager and replace it with strategy.entry() and strategy.exit(). This allows the engine to natively track your stop-losses, take-profits, and position sizing.

Automate the Loss Circuit Breaker: Instead of simulating losses, we hook directly into the strategy.closedtrades array. The script will automatically count actual closed losses for the current day and halt trading if the limit is reached.

Here is the complete, drop-in Strategy script.

Code: Select all

//@version=5
strategy("Institutional Execution Framework [STRATEGY]", shorttitle="A+ Exec Strat", overlay=true, initial_capital=50000, default_qty_type=strategy.cash, calc_on_every_tick=true)

// ==============================================================================
// 1. INPUT GROUPS & PARAMETERS
// ==============================================================================
var string GRP_TIME = "1. Session & Timeframes"
var string GRP_MA   = "2. Trend Alignment"
var string GRP_FILT = "3. Location & Anti-Chasing"
var string GRP_RISK = "4. Risk Pre-Sizing Engine"
var string GRP_CB   = "5. Circuit Breakers (Tilt & Loss)"

i_sessTime  = input.session("0800-1100", "A+ Session Window", group=GRP_TIME)
i_htf       = input.timeframe("60", "Higher Timeframe", group=GRP_TIME)
i_ltf       = input.timeframe("15", "Lower Timeframe", group=GRP_TIME)

i_emaFast   = input.int(9, "Fast EMA", group=GRP_MA)
i_emaSlow   = input.int(21, "Slow EMA", group=GRP_MA)

i_maxAtrExt = input.float(2.0, "Max ATR Extension", step=0.1, group=GRP_FILT, tooltip="Disqualifies trades if price is further than X ATRs from the Slow EMA.")

i_accSize   = input.float(50000, "Account Balance ($)", group=GRP_RISK)
i_riskPct   = input.float(1.0, "Risk Per Trade (%)", step=0.1, group=GRP_RISK)
i_slAtrMult = input.float(1.5, "Stop Loss (ATR Multiplier)", step=0.1, group=GRP_RISK)

i_maxLosses = input.int(2, "Max Daily A+ Losses", group=GRP_CB)
i_maxCGrade = input.int(2, "Max C-Grade Tilt Limit", group=GRP_CB)
i_cGrades   = input.int(0, "Confessional: C-Grades Taken Today", group=GRP_CB)
i_stratRR   = input.float(1.5, "Take Profit (Reward/Risk Ratio)", step=0.1, group=GRP_CB)

// ==============================================================================
// 2. DATA FETCHING (TUPLE OPTIMIZED)
// ==============================================================================
f_ema() => [ta.ema(close, i_emaFast), ta.ema(close, i_emaSlow)]

[htfF, htfS] = request.security(syminfo.tickerid, i_htf, f_ema(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[ltfF, ltfS] = request.security(syminfo.tickerid, i_ltf, f_ema(), gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_ignore)
[chartF, chartS] = f_ema()

// ==============================================================================
// 3. CORE LOGIC (ALIGNMENT, LOCATION, PULLBACK)
// ==============================================================================
inSess = not na(time(timeframe.period, i_sessTime, syminfo.timezone))
atr = ta.atr(14)
distToSlow = math.abs(close - chartS)
slDistance = atr * i_slAtrMult

htfBull = htfF > htfS, ltfBull = ltfF > ltfS, chartBull = chartF > chartS
htfBear = htfF < htfS, ltfBear = ltfF < ltfS, chartBear = chartF < chartS

bullAligned = htfBull and ltfBull and chartBull
bearAligned = htfBear and ltfBear and chartBear

isExtended = distToSlow > (atr * i_maxAtrExt)

inValueLong  = low <= chartF and close > chartS
inValueShort = high >= chartF and close < chartS

baseAPlusLong  = inSess and bullAligned and not isExtended and inValueLong
baseAPlusShort = inSess and bearAligned and not isExtended and inValueShort

// ==============================================================================
// 4. CIRCUIT BREAKER (NATIVE STRATEGY INTEGRATION)
// ==============================================================================
var int dailyLosses = 0
if ta.change(time("D"))
    dailyLosses := 0 // Reset daily loss counter at midnight

// Automatically detect if the last closed strategy trade was a loser today
if strategy.closedtrades > nz(strategy.closedtrades[1])
    if strategy.closedtrades.profit(strategy.closedtrades - 1) < 0
        dailyLosses += 1

breakerTripped = (dailyLosses >= i_maxLosses) or (i_cGrades >= i_maxCGrade)
inTrade = strategy.position_size != 0

// Final Executable Signals
triggerLong  = baseAPlusLong and not breakerTripped and not inTrade
triggerShort = baseAPlusShort and not breakerTripped and not inTrade

// ==============================================================================
// 5. RISK SIZING & STRATEGY EXECUTION
// ==============================================================================
// Dynamically size the position based on live account equity (or fixed input)
riskDollars = i_accSize * (i_riskPct / 100)
calcUnits = riskDollars / (slDistance * syminfo.pointvalue)
units = na(calcUnits) or calcUnits == 0 ? 0 : calcUnits

// Execute trades with bracket orders natively attached
if triggerLong
    trade_sl = close - slDistance
    trade_tp = close + (slDistance * i_stratRR)
    strategy.entry("A+ Long", strategy.long, qty=units)
    strategy.exit("Exit Long", "A+ Long", stop=trade_sl, limit=trade_tp)

if triggerShort
    trade_sl = close + slDistance
    trade_tp = close - (slDistance * i_stratRR)
    strategy.entry("A+ Short", strategy.short, qty=units)
    strategy.exit("Exit Short", "A+ Short", stop=trade_sl, limit=trade_tp)

// ==============================================================================
// 6. VISUALS & HUD
// ==============================================================================
bgcolor(breakerTripped ? color.new(color.red, 95) : triggerLong ? color.new(color.teal, 85) : triggerShort ? color.new(color.maroon, 85) : na, title="A+ Zone Shading")

plot(chartF, "Fast EMA", color=color.new(color.aqua, 0), linewidth=2)
plot(chartS, "Slow EMA", color=color.new(color.orange, 0), linewidth=2)

var table hud = table.new(position.top_right, 2, 8, border_width=1, border_color=color.new(color.gray, 80), frame_color=color.new(color.gray, 80), frame_width=1)

if barstate.islast
    table.cell(hud, 0, 0, "STRATEGY METRIC", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    table.cell(hud, 1, 0, "LIVE STATUS", text_color=color.white, bgcolor=color.new(color.black, 20), text_size=size.small)
    
    table.cell(hud, 0, 1, "Session Window", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 1, inSess ? "ACTIVE" : "CLOSED", text_color=inSess ? color.lime : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    table.cell(hud, 0, 2, "MTF Alignment", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 2, bullAligned ? "BULLISH" : bearAligned ? "BEARISH" : "MIXED", text_color=bullAligned ? color.lime : bearAligned ? color.red : color.gray, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    table.cell(hud, 0, 3, "Location Guard", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 3, isExtended ? "CHASING (EXTENDED)" : "WITHIN VALUE", text_color=isExtended ? color.red : color.lime, bgcolor=color.new(color.black, 60), text_size=size.small)

    string cbStatus = "Loss: " + str.tostring(dailyLosses) + "/" + str.tostring(i_maxLosses) + " | C-Grade: " + str.tostring(i_cGrades) + "/" + str.tostring(i_maxCGrade)
    table.cell(hud, 0, 4, "Circuit Breakers", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 4, cbStatus, text_color=breakerTripped ? color.red : color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)

    string gradeTxt = breakerTripped ? "HALTED (BREAKER TRIPPED)" : inTrade ? "IN ACTIVE TRADE" : (triggerLong or triggerShort) ? "A+ TRIGGER READY" : (bullAligned or bearAligned) and not isExtended ? "WAITING PULLBACK" : isExtended ? "BLOCKED (C-RISK)" : "NO SETUP"
    color gradeClr = breakerTripped ? color.red : inTrade ? color.yellow : (triggerLong or triggerShort) ? color.lime : (bullAligned or bearAligned) and not isExtended ? color.orange : color.gray
    table.cell(hud, 0, 5, "SYSTEM GRADE", text_color=color.white, bgcolor=breakerTripped ? color.new(color.red, 70) : color.new(color.blue, 70), text_size=size.small)
    table.cell(hud, 1, 5, gradeTxt, text_color=gradeClr, bgcolor=breakerTripped ? color.new(color.red, 70) : color.new(color.blue, 70), text_size=size.small)

    table.cell(hud, 0, 6, "Risk Allocation", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 6, "$" + str.tostring(riskDollars, "#.##") + " | " + str.tostring(slDistance / syminfo.mintick, "#") + " ticks", text_color=color.white, bgcolor=color.new(color.black, 60), text_size=size.small)
    
    table.cell(hud, 0, 7, "PRE-SIZED UNITS", text_color=color.silver, bgcolor=color.new(color.black, 60), text_size=size.small)
    table.cell(hud, 1, 7, str.tostring(units, "#.##"), text_color=color.aqua, bgcolor=color.new(color.black, 60), text_size=size.normal)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply