Viewing the resource: Crafting a Head & Shoulders Cosmic Trader with MQL5 🌌

Crafting a Head & Shoulders Cosmic Trader with MQL5 🌌

Allan Munene Mutiiria 2025-06-21 19:32:00 158 Views
This article is meticulously crafted to deliver a professional, engaging, and seamless narrative, fl...

Introduction

Picture yourself at the helm of a starship, your navigation systems humming as you scan the forex galaxy for patterns that signal a seismic market shift, like constellations revealing a new path. The Head & Shoulder Pattern EA is your state-of-the-art navigation radar, designed to detect two powerful chart patterns: the bearish Head & Shoulders and the bullish Inverse Head & Shoulders. These patterns, formed by peaks (bearish) or troughs (bullish) resembling a head flanked by two shoulders, are classic reversal signals, appearing after trends to herald a change in direction. The EA not only identifies these patterns with precision but also draws them on your chart with vibrant lines, triangles, and labels, executes trades on neckline breakouts, and applies trailing stops to lock in profits. It’s like a cosmic autopilot, guiding you through reversals with minimal manual effort while offering visual clarity to track every move.

In this article, I’ll lead you through the code with a continuous, professional narrative that flows like a starry night, weaving together each section to keep you engaged from start to finish. I’ll explain every component as if you’re new to MQL5, using clear language, relatable analogies, and real-world examples—like spotting a Head & Shoulders on EURUSD—to make the concepts come alive. We’ll explore how the EA finds patterns, validates them, trades breakouts, and manages open positions, ensuring you understand not just the “how” but the “why” behind each line of code. With a touch of cosmic charm, this guide will transform the technical into the thrilling, empowering you to harness this EA’s potential. Let’s power up the radar and chart our course through the forex stars!

Strategy Blueprint

Before we dive into the code, let’s map out the EA’s mission, like plotting a star chart for our trading expedition:

  • Pattern Detection: Identifies Head & Shoulders (bearish) and Inverse Head & Shoulders (bullish) patterns by analyzing peaks/troughs within a 50-bar lookback ("LookbackBars"). A bearish pattern has a higher head peak between two lower shoulder peaks, with a neckline connecting troughs; the inverse has a lower head trough between higher shoulders, with a neckline connecting peaks.

  • Trade Execution: Sells on a close below the neckline for bearish patterns, or buys above for bullish patterns, using 0.1 lots ("lotsize") with a stop loss at the right shoulder plus/minus 20 pips ("BufferPoints") and a take profit based on the pattern’s height.

  • Visualization: Draws patterns with red (bearish) or green (bullish) trendlines, coral/green triangles, and labels (“LS”, “HEAD”, “RS”, “NECKLINE”), providing clear visual cues.

  • Risk Management: Applies trailing stops (30 pips after 10 pips profit, "TrailingPoints", "MinTrailingPoints") and tracks traded patterns ("MaxTradedPatterns"=20) to avoid duplicates, ensuring disciplined trading.

  • Validation: Ensures pattern symmetry (shoulder/trough tolerance, "shoulderTolerancePoints", "TroughTolerancePoints"), bar ranges (5–30 bars, "minBarRange", "maxBarRange"), and neckline breakout timing ("RightShoulderBreakoutMultiplier"=1.5). This strategy is like a cosmic navigation system, automating reversal trades with precision while illuminating patterns for strategic decision-making.

Code Implementation

Now, let’s step onto the starship’s bridge and explore the MQL5 code that powers this cosmic trader. I’ll guide you through each section like a mission commander, ensuring the narrative flows seamlessly, with professional polish and engaging details that keep you captivated. We’ll cover initializing the EA, detecting patterns, trading breakouts, visualizing patterns, and managing trades, using clear explanations and examples—like trading a Head & Shoulders on EURUSD—to make it relatable for beginners. Each step will build on the last, creating a cohesive story that transforms code into a thrilling expedition. Let’s activate the radar and begin our journey!

Step 1: Powering Up the Navigation Radar—Initializing the EA

Our adventure starts by powering up the starship’s navigation systems, setting the stage for pattern detection and trading.

//+------------------------------------------------------------------+
//|                                   Head & Shoulder Pattern EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

input int LookbackBars = 50;
input double ThresholdPoints = 50.0;
input double shoulderTolerancePoints = 30;
input double TroughTolerancePoints = 30;
input double BufferPoints = 20;
input double lotsize = 0.1;
input ulong magicNumber = 1234567;
input int maxBarRange = 30;
input int minBarRange = 5;
input double BarRangeMultiplier = 2.0;
input int validationBars = 3;
input double PriceTolerance = 5.0;
input double RightShoulderBreakoutMultiplier = 1.5;
input int MaxTradedPatterns = 20;
input bool UseTrailingStop = true;
input int MinTrailingPoints = 10;
input int TrailingPoints = 30;
input int priceOffsetForText = 10;

struct Extremum {
   int bar;
   datetime time;
   double price;
   bool isPeak;
};

struct TradedPatterns {
   datetime leftShoulderTime;
   double leftShoulderPrice;
};

static datetime lastBartime = 0;
TradedPatterns tradedPatterns[];

int OnInit(){
   obj_Trade.SetExpertMagicNumber(magicNumber);
   ArrayResize(tradedPatterns,0);
   return(INIT_SUCCEEDED);
}

We begin by activating the radar with the #property header, proudly declaring the EA as a creation by Allan in 2025, with a Telegram link for support, like engraving your starship’s mission badge. The "OnInit()" function is our launch sequence, where we prepare the EA for action. It includes "Trade/Trade.mqh" to enable trading via "obj_Trade", sets a unique "magicNumber" (1234567) for trade tracking with "SetExpertMagicNumber()", and initializes the "tradedPatterns[]" array to store traded patterns using "ArrayResize()". Inputs like "LookbackBars" (50), "lotsize" (0.1), and "UseTrailingStop" (true) define the EA’s parameters, like calibrating your ship’s sensors for a 50-bar scan, 0.1-lot trades, and trailing stops.

The struct Extremum stores peak/trough data (bar, time, price, peak status), and struct TradedPatterns tracks traded patterns (left shoulder time/price), like mission logs. Global variables like "lastBartime" and chart properties ("chart_width", "chart_height") prepare for pattern detection and visualization. Returning INIT_SUCCEEDED signals, “Radar’s powered up, ready to scan!” This lean setup ensures the EA is primed for detecting and trading patterns, like a starship ready to chart the forex galaxy.

Step 2: Scanning the Cosmos—Finding Peaks and Troughs

With the radar online, we scan the market for peaks and troughs, the building blocks of Head & Shoulders patterns, like spotting stars in the sky.

void FindExtrema(Extremum &extrema[], int lookback){
   ArrayFree(extrema);
   int bars = Bars(_Symbol,_Period);
   if (lookback >= bars) lookback = bars-1;
   
   double highs[], lows[];
   ArraySetAsSeries(highs,true);
   ArraySetAsSeries(lows,true);
   CopyHigh(_Symbol,_Period,0,lookback+1,highs);
   CopyLow(_Symbol,_Period,0,lookback+1,lows);
   
   bool isUpTrend = highs[lookback] < highs[lookback-1];
   double lastHigh = highs[lookback];
   double lastLow = lows[lookback];
   int lastExtremumBar = lookback;
   
   for (int i=lookback-1; i>= 0; i--){
      if (isUpTrend){
         if (highs[i] > lastHigh){
            lastHigh = highs[i];
            lastExtremumBar = i;
         }
         else if (lows[i] < lastHigh-ThresholdPoints*_Point){
            int size = ArraySize(extrema);
            ArrayResize(extrema,size+1);
            extrema[size].bar = lastExtremumBar;
            extrema[size].time = iTime(_Symbol,_Period,lastExtremumBar);
            extrema[size].price = lastHigh;
            extrema[size].isPeak = true;
            isUpTrend = false;
            lastLow = lows[i];
            lastExtremumBar = i;
         }
      }
      else {
         if (lows[i] < lastLow){
            lastLow = lows[i];
            lastExtremumBar = i;
         }
         else if (highs[i] > lastLow+ThresholdPoints*_Point){
            int size = ArraySize(extrema);
            ArrayResize(extrema,size+1);
            extrema[size].bar = lastExtremumBar;
            extrema[size].time = iTime(_Symbol,_Period,lastExtremumBar);
            extrema[size].price = lastLow;
            extrema[size].isPeak = false;
            isUpTrend = true;
            lastHigh = highs[i];
            lastExtremumBar = i;
         }
      }
   }
}

We move to the navigation deck, where "FindExtrema()" is our radar’s core scanner, identifying peaks and troughs within "LookbackBars" (50). Imagine sweeping the sky for bright stars (peaks) and dark voids (troughs). The function clears the "extrema[]" array with "ArrayFree()", ensuring a fresh scan, and limits lookback to available bars ("Bars()") to avoid errors.

It loads high and low prices into "highs[]" and "lows[]" using "CopyHigh()", "CopyLow()", set as time series with "ArraySetAsSeries()". Starting from the oldest bar, it tracks the trend ("isUpTrend") based on highs ("highs[lookback] < highs[lookback-1]"). In an uptrend, it updates "lastHigh" if a higher high is found; if a low dips below "lastHigh" by "ThresholdPoints" (50 pips), it logs a peak ("isPeak = true") with "ArrayResize()", switches to a downtrend, and updates "lastLow". Downtrends work similarly, logging troughs when highs rise above "lastLow" by 50 pips. Each extremum stores its bar, time ("iTime()"), price, and peak status, like cataloging stars at specific coordinates.

For example, on a EURUSD H1 chart, if highs rise from 1.1900 to 1.1950 over 10 bars, then a low hits 1.1890 (50 pips below), the EA logs a peak at 1.1950. This builds a map of extrema, like a constellation chart, ready for pattern detection, setting the stage for our next mission phase.

Step 3: Charting the Pattern—Detecting Head & Shoulders

With peaks and troughs mapped, we analyze them to detect Head & Shoulders patterns, like identifying a constellation that signals a market reversal.

bool DetectHeadAndShoulders(Extremum &extrema[], int &leftshoulderIdx, int &headIdx, int &rightShoulderIdx, int &necklineStartIdx, int &neckLineEndIdx){
   int size = ArraySize(extrema);
   if (size < 6) return false;
   
   for (int i=size-6; i>=0; i--){
      if (!extrema[i].isPeak && extrema[i+1].isPeak && !extrema[i+2].isPeak && extrema[i+3].isPeak && !extrema[i+4].isPeak && extrema[i+5].isPeak){
         double leftShoulder = extrema[i+1].price;
         double head = extrema[i+3].price;
         double rightShoulder  = extrema[i+5].price;
         double trough1 = extrema[i+2].price;
         double trough2 = extrema[i+4].price;
         
         bool isHeadHighest = true;
         for (int j=MathMax(0,i-5); j<MathMin(size,i+10); j++){
            if (extrema[j].isPeak && extrema[j].price > head && j != i+3){
               isHeadHighest = false;
               break;
            }
         }
         
         int lsBar = extrema[i+1].bar;
         int headBar = extrema[i+3].bar;
         int rsBar = extrema[i+5].bar;
         int lsToHead = lsBar - headBar;
         int headToRs = headBar - rsBar;
         
         if (lsToHead < minBarRange || lsToHead > maxBarRange || headToRs < minBarRange || headToRs > maxBarRange) continue;
         
         int minRange = MathMin(lsToHead, headToRs);
         if (lsToHead > minRange*BarRangeMultiplier || headToRs > minRange*BarRangeMultiplier) continue;
         
         bool rsValid = false;
         int rsBarIndex = extrema[i+5].bar;
         for (int j=rsBarIndex-1; j>=MathMax(0,rsBarIndex-validationBars); j--){
            if (iLow(_Symbol,_Period,j) < rightShoulder-ThresholdPoints*_Point){
               rsValid = true;
               break;
            }
         }
         if (!rsValid) continue;
         
         if (isHeadHighest && head > leftShoulder && head > rightShoulder && MathAbs(leftShoulder-rightShoulder) < shoulderTolerancePoints *_Point && MathAbs(trough1 - trough2) < TroughTolerancePoints *_Point){
            leftshoulderIdx = i+1;
            headIdx = i+3;
            rightShoulderIdx = i+5;
            necklineStartIdx = i+2;
            neckLineEndIdx = i+4;
            Print("Bar ranges: LS to Head = ",lsToHead,", Head to RS = ",headToRs);
            return true;
         }
      }
   }
   return false;
}

bool DetectInversedHeadAndShoulders(Extremum &extrema[], int &leftshoulderIdx, int &headIdx, int &rightShoulderIdx, int &necklineStartIdx, int &neckLineEndIdx){
   int size = ArraySize(extrema);
   if (size < 6) return false;
   
   for (int i=size-6; i>=0; i--){
      if (extrema[i].isPeak && !extrema[i+1].isPeak && extrema[i+2].isPeak && !extrema[i+3].isPeak && extrema[i+4].isPeak && !extrema[i+5].isPeak){
         double leftShoulder = extrema[i+1].price;
         double head = extrema[i+3].price;
         double rightShoulder  = extrema[i+5].price;
         double peak1 = extrema[i+2].price;
         double peak2 = extrema[i+4].price;
         
         bool isHeadLowest = true;
         int headBar = extrema[i+3].bar;
         for (int j=MathMax(0,headBar-5); j<=MathMin(Bars(_Symbol,_Period)-1,headBar+5); j++){
            if (iLow(_Symbol,_Period,j) < head){
               isHeadLowest = false;
               break;
            }
         }
         
         int lsBar = extrema[i+1].bar;
         int rsBar = extrema[i+5].bar;
         int lsToHead = lsBar - headBar;
         int headToRs = headBar - rsBar;
         
         if (lsToHead < minBarRange || lsToHead > maxBarRange || headToRs < minBarRange || headToRs > maxBarRange) continue;
         
         int minRange = MathMin(lsToHead, headToRs);
         if (lsToHead > minRange*BarRangeMultiplier || headToRs > minRange*BarRangeMultiplier) continue;
         
         bool rsValid = false;
         int rsBarIndex = extrema[i+5].bar;
         for (int j=rsBarIndex-1; j>=MathMax(0,rsBarIndex-validationBars); j--){
            if (iHigh(_Symbol,_Period,j) > rightShoulder+ThresholdPoints*_Point){
               rsValid = true;
               break;
            }
         }
         if (!rsValid) continue;
         
         if (isHeadLowest && head < leftShoulder && head < rightShoulder && MathAbs(leftShoulder-rightShoulder) < shoulderTolerancePoints *_Point && MathAbs(peak1 - peak2) < TroughTolerancePoints *_Point){
            leftshoulderIdx = i+1;
            headIdx = i+3;
            rightShoulderIdx = i+5;
            necklineStartIdx = i+2;
            neckLineEndIdx = i+4;
            Print("Bar ranges: LS to Head = ",lsToHead,", Head to RS = ",headToRs);
            return true;
         }
      }
   }
   return false;
}

Now, we’re in the pattern analysis hub, where "DetectHeadAndShoulders()" and "DetectInversedHeadAndShoulders()" act like telescopes searching for specific constellations. These functions scan the "extrema[]" array for sequences of peaks and troughs that form Head & Shoulders patterns, like identifying a star cluster with a distinct shape.

For a bearish Head & Shoulders, "DetectHeadAndShoulders()" looks for a sequence: trough, peak (left shoulder), trough, peak (head), trough, peak (right shoulder), requiring at least 6 extrema ("size < 6"). It checks:

  • The head is the highest peak ("isHeadHighest") within a 5–10 bar range, using "MathMax()", "MathMin()".

  • Shoulders are symmetric ("MathAbs(leftShoulder-rightShoulder) < shoulderTolerancePoints * _Point", 30 pips).

  • Troughs align ("MathAbs(trough1 - trough2) < TroughTolerancePoints * _Point", 30 pips).

  • Bar ranges between shoulders and head are 5–30 bars ("minBarRange", "maxBarRange") and balanced ("BarRangeMultiplier"=2.0).

  • The right shoulder is valid if a low within "validationBars" (3) dips below it by "ThresholdPoints" (50 pips).

If valid, it sets indices ("leftshoulderIdx", "headIdx", etc.) and returns true, logging bar ranges with "Print()". For example, on GBPUSD, a left shoulder at 1.3450 (bar 20), head at 1.3500 (bar 15), right shoulder at 1.3445 (bar 10), with troughs at 1.3400 (bar 18, 12), qualifies if conditions align, like spotting a bearish constellation.

"DetectInversedHeadAndShoulders()" mirrors this for bullish patterns, seeking peak, trough, peak, trough, peak, trough, ensuring the head is the lowest trough ("isHeadLowest") and validating shoulders/peaks similarly. This dual detection ensures the EA catches both reversal types, like a telescope scanning for all stellar patterns.

Step 4: Plotting the Course—Trading and Visualizing Patterns

With a pattern detected, we execute trades on neckline breakouts and draw the pattern, like launching a starship and marking the route on your star map.

void OnTick(){
   if (UseTrailingStop && PositionsTotal() > 0){
      ApplyTrailingStop(MinTrailingPoints,TrailingPoints,obj_Trade,magicNumber);
   }
   
   datetime currentBarTime = iTime(_Symbol,_Period,0);
   if (currentBarTime == lastBartime) return;
   lastBartime = currentBarTime;
   
   chart_width = (int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
   chart_height = (int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
   chart_scale = (int)ChartGetInteger(0,CHART_SCALE);
   chart_first_vis_bar = (int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR);
   chart_vis_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   chart_Prcmin = ChartGetDouble(0, CHART_PRICE_MIN,0);
   chart_Prcmax = ChartGetDouble(0, CHART_PRICE_MAX,0);
   
   if (PositionsTotal() > 0) return;
   
   Extremum extrema[];
   FindExtrema(extrema,LookbackBars);
   
   int leftShoulderIdx, headIdx, rightShoulderIdx, necklineStartIdx, necklineEndIdx;
   
   if (DetectHeadAndShoulders(extrema,leftShoulderIdx,headIdx,rightShoulderIdx,necklineStartIdx,neckLineEndIdx)){
      double closePrice = iClose(_Symbol,_Period,1);
      double necklinePrice = extrema[necklineEndIdx].price;
      
      if (closePrice < necklinePrice){
         datetime lsTime = extrema[leftShoulderIdx].time;
         double lsPrice = extrema[leftShoulderIdx].price;
         
         if (IsPatternTraded(lsTime,lsPrice)) return;
         
         datetime breakoutTime = iTime(_Symbol,_Period,1);
         int lsBar = extrema[leftShoulderIdx].bar;
         int headBar = extrema[headIdx].bar;
         int rsBar = extrema[rightShoulderIdx].bar;
         int necklineStartBar = extrema[necklineStartIdx].bar;
         int necklineEndBar = extrema[necklineEndIdx].bar;
         int breakoutBar = 1;
         
         int lsToHead = lsBar - headBar;
         int headToRs = headBar - rsBar;
         int rsToBreakout = rsBar - breakoutBar;
         int lsToNeckStart = lsBar - necklineStartBar;
         double avgPatternRange = (lsToHead+headToRs)/2.0;
         
         if (rsToBreakout > avgPatternRange *RightShoulderBreakoutMultiplier){
            Print("Pattern rejected....");
            return;
         }
         
         double necklineStartPrice = extrema[necklineStartIdx].price;
         double necklineEndPrice = extrema[necklineEndIdx].price;
         datetime necklineStartTime = extrema[necklineStartIdx].time;
         datetime necklineEndTime = extrema[necklineEndIdx].time;
         int barDiff = necklineStartBar - necklineEndBar;
         double slope = (necklineEndPrice - necklineStartPrice)/barDiff;
         double breakoutNecklinePrice = necklineStartPrice+slope *(necklineStartBar-breakoutBar);
         
         int extendedBar = necklineStartBar;
         datetime extendedNeckLineStartTime = necklineStartTime;
         double extendedNeckLineStartPrice = necklineStartPrice;
         bool foundCrossing = false;
         
         for (int i=necklineStartBar+1; i<Bars(_Symbol,_Period); i++){
            double checkPrice = necklineStartPrice - slope*(i-necklineStartBar);
            if (NeckLineCrossesBar(checkPrice,i)){
               int distance = i-necklineStartBar;
               if (distance <= avgPatternRange*RightShoulderBreakoutMultiplier){
                  extendedBar = i;
                  extendedNeckLineStartTime = iTime(_Symbol,_Period,i);
                  extendedNeckLineStartPrice = checkPrice;
                  foundCrossing = true;
                  Print("Neckline extended to first crossing bar within uniformity: Bar ",extendedBar);
                  break;
               }
               else {
                  Print("Crossing bar ",i," exceeds uniformity!");
                  break;
               }
            }
         }
         
         if (!foundCrossing){
            int barsToExtend = 2*lsToNeckStart;
            extendedBar = necklineStartBar+barsToExtend;
            if (extendedBar >= Bars(_Symbol,_Period)) extendedBar = Bars(_Symbol,_Period)-1;
            extendedNeckLineStartTime = iTime(_Symbol,_Period,extendedBar);
            extendedNeckLineStartPrice = necklineStartPrice - slope*(extendedBar-necklineStartBar);
            Print("Neckline extended to fallback (2x LS to Neckline start)");
         }
         
         string prefix = "HS_"+TimeToString(extrema[headIdx].time,TIME_MINUTES);
         
         DrawTrendLine(prefix+"_LeftToNeckStart",lsTime,lsPrice,necklineStartTime,necklineStartPrice,clrRed,3,STYLE_SOLID);
         DrawTrendLine(prefix+"_NeckStartToHead",necklineStartTime,necklineStartPrice,extrema[headIdx].time,extrema[headIdx].price,clrRed,3,STYLE_SOLID);
         DrawTrendLine(prefix+"_HeadToNeckEnd",extrema[headIdx].time,extrema[headIdx].price,necklineEndTime,necklineEndPrice,clrRed,3,STYLE_SOLID);
         DrawTrendLine(prefix+"_NeckEndToRight",necklineEndTime,necklineEndPrice,extrema[rightShoulderIdx].time,extrema[rightShoulderIdx].price,clrRed,3,STYLE_SOLID);
         DrawTrendLine(prefix+"_Neckline",extendedNeckLineStartTime,extendedNeckLineStartPrice,breakoutTime,breakoutNecklinePrice,clrRed,3,STYLE_SOLID);
         DrawTrendLine(prefix+"_RightToBreakout",extrema[rightShoulderIdx].time,extrema[rightShoulderIdx].price,breakoutTime,breakoutNecklinePrice,clrRed,3,STYLE_SOLID);
         DrawTrendLine(prefix+"_ExtendedToLeftShoulder",extendedNeckLineStartTime,extendedNeckLineStartPrice,lsTime,lsPrice,clrRed,3,STYLE_SOLID);
         
         DrawTriangle(prefix+"_LeftShoulderTriangle",lsTime,lsPrice,necklineStartTime,necklineStartPrice,extendedNeckLineStartTime,extendedNeckLineStartPrice,clrLightCoral);
         DrawTriangle(prefix+"_HeadTriangle",extrema[headIdx].time,extrema[headIdx].price,necklineStartTime,necklineStartPrice,necklineEndTime,necklineEndPrice,clrLightCoral);
         DrawTriangle(prefix+"_RightShoulderTriangle",extrema[rightShoulderIdx].time,extrema[rightShoulderIdx].price,necklineEndTime,necklineEndPrice,breakoutTime,breakoutNecklinePrice,clrLightCoral);
         
         DrawText(prefix+"_LS_Label",lsTime,lsPrice,"LS",clrRed,true);
         DrawText(prefix+"_Head_Label",extrema[headIdx].time,extrema[headIdx].price,"HEAD",clrRed,true);
         DrawText(prefix+"_RS_Label",extrema[rightShoulderIdx].time,extrema[rightShoulderIdx].price,"RS",clrRed,true);
         
         datetime necklineMidTime = extendedNeckLineStartTime+(breakoutTime-extendedNeckLineStartTime)/2;
         double necklineMidPrice = extendedNeckLineStartPrice+slope*(iBarShift(_Symbol,_Period,extendedNeckLineStartTime)-iBarShift(_Symbol,_Period,necklineMidTime));
         
         int x1 = ShiftToX(iBarShift(_Symbol,_Period,extendedNeckLineStartTime));
         int y1 = PriceToY(extendedNeckLineStartPrice);
         int x2 = ShiftToX(iBarShift(_Symbol,_Period,breakoutTime));
         int y2 = PriceToY(breakoutNecklinePrice);
         
         double pixelSlope = (y2-y1)/double (x2-x1);
         double necklineAngle = -atan(pixelSlope) *180/M_PI;
         
         DrawText(prefix+"_Neckline_Label",necklineMidTime,necklineMidPrice,"NECKLINE",clrBlue,false,necklineAngle);
         
         double entryPrice = 0;
         double sl = extrema[rightShoulderIdx].price+BufferPoints*_Point;
         double patternHeight = extrema[headIdx].price - necklinePrice;
         double tp = closePrice - patternHeight;
         
         if (sl > closePrice && tp < closePrice){
            if (obj_Trade.Sell(lotsize,_Symbol,entryPrice,sl,tp,"Head & Shoulders")){
               AddTradedPattern(lsTime,lsPrice);
               Print("Sell trade opened: SL ",DoubleToString(sl,_Digits),", TP ",DoubleToString(tp,_Digits));
            }
         }
      }
   }
}

We’re now at the command center, where "OnTick()" orchestrates the mission, running every price tick but processing only new bars ("iTime()", "lastBartime") to optimize performance, like scanning the galaxy at regular intervals. It updates chart properties ("chart_width", "chart_height", etc.) for visualization, applies trailing stops with "ApplyTrailingStop()" if enabled ("UseTrailingStop") and trades exist ("PositionsTotal()"), and skips pattern detection if trades are open to avoid overtrading.

If no trades are open, it calls "FindExtrema()" and "DetectHeadAndShoulders()". On a bearish pattern detection, it checks if the close price ("iClose()") is below the neckline ("necklinePrice"). If not traded ("IsPatternTraded()", checking "tradedPatterns[]" with "PriceTolerance"=5 pips), it validates the breakout timing ("rsToBreakout <= avgPatternRange * RightShoulderBreakoutMultiplier"). It calculates the neckline slope ("slope") and extends it to a crossing bar or fallback ("barsToExtend=2*lsToNeckStart") using "NeckLineCrossesBar()".

The EA draws the pattern with "DrawTrendLine()" (red lines connecting shoulders, head, neckline, breakout), "DrawTriangle()" (coral triangles for shoulders/head), and "DrawText()" (labels like “LS”, “NECKLINE” with dynamic font sizing, "priceOffsetForText"=10 pips). For a sell, it sets a stop loss above the right shoulder ("BufferPoints"=20 pips), take profit based on pattern height ("patternHeight"), and opens a trade with "obj_Trade.Sell()", logging with "Print()". It adds the pattern to "tradedPatterns[]"via"AddTradedPattern()"`.

For example, on EURUSD, a Head & Shoulders with a head at 1.2100, shoulders at 1.2050/1.2045, neckline at 1.2000, and breakout close at 1.1995 triggers a sell at 0.1 lots, stop loss at 1.2065, take profit at 1.1895 (100-pip height), with red lines and labels drawn. Inverse patterns follow similar logic, using green visuals and buys. This is like launching a starship on a confirmed reversal course, with a star map illuminating the path.

Step 5: Managing the Voyage—Trailing Stops and Cleanup

As we trade, we manage open positions and prepare to dock the starship, ensuring a smooth mission conclusion.

void ApplyTrailingStop (int minTrailPoints, int trailingPoints, CTrade &trade_object, ulong magicNo = 0){
   double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   
   for (int i=PositionsTotal()-1; i>=0; i--){
      ulong ticket = PositionGetTicket(i);
      if (PositionSelectByTicket(ticket) && ticket > 0){
         if (PositionGetString(POSITION_SYMBOL) == _Symbol &&
            (magicNo == 0 || PositionGetInteger(POSITION_MAGIC)==magicNo)){
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentSL = PositionGetDouble(POSITION_SL);
            double currentProfit = PositionGetDouble(POSITION_PROFIT)/(lotsize*SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE));
            
            if (PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY){
               double profitPoints = (bid - openPrice)/_Point;
               if (profitPoints >= minTrailPoints+trailingPoints){
                  double newSl = NormalizeDouble(bid - trailingPoints*_Point,_Digits);
                  if (newSl > openPrice && (newSl > currentSL || currentSL == 0)){
                     if (trade_object.PositionModify(ticket,newSl,PositionGetDouble(POSITION_TP))){
                        Print("Trailing Stop Updated: Ticket ",ticket,", New SL ",DoubleToString(newSl,_Digits));
                     }
                  }
               }
            }
            else if (PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL){
               double profitPoints = (openPrice - ask)/_Point;
               if (profitPoints >= minTrailPoints+trailingPoints){
                  double newSl = NormalizeDouble(ask + trailingPoints*_Point,_Digits);
                  if (newSl < openPrice && (newSl < currentSL || currentSL == 0)){
                     if (trade_object.PositionModify(ticket,newSl,PositionGetDouble(POSITION_TP))){
                        Print("Trailing Stop Updated: Ticket ",ticket,", New SL ",DoubleToString(newSl,_Digits));
                     }
                  }
               }
            }
         }
      }
   }
}

void OnDeinit(const int reason){
}

In the mission control room, "ApplyTrailingStop()" manages open trades, like adjusting your starship’s shields to protect profits. It loops through positions ("PositionsTotal()", "PositionGetTicket()", "PositionSelectByTicket()"), checking if they match the symbol and "magicNumber". For buys, if profits exceed "MinTrailingPoints" (10) plus "TrailingPoints" (30), it sets a new stop loss 30 pips below the bid ("bid - trailingPoints*_Point"), updating with "PositionModify()". Sells adjust upward, ensuring profits are locked in, like trailing a comet’s path. Logs like “Trailing Stop Updated: Ticket 123, New SL 1.2020” track changes, like mission updates.

In the docking bay, "OnDeinit()" is empty, like leaving your starship’s displays active. Adding "ObjectsDeleteAll()" to clear drawn objects (lines, triangles, labels) would ensure a clean chart, like powering down your radar:

ObjectsDeleteAll(0, -1, -1);
ChartRedraw();

For now, the empty "OnDeinit()" keeps things simple, ready for your next voyage. This ensures the EA manages trades effectively while leaving room for cleanup enhancements.

You should now have something as below.

Disclaimer: The ideas and strategies presented in this resource are solely those of the author and are intended for informational and educational purposes only. They do not constitute financial advice, and past performance is not indicative of future results. All materials, including but not limited to text, images, files, and any downloadable content, are protected by copyright and intellectual property laws and are the exclusive property of Forex Algo-Trader or its licensors. Reproduction, distribution, modification, or commercial use of these materials without prior written consent from Forex Algo-Trader is strictly prohibited and may result in legal action. Users are advised to exercise extreme caution, perform thorough independent research, and consult with qualified financial professionals before implementing any trading strategies or decisions based on this resource, as trading in financial markets involves significant risk of loss.

Recent Comments

Go to discussion to Comment or View other Comments

No comments yet. Be the first to comment!