From 2682cfdca100eb2dc704017b7c60d96b78a847d1 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 27 May 2026 12:32:26 -0400 Subject: [PATCH] Initial MQL5 EA Project --- Include/Config.mqh | 51 ++++++++ Include/Logger.mqh | 74 +++++++++++ Include/MarketData.mqh | 186 +++++++++++++++++++++++++++ Include/PositionManager.mqh | 146 ++++++++++++++++++++++ Include/RiskManager.mqh | 158 +++++++++++++++++++++++ Include/Signal.mqh | 16 +++ Include/Strategy.mqh | 151 ++++++++++++++++++++++ Include/TradeManager.mqh | 187 ++++++++++++++++++++++++++++ Include/TrailingStop.mqh | 183 +++++++++++++++++++++++++++ Include/Utilities.mqh | 81 ++++++++++++ MyProEA.ex5 | Bin 0 -> 53130 bytes MyProEA.mq5 | 242 ++++++++++++++++++++++++++++++++++++ 12 files changed, 1475 insertions(+) create mode 100644 Include/Config.mqh create mode 100644 Include/Logger.mqh create mode 100644 Include/MarketData.mqh create mode 100644 Include/PositionManager.mqh create mode 100644 Include/RiskManager.mqh create mode 100644 Include/Signal.mqh create mode 100644 Include/Strategy.mqh create mode 100644 Include/TradeManager.mqh create mode 100644 Include/TrailingStop.mqh create mode 100644 Include/Utilities.mqh create mode 100644 MyProEA.ex5 create mode 100644 MyProEA.mq5 diff --git a/Include/Config.mqh b/Include/Config.mqh new file mode 100644 index 0000000..e978712 --- /dev/null +++ b/Include/Config.mqh @@ -0,0 +1,51 @@ +//+------------------------------------------------------------------+ +//| Config.mqh - Global configuration and input parameters | +//| Central place for all EA settings | +//+------------------------------------------------------------------+ + +#ifndef __CONFIG_MQH__ +#define __CONFIG_MQH__ + +// ==================== LOT SIZING ==================== +enum E_LOT_MODE +{ + LOT_MODE_FIXED = 0, // Use fixed lot size + LOT_MODE_RISK = 1 // Use risk percent method +}; + +input E_LOT_MODE g_lot_mode = LOT_MODE_FIXED; // Lot Sizing Mode +input double g_fixed_lot = 0.1; // Fixed Lot Size (if LOT_MODE_FIXED) +input double g_risk_percent = 2.0; // Risk Percent (if LOT_MODE_RISK) +input double g_max_lot = 10.0; // Maximum Lot Size +input double g_min_lot = 0.01; // Minimum Lot Size + +// ==================== TRADE MANAGEMENT ==================== +input int g_magic_number = 12345; // Magic Number for trades +input int g_stop_loss_points = 100; // Stop Loss in points +input int g_take_profit_points = 200; // Take Profit in points +input int g_max_spread_points = 10; // Max Spread in points +input int g_max_positions = 1; // Max positions at once + +// ==================== TRADING HOURS ==================== +input bool g_use_trading_hours = false; // Enable trading hour filter +input int g_trade_start_hour = 8; // Trading Start Hour (0-23) +input int g_trade_end_hour = 20; // Trading End Hour (0-23) + +// ==================== TRAILING STOP ==================== +input bool g_use_trailing_stop = true; // Use Trailing Stop +input int g_trailing_stop_points= 50; // Trailing Stop Distance +input bool g_use_break_even = true; // Use Break Even +input int g_break_even_profit = 10; // Break Even Trigger profit +input int g_break_even_sl = 2; // Break Even SL distance + +// ==================== STRATEGY PARAMETERS ==================== +input int g_ma_fast_period = 10; // Fast MA Period +input int g_ma_slow_period = 20; // Slow MA Period +input int g_ma_shift = 0; // MA Shift +input ENUM_MA_METHOD g_ma_method = MODE_SMA; // MA Method +input ENUM_APPLIED_PRICE g_ma_price = PRICE_CLOSE; // MA Applied Price + +// ==================== DEBUG ==================== +input bool g_debug_mode = true; // Enable Debug Logging + +#endif //__CONFIG_MQH__ diff --git a/Include/Logger.mqh b/Include/Logger.mqh new file mode 100644 index 0000000..6f55687 --- /dev/null +++ b/Include/Logger.mqh @@ -0,0 +1,74 @@ +//+------------------------------------------------------------------+ +//| Logger.mqh - Simple logging system | +//| Provides info, warning, and error logging with formatting | +//+------------------------------------------------------------------+ + +#ifndef __LOGGER_MQH__ +#define __LOGGER_MQH__ + +#include "Config.mqh" + +class CLogger +{ +private: + bool m_debug_mode; + +public: + // Constructor + CLogger(bool debug_mode = true) + { + m_debug_mode = debug_mode; + } + + // Log info level message + void Info(const string message) + { + if(m_debug_mode) + PrintFormat("[INFO] %s", message); + } + + // Log warning level message + void Warning(const string message) + { + PrintFormat("[WARNING] %s", message); + } + + // Log error level message + void Error(const string message) + { + PrintFormat("[ERROR] %s", message); + } + + // Log formatted message (info level) + // Note: MQL5 does not support user-defined variadic functions, so + // pass an already-formatted string to this method. + void InfoFormat(const string formatted) + { + if(m_debug_mode) + { + PrintFormat("[INFO] %s", formatted); + } + } + + // Log formatted message (error level) + // Note: MQL5 does not support user-defined variadic functions, so + // pass an already-formatted string to this method. + void ErrorFormat(const string formatted) + { + PrintFormat("[ERROR] %s", formatted); + } + + // Set debug mode + void SetDebugMode(bool debug_mode) + { + m_debug_mode = debug_mode; + } + + // Get debug mode + bool GetDebugMode() const + { + return m_debug_mode; + } +}; + +#endif //__LOGGER_MQH__ diff --git a/Include/MarketData.mqh b/Include/MarketData.mqh new file mode 100644 index 0000000..bf7370e --- /dev/null +++ b/Include/MarketData.mqh @@ -0,0 +1,186 @@ +//+------------------------------------------------------------------+ +//| MarketData.mqh - Market data wrapper | +//| Handles bid/ask, spread, symbol info, new bar detection | +//+------------------------------------------------------------------+ + +#ifndef __MARKETDATA_MQH__ +#define __MARKETDATA_MQH__ + +#include "Config.mqh" +#include "Logger.mqh" +#include "Utilities.mqh" + +class CMarketData +{ +private: + string m_symbol; + CLogger *mp_logger; + datetime m_last_bar_time; + int m_digits; + double m_point; + +public: + // Constructor + CMarketData(const string symbol, CLogger *logger) + { + m_symbol = symbol; + mp_logger = logger; + m_last_bar_time = 0; + m_digits = CUtilities::GetDigits(symbol); + m_point = CUtilities::GetPoint(symbol); + } + + // Get current bid price + double GetBid() const + { + return SymbolInfoDouble(m_symbol, SYMBOL_BID); + } + + // Get current ask price + double GetAsk() const + { + return SymbolInfoDouble(m_symbol, SYMBOL_ASK); + } + + // Get current spread in points + int GetSpreadPoints() const + { + double spread_price = GetAsk() - GetBid(); + return CUtilities::PriceToPoints(m_symbol, spread_price); + } + + // Get current spread in price + double GetSpreadPrice() const + { + return GetAsk() - GetBid(); + } + + // Check if spread is acceptable + bool IsSpreadAcceptable(int max_spread_points) const + { + int current_spread = GetSpreadPoints(); + if(current_spread > max_spread_points) + { + if(mp_logger) + { + // Build message without using variadic StringFormat to avoid parser issues + string msg = "Spread too wide: "; + msg += IntegerToString(current_spread); + msg += " > "; + msg += IntegerToString(max_spread_points); + mp_logger.Warning(msg); + } + return false; + } + return true; + } + + // Detect new bar on current timeframe + bool IsNewBar() + { + datetime bar_time = iTime(m_symbol, PERIOD_CURRENT, 0); + + if(m_last_bar_time == 0) + { + m_last_bar_time = bar_time; + return true; + } + + if(bar_time != m_last_bar_time) + { + m_last_bar_time = bar_time; + return true; + } + + return false; + } + + // Get current close price + double GetClose() const + { + return iClose(m_symbol, PERIOD_CURRENT, 0); + } + + // Get current open price + double GetOpen() const + { + return iOpen(m_symbol, PERIOD_CURRENT, 0); + } + + // Get current high price + double GetHigh() const + { + return iHigh(m_symbol, PERIOD_CURRENT, 0); + } + + // Get current low price + double GetLow() const + { + return iLow(m_symbol, PERIOD_CURRENT, 0); + } + + // Get close price of bar N bars ago + double GetCloseAt(int shift) const + { + return iClose(m_symbol, PERIOD_CURRENT, shift); + } + + // Get open price of bar N bars ago + double GetOpenAt(int shift) const + { + return iOpen(m_symbol, PERIOD_CURRENT, shift); + } + + // Get symbol digits + int GetDigits() const + { + return m_digits; + } + + // Get symbol point + double GetPoint() const + { + return m_point; + } + + // Check if trading is allowed for this symbol + bool IsTradingAllowed() const + { + // Ensure symbol allows trading (full trade mode) + long trade_mode = (long)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_MODE); + if(trade_mode != SYMBOL_TRADE_MODE_FULL) + return false; + + // Basic check: ensure symbol is tradeable (has non-zero point and digits) + if(SymbolInfoDouble(m_symbol, SYMBOL_POINT) <= 0.0) + return false; + + return true; + } + + // Get symbol name + string GetSymbol() const + { + return m_symbol; + } + + // Get minimum volume + double GetMinVolume() const + { + return SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN); + } + + // Get maximum volume + double GetMaxVolume() const + { + return SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX); + } + + // Get volume step + double GetVolumeStep() const + { + return SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP); + } +}; + +#endif //__MARKETDATA_MQH__ diff --git a/Include/PositionManager.mqh b/Include/PositionManager.mqh new file mode 100644 index 0000000..5a6b233 --- /dev/null +++ b/Include/PositionManager.mqh @@ -0,0 +1,146 @@ +//+------------------------------------------------------------------+ +//| PositionManager.mqh - Position tracking and validation | +//| Checks for existing positions, prevents duplicates | +//+------------------------------------------------------------------+ + +#ifndef __POSITIONMANAGER_MQH__ +#define __POSITIONMANAGER_MQH__ + +#include "Config.mqh" +#include "Logger.mqh" + +class CPositionManager +{ +private: + CLogger *mp_logger; + +public: + // Constructor + CPositionManager(CLogger *logger) + { + mp_logger = logger; + } + + // Check if there is already an open position for this symbol and magic + bool HasOpenPosition(const string symbol) + { + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionSelectByTicket(PositionGetTicket(i))) + { + if(PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + return true; + } + } + } + return false; + } + + // Get current position count for this symbol and magic + int GetPositionCount(const string symbol) + { + int count = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionSelectByTicket(PositionGetTicket(i))) + { + if(PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + count++; + } + } + } + return count; + } + + // Get current position type (OP_BUY, OP_SELL, or -1 if none) + int GetPositionType(const string symbol) + { + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionSelectByTicket(PositionGetTicket(i))) + { + if(PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + return (int)PositionGetInteger(POSITION_TYPE); + } + } + } + return -1; + } + + // Get current position ticket (returns 0 if none) + ulong GetPositionTicket(const string symbol) + { + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(PositionSelectByTicket(ticket)) + { + if(PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + return ticket; + } + } + } + return 0; + } + + // Get current position profit + double GetPositionProfit(const string symbol) + { + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionSelectByTicket(PositionGetTicket(i))) + { + if(PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + return PositionGetDouble(POSITION_PROFIT); + } + } + } + return 0.0; + } + + // Check if new position is allowed (not exceeding max positions) + bool IsNewPositionAllowed(const string symbol) + { + int current_count = GetPositionCount(symbol); + + if(current_count >= g_max_positions) + { + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Max positions reached: %d >= %d", + current_count, g_max_positions)); + return false; + } + + return true; + } + + // Get total open lots for symbol and magic + double GetTotalOpenLots(const string symbol) + { + double total_lots = 0.0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionSelectByTicket(PositionGetTicket(i))) + { + if(PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + total_lots += PositionGetDouble(POSITION_VOLUME); + } + } + } + return total_lots; + } +}; + +#endif //__POSITIONMANAGER_MQH__ diff --git a/Include/RiskManager.mqh b/Include/RiskManager.mqh new file mode 100644 index 0000000..d64f5a3 --- /dev/null +++ b/Include/RiskManager.mqh @@ -0,0 +1,158 @@ +//+------------------------------------------------------------------+ +//| RiskManager.mqh - Risk and position sizing management | +//| Calculates lot sizes, validates parameters, checks trading hours | +//+------------------------------------------------------------------+ + +#ifndef __RISKMANAGER_MQH__ +#define __RISKMANAGER_MQH__ + +#include "Config.mqh" +#include "Logger.mqh" +#include "MarketData.mqh" +#include "Utilities.mqh" + +class CRiskManager +{ +private: + CMarketData *mp_market_data; + CLogger *mp_logger; + +public: + // Constructor + CRiskManager(CMarketData *market_data, CLogger *logger) + { + mp_market_data = market_data; + mp_logger = logger; + } + + // Calculate lot size based on configuration + double CalculateLotSize(int stop_loss_points) + { + double lot = 0.0; + + if(g_lot_mode == LOT_MODE_FIXED) + { + lot = g_fixed_lot; + } + else if(g_lot_mode == LOT_MODE_RISK) + { + lot = CalculateLotByRisk(stop_loss_points); + } + + return ValidateLotSize(lot); + } + + // Calculate lot size based on risk percent + double CalculateLotByRisk(int stop_loss_points) + { + double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); + double stop_loss_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), stop_loss_points); + double contract_size = CUtilities::GetContractSize(mp_market_data.GetSymbol()); + + if(stop_loss_distance == 0 || contract_size == 0) + return g_min_lot; + + // Risk = Account Balance * Risk Percent / 100 + double risk_amount = account_balance * (g_risk_percent / 100.0); + + // Lot = Risk Amount / (SL Distance * Contract Size * Point) + double point = mp_market_data.GetPoint(); + double lot = risk_amount / (stop_loss_distance * contract_size); + + return lot; + } + + // Validate and normalize lot size + double ValidateLotSize(double lot) + { + // Apply global limits first + if(lot < g_min_lot) + lot = g_min_lot; + if(lot > g_max_lot) + lot = g_max_lot; + + // Normalize to broker's lot step + lot = CUtilities::NormalizeLot(mp_market_data.GetSymbol(), lot); + + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Lot size calculated: %.2f", lot)); + + return lot; + } + + // Check if spread is acceptable + bool IsSpreadAcceptable() + { + return mp_market_data.IsSpreadAcceptable(g_max_spread_points); + } + + // Check if trading is allowed by time filter + bool IsTradingHourValid() + { + if(!g_use_trading_hours) + return true; + + MqlDateTime time_struct; + TimeToStruct(TimeCurrent(), time_struct); + int current_hour = time_struct.hour; + + if(g_trade_start_hour <= g_trade_end_hour) + { + // Normal case: e.g., 8:00 to 20:00 + if(current_hour < g_trade_start_hour || current_hour >= g_trade_end_hour) + { + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)", + current_hour, g_trade_start_hour, g_trade_end_hour)); + return false; + } + } + else + { + // Overnight case: e.g., 20:00 to 8:00 + if(current_hour < g_trade_start_hour && current_hour >= g_trade_end_hour) + { + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)", + current_hour, g_trade_start_hour, g_trade_end_hour)); + return false; + } + } + + return true; + } + + // Calculate stop loss price in absolute terms + double CalculateStopLossPrice(bool buy) + { + double bid = mp_market_data.GetBid(); + double ask = mp_market_data.GetAsk(); + double entry_price = buy ? ask : bid; + double sl_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_stop_loss_points); + + double sl_price = buy ? (entry_price - sl_distance) : (entry_price + sl_distance); + + return CUtilities::NormalizePrice(mp_market_data.GetSymbol(), sl_price); + } + + // Calculate take profit price in absolute terms + double CalculateTakeProfitPrice(bool buy) + { + double bid = mp_market_data.GetBid(); + double ask = mp_market_data.GetAsk(); + double entry_price = buy ? ask : bid; + double tp_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_take_profit_points); + + double tp_price = buy ? (entry_price + tp_distance) : (entry_price - tp_distance); + + return CUtilities::NormalizePrice(mp_market_data.GetSymbol(), tp_price); + } + + // Get market data reference + CMarketData* GetMarketData() + { + return mp_market_data; + } +}; + +#endif //__RISKMANAGER_MQH__ diff --git a/Include/Signal.mqh b/Include/Signal.mqh new file mode 100644 index 0000000..3e83044 --- /dev/null +++ b/Include/Signal.mqh @@ -0,0 +1,16 @@ +//+------------------------------------------------------------------+ +//| Signal.mqh - Trade signal enumeration | +//| Defines possible signals from strategy | +//+------------------------------------------------------------------+ + +#ifndef __SIGNAL_MQH__ +#define __SIGNAL_MQH__ + +enum E_SIGNAL +{ + SIGNAL_NONE = 0, // No signal + SIGNAL_BUY = 1, // Buy signal + SIGNAL_SELL = -1 // Sell signal +}; + +#endif //__SIGNAL_MQH__ diff --git a/Include/Strategy.mqh b/Include/Strategy.mqh new file mode 100644 index 0000000..c58934e --- /dev/null +++ b/Include/Strategy.mqh @@ -0,0 +1,151 @@ +//+------------------------------------------------------------------+ +//| Strategy.mqh - Trading strategy implementation | +//| Simple MA crossover: Buy when Fast MA > Slow MA, Sell opposite | +//+------------------------------------------------------------------+ + +#ifndef __STRATEGY_MQH__ +#define __STRATEGY_MQH__ + +#include "Signal.mqh" +#include "MarketData.mqh" +#include "Logger.mqh" +#include "Config.mqh" + +class CStrategy +{ +private: + int m_ma_fast_handle; + int m_ma_slow_handle; + CMarketData *mp_market_data; + CLogger *mp_logger; + + int m_fast_period; + int m_slow_period; + int m_ma_shift; + ENUM_MA_METHOD m_ma_method; + ENUM_APPLIED_PRICE m_ma_price; + +public: + // Constructor + CStrategy(CMarketData *market_data, CLogger *logger) + { + mp_market_data = market_data; + mp_logger = logger; + + m_fast_period = g_ma_fast_period; + m_slow_period = g_ma_slow_period; + m_ma_shift = g_ma_shift; + m_ma_method = g_ma_method; + m_ma_price = g_ma_price; + + m_ma_fast_handle = INVALID_HANDLE; + m_ma_slow_handle = INVALID_HANDLE; + } + + // Destructor - clean up indicator handles + ~CStrategy() + { + Cleanup(); + } + + // Initialize strategy and create indicator handles + bool Init() + { + // Create fast MA handle + m_ma_fast_handle = iMA(mp_market_data.GetSymbol(), PERIOD_CURRENT, + m_fast_period, m_ma_shift, m_ma_method, m_ma_price); + + if(m_ma_fast_handle == INVALID_HANDLE) + { + if(mp_logger) + mp_logger.Error("Failed to create Fast MA indicator"); + return false; + } + + // Create slow MA handle + m_ma_slow_handle = iMA(mp_market_data.GetSymbol(), PERIOD_CURRENT, + m_slow_period, m_ma_shift, m_ma_method, m_ma_price); + + if(m_ma_slow_handle == INVALID_HANDLE) + { + if(mp_logger) + mp_logger.Error("Failed to create Slow MA indicator"); + return false; + } + + if(mp_logger) + mp_logger.Info("Strategy initialized successfully"); + + return true; + } + + // Generate trading signal + E_SIGNAL GetSignal() + { + if(m_ma_fast_handle == INVALID_HANDLE || m_ma_slow_handle == INVALID_HANDLE) + return SIGNAL_NONE; + + double ma_fast = iGetMainValue(m_ma_fast_handle, 0); + double ma_slow = iGetMainValue(m_ma_slow_handle, 0); + + if(ma_fast == 0 || ma_slow == 0) + return SIGNAL_NONE; + + // Get previous values for confirmation + double ma_fast_prev = iGetMainValue(m_ma_fast_handle, 1); + double ma_slow_prev = iGetMainValue(m_ma_slow_handle, 1); + + if(ma_fast_prev == 0 || ma_slow_prev == 0) + return SIGNAL_NONE; + + // Simple MA crossover logic + // BUY: Fast MA crosses above Slow MA + if(ma_fast_prev <= ma_slow_prev && ma_fast > ma_slow) + { + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("BUY Signal: MA Fast=%.5f > MA Slow=%.5f", ma_fast, ma_slow)); + return SIGNAL_BUY; + } + + // SELL: Fast MA crosses below Slow MA + if(ma_fast_prev >= ma_slow_prev && ma_fast < ma_slow) + { + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("SELL Signal: MA Fast=%.5f < MA Slow=%.5f", ma_fast, ma_slow)); + return SIGNAL_SELL; + } + + return SIGNAL_NONE; + } + + // Clean up indicator handles + void Cleanup() + { + if(m_ma_fast_handle != INVALID_HANDLE) + { + IndicatorRelease(m_ma_fast_handle); + m_ma_fast_handle = INVALID_HANDLE; + } + + if(m_ma_slow_handle != INVALID_HANDLE) + { + IndicatorRelease(m_ma_slow_handle); + m_ma_slow_handle = INVALID_HANDLE; + } + } + +private: + // Safe way to get indicator value + double iGetMainValue(int handle, int shift) + { + double value[]; + ArraySetAsSeries(value, true); + + if(CopyBuffer(handle, 0, shift, 1, value) <= 0) + return 0.0; + + return value[0]; + } +}; + +#endif //__STRATEGY_MQH__ diff --git a/Include/TradeManager.mqh b/Include/TradeManager.mqh new file mode 100644 index 0000000..c88b8c3 --- /dev/null +++ b/Include/TradeManager.mqh @@ -0,0 +1,187 @@ +//+------------------------------------------------------------------+ +//| TradeManager.mqh - Trade execution using CTrade class | +//| Handles buy/sell orders with SL/TP and magic number | +//+------------------------------------------------------------------+ + +#ifndef __TRADEMANAGER_MQH__ +#define __TRADEMANAGER_MQH__ + +#include +#include "Config.mqh" +#include "Logger.mqh" +#include "RiskManager.mqh" + +class CTradeManager +{ +private: + CTrade m_trade; + CLogger *mp_logger; + CRiskManager *mp_risk_manager; + +public: + // Constructor + CTradeManager(CLogger *logger, CRiskManager *risk_manager) + { + mp_logger = logger; + mp_risk_manager = risk_manager; + + // Set magic number + m_trade.SetExpertMagicNumber((ulong)g_magic_number); + + // Set async/sync mode + m_trade.SetAsyncMode(false); + + // Set slippage + m_trade.SetDeviationInPoints(10); + } + + // Destructor + ~CTradeManager() + { + } + + // Open BUY trade + bool OpenBuyTrade(const string symbol, double lot) + { + if(lot <= 0) + { + if(mp_logger) + mp_logger.Error(StringFormat("Invalid lot size: %.2f", lot)); + return false; + } + + double ask = mp_risk_manager.GetMarketData().GetAsk(); + double sl = mp_risk_manager.CalculateStopLossPrice(true); + double tp = mp_risk_manager.CalculateTakeProfitPrice(true); + + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Opening BUY: lot=%.2f, SL=%.5f, TP=%.5f", lot, sl, tp)); + + if(!m_trade.Buy(lot, symbol, ask, sl, tp)) + { + if(mp_logger) + { + mp_logger.Error(StringFormat("Buy trade failed. Result code: %d, Error: %s", + m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription())); + } + return false; + } + + if(mp_logger) + { + mp_logger.Info(StringFormat("Buy trade opened. Ticket: %I64d, Volume: %.2f", + m_trade.ResultOrder(), lot)); + } + + return true; + } + + // Open SELL trade + bool OpenSellTrade(const string symbol, double lot) + { + if(lot <= 0) + { + if(mp_logger) + mp_logger.Error(StringFormat("Invalid lot size: %.2f", lot)); + return false; + } + + double bid = mp_risk_manager.GetMarketData().GetBid(); + double sl = mp_risk_manager.CalculateStopLossPrice(false); + double tp = mp_risk_manager.CalculateTakeProfitPrice(false); + + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Opening SELL: lot=%.2f, SL=%.5f, TP=%.5f", lot, sl, tp)); + + if(!m_trade.Sell(lot, symbol, bid, sl, tp)) + { + if(mp_logger) + { + mp_logger.Error(StringFormat("Sell trade failed. Result code: %d, Error: %s", + m_trade.ResultRetcode(), m_trade.ResultRetcodeDescription())); + } + return false; + } + + if(mp_logger) + { + mp_logger.Info(StringFormat("Sell trade opened. Ticket: %I64d, Volume: %.2f", + m_trade.ResultOrder(), lot)); + } + + return true; + } + + // Close position by ticket + bool ClosePosition(ulong ticket) + { + if(ticket == 0) + return false; + + if(!PositionSelectByTicket(ticket)) + return false; + + double volume = PositionGetDouble(POSITION_VOLUME); + string symbol = PositionGetString(POSITION_SYMBOL); + + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Closing position ticket %I64d, volume %.2f", ticket, volume)); + + if(!m_trade.PositionClose(ticket)) + { + if(mp_logger) + { + mp_logger.Error(StringFormat("Close position failed. Ticket: %I64d, Result: %d", + ticket, m_trade.ResultRetcode())); + } + return false; + } + + if(mp_logger) + mp_logger.Info(StringFormat("Position closed. Ticket: %I64d", ticket)); + + return true; + } + + // Modify position SL and/or TP + bool ModifyPosition(ulong ticket, double sl, double tp) + { + if(ticket == 0) + return false; + + if(!PositionSelectByTicket(ticket)) + return false; + + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Modifying position %I64d: SL=%.5f, TP=%.5f", ticket, sl, tp)); + + if(!m_trade.PositionModify(ticket, sl, tp)) + { + if(mp_logger) + { + mp_logger.Error(StringFormat("Position modify failed. Ticket: %I64d, Result: %d", + ticket, m_trade.ResultRetcode())); + } + return false; + } + + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Position modified. Ticket: %I64d", ticket)); + + return true; + } + + // Get last result code + uint GetResultRetcode() const + { + return m_trade.ResultRetcode(); + } + + // Get CTrade instance + CTrade* GetTradeObject() + { + return &m_trade; + } +}; + +#endif //__TRADEMANAGER_MQH__ diff --git a/Include/TrailingStop.mqh b/Include/TrailingStop.mqh new file mode 100644 index 0000000..4c7ba31 --- /dev/null +++ b/Include/TrailingStop.mqh @@ -0,0 +1,183 @@ +//+------------------------------------------------------------------+ +//| TrailingStop.mqh - Trailing stop and break-even management | +//| Modifies SL based on profit targets | +//+------------------------------------------------------------------+ + +#ifndef __TRAILINGSTOP_MQH__ +#define __TRAILINGSTOP_MQH__ + +#include "Config.mqh" +#include "Logger.mqh" +#include "MarketData.mqh" +#include "Utilities.mqh" +#include "TradeManager.mqh" + +class CTrailingStop +{ +private: + CMarketData *mp_market_data; + CLogger *mp_logger; + CTradeManager *mp_trade_manager; + +public: + // Constructor + CTrailingStop(CMarketData *market_data, CLogger *logger, CTradeManager *trade_manager) + { + mp_market_data = market_data; + mp_logger = logger; + mp_trade_manager = trade_manager; + } + + // Update trailing stop for all positions + void UpdateAllPositions() + { + if(!g_use_trailing_stop && !g_use_break_even) + return; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(PositionSelectByTicket(PositionGetTicket(i))) + { + if(PositionGetString(POSITION_SYMBOL) == mp_market_data.GetSymbol() && + PositionGetInteger(POSITION_MAGIC) == g_magic_number) + { + UpdatePosition(PositionGetTicket(i)); + } + } + } + } + + // Update trailing stop for a single position + void UpdatePosition(ulong ticket) + { + if(ticket == 0) + return; + + if(!PositionSelectByTicket(ticket)) + return; + + ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + double current_sl = PositionGetDouble(POSITION_SL); + double current_tp = PositionGetDouble(POSITION_TP); + double open_price = PositionGetDouble(POSITION_PRICE_OPEN); + double profit = PositionGetDouble(POSITION_PROFIT); + + double new_sl = current_sl; + double new_tp = current_tp; + bool need_modify = false; + + // Apply break-even logic first + if(g_use_break_even) + { + if(ApplyBreakEven(pos_type, open_price, current_sl, profit, new_sl)) + { + need_modify = true; + } + } + + // Apply trailing stop logic + if(g_use_trailing_stop) + { + if(ApplyTrailingStop(pos_type, current_sl, new_sl)) + { + need_modify = true; + } + } + + // Only modify if SL changed + if(need_modify && new_sl != current_sl) + { + mp_trade_manager.ModifyPosition(ticket, new_sl, new_tp); + } + } + +private: + // Apply break-even logic + bool ApplyBreakEven(ENUM_POSITION_TYPE pos_type, double open_price, + double current_sl, double profit, double &new_sl) + { + double break_even_trigger = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), + g_break_even_profit); + double break_even_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), + g_break_even_sl); + + // Break-even only if profit threshold is reached + if(profit < break_even_trigger) + return false; + + // For BUY: move SL to BE (open price + distance) + if(pos_type == POSITION_TYPE_BUY) + { + double be_sl = open_price + break_even_distance; + be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl); + + if(be_sl > current_sl) + { + new_sl = be_sl; + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Break-even applied: BUY SL moved to %.5f", be_sl)); + return true; + } + } + // For SELL: move SL to BE (open price - distance) + else if(pos_type == POSITION_TYPE_SELL) + { + double be_sl = open_price - break_even_distance; + be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl); + + if(be_sl < current_sl) + { + new_sl = be_sl; + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Break-even applied: SELL SL moved to %.5f", be_sl)); + return true; + } + } + + return false; + } + + // Apply trailing stop logic + bool ApplyTrailingStop(ENUM_POSITION_TYPE pos_type, double current_sl, double &new_sl) + { + double bid = mp_market_data.GetBid(); + double ask = mp_market_data.GetAsk(); + double trail_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), + g_trailing_stop_points); + + // For BUY: trailing stop follows price from below + if(pos_type == POSITION_TYPE_BUY) + { + double candidate_sl = bid - trail_distance; + candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl); + + // Only move SL up (never down) + if(candidate_sl > current_sl) + { + new_sl = candidate_sl; + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Trailing stop applied: BUY SL moved to %.5f", candidate_sl)); + return true; + } + } + // For SELL: trailing stop follows price from above + else if(pos_type == POSITION_TYPE_SELL) + { + double candidate_sl = ask + trail_distance; + candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl); + + // Only move SL down (never up) + if(candidate_sl < current_sl) + { + new_sl = candidate_sl; + if(mp_logger && g_debug_mode) + mp_logger.Info(StringFormat("Trailing stop applied: SELL SL moved to %.5f", candidate_sl)); + return true; + } + } + + return false; + } +}; + +#endif //__TRAILINGSTOP_MQH__ diff --git a/Include/Utilities.mqh b/Include/Utilities.mqh new file mode 100644 index 0000000..7ce6f7a --- /dev/null +++ b/Include/Utilities.mqh @@ -0,0 +1,81 @@ +//+------------------------------------------------------------------+ +//| Utilities.mqh - Helper utility functions | +//| Price normalization, lot normalization, etc. | +//+------------------------------------------------------------------+ + +#ifndef __UTILITIES_MQH__ +#define __UTILITIES_MQH__ + +class CUtilities +{ +public: + // Normalize price to bid/ask + static double NormalizePrice(const string symbol, double price) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + int digits = GetDigits(symbol); + return NormalizeDouble(price, digits); + } + + // Normalize lot size to contract size step + static double NormalizeLot(const string symbol, double lot) + { + double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); + double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); + double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); + + // Apply limits + if(lot < min_lot) lot = min_lot; + if(lot > max_lot) lot = max_lot; + + // Round to step + lot = MathFloor(lot / lot_step) * lot_step; + + return NormalizeDouble(lot, 2); + } + + // Convert points to price distance + static double PointsToPrice(const string symbol, int points) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + return (double)points * point; + } + + // Convert price distance to points + static int PriceToPoints(const string symbol, double price_distance) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + return (int)(price_distance / point); + } + + // Get point value + static double GetPoint(const string symbol) + { + return SymbolInfoDouble(symbol, SYMBOL_POINT); + } + + // Get digits + static int GetDigits(const string symbol) + { + long digits = SymbolInfoInteger(symbol, SYMBOL_DIGITS); + if(digits < 0) + digits = 0; + if(digits > 255) + digits = 255; + return (int)digits; + } + + // Safe double comparison with precision + static bool DoubleEquals(double a, double b, double tolerance = 0.00001) + { + return MathAbs(a - b) < tolerance; + } + + // Get contract size (lot multiplier) + static double GetContractSize(const string symbol) + { + return SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE); + } +}; + +#endif //__UTILITIES_MQH__ diff --git a/MyProEA.ex5 b/MyProEA.ex5 new file mode 100644 index 0000000000000000000000000000000000000000..4a7bc7cbca29b24574db9b339c9b13d68ff6d0cb GIT binary patch literal 53130 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 literal 0 HcmV?d00001 diff --git a/MyProEA.mq5 b/MyProEA.mq5 new file mode 100644 index 0000000..557ea7c --- /dev/null +++ b/MyProEA.mq5 @@ -0,0 +1,242 @@ +//+------------------------------------------------------------------+ +//| MyProEA.mq5 - Professional Expert Advisor | +//| Main EA file with clean lifecycle and separation of concerns | +//+------------------------------------------------------------------+ + +#property copyright "Educational EA - BackTesting Only" +#property link "https://www.mql5.com" +#property version "1.00" +#property strict +#property description "Professional modular EA with MA crossover strategy" +#property description "For educational and backtesting purposes only" + +// Include all class definitions +#include "Include/Config.mqh" +#include "Include/Logger.mqh" +#include "Include/Utilities.mqh" +#include "Include/MarketData.mqh" +#include "Include/Signal.mqh" +#include "Include/Strategy.mqh" +#include "Include/RiskManager.mqh" +#include "Include/PositionManager.mqh" +#include "Include/TradeManager.mqh" +#include "Include/TrailingStop.mqh" + +//+------------------------------------------------------------------+ +// Global class instances +//+------------------------------------------------------------------+ + +CLogger *g_logger = NULL; +CMarketData *g_market_data = NULL; +CStrategy *g_strategy = NULL; +CRiskManager *g_risk_manager = NULL; +CPositionManager *g_position_manager = NULL; +CTradeManager *g_trade_manager = NULL; +CTrailingStop *g_trailing_stop = NULL; + +//+------------------------------------------------------------------+ +// EA Initialization +//+------------------------------------------------------------------+ +int OnInit() +{ + // Create logger instance + g_logger = new CLogger(g_debug_mode); + g_logger.Info("===== EA INITIALIZATION START ====="); + + // Create market data handler + g_market_data = new CMarketData(_Symbol, g_logger); + if(!g_market_data.IsTradingAllowed()) + { + g_logger.Error("Trading not allowed for this symbol"); + return INIT_FAILED; + } + + // Create strategy + g_strategy = new CStrategy(g_market_data, g_logger); + if(!g_strategy.Init()) + { + g_logger.Error("Failed to initialize strategy"); + return INIT_FAILED; + } + + // Create risk manager + g_risk_manager = new CRiskManager(g_market_data, g_logger); + + // Create position manager + g_position_manager = new CPositionManager(g_logger); + + // Create trade manager + g_trade_manager = new CTradeManager(g_logger, g_risk_manager); + + // Create trailing stop manager + g_trailing_stop = new CTrailingStop(g_market_data, g_logger, g_trade_manager); + + // Log configuration + g_logger.Info(StringFormat("Magic Number: %d", g_magic_number)); + g_logger.Info(StringFormat("Symbol: %s", _Symbol)); + g_logger.Info(StringFormat("Timeframe: %s", EnumToString(PERIOD_CURRENT))); + g_logger.Info(StringFormat("Lot Mode: %s", + g_lot_mode == LOT_MODE_FIXED ? "FIXED" : "RISK")); + g_logger.Info(StringFormat("Stop Loss: %d points, Take Profit: %d points", + g_stop_loss_points, g_take_profit_points)); + g_logger.Info(StringFormat("Max Positions: %d, Max Spread: %d points", + g_max_positions, g_max_spread_points)); + + g_logger.Info("===== EA INITIALIZATION COMPLETE ====="); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +// EA Deinitialization +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + g_logger.Info("===== EA DEINITIALIZATION START ====="); + + string reason_text = ""; + switch(reason) + { + case REASON_ACCOUNT: reason_text = "Account changed"; break; + case REASON_CHARTCHANGE: reason_text = "Chart changed"; break; + case REASON_CHARTCLOSE: reason_text = "Chart closed"; break; + case REASON_PARAMETERS: reason_text = "Parameters changed"; break; + case REASON_RECOMPILE: reason_text = "EA recompiled"; break; + case REASON_REMOVE: reason_text = "EA removed"; break; + default: reason_text = "Unknown reason"; break; + } + + g_logger.Info(StringFormat("Deinit reason: %s (%d)", reason_text, reason)); + + // Clean up strategy (release indicator handles) + if(g_strategy != NULL) + { + delete g_strategy; + g_strategy = NULL; + } + + // Clean up all other objects + if(g_trailing_stop != NULL) + { + delete g_trailing_stop; + g_trailing_stop = NULL; + } + + if(g_trade_manager != NULL) + { + delete g_trade_manager; + g_trade_manager = NULL; + } + + if(g_position_manager != NULL) + { + delete g_position_manager; + g_position_manager = NULL; + } + + if(g_risk_manager != NULL) + { + delete g_risk_manager; + g_risk_manager = NULL; + } + + if(g_market_data != NULL) + { + delete g_market_data; + g_market_data = NULL; + } + + if(g_logger != NULL) + { + g_logger.Info("===== EA DEINITIALIZATION COMPLETE ====="); + delete g_logger; + g_logger = NULL; + } +} + +//+------------------------------------------------------------------+ +// Main EA Logic - OnTick() +//+------------------------------------------------------------------+ +void OnTick() +{ + // Step 1: Update market data (always) + if(!g_market_data.IsTradingAllowed()) + { + return; + } + + // Step 2: Check spread - if spread is too wide, don't trade + if(!g_risk_manager.IsSpreadAcceptable()) + { + return; + } + + // Step 3: Check trading hours + if(!g_risk_manager.IsTradingHourValid()) + { + return; + } + + // Step 4: Manage existing positions (trailing stop, break-even) + g_trailing_stop.UpdateAllPositions(); + + // Step 5: Only process signals on new bar + if(!g_market_data.IsNewBar()) + { + return; + } + + // Step 6: Get trading signal from strategy + E_SIGNAL signal = g_strategy.GetSignal(); + + if(signal == SIGNAL_NONE) + { + return; + } + + // Step 7: Check if we already have a position + if(g_position_manager.HasOpenPosition(_Symbol)) + { + if(g_debug_mode) + g_logger.Info("Already have open position, skipping entry"); + return; + } + + // Step 8: Check if new position is allowed + if(!g_position_manager.IsNewPositionAllowed(_Symbol)) + { + g_logger.Warning("New position not allowed (max positions reached)"); + return; + } + + // Step 9: Calculate lot size + double lot = g_risk_manager.CalculateLotSize(g_stop_loss_points); + if(lot <= 0) + { + g_logger.Error("Invalid lot size calculated"); + return; + } + + // Step 10: Execute trade based on signal + bool trade_success = false; + + if(signal == SIGNAL_BUY) + { + trade_success = g_trade_manager.OpenBuyTrade(_Symbol, lot); + } + else if(signal == SIGNAL_SELL) + { + trade_success = g_trade_manager.OpenSellTrade(_Symbol, lot); + } + + if(trade_success) + { + if(g_logger) + g_logger.Info("Trade executed successfully"); + } + else + { + if(g_logger) + g_logger.Error("Trade execution failed"); + } +} +