Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save benthemobileguy/e0e94ef115f1816f32a88fc0381b8c81 to your computer and use it in GitHub Desktop.
Save benthemobileguy/e0e94ef115f1816f32a88fc0381b8c81 to your computer and use it in GitHub Desktop.
Strategy
//+------------------------------------------------------------------+
//| 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