Initial MQL5 EA Project

This commit is contained in:
peter
2026-05-27 12:32:26 -04:00
commit 2682cfdca1
12 changed files with 1475 additions and 0 deletions
+51
View File
@@ -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__
+74
View File
@@ -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__
+186
View File
@@ -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__
+146
View File
@@ -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__
+158
View File
@@ -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__
+16
View File
@@ -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__
+151
View File
@@ -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__
+187
View File
@@ -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 <Trade/Trade.mqh>
#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__
+183
View File
@@ -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__
+81
View File
@@ -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__
BIN
View File
Binary file not shown.
+242
View File
@@ -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");
}
}