2
0
Fork 0
TheStrategy/EA-Advisor/thestrategy.mql5

1723 Zeilen
59 KiB
Text

2026-01-24 13:32:27 +00:00
#property strict
#property version "1.00"
#include <Trade\Trade.mqh>
#property description "High/Low Detection Bot: Detects Highs (Bull->Bear) and Lows (Bear->Bull) with Visuals"
//==================== INPUTS ====================
// Symbol
input string InpSymbol = ""; // Symbol (empty = current)
// Visuals
input bool InpDrawHighLow = true; // Draw High/Low Points
input bool InpDrawBOS = true; // Draw BOS Points
input int InpBOSThresholdPoints = 5; // BOS Break Threshold in Points
input bool InpDrawFVG = true; // Draw FVG (Fair Value Gap)
input int InpFVGThresholdPoints = 5; // FVG Threshold in Points
input bool InpDrawiFVG = true; // Draw iFVG (FVG Mitigation)
input bool InpDrawOrderBlock = true; // Draw Order Blocks
input int InpOrderBlockCandles = 5; // Number of candles to check for Order Block
// Detection Parameters
input int InpLookback = 500; // Bars to Scan (History on Init)
input int InpHistoryLimit = 100; // Max Swing Points to Keep in Memory
// Session Settings
input bool InpUseSessions = false; // Master Session Switch
input bool InpTradeLondon = true; // Trade London Session
input bool InpTradeNY = true; // Trade NY Session
input bool InpTradeAsia = true; // Trade Asia Session
input int InpLondonStart = 8; // London Start Hour
input int InpLondonEnd = 16; // London End Hour
input int InpNYStart = 13; // NY Start Hour
input int InpNYStartMin = 0; // NY Start Minute
input int InpNYEnd = 21; // NY End Hour
input int InpAsiaStart = 3; // Asia Start Hour
input int InpAsiaEnd = 8; // Asia End Hour
// Trading Settings
input double InpLotSize = 0.1; // Lot Size
input int InpStopLoss = 200; // Stop Loss (Points) - Backup/Safety
input int InpSLBufferPoints = 10; // Stop Loss Buffer (Points) from OB/FVG
input double InpRiskRewardRatio = 2.0; // Risk:Reward Ratio
input int InpMagicNumber = 123456; // Magic Number
//==================== STRUCTURES ====================
struct TradeSetup {
bool active; // Is there a valid M5 setup waiting?
int direction; // 1 = Buy, -1 = Sell
double slPrice; // The Stop Loss price determined by M5 OB/FVG
datetime setupTime; // When the M5 BOS/iFVG happened
string setupDesc; // Description of the M5 Setup (Trigger)
string confDesc; // Description of the M5 Confirmation
};
struct SwingPoint {
datetime time;
double price;
int type; // 1=High, -1=Low
};
struct BOSPoint {
datetime time;
double price;
int type; // 1=Bullish BOS (break above high), -1=Bearish BOS (break below low)
datetime swingTime; // Time of the swing point that was broken
double swingPrice; // Price of the swing point that was broken
};
struct FVGPoint {
datetime time; // Time of the middle candle (candle 2)
double top; // Top of the FVG gap
double bottom; // Bottom of the FVG gap
int type; // 1=Bullish FVG (gap up), -1=Bearish FVG (gap down)
double size; // Size of the gap in points
bool mitigated; // True if FVG has been mitigated (iFVG)
};
struct iFVGPoint {
datetime time; // Time when FVG was mitigated
datetime fvgTime; // Time of the original FVG
double price; // Price where mitigation occurred
int type; // 1=Bullish FVG mitigated (close below), -1=Bearish FVG mitigated (close above)
double fvgTop; // Original FVG top
double fvgBottom; // Original FVG bottom
};
struct OrderBlock {
datetime time; // Time of the order block (first candle in the sequence)
double top; // Top of the order block
double bottom; // Bottom of the order block
int type; // 1=Bullish Order Block (before bearish BOS), -1=Bearish Order Block (before bullish BOS)
int candleCount; // Number of consecutive candles in the order block
};
//==================== GLOBALS ====================
string SYM = "";
CTrade m_trade;
int g_dayOfYear = -1;
TradeSetup g_pendingSetup;
datetime g_lastTradedSetupTime = 0; // Track the last M5 setup that was traded
datetime g_lastCheckedBarTime = 0;
datetime g_lastCheckedBarTimeM1 = 0;
datetime g_lastCheckedBarTimeM5 = 0;
// Dynamic Arrays to store detected levels "on the run"
// Arrays for specific timeframes only
SwingPoint g_swingHighsM1[];
SwingPoint g_swingLowsM1[];
BOSPoint g_bosPointsM1[];
FVGPoint g_fvgPointsM1[];
iFVGPoint g_ifvgPointsM1[];
OrderBlock g_orderBlocksM1[];
SwingPoint g_swingHighsM5[];
SwingPoint g_swingLowsM5[];
BOSPoint g_bosPointsM5[];
FVGPoint g_fvgPointsM5[];
iFVGPoint g_ifvgPointsM5[];
OrderBlock g_orderBlocksM5[];
// Latest Swing Variables (Always Available)
double g_LatestHighPrice = 0;
datetime g_LatestHighTime = 0;
double g_LatestLowPrice = 0;
datetime g_LatestLowTime = 0;
//==================== TRADING STRATEGY ====================
//==================== TIME HELPERS ====================
// Helper function to find chart by symbol and timeframe
long FindChartBySymbolAndTimeframe(string symbol, ENUM_TIMEFRAMES timeframe)
{
long currChart = ChartFirst();
int limit = 100; // Safety limit to prevent infinite loop
for(int i = 0; i < limit; i++)
{
if(currChart < 0) break; // End of chart list
string chartSymbol = ChartSymbol(currChart);
ENUM_TIMEFRAMES chartPeriod = ChartPeriod(currChart);
if(chartSymbol == symbol && chartPeriod == timeframe)
{
return currChart; // Found the chart
}
currChart = ChartNext(currChart); // Get next chart
}
return 0; // Chart not found
}
// Helper function to get or create chart for specific timeframe
long GetOrCreateChart(string symbol, ENUM_TIMEFRAMES timeframe)
{
// First try to find existing chart
long chartId = FindChartBySymbolAndTimeframe(symbol, timeframe);
if(chartId > 0)
{
return chartId;
}
// If not found, try to create new chart with retry logic
for(int attempt = 0; attempt < 3; attempt++)
{
chartId = ChartOpen(symbol, timeframe);
if(chartId > 0)
{
// Apply "hadi" template to the newly created chart
ChartApplyTemplate(chartId, "hadi");
// Give chart time to initialize
Sleep(100);
return chartId;
}
Sleep(200); // Wait before retry
}
return 0; // Return 0 if all attempts fail
}
int DayOfYearServer()
{
MqlDateTime tm;
TimeToStruct(TimeCurrent(), tm);
return tm.day_of_year;
}
// Function to check if we are in allowed sessions
bool InTradingSession()
{
if(!InpUseSessions) return true; // Master Switch OFF = 24/7 trading
datetime now = TimeCurrent();
MqlDateTime tm;
TimeToStruct(now, tm);
int h = tm.hour;
int m = tm.min;
// Asia (03:00 - 08:00)
bool asia = false;
if(InpTradeAsia)
{
if(h >= InpAsiaStart && h < InpAsiaEnd) asia = true;
}
// London (08:00 - 16:00)
bool london = false;
if(InpTradeLondon)
{
if(h >= InpLondonStart && h < InpLondonEnd) london = true;
}
// New York (13:30? - 21:00)
bool ny = false;
if(InpTradeNY)
{
if(h > InpNYStart && h < InpNYEnd) ny = true;
else if(h == InpNYStart && m >= InpNYStartMin) ny = true;
}
return (asia || london || ny);
}
void ResetDailyCountersIfNeeded()
{
int d = DayOfYearServer();
if(d != g_dayOfYear)
{
g_dayOfYear = d;
}
}
//==================== HELPER FUNCTIONS ====================
// Find the most recent M5 Confirmation (OB or FVG) before the trigger time
double FindM5Confirmation(int type, datetime triggerTime, string &outDescription)
{
double bestSL = 0.0;
datetime bestTime = 0;
string foundType = "";
// Check Order Blocks
int obCount = ArraySize(g_orderBlocksM5);
for(int i = obCount - 1; i >= 0; i--)
{
if(g_orderBlocksM5[i].time > triggerTime) continue; // Future relative to trigger
// We want a supporting OB.
// If Trigger is Bullish (1), we want Bullish OB (1).
// Note: In DetectOrderBlock, we currently set OB type opposite to BOS type.
// A Bullish BOS creates a demand zone (Bullish OB).
// Let's verify existing logic in DetectOrderBlock:
// "int obType = (lastBOS.type == 1) ? -1 : 1;"
// Wait, if lastBOS was Bullish (1), it broke structure UP. The move started from a DOWN move.
// So the OB is the bearish candle before the push up.
// Usually, a Bullish OB supports a Buy.
// In my struct: 1=Bullish OB.
// Let's assume strict matching: Type 1 Trigger needs Type 1 OB.
if(g_orderBlocksM5[i].type == type)
{
if(g_orderBlocksM5[i].time > bestTime)
{
bestTime = g_orderBlocksM5[i].time;
// For Buy (1), SL is Bottom. For Sell (-1), SL is Top.
bestSL = (type == 1) ? g_orderBlocksM5[i].bottom : g_orderBlocksM5[i].top;
foundType = (type == 1) ? "Bullish OB" : "Bearish OB";
}
break; // Found the most recent one
}
}
// Check FVGs
int fvgCount = ArraySize(g_fvgPointsM5);
for(int i = fvgCount - 1; i >= 0; i--)
{
if(g_fvgPointsM5[i].time > triggerTime) continue;
if(g_fvgPointsM5[i].type == type)
{
if(g_fvgPointsM5[i].time > bestTime)
{
bestTime = g_fvgPointsM5[i].time;
// For Buy (1), SL is Bottom. For Sell (-1), SL is Top.
bestSL = (type == 1) ? g_fvgPointsM5[i].bottom : g_fvgPointsM5[i].top;
foundType = (type == 1) ? "Bullish FVG" : "Bearish FVG";
}
break; // Found the most recent one
}
}
if(bestSL > 0)
{
outDescription = foundType + " at " + TimeToString(bestTime);
}
return bestSL;
}
// Execute Trade on M1 Confirmation
void ExecuteM1Entry(int direction, double entryPrice, datetime time, string m1TriggerDesc)
{
if(!InTradingSession()) return; // Respect Session Times
if(!g_pendingSetup.active) return; // Must have valid M5 Setup
if(g_pendingSetup.direction != direction) return; // Must match direction
if(time <= g_pendingSetup.setupTime) return; // Must be AFTER the M5 setup
double sl = g_pendingSetup.slPrice;
double tp = 0;
double risk = 0;
double point = SymbolInfoDouble(SYM, SYMBOL_POINT);
double slBuffer = InpSLBufferPoints * point;
// Construct Short Comment: "5[ConfType]1[TriggerType] MM:SS"
string m5ConfType = "";
if(StringFind(g_pendingSetup.confDesc, "OB") >= 0)
m5ConfType = "ob";
else if(StringFind(g_pendingSetup.confDesc, "FVG") >= 0)
m5ConfType = "fv";
string m1TrigType = "";
if(StringFind(m1TriggerDesc, "BOS") >= 0)
m1TrigType = "bs";
else if(StringFind(m1TriggerDesc, "iFVG") >= 0)
m1TrigType = "if";
MqlDateTime dt;
TimeToStruct(time, dt);
string comment = StringFormat("5%s1%s %02d:%02d", m5ConfType, m1TrigType, dt.hour, dt.min);
// Calculate Risk & Reward
if(direction == 1) // Buy
{
// Apply Buffer (Lower SL)
sl = sl - slBuffer;
if(sl >= entryPrice)
{
Print("Invalid Buy SL: SL ", sl, " >= Price ", entryPrice);
return;
}
risk = entryPrice - sl;
tp = entryPrice + (risk * InpRiskRewardRatio);
m_trade.Buy(InpLotSize, SYM, entryPrice, sl, tp, comment);
}
else // Sell
{
// Apply Buffer (Raise SL)
sl = sl + slBuffer;
if(sl <= entryPrice)
{
Print("Invalid Sell SL: SL ", sl, " <= Price ", entryPrice);
return;
}
risk = sl - entryPrice;
tp = entryPrice - (risk * InpRiskRewardRatio);
m_trade.Sell(InpLotSize, SYM, entryPrice, sl, tp, comment);
}
// Detailed Logging to Terminal
Print("========================================");
Print("TRADE EXECUTED: ", comment);
Print("========================================");
Print("M5 SETUP:");
Print(" Type: ", g_pendingSetup.setupDesc);
Print(" Time: ", TimeToString(g_pendingSetup.setupTime));
Print(" Direction: ", (g_pendingSetup.direction == 1 ? "BULLISH" : "BEARISH"));
Print("M5 CONFIRMATION:");
Print(" Type: ", g_pendingSetup.confDesc);
Print(" SL Level: ", DoubleToString(g_pendingSetup.slPrice, _Digits));
Print("M1 TRIGGER:");
Print(" Type: ", m1TriggerDesc);
Print(" Time: ", TimeToString(time));
Print(" Price: ", DoubleToString(entryPrice, _Digits));
Print("TRADE DETAILS:");
Print(" Direction: ", (direction == 1 ? "BUY" : "SELL"));
Print(" Entry: ", DoubleToString(entryPrice, _Digits));
Print(" SL: ", DoubleToString(sl, _Digits), " (Buffer: ", InpSLBufferPoints, " pts)");
Print(" TP: ", DoubleToString(tp, _Digits));
Print(" Risk: ", DoubleToString(risk, _Digits), " | Reward: ", DoubleToString(risk * InpRiskRewardRatio, _Digits));
Print(" RR Ratio: 1:", DoubleToString(InpRiskRewardRatio, 1));
Print("========================================");
// Reset Setup after execution (One trade per setup)
g_lastTradedSetupTime = g_pendingSetup.setupTime; // Mark this setup as traded
g_pendingSetup.active = false;
}
//==================== BOS DETECTION ====================
// Internal function to register a detected BOS point
void RegisterBOSPoint(datetime time, double price, int type, datetime swingTime, double swingPrice, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT)
{
int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS);
double normPrice = NormalizeDouble(price, digits);
double normSwingPrice = NormalizeDouble(swingPrice, digits);
// Determine chart ID based on timeframe
long chartId = 0; // Default to current chart
if(timeframe == PERIOD_M1)
{
// M1 data is analyzed but not drawn (no chart created)
chartId = 0;
}
else if(timeframe == PERIOD_M5)
{
chartId = GetOrCreateChart(SYM, PERIOD_M5);
}
else
{
return; // Only process M1 and M5 timeframes
}
if(timeframe == PERIOD_M5 && chartId == 0)
{
return; // Don't proceed if no valid chart for M5
}
// Check uniqueness to avoid duplicates
int s = 0;
if(timeframe == PERIOD_M1)
{
s = ArraySize(g_bosPointsM1);
if(s > 0 && g_bosPointsM1[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_bosPointsM1, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_bosPointsM1, s+1);
g_bosPointsM1[s].time = time;
g_bosPointsM1[s].price = normPrice;
g_bosPointsM1[s].type = type;
g_bosPointsM1[s].swingTime = swingTime;
g_bosPointsM1[s].swingPrice = normSwingPrice;
}
else if(timeframe == PERIOD_M5)
{
s = ArraySize(g_bosPointsM5);
if(s > 0 && g_bosPointsM5[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_bosPointsM5, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_bosPointsM5, s+1);
g_bosPointsM5[s].time = time;
g_bosPointsM5[s].price = normPrice;
g_bosPointsM5[s].type = type;
g_bosPointsM5[s].swingTime = swingTime;
g_bosPointsM5[s].swingPrice = normSwingPrice;
}
if(type == 1) // Bullish BOS
{
// Bullish BOS Detected
if(timeframe == PERIOD_M1)
{
string triggerDesc = "Bullish BOS at " + TimeToString(time);
ExecuteM1Entry(1, price, time, triggerDesc);
}
if(InpDrawBOS && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_BOS_Bull_";
DrawBOSMarker(chartId, prefix + TimeToString(time), time, normPrice, swingTime, normSwingPrice, clrGreen);
}
}
else // Bearish BOS
{
// Bearish BOS Detected
if(timeframe == PERIOD_M1)
{
string triggerDesc = "Bearish BOS at " + TimeToString(time);
ExecuteM1Entry(-1, price, time, triggerDesc);
}
if(InpDrawBOS && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_BOS_Bear_";
DrawBOSMarker(chartId, prefix + TimeToString(time), time, normPrice, swingTime, normSwingPrice, clrRed);
}
}
}
// Function to detect BOS on specific timeframe
void DetectBOS(ENUM_TIMEFRAMES timeframe)
{
// Only process M1 and M5 timeframes
if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return;
// Get current completed candle data (wait for candle close)
MqlRates currentRates[];
ArraySetAsSeries(currentRates, true);
if(CopyRates(SYM, timeframe, 1, 2, currentRates) < 2) return; // Need last completed candle and previous one
double currentClose = currentRates[0].close; // Close of last completed candle
double currentHigh = currentRates[0].high;
double currentLow = currentRates[0].low;
datetime currentTime = currentRates[0].time;
// Get the latest swing high and low only (not all historical)
double latestSwingHigh = 0;
datetime latestSwingHighTime = 0;
double latestSwingLow = 999999;
datetime latestSwingLowTime = 0;
// Get the last BOS type to prevent consecutive same-direction BOS
int lastBOSType = 0; // 0 = none, 1 = bullish, -1 = bearish
if(timeframe == PERIOD_M1)
{
int highCount = ArraySize(g_swingHighsM1);
if(highCount > 0)
{
latestSwingHigh = g_swingHighsM1[highCount-1].price;
latestSwingHighTime = g_swingHighsM1[highCount-1].time;
}
int lowCount = ArraySize(g_swingLowsM1);
if(lowCount > 0)
{
latestSwingLow = g_swingLowsM1[lowCount-1].price;
latestSwingLowTime = g_swingLowsM1[lowCount-1].time;
}
// Get last BOS type for M1
int bosCount = ArraySize(g_bosPointsM1);
if(bosCount > 0)
{
lastBOSType = g_bosPointsM1[bosCount-1].type;
}
}
else if(timeframe == PERIOD_M5)
{
int highCount = ArraySize(g_swingHighsM5);
if(highCount > 0)
{
latestSwingHigh = g_swingHighsM5[highCount-1].price;
latestSwingHighTime = g_swingHighsM5[highCount-1].time;
}
int lowCount = ArraySize(g_swingLowsM5);
if(lowCount > 0)
{
latestSwingLow = g_swingLowsM5[lowCount-1].price;
latestSwingLowTime = g_swingLowsM5[lowCount-1].time;
}
// Get last BOS type for M5
int bosCount = ArraySize(g_bosPointsM5);
if(bosCount > 0)
{
lastBOSType = g_bosPointsM5[bosCount-1].type;
}
}
// Check for Bullish BOS: Candle BODY closes above latest swing high
// ONLY if last BOS was not bullish (prevent consecutive bullish BOS)
if(latestSwingHigh > 0 && currentClose > latestSwingHigh && lastBOSType != 1)
{
// Check if this BOS was already detected
bool alreadyDetected = false;
if(timeframe == PERIOD_M1)
{
int bosCount = ArraySize(g_bosPointsM1);
for(int j = 0; j < bosCount; j++)
{
if(g_bosPointsM1[j].type == 1 && g_bosPointsM1[j].swingTime == latestSwingHighTime)
{
alreadyDetected = true;
break;
}
}
}
else if(timeframe == PERIOD_M5)
{
int bosCount = ArraySize(g_bosPointsM5);
for(int j = 0; j < bosCount; j++)
{
if(g_bosPointsM5[j].type == 1 && g_bosPointsM5[j].swingTime == latestSwingHighTime)
{
alreadyDetected = true;
break;
}
}
}
if(!alreadyDetected)
{
RegisterBOSPoint(currentTime, currentClose, 1, latestSwingHighTime, latestSwingHigh, timeframe);
}
}
// Check for Bearish BOS: Candle BODY closes below latest swing low
// ONLY if last BOS was not bearish (prevent consecutive bearish BOS)
if(latestSwingLow < 999999 && currentClose < latestSwingLow && lastBOSType != -1)
{
// Check if this BOS was already detected
bool alreadyDetected = false;
if(timeframe == PERIOD_M1)
{
int bosCount = ArraySize(g_bosPointsM1);
for(int j = 0; j < bosCount; j++)
{
if(g_bosPointsM1[j].type == -1 && g_bosPointsM1[j].swingTime == latestSwingLowTime)
{
alreadyDetected = true;
break;
}
}
}
else if(timeframe == PERIOD_M5)
{
int bosCount = ArraySize(g_bosPointsM5);
for(int j = 0; j < bosCount; j++)
{
if(g_bosPointsM5[j].type == -1 && g_bosPointsM5[j].swingTime == latestSwingLowTime)
{
alreadyDetected = true;
break;
}
}
}
if(!alreadyDetected)
{
RegisterBOSPoint(currentTime, currentClose, -1, latestSwingLowTime, latestSwingLow, timeframe);
}
}
}
// Function to detect FVG (Fair Value Gap) on specific timeframe
void DetectFVG(ENUM_TIMEFRAMES timeframe)
{
// Only process M1 and M5 timeframes
if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return;
// Get 3 most recent completed candles
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(SYM, timeframe, 1, 3, rates) < 3) return; // Need 3 completed candles
// Candle 1 (oldest), Candle 2 (middle), Candle 3 (newest)
double candle1Low = rates[2].low;
double candle1High = rates[2].high;
double candle2Low = rates[1].low;
double candle2High = rates[1].high;
double candle3Low = rates[0].low;
double candle3High = rates[0].high;
datetime candle2Time = rates[1].time;
double pointValue = SymbolInfoDouble(SYM, SYMBOL_POINT);
double threshold = InpFVGThresholdPoints * pointValue;
// Check for Bearish FVG: Candle 1 Low > Candle 3 High + threshold (gap down)
if(candle1Low > candle3High + threshold)
{
double fvgTop = candle1Low;
double fvgBottom = candle3High;
double fvgSize = (fvgTop - fvgBottom) / pointValue;
// Register Bearish FVG (gap down)
RegisterFVGPoint(candle2Time, fvgTop, fvgBottom, -1, fvgSize, timeframe);
}
// Check for Bullish FVG: Candle 1 High < Candle 3 Low - threshold (gap up)
if(candle1High < candle3Low - threshold)
{
double fvgTop = candle3Low;
double fvgBottom = candle1High;
double fvgSize = (fvgTop - fvgBottom) / pointValue;
// Register Bullish FVG (gap up)
RegisterFVGPoint(candle2Time, fvgTop, fvgBottom, 1, fvgSize, timeframe);
}
}
// Internal function to register a detected FVG point
void RegisterFVGPoint(datetime time, double top, double bottom, int type, double size, ENUM_TIMEFRAMES timeframe)
{
int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS);
double normTop = NormalizeDouble(top, digits);
double normBottom = NormalizeDouble(bottom, digits);
// Determine chart ID based on timeframe
long chartId = 0; // Default to current chart
if(timeframe == PERIOD_M1)
{
// M1 data is analyzed but not drawn (no chart created)
chartId = 0;
}
else if(timeframe == PERIOD_M5)
{
chartId = GetOrCreateChart(SYM, PERIOD_M5);
}
else
{
return; // Only process M1 and M5 timeframes
}
if(timeframe == PERIOD_M5 && chartId == 0)
{
return; // Don't proceed if no valid chart for M5
}
// Check uniqueness to avoid duplicates
int s = 0;
if(timeframe == PERIOD_M1)
{
s = ArraySize(g_fvgPointsM1);
if(s > 0 && g_fvgPointsM1[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_fvgPointsM1, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_fvgPointsM1, s+1);
g_fvgPointsM1[s].time = time;
g_fvgPointsM1[s].top = normTop;
g_fvgPointsM1[s].bottom = normBottom;
g_fvgPointsM1[s].type = type;
g_fvgPointsM1[s].size = size;
g_fvgPointsM1[s].mitigated = false;
}
else if(timeframe == PERIOD_M5)
{
s = ArraySize(g_fvgPointsM5);
if(s > 0 && g_fvgPointsM5[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_fvgPointsM5, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_fvgPointsM5, s+1);
g_fvgPointsM5[s].time = time;
g_fvgPointsM5[s].top = normTop;
g_fvgPointsM5[s].bottom = normBottom;
g_fvgPointsM5[s].type = type;
g_fvgPointsM5[s].size = size;
g_fvgPointsM5[s].mitigated = false;
}
if(type == 1) // Bullish FVG
{
// Bullish FVG Detected
if(InpDrawFVG && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_FVG_Bull_";
DrawFVGRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrWhite);
}
}
else // Bearish FVG
{
// Bearish FVG Detected
if(InpDrawFVG && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_FVG_Bear_";
DrawFVGRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrBlue);
}
}
}
// Draws FVG rectangle on the specified chart
void DrawFVGRectangle(long chartId, string name, datetime time, double top, double bottom, color clr)
{
if(!InpDrawFVG) return;
if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering
// Calculate rectangle time range (extend to the right)
datetime endTime = time + (PeriodSeconds() * 5); // Extend 5 bars to the right (quarter of previous length)
// Create rectangle for FVG
if(ObjectCreate(chartId, name, OBJ_RECTANGLE, 0, time, top, endTime, bottom))
{
ObjectSetInteger(chartId, name, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(chartId, name, OBJPROP_BACK, true); // Draw behind price
ObjectSetInteger(chartId, name, OBJPROP_FILL, true); // Fill the rectangle
ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, false); // Visible in object list
ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, true); // Selectable
// Set semi-transparent background for shadow effect
color bgColor = clr;
if(clr == clrGreen)
bgColor = C'0,255,0,50'; // Semi-transparent green
else if(clr == clrRed)
bgColor = C'255,0,0,50'; // Semi-transparent red
else if(clr == clrWhite)
bgColor = C'255,255,255,50'; // Semi-transparent white
else if(clr == clrBlue)
bgColor = C'0,0,255,50'; // Semi-transparent blue
ObjectSetInteger(chartId, name, OBJPROP_BGCOLOR, bgColor);
ChartRedraw(chartId); // Force update
}
}
// Function to detect iFVG (FVG Mitigation) on specific timeframe
void DetectiFVG(ENUM_TIMEFRAMES timeframe)
{
// Only process M1 and M5 timeframes
if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return;
// Get current completed candle data
MqlRates currentRates[];
ArraySetAsSeries(currentRates, true);
if(CopyRates(SYM, timeframe, 1, 2, currentRates) < 2) return; // Need last completed candle
double currentClose = currentRates[0].close;
double currentOpen = currentRates[0].open;
datetime currentTime = currentRates[0].time;
// Determine if current candle is bullish or bearish
bool isCurrentBullish = currentClose > currentOpen;
bool isCurrentBearish = currentClose < currentOpen;
// Check for FVG mitigation in existing FVGs
int fvgCount = 0;
if(timeframe == PERIOD_M1)
{
fvgCount = ArraySize(g_fvgPointsM1);
}
else if(timeframe == PERIOD_M5)
{
fvgCount = ArraySize(g_fvgPointsM5);
}
for(int i = 0; i < fvgCount; i++)
{
FVGPoint fvg;
if(timeframe == PERIOD_M1)
{
fvg = g_fvgPointsM1[i];
}
else
{
fvg = g_fvgPointsM5[i];
}
// Skip if already mitigated
if(fvg.mitigated) continue;
// Check for Bearish FVG mitigation: BULLISH candle body closes ABOVE FVG top
if(fvg.type == -1 && isCurrentBullish && currentClose > fvg.top)
{
// Mark FVG as mitigated
if(timeframe == PERIOD_M1)
{
g_fvgPointsM1[i].mitigated = true;
}
else
{
g_fvgPointsM5[i].mitigated = true;
}
// Register iFVG (FVG mitigation)
RegisteriFVGPoint(currentTime, fvg.time, currentClose, -1, fvg.top, fvg.bottom, timeframe);
}
// Check for Bullish FVG mitigation: BEARISH candle body closes BELOW FVG bottom
if(fvg.type == 1 && isCurrentBearish && currentClose < fvg.bottom)
{
// Mark FVG as mitigated
if(timeframe == PERIOD_M1)
{
g_fvgPointsM1[i].mitigated = true;
}
else
{
g_fvgPointsM5[i].mitigated = true;
}
// Register iFVG (FVG mitigation)
RegisteriFVGPoint(currentTime, fvg.time, currentClose, 1, fvg.top, fvg.bottom, timeframe);
}
}
}
// Internal function to register a detected iFVG (FVG mitigation)
void RegisteriFVGPoint(datetime time, datetime fvgTime, double price, int type, double fvgTop, double fvgBottom, ENUM_TIMEFRAMES timeframe)
{
int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS);
double normPrice = NormalizeDouble(price, digits);
// Determine chart ID based on timeframe
long chartId = 0; // Default to current chart
if(timeframe == PERIOD_M1)
{
// M1 data is analyzed but not drawn (no chart created)
chartId = 0;
}
else if(timeframe == PERIOD_M5)
{
chartId = GetOrCreateChart(SYM, PERIOD_M5);
}
else
{
return; // Only process M1 and M5 timeframes
}
if(timeframe == PERIOD_M5 && chartId == 0)
{
return; // Don't proceed if no valid chart for M5
}
// Check uniqueness to avoid duplicates
int s = 0;
if(timeframe == PERIOD_M1)
{
s = ArraySize(g_ifvgPointsM1);
if(s > 0 && g_ifvgPointsM1[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_ifvgPointsM1, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_ifvgPointsM1, s+1);
g_ifvgPointsM1[s].time = time;
g_ifvgPointsM1[s].fvgTime = fvgTime;
g_ifvgPointsM1[s].price = normPrice;
g_ifvgPointsM1[s].type = type;
g_ifvgPointsM1[s].fvgTop = fvgTop;
g_ifvgPointsM1[s].fvgBottom = fvgBottom;
}
else if(timeframe == PERIOD_M5)
{
s = ArraySize(g_ifvgPointsM5);
if(s > 0 && g_ifvgPointsM5[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_ifvgPointsM5, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_ifvgPointsM5, s+1);
g_ifvgPointsM5[s].time = time;
g_ifvgPointsM5[s].fvgTime = fvgTime;
g_ifvgPointsM5[s].price = normPrice;
g_ifvgPointsM5[s].type = type;
g_ifvgPointsM5[s].fvgTop = fvgTop;
g_ifvgPointsM5[s].fvgBottom = fvgBottom;
}
if(type == -1) // Bearish FVG mitigated (bullish candle closes above)
{
// iFVG Bearish Mitigated (Actually a Bullish Signal?)
// Wait:
// iFVG Point Type -1 means "Bearish FVG mitigated".
// Original FVG was Bearish (Gap Down).
// Mitigation means price closed ABOVE it (Bullish move).
// So this is a BULLISH signal (Inversion FVG).
if(timeframe == PERIOD_M1)
{
string triggerDesc = "Bullish iFVG at " + TimeToString(time);
ExecuteM1Entry(1, price, time, triggerDesc);
}
if(InpDrawiFVG && timeframe == PERIOD_M5) // Only draw for M5
{
// Construct the name of the ORIGINAL FVG that was mitigated
// Type -1 iFVG means original FVG was Bearish (-1)
string originalName = "M5_FVG_Bear_" + TimeToString(fvgTime);
string markerName = "M5_iFVG_Bear_" + TimeToString(time); // Use mitigation time for marker
DrawiFVGVisuals(chartId, originalName, markerName, time, fvgTop, fvgBottom, clrWhite); // Bullish Signal = White
}
}
else // Bullish FVG mitigated (bearish candle closes below)
{
// iFVG Bullish Mitigated (Actually a Bearish Signal?)
// Original FVG was Bullish (Gap Up).
// Mitigation means price closed BELOW it (Bearish move).
// So this is a BEARISH signal (Inversion FVG).
if(timeframe == PERIOD_M1)
{
string triggerDesc = "Bearish iFVG at " + TimeToString(time);
ExecuteM1Entry(-1, price, time, triggerDesc);
}
if(InpDrawiFVG && timeframe == PERIOD_M5) // Only draw for M5
{
// Construct the name of the ORIGINAL FVG that was mitigated
// Type 1 iFVG means original FVG was Bullish (1)
string originalName = "M5_FVG_Bull_" + TimeToString(fvgTime);
string markerName = "M5_iFVG_Bull_" + TimeToString(time); // Use mitigation time for marker
DrawiFVGVisuals(chartId, originalName, markerName, time, fvgTop, fvgBottom, clrBlue); // Bearish Signal = Blue
}
}
}
// Draws iFVG Visuals (Modify Zone + Marker)
void DrawiFVGVisuals(long chartId, string originalFVGName, string markerName, datetime time, double top, double bottom, color clr)
{
if(!InpDrawiFVG) return;
// 1. Modify the Original FVG Zone (if it exists) to be Dotted and Transparent
if(ObjectFind(chartId, originalFVGName) >= 0)
{
ObjectSetInteger(chartId, originalFVGName, OBJPROP_COLOR, clr); // Change to iFVG color
ObjectSetInteger(chartId, originalFVGName, OBJPROP_STYLE, STYLE_DOT); // Dotted line
ObjectSetInteger(chartId, originalFVGName, OBJPROP_WIDTH, 1);
ObjectSetInteger(chartId, originalFVGName, OBJPROP_FILL, false); // Remove fill
ObjectSetInteger(chartId, originalFVGName, OBJPROP_BGCOLOR, 0); // Clear background color
ChartRedraw(chartId);
}
// 2. Draw the Marker (X) at mitigation time
string arrowName = markerName + "_Marker";
if(ObjectFind(chartId, arrowName) < 0)
{
double midPrice = (top + bottom) / 2.0;
if(ObjectCreate(chartId, arrowName, OBJ_ARROW, 0, time, midPrice))
{
ObjectSetInteger(chartId, arrowName, OBJPROP_ARROWCODE, 251); // X symbol
ObjectSetInteger(chartId, arrowName, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, arrowName, OBJPROP_WIDTH, 2);
ObjectSetInteger(chartId, arrowName, OBJPROP_ANCHOR, ANCHOR_CENTER);
ObjectSetInteger(chartId, arrowName, OBJPROP_HIDDEN, false);
ChartRedraw(chartId);
}
}
}
// Function to detect Order Blocks on specific timeframe
void DetectOrderBlock(ENUM_TIMEFRAMES timeframe)
{
// Only process M1 and M5 timeframes
if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return;
// Get recent BOS to check for order blocks
int bosCount = 0;
if(timeframe == PERIOD_M1)
{
bosCount = ArraySize(g_bosPointsM1);
}
else if(timeframe == PERIOD_M5)
{
bosCount = ArraySize(g_bosPointsM5);
}
// Check the most recent BOS for order block detection
if(bosCount == 0) return;
BOSPoint lastBOS;
if(timeframe == PERIOD_M1)
{
lastBOS = g_bosPointsM1[bosCount-1];
}
else
{
lastBOS = g_bosPointsM5[bosCount-1];
}
// Get candle data for order block analysis
MqlRates rates[];
ArraySetAsSeries(rates, true);
// Get candles before BOS point (going backwards in time)
int barsToGet = InpOrderBlockCandles; // Get extra bars to find order block
if(CopyRates(SYM, timeframe, lastBOS.time, barsToGet, rates) < barsToGet) return;
// Find the BOS candle in the rates array (should be at index 0 or close to it)
int bosCandleIndex = -1;
for(int i = 0; i < ArraySize(rates); i++)
{
if(rates[i].time == lastBOS.time)
{
bosCandleIndex = i;
break;
}
}
if(bosCandleIndex == -1) return;
// Look backwards from BOS to find consecutive opposite-direction candles
int consecutiveCount = 0;
double highestHigh = 0;
double lowestLow = 999999;
datetime firstCandleTime = 0;
bool foundOrderBlock = false;
if(lastBOS.type == 1) // Bullish BOS - look for consecutive bearish candles
{
// Start looking backwards from BOS for first bearish candle
int startIdx = bosCandleIndex + 1;
// Skip same-direction candles (blue for bullish BOS) to find first opposite (red)
while(startIdx < ArraySize(rates) && rates[startIdx].close > rates[startIdx].open)
{
startIdx++; // Skip bullish candles
}
// Now collect consecutive bearish candles
for(int i = startIdx; i < ArraySize(rates) && consecutiveCount < InpOrderBlockCandles; i++)
{
if(rates[i].close < rates[i].open) // Bearish candle
{
if(consecutiveCount == 0) firstCandleTime = rates[i].time;
consecutiveCount++;
if(rates[i].high > highestHigh) highestHigh = rates[i].high;
if(rates[i].low < lowestLow) lowestLow = rates[i].low;
foundOrderBlock = true;
}
else
{
break; // Stop at first non-bearish candle
}
}
}
else if(lastBOS.type == -1) // Bearish BOS - look for consecutive bullish candles
{
// Start looking backwards from BOS for first bullish candle
int startIdx = bosCandleIndex + 1;
// Skip same-direction candles (red for bearish BOS) to find first opposite (green)
while(startIdx < ArraySize(rates) && rates[startIdx].close < rates[startIdx].open)
{
startIdx++; // Skip bearish candles
}
// Now collect consecutive bullish candles
for(int i = startIdx; i < ArraySize(rates) && consecutiveCount < InpOrderBlockCandles; i++)
{
if(rates[i].close > rates[i].open) // Bullish candle
{
if(consecutiveCount == 0) firstCandleTime = rates[i].time;
consecutiveCount++;
if(rates[i].high > highestHigh) highestHigh = rates[i].high;
if(rates[i].low < lowestLow) lowestLow = rates[i].low;
foundOrderBlock = true;
}
else
{
break; // Stop at first non-bullish candle
}
}
}
// Register order block if found
if(foundOrderBlock && consecutiveCount >= 1)
{
int obType = (lastBOS.type == 1) ? -1 : 1; // Opposite of BOS type
RegisterOrderBlock(firstCandleTime, highestHigh, lowestLow, obType, consecutiveCount, timeframe);
}
}
// Internal function to register a detected Order Block
void RegisterOrderBlock(datetime time, double top, double bottom, int type, int candleCount, ENUM_TIMEFRAMES timeframe)
{
int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS);
double normTop = NormalizeDouble(top, digits);
double normBottom = NormalizeDouble(bottom, digits);
// Determine chart ID based on timeframe
long chartId = 0; // Default to current chart
if(timeframe == PERIOD_M1)
{
// M1 data is analyzed but not drawn (no chart created)
chartId = 0;
}
else if(timeframe == PERIOD_M5)
{
chartId = GetOrCreateChart(SYM, PERIOD_M5);
}
else
{
return; // Only process M1 and M5 timeframes
}
if(timeframe == PERIOD_M5 && chartId == 0)
{
return; // Don't proceed if no valid chart for M5
}
// Check uniqueness to avoid duplicates
int s = 0;
if(timeframe == PERIOD_M1)
{
s = ArraySize(g_orderBlocksM1);
if(s > 0 && g_orderBlocksM1[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_orderBlocksM1, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_orderBlocksM1, s+1);
g_orderBlocksM1[s].time = time;
g_orderBlocksM1[s].top = normTop;
g_orderBlocksM1[s].bottom = normBottom;
g_orderBlocksM1[s].type = type;
g_orderBlocksM1[s].candleCount = candleCount;
}
else if(timeframe == PERIOD_M5)
{
s = ArraySize(g_orderBlocksM5);
if(s > 0 && g_orderBlocksM5[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_orderBlocksM5, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_orderBlocksM5, s+1);
g_orderBlocksM5[s].time = time;
g_orderBlocksM5[s].top = normTop;
g_orderBlocksM5[s].bottom = normBottom;
g_orderBlocksM5[s].type = type;
g_orderBlocksM5[s].candleCount = candleCount;
}
if(type == -1) // Bearish Order Block (before bullish BOS)
{
// Bearish Order Block
if(InpDrawOrderBlock && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_OB_Bear_";
DrawOrderBlockRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrRed);
}
}
else // Bullish Order Block (before bearish BOS)
{
// Bullish Order Block
if(InpDrawOrderBlock && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_OB_Bull_";
DrawOrderBlockRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrGreen);
}
}
}
void DrawOrderBlockRectangle(long chartId, string name, datetime time, double top, double bottom, color clr)
{
if(!InpDrawOrderBlock) return;
if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering
// Calculate rectangle time range (extend to the right)
datetime endTime = time + (PeriodSeconds() * 5); // Extend 30 bars to the right
// Create rectangle for Order Block
if(ObjectCreate(chartId, name, OBJ_RECTANGLE, 0, time, top, endTime, bottom))
{
ObjectSetInteger(chartId, name, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 2);
ObjectSetInteger(chartId, name, OBJPROP_BACK, true); // Draw behind price
ObjectSetInteger(chartId, name, OBJPROP_FILL, true); // Fill the rectangle
ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, false); // Visible in object list
ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, true); // Selectable
// Set semi-transparent background
color bgColor = clr;
if(clr == clrGreen)
bgColor = C'0,255,0,30'; // Very transparent green
else if(clr == clrRed)
bgColor = C'255,0,0,30'; // Very transparent red
ObjectSetInteger(chartId, name, OBJPROP_BGCOLOR, bgColor);
ChartRedraw(chartId); // Force update
}
}
//==================== DETECTION & VISUALS ====================
// Draws a short horizontal line from wick to the right for swing points
void DrawHighLow(long chartId, string name, datetime time, double price, color clr)
{
if(!InpDrawHighLow) return;
if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering
// Calculate end time (extend a few bars to the right)
datetime endTime = time + (PeriodSeconds() * 2); // Extend 2 bars to the right (half the previous length)
// Create short horizontal trendline instead of arrow
if(ObjectCreate(chartId, name, OBJ_TREND, 0, time, price, endTime, price))
{
ObjectSetInteger(chartId, name, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 2);
ObjectSetInteger(chartId, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, false); // Visible in object list
ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, true); // Selectable
ObjectSetInteger(chartId, name, OBJPROP_BACK, false); // Bring to front
ObjectSetInteger(chartId, name, OBJPROP_RAY_RIGHT, false); // Don't extend infinitely
ChartRedraw(chartId); // Force update
}
}
// Draws BOS marker with line connecting to broken swing point
void DrawBOSMarker(long chartId, string name, datetime bosTime, double bosPrice, datetime swingTime, double swingPrice, color clr)
{
if(!InpDrawBOS) return;
if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering
// Create BOS arrow
string arrowName = name + "_Arrow";
if(ObjectCreate(chartId, arrowName, OBJ_ARROW, 0, bosTime, bosPrice))
{
ObjectSetInteger(chartId, arrowName, OBJPROP_ARROWCODE, 236); // Diamond symbol
ObjectSetInteger(chartId, arrowName, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, arrowName, OBJPROP_WIDTH, 3);
ObjectSetInteger(chartId, arrowName, OBJPROP_ANCHOR, ANCHOR_CENTER);
ObjectSetInteger(chartId, arrowName, OBJPROP_HIDDEN, false);
ObjectSetInteger(chartId, arrowName, OBJPROP_SELECTABLE, true);
}
// Create trendline from swing point to BOS point
string lineName = name + "_Line";
if(ObjectCreate(chartId, lineName, OBJ_TREND, 0, swingTime, swingPrice, bosTime, bosPrice))
{
ObjectSetInteger(chartId, lineName, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, lineName, OBJPROP_WIDTH, 2);
ObjectSetInteger(chartId, lineName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(chartId, lineName, OBJPROP_HIDDEN, false);
ObjectSetInteger(chartId, lineName, OBJPROP_SELECTABLE, true);
ObjectSetInteger(chartId, lineName, OBJPROP_RAY_RIGHT, false);
}
// Create label at swing point
string labelName = name + "_Label";
string labelText = StringFormat("BOS %.5f", swingPrice);
if(ObjectCreate(chartId, labelName, OBJ_TEXT, 0, swingTime, swingPrice))
{
ObjectSetString(chartId, labelName, OBJPROP_TEXT, labelText);
ObjectSetInteger(chartId, labelName, OBJPROP_COLOR, clr);
ObjectSetInteger(chartId, labelName, OBJPROP_FONTSIZE, 8);
ObjectSetInteger(chartId, labelName, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
ObjectSetInteger(chartId, labelName, OBJPROP_HIDDEN, false);
ObjectSetInteger(chartId, labelName, OBJPROP_SELECTABLE, true);
}
ChartRedraw(chartId); // Force update
}
// Internal function to register a detected point
void RegisterSwingPoint(datetime time, double price, int type, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT)
{
int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS);
double normPrice = NormalizeDouble(price, digits);
// Determine chart ID based on timeframe
long chartId = 0; // Default to current chart
if(timeframe == PERIOD_M1)
{
// M1 data is analyzed but not drawn (no chart created)
chartId = 0;
}
else if(timeframe == PERIOD_M5)
{
chartId = GetOrCreateChart(SYM, PERIOD_M5);
}
else
{
return; // Only process M1 and M5 timeframes
}
if(timeframe == PERIOD_M5 && chartId == 0)
{
return; // Don't proceed if no valid chart for M5
}
// Check uniqueness to avoid duplicates (especially during live runs)
if(type == 1) // High
{
int s = 0;
if(timeframe == PERIOD_M1)
{
s = ArraySize(g_swingHighsM1);
if(s > 0 && g_swingHighsM1[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_swingHighsM1, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_swingHighsM1, s+1);
g_swingHighsM1[s].time = time;
g_swingHighsM1[s].price = normPrice;
}
else if(timeframe == PERIOD_M5)
{
s = ArraySize(g_swingHighsM5);
if(s > 0 && g_swingHighsM5[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_swingHighsM5, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_swingHighsM5, s+1);
g_swingHighsM5[s].time = time;
g_swingHighsM5[s].price = normPrice;
}
// High Detected
if(InpDrawHighLow && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_High_";
DrawHighLow(chartId, prefix + TimeToString(time), time, normPrice, clrLime);
}
}
else // Low
{
int s = 0;
if(timeframe == PERIOD_M1)
{
s = ArraySize(g_swingLowsM1);
if(s > 0 && g_swingLowsM1[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_swingLowsM1, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_swingLowsM1, s+1);
g_swingLowsM1[s].time = time;
g_swingLowsM1[s].price = normPrice;
}
else if(timeframe == PERIOD_M5)
{
s = ArraySize(g_swingLowsM5);
if(s > 0 && g_swingLowsM5[s-1].time == time) return; // Already exists
// Enforce History Limit (FIFO)
if(s >= InpHistoryLimit){
ArrayRemove(g_swingLowsM5, 0, 1);
s--; // Decrement size after removal
}
ArrayResize(g_swingLowsM5, s+1);
g_swingLowsM5[s].time = time;
g_swingLowsM5[s].price = normPrice;
}
// Low Detected
if(InpDrawHighLow && timeframe == PERIOD_M5) // Only draw for M5
{
string prefix = "M5_Low_";
DrawHighLow(chartId, prefix + TimeToString(time), time, normPrice, clrRed);
}
}
}
// Function to scan HISTORY logic for specific timeframe
void InitHistoryTimeframe(ENUM_TIMEFRAMES timeframe)
{
int Lookback = InpLookback;
MqlRates rates[];
ArraySetAsSeries(rates, true); // Index 0 is newest
int copied = CopyRates(SYM, timeframe, 1, Lookback + 2, rates); // Start from Bar 1 (skip forming)
if(copied < Lookback + 2) return;
for(int i = Lookback - 1; i >= 0; i--)
{
bool bullRight = rates[i].close > rates[i].open;
bool bearRight = rates[i].close < rates[i].open;
bool bullLeft = rates[i+1].close > rates[i+1].open;
bool bearLeft = rates[i+1].close < rates[i+1].open;
// Low: Bearish (i+1) -> Bullish (i)
if(bearLeft && bullRight)
{
double lowPrice = MathMin(rates[i].low, rates[i+1].low);
datetime lowTime = (rates[i].low <= rates[i+1].low) ? rates[i].time : rates[i+1].time;
RegisterSwingPoint(lowTime, lowPrice, -1, timeframe);
}
// High: Bullish (i+1) -> Bearish (i)
if(bullLeft && bearRight)
{
double highPrice = MathMax(rates[i].high, rates[i+1].high);
datetime highTime = (rates[i].high >= rates[i+1].high) ? rates[i].time : rates[i+1].time;
RegisterSwingPoint(highTime, highPrice, 1, timeframe);
}
}
}
// Function to scan HISTORY logic (runs once on Init)
void InitHistory()
{
// Initialize history for M1 and M5 timeframes only
InitHistoryTimeframe(PERIOD_M1);
InitHistoryTimeframe(PERIOD_M5);
}
// Function to check LATEST signals for specific timeframe
void CheckLivePatternTimeframe(ENUM_TIMEFRAMES timeframe, datetime &lastCheckedBarTime)
{
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(SYM, timeframe, 1, 3, rates) < 3) return; // Need Bar 1 and Bar 2
// Dedup: Only process if we haven't checked this confirmation bar yet
if(rates[0].time == lastCheckedBarTime) return;
lastCheckedBarTime = rates[0].time; // Mark confirmation bar as processed
bool bullRight = rates[0].close > rates[0].open;
bool bearRight = rates[0].close < rates[0].open;
bool bullLeft = rates[1].close > rates[1].open;
bool bearLeft = rates[1].close < rates[1].open;
// Low: Bearish (Left/Bar2) -> Bullish (Right/Bar1)
if(bearLeft && bullRight)
{
double lowPrice = MathMin(rates[0].low, rates[1].low);
datetime lowTime = (rates[0].low <= rates[1].low) ? rates[0].time : rates[1].time;
RegisterSwingPoint(lowTime, lowPrice, -1, timeframe);
}
// High: Bullish (Left/Bar2) -> Bearish (Right/Bar1)
if(bullLeft && bearRight)
{
double highPrice = MathMax(rates[0].high, rates[1].high);
datetime highTime = (rates[0].high >= rates[1].high) ? rates[0].time : rates[1].time;
RegisterSwingPoint(highTime, highPrice, 1, timeframe);
}
}
//==================== INIT ====================
int OnInit()
{
SYM = (StringLen(InpSymbol) == 0 ? _Symbol : InpSymbol);
m_trade.SetExpertMagicNumber(InpMagicNumber);
g_dayOfYear = DayOfYearServer();
// Reset Global Setup
g_pendingSetup.active = false;
g_pendingSetup.direction = 0;
g_pendingSetup.slPrice = 0;
g_pendingSetup.setupTime = 0;
// Initialize chart for M5 timeframe only (M1 data will be analyzed but no chart created)
long m5Chart = GetOrCreateChart(SYM, PERIOD_M5);
// Clear Arrays
ArrayResize(g_swingHighsM1, 0);
ArrayResize(g_swingLowsM1, 0);
ArrayResize(g_bosPointsM1, 0);
ArrayResize(g_fvgPointsM1, 0);
ArrayResize(g_ifvgPointsM1, 0);
ArrayResize(g_orderBlocksM1, 0);
ArrayResize(g_swingHighsM5, 0);
ArrayResize(g_swingLowsM5, 0);
ArrayResize(g_bosPointsM5, 0);
ArrayResize(g_fvgPointsM5, 0);
ArrayResize(g_ifvgPointsM5, 0);
ArrayResize(g_orderBlocksM5, 0);
// Populate History Once
InitHistory();
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
// Clean up M5 chart if it exists (M1 data was analyzed but no chart was created)
long m5ChartId = FindChartBySymbolAndTimeframe(SYM, PERIOD_M5);
if(m5ChartId > 0)
{
ObjectsDeleteAll(m5ChartId, "M5_High_");
ObjectsDeleteAll(m5ChartId, "M5_Low_");
ObjectsDeleteAll(m5ChartId, "M5_BOS_Bull_");
ObjectsDeleteAll(m5ChartId, "M5_BOS_Bear_");
ObjectsDeleteAll(m5ChartId, "M5_FVG_");
ObjectsDeleteAll(m5ChartId, "M5_iFVG_");
ObjectsDeleteAll(m5ChartId, "M5_OB_");
}
}
// Check M5 Handover at the end of the tick
// This ensures that Order Blocks detected *after* the BOS are included in the confirmation search
void CheckM5Handover()
{
datetime lastM5Time = iTime(SYM, PERIOD_M5, 1);
// Check M5 BOS
int bosCount = ArraySize(g_bosPointsM5);
if(bosCount > 0)
{
BOSPoint bos = g_bosPointsM5[bosCount-1];
if(bos.time == lastM5Time) // New BOS
{
// Prevent re-activating a setup we just traded
if(bos.time == g_lastTradedSetupTime) return;
int dir = (bos.type == 1) ? 1 : -1;
string confDesc = "";
double confSL = FindM5Confirmation(dir, bos.time, confDesc);
if(confSL > 0)
{
g_pendingSetup.active = true;
g_pendingSetup.direction = dir;
g_pendingSetup.slPrice = confSL;
g_pendingSetup.setupTime = bos.time;
g_pendingSetup.setupDesc = (dir==1?"Bullish":"Bearish") + " BOS at " + TimeToString(bos.time);
g_pendingSetup.confDesc = confDesc;
Print("M5 Setup Found: BOS ", (dir==1?"Bull":"Bear"), " at ", TimeToString(bos.time), ". SL: ", confSL);
}
}
}
// Check M5 iFVG
int ifvgCount = ArraySize(g_ifvgPointsM5);
if(ifvgCount > 0)
{
iFVGPoint ifvg = g_ifvgPointsM5[ifvgCount-1];
if(ifvg.time == lastM5Time) // New iFVG
{
// Prevent re-activating a setup we just traded
if(ifvg.time == g_lastTradedSetupTime) return;
// iFVG logic: type -1 (Bearish Mitigated) -> Bullish Signal (1)
// type 1 (Bullish Mitigated) -> Bearish Signal (-1)
int dir = (ifvg.type == -1) ? 1 : -1;
string confDesc = "";
double confSL = FindM5Confirmation(dir, ifvg.time, confDesc);
if(confSL > 0)
{
g_pendingSetup.active = true;
g_pendingSetup.direction = dir;
g_pendingSetup.slPrice = confSL;
g_pendingSetup.setupTime = ifvg.time;
g_pendingSetup.setupDesc = (dir==1?"Bullish":"Bearish") + " iFVG at " + TimeToString(ifvg.time);
g_pendingSetup.confDesc = confDesc;
Print("M5 Setup Found: iFVG ", (dir==1?"Bull":"Bear"), " at ", TimeToString(ifvg.time), ". SL: ", confSL);
}
}
}
}
//==================== TICK ====================
void OnTick()
{
ResetDailyCountersIfNeeded();
// -- Real-Time Check for all timeframes --
CheckLivePatternTimeframe(PERIOD_M1, g_lastCheckedBarTimeM1);
CheckLivePatternTimeframe(PERIOD_M5, g_lastCheckedBarTimeM5);
// -- BOS Detection on 1min and 5min timeframes --
DetectBOS(PERIOD_M1); // 1-minute timeframe
DetectBOS(PERIOD_M5); // 5-minute timeframe
// -- FVG Detection on 1min and 5min timeframes --
DetectFVG(PERIOD_M1); // 1-minute timeframe
DetectFVG(PERIOD_M5); // 5-minute timeframe
// -- iFVG Detection on 1min and 5min timeframes --
DetectiFVG(PERIOD_M1); // 1-minute timeframe
DetectiFVG(PERIOD_M5); // 5-minute timeframe
// -- Order Block Detection on 1min and 5min timeframes --
DetectOrderBlock(PERIOD_M1); // 1-minute timeframe
DetectOrderBlock(PERIOD_M5); // 5-minute timeframe
// -- Check for M5 Handover (Late Update to catch Order Blocks) --
CheckM5Handover();
}
//==================== END OF EA ====================