2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-19 06:06:20 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| lwOopsPatternExpert.mq5 |
|
|
|
|
|
//| Copyright 2026, MetaQuotes Ltd. |
|
|
|
|
|
//| https://www.mql5.com |
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
#property copyright "Copyright 2026, MetaQuotes Ltd. Developer: Chacha Ian"
|
|
|
|
|
#property link "https://www.mql5.com/en/users/chachaian"
|
|
|
|
|
#property version "1.00"
|
|
|
|
|
#property description "Detects and trades Larry Williams' Oops gap reversal pattern."
|
|
|
|
|
#property description "The EA tracks qualifying gaps, confirms closed-bar reversals,"
|
|
|
|
|
#property description "and calculates the stop loss, take profit, and position size."
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Standard Libraries |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Custom Enumerations |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
enum ENUM_OOPS_TRADE_DIRECTION
|
|
|
|
|
{
|
|
|
|
|
OOPS_TRADE_LONG_ONLY,
|
|
|
|
|
OOPS_TRADE_SHORT_ONLY,
|
|
|
|
|
OOPS_TRADE_BOTH
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum ENUM_LOT_SIZE_INPUT_MODE
|
|
|
|
|
{
|
|
|
|
|
MODE_MANUAL,
|
|
|
|
|
MODE_AUTO
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| User Input Variables |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
input group "Information"
|
|
|
|
|
input ulong magicNumber = 254700680002;
|
|
|
|
|
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
|
|
|
|
|
|
|
|
|
|
input group "Oops Pattern Configurations"
|
|
|
|
|
input double minimumGapSizePoints = 500;
|
|
|
|
|
input int maxGapValidityBars = 3;
|
|
|
|
|
|
|
|
|
|
input group "Trade and Risk Management"
|
|
|
|
|
input ENUM_OOPS_TRADE_DIRECTION tradeDirection = OOPS_TRADE_BOTH;
|
|
|
|
|
input double riskRewardRatio = 2.5;
|
|
|
|
|
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode = MODE_AUTO;
|
|
|
|
|
input double riskPerTradePercent = 1.0;
|
|
|
|
|
input double positionSize = 0.1;
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Oops Pattern State |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Stores the detected gap, its lifecycle, and prepared trade data. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
struct OopsPatternState
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
bool gapDetected;
|
|
|
|
|
bool isGapUp;
|
|
|
|
|
bool isGapDown;
|
|
|
|
|
datetime gapBarTime;
|
|
|
|
|
double gapOpenPrice;
|
|
|
|
|
double gapBarHigh;
|
|
|
|
|
double gapBarLow;
|
|
|
|
|
double previousHigh;
|
|
|
|
|
double previousLow;
|
|
|
|
|
int barsSinceGap;
|
|
|
|
|
int maxBarsToFill;
|
|
|
|
|
bool gapFilled;
|
|
|
|
|
bool gapInvalidated;
|
|
|
|
|
double bullishTakeProfit;
|
|
|
|
|
double bearishTakeProfit;
|
|
|
|
|
double lotSize;
|
|
|
|
|
ENUM_ORDER_TYPE orderType;
|
|
|
|
|
double positionEntryPrice;
|
2026-07-27 13:45:35 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Shared Program State |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
OopsPatternState oopsState; // Active Oops setup tracked across bars
|
2026-07-31 13:14:04 +03:00
|
|
|
CTrade Trade; // Submits orders and exposes execution results
|
|
|
|
|
double askPrice; // Latest verified price used for buy execution
|
|
|
|
|
double bidPrice; // Latest verified price used for sell execution
|
2026-07-27 13:45:35 +03:00
|
|
|
datetime currentTime; // Latest terminal time received by the EA
|
|
|
|
|
datetime lastBarOpenTime; // Opening time of the last processed bar
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Reads a double-valued symbol property safely |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetSymbolDoubleValue(string symbol,
|
|
|
|
|
ENUM_SYMBOL_INFO_DOUBLE property,
|
|
|
|
|
double &value,
|
|
|
|
|
string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Clear any earlier runtime error and initialize the output
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
value = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Request the selected symbol property
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!SymbolInfoDouble(symbol, property, value))
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
PrintFormat("%s: Failed to read symbol property %s for %s. Error %d.",
|
|
|
|
|
context,
|
|
|
|
|
EnumToString(property),
|
|
|
|
|
symbol,
|
|
|
|
|
errorCode);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Reads the opening time of a selected bar safely |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetBarTime(string symbol,
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
int shift,
|
|
|
|
|
datetime &value,
|
|
|
|
|
string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Request the opening time of the selected bar
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
value = iTime(symbol, tf, shift);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- A zero value indicates that the bar data is unavailable
|
2026-07-27 13:45:35 +03:00
|
|
|
if(value == 0)
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
PrintFormat("%s: Failed to read bar time for %s, timeframe %s, "
|
|
|
|
|
"shift %d. Error %d.",
|
|
|
|
|
context,
|
|
|
|
|
symbol,
|
|
|
|
|
EnumToString(tf),
|
|
|
|
|
shift,
|
|
|
|
|
errorCode);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Reads the opening price of a selected bar safely |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetBarOpen(string symbol,
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
int shift,
|
|
|
|
|
double &value,
|
|
|
|
|
string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Request the opening price of the selected bar
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
value = iOpen(symbol, tf, shift);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject unavailable or invalid price data
|
2026-07-27 13:45:35 +03:00
|
|
|
if(value == 0.0)
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
PrintFormat("%s: Failed to read bar open for %s, timeframe %s, "
|
|
|
|
|
"shift %d. Error %d.",
|
|
|
|
|
context,
|
|
|
|
|
symbol,
|
|
|
|
|
EnumToString(tf),
|
|
|
|
|
shift,
|
|
|
|
|
errorCode);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Reads the highest price of a selected bar safely |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetBarHigh(string symbol,
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
int shift,
|
|
|
|
|
double &value,
|
|
|
|
|
string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Request the highest price of the selected bar
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
value = iHigh(symbol, tf, shift);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject unavailable or invalid price data
|
2026-07-27 13:45:35 +03:00
|
|
|
if(value == 0.0)
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
PrintFormat("%s: Failed to read bar high for %s, timeframe %s, "
|
|
|
|
|
"shift %d. Error %d.",
|
|
|
|
|
context,
|
|
|
|
|
symbol,
|
|
|
|
|
EnumToString(tf),
|
|
|
|
|
shift,
|
|
|
|
|
errorCode);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Reads the lowest price of a selected bar safely |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetBarLow(string symbol,
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
int shift,
|
|
|
|
|
double &value,
|
|
|
|
|
string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Request the lowest price of the selected bar
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
value = iLow(symbol, tf, shift);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject unavailable or invalid price data
|
2026-07-27 13:45:35 +03:00
|
|
|
if(value == 0.0)
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
PrintFormat("%s: Failed to read bar low for %s, timeframe %s, "
|
|
|
|
|
"shift %d. Error %d.",
|
|
|
|
|
context,
|
|
|
|
|
symbol,
|
|
|
|
|
EnumToString(tf),
|
|
|
|
|
shift,
|
|
|
|
|
errorCode);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Reads the closing price of a selected bar safely |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool GetBarClose(string symbol,
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
int shift,
|
|
|
|
|
double &value,
|
|
|
|
|
string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Request the closing price of the selected bar
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
value = iClose(symbol, tf, shift);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject unavailable or invalid price data
|
2026-07-27 13:45:35 +03:00
|
|
|
if(value == 0.0)
|
|
|
|
|
{
|
|
|
|
|
int errorCode = GetLastError();
|
|
|
|
|
|
|
|
|
|
PrintFormat("%s: Failed to read bar close for %s, timeframe %s, "
|
|
|
|
|
"shift %d. Error %d.",
|
|
|
|
|
context,
|
|
|
|
|
symbol,
|
|
|
|
|
EnumToString(tf),
|
|
|
|
|
shift,
|
|
|
|
|
errorCode);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Resets the stored Oops setup to a neutral state |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void ResetOopsPatternState()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Clear the identity of the previous setup
|
|
|
|
|
oopsState.gapDetected = false;
|
|
|
|
|
oopsState.isGapUp = false;
|
|
|
|
|
oopsState.isGapDown = false;
|
|
|
|
|
oopsState.gapBarTime = 0;
|
|
|
|
|
oopsState.gapOpenPrice = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Clear the stored reference prices
|
|
|
|
|
oopsState.gapBarHigh = 0.0;
|
|
|
|
|
oopsState.gapBarLow = 0.0;
|
|
|
|
|
oopsState.previousHigh = 0.0;
|
|
|
|
|
oopsState.previousLow = 0.0;
|
|
|
|
|
|
|
|
|
|
//--- Restore the setup lifecycle defaults
|
|
|
|
|
oopsState.barsSinceGap = 0;
|
|
|
|
|
oopsState.maxBarsToFill = maxGapValidityBars;
|
|
|
|
|
oopsState.gapFilled = false;
|
|
|
|
|
oopsState.gapInvalidated = false;
|
|
|
|
|
|
|
|
|
|
//--- Clear prepared trade values and restore input-based defaults
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.bullishTakeProfit = 0.0;
|
|
|
|
|
oopsState.bearishTakeProfit = 0.0;
|
|
|
|
|
oopsState.lotSize = positionSize;
|
|
|
|
|
oopsState.orderType = ORDER_TYPE_BUY;
|
|
|
|
|
oopsState.positionEntryPrice = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Initialize the stored entry with a verified market price
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetSymbolDoubleValue(_Symbol,
|
|
|
|
|
SYMBOL_ASK,
|
|
|
|
|
oopsState.positionEntryPrice,
|
|
|
|
|
"ResetOopsPatternState"))
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Keep a neutral value when the symbol price is unavailable
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.positionEntryPrice = 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Configures the chart for clear visual testing |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool ConfigureChartAppearance()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Apply a white background
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set chart background. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Remove the grid to reduce visual clutter
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_SHOW_GRID, false))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to hide the chart grid. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Display prices as candlesticks
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_MODE, CHART_CANDLES))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set candle chart mode. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Use black for chart labels and price-scale text
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set chart foreground. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Keep both candle bodies white
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrWhite))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set the bullish candle color. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrWhite))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set the bearish candle color. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Distinguish bullish and bearish candle outlines
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set the chart-up color. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack))
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: Failed to set the chart-down color. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Apply the queued chart-property changes
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
ChartRedraw(0);
|
|
|
|
|
|
|
|
|
|
int redrawError = GetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(redrawError != 0)
|
|
|
|
|
{
|
|
|
|
|
Print("ConfigureChartAppearance: ChartRedraw reported error ",
|
|
|
|
|
redrawError, ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Returns true once when a new bar opens on the selected timeframe |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsNewBar(string symbol,
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
datetime &lastTm)
|
|
|
|
|
{
|
|
|
|
|
datetime currentTm = 0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Stop when the opening time of bar zero is unavailable
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarTime(symbol,
|
|
|
|
|
tf,
|
|
|
|
|
0,
|
|
|
|
|
currentTm,
|
|
|
|
|
"IsNewBar"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Matching timestamps indicate that this bar was already processed
|
2026-07-27 13:45:35 +03:00
|
|
|
if(currentTm == lastTm)
|
|
|
|
|
return false;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Store the verified timestamp before allowing strategy processing
|
2026-07-27 13:45:35 +03:00
|
|
|
lastTm = currentTm;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Returns true when the current bar opens far enough below the |
|
2026-07-31 13:14:04 +03:00
|
|
|
//| previous bar's low to qualify as a gap-down setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsGapDown()
|
|
|
|
|
{
|
|
|
|
|
double currentOpen = 0.0;
|
|
|
|
|
double previousLow = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- A gap cannot be evaluated without both reference prices
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarOpen(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
currentOpen,
|
|
|
|
|
"IsGapDown"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarLow(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
previousLow,
|
|
|
|
|
"IsGapDown"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Measure the distance from the current open to the previous low
|
2026-07-27 13:45:35 +03:00
|
|
|
double gapSize = previousLow - currentOpen;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Convert the configured point threshold into a price distance
|
2026-07-27 13:45:35 +03:00
|
|
|
return(gapSize >= minimumGapSizePoints * _Point);
|
|
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Returns true when the current bar opens far enough above the |
|
2026-07-31 13:14:04 +03:00
|
|
|
//| previous bar's high to qualify as a gap-up setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsGapUp()
|
|
|
|
|
{
|
|
|
|
|
double currentOpen = 0.0;
|
|
|
|
|
double previousHigh = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- A gap cannot be evaluated without both reference prices
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarOpen(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
currentOpen,
|
|
|
|
|
"IsGapUp"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarHigh(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
previousHigh,
|
|
|
|
|
"IsGapUp"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Measure the distance from the previous high to the current open
|
2026-07-27 13:45:35 +03:00
|
|
|
double gapSize = currentOpen - previousHigh;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Convert the configured point threshold into a price distance
|
2026-07-27 13:45:35 +03:00
|
|
|
return(gapSize >= minimumGapSizePoints * _Point);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Detects a qualifying gap and stores one complete Oops setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void DetectAndInitializeOopsGap()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Preserve the current setup until it confirms or expires
|
2026-07-27 13:45:35 +03:00
|
|
|
if(oopsState.gapDetected)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- A gap up prepares a possible bearish reversal
|
2026-07-27 13:45:35 +03:00
|
|
|
if(IsGapUp())
|
|
|
|
|
{
|
|
|
|
|
datetime gapBarTime = 0;
|
|
|
|
|
double gapOpenPrice = 0.0;
|
|
|
|
|
double previousHigh = 0.0;
|
|
|
|
|
double previousLow = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Collect every required value before changing shared state
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarTime(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
gapBarTime,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarOpen(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
gapOpenPrice,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarHigh(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
previousHigh,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarLow(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
previousLow,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Commit the bearish setup only after every data read succeeds
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.gapDetected = true;
|
|
|
|
|
oopsState.isGapUp = true;
|
|
|
|
|
oopsState.isGapDown = false;
|
|
|
|
|
oopsState.gapBarTime = gapBarTime;
|
|
|
|
|
oopsState.gapOpenPrice = gapOpenPrice;
|
|
|
|
|
oopsState.previousHigh = previousHigh;
|
|
|
|
|
oopsState.previousLow = previousLow;
|
|
|
|
|
oopsState.barsSinceGap = 0;
|
|
|
|
|
oopsState.maxBarsToFill = maxGapValidityBars;
|
|
|
|
|
oopsState.gapFilled = false;
|
|
|
|
|
oopsState.gapInvalidated = false;
|
|
|
|
|
oopsState.orderType = ORDER_TYPE_SELL;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- A gap down prepares a possible bullish reversal
|
2026-07-27 13:45:35 +03:00
|
|
|
if(IsGapDown())
|
|
|
|
|
{
|
|
|
|
|
datetime gapBarTime = 0;
|
|
|
|
|
double gapOpenPrice = 0.0;
|
|
|
|
|
double previousHigh = 0.0;
|
|
|
|
|
double previousLow = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Collect every required value before changing shared state
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarTime(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
gapBarTime,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarOpen(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
gapOpenPrice,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarHigh(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
previousHigh,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetBarLow(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
previousLow,
|
|
|
|
|
"DetectAndInitializeOopsGap"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Commit the bullish setup only after every data read succeeds
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.gapDetected = true;
|
|
|
|
|
oopsState.isGapUp = false;
|
|
|
|
|
oopsState.isGapDown = true;
|
|
|
|
|
oopsState.gapBarTime = gapBarTime;
|
|
|
|
|
oopsState.gapOpenPrice = gapOpenPrice;
|
|
|
|
|
oopsState.previousHigh = previousHigh;
|
|
|
|
|
oopsState.previousLow = previousLow;
|
|
|
|
|
oopsState.barsSinceGap = 0;
|
|
|
|
|
oopsState.maxBarsToFill = maxGapValidityBars;
|
|
|
|
|
oopsState.gapFilled = false;
|
|
|
|
|
oopsState.gapInvalidated = false;
|
|
|
|
|
oopsState.orderType = ORDER_TYPE_BUY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Updates the age of the active Oops setup and removes it after |
|
2026-07-31 13:14:04 +03:00
|
|
|
//| the configured confirmation window expires |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateOopsGapState()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- There is no lifecycle to update without an active setup
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!oopsState.gapDetected)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
datetime currentBarTime = 0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Preserve the current state when bar timing cannot be verified
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarTime(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
0,
|
|
|
|
|
currentBarTime,
|
|
|
|
|
"UpdateOopsGapState"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- The gap bar starts the setup but is not an elapsed fill bar
|
2026-07-27 13:45:35 +03:00
|
|
|
if(currentBarTime == oopsState.gapBarTime)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Count the newly opened bar once within the new-bar workflow
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.barsSinceGap++;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Remove the setup after its allowed validity window is exceeded
|
2026-07-27 13:45:35 +03:00
|
|
|
if(oopsState.barsSinceGap > oopsState.maxBarsToFill)
|
|
|
|
|
{
|
|
|
|
|
oopsState.gapInvalidated = true;
|
|
|
|
|
ResetOopsPatternState();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Returns true when an active gap-down setup confirms a bullish |
|
2026-07-31 13:14:04 +03:00
|
|
|
//| reversal through the close of a later completed bar |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsBullishSignal()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Accept only an active, unprocessed gap-down setup
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!oopsState.gapDetected ||
|
|
|
|
|
!oopsState.isGapDown ||
|
|
|
|
|
oopsState.gapFilled ||
|
|
|
|
|
oopsState.gapInvalidated)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Require at least one completed bar after the original gap bar
|
2026-07-27 13:45:35 +03:00
|
|
|
if(oopsState.barsSinceGap < 1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
double closePrice = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Bar one is the most recently completed candle
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarClose(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
closePrice,
|
|
|
|
|
"IsBullishSignal"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Confirm only after price closes back at or above the previous low
|
2026-07-27 13:45:35 +03:00
|
|
|
if(closePrice < oopsState.previousLow)
|
|
|
|
|
return false;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Mark the setup as filled before returning the signal
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.gapFilled = true;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Returns true when an active gap-up setup confirms a bearish |
|
2026-07-31 13:14:04 +03:00
|
|
|
//| reversal through the close of a later completed bar |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsBearishSignal()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Accept only an active, unprocessed gap-up setup
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!oopsState.gapDetected ||
|
|
|
|
|
!oopsState.isGapUp ||
|
|
|
|
|
oopsState.gapFilled ||
|
|
|
|
|
oopsState.gapInvalidated)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Require at least one completed bar after the original gap bar
|
2026-07-27 13:45:35 +03:00
|
|
|
if(oopsState.barsSinceGap < 1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
double closePrice = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Bar one is the most recently completed candle
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarClose(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
1,
|
|
|
|
|
closePrice,
|
|
|
|
|
"IsBearishSignal"))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Confirm only after price closes back at or below the previous high
|
2026-07-27 13:45:35 +03:00
|
|
|
if(closePrice > oopsState.previousHigh)
|
|
|
|
|
return false;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Mark the setup as filled before returning the signal
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.gapFilled = true;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Stores the gap-bar low as the stop reference for a buy setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateBullishGapBarStopLevel()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Locate the original gap bar using its stored opening time
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
int gapIndex = iBarShift(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
oopsState.gapBarTime);
|
|
|
|
|
|
|
|
|
|
if(gapIndex == -1)
|
|
|
|
|
{
|
|
|
|
|
Print("UpdateBullishGapBarStopLevel: Failed to locate the gap bar. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double gapBarLow = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the low of the recovered gap bar
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarLow(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
gapIndex,
|
|
|
|
|
gapBarLow,
|
|
|
|
|
"UpdateBullishGapBarStopLevel"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Store the structural stop reference for the bullish setup
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.gapBarLow = gapBarLow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Stores the gap-bar high as the stop reference for a sell setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateBearishGapBarStopLevel()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Locate the original gap bar using its stored opening time
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
|
|
|
|
|
|
|
|
|
int gapIndex = iBarShift(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
oopsState.gapBarTime);
|
|
|
|
|
|
|
|
|
|
if(gapIndex == -1)
|
|
|
|
|
{
|
|
|
|
|
Print("UpdateBearishGapBarStopLevel: Failed to locate the gap bar. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double gapBarHigh = 0.0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the high of the recovered gap bar
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetBarHigh(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
gapIndex,
|
|
|
|
|
gapBarHigh,
|
|
|
|
|
"UpdateBearishGapBarStopLevel"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Store the structural stop reference for the bearish setup
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.gapBarHigh = gapBarHigh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Calculates the take-profit level for a confirmed buy setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateBullishTakeProfit(double entryPrice)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
double stopLoss = oopsState.gapBarLow;
|
2026-07-27 13:45:35 +03:00
|
|
|
double riskDistance = entryPrice - stopLoss;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject a stop placed at or above the intended buy entry
|
2026-07-27 13:45:35 +03:00
|
|
|
if(riskDistance <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
PrintFormat("UpdateBullishTakeProfit: Invalid prices. Entry %.*f, "
|
|
|
|
|
"stop loss %.*f.",
|
|
|
|
|
_Digits,
|
|
|
|
|
entryPrice,
|
|
|
|
|
_Digits,
|
|
|
|
|
stopLoss);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Project the target above the entry by the configured risk multiple
|
2026-07-27 13:45:35 +03:00
|
|
|
double projectedTP = entryPrice +
|
|
|
|
|
(riskDistance * riskRewardRatio);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Store a price normalized to the symbol's number of digits
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.bullishTakeProfit = NormalizeDouble(projectedTP,
|
2026-07-31 13:14:04 +03:00
|
|
|
_Digits);
|
2026-07-27 13:45:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Calculates the take-profit level for a confirmed sell setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateBearishTakeProfit(double entryPrice)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
double stopLoss = oopsState.gapBarHigh;
|
2026-07-27 13:45:35 +03:00
|
|
|
double riskDistance = stopLoss - entryPrice;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject a stop placed at or below the intended sell entry
|
2026-07-27 13:45:35 +03:00
|
|
|
if(riskDistance <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
PrintFormat("UpdateBearishTakeProfit: Invalid prices. Entry %.*f, "
|
|
|
|
|
"stop loss %.*f.",
|
|
|
|
|
_Digits,
|
|
|
|
|
entryPrice,
|
|
|
|
|
_Digits,
|
|
|
|
|
stopLoss);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Project the target below the entry by the configured risk multiple
|
2026-07-27 13:45:35 +03:00
|
|
|
double projectedTP = entryPrice -
|
|
|
|
|
(riskDistance * riskRewardRatio);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Store a price normalized to the symbol's number of digits
|
2026-07-27 13:45:35 +03:00
|
|
|
oopsState.bearishTakeProfit = NormalizeDouble(projectedTP,
|
2026-07-31 13:14:04 +03:00
|
|
|
_Digits);
|
2026-07-27 13:45:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Calculates a broker-compatible volume from the configured risk |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
double CalculatePositionSizeByRisk(ENUM_ORDER_TYPE orderType,
|
|
|
|
|
double entryPrice,
|
|
|
|
|
double stopLossPrice)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Use the account balance as the base for percentage risk
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
|
|
|
|
|
|
if(accountBalance <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
Print("CalculatePositionSizeByRisk: Invalid account balance. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Convert the selected percentage into a monetary risk amount
|
2026-07-27 13:45:35 +03:00
|
|
|
double amountAtRisk = (riskPerTradePercent / 100.0) *
|
|
|
|
|
accountBalance;
|
|
|
|
|
|
|
|
|
|
if(amountAtRisk <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
Print("CalculatePositionSizeByRisk: The calculated risk amount is invalid.");
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Estimate the loss produced by one lot at the selected stop
|
2026-07-27 13:45:35 +03:00
|
|
|
double lossPerLot = 0.0;
|
|
|
|
|
|
|
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!OrderCalcProfit(orderType,
|
|
|
|
|
_Symbol,
|
|
|
|
|
1.0,
|
|
|
|
|
entryPrice,
|
|
|
|
|
stopLossPrice,
|
|
|
|
|
lossPerLot))
|
|
|
|
|
{
|
|
|
|
|
Print("CalculatePositionSizeByRisk: OrderCalcProfit failed. Error ",
|
|
|
|
|
GetLastError(), ".");
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lossPerLot = MathAbs(lossPerLot);
|
|
|
|
|
|
|
|
|
|
if(lossPerLot <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
Print("CalculatePositionSizeByRisk: Loss per lot is invalid.");
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Divide the permitted loss by the estimated one-lot loss
|
2026-07-27 13:45:35 +03:00
|
|
|
double volume = amountAtRisk / lossPerLot;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the broker's volume constraints for the current symbol
|
2026-07-27 13:45:35 +03:00
|
|
|
double minLot = 0.0;
|
|
|
|
|
double maxLot = 0.0;
|
|
|
|
|
double lotStep = 0.0;
|
|
|
|
|
|
|
|
|
|
if(!GetSymbolDoubleValue(_Symbol,
|
|
|
|
|
SYMBOL_VOLUME_MIN,
|
|
|
|
|
minLot,
|
|
|
|
|
"CalculatePositionSizeByRisk"))
|
|
|
|
|
{
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetSymbolDoubleValue(_Symbol,
|
|
|
|
|
SYMBOL_VOLUME_MAX,
|
|
|
|
|
maxLot,
|
|
|
|
|
"CalculatePositionSizeByRisk"))
|
|
|
|
|
{
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!GetSymbolDoubleValue(_Symbol,
|
|
|
|
|
SYMBOL_VOLUME_STEP,
|
|
|
|
|
lotStep,
|
|
|
|
|
"CalculatePositionSizeByRisk"))
|
|
|
|
|
{
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Reject inconsistent broker volume specifications
|
2026-07-27 13:45:35 +03:00
|
|
|
if(minLot <= 0.0 ||
|
|
|
|
|
maxLot <= 0.0 ||
|
|
|
|
|
lotStep <= 0.0 ||
|
|
|
|
|
minLot > maxLot)
|
|
|
|
|
{
|
|
|
|
|
Print("CalculatePositionSizeByRisk: Invalid broker volume constraints.");
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Round down so normalization does not increase the intended risk
|
2026-07-27 13:45:35 +03:00
|
|
|
volume = MathFloor(volume / lotStep) * lotStep;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Clamp the result to the broker's permitted range
|
2026-07-27 13:45:35 +03:00
|
|
|
if(volume < minLot)
|
|
|
|
|
volume = minLot;
|
|
|
|
|
|
|
|
|
|
if(volume > maxLot)
|
|
|
|
|
volume = maxLot;
|
|
|
|
|
|
|
|
|
|
return NormalizeDouble(volume, 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Updates the volume prepared for the current Oops setup |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void UpdateOopsPositionSize()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Manual mode uses the fixed volume selected in the inputs
|
2026-07-27 13:45:35 +03:00
|
|
|
if(lotSizeMode == MODE_MANUAL)
|
|
|
|
|
{
|
|
|
|
|
oopsState.lotSize = positionSize;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Select the structural stop associated with the setup direction
|
2026-07-27 13:45:35 +03:00
|
|
|
double stopLossPrice = 0.0;
|
|
|
|
|
|
|
|
|
|
if(oopsState.orderType == ORDER_TYPE_BUY)
|
|
|
|
|
stopLossPrice = oopsState.gapBarLow;
|
|
|
|
|
else
|
|
|
|
|
if(oopsState.orderType == ORDER_TYPE_SELL)
|
|
|
|
|
stopLossPrice = oopsState.gapBarHigh;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Calculate the volume from the prepared entry and stop prices
|
2026-07-27 13:45:35 +03:00
|
|
|
double calculatedLot = CalculatePositionSizeByRisk(
|
|
|
|
|
oopsState.orderType,
|
|
|
|
|
oopsState.positionEntryPrice,
|
|
|
|
|
stopLossPrice
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Fall back to the manual value when automatic sizing fails
|
2026-07-27 13:45:35 +03:00
|
|
|
if(calculatedLot <= 0.0)
|
|
|
|
|
{
|
|
|
|
|
Print("UpdateOopsPositionSize: Falling back to manual lot size.");
|
|
|
|
|
oopsState.lotSize = positionSize;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oopsState.lotSize = calculatedLot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Returns true when an open buy position uses the supplied magic |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsThereAnActiveBuyPosition(ulong magic)
|
|
|
|
|
{
|
|
|
|
|
int totalPositions = PositionsTotal();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Inspect every open position
|
2026-07-27 13:45:35 +03:00
|
|
|
for(int i = totalPositions - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- PositionGetTicket() also selects the position for property access
|
2026-07-27 13:45:35 +03:00
|
|
|
ulong ticket = PositionGetTicket(i);
|
|
|
|
|
|
|
|
|
|
if(ticket == 0)
|
|
|
|
|
{
|
|
|
|
|
Print("IsThereAnActiveBuyPosition: Failed to select position ",
|
|
|
|
|
i, ". Error ", GetLastError(), ".");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long positionMagic = 0;
|
|
|
|
|
long positionType = -1;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the identifier assigned by the opening Expert Advisor
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!PositionGetInteger(POSITION_MAGIC, positionMagic))
|
|
|
|
|
{
|
|
|
|
|
Print("IsThereAnActiveBuyPosition: Failed to read POSITION_MAGIC "
|
|
|
|
|
"for ticket ", ticket, ". Error ", GetLastError(), ".");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the direction of the selected position
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!PositionGetInteger(POSITION_TYPE, positionType))
|
|
|
|
|
{
|
|
|
|
|
Print("IsThereAnActiveBuyPosition: Failed to read POSITION_TYPE "
|
|
|
|
|
"for ticket ", ticket, ". Error ", GetLastError(), ".");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Stop after finding a buy position managed by this EA
|
2026-07-27 13:45:35 +03:00
|
|
|
if((ulong)positionMagic == magic &&
|
|
|
|
|
(ENUM_POSITION_TYPE)positionType == POSITION_TYPE_BUY)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Returns true when an open sell position uses the supplied magic |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsThereAnActiveSellPosition(ulong magic)
|
|
|
|
|
{
|
|
|
|
|
int totalPositions = PositionsTotal();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Inspect every open position
|
2026-07-27 13:45:35 +03:00
|
|
|
for(int i = totalPositions - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Select the position and obtain its ticket
|
2026-07-27 13:45:35 +03:00
|
|
|
ulong ticket = PositionGetTicket(i);
|
|
|
|
|
|
|
|
|
|
if(ticket == 0)
|
|
|
|
|
{
|
|
|
|
|
Print("IsThereAnActiveSellPosition: Failed to select position ",
|
|
|
|
|
i, ". Error ", GetLastError(), ".");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long positionMagic = 0;
|
|
|
|
|
long positionType = -1;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the identifier assigned by the opening Expert Advisor
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!PositionGetInteger(POSITION_MAGIC, positionMagic))
|
|
|
|
|
{
|
|
|
|
|
Print("IsThereAnActiveSellPosition: Failed to read POSITION_MAGIC "
|
|
|
|
|
"for ticket ", ticket, ". Error ", GetLastError(), ".");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the direction of the selected position
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetLastError();
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!PositionGetInteger(POSITION_TYPE, positionType))
|
|
|
|
|
{
|
|
|
|
|
Print("IsThereAnActiveSellPosition: Failed to read POSITION_TYPE "
|
|
|
|
|
"for ticket ", ticket, ". Error ", GetLastError(), ".");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Stop after finding a sell position managed by this EA
|
2026-07-27 13:45:35 +03:00
|
|
|
if((ulong)positionMagic == magic &&
|
|
|
|
|
(ENUM_POSITION_TYPE)positionType == POSITION_TYPE_SELL)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Checks whether the trade server accepted the submitted request |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsTradeRequestSuccessful(string context)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Read the result code returned by the trade server
|
2026-07-27 13:45:35 +03:00
|
|
|
uint retcode = Trade.ResultRetcode();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Accept completed, partially completed, or placed requests
|
2026-07-27 13:45:35 +03:00
|
|
|
if(retcode == TRADE_RETCODE_DONE ||
|
|
|
|
|
retcode == TRADE_RETCODE_DONE_PARTIAL ||
|
|
|
|
|
retcode == TRADE_RETCODE_PLACED)
|
|
|
|
|
{
|
|
|
|
|
PrintFormat("%s: Trade request accepted. Retcode %u (%s).",
|
|
|
|
|
context,
|
|
|
|
|
retcode,
|
|
|
|
|
Trade.ResultRetcodeDescription());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Report the complete server response when the request is rejected
|
2026-07-27 13:45:35 +03:00
|
|
|
PrintFormat("%s: Trade request rejected. Retcode %u (%s). Comment: %s.",
|
|
|
|
|
context,
|
|
|
|
|
retcode,
|
|
|
|
|
Trade.ResultRetcodeDescription(),
|
|
|
|
|
Trade.ResultComment());
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Sends a market buy request and verifies the server response |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool OpenBuy(double entryPrice,
|
|
|
|
|
double stopLoss,
|
|
|
|
|
double takeProfit,
|
|
|
|
|
double lotSize)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Submit the market buy request with the prepared trade values
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!Trade.Buy(lotSize,
|
|
|
|
|
_Symbol,
|
|
|
|
|
entryPrice,
|
|
|
|
|
stopLoss,
|
|
|
|
|
takeProfit))
|
|
|
|
|
{
|
|
|
|
|
PrintFormat("OpenBuy: Trade.Buy failed. Error %d. Retcode %u (%s). "
|
|
|
|
|
"Comment: %s.",
|
|
|
|
|
GetLastError(),
|
|
|
|
|
Trade.ResultRetcode(),
|
|
|
|
|
Trade.ResultRetcodeDescription(),
|
|
|
|
|
Trade.ResultComment());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Confirm that the trade server accepted the submitted request
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!IsTradeRequestSuccessful("OpenBuy"))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Sends a market sell request and verifies the server response |
|
2026-07-27 13:45:35 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool OpenSell(double entryPrice,
|
|
|
|
|
double stopLoss,
|
|
|
|
|
double takeProfit,
|
|
|
|
|
double lotSize)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Submit the market sell request with the prepared trade values
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!Trade.Sell(lotSize,
|
|
|
|
|
_Symbol,
|
|
|
|
|
entryPrice,
|
|
|
|
|
stopLoss,
|
|
|
|
|
takeProfit))
|
|
|
|
|
{
|
|
|
|
|
PrintFormat("OpenSell: Trade.Sell failed. Error %d. Retcode %u (%s). "
|
|
|
|
|
"Comment: %s.",
|
|
|
|
|
GetLastError(),
|
|
|
|
|
Trade.ResultRetcode(),
|
|
|
|
|
Trade.ResultRetcodeDescription(),
|
|
|
|
|
Trade.ResultComment());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Confirm that the trade server accepted the submitted request
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!IsTradeRequestSuccessful("OpenSell"))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 06:06:20 -07:00
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Expert initialization function |
|
2026-07-19 06:06:20 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
int OnInit()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Stop initialization if the testing chart cannot be configured
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!ConfigureChartAppearance())
|
|
|
|
|
{
|
|
|
|
|
Print("OnInit: Failed to configure the chart appearance.");
|
|
|
|
|
return INIT_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Assign the identifier used to distinguish this EA's positions
|
2026-07-27 13:45:35 +03:00
|
|
|
Trade.SetExpertMagicNumber(magicNumber);
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Allow the first verified bar time to initialize bar tracking
|
2026-07-27 13:45:35 +03:00
|
|
|
lastBarOpenTime = 0;
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Start without an active or partially initialized setup
|
2026-07-27 13:45:35 +03:00
|
|
|
ResetOopsPatternState();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
return INIT_SUCCEEDED;
|
2026-07-19 06:06:20 -07:00
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
2026-07-19 06:06:20 -07:00
|
|
|
//+------------------------------------------------------------------+
|
2026-07-31 13:14:04 +03:00
|
|
|
//| Expert deinitialization function |
|
2026-07-19 06:06:20 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void OnDeinit(const int reason)
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Record the reason supplied by MetaTrader 5 when the EA stops
|
2026-07-27 13:45:35 +03:00
|
|
|
Print("Program terminated! Reason code: ", reason);
|
2026-07-19 06:06:20 -07:00
|
|
|
}
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-19 06:06:20 -07:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Expert tick function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void OnTick()
|
|
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Stop the current processing cycle when the Ask price is unavailable
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetSymbolDoubleValue(_Symbol,
|
|
|
|
|
SYMBOL_ASK,
|
|
|
|
|
askPrice,
|
|
|
|
|
"OnTick"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Stop the current processing cycle when the Bid price is unavailable
|
2026-07-27 13:45:35 +03:00
|
|
|
if(!GetSymbolDoubleValue(_Symbol,
|
|
|
|
|
SYMBOL_BID,
|
|
|
|
|
bidPrice,
|
|
|
|
|
"OnTick"))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Store the latest terminal time for the current event cycle
|
2026-07-27 13:45:35 +03:00
|
|
|
currentTime = TimeCurrent();
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Evaluate the strategy only once when a new candle opens
|
|
|
|
|
if(!IsNewBar(_Symbol,
|
|
|
|
|
timeframe,
|
|
|
|
|
lastBarOpenTime))
|
2026-07-27 13:45:35 +03:00
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Create a new setup only when no earlier gap is active
|
|
|
|
|
DetectAndInitializeOopsGap();
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Increase the setup age or remove it after expiration
|
|
|
|
|
UpdateOopsGapState();
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Prepare and process a confirmed bullish reversal
|
|
|
|
|
if(IsBullishSignal())
|
|
|
|
|
{
|
|
|
|
|
Print("Bullish Signal Detected!!");
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Use the current Ask price as the intended buy entry
|
|
|
|
|
oopsState.positionEntryPrice = askPrice;
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Prepare the stop loss, take profit, and position size
|
|
|
|
|
UpdateBullishGapBarStopLevel();
|
|
|
|
|
UpdateBullishTakeProfit(oopsState.positionEntryPrice);
|
|
|
|
|
UpdateOopsPositionSize();
|
|
|
|
|
|
|
|
|
|
//--- Continue only when bullish trading is permitted
|
|
|
|
|
if(tradeDirection == OOPS_TRADE_BOTH ||
|
|
|
|
|
tradeDirection == OOPS_TRADE_LONG_ONLY)
|
|
|
|
|
{
|
|
|
|
|
//--- Block the order when another EA-managed position is active
|
|
|
|
|
if(!IsThereAnActiveBuyPosition(magicNumber) &&
|
|
|
|
|
!IsThereAnActiveSellPosition(magicNumber))
|
2026-07-27 13:45:35 +03:00
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
if(!OpenBuy(oopsState.positionEntryPrice,
|
|
|
|
|
oopsState.gapBarLow,
|
|
|
|
|
oopsState.bullishTakeProfit,
|
|
|
|
|
oopsState.lotSize))
|
2026-07-27 13:45:35 +03:00
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
Print("OnTick: The bullish Oops trade was not opened.");
|
2026-07-27 13:45:35 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Complete the setup lifecycle regardless of execution outcome
|
|
|
|
|
ResetOopsPatternState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--- Prepare and process a confirmed bearish reversal
|
|
|
|
|
if(IsBearishSignal())
|
|
|
|
|
{
|
|
|
|
|
Print("Bearish Signal Detected!!");
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Use the current Bid price as the intended sell entry
|
|
|
|
|
oopsState.positionEntryPrice = bidPrice;
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Prepare the stop loss, take profit, and position size
|
|
|
|
|
UpdateBearishGapBarStopLevel();
|
|
|
|
|
UpdateBearishTakeProfit(oopsState.positionEntryPrice);
|
|
|
|
|
UpdateOopsPositionSize();
|
2026-07-27 13:45:35 +03:00
|
|
|
|
2026-07-31 13:14:04 +03:00
|
|
|
//--- Continue only when bearish trading is permitted
|
|
|
|
|
if(tradeDirection == OOPS_TRADE_BOTH ||
|
|
|
|
|
tradeDirection == OOPS_TRADE_SHORT_ONLY)
|
|
|
|
|
{
|
|
|
|
|
//--- Block the order when another EA-managed position is active
|
|
|
|
|
if(!IsThereAnActiveBuyPosition(magicNumber) &&
|
|
|
|
|
!IsThereAnActiveSellPosition(magicNumber))
|
2026-07-27 13:45:35 +03:00
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
if(!OpenSell(oopsState.positionEntryPrice,
|
|
|
|
|
oopsState.gapBarHigh,
|
|
|
|
|
oopsState.bearishTakeProfit,
|
|
|
|
|
oopsState.lotSize))
|
2026-07-27 13:45:35 +03:00
|
|
|
{
|
2026-07-31 13:14:04 +03:00
|
|
|
Print("OnTick: The bearish Oops trade was not opened.");
|
2026-07-27 13:45:35 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-31 13:14:04 +03:00
|
|
|
|
|
|
|
|
//--- Clear the completed setup before tracking another gap
|
|
|
|
|
ResetOopsPatternState();
|
2026-07-27 13:45:35 +03:00
|
|
|
}
|
2026-07-19 06:06:20 -07:00
|
|
|
}
|
2026-07-27 13:45:35 +03:00
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|