mirror of
https://github.com/rithsila/MT5-EA-Sniper-Strategy.git
synced 2026-08-21 14:48:28 +00:00
✅ 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)
2889 lines
92 KiB
Plaintext
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);
|
|
}
|