//+------------------------------------------------------------------+ //| lwOopsPatternIndicator.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/en/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/en/users/chachaian" #property version "1.00" //--- Indicator configuration #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- Bullish signal plot #property indicator_label1 "Bullish Oops Signal" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrSeaGreen #property indicator_width1 2 //--- Bearish signal plot #property indicator_label2 "Bearish Oops Signal" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrBlack #property indicator_width2 2 //--- Bullish signal plot #property indicator_label1 "Bullish Oops Signal" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrSeaGreen #property indicator_width1 2 //--- Bearish signal plot #property indicator_label2 "Bearish Oops Signal" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrBlack #property indicator_width2 2 //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input group "Oops Pattern Configurations" input double minimumGapSizePoints = 500; input int maxGapValidityBars = 4; //+------------------------------------------------------------------+ //| Indicator buffers | //+------------------------------------------------------------------+ double bullishSignalBuffer[]; double bearishSignalBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { if(minimumGapSizePoints < 0.0) { PrintFormat("Invalid minimum gap size: %.2f. The value cannot be negative.", minimumGapSizePoints); return INIT_PARAMETERS_INCORRECT; } if(maxGapValidityBars < 0) { PrintFormat("Invalid maximum gap validity: %d. The value cannot be negative.", maxGapValidityBars); return INIT_PARAMETERS_INCORRECT; } ResetLastError(); if(!SetIndexBuffer(0, bullishSignalBuffer, INDICATOR_DATA)) { PrintFormat("Failed to bind the bullish signal buffer. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!SetIndexBuffer(1, bearishSignalBuffer, INDICATOR_DATA)) { PrintFormat("Failed to bind the bearish signal buffer. Error: %d", GetLastError()); return INIT_FAILED; } if(!InitializeArraysAsNonTimeSeries()) return INIT_FAILED; ResetLastError(); if(!PlotIndexSetInteger(0, PLOT_ARROW, 233)) { PrintFormat("Failed to set the bullish arrow symbol. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, 20)) { PrintFormat("Failed to set the bullish arrow shift. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE)) { PrintFormat("Failed to set the bullish plot empty value. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!PlotIndexSetInteger(1, PLOT_ARROW, 234)) { PrintFormat("Failed to set the bearish arrow symbol. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -20)) { PrintFormat("Failed to set the bearish arrow shift. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE)) { PrintFormat("Failed to set the bearish plot empty value. Error: %d", GetLastError()); return INIT_FAILED; } ResetLastError(); if(!IndicatorSetString(INDICATOR_SHORTNAME, "Oops Pattern Indicator")) { PrintFormat("Failed to set the indicator short name. Error: %d", GetLastError()); return INIT_FAILED; } if(!ConfigureChartAppearance()) return INIT_FAILED; return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| 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[]) { //--- Ensure enough bars are available for comparison and updating if(rates_total < 3) return 0; //--- Apply direct indexing to the incoming price arrays if(!InitializePriceArraysAsNonTimeSeries(open, high, low, close)) return prev_calculated; //--- Rebuild all signals on first launch or after a history reset; //--- or when multiple new bars become available at once if(prev_calculated == 0 || prev_calculated > rates_total || rates_total - prev_calculated > 1) { ArrayInitialize(bullishSignalBuffer, EMPTY_VALUE); ArrayInitialize(bearishSignalBuffer, EMPTY_VALUE); MapHistoricalOopsSignals(rates_total, open, high, low, close); return rates_total; } //--- Update only the latest completed bar when new data is added if(prev_calculated < rates_total) UpdateLatestOopsSignalOnNewBar(rates_total, open, high, low, close); return rates_total; } //+------------------------------------------------------------------+ //| Sets indicator buffers to direct indexing | //+------------------------------------------------------------------+ bool InitializeArraysAsNonTimeSeries() { ResetLastError(); if(!ArraySetAsSeries(bullishSignalBuffer, false)) { PrintFormat("Failed to set direct indexing for the bullish buffer. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ArraySetAsSeries(bearishSignalBuffer, false)) { PrintFormat("Failed to set direct indexing for the bearish buffer. Error: %d", GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Sets the price arrays to direct indexing | //+------------------------------------------------------------------+ bool InitializePriceArraysAsNonTimeSeries(const double &open[], const double &high[], const double &low[], const double &close[]) { ResetLastError(); if(!ArraySetAsSeries(open, false)) { PrintFormat("Failed to set direct indexing for the Open array. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ArraySetAsSeries(high, false)) { PrintFormat("Failed to set direct indexing for the High array. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ArraySetAsSeries(low, false)) { PrintFormat("Failed to set direct indexing for the Low array. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ArraySetAsSeries(close, false)) { PrintFormat("Failed to set direct indexing for the Close array. Error: %d", GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Returns true if the bar opens with a valid gap down | //+------------------------------------------------------------------+ bool IsGapDownAtIndex(const int i, const double &open[], const double &low[]) { //--- Index 0 has no previous bar for comparison if(i < 1) return false; //--- Measure the distance from the current open to the previous low double currentOpen = open[i]; double previousLow = low[i - 1]; double gapSize = previousLow - currentOpen; //--- Accept only gaps that meet the configured minimum size return gapSize >= minimumGapSizePoints * _Point; } //+------------------------------------------------------------------+ //| Returns true if the bar opens with a valid gap up | //+------------------------------------------------------------------+ bool IsGapUpAtIndex(const int i, const double &open[], const double &high[]) { //--- Index 0 has no previous bar for comparison if(i < 1) return false; //--- Measure the distance from the previous high to the current open double currentOpen = open[i]; double previousHigh = high[i - 1]; double gapSize = currentOpen - previousHigh; //--- Accept only gaps that meet the configured minimum size return gapSize >= minimumGapSizePoints * _Point; } //+------------------------------------------------------------------+ //| Returns true if a bullish Oops signal exists at the index | //+------------------------------------------------------------------+ bool IsBullishOopsSignalAtIndex(const int i, const double &open[], const double &high[], const double &low[], const double &close[]) { //--- Index 0 has no previous bar for gap comparison if(i < 1) return false; //--- Path 1: the gap-down bar fills the gap by its own close if(IsGapDownAtIndex(i, open, low)) { double previousLow = low[i - 1]; if(close[i] >= previousLow) return true; } //--- Path 2: search recent bars for an earlier valid gap down int startGapIndex = MathMax(1, i - maxGapValidityBars); for(int j = startGapIndex; j < i; j++) { if(!IsGapDownAtIndex(j, open, low)) continue; double previousLow = low[j - 1]; //--- The current bar must close back at or above the gap boundary if(close[i] < previousLow) continue; //--- Reject the signal if the gap was already filled earlier bool alreadyFilledEarlier = false; for(int k = j; k < i; k++) { if(close[k] >= previousLow) { alreadyFilledEarlier = true; break; } } if(!alreadyFilledEarlier) return true; } return false; } //+------------------------------------------------------------------+ //| Returns true if a bearish Oops signal exists at the index | //+------------------------------------------------------------------+ bool IsBearishOopsSignalAtIndex(const int i, const double &open[], const double &high[], const double &low[], const double &close[]) { //--- Index 0 has no previous bar for gap comparison if(i < 1) return false; //--- Path 1: the gap-up bar fills the gap by its own close if(IsGapUpAtIndex(i, open, high)) { double previousHigh = high[i - 1]; if(close[i] <= previousHigh) return true; } //--- Path 2: search recent bars for an earlier valid gap up int startGapIndex = MathMax(1, i - maxGapValidityBars); for(int j = startGapIndex; j < i; j++) { if(!IsGapUpAtIndex(j, open, high)) continue; double previousHigh = high[j - 1]; //--- The current bar must close back at or below the gap boundary if(close[i] > previousHigh) continue; //--- Reject the signal if the gap was already filled earlier bool alreadyFilledEarlier = false; for(int k = j; k < i; k++) { if(close[k] <= previousHigh) { alreadyFilledEarlier = true; break; } } if(!alreadyFilledEarlier) return true; } return false; } //+------------------------------------------------------------------+ //| Maps all completed historical Oops signals | //+------------------------------------------------------------------+ void MapHistoricalOopsSignals(const int rates_total, const double &open[], const double &high[], const double &low[], const double &close[]) { if(rates_total < 3) return; //--- Index 0 has no previous bar and cannot produce a signal bullishSignalBuffer[0] = EMPTY_VALUE; bearishSignalBuffer[0] = EMPTY_VALUE; //--- Evaluate completed historical bars only for(int i = 1; i < rates_total - 1; i++) { bullishSignalBuffer[i] = EMPTY_VALUE; bearishSignalBuffer[i] = EMPTY_VALUE; if(IsBullishOopsSignalAtIndex(i, open, high, low, close)) bullishSignalBuffer[i] = low[i] - (10 * _Point); if(IsBearishOopsSignalAtIndex(i, open, high, low, close)) bearishSignalBuffer[i] = high[i] + (10 * _Point); } //--- Keep the current forming bar empty int currentIndex = rates_total - 1; bullishSignalBuffer[currentIndex] = EMPTY_VALUE; bearishSignalBuffer[currentIndex] = EMPTY_VALUE; } //+------------------------------------------------------------------+ //| Configures the chart appearance | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { ResetLastError(); if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)) { PrintFormat("Failed to set the chart background color. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_SHOW_GRID, false)) { PrintFormat("Failed to hide the chart grid. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES)) { PrintFormat("Failed to set the chart to candlestick mode. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)) { PrintFormat("Failed to set the chart foreground color. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrSeaGreen)) { PrintFormat("Failed to set the bullish candle color. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)) { PrintFormat("Failed to set the bearish candle color. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)) { PrintFormat("Failed to set the chart-up color. Error: %d", GetLastError()); return false; } ResetLastError(); if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)) { PrintFormat("Failed to set the chart-down color. Error: %d", GetLastError()); return false; } ResetLastError(); ChartRedraw(0); int errorCode = GetLastError(); if(errorCode != ERR_SUCCESS) { PrintFormat("Chart redraw reported an error. Error: %d", errorCode); return false; } return true; } //+------------------------------------------------------------------+ //| Updates the signal on the latest completed bar | //+------------------------------------------------------------------+ void UpdateLatestOopsSignalOnNewBar(const int rates_total, const double &open[], const double &high[], const double &low[], const double &close[]) { if(rates_total < 3) return; int signalIndex = rates_total - 2; int currentIndex = rates_total - 1; //--- Clear the latest completed and current bars before recalculation bullishSignalBuffer[signalIndex] = EMPTY_VALUE; bearishSignalBuffer[signalIndex] = EMPTY_VALUE; bullishSignalBuffer[currentIndex] = EMPTY_VALUE; bearishSignalBuffer[currentIndex] = EMPTY_VALUE; //--- Evaluate the latest completed bar using the established rules if(IsBullishOopsSignalAtIndex(signalIndex, open, high, low, close)) bullishSignalBuffer[signalIndex] = low[signalIndex] - (10 * _Point); if(IsBearishOopsSignalAtIndex(signalIndex, open, high, low, close)) bearishSignalBuffer[signalIndex] = high[signalIndex] + (10 * _Point); } //+------------------------------------------------------------------+