Files
MT5-EA-Sniper-Strategy/src/SniperEA.mq5
T
rithsila 2a998b1e2a feat: Complete Phase 1 implementation and comprehensive development workflow
 Phase 1 Core Trading Logic - COMPLETE (100%)
- All core trading functions implemented and tested
- Pattern detection working (OB, FVG, BOS, Liquidity Sweeps)
- Risk management system functional (1% risk per trade)
- Multi-timeframe analysis operational
- Trade execution logic complete
- Strategy Tester validation successful

📚 Development Workflow Framework - NEW
- Complete MT5 EA development workflow documentation
- 4-tier testing protocol (Unit → Integration → Strategy → Live Demo)
- Compilation automation and validation scripts
- Feature branch methodology for incremental development
- Performance regression testing framework
- Standardized test datasets for consistent backtesting

🧪 Testing Infrastructure - NEW
- Baseline testing scripts and procedures
- Pattern validation framework
- Risk management stress testing
- Quick monitoring and troubleshooting guides
- Comprehensive testing documentation

📊 Updated Implementation Plan
- Corrected completion status from 45% to 85%
- Phase 1 marked as complete with all tasks checked off
- Updated priority focus to Phase 3 (Visualization) or Phase 4 (Performance Tracking)

🔧 Technical Improvements
- Updated SniperEA.mq5 with debug mode enabled
- Compiled EA successfully (85KB .ex5 file)
- Validated all core functions through Strategy Tester
- Clean initialization and deinitialization confirmed

Next: Focus on Phase 3 (Chart Visualization) or Phase 4 (Performance Tracking)
2025-09-25 22:16:35 +07:00

2889 lines
92 KiB
Plaintext

//+------------------------------------------------------------------+
//| SniperEA.mq5 |
//| MT5 Sniper Strategy Expert Advisor |
//| OB + BOS + Liquidity Sweep + FVG |
//+------------------------------------------------------------------+
#property copyright "Sniper Strategy EA"
#property link ""
#property version "1.00"
//--- Include files
#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>
#include <Trade/OrderInfo.mqh>
//--- Global objects
CTrade trade;
CPositionInfo position;
COrderInfo order;
//--- Input parameters
input group "=== Core Settings ===" input int MaxTradesPerDay = 3; // Maximum trades per symbol per day
input double RiskPercent = 1.0; // Risk percentage per trade
input double MinRR = 2.0; // Minimum risk-reward ratio
input bool UseTimeFilter = true; // Enable session filtering
input group "=== Session Settings ===" input string AsiaStart = "00:00"; // Asia session start (GMT)
input string AsiaEnd = "09:00"; // Asia session end (GMT)
input string LondonStart = "08:00"; // London session start (GMT)
input string LondonEnd = "17:00"; // London session end (GMT)
input string NYStart = "13:00"; // New York session start (GMT)
input string NYEnd = "22:00"; // New York session end (GMT)
input group "=== Risk Management ===" input int MaxSL = 50; // Maximum stop loss in pips
input int MinSL = 10; // Minimum stop loss in pips
input double MaxSlippage = 2.0; // Maximum slippage in pips
input int MaxPositions = 10; // Maximum total positions
input int MaxPositionsPerSymbol = 3; // Maximum positions per symbol
input group "=== Pattern Detection ===" input int OBLookback = 20; // Order Block lookback candles
input double MinFVGSize = 3.0; // Minimum FVG size in pips
input double MinSweepDistance = 5.0; // Minimum sweep distance in pips
input int BOSConfirmationCandles = 3; // BOS confirmation within candles
input int SwingLookback = 10; // Swing high/low lookback period
input double OBStrengthFilter = 0.5; // Order Block strength filter (0-1)
input bool RequireMultiTFConfirmation = true; // Require multi-timeframe confirmation
input group "=== Visualization ===" input bool ShowOrderBlocks = true; // Show Order Block zones
input bool ShowFVG = true; // Show Fair Value Gaps
input bool ShowBOS = true; // Show Break of Structure
input bool ShowSweeps = true; // Show Liquidity Sweeps
input bool ShowTradeLevels = true; // Show Entry/SL/TP levels
input group "=== Symbols to Trade ===" input string Symbol1 = "EURUSD"; // Symbol 1
input string Symbol2 = "GBPUSD"; // Symbol 2
input string Symbol3 = "USDJPY"; // Symbol 3
input string Symbol4 = "USDCHF"; // Symbol 4
input string Symbol5 = "AUDUSD"; // Symbol 5
input string Symbol6 = "USDCAD"; // Symbol 6
input string Symbol7 = "NZDUSD"; // Symbol 7
input string Symbol8 = "XAUUSD"; // Symbol 8 (Gold)
input group "=== Logging & Debug ===" input bool EnableDetailedLogging = true; // Enable detailed logging
input bool EnableDebugMode = true; // Enable debug mode
input bool LogPatternDetection = true; // Log pattern detection events
input bool LogTradeExecution = true; // Log trade execution details
//--- Global variables
string SymbolsToTrade[];
int TotalSymbols = 0;
datetime LastBarTime = 0;
bool IsInitialized = false;
string LogPrefix = "SniperEA";
int LogLevel = 0; // 0=Info, 1=Warning, 2=Error, 3=Debug
//--- Structure definitions
struct OrderBlock
{
double high;
double low;
datetime time;
bool is_bullish;
bool is_fresh;
int strength;
};
struct FairValueGap
{
double top;
double bottom;
datetime time;
bool is_bullish;
bool is_filled;
};
struct LiquiditySweep
{
double level;
datetime time;
bool is_high_sweep;
bool confirmed;
};
struct BreakOfStructure
{
double level;
datetime time;
bool is_bullish;
bool confirmed;
};
//--- Function declarations
bool ConfirmBOS(string symbol, ENUM_TIMEFRAMES timeframe, int break_bar, bool is_bullish, double level);
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
Print("=== Sniper EA Initialization Started ===");
// Initialize trade object
trade.SetExpertMagicNumber(123456);
trade.SetDeviationInPoints((int)(MaxSlippage * 10));
trade.SetTypeFilling(ORDER_FILLING_FOK);
// Setup symbols array
if (!SetupSymbolsArray())
{
Print("ERROR: Failed to setup symbols array");
return INIT_FAILED;
}
// Validate input parameters
if (!ValidateInputs())
{
Print("ERROR: Invalid input parameters");
return INIT_FAILED;
}
// Initialize chart objects
if (!InitializeChartObjects())
{
Print("ERROR: Failed to initialize chart objects");
return INIT_FAILED;
}
// Initialize multi-timeframe analysis
if (!InitializeMultiTimeframeAnalysis())
{
Print("ERROR: Failed to initialize multi-timeframe analysis");
return INIT_FAILED;
}
IsInitialized = true;
LastBarTime = iTime(_Symbol, PERIOD_M1, 0);
Print("=== Sniper EA Initialization Completed Successfully ===");
Print("Trading Symbols: ", TotalSymbols);
Print("Risk per Trade: ", RiskPercent, "%");
Print("Minimum R:R Ratio: ", MinRR, ":1");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("=== Sniper EA Deinitialization Started ===");
// Clean up chart objects
CleanupChartObjects();
// Print deinitialization reason
string deinit_reason = "";
switch (reason)
{
case REASON_PROGRAM:
deinit_reason = "Expert Advisor terminated";
break;
case REASON_REMOVE:
deinit_reason = "Expert Advisor removed from chart";
break;
case REASON_RECOMPILE:
deinit_reason = "Expert Advisor recompiled";
break;
case REASON_CHARTCHANGE:
deinit_reason = "Chart symbol or period changed";
break;
case REASON_CHARTCLOSE:
deinit_reason = "Chart closed";
break;
case REASON_PARAMETERS:
deinit_reason = "Input parameters changed";
break;
case REASON_ACCOUNT:
deinit_reason = "Account changed";
break;
default:
deinit_reason = "Unknown reason";
break;
}
Print("Deinitialization Reason: ", deinit_reason);
Print("=== Sniper EA Deinitialization Completed ===");
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if (!IsInitialized)
return;
// Check for new bar
datetime current_bar_time = iTime(_Symbol, PERIOD_M1, 0);
if (current_bar_time == LastBarTime)
return;
LastBarTime = current_bar_time;
// Main trading logic will be implemented here
ProcessTradingLogic();
}
//+------------------------------------------------------------------+
//| Setup symbols array from input parameters |
//+------------------------------------------------------------------+
bool SetupSymbolsArray()
{
ArrayResize(SymbolsToTrade, 0);
TotalSymbols = 0;
string symbols[8] = {Symbol1, Symbol2, Symbol3, Symbol4, Symbol5, Symbol6, Symbol7, Symbol8};
for (int i = 0; i < 8; i++)
{
if (symbols[i] != "" && symbols[i] != "NONE")
{
ArrayResize(SymbolsToTrade, TotalSymbols + 1);
SymbolsToTrade[TotalSymbols] = symbols[i];
TotalSymbols++;
}
}
return TotalSymbols > 0;
}
//+------------------------------------------------------------------+
//| Validate input parameters |
//+------------------------------------------------------------------+
bool ValidateInputs()
{
if (RiskPercent <= 0 || RiskPercent > 10)
{
Print("ERROR: Risk percent must be between 0 and 10");
return false;
}
if (MinRR < 1.0)
{
Print("ERROR: Minimum R:R ratio must be at least 1.0");
return false;
}
if (MaxSL <= MinSL)
{
Print("ERROR: Maximum SL must be greater than Minimum SL");
return false;
}
if (MaxTradesPerDay <= 0)
{
Print("ERROR: Max trades per day must be positive");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Initialize chart objects |
//+------------------------------------------------------------------+
bool InitializeChartObjects()
{
// Set chart properties for better visualization
ChartSetInteger(0, CHART_SHOW_GRID, false);
ChartSetInteger(0, CHART_SHOW_VOLUMES, false);
ChartSetInteger(0, CHART_SHOW_OHLC, true);
// Create information panel background
if (ObjectCreate(0, "SniperEA_InfoPanel", OBJ_RECTANGLE_LABEL, 0, 0, 0))
{
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_YDISTANCE, 30);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_XSIZE, 250);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_YSIZE, 150);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_BGCOLOR, clrDarkSlateGray);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_WIDTH, 1);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_BACK, false);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_SELECTED, false);
ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_HIDDEN, true);
}
// Create EA status label
if (ObjectCreate(0, "SniperEA_Status", OBJ_LABEL, 0, 0, 0))
{
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_YDISTANCE, 40);
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_COLOR, clrLime);
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_FONTSIZE, 10);
ObjectSetString(0, "SniperEA_Status", OBJPROP_FONT, "Arial Bold");
ObjectSetString(0, "SniperEA_Status", OBJPROP_TEXT, "Sniper EA - ACTIVE");
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_SELECTED, false);
ObjectSetInteger(0, "SniperEA_Status", OBJPROP_HIDDEN, true);
}
Print("Chart objects initialized successfully");
return true;
}
//+------------------------------------------------------------------+
//| Clean up chart objects |
//+------------------------------------------------------------------+
void CleanupChartObjects()
{
// Clean up all chart objects created by the EA
int total_objects = ObjectsDeleteAll(0, "SniperEA_");
Print("Cleaned up ", total_objects, " chart objects");
}
//+------------------------------------------------------------------+
//| Update information panel |
//+------------------------------------------------------------------+
void UpdateInfoPanel()
{
// Get current session
string current_session = GetCurrentSession();
// Get account information
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double account_equity = AccountInfoDouble(ACCOUNT_EQUITY);
double account_margin = AccountInfoDouble(ACCOUNT_MARGIN);
// Count current positions
int total_positions = PositionsTotal();
// Create info text
string info_text = StringFormat(
"Session: %s\n" +
"Balance: %.2f\n" +
"Equity: %.2f\n" +
"Margin: %.2f\n" +
"Positions: %d/%d",
current_session,
account_balance,
account_equity,
account_margin,
total_positions,
MaxPositions);
// Update info label
if (ObjectFind(0, "SniperEA_Info") < 0)
{
ObjectCreate(0, "SniperEA_Info", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_XDISTANCE, 20);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_YDISTANCE, 60);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_FONTSIZE, 8);
ObjectSetString(0, "SniperEA_Info", OBJPROP_FONT, "Courier New");
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_SELECTED, false);
ObjectSetInteger(0, "SniperEA_Info", OBJPROP_HIDDEN, true);
}
ObjectSetString(0, "SniperEA_Info", OBJPROP_TEXT, info_text);
}
//+------------------------------------------------------------------+
//| Get current trading session |
//+------------------------------------------------------------------+
string GetCurrentSession()
{
MqlDateTime dt;
TimeToStruct(TimeGMT(), dt);
int current_hour = dt.hour;
int current_minute = dt.min;
int current_time_minutes = current_hour * 60 + current_minute;
// Convert session times to minutes
int asia_start = (int)(StringToTime("1970.01.01 " + AsiaStart) % 86400 / 60);
int asia_end = (int)(StringToTime("1970.01.01 " + AsiaEnd) % 86400 / 60);
int london_start = (int)(StringToTime("1970.01.01 " + LondonStart) % 86400 / 60);
int london_end = (int)(StringToTime("1970.01.01 " + LondonEnd) % 86400 / 60);
int ny_start = (int)(StringToTime("1970.01.01 " + NYStart) % 86400 / 60);
int ny_end = (int)(StringToTime("1970.01.01 " + NYEnd) % 86400 / 60);
// Check which session we're in
if ((current_time_minutes >= asia_start && current_time_minutes < asia_end) ||
(asia_start > asia_end && (current_time_minutes >= asia_start || current_time_minutes < asia_end)))
return "ASIA";
if ((current_time_minutes >= london_start && current_time_minutes < london_end) ||
(london_start > london_end && (current_time_minutes >= london_start || current_time_minutes < london_end)))
return "LONDON";
if ((current_time_minutes >= ny_start && current_time_minutes < ny_end) ||
(ny_start > ny_end && (current_time_minutes >= ny_start || current_time_minutes < ny_end)))
return "NEW YORK";
return "OFF HOURS";
}
//+------------------------------------------------------------------+
//| Trade Execution Functions |
//+------------------------------------------------------------------+
bool ExecuteBuyTrade(string symbol, double entry, double sl, double tp, double lot_size)
{
// Validate trade parameters
if (!ValidateTradeParameters(symbol, true, entry, sl, tp, lot_size))
{
LogError(StringFormat("Invalid buy trade parameters for %s", symbol));
return false;
}
// Normalize prices
entry = NormalizePrice(symbol, entry);
sl = NormalizePrice(symbol, sl);
tp = NormalizePrice(symbol, tp);
// Execute buy trade
bool result = trade.Buy(lot_size, symbol, entry, sl, tp, "Sniper EA Buy");
if (result)
{
LogTrade("BUY EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f, TP: %.5f, Lot: %.2f", entry, sl, tp, lot_size));
return true;
}
else
{
int error_code = trade.ResultRetcode();
HandleTradeError(error_code, "Buy Trade Execution");
return false;
}
}
bool ExecuteSellTrade(string symbol, double entry, double sl, double tp, double lot_size)
{
// Validate trade parameters
if (!ValidateTradeParameters(symbol, false, entry, sl, tp, lot_size))
{
LogError(StringFormat("Invalid sell trade parameters for %s", symbol));
return false;
}
// Normalize prices
entry = NormalizePrice(symbol, entry);
sl = NormalizePrice(symbol, sl);
tp = NormalizePrice(symbol, tp);
// Execute sell trade
bool result = trade.Sell(lot_size, symbol, entry, sl, tp, "Sniper EA Sell");
if (result)
{
LogTrade("SELL EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f, TP: %.5f, Lot: %.2f", entry, sl, tp, lot_size));
return true;
}
else
{
int error_code = trade.ResultRetcode();
HandleTradeError(error_code, "Sell Trade Execution");
return false;
}
}
bool ValidateTradeParameters(string symbol, bool is_buy, double entry, double sl, double tp, double lot_size)
{
// Check symbol validity
if (!SymbolSelect(symbol, true))
{
LogError(StringFormat("Symbol %s not available", symbol));
return false;
}
// Check lot size
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if (lot_size < min_lot || lot_size > max_lot)
{
LogError(StringFormat("Invalid lot size %.2f for %s (min: %.2f, max: %.2f)", lot_size, symbol, min_lot, max_lot));
return false;
}
// Check price validity
if (entry <= 0 || sl <= 0 || tp <= 0)
{
LogError("Invalid price levels - all prices must be positive");
return false;
}
// Check stop loss and take profit logic
if (is_buy)
{
if (sl >= entry)
{
LogError("Buy trade: Stop loss must be below entry price");
return false;
}
if (tp <= entry)
{
LogError("Buy trade: Take profit must be above entry price");
return false;
}
}
else
{
if (sl <= entry)
{
LogError("Sell trade: Stop loss must be above entry price");
return false;
}
if (tp >= entry)
{
LogError("Sell trade: Take profit must be below entry price");
return false;
}
}
// Check minimum distance requirements
int stops_level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double min_distance = stops_level * point;
if (is_buy)
{
if ((entry - sl) < min_distance || (tp - entry) < min_distance)
{
LogError(StringFormat("Insufficient distance to stops level (%d points)", stops_level));
return false;
}
}
else
{
if ((sl - entry) < min_distance || (entry - tp) < min_distance)
{
LogError(StringFormat("Insufficient distance to stops level (%d points)", stops_level));
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Position Sizing and Risk Calculation Functions |
//+------------------------------------------------------------------+
double CalculatePositionSize(string symbol, double risk_amount, double sl_distance)
{
if (sl_distance <= 0)
{
LogError("Invalid stop loss distance for position sizing");
return 0.0;
}
// Get symbol specifications
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if (tick_value == 0 || tick_size == 0)
{
LogError(StringFormat("Invalid symbol specifications for %s", symbol));
return 0.0;
}
// Calculate position size based on risk
double value_per_pip = tick_value / tick_size;
double position_size = risk_amount / (sl_distance * value_per_pip);
// Normalize to lot step
position_size = MathFloor(position_size / lot_step) * lot_step;
// Apply limits
position_size = MathMax(position_size, min_lot);
position_size = MathMin(position_size, max_lot);
LogDebug(StringFormat("Position size calculated for %s: %.2f lots (Risk: %.2f, SL Distance: %.5f)",
symbol, position_size, risk_amount, sl_distance));
return position_size;
}
double CalculateRiskAmount(double account_balance, double risk_percent)
{
if (risk_percent <= 0 || risk_percent > 10)
{
LogError(StringFormat("Invalid risk percentage: %.2f%%", risk_percent));
return 0.0;
}
double risk_amount = account_balance * (risk_percent / 100.0);
LogDebug(StringFormat("Risk amount calculated: %.2f (%.2f%% of %.2f)",
risk_amount, risk_percent, account_balance));
return risk_amount;
}
bool ValidateTradeConditions(string symbol, bool is_buy)
{
// Check if symbol is tradeable
if (!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE))
{
LogWarning(StringFormat("Trading disabled for %s", symbol));
return false;
}
// Check market hours
if (!SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE))
{
LogWarning(StringFormat("Market closed for %s", symbol));
return false;
}
// Check position limits
int current_positions = CountPositionsForSymbol(symbol);
if (current_positions >= MaxPositionsPerSymbol)
{
LogWarning(StringFormat("Maximum positions reached for %s (%d/%d)",
symbol, current_positions, MaxPositionsPerSymbol));
return false;
}
// Check total position limit
int total_positions = PositionsTotal();
if (total_positions >= MaxPositions)
{
LogWarning(StringFormat("Maximum total positions reached (%d/%d)",
total_positions, MaxPositions));
return false;
}
// Check account free margin
double required_margin = CalculateRequiredMargin(symbol, 0.01); // Minimum lot for estimation
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
if (free_margin < required_margin * 10) // Require 10x minimum margin as buffer
{
LogWarning(StringFormat("Insufficient free margin: %.2f (required: %.2f)",
free_margin, required_margin * 10));
return false;
}
return true;
}
int CountPositionsForSymbol(string symbol)
{
int count = 0;
for (int i = 0; i < PositionsTotal(); i++)
{
if (position.SelectByIndex(i))
{
if (position.Symbol() == symbol && position.Magic() == trade.RequestMagic())
{
count++;
}
}
}
return count;
}
double CalculateRequiredMargin(string symbol, double lot_size)
{
double margin_required = 0;
// Use OrderCalcMargin for accurate calculation
if (!OrderCalcMargin(ORDER_TYPE_BUY, symbol, lot_size,
SymbolInfoDouble(symbol, SYMBOL_ASK), margin_required))
{
// Fallback calculation
double contract_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
double margin_rate = SymbolInfoDouble(symbol, SYMBOL_MARGIN_INITIAL);
double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
margin_required = (lot_size * contract_size * current_price * margin_rate) /
AccountInfoInteger(ACCOUNT_LEVERAGE);
}
return margin_required;
}
//+------------------------------------------------------------------+
//| Stop Loss and Take Profit Calculation Functions |
//+------------------------------------------------------------------+
double CalculateStopLoss(string symbol, bool is_buy, OrderBlock &ob, LiquiditySweep &sweep)
{
double sl_price = 0.0;
double pip_value = CalculatePipValue(symbol);
double buffer = 5.0 * pip_value; // 5 pip buffer beyond the level
if (is_buy)
{
// For buy trades, SL should be below the entry level
if (sweep.level > 0 && !sweep.is_high_sweep)
{
// Use liquidity sweep level for SL (low sweep for buy setup)
sl_price = sweep.level - buffer;
LogDebug(StringFormat("Buy SL based on liquidity sweep: %.5f", sl_price));
}
else if (ob.is_bullish && ob.low > 0)
{
// Use Order Block low for SL
sl_price = ob.low - buffer;
LogDebug(StringFormat("Buy SL based on Order Block: %.5f", sl_price));
}
else
{
// Fallback: use current price with minimum SL
double current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
sl_price = current_price - (MinSL * pip_value);
LogDebug(StringFormat("Buy SL fallback: %.5f", sl_price));
}
}
else
{
// For sell trades, SL should be above the entry level
if (sweep.level > 0 && sweep.is_high_sweep)
{
// Use liquidity sweep level for SL (high sweep for sell setup)
sl_price = sweep.level + buffer;
LogDebug(StringFormat("Sell SL based on liquidity sweep: %.5f", sl_price));
}
else if (!ob.is_bullish && ob.high > 0)
{
// Use Order Block high for SL
sl_price = ob.high + buffer;
LogDebug(StringFormat("Sell SL based on Order Block: %.5f", sl_price));
}
else
{
// Fallback: use current price with minimum SL
double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
sl_price = current_price + (MinSL * pip_value);
LogDebug(StringFormat("Sell SL fallback: %.5f", sl_price));
}
}
// Validate SL distance
double current_price = is_buy ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID);
double sl_distance = MathAbs(current_price - sl_price);
double min_sl_distance = MinSL * pip_value;
double max_sl_distance = MaxSL * pip_value;
if (sl_distance < min_sl_distance)
{
LogWarning(StringFormat("SL distance too small (%.1f pips), adjusting to minimum", sl_distance / pip_value));
sl_price = is_buy ? current_price - min_sl_distance : current_price + min_sl_distance;
}
else if (sl_distance > max_sl_distance)
{
LogWarning(StringFormat("SL distance too large (%.1f pips), adjusting to maximum", sl_distance / pip_value));
sl_price = is_buy ? current_price - max_sl_distance : current_price + max_sl_distance;
}
return NormalizePrice(symbol, sl_price);
}
double CalculateTakeProfit(string symbol, bool is_buy, double entry, double sl, double rr_ratio)
{
if (rr_ratio < MinRR)
{
LogWarning(StringFormat("RR ratio %.2f below minimum %.2f, adjusting", rr_ratio, MinRR));
rr_ratio = MinRR;
}
double sl_distance = MathAbs(entry - sl);
double tp_distance = sl_distance * rr_ratio;
double tp_price = 0.0;
if (is_buy)
{
tp_price = entry + tp_distance;
}
else
{
tp_price = entry - tp_distance;
}
LogDebug(StringFormat("TP calculated for %s: %.5f (RR: %.2f:1, Distance: %.1f pips)",
symbol, tp_price, rr_ratio, tp_distance / CalculatePipValue(symbol)));
return NormalizePrice(symbol, tp_price);
}
double CalculateOptimalRR(string symbol, bool is_buy, double entry, FairValueGap &fvg)
{
double base_rr = MinRR; // Start with minimum RR
// Adjust RR based on FVG size (larger gaps = higher potential)
if (fvg.top > 0 && fvg.bottom > 0)
{
double fvg_size = fvg.top - fvg.bottom;
double pip_value = CalculatePipValue(symbol);
double fvg_pips = fvg_size / pip_value;
if (fvg_pips > 10)
{
base_rr = 3.0; // Higher RR for larger FVGs
}
else if (fvg_pips > 5)
{
base_rr = 2.5;
}
}
// Adjust based on session (higher volatility = higher RR potential)
string current_session = GetCurrentSession();
if (current_session == "LONDON" || current_session == "NEW YORK")
{
base_rr += 0.5; // Add 0.5 to RR during high volatility sessions
}
// Cap the maximum RR
base_rr = MathMin(base_rr, 4.0);
LogDebug(StringFormat("Optimal RR calculated: %.2f:1 for %s", base_rr, symbol));
return base_rr;
}
//+------------------------------------------------------------------+
//| Entry Opportunity Analysis Functions |
//+------------------------------------------------------------------+
bool AnalyzeEntryOpportunity(string symbol, ENUM_TIMEFRAMES tf = PERIOD_M1)
{
LogDebug(StringFormat("Analyzing entry opportunity for %s on %s", symbol, EnumToString(tf)));
// Update multi-timeframe analysis for this symbol
if (!UpdateMultiTimeframeAnalysis(symbol))
{
LogWarning(StringFormat("Failed to update multi-timeframe analysis for %s", symbol));
return false;
}
// Get M1 timeframe data for entry signals
MarketStructureData m1_data;
if (!GetTimeframeData(PERIOD_M1, m1_data) || !m1_data.is_valid)
{
LogDebug(StringFormat("M1 data not available or invalid for %s", symbol));
return false;
}
// Check multi-timeframe bias if required
if (RequireMultiTFConfirmation)
{
string market_bias = GetMarketBias(symbol);
if (market_bias == "NEUTRAL")
{
LogDebug(StringFormat("Neutral market bias for %s, skipping", symbol));
return false;
}
}
// Analyze bullish setups
if (AnalyzeBullishSetup(symbol))
{
LogPattern("Entry Opportunity", symbol, "Bullish setup detected");
return true;
}
// Analyze bearish setups
if (AnalyzeBearishSetup(symbol))
{
LogPattern("Entry Opportunity", symbol, "Bearish setup detected");
return true;
}
return false;
}
bool AnalyzeBullishSetup(string symbol)
{
// Get M1 timeframe data
MarketStructureData m1_data;
if (!GetTimeframeData(PERIOD_M1, m1_data) || !m1_data.is_valid)
{
return false;
}
// Step 1: Find valid liquidity sweep (low sweep for bullish setup)
LiquiditySweep valid_sweep;
bool sweep_found = false;
for (int i = 0; i < ArraySize(m1_data.liquidity_sweeps); i++)
{
if (!m1_data.liquidity_sweeps[i].is_high_sweep &&
IsLiquiditySweepValid(symbol, PERIOD_M1, m1_data.liquidity_sweeps[i]))
{
valid_sweep = m1_data.liquidity_sweeps[i];
sweep_found = true;
break;
}
}
if (!sweep_found)
{
LogDebug(StringFormat("No valid low sweep found for bullish setup on %s", symbol));
return false;
}
// Step 2: Find opposite direction BOS (bullish BOS after low sweep)
BreakOfStructure valid_bos;
bool bos_found = false;
for (int i = 0; i < ArraySize(m1_data.bos_events); i++)
{
if (m1_data.bos_events[i].is_bullish &&
m1_data.bos_events[i].confirmed &&
m1_data.bos_events[i].time > valid_sweep.time) // BOS must be after sweep
{
valid_bos = m1_data.bos_events[i];
bos_found = true;
break;
}
}
if (!bos_found)
{
LogDebug(StringFormat("No valid bullish BOS found after low sweep on %s", symbol));
return false;
}
// Step 3: Find valid FVG between BOS and current price
FairValueGap valid_fvg;
bool fvg_found = false;
for (int i = 0; i < ArraySize(m1_data.fair_value_gaps); i++)
{
if (m1_data.fair_value_gaps[i].is_bullish &&
IsFVGValid(symbol, PERIOD_M1, m1_data.fair_value_gaps[i]) &&
m1_data.fair_value_gaps[i].time > valid_bos.time) // FVG must be after BOS
{
valid_fvg = m1_data.fair_value_gaps[i];
fvg_found = true;
break;
}
}
if (!fvg_found)
{
LogDebug(StringFormat("No valid bullish FVG found after BOS on %s", symbol));
return false;
}
// Step 4: Find fresh bullish Order Block
OrderBlock valid_ob;
bool ob_found = false;
for (int i = 0; i < ArraySize(m1_data.order_blocks); i++)
{
if (m1_data.order_blocks[i].is_bullish &&
m1_data.order_blocks[i].is_fresh &&
m1_data.order_blocks[i].strength >= OBStrengthFilter &&
m1_data.order_blocks[i].time > valid_fvg.time) // OB must be after FVG
{
valid_ob = m1_data.order_blocks[i];
ob_found = true;
break;
}
}
if (!ob_found)
{
LogDebug(StringFormat("No valid fresh bullish OB found after FVG on %s", symbol));
return false;
}
// Step 5: Check multi-timeframe alignment
if (RequireMultiTFConfirmation)
{
if (!IsMultiTimeframeAligned(symbol, true))
{
LogDebug(StringFormat("Multi-timeframe not aligned for bullish setup on %s", symbol));
return false;
}
}
// Step 6: Execute bullish trade
return ExecuteBullishTrade(symbol, valid_ob, valid_fvg, valid_sweep);
}
bool AnalyzeBearishSetup(string symbol)
{
// Get M1 timeframe data
MarketStructureData m1_data;
if (!GetTimeframeData(PERIOD_M1, m1_data) || !m1_data.is_valid)
{
return false;
}
// Step 1: Find valid liquidity sweep (high sweep for bearish setup)
LiquiditySweep valid_sweep;
bool sweep_found = false;
for (int i = 0; i < ArraySize(m1_data.liquidity_sweeps); i++)
{
if (m1_data.liquidity_sweeps[i].is_high_sweep &&
IsLiquiditySweepValid(symbol, PERIOD_M1, m1_data.liquidity_sweeps[i]))
{
valid_sweep = m1_data.liquidity_sweeps[i];
sweep_found = true;
break;
}
}
if (!sweep_found)
{
LogDebug(StringFormat("No valid high sweep found for bearish setup on %s", symbol));
return false;
}
// Step 2: Find opposite direction BOS (bearish BOS after high sweep)
BreakOfStructure valid_bos;
bool bos_found = false;
for (int i = 0; i < ArraySize(m1_data.bos_events); i++)
{
if (!m1_data.bos_events[i].is_bullish &&
m1_data.bos_events[i].confirmed &&
m1_data.bos_events[i].time > valid_sweep.time) // BOS must be after sweep
{
valid_bos = m1_data.bos_events[i];
bos_found = true;
break;
}
}
if (!bos_found)
{
LogDebug(StringFormat("No valid bearish BOS found after high sweep on %s", symbol));
return false;
}
// Step 3: Find valid FVG between BOS and current price
FairValueGap valid_fvg;
bool fvg_found = false;
for (int i = 0; i < ArraySize(m1_data.fair_value_gaps); i++)
{
if (!m1_data.fair_value_gaps[i].is_bullish &&
IsFVGValid(symbol, PERIOD_M1, m1_data.fair_value_gaps[i]) &&
m1_data.fair_value_gaps[i].time > valid_bos.time) // FVG must be after BOS
{
valid_fvg = m1_data.fair_value_gaps[i];
fvg_found = true;
break;
}
}
if (!fvg_found)
{
LogDebug(StringFormat("No valid bearish FVG found after BOS on %s", symbol));
return false;
}
// Step 4: Find fresh bearish Order Block
OrderBlock valid_ob;
bool ob_found = false;
for (int i = 0; i < ArraySize(m1_data.order_blocks); i++)
{
if (!m1_data.order_blocks[i].is_bullish &&
m1_data.order_blocks[i].is_fresh &&
m1_data.order_blocks[i].strength >= OBStrengthFilter &&
m1_data.order_blocks[i].time > valid_fvg.time) // OB must be after FVG
{
valid_ob = m1_data.order_blocks[i];
ob_found = true;
break;
}
}
if (!ob_found)
{
LogDebug(StringFormat("No valid fresh bearish OB found after FVG on %s", symbol));
return false;
}
// Step 5: Check multi-timeframe alignment
if (RequireMultiTFConfirmation)
{
if (!IsMultiTimeframeAligned(symbol, false))
{
LogDebug(StringFormat("Multi-timeframe not aligned for bearish setup on %s", symbol));
return false;
}
}
// Step 6: Execute bearish trade
return ExecuteBearishTrade(symbol, valid_ob, valid_fvg, valid_sweep);
}
//+------------------------------------------------------------------+
//| Trade Execution Logic Functions |
//+------------------------------------------------------------------+
bool ExecuteBullishTrade(string symbol, OrderBlock &ob, FairValueGap &fvg, LiquiditySweep &sweep)
{
LogInfo(StringFormat("Executing bullish trade for %s", symbol));
// Validate trade conditions
if (!ValidateTradeConditions(symbol, true))
{
LogWarning(StringFormat("Trade conditions not met for bullish trade on %s", symbol));
return false;
}
// Calculate entry price (prefer FVG midpoint, fallback to OB zone)
double entry_price = 0.0;
if (fvg.top > 0 && fvg.bottom > 0)
{
entry_price = GetFVGMidpoint(fvg);
LogDebug(StringFormat("Using FVG midpoint for entry: %.5f", entry_price));
}
else
{
entry_price = (ob.high + ob.low) / 2.0; // OB midpoint
LogDebug(StringFormat("Using OB midpoint for entry: %.5f", entry_price));
}
// Calculate stop loss
double sl_price = CalculateStopLoss(symbol, true, ob, sweep);
if (sl_price <= 0)
{
LogError(StringFormat("Invalid stop loss calculated for %s", symbol));
return false;
}
// Calculate optimal risk-reward ratio
double rr_ratio = CalculateOptimalRR(symbol, true, entry_price, fvg);
// Calculate take profit
double tp_price = CalculateTakeProfit(symbol, true, entry_price, sl_price, rr_ratio);
if (tp_price <= entry_price)
{
LogError(StringFormat("Invalid take profit calculated for %s", symbol));
return false;
}
// Calculate position size
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double risk_amount = CalculateRiskAmount(account_balance, RiskPercent);
double sl_distance = MathAbs(entry_price - sl_price);
double lot_size = CalculatePositionSize(symbol, risk_amount, sl_distance);
if (lot_size <= 0)
{
LogError(StringFormat("Invalid lot size calculated for %s", symbol));
return false;
}
// Execute the trade
bool trade_result = ExecuteBuyTrade(symbol, entry_price, sl_price, tp_price, lot_size);
if (trade_result)
{
LogTrade("BULLISH SETUP EXECUTED", symbol,
StringFormat("Entry: %.5f, SL: %.5f (%.1f pips), TP: %.5f (%.2f:1 RR), Lot: %.2f",
entry_price, sl_price, sl_distance / CalculatePipValue(symbol),
tp_price, rr_ratio, lot_size));
}
return trade_result;
}
bool ExecuteBearishTrade(string symbol, OrderBlock &ob, FairValueGap &fvg, LiquiditySweep &sweep)
{
LogInfo(StringFormat("Executing bearish trade for %s", symbol));
// Validate trade conditions
if (!ValidateTradeConditions(symbol, false))
{
LogWarning(StringFormat("Trade conditions not met for bearish trade on %s", symbol));
return false;
}
// Calculate entry price (prefer FVG midpoint, fallback to OB zone)
double entry_price = 0.0;
if (fvg.top > 0 && fvg.bottom > 0)
{
entry_price = GetFVGMidpoint(fvg);
LogDebug(StringFormat("Using FVG midpoint for entry: %.5f", entry_price));
}
else
{
entry_price = (ob.high + ob.low) / 2.0; // OB midpoint
LogDebug(StringFormat("Using OB midpoint for entry: %.5f", entry_price));
}
// Calculate stop loss
double sl_price = CalculateStopLoss(symbol, false, ob, sweep);
if (sl_price <= 0)
{
LogError(StringFormat("Invalid stop loss calculated for %s", symbol));
return false;
}
// Calculate optimal risk-reward ratio
double rr_ratio = CalculateOptimalRR(symbol, false, entry_price, fvg);
// Calculate take profit
double tp_price = CalculateTakeProfit(symbol, false, entry_price, sl_price, rr_ratio);
if (tp_price >= entry_price)
{
LogError(StringFormat("Invalid take profit calculated for %s", symbol));
return false;
}
// Calculate position size
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double risk_amount = CalculateRiskAmount(account_balance, RiskPercent);
double sl_distance = MathAbs(entry_price - sl_price);
double lot_size = CalculatePositionSize(symbol, risk_amount, sl_distance);
if (lot_size <= 0)
{
LogError(StringFormat("Invalid lot size calculated for %s", symbol));
return false;
}
// Execute the trade
bool trade_result = ExecuteSellTrade(symbol, entry_price, sl_price, tp_price, lot_size);
if (trade_result)
{
LogTrade("BEARISH SETUP EXECUTED", symbol,
StringFormat("Entry: %.5f, SL: %.5f (%.1f pips), TP: %.5f (%.2f:1 RR), Lot: %.2f",
entry_price, sl_price, sl_distance / CalculatePipValue(symbol),
tp_price, rr_ratio, lot_size));
}
return trade_result;
}
//+------------------------------------------------------------------+
//| Main trading logic processor |
//+------------------------------------------------------------------+
void ProcessTradingLogic()
{
// Update information panel
UpdateInfoPanel();
// Check if trading is allowed in current session
if (UseTimeFilter && GetCurrentSession() == "OFF HOURS")
{
LogDebug("Trading outside allowed session hours");
return;
}
// Check account status
if (!IsAccountTradingAllowed())
{
LogWarning("Account trading not allowed");
return;
}
// Manage existing positions first
ManageOpenPositions();
// Check if we can open new positions
if (PositionsTotal() >= MaxPositions)
{
LogDebug(StringFormat("Maximum positions reached (%d/%d)", PositionsTotal(), MaxPositions));
return;
}
// Process each symbol for trading opportunities
for (int i = 0; i < TotalSymbols; i++)
{
string symbol = SymbolsToTrade[i];
// Skip if symbol has reached maximum positions
if (CountPositionsForSymbol(symbol) >= MaxPositionsPerSymbol)
{
LogDebug(StringFormat("Maximum positions reached for %s (%d/%d)",
symbol, CountPositionsForSymbol(symbol), MaxPositionsPerSymbol));
continue;
}
// Analyze entry opportunities for this symbol
if (AnalyzeEntryOpportunity(symbol, PERIOD_M1))
{
LogInfo(StringFormat("Entry opportunity processed for %s", symbol));
}
}
// Update multi-timeframe status for debugging
if (EnableDebugMode)
{
for (int i = 0; i < TotalSymbols; i++)
{
PrintMultiTimeframeStatus(SymbolsToTrade[i]);
}
}
}
bool IsAccountTradingAllowed()
{
// Check if trading is allowed on the account
if (!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
{
LogError("Trading not allowed on this account");
return false;
}
// Check if Expert Advisors are allowed
if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
{
LogError("Expert Advisor trading not allowed in terminal");
return false;
}
// Check account balance
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
if (account_balance <= 0)
{
LogError("Invalid account balance");
return false;
}
// Check free margin
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
if (free_margin <= 0)
{
LogError("No free margin available");
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Position Management Functions |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
if (position.SelectByIndex(i))
{
// Only manage positions opened by this EA
if (position.Magic() != trade.RequestMagic())
continue;
string symbol = position.Symbol();
ulong ticket = position.Ticket();
// Check for position management opportunities
if (ShouldUpdatePosition(ticket))
{
UpdatePositionManagement(ticket);
}
}
}
}
bool ShouldUpdatePosition(ulong ticket)
{
if (!position.SelectByTicket(ticket))
return false;
// Check if position is in profit for trailing stop
double current_profit = position.Profit();
double position_open_price = position.PriceOpen();
double current_price = position.Type() == POSITION_TYPE_BUY ? SymbolInfoDouble(position.Symbol(), SYMBOL_BID) : SymbolInfoDouble(position.Symbol(), SYMBOL_ASK);
// Simple break-even logic
double pip_value = CalculatePipValue(position.Symbol());
double profit_pips = MathAbs(current_price - position_open_price) / pip_value;
// Move to break-even when in 20+ pips profit
if (profit_pips >= 20.0)
{
double current_sl = position.StopLoss();
double break_even_price = position_open_price;
if (position.Type() == POSITION_TYPE_BUY)
{
if (current_sl < break_even_price)
{
LogInfo(StringFormat("Moving position %llu to break-even", ticket));
return true;
}
}
else
{
if (current_sl > break_even_price)
{
LogInfo(StringFormat("Moving position %llu to break-even", ticket));
return true;
}
}
}
return false;
}
void UpdatePositionManagement(ulong ticket)
{
if (!position.SelectByTicket(ticket))
return;
double new_sl = position.PriceOpen(); // Break-even
double current_tp = position.TakeProfit();
// Modify position to break-even
if (trade.PositionModify(ticket, new_sl, current_tp))
{
LogTrade("POSITION MODIFIED", position.Symbol(),
StringFormat("Ticket: %llu moved to break-even at %.5f", ticket, new_sl));
}
else
{
int error_code = trade.ResultRetcode();
HandleTradeError(error_code, "Position Modification");
}
}
//+------------------------------------------------------------------+
//| Logging Functions |
//+------------------------------------------------------------------+
void LogInfo(string message)
{
if (EnableDetailedLogging)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
Print("[", timestamp, "] [INFO] ", LogPrefix, ": ", message);
}
}
void LogWarning(string message)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
Print("[", timestamp, "] [WARNING] ", LogPrefix, ": ", message);
}
void LogError(string message)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
Print("[", timestamp, "] [ERROR] ", LogPrefix, ": ", message);
}
void LogDebug(string message)
{
if (EnableDebugMode)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
Print("[", timestamp, "] [DEBUG] ", LogPrefix, ": ", message);
}
}
void LogPattern(string pattern_type, string symbol, string details)
{
if (LogPatternDetection)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
Print("[", timestamp, "] [PATTERN] ", LogPrefix, ": ", pattern_type, " detected on ", symbol, " - ", details);
}
}
void LogTrade(string action, string symbol, string details)
{
if (LogTradeExecution)
{
string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS);
Print("[", timestamp, "] [TRADE] ", LogPrefix, ": ", action, " on ", symbol, " - ", details);
}
}
//+------------------------------------------------------------------+
//| Error Handling Functions |
//+------------------------------------------------------------------+
bool HandleTradeError(int error_code, string operation)
{
string error_description = "";
bool is_critical = false;
switch (error_code)
{
case TRADE_RETCODE_REQUOTE:
error_description = "Requote";
break;
case TRADE_RETCODE_REJECT:
error_description = "Request rejected";
is_critical = true;
break;
case TRADE_RETCODE_CANCEL:
error_description = "Request canceled by trader";
break;
case TRADE_RETCODE_PLACED:
error_description = "Order placed";
return true; // Success
case TRADE_RETCODE_DONE:
error_description = "Request completed";
return true; // Success
case TRADE_RETCODE_DONE_PARTIAL:
error_description = "Request partially completed";
return true; // Partial success
case TRADE_RETCODE_ERROR:
error_description = "Request processing error";
is_critical = true;
break;
case TRADE_RETCODE_TIMEOUT:
error_description = "Request timeout";
break;
case TRADE_RETCODE_INVALID:
error_description = "Invalid request";
is_critical = true;
break;
case TRADE_RETCODE_INVALID_VOLUME:
error_description = "Invalid volume";
is_critical = true;
break;
case TRADE_RETCODE_INVALID_PRICE:
error_description = "Invalid price";
break;
case TRADE_RETCODE_INVALID_STOPS:
error_description = "Invalid stops";
break;
case TRADE_RETCODE_TRADE_DISABLED:
error_description = "Trade disabled";
is_critical = true;
break;
case TRADE_RETCODE_MARKET_CLOSED:
error_description = "Market closed";
break;
case TRADE_RETCODE_NO_MONEY:
error_description = "No money";
is_critical = true;
break;
case TRADE_RETCODE_PRICE_CHANGED:
error_description = "Price changed";
break;
case TRADE_RETCODE_PRICE_OFF:
error_description = "Off quotes";
break;
case TRADE_RETCODE_INVALID_EXPIRATION:
error_description = "Invalid expiration";
break;
case TRADE_RETCODE_ORDER_CHANGED:
error_description = "Order state changed";
break;
case TRADE_RETCODE_TOO_MANY_REQUESTS:
error_description = "Too many requests";
break;
case TRADE_RETCODE_NO_CHANGES:
error_description = "No changes";
break;
case TRADE_RETCODE_SERVER_DISABLES_AT:
error_description = "Autotrading disabled by server";
is_critical = true;
break;
case TRADE_RETCODE_CLIENT_DISABLES_AT:
error_description = "Autotrading disabled by client";
is_critical = true;
break;
case TRADE_RETCODE_LOCKED:
error_description = "Request locked";
break;
case TRADE_RETCODE_FROZEN:
error_description = "Order or position frozen";
break;
case TRADE_RETCODE_INVALID_FILL:
error_description = "Invalid fill";
break;
case TRADE_RETCODE_CONNECTION:
error_description = "No connection";
is_critical = true;
break;
case TRADE_RETCODE_ONLY_REAL:
error_description = "Only real accounts allowed";
is_critical = true;
break;
case TRADE_RETCODE_LIMIT_ORDERS:
error_description = "Limit orders limit reached";
break;
case TRADE_RETCODE_LIMIT_VOLUME:
error_description = "Volume limit reached";
break;
case TRADE_RETCODE_INVALID_ORDER:
error_description = "Invalid order";
is_critical = true;
break;
case TRADE_RETCODE_POSITION_CLOSED:
error_description = "Position already closed";
break;
default:
error_description = "Unknown error";
is_critical = true;
break;
}
if (is_critical)
{
LogError(StringFormat("%s failed with critical error %d: %s", operation, error_code, error_description));
}
else
{
LogWarning(StringFormat("%s failed with error %d: %s", operation, error_code, error_description));
}
return false;
}
//+------------------------------------------------------------------+
//| Utility Functions |
//+------------------------------------------------------------------+
double NormalizePrice(string symbol, double price)
{
return NormalizeDouble(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS));
}
double CalculatePipValue(string symbol)
{
double pip_size = SymbolInfoDouble(symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
if (digits == 5 || digits == 3)
pip_size *= 10;
return pip_size;
}
bool IsNewBar(string symbol, ENUM_TIMEFRAMES timeframe)
{
static datetime last_bar_time = 0;
datetime current_bar_time = iTime(symbol, timeframe, 0);
if (current_bar_time != last_bar_time)
{
last_bar_time = current_bar_time;
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Order Block Detection Functions |
//+------------------------------------------------------------------+
bool DetectOrderBlocks(string symbol, ENUM_TIMEFRAMES timeframe, OrderBlock &order_blocks[])
{
ArrayResize(order_blocks, 0);
int bars_to_analyze = MathMin(OBLookback * 2, iBars(symbol, timeframe) - 10);
if (bars_to_analyze < 10)
return false;
LogDebug(StringFormat("Analyzing %d bars for Order Blocks on %s %s", bars_to_analyze, symbol, EnumToString(timeframe)));
// Look for potential Order Blocks
for (int i = 5; i < bars_to_analyze; i++)
{
// Get candle data
double high = iHigh(symbol, timeframe, i);
double low = iLow(symbol, timeframe, i);
double open = iOpen(symbol, timeframe, i);
double close = iClose(symbol, timeframe, i);
datetime time = iTime(symbol, timeframe, i);
// Check for bullish Order Block (demand zone)
if (IsBullishOrderBlock(symbol, timeframe, i))
{
OrderBlock ob;
ob.high = high;
ob.low = low;
ob.time = time;
ob.is_bullish = true;
ob.is_fresh = IsOrderBlockFresh(symbol, timeframe, i, true);
ob.strength = CalculateOrderBlockStrength(symbol, timeframe, i, true);
if (ob.strength >= OBStrengthFilter)
{
ArrayResize(order_blocks, ArraySize(order_blocks) + 1);
order_blocks[ArraySize(order_blocks) - 1] = ob;
LogPattern("Order Block", symbol, StringFormat("Bullish OB at %.5f-%.5f, Strength: %.2f", ob.low, ob.high, ob.strength));
}
}
// Check for bearish Order Block (supply zone)
if (IsBearishOrderBlock(symbol, timeframe, i))
{
OrderBlock ob;
ob.high = high;
ob.low = low;
ob.time = time;
ob.is_bullish = false;
ob.is_fresh = IsOrderBlockFresh(symbol, timeframe, i, false);
ob.strength = CalculateOrderBlockStrength(symbol, timeframe, i, false);
if (ob.strength >= OBStrengthFilter)
{
ArrayResize(order_blocks, ArraySize(order_blocks) + 1);
order_blocks[ArraySize(order_blocks) - 1] = ob;
LogPattern("Order Block", symbol, StringFormat("Bearish OB at %.5f-%.5f, Strength: %.2f", ob.low, ob.high, ob.strength));
}
}
}
LogDebug(StringFormat("Found %d Order Blocks on %s %s", ArraySize(order_blocks), symbol, EnumToString(timeframe)));
return ArraySize(order_blocks) > 0;
}
bool IsBullishOrderBlock(string symbol, ENUM_TIMEFRAMES timeframe, int index)
{
// Get current candle data
double open = iOpen(symbol, timeframe, index);
double close = iClose(symbol, timeframe, index);
double high = iHigh(symbol, timeframe, index);
double low = iLow(symbol, timeframe, index);
// Must be a bullish candle
if (close <= open)
return false;
// Check for strong bullish momentum (body > 60% of total range)
double body_size = close - open;
double total_range = high - low;
if (total_range == 0)
return false;
double body_ratio = body_size / total_range;
if (body_ratio < 0.6)
return false;
// Check for significant volume increase (if available)
long current_volume = iVolume(symbol, timeframe, index);
long avg_volume = 0;
for (int i = 1; i <= 5; i++)
{
avg_volume += iVolume(symbol, timeframe, index + i);
}
avg_volume /= 5;
if (current_volume < avg_volume * 1.2)
return false;
// Check for price rejection from this level in subsequent candles
bool has_rejection = false;
for (int i = 1; i <= 5; i++)
{
if (index - i < 0)
break;
double test_low = iLow(symbol, timeframe, index - i);
double test_close = iClose(symbol, timeframe, index - i);
// Price came back to test the OB zone and bounced
if (test_low <= high && test_low >= low && test_close > high)
{
has_rejection = true;
break;
}
}
return has_rejection;
}
bool IsBearishOrderBlock(string symbol, ENUM_TIMEFRAMES timeframe, int index)
{
// Get current candle data
double open = iOpen(symbol, timeframe, index);
double close = iClose(symbol, timeframe, index);
double high = iHigh(symbol, timeframe, index);
double low = iLow(symbol, timeframe, index);
// Must be a bearish candle
if (close >= open)
return false;
// Check for strong bearish momentum (body > 60% of total range)
double body_size = open - close;
double total_range = high - low;
if (total_range == 0)
return false;
double body_ratio = body_size / total_range;
if (body_ratio < 0.6)
return false;
// Check for significant volume increase (if available)
long current_volume = iVolume(symbol, timeframe, index);
long avg_volume = 0;
for (int i = 1; i <= 5; i++)
{
avg_volume += iVolume(symbol, timeframe, index + i);
}
avg_volume /= 5;
if (current_volume < avg_volume * 1.2)
return false;
// Check for price rejection from this level in subsequent candles
bool has_rejection = false;
for (int i = 1; i <= 5; i++)
{
if (index - i < 0)
break;
double test_high = iHigh(symbol, timeframe, index - i);
double test_close = iClose(symbol, timeframe, index - i);
// Price came back to test the OB zone and bounced
if (test_high >= low && test_high <= high && test_close < low)
{
has_rejection = true;
break;
}
}
return has_rejection;
}
bool IsOrderBlockFresh(string symbol, ENUM_TIMEFRAMES timeframe, int ob_index, bool is_bullish)
{
double ob_high = iHigh(symbol, timeframe, ob_index);
double ob_low = iLow(symbol, timeframe, ob_index);
// Check if price has significantly broken through the OB zone
for (int i = 0; i < ob_index; i++)
{
double test_high = iHigh(symbol, timeframe, i);
double test_low = iLow(symbol, timeframe, i);
if (is_bullish)
{
// For bullish OB, check if price broke significantly below
if (test_low < ob_low - (ob_high - ob_low) * 0.5)
return false;
}
else
{
// For bearish OB, check if price broke significantly above
if (test_high > ob_high + (ob_high - ob_low) * 0.5)
return false;
}
}
return true;
}
double CalculateOrderBlockStrength(string symbol, ENUM_TIMEFRAMES timeframe, int index, bool is_bullish)
{
double strength = 0.0;
// Factor 1: Candle body size relative to average
double body_size = MathAbs(iClose(symbol, timeframe, index) - iOpen(symbol, timeframe, index));
double avg_body = 0;
for (int i = 1; i <= 10; i++)
{
avg_body += MathAbs(iClose(symbol, timeframe, index + i) - iOpen(symbol, timeframe, index + i));
}
avg_body /= 10;
if (avg_body > 0)
strength += (body_size / avg_body) * 0.3; // 30% weight
// Factor 2: Volume relative to average
long current_volume = iVolume(symbol, timeframe, index);
long avg_volume = 0;
for (int i = 1; i <= 10; i++)
{
avg_volume += iVolume(symbol, timeframe, index + i);
}
avg_volume /= 10;
if (avg_volume > 0)
strength += ((double)current_volume / avg_volume) * 0.2; // 20% weight
// Factor 3: Number of times price respected the level
int respect_count = 0;
double ob_high = iHigh(symbol, timeframe, index);
double ob_low = iLow(symbol, timeframe, index);
for (int i = 1; i < index && i <= 20; i++)
{
double test_high = iHigh(symbol, timeframe, index - i);
double test_low = iLow(symbol, timeframe, index - i);
double test_close = iClose(symbol, timeframe, index - i);
if (is_bullish)
{
if (test_low <= ob_high && test_low >= ob_low && test_close > ob_high)
respect_count++;
}
else
{
if (test_high >= ob_low && test_high <= ob_high && test_close < ob_low)
respect_count++;
}
}
strength += respect_count * 0.1; // 10% weight per respect
// Factor 4: Time since formation (fresher = stronger)
double time_factor = 1.0 - (index / (double)OBLookback);
strength += time_factor * 0.3; // 30% weight
return MathMin(strength, 2.0); // Cap at 2.0
}
//+------------------------------------------------------------------+
//| Break of Structure Detection Functions |
//+------------------------------------------------------------------+
bool DetectBreakOfStructure(string symbol, ENUM_TIMEFRAMES timeframe, BreakOfStructure &bos_events[])
{
ArrayResize(bos_events, 0);
int bars_to_analyze = MathMin(SwingLookback * 3, iBars(symbol, timeframe) - 10);
if (bars_to_analyze < 20)
return false;
LogDebug(StringFormat("Analyzing %d bars for Break of Structure on %s %s", bars_to_analyze, symbol, EnumToString(timeframe)));
// Find swing highs and lows first
double swing_highs[];
double swing_lows[];
datetime swing_high_times[];
datetime swing_low_times[];
FindSwingPoints(symbol, timeframe, bars_to_analyze, swing_highs, swing_lows, swing_high_times, swing_low_times);
// Analyze for BOS patterns
AnalyzeBOSPatterns(symbol, timeframe, swing_highs, swing_lows, swing_high_times, swing_low_times, bos_events);
LogDebug(StringFormat("Found %d BOS events on %s %s", ArraySize(bos_events), symbol, EnumToString(timeframe)));
return ArraySize(bos_events) > 0;
}
void FindSwingPoints(string symbol, ENUM_TIMEFRAMES timeframe, int bars_to_analyze,
double &swing_highs[], double &swing_lows[],
datetime &swing_high_times[], datetime &swing_low_times[])
{
ArrayResize(swing_highs, 0);
ArrayResize(swing_lows, 0);
ArrayResize(swing_high_times, 0);
ArrayResize(swing_low_times, 0);
for (int i = SwingLookback; i < bars_to_analyze - SwingLookback; i++)
{
double current_high = iHigh(symbol, timeframe, i);
double current_low = iLow(symbol, timeframe, i);
datetime current_time = iTime(symbol, timeframe, i);
// Check for swing high
bool is_swing_high = true;
for (int j = 1; j <= SwingLookback; j++)
{
if (iHigh(symbol, timeframe, i - j) >= current_high ||
iHigh(symbol, timeframe, i + j) >= current_high)
{
is_swing_high = false;
break;
}
}
if (is_swing_high)
{
ArrayResize(swing_highs, ArraySize(swing_highs) + 1);
ArrayResize(swing_high_times, ArraySize(swing_high_times) + 1);
swing_highs[ArraySize(swing_highs) - 1] = current_high;
swing_high_times[ArraySize(swing_high_times) - 1] = current_time;
}
// Check for swing low
bool is_swing_low = true;
for (int j = 1; j <= SwingLookback; j++)
{
if (iLow(symbol, timeframe, i - j) <= current_low ||
iLow(symbol, timeframe, i + j) <= current_low)
{
is_swing_low = false;
break;
}
}
if (is_swing_low)
{
ArrayResize(swing_lows, ArraySize(swing_lows) + 1);
ArrayResize(swing_low_times, ArraySize(swing_low_times) + 1);
swing_lows[ArraySize(swing_lows) - 1] = current_low;
swing_low_times[ArraySize(swing_low_times) - 1] = current_time;
}
}
}
void AnalyzeBOSPatterns(string symbol, ENUM_TIMEFRAMES timeframe,
double &swing_highs[], double &swing_lows[],
datetime &swing_high_times[], datetime &swing_low_times[],
BreakOfStructure &bos_events[])
{
// Analyze bullish BOS (breaking above previous swing high)
for (int i = 1; i < ArraySize(swing_highs); i++)
{
double previous_high = swing_highs[i];
datetime previous_time = swing_high_times[i];
// Look for price breaking above this high
int start_bar = iBarShift(symbol, timeframe, previous_time);
if (start_bar < 0)
continue;
for (int j = 0; j < start_bar && j < BOSConfirmationCandles * 2; j++)
{
double current_high = iHigh(symbol, timeframe, j);
double current_close = iClose(symbol, timeframe, j);
datetime current_time = iTime(symbol, timeframe, j);
if (current_high > previous_high && current_close > previous_high)
{
// Confirm the break with subsequent candles
bool confirmed = ConfirmBOS(symbol, timeframe, j, true, previous_high);
if (confirmed)
{
BreakOfStructure bos;
bos.level = previous_high;
bos.time = current_time;
bos.is_bullish = true;
bos.confirmed = true;
ArrayResize(bos_events, ArraySize(bos_events) + 1);
bos_events[ArraySize(bos_events) - 1] = bos;
LogPattern("Break of Structure", symbol, StringFormat("Bullish BOS at %.5f", previous_high));
break;
}
}
}
}
// Analyze bearish BOS (breaking below previous swing low)
for (int i = 1; i < ArraySize(swing_lows); i++)
{
double previous_low = swing_lows[i];
datetime previous_time = swing_low_times[i];
// Look for price breaking below this low
int start_bar = iBarShift(symbol, timeframe, previous_time);
if (start_bar < 0)
continue;
for (int j = 0; j < start_bar && j < BOSConfirmationCandles * 2; j++)
{
double current_low = iLow(symbol, timeframe, j);
double current_close = iClose(symbol, timeframe, j);
datetime current_time = iTime(symbol, timeframe, j);
if (current_low < previous_low && current_close < previous_low)
{
// Confirm the break with subsequent candles
bool confirmed = ConfirmBOS(symbol, timeframe, j, false, previous_low);
if (confirmed)
{
BreakOfStructure bos;
bos.level = previous_low;
bos.time = current_time;
bos.is_bullish = false;
bos.confirmed = true;
ArrayResize(bos_events, ArraySize(bos_events) + 1);
bos_events[ArraySize(bos_events) - 1] = bos;
LogPattern("Break of Structure", symbol, StringFormat("Bearish BOS at %.5f", previous_low));
break;
}
}
}
}
}
bool ConfirmBOS(string symbol, ENUM_TIMEFRAMES timeframe, int break_bar, bool is_bullish, double level)
{
int confirmation_count = 0;
// Check subsequent candles for confirmation
for (int i = 0; i < BOSConfirmationCandles && break_bar - i >= 0; i++)
{
double close_price = iClose(symbol, timeframe, break_bar - i);
if (is_bullish)
{
if (close_price > level)
confirmation_count++;
}
else
{
if (close_price < level)
confirmation_count++;
}
}
// Require at least 2 out of 3 confirmation candles
return confirmation_count >= MathMax(2, BOSConfirmationCandles / 2);
}
bool IsBOSValid(string symbol, ENUM_TIMEFRAMES timeframe, BreakOfStructure &bos)
{
// Check if BOS is recent enough
datetime current_time = iTime(symbol, timeframe, 0);
int time_diff = (int)((current_time - bos.time) / PeriodSeconds(timeframe));
if (time_diff > BOSConfirmationCandles * 3)
return false;
// Check if price is still respecting the BOS level
double current_price = iClose(symbol, timeframe, 0);
if (bos.is_bullish)
{
return current_price > bos.level;
}
else
{
return current_price < bos.level;
}
}
//+------------------------------------------------------------------+
//| Fair Value Gap Detection Functions |
//+------------------------------------------------------------------+
bool DetectFairValueGaps(string symbol, ENUM_TIMEFRAMES timeframe, FairValueGap &fvg_array[])
{
ArrayResize(fvg_array, 0);
int bars_to_analyze = MathMin(50, iBars(symbol, timeframe) - 5);
if (bars_to_analyze < 10)
return false;
LogDebug(StringFormat("Analyzing %d bars for Fair Value Gaps on %s %s", bars_to_analyze, symbol, EnumToString(timeframe)));
double pip_value = CalculatePipValue(symbol);
double min_gap_size = MinFVGSize * pip_value;
// Look for FVG patterns (3-candle pattern)
for (int i = 2; i < bars_to_analyze; i++)
{
// Get three consecutive candles
double high1 = iHigh(symbol, timeframe, i); // First candle
double low1 = iLow(symbol, timeframe, i);
double high2 = iHigh(symbol, timeframe, i - 1); // Middle candle (impulse)
double low2 = iLow(symbol, timeframe, i - 1);
double high3 = iHigh(symbol, timeframe, i - 2); // Third candle
double low3 = iLow(symbol, timeframe, i - 2);
datetime gap_time = iTime(symbol, timeframe, i - 1);
// Check for bullish FVG (gap between candle 1 high and candle 3 low)
if (low3 > high1)
{
double gap_size = low3 - high1;
if (gap_size >= min_gap_size)
{
FairValueGap fvg;
fvg.top = low3;
fvg.bottom = high1;
fvg.time = gap_time;
fvg.is_bullish = true;
fvg.is_filled = IsFVGFilled(symbol, timeframe, i - 2, fvg.top, fvg.bottom, true);
if (!fvg.is_filled)
{
ArrayResize(fvg_array, ArraySize(fvg_array) + 1);
fvg_array[ArraySize(fvg_array) - 1] = fvg;
LogPattern("Fair Value Gap", symbol, StringFormat("Bullish FVG at %.5f-%.5f, Size: %.1f pips", fvg.bottom, fvg.top, gap_size / pip_value));
}
}
}
// Check for bearish FVG (gap between candle 1 low and candle 3 high)
if (high3 < low1)
{
double gap_size = low1 - high3;
if (gap_size >= min_gap_size)
{
FairValueGap fvg;
fvg.top = low1;
fvg.bottom = high3;
fvg.time = gap_time;
fvg.is_bullish = false;
fvg.is_filled = IsFVGFilled(symbol, timeframe, i - 2, fvg.top, fvg.bottom, false);
if (!fvg.is_filled)
{
ArrayResize(fvg_array, ArraySize(fvg_array) + 1);
fvg_array[ArraySize(fvg_array) - 1] = fvg;
LogPattern("Fair Value Gap", symbol, StringFormat("Bearish FVG at %.5f-%.5f, Size: %.1f pips", fvg.bottom, fvg.top, gap_size / pip_value));
}
}
}
}
LogDebug(StringFormat("Found %d unfilled FVGs on %s %s", ArraySize(fvg_array), symbol, EnumToString(timeframe)));
return ArraySize(fvg_array) > 0;
}
bool IsFVGFilled(string symbol, ENUM_TIMEFRAMES timeframe, int start_bar, double top, double bottom, bool is_bullish)
{
// Check if price has filled the FVG since its formation
for (int i = 0; i < start_bar; i++)
{
double high = iHigh(symbol, timeframe, i);
double low = iLow(symbol, timeframe, i);
if (is_bullish)
{
// For bullish FVG, check if price came back down to fill the gap
if (low <= bottom)
return true;
}
else
{
// For bearish FVG, check if price came back up to fill the gap
if (high >= top)
return true;
}
}
return false;
}
bool IsFVGValid(string symbol, ENUM_TIMEFRAMES timeframe, FairValueGap &fvg)
{
// Check if FVG is still unfilled
if (fvg.is_filled)
return false;
// Check current price position relative to FVG
double current_price = iClose(symbol, timeframe, 0);
if (fvg.is_bullish)
{
// For bullish FVG, price should be above the gap
return current_price > fvg.top;
}
else
{
// For bearish FVG, price should be below the gap
return current_price < fvg.bottom;
}
}
double GetFVGMidpoint(FairValueGap &fvg)
{
return (fvg.top + fvg.bottom) / 2.0;
}
bool IsPriceInFVG(double price, FairValueGap &fvg)
{
return price >= fvg.bottom && price <= fvg.top;
}
void UpdateFVGStatus(string symbol, ENUM_TIMEFRAMES timeframe, FairValueGap &fvg_array[])
{
// Update the filled status of existing FVGs
for (int i = 0; i < ArraySize(fvg_array); i++)
{
if (!fvg_array[i].is_filled)
{
double current_high = iHigh(symbol, timeframe, 0);
double current_low = iLow(symbol, timeframe, 0);
if (fvg_array[i].is_bullish)
{
if (current_low <= fvg_array[i].bottom)
{
fvg_array[i].is_filled = true;
LogPattern("Fair Value Gap", symbol, "Bullish FVG filled");
}
}
else
{
if (current_high >= fvg_array[i].top)
{
fvg_array[i].is_filled = true;
LogPattern("Fair Value Gap", symbol, "Bearish FVG filled");
}
}
}
}
}
//+------------------------------------------------------------------+
//| Liquidity Sweep Detection Functions |
//+------------------------------------------------------------------+
bool DetectLiquiditySweeps(string symbol, ENUM_TIMEFRAMES timeframe, LiquiditySweep &sweep_array[])
{
ArrayResize(sweep_array, 0);
int bars_to_analyze = MathMin(100, iBars(symbol, timeframe) - 10);
if (bars_to_analyze < 20)
return false;
LogDebug(StringFormat("Analyzing %d bars for Liquidity Sweeps on %s %s", bars_to_analyze, symbol, EnumToString(timeframe)));
double pip_value = CalculatePipValue(symbol);
double min_sweep_distance = MinSweepDistance * pip_value;
// Find equal highs and lows first
double equal_highs[];
double equal_lows[];
datetime equal_high_times[];
datetime equal_low_times[];
FindEqualHighsLows(symbol, timeframe, bars_to_analyze, equal_highs, equal_lows, equal_high_times, equal_low_times);
// Look for liquidity sweeps above equal highs
for (int i = 0; i < ArraySize(equal_highs); i++)
{
double equal_high = equal_highs[i];
datetime equal_time = equal_high_times[i];
int equal_bar = iBarShift(symbol, timeframe, equal_time);
if (equal_bar < 0)
continue;
// Look for sweep above this equal high
for (int j = 0; j < equal_bar && j < 20; j++)
{
double current_high = iHigh(symbol, timeframe, j);
double current_close = iClose(symbol, timeframe, j);
datetime current_time = iTime(symbol, timeframe, j);
// Check if price swept above equal high
if (current_high > equal_high + min_sweep_distance)
{
// Check for rejection (close back below equal high)
if (current_close < equal_high)
{
LiquiditySweep sweep;
sweep.level = equal_high;
sweep.time = current_time;
sweep.is_high_sweep = true;
sweep.confirmed = ConfirmLiquiditySweep(symbol, timeframe, j, true, equal_high);
if (sweep.confirmed)
{
ArrayResize(sweep_array, ArraySize(sweep_array) + 1);
sweep_array[ArraySize(sweep_array) - 1] = sweep;
LogPattern("Liquidity Sweep", symbol, StringFormat("High sweep at %.5f, Distance: %.1f pips", equal_high, (current_high - equal_high) / pip_value));
}
break;
}
}
}
}
// Look for liquidity sweeps below equal lows
for (int i = 0; i < ArraySize(equal_lows); i++)
{
double equal_low = equal_lows[i];
datetime equal_time = equal_low_times[i];
int equal_bar = iBarShift(symbol, timeframe, equal_time);
if (equal_bar < 0)
continue;
// Look for sweep below this equal low
for (int j = 0; j < equal_bar && j < 20; j++)
{
double current_low = iLow(symbol, timeframe, j);
double current_close = iClose(symbol, timeframe, j);
datetime current_time = iTime(symbol, timeframe, j);
// Check if price swept below equal low
if (current_low < equal_low - min_sweep_distance)
{
// Check for rejection (close back above equal low)
if (current_close > equal_low)
{
LiquiditySweep sweep;
sweep.level = equal_low;
sweep.time = current_time;
sweep.is_high_sweep = false;
sweep.confirmed = ConfirmLiquiditySweep(symbol, timeframe, j, false, equal_low);
if (sweep.confirmed)
{
ArrayResize(sweep_array, ArraySize(sweep_array) + 1);
sweep_array[ArraySize(sweep_array) - 1] = sweep;
LogPattern("Liquidity Sweep", symbol, StringFormat("Low sweep at %.5f, Distance: %.1f pips", equal_low, (equal_low - current_low) / pip_value));
}
break;
}
}
}
}
LogDebug(StringFormat("Found %d Liquidity Sweeps on %s %s", ArraySize(sweep_array), symbol, EnumToString(timeframe)));
return ArraySize(sweep_array) > 0;
}
void FindEqualHighsLows(string symbol, ENUM_TIMEFRAMES timeframe, int bars_to_analyze,
double &equal_highs[], double &equal_lows[],
datetime &equal_high_times[], datetime &equal_low_times[])
{
ArrayResize(equal_highs, 0);
ArrayResize(equal_lows, 0);
ArrayResize(equal_high_times, 0);
ArrayResize(equal_low_times, 0);
double pip_value = CalculatePipValue(symbol);
double tolerance = 2.0 * pip_value; // 2 pip tolerance for "equal" levels
// Find swing points first
double swing_highs[];
double swing_lows[];
datetime swing_high_times[];
datetime swing_low_times[];
FindSwingPoints(symbol, timeframe, bars_to_analyze, swing_highs, swing_lows, swing_high_times, swing_low_times);
// Find equal highs
for (int i = 0; i < ArraySize(swing_highs); i++)
{
double current_high = swing_highs[i];
datetime current_time = swing_high_times[i];
int equal_count = 1;
// Count how many swing highs are at similar level
for (int j = i + 1; j < ArraySize(swing_highs); j++)
{
if (MathAbs(swing_highs[j] - current_high) <= tolerance)
{
equal_count++;
}
}
// If we have at least 2 equal highs, add to array
if (equal_count >= 2)
{
// Check if this level is already in the array
bool already_exists = false;
for (int k = 0; k < ArraySize(equal_highs); k++)
{
if (MathAbs(equal_highs[k] - current_high) <= tolerance)
{
already_exists = true;
break;
}
}
if (!already_exists)
{
ArrayResize(equal_highs, ArraySize(equal_highs) + 1);
ArrayResize(equal_high_times, ArraySize(equal_high_times) + 1);
equal_highs[ArraySize(equal_highs) - 1] = current_high;
equal_high_times[ArraySize(equal_high_times) - 1] = current_time;
}
}
}
// Find equal lows
for (int i = 0; i < ArraySize(swing_lows); i++)
{
double current_low = swing_lows[i];
datetime current_time = swing_low_times[i];
int equal_count = 1;
// Count how many swing lows are at similar level
for (int j = i + 1; j < ArraySize(swing_lows); j++)
{
if (MathAbs(swing_lows[j] - current_low) <= tolerance)
{
equal_count++;
}
}
// If we have at least 2 equal lows, add to array
if (equal_count >= 2)
{
// Check if this level is already in the array
bool already_exists = false;
for (int k = 0; k < ArraySize(equal_lows); k++)
{
if (MathAbs(equal_lows[k] - current_low) <= tolerance)
{
already_exists = true;
break;
}
}
if (!already_exists)
{
ArrayResize(equal_lows, ArraySize(equal_lows) + 1);
ArrayResize(equal_low_times, ArraySize(equal_low_times) + 1);
equal_lows[ArraySize(equal_lows) - 1] = current_low;
equal_low_times[ArraySize(equal_low_times) - 1] = current_time;
}
}
}
}
bool ConfirmLiquiditySweep(string symbol, ENUM_TIMEFRAMES timeframe, int sweep_bar, bool is_high_sweep, double level)
{
// Check for strong rejection after the sweep
double sweep_high = iHigh(symbol, timeframe, sweep_bar);
double sweep_low = iLow(symbol, timeframe, sweep_bar);
double sweep_close = iClose(symbol, timeframe, sweep_bar);
if (is_high_sweep)
{
// For high sweep, look for bearish rejection
double wick_size = sweep_high - sweep_close;
double body_size = MathAbs(iClose(symbol, timeframe, sweep_bar) - iOpen(symbol, timeframe, sweep_bar));
// Wick should be at least 2x the body size
if (wick_size < body_size * 2)
return false;
// Close should be below the swept level
if (sweep_close >= level)
return false;
}
else
{
// For low sweep, look for bullish rejection
double wick_size = sweep_close - sweep_low;
double body_size = MathAbs(iClose(symbol, timeframe, sweep_bar) - iOpen(symbol, timeframe, sweep_bar));
// Wick should be at least 2x the body size
if (wick_size < body_size * 2)
return false;
// Close should be above the swept level
if (sweep_close <= level)
return false;
}
return true;
}
bool IsLiquiditySweepValid(string symbol, ENUM_TIMEFRAMES timeframe, LiquiditySweep &sweep)
{
if (!sweep.confirmed)
return false;
// Check if sweep is recent enough
datetime current_time = iTime(symbol, timeframe, 0);
int time_diff = (int)((current_time - sweep.time) / PeriodSeconds(timeframe));
if (time_diff > 10)
return false; // Must be within last 10 candles
// Check current price position
double current_price = iClose(symbol, timeframe, 0);
if (sweep.is_high_sweep)
{
// For high sweep, price should be below the swept level
return current_price < sweep.level;
}
else
{
// For low sweep, price should be above the swept level
return current_price > sweep.level;
}
}
//+------------------------------------------------------------------+
//| Multi-Timeframe Analysis Engine |
//+------------------------------------------------------------------+
struct MarketStructureData
{
OrderBlock order_blocks[];
FairValueGap fair_value_gaps[];
BreakOfStructure bos_events[];
LiquiditySweep liquidity_sweeps[];
ENUM_TIMEFRAMES timeframe;
datetime last_update;
bool is_valid;
};
// Global market structure data for different timeframes
MarketStructureData MTF_Data_M1;
MarketStructureData MTF_Data_M15;
MarketStructureData MTF_Data_H4;
MarketStructureData MTF_Data_D1;
MarketStructureData MTF_Data_W1;
bool InitializeMultiTimeframeAnalysis()
{
LogInfo("Initializing Multi-Timeframe Analysis Engine");
// Initialize timeframe data structures
MTF_Data_M1.timeframe = PERIOD_M1;
MTF_Data_M1.is_valid = false;
MTF_Data_M1.last_update = 0;
MTF_Data_M15.timeframe = PERIOD_M15;
MTF_Data_M15.is_valid = false;
MTF_Data_M15.last_update = 0;
MTF_Data_H4.timeframe = PERIOD_H4;
MTF_Data_H4.is_valid = false;
MTF_Data_H4.last_update = 0;
MTF_Data_D1.timeframe = PERIOD_D1;
MTF_Data_D1.is_valid = false;
MTF_Data_D1.last_update = 0;
MTF_Data_W1.timeframe = PERIOD_W1;
MTF_Data_W1.is_valid = false;
MTF_Data_W1.last_update = 0;
LogInfo("Multi-Timeframe Analysis Engine initialized successfully");
return true;
}
bool UpdateMultiTimeframeAnalysis(string symbol)
{
LogDebug("Updating Multi-Timeframe Analysis for " + symbol);
bool updated = false;
// Update M1 analysis (most frequent)
if (IsNewBar(symbol, PERIOD_M1) || !MTF_Data_M1.is_valid)
{
updated |= UpdateTimeframeData(symbol, MTF_Data_M1);
}
// Update M15 analysis
if (IsTimeframeUpdateNeeded(symbol, MTF_Data_M15) || !MTF_Data_M15.is_valid)
{
updated |= UpdateTimeframeData(symbol, MTF_Data_M15);
}
// Update H4 analysis
if (IsTimeframeUpdateNeeded(symbol, MTF_Data_H4) || !MTF_Data_H4.is_valid)
{
updated |= UpdateTimeframeData(symbol, MTF_Data_H4);
}
// Update D1 analysis
if (IsTimeframeUpdateNeeded(symbol, MTF_Data_D1) || !MTF_Data_D1.is_valid)
{
updated |= UpdateTimeframeData(symbol, MTF_Data_D1);
}
// Update W1 analysis (least frequent)
if (IsTimeframeUpdateNeeded(symbol, MTF_Data_W1) || !MTF_Data_W1.is_valid)
{
updated |= UpdateTimeframeData(symbol, MTF_Data_W1);
}
if (updated)
{
LogDebug("Multi-Timeframe Analysis updated for " + symbol);
}
return updated;
}
bool IsTimeframeUpdateNeeded(string symbol, MarketStructureData &mtf_data)
{
datetime current_bar_time = iTime(symbol, mtf_data.timeframe, 0);
return current_bar_time != mtf_data.last_update;
}
bool UpdateTimeframeData(string symbol, MarketStructureData &mtf_data)
{
LogDebug(StringFormat("Updating %s analysis for %s", EnumToString(mtf_data.timeframe), symbol));
bool success = true;
// Update Order Blocks
success &= DetectOrderBlocks(symbol, mtf_data.timeframe, mtf_data.order_blocks);
// Update Fair Value Gaps
success &= DetectFairValueGaps(symbol, mtf_data.timeframe, mtf_data.fair_value_gaps);
// Update Break of Structure events
success &= DetectBreakOfStructure(symbol, mtf_data.timeframe, mtf_data.bos_events);
// Update Liquidity Sweeps
success &= DetectLiquiditySweeps(symbol, mtf_data.timeframe, mtf_data.liquidity_sweeps);
// Update metadata
mtf_data.last_update = iTime(symbol, mtf_data.timeframe, 0);
mtf_data.is_valid = success;
if (success)
{
LogDebug(StringFormat("%s analysis completed: OB=%d, FVG=%d, BOS=%d, Sweeps=%d",
EnumToString(mtf_data.timeframe),
ArraySize(mtf_data.order_blocks),
ArraySize(mtf_data.fair_value_gaps),
ArraySize(mtf_data.bos_events),
ArraySize(mtf_data.liquidity_sweeps)));
}
return success;
}
string GetMarketBias(string symbol)
{
// Analyze higher timeframes for overall market bias
string h4_bias = GetTimeframeBias(symbol, MTF_Data_H4);
string d1_bias = GetTimeframeBias(symbol, MTF_Data_D1);
string w1_bias = GetTimeframeBias(symbol, MTF_Data_W1);
// Weight the biases (Weekly > Daily > H4)
if (w1_bias == d1_bias && d1_bias == h4_bias)
{
return w1_bias; // All timeframes agree
}
else if (w1_bias == d1_bias)
{
return w1_bias; // Higher timeframes agree
}
else if (d1_bias == h4_bias)
{
return d1_bias; // Lower timeframes agree
}
else
{
return w1_bias; // Default to highest timeframe
}
}
string GetTimeframeBias(string symbol, MarketStructureData &mtf_data)
{
if (!mtf_data.is_valid)
return "NEUTRAL";
int bullish_signals = 0;
int bearish_signals = 0;
// Analyze BOS events
for (int i = 0; i < ArraySize(mtf_data.bos_events); i++)
{
if (IsBOSValid(symbol, mtf_data.timeframe, mtf_data.bos_events[i]))
{
if (mtf_data.bos_events[i].is_bullish)
bullish_signals++;
else
bearish_signals++;
}
}
// Analyze Order Blocks
for (int i = 0; i < ArraySize(mtf_data.order_blocks); i++)
{
if (mtf_data.order_blocks[i].is_fresh && mtf_data.order_blocks[i].strength > OBStrengthFilter)
{
if (mtf_data.order_blocks[i].is_bullish)
bullish_signals++;
else
bearish_signals++;
}
}
// Analyze Liquidity Sweeps
for (int i = 0; i < ArraySize(mtf_data.liquidity_sweeps); i++)
{
if (IsLiquiditySweepValid(symbol, mtf_data.timeframe, mtf_data.liquidity_sweeps[i]))
{
if (mtf_data.liquidity_sweeps[i].is_high_sweep)
bearish_signals++; // High sweep typically leads to bearish move
else
bullish_signals++; // Low sweep typically leads to bullish move
}
}
// Determine bias
if (bullish_signals > bearish_signals + 1)
return "BULLISH";
else if (bearish_signals > bullish_signals + 1)
return "BEARISH";
else
return "NEUTRAL";
}
bool IsMultiTimeframeAligned(string symbol, bool is_bullish_setup)
{
if (!RequireMultiTFConfirmation)
return true;
string market_bias = GetMarketBias(symbol);
if (is_bullish_setup)
{
return market_bias == "BULLISH" || market_bias == "NEUTRAL";
}
else
{
return market_bias == "BEARISH" || market_bias == "NEUTRAL";
}
}
void CopyMarketStructureData(const MarketStructureData &source, MarketStructureData &dest)
{
// Copy arrays
ArrayResize(dest.order_blocks, ArraySize(source.order_blocks));
ArrayCopy(dest.order_blocks, source.order_blocks);
ArrayResize(dest.fair_value_gaps, ArraySize(source.fair_value_gaps));
ArrayCopy(dest.fair_value_gaps, source.fair_value_gaps);
ArrayResize(dest.bos_events, ArraySize(source.bos_events));
ArrayCopy(dest.bos_events, source.bos_events);
ArrayResize(dest.liquidity_sweeps, ArraySize(source.liquidity_sweeps));
ArrayCopy(dest.liquidity_sweeps, source.liquidity_sweeps);
// Copy simple fields
dest.timeframe = source.timeframe;
dest.last_update = source.last_update;
dest.is_valid = source.is_valid;
}
bool GetTimeframeData(ENUM_TIMEFRAMES timeframe, MarketStructureData &mtf_data)
{
switch (timeframe)
{
case PERIOD_M1:
CopyMarketStructureData(MTF_Data_M1, mtf_data);
return true;
case PERIOD_M15:
CopyMarketStructureData(MTF_Data_M15, mtf_data);
return true;
case PERIOD_H4:
CopyMarketStructureData(MTF_Data_H4, mtf_data);
return true;
case PERIOD_D1:
CopyMarketStructureData(MTF_Data_D1, mtf_data);
return true;
case PERIOD_W1:
CopyMarketStructureData(MTF_Data_W1, mtf_data);
return true;
default:
return false;
}
}
void PrintMultiTimeframeStatus(string symbol)
{
if (!EnableDebugMode)
return;
string status = StringFormat(
"=== Multi-Timeframe Status for %s ===\n" +
"Market Bias: %s\n" +
"M1 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" +
"M15 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" +
"H4 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" +
"D1 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" +
"W1 - OB:%d FVG:%d BOS:%d Sweeps:%d",
symbol,
GetMarketBias(symbol),
ArraySize(MTF_Data_M1.order_blocks), ArraySize(MTF_Data_M1.fair_value_gaps), ArraySize(MTF_Data_M1.bos_events), ArraySize(MTF_Data_M1.liquidity_sweeps),
ArraySize(MTF_Data_M15.order_blocks), ArraySize(MTF_Data_M15.fair_value_gaps), ArraySize(MTF_Data_M15.bos_events), ArraySize(MTF_Data_M15.liquidity_sweeps),
ArraySize(MTF_Data_H4.order_blocks), ArraySize(MTF_Data_H4.fair_value_gaps), ArraySize(MTF_Data_H4.bos_events), ArraySize(MTF_Data_H4.liquidity_sweeps),
ArraySize(MTF_Data_D1.order_blocks), ArraySize(MTF_Data_D1.fair_value_gaps), ArraySize(MTF_Data_D1.bos_events), ArraySize(MTF_Data_D1.liquidity_sweeps),
ArraySize(MTF_Data_W1.order_blocks), ArraySize(MTF_Data_W1.fair_value_gaps), ArraySize(MTF_Data_W1.bos_events), ArraySize(MTF_Data_W1.liquidity_sweeps));
LogDebug(status);
}