preview
Building A Candlestick Trend Constraint Model (Part 11): Revealing Lower-Timeframe Triggers Within Higher-Timeframe Candle Relationships

Building A Candlestick Trend Constraint Model (Part 11): Revealing Lower-Timeframe Triggers Within Higher-Timeframe Candle Relationships

MetaTrader 5 — Examples |
171 0
Clemence Benjamin
Clemence Benjamin

A higher-timeframe candle gives us a broad view of price direction, but its final shape hides the sequence of lower-timeframe events. I returned to our candlestick research to make that sequence explicit. In this article, we develop an A-B-C-D model in which completed A-B candles select the directional rules, C provides the first qualifying setup, and D supplies a closed-bar confirmation window for optional execution.

Contents

  1. Objective and Scope
  2. The Four-Candle Constraint Model
  3. Classifying Candles A and B
  4. Scanning Trigger Bars Inside Candle C
  5. Confirmation and Invalidation Inside Candle D
  6. System Architecture
  7. Implementation
  8. Operating the Expert Advisor
  9. Planning Compilation and Runtime Tests
  10. Limitations
  11. Conclusion
  12. Key Lessons
  13. Attachments



Objective and Scope

Our first article studied the internal development of a daily candle. In Part 10, we combined trend constraints with Golden and Death Cross logic. We now use the completed higher-timeframe relationship to select the lower-timeframe conditions we want to examine. A-B supplies our directional hypothesis, and the C-D sequence makes its event order explicit.

Candles A and B define the context. Candle C is accepted when its lower-timeframe bars meet the retest and displacement rules, and Candle D provides the execution window. We use accepted to mean a match to those rules. We assess the trading value of the resulting signals through separate experiments.

We want an Expert Advisor (EA) that distinguishes a completed context from a developing trigger. The state model answers four operational questions:

  • What completed relationship exists between Candles A and B?
  • Did the configured trigger bars inside C satisfy the retest and structure-break rules?
  • Did a completed trigger bar inside D close beyond the C extreme and buffer?
  • Did the examined pre-trigger closes remain within the protected-level rule?

The defaults are D1 for Candles A-D and M1 for trigger bars. InpContextTimeframe and InpTriggerTimeframe select the periods independently of the host chart. Initialization requires positive period lengths and a trigger period shorter than the context period; it does not verify boundary alignment. We use D1/M1 as the worked configuration. Other combinations require separate synchronization checks. Trading is optional and disabled by default.



The Four-Candle Constraint Model

When D opens, A, B, and C are completed context bars. The EA classifies A-B and requests C history without using D’s eventual close. Live decisions use a completed bar from InpTriggerTimeframe, keeping the developing trigger bar outside the decision.

CandleStatus when D opensResponsibilityData used
AClosedDefines the original range and directionContext-timeframe OHLC
BClosedContinues, reverses, or compresses AContext-timeframe OHLC relative to A
CClosedRetests the reference and arms the sequenceTrigger bars whose opening times lie between C open and D open
DDevelopingConfirms, invalidates, or expires the sequenceNewly observed completed trigger bars

RefreshContextSequence requests four records from InpContextTimeframe on the chart symbol. With series indexing enabled, records 0, 1, 2, and 3 represent D, C, B, and A respectively. Changing the host chart period does not change these assignments when both timeframe inputs are explicit; PERIOD_CURRENT would make the selected input follow the chart.

An unclassified A-B pair or a C scan without an accepted setup is labeled SEQUENCE_EXPIRED immediately; in this implementation that state also means inactive. An accepted C setup produces SEQUENCE_TRACK_D. A pre-trigger D close may then produce SEQUENCE_TRIGGERED or SEQUENCE_INVALIDATED. At the next context opening, the EA logs the old state and initializes a new sequence. An unconfirmed tracking state is not explicitly relabeled expired before that log.

Fig. 1. A-B context, the C minute setup, and the D confirmation workflow

Fig. 1. A-B context, the C minute setup, and the D confirmation workflow



Classifying Candles A and B

The classifier intentionally uses a small collection of explainable relationships. It does not attempt to name every traditional candlestick pattern. Each accepted relationship must supply either a direction or a controlled way for C to choose one.

RelationshipA-B conditionReference levelProtected level
Bullish continuationA and B are bullish; B closes above the A high plus the breakout bufferA highLower of the A and B lows
Bearish continuationA and B are bearish; B closes below the A low minus the breakout bufferA lowHigher of the A and B highs
Bullish reversalA is bearish and B is bullish; B low < A low − buffer and B close ≥ BullishRecoveryA lowB low
Bearish reversalA is bullish and B is bearish; B high > A high + buffer and B close ≤ BearishRecoveryA highB high
Inside-bar compressionB high < A high − buffer and B low > A low + bufferBoundary broken by COpposite A boundary
UnclassifiedNo listed condition is satisfiedNoneNone; no signal is permitted

The price buffer is InpBreakBufferPoints multiplied by SYMBOL_POINT. Continuation, sweep, compression, and structure-break comparisons use strict inequalities at their buffered boundaries. The reversal recovery thresholds are:

BullishRecovery = A low + (A high - A low) × f

BearishRecovery = A high - (A high - A low) × f

A low and A high define Candle A’s range, and f is InpReversalCloseFraction, strictly between zero and one. Recovery includes equality at the threshold: a bullish B may close exactly at BullishRecovery, and a bearish B may close exactly at BearishRecovery. Equality at a sweep or buffered compression boundary does not qualify. A doji is neither bullish nor bearish, although it may still satisfy the compression range test. A nonpositive A range is unclassified. The classifier tests compression first, then continuation, then reversal.

The following extract is the complete classifier used by our EA. Because A and B are completed records, its output remains fixed throughout Candle D.

//+------------------------------------------------------------------+
//| Classifies the completed relationship between Candles A and B    |
//+------------------------------------------------------------------+
ENUM_AB_RELATION ClassifyAB(const MqlRates &a,const MqlRates &b)
  {
   double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
   double buffer=InpBreakBufferPoints*point;
   double a_range=a.high-a.low;

   if(a_range<=0.0)
      return(AB_RELATION_NONE);

//--- Compression is directionless until Candle C breaks a boundary
   if(b.high<a.high-buffer && b.low>a.low+buffer)
      return(AB_INSIDE_COMPRESSION);

//--- Two aligned candles plus a close beyond A define continuation
   if(IsBullish(a) && IsBullish(b) && b.close>a.high+buffer)
      return(AB_BULLISH_CONTINUATION);

   if(IsBearish(a) && IsBearish(b) && b.close<a.low-buffer)
      return(AB_BEARISH_CONTINUATION);

//--- A sweep and recovery through a configurable part of A define reversal
   double bullish_recovery=a.low+a_range*InpReversalCloseFraction;
   double bearish_recovery=a.high-a_range*InpReversalCloseFraction;

   if(IsBearish(a) && IsBullish(b) &&
      b.low<a.low-buffer && b.close>=bullish_recovery)
      return(AB_BULLISH_REVERSAL);

   if(IsBullish(a) && IsBearish(b) &&
      b.high>a.high+buffer && b.close<=bearish_recovery)
      return(AB_BEARISH_REVERSAL);

   return(AB_RELATION_NONE);
  }

Continuation requires two aligned candle bodies and a confirmed close beyond A. Reversal requires opposing candle directions, a sweep, and recovery. Compression deliberately starts without a direction because Candle C must reveal which A boundary the lower-timeframe path accepts.



Scanning Trigger Bars Inside Candle C

The time-range form of CopyRates selects C’s trigger records by their opening timestamps, from C open through D open minus one second. The stop time excludes a record opening at D. The EA scans the returned records chronologically once the minimum count is available. History coverage remains a separate check, described below.

For a directional continuation or reversal, C must complete two stages:

  1. A trigger candle meets the reference-level retest inequalities below.
  2. A displacement close breaks the previous InpSwingDepth trigger highs or lows by the buffer. In the directional scanner, the retest and break may occur on the same bar.

Let r be the reference, t be InpRetestTolerancePoints × SYMBOL_POINT, and b be the price buffer. A touch is defined by the following inequalities, not by an absolute distance from every OHLC value:

Scanner and directionRetest condition
Directional bullishLow ≤ r + t and Close ≥ r − b
Directional bearishHigh ≥ r − t and Close ≤ r + b
Compression bullishLow ≤ r + t and Close ≥ r
Compression bearishHigh ≥ r − t and Close ≤ r

These are one-sided tests: a wick can extend through the reference if the close meets its condition and does not invalidate protection. The directional touched flag remains set once accepted. Both scanners return at the first qualifying structure break and store that bar’s opening time in c_setup_time. They do not select a later or stronger setup, and they do not scan the remainder of C for a later cancellation. Thus C protection is checked only through the accepted setup bar.

A displacement candle is not defined by color alone. Its body must occupy a minimum proportion of its complete range:

R_body = |Close - Open| / (High - Low)

Open, High, Low, and Close belong to the examined trigger bar. IsDisplacement first rejects High − Low ≤ 0, so it never divides by a zero range. For a positive range, R_body must be at least InpMinBodyRangeRatio, and the body must have the required direction. Equality at the default ratio of 0.55 passes. The ratio, buffer, retest tolerance, recovery fraction, and swing depth are design defaults, not statistically estimated thresholds.

Compression has three distinct bar stages: a displacement close outside A plus the buffer, a subsequent retest of that broken boundary, and a later displacement structure break. The first qualifying breakout in chronological order fixes the direction. The scanner does not switch to the opposite direction; a later close beyond its protected A boundary cancels the scan. The breakout and retest stages each continue to the next bar, so neither can also serve as the final structure-break stage.

AnalyzeCSetup requests the configured trigger history and enforces a minimum count before dispatching to the compression or directional scanner. It does not test every expected bar time against the symbol’s trading sessions.

//+------------------------------------------------------------------+
//| Loads trigger bars inside C and checks the setup                 |
//+------------------------------------------------------------------+
bool AnalyzeCSetup(void)
  {
//--- Load every trigger bar that closed before Candle D opened
   datetime stop_time=g_sequence.d.time-1;
   MqlRates trigger_bars[];

   ResetLastError();
   int copied=CopyRates(_Symbol,InpTriggerTimeframe,
                        g_sequence.c.time,stop_time,trigger_bars);

   if(copied<InpSwingDepth+2)
     {
      PrintFormat("C setup unavailable: copied=%d, error=%d%s",
                  copied,GetLastError(),(copied<0 && MQLInfoInteger(MQL_TESTER) ? " (check tester mode and trigger-history availability)" : ""));
      return(false);
     }

//--- CopyRates stores the oldest record first in physical memory
   ArraySetAsSeries(trigger_bars,false);

//--- Compression and directional setups use different scanners
   if(g_sequence.relation==AB_INSIDE_COMPRESSION)
      return(AnalyzeCompressionC(trigger_bars));

   SetDirectionalLevels();
   return(AnalyzeDirectionalC(trigger_bars));
  }

If too few records are returned, the EA leaves C unavailable and does not index the short array. A partial return above the minimum can still hide missing bars. The terminal may build or download history during a request, so reinitialize after synchronization when necessary. For periods that do not align, an included bar may open inside C and close after D opens. Use an aligned pair such as D1/M1 until explicit containment and coverage checks are added.

AnalyzeDirectionalC implements the one-sided retest, checked-close protection, and first-setup policy described above. Notice that touched can be set immediately before the structure comparison on the same bar.

//+------------------------------------------------------------------+
//| Scans C for a retest and a trigger-bar structure break           |
//+------------------------------------------------------------------+
bool AnalyzeDirectionalC(const MqlRates &trigger_bars[])
  {
   double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
   double tolerance=InpRetestTolerancePoints*point;
   double buffer=InpBreakBufferPoints*point;
//--- Walk C chronologically looking for a retest then a structure break
   bool touched=false;

   for(int i=0;i<ArraySize(trigger_bars);i++)
     {
      //--- Cancel a setup that violates the A-B protected extreme
      if(g_sequence.direction>0 &&
         trigger_bars[i].close<g_sequence.protected_level-buffer)
         return(false);

      if(g_sequence.direction<0 &&
         trigger_bars[i].close>g_sequence.protected_level+buffer)
         return(false);

      //--- The retest must touch the reference and close on its valid side
      if(!touched && g_sequence.direction>0 &&
         trigger_bars[i].low<=g_sequence.reference_level+tolerance &&
         trigger_bars[i].close>=g_sequence.reference_level-buffer)
         touched=true;

      if(!touched && g_sequence.direction<0 &&
         trigger_bars[i].high>=g_sequence.reference_level-tolerance &&
         trigger_bars[i].close<=g_sequence.reference_level+buffer)
         touched=true;

      if(!touched || i<InpSwingDepth)
         continue;

      //--- A displacement close through recent trigger-timeframe structure arms Candle D
      if(g_sequence.direction>0 &&
         IsDisplacement(trigger_bars[i],1) &&
         trigger_bars[i].close>PreviousHigh(trigger_bars,i)+buffer)
        {
         g_sequence.c_setup_time=trigger_bars[i].time;
         g_sequence.c_setup_level=trigger_bars[i].close;
         return(true);
        }

      if(g_sequence.direction<0 &&
         IsDisplacement(trigger_bars[i],-1) &&
         trigger_bars[i].close<PreviousLow(trigger_bars,i)-buffer)
        {
         g_sequence.c_setup_time=trigger_bars[i].time;
         g_sequence.c_setup_level=trigger_bars[i].close;
         return(true);
        }
     }

   return(false);
  }



Confirmation and Invalidation Inside Candle D

C arms the sequence without submitting an order. D confirmation marks the point where our entry conditions are satisfied. We assess the predictive value of those conditions separately.

A bullish confirmation requires a bullish displacement close strictly above C high + buffer; the bearish rule requires a bearish displacement close strictly below C low − buffer. While the state is tracking, an invalidating close is checked first. A wick or tick through protection is insufficient, and equality at the buffered protection boundary does not invalidate. After a signal, the state only updates excursion measurements; it does not apply this invalidation rule as position management.

D eventBullish sequenceBearish sequenceResult
ConfirmationBullish displacement close above the C high and bufferBearish displacement close below the C low and bufferOne signal; state becomes triggered
InvalidationTrigger close < protected level − bufferTrigger close > protected level + bufferNo later D signal is permitted
No decisionNeither boundary is confirmedNeither boundary is confirmedOur model keeps waiting until D closes
ExpiryD ends without a triggerD ends without a triggerThe next context opening starts a new sequence; the old tracking state is logged without an explicit expiry transition

If D opens beyond C’s high or low, the opening gap alone does not trigger a signal. The EA waits for an examined completed trigger bar that satisfies direction, body ratio, and buffered close requirements; a fresh crossing from the other side is not required. On startup, ReplayDHistory uses the same detector with allow_trade=false, so reconstructed historical signals cannot submit orders.

UpdateExcursion measures each processed trigger bar relative to the signal bar’s close. For a bullish signal, favorable movement is High − signal close and adverse movement is signal close − Low; a bearish signal reverses the directions. The function clamps negative values to zero, retains the maxima, and reports them in symbol points. We use these OHLC values as diagnostics of the processed price path.

Measurement begins with the confirmation bar’s full range, including prices reached before its closing signal. Subsequent processed bars extend the maxima. The live gate processes the latest completed bar, so a tick gap can leave intervening bars unexamined. At a context transition, the new sequence is initialized before the old window’s final closed bar is evaluated. The diagnostic horizon therefore consists of the bars actually processed, including that initial confirmation-bar range.



System Architecture

The context loader assigns four bars from InpContextTimeframe. The classifier selects an A-B relationship, the C scanner applies trigger-timeframe rules, and the D state machine examines completed trigger bars. Visualization and optional execution consume the decision. D1/M1 in the diagrams denotes the default worked configuration.

Components, data flow, and execution safety boundary

Fig. 2. Components, data flow, and execution safety boundary

The safety boundary is deliberate. Detection remains useful when trading permissions are unavailable or InpEnableTrading is false. Optional execution performs independent permission, position, spread, volume, stop distance, and trade-result checks. A confirmed analytical signal therefore does not imply that a broker accepted a position.



Implementation

State and Candle Data

SSequence stores the four context records with their classification, direction, levels, event timestamps, and excursion diagnostics. Keeping them together gives each function the same A-B-C-D assignment.

struct SSequence
  {
   MqlRates             a;
   MqlRates             b;
   MqlRates             c;
   MqlRates             d;
   ENUM_AB_RELATION     relation;
   ENUM_SEQUENCE_STATE  state;
   int                  direction;
   double               reference_level;
   double               protected_level;
   datetime             c_setup_time;
   double               c_setup_level;
   datetime             d_signal_time;
   double               signal_price;
   double               mfe;
   double               mae;
  };

A direction of 1 selects bullish comparisons, −1 selects bearish comparisons, and zero leaves direction unresolved or inactive. The reference is the retest threshold. The protected level is the threshold for a cancellation based on examined closes, subject to the scanner’s stopping policy.

Gating Completed Trigger Bars

The OnTick handler copies the two most recent records of InpTriggerTimeframe. It examines record 1 when its opening time differs from g_last_trigger_time. This suppresses repeated processing of the latest completed bar; it does not guarantee processing every bar when several have elapsed between ticks.

//+------------------------------------------------------------------+
//| Processes each newly completed trigger-timeframe candle          |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   MqlRates trigger_bars[];
   ArraySetAsSeries(trigger_bars,true);

   if(CopyRates(_Symbol,InpTriggerTimeframe,0,2,trigger_bars)!=2)
      return;

//--- Resolve the newest completed trigger candle
   datetime closed_time=trigger_bars[1].time;
//--- Skip repeats of the same completed candle
   if(closed_time==g_last_trigger_time)
      return;

//--- Roll the sequence forward when a new D candle has opened
   if(!RefreshContextSequence(false))
      return;

   g_last_trigger_time=closed_time;
//--- Trigger bars before D opened are outside the sequence
   if(closed_time<g_sequence.d.time)
      return;

//--- Refresh the D box, evaluate the bar, and repaint the text
   UpdateCandleD();
   EvaluateDBar(trigger_bars[1],true);
   UpdateStatus();
  }

RefreshContextSequence runs before EvaluateDBar. A timestamp guard then excludes bars opening before the new D. This keeps the old window’s last bar out of the new sequence, but the live path does not separately process that bar for the old sequence.

History Reconstruction

On initialization, the EA loads the current context and replays the available completed trigger bars inside D. Replay calls EvaluateDBar with allow_trade=false. State and chart signals can be reconstructed without a historical order request; incomplete history can still change the reconstructed state.

The PeriodSeconds function supplies nominal period lengths for validation and the D box’s visual extent. Context opening timestamps bound the C history request. Neither mechanism proves session coverage or containment of trigger bars in non-aligned periods. Bar timestamps identify opening time; the confirmation is available only after that bar closes.

Visualization

The EA uses ObjectCreate for candle boxes, decision lines, labels, and a signal arrow. Names contain the CTC_P11 prefix, magic number, and D opening time where applicable. This separates sequences and distinct magic numbers on a chart; it is not a globally unique instance identifier. Reusing the same namespace on one chart can reuse or delete existing objects. Use a distinct magic number for each independent deployment.

The CCanvas class displays six chart-comment lines: title, A-B relationship, direction, state, C setup time, and trading setting. The canvas is observational. Candle boxes, levels, and arrows are separate chart objects.

CreateStatusCanvas() initializes the bitmap and font. UpdateStatus() clears the bitmap, draws the text lines using the chart foreground color, and then calls Update(). DestroyStatusCanvas() releases the bitmap and the bound label during deinitialization.

//+------------------------------------------------------------------+
//| Creates the canvas used only for chart-comment text              |
//+------------------------------------------------------------------+
bool CreateStatusCanvas(void)
  {
   if(!InpShowStatusPanel)
      return(true);

//--- Create the transparent bitmap label that hosts the text
   string name=g_prefix+"STATUS_CANVAS";
   ResetLastError();
   if(!g_status_canvas.CreateBitmapLabel(0,0,name,10,20,360,116,
                                         COLOR_FORMAT_ARGB_NORMALIZE))
     {
      PrintFormat("CreateBitmapLabel() failed. Error=%d",GetLastError());
      return(false);
     }

//--- A fixed font keeps every status line aligned
   if(!g_status_canvas.FontSet("Arial",-100))
     {
      PrintFormat("CCanvas FontSet() failed. Error=%d",GetLastError());
      //--- Clean up the partially created canvas on failure
      g_status_canvas.Destroy();
      return(false);
     }

   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
   g_status_canvas_ready=true;
   return(true);
  }

//+------------------------------------------------------------------+
//| Draws the original chart comments through CCanvas                |
//+------------------------------------------------------------------+
void UpdateStatus(void)
  {
   if(!g_status_canvas_ready)
      return;

//--- Compose the compact state lines from the runtime sequence
   string direction="Neutral";
   if(g_sequence.direction>0)
      direction="Bullish";
   else
      if(g_sequence.direction<0)
         direction="Bearish";

   string c_setup=(g_sequence.c_setup_time>0 ?
                   TimeToString(g_sequence.c_setup_time,
                                TIME_DATE|TIME_MINUTES) :
                   "none");

   string lines[6];
   lines[0]="Candlestick Trend Constraint Part 11";
   lines[1]="A-B: "+RelationName(g_sequence.relation);
   lines[2]="Direction: "+direction;
   lines[3]="State: "+StateName(g_sequence.state);
   lines[4]="C setup: "+c_setup;
   lines[5]="Trading: "+(InpEnableTrading ? "enabled" : "disabled");

//--- Paint in the chart foreground color for any theme
   color chart_text=(color)ChartGetInteger(0,CHART_COLOR_FOREGROUND);
   uint text_color=ColorToARGB(chart_text,255);

//--- Clear the previous text while keeping the canvas transparent
   g_status_canvas.Erase(0);
   for(int i=0;i<ArraySize(lines);i++)
      g_status_canvas.TextOut(4,4+i*18,lines[i],text_color);

   g_status_canvas.Update();
  }

//+------------------------------------------------------------------+
//| Releases the canvas and its bound bitmap-label object            |
//+------------------------------------------------------------------+
void DestroyStatusCanvas(void)
  {
   if(!g_status_canvas_ready)
      return;

   g_status_canvas.Destroy();
   g_status_canvas_ready=false;
  }

If InpDeleteObjectsOnRemoval is false, the completed sequence objects remain on the chart after our EA is removed. Enabling that input deletes only objects owned by our EA's prefix. The canvas itself is always destroyed during deinitialization because its bitmap resource belongs to the running EA instance.

Figure 3 illustrates a bullish sequence in synthetic price units: the price buffer is 0.2, retest tolerance is 0.3, swing depth is three trigger bars, and the minimum body ratio is 0.55. C retests A high at 105 and records its first structure break. D’s confirming close at 112 is above C high 111 plus the buffer. These illustrative values are not market measurements or the default symbol-point settings.

CTC_P11_ABCD_Illustration.png

Fig. 3. Illustrative bullish A-B context, C retest and setup, and D confirmation; synthetic price units

Optional Execution

Our EA includes the CTrade class, but InpEnableTrading defaults to false. When enabled, a D confirmation passes through the following controls:

  • MQL program, terminal, and account trading permissions must be available.
  • A netting account must have no existing position on the symbol.
  • A hedging account must have no position owned by this symbol and magic number.
  • The current spread must not exceed InpMaxSpreadPoints.
  • The requested volume is clamped and stepped to the symbol's permitted volume properties.
  • The stop is placed beyond the protected level and adjusted to respect the symbol's minimum stop distance.
  • The take-profit distance is derived from the final entry-to-stop distance and InpRiskReward.
  • The Boolean trade call and the server result code are both checked.

TradeResultAccepted accepts TRADE_RETCODE_DONE, TRADE_RETCODE_DONE_PARTIAL, and TRADE_RETCODE_PLACED. DONE reports completion; DONE_PARTIAL reports a partial result; PLACED reports placement rather than a proven fill. As the CTrade Buy documentation explains, a successful method return alone does not establish trade execution. Check the return code and deal/order evidence. This EA logs those identifiers but does not implement transaction-level reconciliation or retry a failed request for the same sequence. The signal remains triggered even when trading is disabled or the request is rejected.



Operating the Expert Advisor

Place Candlestick_Trend_Constraint_P11.mq5 in MQL5/Experts and compile it in MetaEditor. Attach it to the intended symbol. Select explicit context and trigger periods; with those inputs fixed, the host chart timeframe does not select the strategy’s periods.

InputDefaultPurpose
InpContextTimeframePERIOD_D1Timeframe of Candles A-D
InpTriggerTimeframePERIOD_M1Internal timeframe used for C and D
InpBreakBufferPoints10Separates a qualified break from marginal equality
InpReversalCloseFraction0.50Required recovery through the A range after a sweep
InpSwingDepth3Number of preceding trigger bars defining local structure
InpRetestTolerancePoints30Permitted distance around a reference retest
InpMinBodyRangeRatio0.55Minimum body share of a trigger displacement candle
InpDrawSequencetrueDisplays boxes, levels, labels, and signals
InpDeleteObjectsOnRemovalfalseControls whether owned chart objects persist
InpShowStatusPaneltrueDisplays the original chart-comment lines through CCanvas
InpEnableTradingfalseKeeps analysis separate from live execution
InpVolume0.10Requested fixed volume when execution is enabled
InpRiskReward2.00Take-profit multiple of the final stop distance
InpStopBufferPoints100Places the stop beyond the protected level
InpMaxSpreadPoints50Blocks execution during excessive spread
InpDeviationPoints20Maximum permitted trade deviation
InpMagicNumber24516011Identifies positions and chart objects owned by Part 11

Begin with trading disabled. Inspect several completed sequences in the Strategy Tester visualization and verify that the reference, protected level, C setup, and D signal match the Experts log. Point-based inputs are symbol-specific. A value that is suitable for a five-digit currency symbol may be too small or too large for an index, metal, or synthetic symbol.



Planning Compilation and Runtime Tests

The following scenarios specify our test plan for the EA’s rules and event handling. Compilation results and recorded scenario outcomes remain to be collected. We use the visual walkthrough to explain the workflow; the planned runs will supply the evidence for evaluating its behavior.

Compile Candlestick_Trend_Constraint_P11.mq5 in MetaEditor and record the build plus error and warning counts. Synchronize the configured trigger history before the visual run. Initialization and compilation are separate checks.

For the default worked case, select D1/M1 and begin with InpEnableTrading=false. Use "Every tick based on real ticks" when the required history is available, and record the modeling mode actually used. For this walkthrough, avoid "Open prices only". The official tester reference restricts that mode’s access to periods below, or non-multiple periods relative to, the testing period. Intrabar ordering and fill analysis require appropriate tick data and execution records.

ScenarioExpected observationEvidence required
Bullish continuationA-B classification, rule-qualified C retest and structure break, then one D close above the C highChart objects and Experts log
Bearish continuationMirrored classification and one D close below the C lowChart objects and Experts log
Bullish and bearish reversalB sweep and recovery satisfy the configured fraction before C is analyzedContext-timeframe OHLC and relation log
Inside compressionC chooses a direction, retests the broken A boundary, and breaks trigger-bar structureTrigger-bar visual replay
C without an accepted setupThe sequence expires and D never triggersCCanvas chart comment and log
Protected-level breachInvalidation takes priority and later D breaks are ignoredTrigger-bar close and state log
Mid-D attachmentEarlier D trigger bars are replayed, but no historical trade is submittedJournal and account history
Insufficient historyOur EA reports the unavailable C setup without accessing invalid indicesExperts log
Trading enabledAt most one request per sequence after all execution gates passTrade request/result log and account mode
Additional planned checkExpected rule or limitation
Buffered equality and dojiTouching a strict breakout, sweep, or compression boundary fails; recovery equality and body-ratio equality pass. A doji has no body direction.
Zero-range inputA with High=Low is unclassified; a zero-range trigger bar cannot satisfy displacement.
C competition and cancellationConfirm first-break direction for compression, same-bar retest/break for directional C, first qualifying setup, and no scan after that setup.
D gap and protectionAn opening gap alone does not confirm. A wick beyond protection does not invalidate; a strict buffered close does while tracking.
History and event coverageTest missing trigger records, several bars between ticks, and a non-aligned timeframe pair. Record omissions rather than treating a successful copy as complete coverage.
End of context windowInspect the final old-D trigger bar and its omission from the old sequence before context replacement.
Ownership and trade resultsCheck retained objects with a reused magic number, cleanup scope, and DONE/PARTIAL/PLACED outcomes independently of signal counts.

Fig. 4. Strategy Tester visual demonstration

Fig. 4. Strategy Tester visual demonstration

Keep the terminal build, symbol, explicit periods, demo account mode, date range, modeling mode, and input set with the recorded run. Check signals independently of order requests and deals. To study predictive value, compare the detector with an eligible baseline and use chronological holdout data, realistic costs, and several instruments or regimes.



Limitations

  • The A-B taxonomy and default thresholds are research choices. Reproducible comparisons do not establish their stability or market advantage.
  • Protection uses examined closes before a trigger. The C scanner stops at its first setup, and triggered D states do not model later analytical invalidation or trade exits.
  • The trigger period must be shorter than the context period, but initialization does not enforce alignment or complete history coverage.
  • The live timestamp gate processes only the latest completed trigger bar. Tick gaps and the old/new context transition can omit bars.
  • Excursion diagnostics include the confirmation bar’s full range and omit trading costs. They are not verified post-confirmation outcomes or realized returns.
  • Context candles depend on server and session boundaries. A D1/M1 example cannot validate other period pairs or data feeds.
  • Object names are scoped by a chart namespace and magic number; reusing that namespace can collide with persistent objects.
  • InpDeleteObjectsOnRemoval controls prefix-based cleanup. CCanvas is always destroyed, but retained objects require deliberate namespace management.
  • A performance conclusion requires completed, reproducible tests, adequate independent samples, cost assumptions, and out-of-sample evaluation.



Conclusion

In this article, we have implemented a four-candle state model that gives context, setup, and confirmation separate responsibilities. The EA classifies completed A-B candles, records the first qualifying C setup, and evaluates completed trigger bars inside D. Its chart objects and CCanvas comments make those decisions visible, while the replay and execution gates keep reconstructed signals separate from new trade requests.

You can now follow each decision from A-B classification to a D signal and distinguish that signal from an order result. We have established a concrete framework for testing our candlestick hypothesis. Compilation and recorded scenario tests are the next step before assessing its market value.



Key Lessons

LessonDescription
Use completed context bars to select explicit directional rulesA-B classification selects the detector’s directional comparisons. Lower-timeframe bars supply retests and structure breaks; predictive value requires separate evidence.
Separate context, setup, and confirmationGiving each stage one responsibility prevents a single condition from mixing market bias, trigger detection, and confirmation. This separation also makes each rule easier to test and reuse.
Represent sequential logic as explicit stateA finite-state model makes allowed transitions visible and prevents an event from bypassing the required order. The principle applies to trading logic, workflow automation, protocol handling, and other event-driven programs.
Process completed bars onceA completed-bar timestamp gate avoids duplicate decisions, reduces tick-level noise, and makes live behavior easier to reproduce in the Strategy Tester.
Bound lower-timeframe history with real timestampsOpening timestamps define context windows without assuming a fixed bar count. Separate checks are still needed for containment, session gaps, and missing history.
Replay state without replaying executionThe same detector can reconstruct analytical state after initialization, while a separate execution permission prevents historical signals from creating late trades.
Keep visualization observationalChart objects and CCanvas report our model's state without changing classification or execution. Interface code can therefore evolve without altering the trading rules.
Treat a signal and an accepted order as different outcomesA confirmed pattern is an analytical result. Permissions, ownership, spread, volume, stop distance, and server return codes independently decide whether a request proceeds. Placement and fill remain distinct outcomes.
Own and release chart resources explicitlyA documented prefix and magic number define cleanup scope. Their uniqueness must be managed explicitly; a shared namespace does not isolate deployments.

Attachments

File NameTypeDescription
Candlestick_Trend_Constraint_P11.mq5Expert AdvisorConfigurable context-timeframe A-B classifier, trigger-timeframe Candle C setup scanner, Candle D state machine, visualization, replay, and optional guarded execution.
Features of Custom Indicators Creation Features of Custom Indicators Creation
Creation of Custom Indicators in the MetaTrader trading system has a number of features.
Bimodal Market Profile with Delta and Memory in MQL5 Bimodal Market Profile with Delta and Memory in MQL5
The classic Market Profile from forty years ago is still replicated in dozens of indicators that differ only in the color of their bars. In this article, I examine three conceptual blind spots in the original theory — the monolithic Value Area in bimodal distributions, TPO’s blindness to the aggressor, and the lack of memory between sessions — and build an indicator that addresses each of them: bimodality detection with a dead zone, order flow via CopyTicksRange with absorption detection, and composite market memory using Naked POC and HVN/LVN. The complete source code is included.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Building a Prop Firm Compliance Monitor in MQL5 (Part 3): Daily Drawdown, Overall Drawdown, and Profit Targets Building a Prop Firm Compliance Monitor in MQL5 (Part 3): Daily Drawdown, Overall Drawdown, and Profit Targets
This part turns stored account references into a financial monitoring engine for MetaTrader 5. It calculates daily drawdown from the confirmed session balance, overall drawdown from the initial challenge balance with a static equity floor, and profit‑target progress from realized balance. The EA derives usage, remaining allowance, and status for each rule, then aggregates them into a single guard state where risk conditions override target completion.