Created
June 14, 2025 19:28
-
-
Save benthemobileguy/e0e94ef115f1816f32a88fc0381b8c81 to your computer and use it in GitHub Desktop.
Strategy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//+------------------------------------------------------------------+ | |
//| Complete Price Action EA.mq5 | | |
//| Copyright 2025 | | |
//+------------------------------------------------------------------+ | |
#property copyright "Copyright 2025" | |
#property link "" | |
#property version "1.00" | |
#property strict | |
// Include required files | |
#include <Trade\Trade.mqh> | |
#include <Trade\SymbolInfo.mqh> | |
#include <Trade\PositionInfo.mqh> | |
// Class instances | |
CTrade trade; | |
CSymbolInfo symbolInfo; | |
CPositionInfo positionInfo; | |
//--- Input Parameters - Risk Management | |
input double MaxRiskPerTrade = 4.0; // Maximum risk per trade ($) | |
input double ATRMultiplierForSL = 2.33; // ATR Multiplier for Stop Loss | |
input double BreakEvenProfit = 3.0; // Move to breakeven at this profit ($) | |
//--- Input Parameters - Trailing Stop | |
input bool UseTrailingStop = true; // Use ATR Trailing Stop | |
input double TrailingStopATR = 2.0; // ATR Multiplier for Trailing Stop | |
input bool UsePartialProfit = true; // Use Partial Take Profit | |
input double PartialProfitPercent = 0.5; // Percentage of position to close for partial profit | |
input double DailyLossLimit = 12.0; // Daily loss limit ($) | |
input int MaxDailyTrades = 30; // Maximum trades per day | |
input int MinutesBetweenTrades = 2; // Minutes between trades | |
input int MagicNumber = 123456; // EA's unique magic number | |
//--- Input Parameters - Timeframes | |
input ENUM_TIMEFRAMES TimeframeEntry = PERIOD_M15; // Entry timeframe | |
input ENUM_TIMEFRAMES TimeframeMid = PERIOD_M15; // Middle timeframe (Not used in final logic, but kept for structure) | |
input ENUM_TIMEFRAMES TimeframeHigh = PERIOD_H1; // Higher timeframe | |
//--- Input Parameters - Indicators | |
input int RSIPeriod = 14; // RSI Period | |
input double RSIOverbought = 70; // RSI Overbought level | |
input double RSIOversold = 30; // RSI Oversold level | |
input int FastMA = 30; // Fast MA period | |
input int SlowMA = 60; // Slow MA period | |
input int LongMA = 180; // Long MA period | |
//--- Input Parameters - Pattern Detection | |
input bool UseChartPatterns = true; // Use chart patterns | |
input bool UseCandlePatterns = true; // Use candle patterns | |
input double MinPatternSize = 20; // Minimum pattern size in points | |
//--- Input Parameters - Alerts | |
input bool EnableEmailAlerts = true; // Enable email alerts | |
input bool EnablePushAlerts = true; // Enable push notifications | |
input bool EnableSoundAlerts = true; // Enable sound alerts | |
input string AlertSound = "alert.wav"; // Sound file for alerts | |
//--- Global Variables | |
int atrHandle; | |
int rsiHandle; | |
int fastMAHandle; | |
int slowMAHandle; | |
int longMAHandle; | |
datetime lastTradeTime = 0; | |
datetime currentDay = 0; | |
int todayTrades = 0; | |
double todayProfit = 0; | |
bool initialized = false; | |
string lastError = ""; | |
//--- Pattern Structure | |
struct PricePattern | |
{ | |
bool valid; | |
ENUM_ORDER_TYPE direction; | |
double entryPrice; | |
double stopLoss; | |
double takeProfit; | |
string patternName; | |
double lotSize; // Add lot size to the pattern structure | |
}; | |
//--- Trend Definition | |
enum TrendDirection | |
{ | |
TREND_BULLISH, | |
TREND_BEARISH, | |
TREND_SIDEWAYS | |
}; | |
//--- Market Structure | |
struct MarketStructure | |
{ | |
bool strongTrend; | |
TrendDirection trendDirection; | |
double keyLevel; | |
double nextSupport; | |
double nextResistance; | |
bool validSetup; | |
}; | |
//+------------------------------------------------------------------+ | |
//| Expert initialization function | | |
//+------------------------------------------------------------------+ | |
int OnInit() | |
{ | |
// Initialize indicators | |
rsiHandle = iRSI(_Symbol, TimeframeMid, RSIPeriod, PRICE_CLOSE); | |
fastMAHandle = iMA(_Symbol, TimeframeHigh, FastMA, 0, MODE_EMA, PRICE_CLOSE); | |
slowMAHandle = iMA(_Symbol, TimeframeHigh, SlowMA, 0, MODE_EMA, PRICE_CLOSE); | |
longMAHandle = iMA(_Symbol, TimeframeHigh, LongMA, 0, MODE_EMA, PRICE_CLOSE); | |
if(rsiHandle == INVALID_HANDLE || fastMAHandle == INVALID_HANDLE || | |
slowMAHandle == INVALID_HANDLE || longMAHandle == INVALID_HANDLE) | |
{ | |
Print("Error initializing indicators"); | |
return INIT_FAILED; | |
} | |
// Initialize trade settings | |
trade.SetDeviationInPoints(10); | |
symbolInfo.Name(_Symbol); | |
symbolInfo.RefreshRates(); | |
atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14); | |
if(atrHandle == INVALID_HANDLE) | |
{ | |
Print("Error creating ATR indicator"); | |
return INIT_FAILED; | |
} | |
// Reset daily values | |
ResetDailyValues(); | |
// Log initialization | |
LogTradeActivity("EA Initialized successfully", true); | |
initialized = true; | |
return(INIT_SUCCEEDED); | |
} | |
//+------------------------------------------------------------------+ | |
//| Expert deinitialization function | | |
//+------------------------------------------------------------------+ | |
void OnDeinit(const int reason) | |
{ | |
// Release indicators | |
IndicatorRelease(rsiHandle); | |
IndicatorRelease(fastMAHandle); | |
IndicatorRelease(slowMAHandle); | |
IndicatorRelease(longMAHandle); | |
IndicatorRelease(atrHandle); // Add this line | |
// Log deinitialization | |
LogTradeActivity("EA Deinitialized - Reason: " + IntegerToString(reason)); | |
} | |
//+------------------------------------------------------------------+ | |
//| Expert tick function | | |
//+------------------------------------------------------------------+ | |
void OnTick() | |
{ | |
// --- 'Once-Per-Bar' Execution Logic --- | |
static datetime lastBarTime = 0; | |
datetime currentBarTime = (datetime)SeriesInfoInteger(_Symbol, TimeframeEntry, SERIES_LASTBAR_DATE); | |
if(lastBarTime == currentBarTime) | |
{ | |
return; // Not a new bar, do nothing | |
} | |
lastBarTime = currentBarTime; // Update the time of the last processed bar | |
if(!initialized || !CanTrade()) return; | |
// Update symbol info | |
symbolInfo.RefreshRates(); | |
// Check for open positions | |
if(PositionSelect(_Symbol)) | |
{ | |
ManagePosition(); | |
return; | |
} | |
// Market analysis | |
MarketStructure market = AnalyzeMarketStructure(); | |
if(!market.validSetup) return; | |
// Pattern recognition | |
PricePattern pattern = FindTradeSetup(market); | |
if(!pattern.valid) return; | |
// Execute trade if valid setup found | |
ExecuteTrade(pattern, market); | |
} | |
//+------------------------------------------------------------------+ | |
//| Check if trading is allowed | | |
//+------------------------------------------------------------------+ | |
bool CanTrade() | |
{ | |
if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) | |
{ | |
lastError = "Trading not allowed"; | |
return false; | |
} | |
if(currentDay != iTime(_Symbol, PERIOD_D1, 0)) | |
ResetDailyValues(); | |
if(todayTrades >= MaxDailyTrades) | |
{ | |
lastError = "Maximum daily trades reached"; | |
return false; | |
} | |
if(todayProfit <= -DailyLossLimit) | |
{ | |
lastError = "Daily loss limit reached"; | |
return false; | |
} | |
if(TimeCurrent() - lastTradeTime < MinutesBetweenTrades * 60) | |
{ | |
lastError = "Minimum time between trades not elapsed"; | |
return false; | |
} | |
return true; | |
} | |
//+------------------------------------------------------------------+ | |
//| Reset daily values | | |
//+------------------------------------------------------------------+ | |
void ResetDailyValues() | |
{ | |
currentDay = iTime(_Symbol, PERIOD_D1, 0); | |
todayTrades = 0; // Reset trade counter for the new day | |
todayProfit = 0; // Reset profit for the new day | |
// Request trade history for the current day to calculate profit from trades closed today | |
if(HistorySelect(currentDay, TimeCurrent())) | |
{ | |
for(uint i = 0; i < HistoryDealsTotal(); i++) | |
{ | |
ulong ticket = HistoryDealGetTicket(i); | |
if(ticket > 0) | |
{ | |
// We only care about deals for this EA on this symbol that were closed today | |
if(HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol && | |
HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber && | |
HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) | |
{ | |
todayProfit += HistoryDealGetDouble(ticket, DEAL_PROFIT); | |
} | |
} | |
} | |
} | |
LogTradeActivity("Daily values reset. New day started. Initial P/L for today (from previous trades closed today): " + DoubleToString(todayProfit, 2)); | |
} | |
//+------------------------------------------------------------------+ | |
//| Market structure analysis | | |
//+------------------------------------------------------------------+ | |
MarketStructure AnalyzeMarketStructure() | |
{ | |
MarketStructure market = {false, TREND_SIDEWAYS, 0, 0, 0, false}; | |
// Get indicator values | |
double fastMA[], slowMA[], longMA[], rsi[]; | |
ArraySetAsSeries(fastMA, true); | |
ArraySetAsSeries(slowMA, true); | |
ArraySetAsSeries(longMA, true); | |
ArraySetAsSeries(rsi, true); | |
if(CopyBuffer(fastMAHandle, 0, 0, 3, fastMA) <= 0 || | |
CopyBuffer(slowMAHandle, 0, 0, 3, slowMA) <= 0 || | |
CopyBuffer(longMAHandle, 0, 0, 3, longMA) <= 0 || | |
CopyBuffer(rsiHandle, 0, 0, 3, rsi) <= 0) | |
{ | |
LogTradeActivity("Error copying indicator buffers"); | |
return market; | |
} | |
// Determine trend direction | |
bool bullishTrend = fastMA[0] > slowMA[0] && slowMA[0] > longMA[0]; | |
bool bearishTrend = fastMA[0] < slowMA[0] && slowMA[0] < longMA[0]; | |
// Set market structure | |
market.strongTrend = bullishTrend || bearishTrend; | |
if(bullishTrend) | |
market.trendDirection = TREND_BULLISH; | |
else if(bearishTrend) | |
market.trendDirection = TREND_BEARISH; | |
else | |
market.trendDirection = TREND_SIDEWAYS; | |
// Find key levels | |
double levels[]; | |
if(!FindKeyLevels(levels)) return market; | |
market.nextSupport = FindNearestLevel(levels, false); | |
market.nextResistance = FindNearestLevel(levels, true); | |
market.validSetup = true; | |
return market; | |
} | |
//+------------------------------------------------------------------+ | |
//| Find trade setup based on patterns | | |
//+------------------------------------------------------------------+ | |
PricePattern FindTradeSetup(MarketStructure& market) | |
{ | |
PricePattern pattern = {false, ORDER_TYPE_BUY, 0, 0, 0, ""}; | |
MqlRates rates[]; | |
ArraySetAsSeries(rates, true); | |
if(CopyRates(_Symbol, TimeframeEntry, 0, 50, rates) <= 0) | |
return pattern; | |
// Check for chart patterns | |
if(UseChartPatterns) | |
{ | |
// Head and Shoulders | |
if(IsHeadAndShoulders(rates)) | |
{ | |
pattern = CreatePattern(rates, "H&S", ORDER_TYPE_SELL); | |
if(pattern.valid) return pattern; | |
} | |
// Inverse Head and Shoulders | |
if(IsInverseHeadAndShoulders(rates)) | |
{ | |
pattern = CreatePattern(rates, "IH&S", ORDER_TYPE_BUY); | |
if(pattern.valid) return pattern; | |
} | |
// Bull Flag | |
if(IsBullFlag(rates)) | |
{ | |
pattern = CreatePattern(rates, "Bull Flag", ORDER_TYPE_BUY); | |
if(pattern.valid) return pattern; | |
} | |
// Bear Flag | |
if(IsBearFlag(rates)) | |
{ | |
pattern = CreatePattern(rates, "Bear Flag", ORDER_TYPE_SELL); | |
if(pattern.valid) return pattern; | |
} | |
} | |
// Check for candlestick patterns | |
if(UseCandlePatterns) | |
{ | |
// Engulfing patterns | |
if(IsBullishEngulfing(rates)) | |
{ | |
pattern = CreatePattern(rates, "Bullish Engulfing", ORDER_TYPE_BUY); | |
if(pattern.valid) return pattern; | |
} | |
if(IsBearishEngulfing(rates)) | |
{ | |
pattern = CreatePattern(rates, "Bearish Engulfing", ORDER_TYPE_SELL); | |
if(pattern.valid) return pattern; | |
} | |
// Pin bars | |
if(IsBullishPinBar(rates)) | |
{ | |
pattern = CreatePattern(rates, "Bullish Pin Bar", ORDER_TYPE_BUY); | |
if(pattern.valid) return pattern; | |
} | |
if(IsBearishPinBar(rates)) | |
{ | |
pattern = CreatePattern(rates, "Bearish Pin Bar", ORDER_TYPE_SELL); | |
if(pattern.valid) return pattern; | |
} | |
} | |
return pattern; | |
} | |
//+------------------------------------------------------------------+ | |
//| Create pattern with proper risk management | | |
//+------------------------------------------------------------------+ | |
PricePattern CreatePattern(const MqlRates& rates[], string name, ENUM_ORDER_TYPE direction) | |
{ | |
PricePattern pattern = {false, direction, 0, 0, 0, name, 0}; | |
// Set entry price | |
pattern.entryPrice = direction == ORDER_TYPE_BUY ? rates[0].high + symbolInfo.Point() : | |
rates[0].low - symbolInfo.Point(); | |
// --- DYNAMIC RISK CALCULATION --- | |
double atr = GetATRValue(1); | |
if(atr == 0) return pattern; | |
// 1. Set Stop Loss based on ATR | |
double slDistance = atr * ATRMultiplierForSL; | |
pattern.stopLoss = direction == ORDER_TYPE_BUY ? | |
pattern.entryPrice - slDistance : | |
pattern.entryPrice + slDistance; | |
// 2. Calculate Lot Size based on Stop Loss distance and Max Risk | |
double slPoints = slDistance / symbolInfo.Point(); | |
double tickValue = symbolInfo.TickValue(); | |
double tickSize = symbolInfo.TickSize(); | |
double valuePerLot = symbolInfo.ContractSize() * tickValue / tickSize; | |
pattern.lotSize = (MaxRiskPerTrade / (slPoints * valuePerLot)) * (tickValue / tickSize); | |
// Normalize and check against min/max lot sizes | |
pattern.lotSize = NormalizeDouble(pattern.lotSize, 2); | |
if(pattern.lotSize < symbolInfo.LotsMin()) pattern.lotSize = symbolInfo.LotsMin(); | |
if(pattern.lotSize > symbolInfo.LotsMax()) pattern.lotSize = symbolInfo.LotsMax(); | |
// 3. Set Take Profit based on a 1:3 Risk:Reward ratio | |
pattern.takeProfit = direction == ORDER_TYPE_BUY ? | |
pattern.entryPrice + (slDistance * 3) : | |
pattern.entryPrice - (slDistance * 3); | |
// Validate pattern | |
if(pattern.takeProfit != 0 && | |
MathAbs(pattern.entryPrice - pattern.stopLoss) >= MinPatternSize * symbolInfo.Point()) | |
{ | |
pattern.valid = true; | |
} | |
// Log pattern details | |
LogTradeActivity("Pattern created: " + name + | |
"\nLot Size: " + DoubleToString(pattern.lotSize, 2) + | |
"\nEntry: " + DoubleToString(pattern.entryPrice, _Digits) + | |
"\nSL: " + DoubleToString(pattern.stopLoss, _Digits) + | |
"\nTP: " + DoubleToString(pattern.takeProfit, _Digits) + | |
"\nRisk:Reward: 1:3"); | |
return pattern; | |
} | |
//+------------------------------------------------------------------+ | |
//| Execute trade based on pattern | | |
//+------------------------------------------------------------------+ | |
void ExecuteTrade(const PricePattern& pattern, const MarketStructure& market) | |
{ | |
if(!pattern.valid) return; | |
// --- CONFLUENCE FILTER --- | |
// Check if the trade direction aligns with the higher timeframe trend | |
if(pattern.direction == ORDER_TYPE_BUY && market.trendDirection != TREND_BULLISH) | |
{ | |
LogTradeActivity("Trade skipped (BUY): No confluence with higher timeframe trend (Trend is " + EnumToString(market.trendDirection) + ")."); | |
return; | |
} | |
if(pattern.direction == ORDER_TYPE_SELL && market.trendDirection != TREND_BEARISH) | |
{ | |
LogTradeActivity("Trade skipped (SELL): No confluence with higher timeframe trend (Trend is " + EnumToString(market.trendDirection) + ")."); | |
return; | |
} | |
// Get confluence analysis before trade execution | |
string confluenceAnalysis = BuildConfluenceLog(pattern); | |
// Prepare trade request | |
MqlTradeRequest request = {}; | |
request.action = TRADE_ACTION_DEAL; | |
request.symbol = _Symbol; | |
request.volume = pattern.lotSize; // Use dynamically calculated lot size | |
request.type = pattern.direction; | |
request.price = pattern.entryPrice; | |
request.sl = pattern.stopLoss; | |
request.tp = pattern.takeProfit; | |
request.deviation = 5; | |
request.magic = MagicNumber; | |
request.comment = pattern.patternName; | |
// Execute trade | |
MqlTradeResult result = {}; | |
bool success = OrderSend(request, result); | |
if(success) | |
{ | |
lastTradeTime = TimeCurrent(); | |
todayTrades++; | |
// Create detailed trade entry log | |
string tradeLog = "\n=== NEW TRADE EXECUTION ===\n"; | |
tradeLog += "Pattern: " + pattern.patternName + "\n"; | |
tradeLog += "Direction: " + (pattern.direction == ORDER_TYPE_BUY ? "BUY" : "SELL") + "\n"; | |
tradeLog += "Entry Price: " + DoubleToString(pattern.entryPrice, _Digits) + "\n"; | |
tradeLog += "Stop Loss: " + DoubleToString(pattern.stopLoss, _Digits) + "\n"; | |
tradeLog += "Take Profit: " + DoubleToString(pattern.takeProfit, _Digits) + "\n"; | |
tradeLog += "Risk/Reward: " + DoubleToString(MathAbs(pattern.takeProfit - pattern.entryPrice) / | |
MathAbs(pattern.stopLoss - pattern.entryPrice), 2) + "\n"; | |
tradeLog += "\nTRADE CONFLUENCE ANALYSIS:\n" + confluenceAnalysis; | |
LogTradeActivity(tradeLog, true); | |
} | |
else | |
{ | |
LogTradeActivity("Trade execution failed: " + IntegerToString(result.retcode) + | |
"\nAttempted Pattern: " + pattern.patternName, true); | |
} | |
} | |
string BuildConfluenceLog(const PricePattern& pattern) | |
{ | |
string confluence = ""; | |
// Get indicator values | |
double rsi[], fastMA[], slowMA[], longMA[]; | |
ArraySetAsSeries(rsi, true); | |
ArraySetAsSeries(fastMA, true); | |
ArraySetAsSeries(slowMA, true); | |
ArraySetAsSeries(longMA, true); | |
CopyBuffer(rsiHandle, 0, 0, 3, rsi); | |
CopyBuffer(fastMAHandle, 0, 0, 3, fastMA); | |
CopyBuffer(slowMAHandle, 0, 0, 3, slowMA); | |
CopyBuffer(longMAHandle, 0, 0, 3, longMA); | |
// 1. Trend Analysis | |
confluence += "1. Trend Analysis:\n"; | |
bool bullishTrend = fastMA[0] > slowMA[0] && slowMA[0] > longMA[0]; | |
bool bearishTrend = fastMA[0] < slowMA[0] && slowMA[0] < longMA[0]; | |
confluence += " - Higher Timeframe Trend: " + | |
(bullishTrend ? "Bullish" : (bearishTrend ? "Bearish" : "Sideways")) + "\n"; | |
confluence += " - Fast MA Value: " + DoubleToString(fastMA[0], _Digits) + "\n"; | |
confluence += " - Slow MA Value: " + DoubleToString(slowMA[0], _Digits) + "\n"; | |
confluence += " - Long MA Value: " + DoubleToString(longMA[0], _Digits) + "\n"; | |
// 2. Momentum | |
confluence += "\n2. Momentum:\n"; | |
confluence += " - RSI Value: " + DoubleToString(rsi[0], 2) + "\n"; | |
confluence += " - RSI Condition: " + | |
(rsi[0] > RSIOverbought ? "Overbought" : | |
(rsi[0] < RSIOversold ? "Oversold" : "Neutral")) + "\n"; | |
// 3. Pattern Details | |
confluence += "\n3. Pattern Details:\n"; | |
confluence += " - Pattern Type: " + pattern.patternName + "\n"; | |
// Get recent price action | |
MqlRates rates[]; | |
ArraySetAsSeries(rates, true); | |
CopyRates(_Symbol, TimeframeEntry, 0, 5, rates); | |
// 4. Support/Resistance | |
confluence += "\n4. Support/Resistance Analysis:\n"; | |
double levels[]; | |
if(FindKeyLevels(levels)) | |
{ | |
double nearestSupport = FindNearestLevel(levels, false); | |
double nearestResistance = FindNearestLevel(levels, true); | |
confluence += " - Nearest Support: " + DoubleToString(nearestSupport, _Digits) + "\n"; | |
confluence += " - Nearest Resistance: " + DoubleToString(nearestResistance, _Digits) + "\n"; | |
confluence += " - Distance to Support: " + | |
DoubleToString(MathAbs(rates[0].close - nearestSupport) / _Point, 0) + " points\n"; | |
confluence += " - Distance to Resistance: " + | |
DoubleToString(MathAbs(rates[0].close - nearestResistance) / _Point, 0) + " points\n"; | |
} | |
// 5. Volatility | |
confluence += "\n5. Market Volatility:\n"; | |
double atr = GetATRValue(1); | |
confluence += " - ATR Value: " + DoubleToString(atr, _Digits) + "\n"; | |
confluence += " - Volatility Level: " + | |
(atr > GetATRValue(20) * 1.5 ? "High" : | |
(atr < GetATRValue(20) * 0.5 ? "Low" : "Normal")) + "\n"; | |
// 6. Market Session Analysis | |
confluence += "\n6. Market Session Analysis:\n"; | |
datetime currentTime = TimeCurrent(); | |
MqlDateTime dt; | |
TimeToStruct(currentTime, dt); | |
// Calculate session based on GMT | |
int gmtHour = dt.hour; | |
string currentSession; | |
if(gmtHour >= 22 || gmtHour < 7) currentSession = "Asian Session"; | |
else if(gmtHour >= 7 && gmtHour < 16) currentSession = "London Session"; | |
else currentSession = "New York Session"; | |
confluence += " - Current Session: " + currentSession + "\n"; | |
confluence += " - Time: " + TimeToString(currentTime) + "\n"; | |
confluence += " - Day of Week: " + EnumToString((ENUM_DAY_OF_WEEK)dt.day_of_week) + "\n"; | |
// 7. Entry Quality Metrics:\n"; | |
confluence += "\n7. Entry Quality Metrics:\n"; | |
double riskReward = MathAbs(pattern.takeProfit - pattern.entryPrice) / | |
MathAbs(pattern.stopLoss - pattern.entryPrice); | |
confluence += " - Risk/Reward Ratio: " + DoubleToString(riskReward, 2) + "\n"; | |
confluence += " - Stop Loss Distance: " + | |
DoubleToString(MathAbs(pattern.entryPrice - pattern.stopLoss) / _Point, 0) + " points\n"; | |
confluence += " - Take Profit Distance: " + | |
DoubleToString(MathAbs(pattern.takeProfit - pattern.entryPrice) / _Point, 0) + " points\n"; | |
return confluence; | |
} | |
//+------------------------------------------------------------------+ | |
//| Pattern Recognition Functions | | |
//+------------------------------------------------------------------+ | |
//--- Head and Shoulders Pattern | |
bool IsHeadAndShoulders(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 20) return false; | |
double leftShoulder = 0, head = 0, rightShoulder = 0; | |
int leftShoulderIdx = 0, headIdx = 0, rightShoulderIdx = 0; | |
// Find head (highest point) | |
for(int i = 5; i < 15; i++) | |
{ | |
if(rates[i].high > head) | |
{ | |
head = rates[i].high; | |
headIdx = i; | |
} | |
} | |
// Find left shoulder | |
for(int i = 0; i < headIdx - 2; i++) | |
{ | |
if(rates[i].high > leftShoulder && rates[i].high < head) | |
{ | |
leftShoulder = rates[i].high; | |
leftShoulderIdx = i; | |
} | |
} | |
// Find right shoulder | |
for(int i = headIdx + 2; i < ArraySize(rates); i++) | |
{ | |
if(rates[i].high > rightShoulder && rates[i].high < head) | |
{ | |
rightShoulder = rates[i].high; | |
rightShoulderIdx = i; | |
} | |
} | |
// Validate pattern | |
if(leftShoulder > 0 && head > 0 && rightShoulder > 0) | |
{ | |
// Check if shoulders are at similar levels | |
if(MathAbs(leftShoulder - rightShoulder) / leftShoulder < 0.1) | |
{ | |
// Check if head is significantly higher than shoulders | |
if(head > leftShoulder * 1.1 && head > rightShoulder * 1.1) | |
return true; | |
} | |
} | |
return false; | |
} | |
//--- Inverse Head and Shoulders Pattern | |
bool IsInverseHeadAndShoulders(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 20) return false; | |
double leftShoulder = DBL_MAX, head = DBL_MAX, rightShoulder = DBL_MAX; | |
int leftShoulderIdx = 0, headIdx = 0, rightShoulderIdx = 0; | |
// Find head (lowest point) | |
for(int i = 5; i < 15; i++) | |
{ | |
if(rates[i].low < head) | |
{ | |
head = rates[i].low; | |
headIdx = i; | |
} | |
} | |
// Find left shoulder | |
for(int i = 0; i < headIdx - 2; i++) | |
{ | |
if(rates[i].low < leftShoulder && rates[i].low > head) | |
{ | |
leftShoulder = rates[i].low; | |
leftShoulderIdx = i; | |
} | |
} | |
// Find right shoulder | |
for(int i = headIdx + 2; i < ArraySize(rates); i++) | |
{ | |
if(rates[i].low < rightShoulder && rates[i].low > head) | |
{ | |
rightShoulder = rates[i].low; | |
rightShoulderIdx = i; | |
} | |
} | |
// Validate pattern | |
if(leftShoulder < DBL_MAX && head < DBL_MAX && rightShoulder < DBL_MAX) | |
{ | |
// Check if shoulders are at similar levels | |
if(MathAbs(leftShoulder - rightShoulder) / leftShoulder < 0.1) | |
{ | |
// Check if head is significantly lower than shoulders | |
if(head < leftShoulder * 0.9 && head < rightShoulder * 0.9) | |
return true; | |
} | |
} | |
return false; | |
} | |
//--- Bull Flag Pattern | |
bool IsBullFlag(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 15) return false; | |
// 1. Look for strong upward move (the "pole") | |
int poleEndIdx = -1; | |
double poleHigh = 0; | |
double atr = GetATRValue(14); | |
if(atr == 0) return false; | |
for(int i = 10; i >= 5; i--) // Search for the pole in recent history | |
{ | |
if(rates[i].high - rates[i].low > 2 * atr && rates[i].close > rates[i].open) | |
{ | |
poleEndIdx = i; | |
poleHigh = rates[i].high; | |
break; | |
} | |
} | |
if(poleEndIdx == -1) return false; // No pole found | |
// 2. Look for consolidation (the "flag") after the pole | |
// The flag should be a series of lower highs and lower lows, forming a downward channel | |
bool flagFound = true; | |
for(int i = 1; i < poleEndIdx; i++) | |
{ | |
// Highs should be lower than the pole's high and trending down | |
if(rates[i].high >= poleHigh || rates[i].high > rates[i+1].high) | |
{ | |
flagFound = false; | |
break; | |
} | |
// Lows should also be trending down | |
if(rates[i].low > rates[i+1].low) | |
{ | |
flagFound = false; | |
break; | |
} | |
} | |
// 3. Check for a breakout above the flag's high | |
if(flagFound && rates[0].close > rates[1].high) | |
{ | |
LogTradeActivity("Bull Flag pattern identified."); | |
return true; | |
} | |
return false; | |
} | |
//--- Bear Flag Pattern | |
bool IsBearFlag(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 15) return false; | |
// 1. Look for strong downward move (the "pole") | |
int poleEndIdx = -1; | |
double poleLow = 0; | |
double atr = GetATRValue(14); | |
if(atr == 0) return false; | |
for(int i = 10; i >= 5; i--) // Search for the pole in recent history | |
{ | |
if(rates[i].high - rates[i].low > 2 * atr && rates[i].close < rates[i].open) | |
{ | |
poleEndIdx = i; | |
poleLow = rates[i].low; | |
break; | |
} | |
} | |
if(poleEndIdx == -1) return false; // No pole found | |
// 2. Look for consolidation (the "flag") after the pole | |
// The flag should be a series of higher highs and higher lows, forming an upward channel | |
bool flagFound = true; | |
for(int i = 1; i < poleEndIdx; i++) | |
{ | |
// Lows should be higher than the pole's low and trending up | |
if(rates[i].low <= poleLow || rates[i].low < rates[i+1].low) | |
{ | |
flagFound = false; | |
break; | |
} | |
// Highs should also be trending up | |
if(rates[i].high < rates[i+1].high) | |
{ | |
flagFound = false; | |
break; | |
} | |
} | |
// 3. Check for a breakout below the flag's low | |
if(flagFound && rates[0].close < rates[1].low) | |
{ | |
LogTradeActivity("Bear Flag pattern identified."); | |
return true; | |
} | |
return false; | |
} | |
//+------------------------------------------------------------------+ | |
//| Candlestick Pattern Functions | | |
//+------------------------------------------------------------------+ | |
//--- Bullish Engulfing Pattern | |
bool IsBullishEngulfing(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 3) return false; | |
double atr = GetATRValue(1); | |
if(atr == 0) return false; | |
return (rates[1].close > rates[1].open && // Current candle bullish | |
rates[2].close < rates[2].open && // Previous candle bearish | |
rates[1].close > rates[2].open && // Current close above prev open | |
rates[1].open < rates[2].close && // Current open below prev close | |
rates[1].high - rates[1].low > atr); // Significant size | |
} | |
//--- Bearish Engulfing Pattern | |
bool IsBearishEngulfing(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 3) return false; | |
double atr = GetATRValue(1); | |
if(atr == 0) return false; | |
return (rates[1].close < rates[1].open && // Current candle bearish | |
rates[2].close > rates[2].open && // Previous candle bullish | |
rates[1].close < rates[2].open && // Current close below prev open | |
rates[1].open > rates[2].close && // Current open above prev close | |
rates[1].high - rates[1].low > atr); // Significant size | |
} | |
//--- Bullish Pin Bar | |
bool IsBullishPinBar(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 2) return false; | |
double atr = GetATRValue(1); | |
if(atr == 0) return false; | |
double bodySize = MathAbs(rates[1].close - rates[1].open); | |
double upperWick = rates[1].high - MathMax(rates[1].close, rates[1].open); | |
double lowerWick = MathMin(rates[1].close, rates[1].open) - rates[1].low; | |
return (lowerWick > bodySize * 2 && // Long lower wick | |
lowerWick > upperWick * 2 && // Lower wick > upper wick | |
bodySize < atr * 0.5); // Small body | |
} | |
//--- Bearish Pin Bar | |
bool IsBearishPinBar(const MqlRates& rates[]) | |
{ | |
if(ArraySize(rates) < 2) return false; | |
double atr = GetATRValue(1); | |
if(atr == 0) return false; | |
double bodySize = MathAbs(rates[1].close - rates[1].open); | |
double upperWick = rates[1].high - MathMax(rates[1].close, rates[1].open); | |
double lowerWick = MathMin(rates[1].close, rates[1].open) - rates[1].low; | |
return (upperWick > bodySize * 2 && // Long upper wick | |
upperWick > lowerWick * 2 && // Upper wick > lower wick | |
bodySize < atr * 0.5); // Small body | |
} | |
//+------------------------------------------------------------------+ | |
//| Position Management Functions | | |
//+------------------------------------------------------------------+ | |
void ManagePosition() | |
{ | |
if(!PositionSelect(_Symbol)) return; | |
double positionProfit = PositionGetDouble(POSITION_PROFIT); | |
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); | |
double initialSL = PositionGetDouble(POSITION_SL); | |
double currentSL = initialSL; | |
double currentTP = PositionGetDouble(POSITION_TP); | |
double positionVolume = PositionGetDouble(POSITION_VOLUME); | |
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); | |
// --- Partial Take Profit Logic --- | |
if(UsePartialProfit) | |
{ | |
double riskDistance = MathAbs(openPrice - initialSL); | |
if(posType == POSITION_TYPE_BUY && symbolInfo.Bid() >= openPrice + riskDistance) | |
{ | |
double closeVolume = NormalizeDouble(positionVolume * PartialProfitPercent, 2); | |
if(closeVolume >= symbolInfo.LotsMin()) | |
{ | |
trade.PositionClose(_Symbol, closeVolume); | |
LogTradeActivity("Partial profit taken. Closed " + DoubleToString(closeVolume, 2) + " lots.", true); | |
// After closing partial, move SL to BE. We must re-select the position to get updated SL. | |
if(PositionSelect(_Symbol)) | |
{ | |
trade.PositionModify(_Symbol, openPrice, currentTP); | |
} | |
return; // Exit manage position for this tick | |
} | |
} | |
else if(posType == POSITION_TYPE_SELL && symbolInfo.Ask() <= openPrice - riskDistance) | |
{ | |
double closeVolume = NormalizeDouble(positionVolume * PartialProfitPercent, 2); | |
if(closeVolume >= symbolInfo.LotsMin()) | |
{ | |
trade.PositionClose(_Symbol, closeVolume); | |
LogTradeActivity("Partial profit taken. Closed " + DoubleToString(closeVolume, 2) + " lots.", true); | |
if(PositionSelect(_Symbol)) | |
{ | |
trade.PositionModify(_Symbol, openPrice, currentTP); | |
} | |
return; // Exit manage position for this tick | |
} | |
} | |
} | |
// --- ATR Trailing Stop Logic --- | |
if(UseTrailingStop && positionProfit > 0) | |
{ | |
double atr = GetATRValue(1); | |
if(atr > 0) | |
{ | |
double newSL = 0; | |
if(posType == POSITION_TYPE_BUY) | |
{ | |
newSL = symbolInfo.Bid() - (atr * TrailingStopATR); | |
if(newSL > openPrice && newSL > currentSL) | |
{ | |
if(trade.PositionModify(_Symbol, newSL, currentTP)) | |
LogTradeActivity("Trailing Stop updated for BUY position to " + DoubleToString(newSL, _Digits)); | |
} | |
} | |
else // POSITION_TYPE_SELL | |
{ | |
newSL = symbolInfo.Ask() + (atr * TrailingStopATR); | |
if(newSL < openPrice && newSL < currentSL) | |
{ | |
if(trade.PositionModify(_Symbol, newSL, currentTP)) | |
LogTradeActivity("Trailing Stop updated for SELL position to " + DoubleToString(newSL, _Digits)); | |
} | |
} | |
} | |
} | |
// --- Breakeven Logic (only if other logic hasn't triggered) --- | |
else if(positionProfit >= BreakEvenProfit && currentSL != openPrice) | |
{ | |
double newSL = openPrice; | |
if(posType == POSITION_TYPE_BUY) newSL += symbolInfo.Point() * 2; | |
else newSL -= symbolInfo.Point() * 2; | |
if(trade.PositionModify(_Symbol, newSL, currentTP)) | |
{ | |
LogTradeActivity("Position moved to breakeven", true); | |
} | |
} | |
// Check for exit signals | |
if(IsExitSignal()) | |
{ | |
if(trade.PositionClose(_Symbol)) | |
{ | |
LogTradeActivity("Position closed on exit signal", true); | |
} | |
} | |
} | |
//+------------------------------------------------------------------+ | |
//| Support Functions | | |
//+------------------------------------------------------------------+ | |
//+------------------------------------------------------------------+ | |
//| Logging Function | | |
//+------------------------------------------------------------------+ | |
void LogTradeActivity(string message, bool isAlert = false) | |
{ | |
string logMessage = TimeToString(TimeCurrent()) + " | " + _Symbol + " | " + message; | |
// Write to log file | |
int handle = FileOpen("PriceActionEA_Log.txt", FILE_WRITE|FILE_READ|FILE_TXT); | |
if(handle != INVALID_HANDLE) | |
{ | |
FileSeek(handle, 0, SEEK_END); | |
FileWriteString(handle, logMessage + "\n"); | |
FileClose(handle); | |
} | |
// Print to terminal | |
Print(logMessage); | |
// Send alerts if enabled and it's an alert message | |
if(isAlert) | |
{ | |
if(EnableEmailAlerts) SendMail("Price Action EA Alert", logMessage); | |
if(EnablePushAlerts) SendNotification(logMessage); | |
if(EnableSoundAlerts) PlaySound(AlertSound); | |
} | |
} | |
double FindNearestLevel(const double& levels[], bool above) | |
{ | |
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); | |
double nearestLevel = above ? DBL_MAX : 0; | |
for(int i = 0; i < ArraySize(levels); i++) | |
{ | |
if(above && levels[i] > currentPrice) | |
{ | |
if(levels[i] < nearestLevel) | |
nearestLevel = levels[i]; | |
} | |
else if(!above && levels[i] < currentPrice) | |
{ | |
if(levels[i] > nearestLevel) | |
nearestLevel = levels[i]; | |
} | |
} | |
return nearestLevel == DBL_MAX ? 0 : nearestLevel; | |
} | |
//+------------------------------------------------------------------+ | |
//| Custom functions to check additional conditions | | |
//+------------------------------------------------------------------+ | |
bool IsExitSignal() | |
{ | |
if(!PositionSelect(_Symbol)) return false; | |
// Get current position type | |
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); | |
// Get indicator values | |
double rsi[]; | |
ArraySetAsSeries(rsi, true); | |
if(CopyBuffer(rsiHandle, 0, 0, 3, rsi) <= 0) | |
return false; | |
// Exit long positions | |
if(posType == POSITION_TYPE_BUY) | |
{ | |
// Exit if RSI moves into overbought territory | |
if(rsi[0] > RSIOverbought) return true; | |
// Check for bearish reversal patterns | |
MqlRates rates[]; | |
ArraySetAsSeries(rates, true); | |
if(CopyRates(_Symbol, TimeframeEntry, 0, 3, rates) <= 0) | |
return false; | |
if(IsBearishEngulfing(rates) || IsBearishPinBar(rates)) | |
return true; | |
} | |
// Exit short positions | |
else if(posType == POSITION_TYPE_SELL) | |
{ | |
// Exit if RSI moves into oversold territory | |
if(rsi[0] < RSIOversold) return true; | |
// Check for bullish reversal patterns | |
MqlRates rates[]; | |
ArraySetAsSeries(rates, true); | |
if(CopyRates(_Symbol, TimeframeEntry, 0, 3, rates) <= 0) | |
return false; | |
if(IsBullishEngulfing(rates) || IsBullishPinBar(rates)) | |
return true; | |
} | |
return false; | |
} | |
bool FindKeyLevels(double& levels[]) | |
{ | |
MqlRates rates[]; | |
ArraySetAsSeries(rates, true); | |
if(CopyRates(_Symbol, TimeframeHigh, 0, 100, rates) <= 0) | |
return false; | |
ArrayResize(levels, 0); | |
int levelCount = 0; | |
// Find key levels based on swing highs and lows | |
for(int i = 2; i < ArraySize(rates) - 2; i++) | |
{ | |
// Swing high | |
if(rates[i].high > rates[i-1].high && rates[i].high > rates[i-2].high && | |
rates[i].high > rates[i+1].high && rates[i].high > rates[i+2].high) | |
{ | |
ArrayResize(levels, levelCount + 1); | |
levels[levelCount++] = rates[i].high; | |
} | |
// Swing low | |
if(rates[i].low < rates[i-1].low && rates[i].low < rates[i-2].low && | |
rates[i].low < rates[i+1].low && rates[i].low < rates[i+2].low) | |
{ | |
ArrayResize(levels, levelCount + 1); | |
levels[levelCount++] = rates[i].low; | |
} | |
} | |
return levelCount > 0; | |
} | |
double GetATRValue(int shift) | |
{ | |
double atrBuffer[]; | |
ArraySetAsSeries(atrBuffer, true); | |
if(CopyBuffer(atrHandle, 0, shift, 1, atrBuffer) <= 0) | |
return 0; | |
return atrBuffer[0]; | |
} | |
//+------------------------------------------------------------------+ | |
//| Trade Transaction event handler | | |
//+------------------------------------------------------------------+ | |
void OnTradeTransaction(const MqlTradeTransaction& trans, | |
const MqlTradeRequest& request, | |
const MqlTradeResult& result) | |
{ | |
// We are interested in completed deals that add to history | |
if(trans.type != TRADE_TRANSACTION_DEAL_ADD) | |
return; | |
// Get the ticket of the deal from the transaction | |
ulong deal_ticket = trans.deal; | |
if(deal_ticket == 0) | |
return; | |
// Select the deal in the history to access its properties | |
if(!HistoryDealSelect(deal_ticket)) | |
return; | |
// Check if the deal is for our symbol and magic number | |
if(HistoryDealGetString(deal_ticket, DEAL_SYMBOL) == _Symbol && | |
HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == MagicNumber) | |
{ | |
// A deal is either an entry (IN) or an exit (OUT). We only update profit on exits. | |
if(HistoryDealGetInteger(deal_ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) | |
{ | |
double profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); | |
todayProfit += profit; // Add the profit/loss of the closed trade | |
LogTradeActivity("Deal closed. Profit: " + DoubleToString(profit, 2) + | |
". Today's total P/L: " + DoubleToString(todayProfit, 2), true); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment