//+------------------------------------------------------------------+
//| Your EA Name |
//| Your Name or Info |
//+------------------------------------------------------------------+
#property copyright "Your Name or Info"
#property version "1.00"
#property strict
// Include statements
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
// Import the functions from the SQLiteWrapper.dll
#import "SQLiteWrapper.dll"
long OpenDatabase(const char &filename[], char &errmsg[], int
errmsgSize);
int CloseDatabase(long dbHandle);
int ExecuteSQL(long dbHandle, const char &query[], char &errmsg[], int
errmsgSize);
int ExportTradeLogsToCSV(long dbHandle, const char &csvFilePath[], char
&errmsg[], int errmsgSize);
#import
// Import ShellExecute function from shell32.dll
#import "shell32.dll"
int ShellExecuteW(int hwnd, string Operation, string File, string
Parameters, string Directory, int ShowCmd);
#import
// Global variables
CTrade trade;
long dbHandle; // Changed from int to long to match the return type of
OpenDatabase
int nextTradeID = 1;
//+------------------------------------------------------------------+
//| Struct definitions |
//+------------------------------------------------------------------+
struct OpenPositionData
ulong positionID;
ulong entryDealTicket;
string symbol;
string tradeType; // "Buy" or "Sell"
double entryPrice;
datetime entryTime;
double atr;
double wprValue; // Williams %R value
double lotSize;
string reasonEntry;
string date;
string time;
double currentStopLoss; // Field to track current Stop Loss
double takeProfitPrice;
int profitLevelReached; // Field to track profit levels reached
bool trailingStopActivated; // Indicates if trailing stop was activated
double profitLevelAtTrailingStop; // Stores profit level at trailing stop
activation
bool isLogged; // New field to indicate if the position has
been logged
};
// Array to store open positions
OpenPositionData openPositions[];
//+------------------------------------------------------------------+
//| Struct for trade data logging |
//+------------------------------------------------------------------+
struct TradeData
ulong positionID; // Position ID
string symbol;
string tradeType;
double entryPrice;
double exitPrice;
double lotSize;
string entryDate;
string entryTime;
string exitDate;
string exitTime;
long duration;
string reasonEntry;
string reasonExit;
double profitLoss;
double swap; // Added field for swap
double commission; // Added field for commission
double atr;
double wprValue; // Williams %R value
string remarks;
};
//--- Input parameters
input double HighRisk = 5.0; // High risk percentage
input double LowRisk = 0.7; // Low risk percentage
input int MagicNumber = 987654321; // Magic number for trades
input int Slippage = 10; // Slippage for trades
// --- Input parameters for each indicator's timeframe
input ENUM_TIMEFRAMES ATRTimeframe = PERIOD_M1; // Timeframe
for ATR
input ENUM_TIMEFRAMES WPRTimeframe = PERIOD_M1; // Timeframe
for Williams %R
input ENUM_TIMEFRAMES PivotTimeframe = PERIOD_H6; // Timeframe for
Pivot Points
input ENUM_TIMEFRAMES TickVolumeTimeframe = PERIOD_M1; //
Timeframe for tick volume
input ENUM_TIMEFRAMES VolumeProfileTimeframe = PERIOD_M1; //
Timeframe for Volume Profile
input int VolumeProfilePeriod = 20; // Look-back period for
Volume Profile
//--- Input parameters for Trailing Stop ---
input bool EnableTrailingStop = true; // Toggle to enable or
disable Trailing Stop
// input double TrailingStopATRMultiplier = 2.0; // **Increased
Multiplier** for ATR to set trailing stop distance
// input int TrailingStopATRPeriod = 14; // ATR period for Trailing
Stop calculation
// input double TrailingStopMinDistance = 10.0; // Minimum trailing
stop distance in points
//--- ATR variables
input int atrPeriod = 14; // ATR period (adjustable in input)
input double atrLogThreshold = 0.0001; // Set a threshold for significant
ATR change
input double atrMinThreshold = 0.0001; // Minimum ATR value for trading
(adjustable in input)
//--- Williams %R Input parameters
input int WPR_Period = 14; // Period for Williams %R
input int WPR_Overbought = -20; // Overbought level
input int WPR_Oversold = -80; // Oversold level
int wprHandle; // Handle for Williams %R
double wprValueGlobal = 0.0; // Global variable to store Williams %R
value
bool tradeExecuted = false; // Initialize trade execution flag
//--- Global variables
double prevATRValue = 0.0;
double lastLoggedATRValue = 0.0;
bool isHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE) ==
ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- Adjust stop-loss multiplier for low volatility
input double atrStopLossMultiplierHighVolatility = 3.0; // Higher SL
multiplier for high volatility
input double atrStopLossMultiplierLowVolatility = 2.0; // SL multiplier for
low volatility
//--- Lot size adjustment based on volatility
input double volatilityLotMultiplier = 1.0;
//--- Consecutive loss protection
input int maxConsecutiveLosses = 3; // Max allowed consecutive losses
before pausing trading
int consecutiveLosses = 0;
bool isPaused = false;
datetime pauseEndTime;
//--- Volatility parameters
input double atrVolatilityThreshold = 0.0008; // Threshold to determine
volatility
bool isHighVolatility = false;
bool wasHighVolatility = false;
datetime nextVolatilityCheckTime = 0;
//--- Cooldown parameters
datetime lastTradeTime = 0;
input int cooldownTime = 30; // Cooldown time in seconds
//--- Input parameters for risk management
input double stopLossMultiplier = 10; // Multiplier for the stop loss points
double minStopLossPoints; // Minimum stop loss points variable
(initialized later)
//--- Input parameters for Trailing Stop Profit Levels ---
input double ProfitLevel1 = 15.0;
input double ProfitLevel2 = 30.0;
input double ProfitLevel3 = 45.0;
input double ProfitLevel4 = 60.0;
input double ProfitLevel5 = 75.0;
input uint TimerIntervalMilliseconds = 500; // Timer interval in
milliseconds
input double RiskToRewardRatio = 1.5; // Default risk-to-reward ratio
input bool EnableDatabaseLogging = true; // Toggle for database logging
// Define log levels
enum LogLevelEnum
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_WARNING= 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_DEBUG = 4
};
// Define log categories as bit flags
enum LogCategoryEnum {
LOG_CAT_NONE = 0x0000,
LOG_CAT_ATR = 0x0001,
LOG_CAT_TRADE_EXECUTION = 0x0002,
LOG_CAT_SLTP_VALUES = 0x0004,
LOG_CAT_RISK_MANAGEMENT = 0x0008,
LOG_CAT_ERRORS = 0x0010,
LOG_CAT_OTHER = 0x0020,
LOG_CAT_ALL = 0xFFFF
};
// Input parameter for log level
input LogLevelEnum LogLevel = LOG_LEVEL_INFO;
// Input parameter for log categories (bit flags)
input uint LogCategories = LOG_CAT_ALL; // Default to all categories
// Global array to store log entries
struct LogEntry {
string date;
string time;
string logLevel;
string category;
string message;
};
LogEntry logEntries[];
// Global variable to count ticks
int tickCount = 0;
ulong loggedPositionIDs[];
//--- Input parameters for closing trades before end of day
input bool CloseTradesBeforeEndOfDay = true; // Enable closing trades
before end of day
input int CloseTradesHour = 23; // Hour to close all trades (24-hour
format)
input int CloseTradesMinute = 59; // Minute to close all trades
//--- Input parameters for tick volume filtering
input int TickVolumePeriods = 5; // Number of periods to consider for
average tick volume
input double TickVolumeMultiplier = 1.0; // Multiplier for average tick
volume threshold
input bool EnableTickVolumeFilter = true; // Enable or disable tick volume
filtering
//--- Input parameters for trend detection ---
input ENUM_TIMEFRAMES TrendTimeframe = PERIOD_H6; // Trend
Detection
input int TrendMAPeriod = 50; // Period for moving average
used in trend detection
input ENUM_MA_METHOD TrendMAMethod = MODE_SMA; // Moving
average method (SMA, EMA, etc.)
int volumeProfileHandle; // Handle for Volume Profile custom indicator
// New global variables for trade counting
input int maxTradesPerDay = 10; // Maximum allowed trades per day
int dailyTradeCount = 0; // Current trade count for the day
datetime lastTradeDay = 0; // Stores the day of the last trade to reset
count daily
datetime lastBarTime = 0; // To track the last bar time for resetting
tradeExecuted
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
// Set the minimum stop loss points based on the multiplier and the
symbol's _Point value
minStopLossPoints = stopLossMultiplier * _Point;
// Check if automated trading is allowed
if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
LogMessage("Automated trading is disabled. Please enable
automated trading.", LOG_LEVEL_ERROR);
return INIT_FAILED;
// Ensure the symbol is selected and data is available
if (!SymbolSelect(_Symbol, true))
LogMessage("Failed to select symbol " + _Symbol,
LOG_LEVEL_ERROR);
return INIT_FAILED;
// Initialize timer for regular updates
EventSetMillisecondTimer(TimerIntervalMilliseconds);
// Initialize logging and database
LogMessage("Initializing EA and preparing log file...", LOG_LEVEL_INFO);
// --- Database Initialization ---
// Get the path to the Files directory
string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\
Files";
string dbPath = filesDir + "\\TradeLog.db";
// Convert dbPath to a char array
char filePath[512];
StringToCharArray(dbPath, filePath);
// Log the paths for debugging
LogMessage("Files directory: " + filesDir, LOG_LEVEL_DEBUG);
LogMessage("Database path: " + dbPath, LOG_LEVEL_DEBUG);
// Database handle and error message buffer
char errorMsg[256];
// Open the database
dbHandle = OpenDatabase(filePath, errorMsg, sizeof(errorMsg));
if (dbHandle == 0)
LogMessage("Failed to open database. Error: " +
CharArrayToString(errorMsg), LOG_LEVEL_ERROR);
return INIT_FAILED;
else
{
LogMessage("Database opened successfully.", LOG_LEVEL_INFO);
// Create the trades table if it doesn't exist
string createTableQuery = "CREATE TABLE IF NOT EXISTS trades ("
"TradeID INTEGER PRIMARY KEY AUTOINCREMENT, "
"PositionID INTEGER, "
"EntryDate TEXT, "
"EntryTime TEXT, "
"ExitDate TEXT, "
"ExitTime TEXT, "
"Symbol TEXT, "
"TradeType TEXT, "
"EntryPrice REAL, "
"ExitPrice REAL, "
"ReasonEntry TEXT, "
"ReasonExit TEXT, "
"ProfitLoss REAL, "
"Swap REAL, " // Added Swap field
"Commission REAL, " // Added Commission field
"ATR REAL, "
"WPRValue REAL, "
"Duration INTEGER, "
"LotSize REAL, "
"Remarks TEXT"
");";
char query[4096];
StringToCharArray(createTableQuery, query);
int execResult = ExecuteSQL(dbHandle, query, errorMsg,
sizeof(errorMsg));
if (execResult != 0)
LogMessage("Error creating trades table. Error: " +
CharArrayToString(errorMsg), LOG_LEVEL_ERROR);
CloseDatabase(dbHandle);
return INIT_FAILED;
LogMessage("Trades table created or already exists.",
LOG_LEVEL_INFO);
// Create the TradeLog table for failed trades
string createFailedTradesTableQuery = "CREATE TABLE IF NOT EXISTS
TradeLog ("
"Date TEXT, "
"Time TEXT, "
"Symbol TEXT, "
"Remarks TEXT, "
"ATR REAL"
");";
StringToCharArray(createFailedTradesTableQuery, query);
execResult = ExecuteSQL(dbHandle, query, errorMsg, sizeof(errorMsg));
if (execResult != 0)
LogMessage("Error creating TradeLog table. Error: " +
CharArrayToString(errorMsg), LOG_LEVEL_ERROR);
CloseDatabase(dbHandle);
return INIT_FAILED;
}
LogMessage("TradeLog table created or already exists.",
LOG_LEVEL_INFO);
// Create the CustomLog table for logs while testing
string createLogTableQuery = "CREATE TABLE IF NOT EXISTS LogEntries
("
"Date TEXT, "
"Time TEXT, "
"LogLevel TEXT, "
"Category TEXT, "
"Message TEXT"
");";
char logTableQuery[1024];
StringToCharArray(createLogTableQuery, logTableQuery);
ExecuteSQL(dbHandle, logTableQuery, errorMsg, sizeof(errorMsg));
// --- End of Database Initialization ---
// Initialize Williams %R indicator handle
wprHandle = iWPR(_Symbol, WPRTimeframe, WPR_Period);
if (wprHandle == INVALID_HANDLE)
LogMessage("Failed to create Williams %R handle",
LOG_LEVEL_ERROR);
return INIT_FAILED;
LogMessage("Williams %R initialized", LOG_LEVEL_INFO);
// Initialize Volume Profile Indicator with correct file name ("Volume
Indicator")
// Pass VolumeProfilePeriod and VolumeProfileTimeframe as parameters
to the indicator
volumeProfileHandle = iCustom(_Symbol, VolumeProfileTimeframe,
"Volume Indicator", VolumeProfilePeriod);
if (volumeProfileHandle == INVALID_HANDLE)
LogMessage("Failed to initialize Volume Profile Indicator.",
LOG_LEVEL_ERROR);
return INIT_FAILED;
LogMessage("Volume Profile Indicator initialized with
VolumeProfilePeriod=" + IntegerToString(VolumeProfilePeriod) + " and
Timeframe=" + EnumToString(VolumeProfileTimeframe),
LOG_LEVEL_INFO);
LogMessage("Expert initialized.", LOG_LEVEL_INFO);
return INIT_SUCCEEDED;
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
// Flush remaining logs to ensure all data is written to the database
FlushLogsToDatabase();
Print("Expert deinitialized with reason: ", reason);
// Check for any open positions and log them
int totalPositions = ArraySize(openPositions);
for (int i = 0; i < totalPositions; i++)
ulong positionID = openPositions[i].positionID;
// Attempt to find the closing deal
ulong closingDealTicket = FindClosingDealForPosition(positionID);
if (closingDealTicket != 0)
LogClosedTrade(positionID, closingDealTicket);
else
// Manually construct TradeData and log the trade
LogMessage("No closing deal found for position ID: " +
IntegerToString(positionID) + ". Logging as closed due to deinitialization.",
LOG_LEVEL_WARNING);
// Manually construct TradeData
TradeData tradeData;
OpenPositionData entryData = openPositions[i];
tradeData.positionID = entryData.positionID;
tradeData.entryDate = entryData.date;
tradeData.entryTime = entryData.time;
tradeData.entryPrice = entryData.entryPrice;
tradeData.reasonEntry = entryData.reasonEntry;
tradeData.lotSize = entryData.lotSize;
tradeData.duration = (long)(TimeCurrent() - entryData.entryTime);
tradeData.symbol = entryData.symbol;
// Correctly set the exit price based on the trade type
if (entryData.tradeType == "Buy")
tradeData.exitPrice = SymbolInfoDouble(_Symbol,
SYMBOL_BID);
else
tradeData.exitPrice = SymbolInfoDouble(_Symbol,
SYMBOL_ASK);
tradeData.exitPrice = NormalizeDouble(tradeData.exitPrice,
_Digits);
tradeData.tradeType = entryData.tradeType;
tradeData.exitDate = TimeToString(TimeCurrent(), TIME_DATE);
tradeData.exitTime = TimeToString(TimeCurrent(), TIME_SECONDS
| TIME_MINUTES);
tradeData.atr = entryData.atr;
tradeData.wprValue = entryData.wprValue;
tradeData.reasonExit = "Closed due to EA deinitialization";
tradeData.remarks = "Logged on deinitialization";
// Retrieve swap and commission
double swap = 0.0;
double commission = 0.0;
// Attempt to find the last deal associated with this position
ulong lastDealTicket = FindLastDealForPosition(positionID);
if (lastDealTicket != 0 && HistoryDealSelect(lastDealTicket))
swap = HistoryDealGetDouble(lastDealTicket, DEAL_SWAP);
commission = HistoryDealGetDouble(lastDealTicket,
DEAL_COMMISSION);
// Calculate profit/loss using OrderCalcProfit
ENUM_ORDER_TYPE orderType = (tradeData.tradeType ==
"Buy") ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
double calculatedProfitLoss = 0.0;
// Adjust the lot size to standard lots if necessary
double adjustedLotSize = tradeData.lotSize;
if (adjustedLotSize > 50.0)
adjustedLotSize = adjustedLotSize / 100.0;
if (OrderCalcProfit(
orderType,
_Symbol,
adjustedLotSize,
tradeData.entryPrice,
tradeData.exitPrice,
calculatedProfitLoss))
// Adjust profit/loss
tradeData.profitLoss = calculatedProfitLoss + swap +
commission;
tradeData.swap = swap;
tradeData.commission = commission;
else
int errorCode = GetLastError();
PrintFormat("Failed to calculate profit/loss using OrderCalcProfit.
Error code: %d, Description: %s", errorCode, ErrorDescription(errorCode));
tradeData.profitLoss = 0.0; // Default to zero if calculation fails
tradeData.swap = swap;
tradeData.commission = commission;
// Log the trade
LogTrade(tradeData);
// Clear openPositions array
ArrayResize(openPositions, 0);
// Export trade log to Excel before closing the database
ExportTradeLogToExcel();
// Close the database if it's open
if (dbHandle != 0)
int closeResult = CloseDatabase(dbHandle);
if (closeResult != 0)
LogMessage("Failed to close the database.", LOG_LEVEL_ERROR);
else
LogMessage("Database closed successfully.", LOG_LEVEL_INFO);
dbHandle = 0; // Reset the handle
// Remove the timer
EventKillTimer();
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
// Close trades before end of day to avoid swaps
if (CloseTradesBeforeEndOfDay && IsTimeToCloseTrades())
LogMessage("End of day detected. Closing all positions to avoid
swaps.", LOG_LEVEL_INFO);
CloseAllPositions();
return; // Skip further processing on this tick
}
// Check and reset daily trade count if a new day starts
MqlDateTime currentTimeStruct;
TimeToStruct(TimeCurrent(), currentTimeStruct);
int currentDay = currentTimeStruct.day;
if (currentDay != lastTradeDay)
dailyTradeCount = 0; // Reset trade count
lastTradeDay = currentDay;
LogMessage("New day detected. Daily trade count reset.",
LOG_LEVEL_INFO);
// Check if daily trade limit has been reached
if (dailyTradeCount >= maxTradesPerDay)
LogMessage("Daily trade limit reached. No further trades will be
executed today.", LOG_LEVEL_WARNING);
return;
// Check for new bar
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); // Get
the time of the current bar
if (currentBarTime != lastBarTime)
// A new bar has started
tradeExecuted = false;
lastBarTime = currentBarTime;
}
// Check if cooldown period has passed
if (TimeCurrent() - lastTradeTime < cooldownTime)
LogMessage("Cooldown time is active. Waiting before placing new
trades.", LOG_LEVEL_WARNING);
return;
// Prevent repeated trade execution if tradeExecuted is set
if (tradeExecuted)
LogMessage("Trade already executed on this bar, skipping further
trade execution.", LOG_LEVEL_WARNING);
return;
if (EnableTrailingStop)
// Manage trailing stops on every tick
int totalPositions = ArraySize(openPositions);
for (int i = 0; i < totalPositions; i++)
OpenPositionData position = openPositions[i];
ManageTrailingStop(
position.positionID,
position.entryPrice,
position.takeProfitPrice,
isHighVolatility ? HighRisk : LowRisk
);
totalPositions = ArraySize(openPositions);
if (i >= totalPositions) break;
// Always check for closed positions
CheckForClosedPositions();
// Skip trading if ATR is below minimum threshold
double atrValue = GetATR(atrPeriod);
if (atrValue < atrMinThreshold)
LogMessage("ATR below minimum threshold. No trade will be placed.
ATR: " + DoubleToString(atrValue, _Digits), LOG_LEVEL_WARNING,
LOG_CAT_ATR);
return;
// Log and verify volatility status
CheckVolatility(atrValue);
LogMessage("ATR=" + DoubleToString(atrValue, _Digits) + ",
Volatility=" + (isHighVolatility ? "High" : "Low"), LOG_LEVEL_DEBUG,
LOG_CAT_ATR);
// Ensure trades are processed within trading hours
if (!IsWithinTradingHours())
LogMessage("Outside trading hours.", LOG_LEVEL_WARNING);
return;
}
// Fetch Williams %R value for current bar
double wprValue[1];
if (CopyBuffer(wprHandle, 0, 1, 1, wprValue) <= 0)
LogMessage("Failed to retrieve valid Williams %R value for current
bar.", LOG_LEVEL_WARNING);
LogFailedTrade("Williams %R Indicator Failure");
return;
wprValueGlobal = wprValue[0];
LogMessage("Williams %R Value: " + DoubleToString(wprValue[0], 2),
LOG_LEVEL_DEBUG);
bool buySignal = false, sellSignal = false;
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// Fetch high and low volume levels from Volume Profile Indicator
double highVolumeLevel[1];
double lowVolumeLevel[1];
if (CopyBuffer(volumeProfileHandle, 0, 0, 1, highVolumeLevel) <= 0 ||
CopyBuffer(volumeProfileHandle, 1, 0, 1, lowVolumeLevel) <= 0)
LogMessage("Failed to retrieve volume profile data for
support/resistance levels.", LOG_LEVEL_WARNING);
return;
// Log Volume Profile Levels
LogMessage("High Volume Level: " +
DoubleToString(highVolumeLevel[0], _Digits) + ", Low Volume Level: " +
DoubleToString(lowVolumeLevel[0], _Digits), LOG_LEVEL_DEBUG,
LOG_CAT_ATR);
// Conditions for buy and sell signals based on Volume Profile levels and
trend direction
int trendDirection = GetTrendDirection();
if (wprValueGlobal <= WPR_Oversold && currentPrice <=
lowVolumeLevel[0] * 1.002 && trendDirection == 1)
buySignal = true;
LogMessage("Buy Signal with Volume Profile support and uptrend
alignment!", LOG_LEVEL_INFO, LOG_CAT_TRADE_EXECUTION);
if (wprValueGlobal >= WPR_Overbought && currentPrice >=
highVolumeLevel[0] * 0.998 && trendDirection == -1)
sellSignal = true;
LogMessage("Sell Signal with Volume Profile resistance and
downtrend alignment!", LOG_LEVEL_INFO, LOG_CAT_TRADE_EXECUTION);
// No signal detected; log and exit
if (!buySignal && !sellSignal)
LogMessage("No buy or sell signal triggered on this bar.",
LOG_LEVEL_DEBUG);
return;
}
// Check if tick volume is increasing
bool tickVolumeIncreasing = IsTickVolumeIncreasing();
// Execute Buy Trade if conditions are met
if (buySignal && tickVolumeIncreasing)
LogMessage("Executing Buy trade based on ATR, Williams %R,
Volume Profile support, and trend alignment.", LOG_LEVEL_INFO,
LOG_CAT_TRADE_EXECUTION);
ExecuteTrade(ORDER_TYPE_BUY, atrValue, "ATR, WPR, Volume Profile,
and Trend", wprValueGlobal);
tradeExecuted = true; // Set flag to indicate trade has been executed
lastTradeTime = TimeCurrent(); // Update lastTradeTime
else if (buySignal)
LogMessage("Buy signal detected but tick volume is not increasing or
trade already executed. Skipping trade.", LOG_LEVEL_INFO);
// Use 'else if' to prevent executing both trades
else if (sellSignal && tickVolumeIncreasing)
LogMessage("Executing Sell trade based on ATR, Williams %R,
Volume Profile resistance, and trend alignment.", LOG_LEVEL_INFO,
LOG_CAT_TRADE_EXECUTION);
ExecuteTrade(ORDER_TYPE_SELL, atrValue, "ATR, WPR, Volume
Profile, and Trend", wprValueGlobal);
tradeExecuted = true; // Set flag to indicate trade has been executed
lastTradeTime = TimeCurrent(); // Update lastTradeTime
}
else if (sellSignal)
LogMessage("Sell signal detected but tick volume is not increasing or
trade already executed. Skipping trade.", LOG_LEVEL_INFO);
//+------------------------------------------------------------------+
//| Open Position Function using CTrade with SL/TP Validation |
//+------------------------------------------------------------------+
bool OpenPosition(ENUM_ORDER_TYPE orderType, double lotSize, double
entryPrice, double slPrice, double tpPrice, string reason, double atrValue,
double wprValue)
// Validate SL and TP levels before opening the position
if (!CheckStopLossAndTakeProfit(orderType, slPrice, tpPrice, entryPrice))
LogFailedTrade("Invalid SL/TP levels", 0, "SL/TP levels do not meet
broker requirements");
return false;
// Execute the trade with proper SL and TP
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
bool tradeResult = false;
ulong entryDealTicket = 0;
if (orderType == ORDER_TYPE_BUY)
{
tradeResult = trade.Buy(lotSize, NULL, entryPrice, slPrice, tpPrice,
"My EA Trade");
else
tradeResult = trade.Sell(lotSize, NULL, entryPrice, slPrice, tpPrice,
"My EA Trade");
if (tradeResult)
// Trade was successful, retrieve the deal ticket
entryDealTicket = trade.ResultDeal();
if (entryDealTicket == 0)
Print("Failed to get deal ticket after trade execution.");
return false;
// Log SL/TP values after trade execution
double executedSL = 0.0;
double executedTP = 0.0;
int retryCount = 0;
const int maxRetries = 5;
bool dealSelected = false;
// Retry to fetch SL/TP values, since they may not be immediately
available
while (retryCount < maxRetries)
if (HistoryDealSelect(entryDealTicket))
executedSL = HistoryDealGetDouble(entryDealTicket, DEAL_SL);
executedTP = HistoryDealGetDouble(entryDealTicket, DEAL_TP);
if (executedSL != 0.0 && executedTP != 0.0) // Ensure valid
values
dealSelected = true;
break;
Print("Waiting to retrieve SL/TP values...");
Sleep(200); // Wait 200ms before retrying
retryCount++;
if (!dealSelected)
Print("Failed to retrieve SL/TP values after retries.");
return false;
LogMessage("Executed SL=" + DoubleToString(executedSL, _Digits)
+ ", Executed TP=" + DoubleToString(executedTP, _Digits),
LOG_LEVEL_INFO);
// Check if executed SL/TP differs from calculated
if (NormalizeDouble(executedSL, _Digits) != NormalizeDouble(slPrice,
_Digits) ||
NormalizeDouble(executedTP, _Digits) !=
NormalizeDouble(tpPrice, _Digits))
LogMessage("ERROR: Broker executed different SL/TP than
expected.", LOG_LEVEL_ERROR);
// Retrieve the position ID from the deal
ulong positionID = HistoryDealGetInteger(entryDealTicket,
DEAL_POSITION_ID);
if (positionID == 0)
LogMessage("Failed to get position ID from deal.",
LOG_LEVEL_ERROR);
return false;
// Store the open position data
OpenPositionData newPosition;
newPosition.entryDealTicket = entryDealTicket;
newPosition.positionID = positionID;
newPosition.symbol = _Symbol;
newPosition.tradeType = (orderType == ORDER_TYPE_BUY) ? "Buy" :
"Sell";
newPosition.entryPrice = entryPrice;
newPosition.entryTime = TimeCurrent();
newPosition.atr = atrValue;
newPosition.wprValue = wprValue;
newPosition.lotSize = lotSize;
newPosition.reasonEntry = reason;
newPosition.date = TimeToString(newPosition.entryTime,
TIME_DATE);
newPosition.time = TimeToString(newPosition.entryTime,
TIME_SECONDS | TIME_MINUTES);
newPosition.currentStopLoss = slPrice;
newPosition.takeProfitPrice = tpPrice;
newPosition.profitLevelReached = 0;
newPosition.trailingStopActivated = false;
newPosition.profitLevelAtTrailingStop = 0.0;
newPosition.isLogged = false;
// Add the new position to the openPositions array
AddOpenPosition(newPosition);
// Debug logging
LogMessage("Opened position: positionID=" +
IntegerToString(positionID) + ", entryDealTicket=" +
IntegerToString(entryDealTicket) + ", symbol=" + _Symbol + ",
tradeType=" + newPosition.tradeType, LOG_LEVEL_INFO);
LogMessage("Current openPositions count: " +
IntegerToString(ArraySize(openPositions)), LOG_LEVEL_INFO);
// Update the last trade time to avoid overtrading
lastTradeTime = TimeCurrent();
// Set a flag to indicate a new trade was executed, requiring trailing
stop management
bool trailingStopPending = true;
return true;
else
// Log failed trade execution
uint errorCode = trade.ResultRetcode();
string errorDescription = trade.ResultRetcodeDescription();
LogFailedTrade("Trade Order Failed", errorCode, errorDescription);
return false;
//+------------------------------------------------------------------+
//| Adding Open Positions |
//+------------------------------------------------------------------+
void AddOpenPosition(OpenPositionData &positionData)
positionData.profitLevelReached = 0; // Initialize profitLevelReached
ArrayResize(openPositions, ArraySize(openPositions) + 1);
openPositions[ArraySize(openPositions) - 1] = positionData;
//+------------------------------------------------------------------+
//| Finding Open Positions |
//+------------------------------------------------------------------+
int FindOpenPositionIndex(ulong positionID)
for (int i = 0; i < ArraySize(openPositions); i++)
{
if (openPositions[i].positionID == positionID)
return i;
return -1; // Not found
//+------------------------------------------------------------------+
//| Removing Open Positions |
//+------------------------------------------------------------------+
void RemoveOpenPositionByIndex(int index)
if (index >= 0 && index < ArraySize(openPositions))
for (int i = index; i < ArraySize(openPositions) - 1; i++)
openPositions[i] = openPositions[i + 1];
ArrayResize(openPositions, ArraySize(openPositions) - 1);
//+------------------------------------------------------------------+
//| OnTradeTransaction Event Handler |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans, const
MqlTradeRequest &request, const MqlTradeResult &result)
{
if (trans.type == TRADE_TRANSACTION_DEAL_ADD)
ulong dealTicket = trans.deal;
if (HistoryDealSelect(dealTicket))
ulong positionID = HistoryDealGetInteger(dealTicket,
DEAL_POSITION_ID);
ENUM_DEAL_ENTRY dealEntry =
(ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if (dealEntry == DEAL_ENTRY_OUT || dealEntry ==
DEAL_ENTRY_OUT_BY)
// Add this check to prevent duplicate logging
if (IsPositionLogged(positionID))
LogMessage("Position ID " + IntegerToString(positionID) + "
has already been logged. Skipping.", LOG_LEVEL_DEBUG);
return;
LogMessage("Deal " + IntegerToString(dealTicket) + " is a
closing deal for position " + IntegerToString(positionID),
LOG_LEVEL_INFO, LOG_CAT_TRADE_EXECUTION);
// Log the closed trade
LogClosedTrade(positionID, dealTicket);
// Remove the position from openPositions after logging
int index = FindOpenPositionIndex(positionID);
if (index != -1)
RemoveOpenPositionByIndex(index);
LogMessage("Removed position from openPositions in
OnTradeTransaction: positionID=" + IntegerToString(positionID),
LOG_LEVEL_DEBUG, LOG_CAT_TRADE_EXECUTION);
//+------------------------------------------------------------------+
//| Check for Closed Positions |
//+------------------------------------------------------------------+
void CheckForClosedPositions()
// Ensure the history is up to date
HistorySelect(0, TimeCurrent());
int i = 0;
while (i < ArraySize(openPositions))
ulong positionID = openPositions[i].positionID;
// Check if the position is still open
if (!PositionSelectByTicket(positionID))
// Position is no longer open; it has been closed
LogMessage("Position " + IntegerToString(positionID) + " is closed.
Attempting to find closing deal.", LOG_LEVEL_DEBUG);
// Check if positionID has already been logged
if (IsPositionLogged(positionID))
LogMessage("Position ID " + IntegerToString(positionID) + " has
already been logged. Skipping.", LOG_LEVEL_DEBUG);
// Remove from openPositions
RemoveOpenPositionByIndex(i);
continue;
ulong closingDealTicket = FindClosingDealForPosition(positionID);
if (closingDealTicket != 0)
// Log the trade
LogClosedTrade(positionID, closingDealTicket);
// Remove from openPositions
RemoveOpenPositionByIndex(i);
LogMessage("Removed position from openPositions:
positionID=" + IntegerToString(positionID), LOG_LEVEL_DEBUG);
// Do not increment i, as the array has shrunk
continue;
else
{
LogMessage("Closing deal not found for position ID: " +
IntegerToString(positionID), LOG_LEVEL_WARNING);
i++; // Increment i to check the next position
else
LogMessage("Position " + IntegerToString(positionID) + " is still
open.", LOG_LEVEL_DEBUG);
i++; // Only increment if no removal
//+------------------------------------------------------------------+
//| Find Closing Deal for Position |
//+------------------------------------------------------------------+
ulong FindClosingDealForPosition(ulong positionID)
// Ensure the history is refreshed
HistorySelect(0, TimeCurrent());
// Get the number of deals in history
int totalDeals = HistoryDealsTotal();
for (int i = totalDeals - 1; i >= 0; i--)
ulong dealTicket = HistoryDealGetTicket(i);
if (HistoryDealSelect(dealTicket))
{
ulong dealPositionID = HistoryDealGetInteger(dealTicket,
DEAL_POSITION_ID);
if (dealPositionID == positionID)
ENUM_DEAL_ENTRY dealEntry =
(ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if (dealEntry == DEAL_ENTRY_OUT || dealEntry ==
DEAL_ENTRY_OUT_BY)
LogMessage("Found closing deal: dealTicket=" +
IntegerToString(dealTicket) + " for positionID=" +
IntegerToString(positionID), LOG_LEVEL_DEBUG);
return dealTicket;
else
LogMessage("Failed to select dealTicket=" +
IntegerToString(dealTicket), LOG_LEVEL_WARNING);
LogMessage("No closing deal found for positionID=" +
IntegerToString(positionID), LOG_LEVEL_WARNING);
return 0; // No closing deal found
//+------------------------------------------------------------------+
//| Log Closed Trade Function |
//+------------------------------------------------------------------+
void LogClosedTrade(ulong positionID, ulong closingDealTicket)
{
// Check if the positionID has already been logged
if (IsPositionLogged(positionID))
LogMessage("Position ID " + IntegerToString(positionID) + " has
already been logged. Skipping.", LOG_LEVEL_DEBUG);
return;
// Ensure the closing deal exists in history
if (!HistoryDealSelect(closingDealTicket))
Print("Failed to select closing deal ", closingDealTicket);
return;
// Attempt to find the open position data
int index = FindOpenPositionIndex(positionID);
TradeData tradeData;
if (index == -1)
// Entry data not found; reconstruct trade data from deals
PrintFormat("Open position data not found for position ID: %I64u",
positionID);
// Get the opening deal
ulong openingDealTicket = FindOpeningDealForPosition(positionID);
if (openingDealTicket == 0)
{
PrintFormat("Opening deal not found for position ID: %I64u",
positionID);
return;
if (!HistoryDealSelect(openingDealTicket))
PrintFormat("Failed to select opening deal %I64u",
openingDealTicket);
return;
datetime entryTime =
(datetime)HistoryDealGetInteger(openingDealTicket, DEAL_TIME);
tradeData.entryDate = TimeToString(entryTime, TIME_DATE);
tradeData.entryTime = TimeToString(entryTime, TIME_SECONDS |
TIME_MINUTES);
tradeData.entryPrice = HistoryDealGetDouble(openingDealTicket,
DEAL_PRICE);
tradeData.reasonEntry = "Unknown (Entry data not found)";
tradeData.lotSize = HistoryDealGetDouble(openingDealTicket,
DEAL_VOLUME);
tradeData.duration = (long)(HistoryDealGetInteger(closingDealTicket,
DEAL_TIME) - HistoryDealGetInteger(openingDealTicket, DEAL_TIME));
tradeData.tradeType = HistoryDealGetInteger(openingDealTicket,
DEAL_TYPE) == DEAL_TYPE_BUY ? "Buy" : "Sell";
tradeData.symbol = HistoryDealGetString(closingDealTicket,
DEAL_SYMBOL);
tradeData.exitPrice = HistoryDealGetDouble(closingDealTicket,
DEAL_PRICE);
datetime exitTime =
(datetime)HistoryDealGetInteger(closingDealTicket, DEAL_TIME);
tradeData.exitDate = TimeToString(exitTime, TIME_DATE);
tradeData.exitTime = TimeToString(exitTime, TIME_SECONDS |
TIME_MINUTES);
// Retrieve profit, swap, and commission from the closing deal
double dealProfit = HistoryDealGetDouble(closingDealTicket,
DEAL_PROFIT);
double dealSwap = HistoryDealGetDouble(closingDealTicket,
DEAL_SWAP);
double dealCommission = HistoryDealGetDouble(closingDealTicket,
DEAL_COMMISSION);
tradeData.profitLoss = dealProfit + dealSwap + dealCommission;
tradeData.swap = dealSwap;
tradeData.commission = dealCommission;
// Use GetReasonExitString to get user-friendly reason
ENUM_DEAL_REASON reason =
(ENUM_DEAL_REASON)HistoryDealGetInteger(closingDealTicket,
DEAL_REASON);
tradeData.reasonExit = GetReasonExitString(reason);
tradeData.remarks = "Logged without open position data";
// Log the completed trade
PrintFormat("Logging trade (no open position data): positionID=
%I64u, symbol=%s, tradeType=%s", positionID, tradeData.symbol,
tradeData.tradeType);
LogTrade(tradeData);
LogMessage("Trade closed (no open position data): positionID=" +
IntegerToString(positionID) +
", symbol=" + tradeData.symbol +
", tradeType=" + tradeData.tradeType +
", profitLoss=" + DoubleToString(tradeData.profitLoss, 2),
LOG_LEVEL_INFO, LOG_CAT_TRADE_EXECUTION);
return;
// Code for when open position data is found
// Retrieve existing open position data to log it correctly
OpenPositionData entryData = openPositions[index];
// Prepare trade data using stored entry data
tradeData.positionID = entryData.positionID; // Position ID from open
position data
tradeData.entryDate = entryData.date;
tradeData.entryTime = entryData.time;
tradeData.entryPrice = entryData.entryPrice;
tradeData.reasonEntry = entryData.reasonEntry;
tradeData.lotSize = entryData.lotSize;
tradeData.duration = (long)(HistoryDealGetInteger(closingDealTicket,
DEAL_TIME) - entryData.entryTime);
tradeData.symbol = HistoryDealGetString(closingDealTicket,
DEAL_SYMBOL);
tradeData.exitPrice = HistoryDealGetDouble(closingDealTicket,
DEAL_PRICE);
tradeData.tradeType = entryData.tradeType; // Fetch trade type
datetime exitTime =
(datetime)HistoryDealGetInteger(closingDealTicket, DEAL_TIME);
tradeData.exitDate = TimeToString(exitTime, TIME_DATE);
tradeData.exitTime = TimeToString(exitTime, TIME_SECONDS |
TIME_MINUTES);
// Log additional relevant data
tradeData.atr = entryData.atr; // ATR from open position
tradeData.wprValue = entryData.wprValue; // Williams %R from open
position
ENUM_DEAL_REASON reason =
(ENUM_DEAL_REASON)HistoryDealGetInteger(closingDealTicket,
DEAL_REASON);
tradeData.reasonExit = GetReasonExitString(reason);
// Retrieve profit, swap, and commission from the closing deal
double dealProfit = HistoryDealGetDouble(closingDealTicket,
DEAL_PROFIT);
double dealSwap = HistoryDealGetDouble(closingDealTicket,
DEAL_SWAP);
double dealCommission = HistoryDealGetDouble(closingDealTicket,
DEAL_COMMISSION);
tradeData.profitLoss = dealProfit + dealSwap + dealCommission;
tradeData.swap = dealSwap;
tradeData.commission = dealCommission;
tradeData.remarks = ""; // No additional remarks needed when open
data is found
ArrayResize(loggedPositionIDs, ArraySize(loggedPositionIDs) + 1);
loggedPositionIDs[ArraySize(loggedPositionIDs) - 1] = positionID;
// Log the completed trade with open position data found
PrintFormat("Logging trade with open position data: positionID=%I64u,
symbol=%s, tradeType=%s", positionID, tradeData.symbol,
tradeData.tradeType);
LogTrade(tradeData);
//+------------------------------------------------------------------+
//| Find Opening Deal for Position |
//+------------------------------------------------------------------+
ulong FindOpeningDealForPosition(ulong positionID)
// Ensure the history is refreshed
HistorySelect(0, TimeCurrent());
// Get the number of deals in history
int totalDeals = HistoryDealsTotal();
for (int i = totalDeals - 1; i >= 0; i--)
ulong dealTicket = HistoryDealGetTicket(i);
if (HistoryDealSelect(dealTicket))
ulong dealPositionID = HistoryDealGetInteger(dealTicket,
DEAL_POSITION_ID);
if (dealPositionID == positionID)
ENUM_DEAL_ENTRY dealEntry =
(ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if (dealEntry == DEAL_ENTRY_IN)
LogMessage("Found opening deal: dealTicket=" +
IntegerToString(dealTicket) + " for positionID=" +
IntegerToString(positionID), LOG_LEVEL_DEBUG);
return dealTicket;
else
LogMessage("Failed to select dealTicket=" +
IntegerToString(dealTicket), LOG_LEVEL_WARNING);
PrintFormat("No opening deal found for positionID=%I64u", positionID);
return 0; // No opening deal found
//+------------------------------------------------------------------+
//| Log Trade Function |
//+------------------------------------------------------------------+
void LogTrade(TradeData &tradeData)
// Sanitize strings for SQL
string sanitizedReasonEntry = tradeData.reasonEntry;
string sanitizedReasonExit = tradeData.reasonExit;
string sanitizedRemarks = tradeData.remarks;
StringReplace(sanitizedReasonEntry, "'", "''");
StringReplace(sanitizedReasonExit, "'", "''");
StringReplace(sanitizedRemarks, "'", "''");
// Prepare SQL INSERT statement without TradeID
string insertQuery = "INSERT INTO trades (EntryDate, EntryTime,
ExitDate, ExitTime, Symbol, TradeType, EntryPrice, ExitPrice, "
"ReasonEntry, ReasonExit, ProfitLoss, Swap, Commission,
ATR, WPRValue, Duration, LotSize, Remarks) VALUES (" +
"'" + tradeData.entryDate + "'," +
"'" + tradeData.entryTime + "'," +
"'" + tradeData.exitDate + "'," +
"'" + tradeData.exitTime + "'," +
"'" + tradeData.symbol + "'," +
"'" + tradeData.tradeType + "'," +
DoubleToString(tradeData.entryPrice, _Digits) + "," +
DoubleToString(tradeData.exitPrice, _Digits) + "," +
"'" + sanitizedReasonEntry + "'," +
"'" + sanitizedReasonExit + "'," +
DoubleToString(tradeData.profitLoss, 2) + "," +
DoubleToString(tradeData.swap, 2) + "," + // Include
swap
DoubleToString(tradeData.commission, 2) + "," + //
Include commission
DoubleToString(tradeData.atr, _Digits) + "," +
DoubleToString(tradeData.wprValue, 2) + "," +
IntegerToString(tradeData.duration) + "," +
DoubleToString(tradeData.lotSize, 2) + "," +
"'" + sanitizedRemarks + "');";
// Convert query to char array for ExecuteSQL
char query[4096];
StringToCharArray(insertQuery, query);
char errorMsg[256];
// Execute SQL without checking LogLevel
int execResult = ExecuteSQL(dbHandle, query, errorMsg,
sizeof(errorMsg));
if (execResult != 0)
PrintFormat("Error inserting trade log to database: %s",
CharArrayToString(errorMsg));
else
Print("Trade logged to database successfully.");
//+------------------------------------------------------------------+
//| Validate Indicator Value |
//+------------------------------------------------------------------+
bool IsValidValue(double value)
return MathIsValidNumber(value);
//+------------------------------------------------------------------+
//| Trading Hours Check |
//+------------------------------------------------------------------+
bool IsWithinTradingHours()
{
datetime currentTime = TimeCurrent();
MqlDateTime timeStruct;
TimeToStruct(currentTime, timeStruct);
// Exclude weekends (Saturday and Sunday)
if (timeStruct.day_of_week == 0 || timeStruct.day_of_week == 6)
return false;
return true;
//+------------------------------------------------------------------+
//| Execute Trade Function |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, double atrValue, string
reason, double wprValue)
double stopLossPoints, takeProfitPoints;
// Calculate SL/TP values based on ATR and risk-to-reward ratio
if (!CalculateSLTP(atrValue, stopLossPoints, takeProfitPoints))
LogFailedTrade("Failed to calculate valid SL/TP"); // Calls centralized
error logging
return;
// Calculate lot size based on risk level adjusted for volatility
double lotSize = CalculateLotSize(stopLossPoints, isHighVolatility ?
HighRisk : LowRisk);
// Get entry price based on order type
double entryPrice = (orderType == ORDER_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol,
SYMBOL_BID);
double slPrice = 0.0, tpPrice = 0.0;
// Calculate Stop Loss (SL) and Take Profit (TP) prices
if (orderType == ORDER_TYPE_BUY)
slPrice = NormalizeDouble(entryPrice - stopLossPoints * _Point,
_Digits);
tpPrice = NormalizeDouble(entryPrice + takeProfitPoints * _Point,
_Digits);
else if (orderType == ORDER_TYPE_SELL)
slPrice = NormalizeDouble(entryPrice + stopLossPoints * _Point,
_Digits);
tpPrice = NormalizeDouble(entryPrice - takeProfitPoints * _Point,
_Digits);
// Attempt to open the position using the calculated SL and TP
bool tradeSuccess = OpenPosition(orderType, lotSize, entryPrice,
slPrice, tpPrice, reason, atrValue, wprValue);
// Log success or failure of the trade execution
if (tradeSuccess)
{
// Increment the daily trade count
dailyTradeCount++;
LogMessage("Trade executed successfully. Daily trade count: " +
IntegerToString(dailyTradeCount), LOG_LEVEL_INFO);
// Centralized logging for trade execution details
LogMessage("Trade Executed: " + ((orderType == ORDER_TYPE_BUY)
? "Buy" : "Sell") +
" Entry=" + DoubleToString(entryPrice, _Digits) +
" SL=" + DoubleToString(slPrice, _Digits) +
" TP=" + DoubleToString(tpPrice, _Digits) +
" Lot=" + DoubleToString(lotSize, 2), LOG_LEVEL_INFO,
LOG_CAT_TRADE_EXECUTION);
else
LogMessage("Trade execution failed", LOG_LEVEL_ERROR,
LOG_CAT_ERRORS);
//+------------------------------------------------------------------+
//| SL/TP Calculation: SL based on ATR, TP based on Risk-to-Reward |
//+------------------------------------------------------------------+
bool CalculateSLTP(double atr, double &stopLossPoints, double
&takeProfitPoints)
double riskToRewardRatio = RiskToRewardRatio; // Use the input
parameter
// Step 1: Set Stop Loss based on ATR and volatility
if (isHighVolatility)
stopLossPoints = (atr * atrStopLossMultiplierHighVolatility) / _Point; //
SL for high volatility
else
stopLossPoints = (atr * atrStopLossMultiplierLowVolatility) / _Point; //
SL for low volatility
// Step 2: Calculate Take Profit using the Risk-to-Reward ratio
takeProfitPoints = stopLossPoints * riskToRewardRatio;
// Log the calculated SL/TP values
LogMessage("SL=" + DoubleToString(stopLossPoints, _Digits) +
", TP=" + DoubleToString(takeProfitPoints, _Digits),
LOG_LEVEL_DEBUG, LOG_CAT_SLTP_VALUES);
// Step 3: Validate SL/TP against broker's minimum stop level
double minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_STOPS_LEVEL);
if (minStopLevelPoints == 0)
minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_FREEZE_LEVEL);
// Adjust SL/TP if needed
if (stopLossPoints < minStopLevelPoints)
Print("Stop Loss is less than the minimum stop level, adjusting: ",
stopLossPoints, " to ", minStopLevelPoints);
stopLossPoints = minStopLevelPoints; // Adjust to meet broker’s
minimum stop level
// Recalculate Take Profit based on the new Stop Loss
takeProfitPoints = stopLossPoints * riskToRewardRatio;
PrintFormat("Adjusted TP based on new SL: %.5f", takeProfitPoints);
return true;
//+------------------------------------------------------------------+
//| Lot Size Calculation based on Risk Percentage |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPoints, double riskPercentage)
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = balance * riskPercentage / 100.0;
double tickSize = SymbolInfoDouble(_Symbol,
SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol,
SYMBOL_TRADE_TICK_VALUE);
// Calculate the value per point per lot
double pointValuePerLot = tickValue / tickSize;
// Calculate lot size
double lotSize = riskAmount / (stopLossPoints * _Point *
pointValuePerLot);
// Adjust for lot step and min/max volume
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
// Round down to nearest lot step
lotSize = MathFloor(lotSize / lotStep) * lotStep;
// Ensure lot size is within allowed limits
lotSize = MathMax(minLot, MathMin(lotSize, maxLot));
// Log risk management calculations
LogMessage("Risk Percentage: " + DoubleToString(riskPercentage, 2),
LOG_LEVEL_INFO, LOG_CAT_RISK_MANAGEMENT);
return lotSize;
//+------------------------------------------------------------------+
//| Check Stop Loss and Take Profit Levels |
//+------------------------------------------------------------------+
bool CheckStopLossAndTakeProfit(ENUM_ORDER_TYPE orderType, double
slPrice, double tpPrice, double entryPrice) {
double minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_STOPS_LEVEL);
if (minStopLevelPoints == 0)
minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_FREEZE_LEVEL);
double slDistance = 0.0;
double tpDistance = 0.0;
if (orderType == ORDER_TYPE_BUY) {
slDistance = (entryPrice - slPrice) / _Point;
tpDistance = (tpPrice - entryPrice) / _Point;
} else if (orderType == ORDER_TYPE_SELL) {
slDistance = (slPrice - entryPrice) / _Point;
tpDistance = (entryPrice - tpPrice) / _Point;
slDistance = MathAbs(slDistance);
tpDistance = MathAbs(tpDistance);
if (slDistance < minStopLevelPoints || tpDistance < minStopLevelPoints)
{
LogSLTPValidation(slPrice, tpPrice, minStopLevelPoints, orderType); //
Log SL/TP values if validation fails
LogError("SL or TP too close to price. Minimum stop level: " +
DoubleToString(minStopLevelPoints, _Digits));
return false;
LogMessage("SL=" + DoubleToString(slPrice, _Digits) + " and TP=" +
DoubleToString(tpPrice, _Digits) + " passed validation.", LOG_LEVEL_INFO,
LOG_CAT_SLTP_VALUES);
return true;
}
//+------------------------------------------------------------------+
//| Consolidate ATR Logging into LogMessage |
//+------------------------------------------------------------------+
double GetATR(int period) {
int atrHandle = iATR(_Symbol, ATRTimeframe, period); // Use
ATRTimeframe here
if (atrHandle == INVALID_HANDLE) {
LogMessage("Invalid ATR handle. Cannot retrieve ATR.",
LOG_LEVEL_ERROR, LOG_CAT_ERRORS);
return 0;
double atrValues[1];
if (CopyBuffer(atrHandle, 0, 1, 1, atrValues) <= 0) {
LogMessage("Failed to retrieve ATR values.", LOG_LEVEL_WARNING,
LOG_CAT_ERRORS);
return 0;
IndicatorRelease(atrHandle);
return atrValues[0];
//+------------------------------------------------------------------+
//| Helper Function to Get Indicator Value |
//+------------------------------------------------------------------+
double GetIndicatorValue(int handle, int bufferIndex = 0)
double value[1];
if (CopyBuffer(handle, bufferIndex, 1, 1, value) <= 0)
return 0.0;
else
return value[0];
//+------------------------------------------------------------------+
//| Log Failed Trade Function |
//+------------------------------------------------------------------+
void LogFailedTrade(string reason, uint errorCode = 0, string
errorDescription = "") {
datetime currentTime = TimeCurrent();
string date = TimeToString(currentTime, TIME_DATE);
string time = TimeToString(currentTime, TIME_SECONDS |
TIME_MINUTES);
string symbol = _Symbol;
// Sanitize and handle single quotes in reason
string sanitizedReason = SanitizeForSQL(reason);
// Fetch or generate the error code and description
if (errorCode == 0) {
errorCode = GetLastError();
errorDescription = ErrorDescription((int)errorCode);
ResetLastError();
// Use FormatErrorMessage to create the full reason message
string fullReason = sanitizedReason + " - " +
FormatErrorMessage(errorCode, errorDescription);
// Prepare the SQL query
string insertQuery = "INSERT INTO TradeLog (Date, Time, Symbol,
Remarks, ATR) VALUES ("
"'" + date + "',"
"'" + time + "',"
"'" + symbol + "',"
"'" + SanitizeForSQL(fullReason) + "',"
+ DoubleToString(GetATR(atrPeriod), _Digits) + ");";
// Execute the SQL query
char query[2048];
StringToCharArray(insertQuery, query);
char errorMsg[256];
// If the SQL execution fails, log the error in the console
int execResult = ExecuteSQL(dbHandle, query, errorMsg,
sizeof(errorMsg));
if (execResult != 0) {
PrintFormat("Error inserting failed trade log. Error: %s",
CharArrayToString(errorMsg));
//+------------------------------------------------------------------+
//| Error Description Function |
//+------------------------------------------------------------------+
string ErrorDescription(int errorCode)
switch(errorCode)
{
case 0: return "No error";
case 1: return "No result, but error is unknown";
case 2: return "Common error";
case 3: return "Invalid trade parameters";
case 4: return "Trade server busy";
case 5: return "Old version of the client terminal";
case 6: return "No connection with trade server";
case 7: return "Not enough rights";
case 8: return "Too frequent requests";
case 9: return "Malfunctional trade operation";
case 10: return "Invalid account";
case 11: return "Invalid price";
case 12: return "Invalid stops";
case 13: return "Invalid trade volume";
case 14: return "Market closed";
case 15: return "Trade disabled";
case 16: return "Not enough money";
case 17: return "Price changed";
// More cases can be added as needed...
default: return "Unknown or custom error code"; // Default for
unexpected error codes
//+------------------------------------------------------------------+
//| Get Reason Exit String |
//+------------------------------------------------------------------+
string GetReasonExitString(ENUM_DEAL_REASON reason)
{
switch(reason)
case DEAL_REASON_SL:
return "Stop Loss Hit";
case DEAL_REASON_TP:
return "Take Profit Hit";
case DEAL_REASON_SO:
return "Stop Out";
case DEAL_REASON_CLIENT:
return "Closed Manually";
case DEAL_REASON_EXPERT:
return "Closed by Expert";
// Add other cases as needed
default:
return "Unknown Reason";
//+------------------------------------------------------------------+
//| Export Trade Log to Excel |
//+------------------------------------------------------------------+
void ExportTradeLogToExcel()
// Ensure that logs are flushed to the database before exporting
FlushLogsToDatabase();
// Define the CSV file path
string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\
Files";
string csvFilePath = filesDir + "\\TradeLog.csv";
char csvFile[512];
StringToCharArray(csvFilePath, csvFile);
// Error message buffer
char errorMsg[256];
ZeroMemory(errorMsg);
// Check if database handle is valid
if (dbHandle == 0)
Print("Database handle is invalid. Cannot export trade logs.");
return;
// Call the ExportTradeLogsToCSV function
int result = ExportTradeLogsToCSV(dbHandle, csvFile, errorMsg,
sizeof(errorMsg));
if (result != 0)
PrintFormat("Error exporting trade logs to CSV. Error: %s",
CharArrayToString(errorMsg));
return;
else
Print("Trade logs exported to CSV successfully.");
}
// Open the CSV file using ShellExecute
bool openResult = OpenCSVFileInExcel(csvFilePath);
if (!openResult)
Print("Failed to open the CSV file in Excel.");
//+------------------------------------------------------------------+
//| Open CSV File in Excel |
//+------------------------------------------------------------------+
bool OpenCSVFileInExcel(string csvFilePath)
// Use ShellExecute to open the CSV file with the default associated
program (Excel)
int res = ShellExecuteW(0, "open", csvFilePath, "", "", 1);
return (res > 32);
//+------------------------------------------------------------------+
//| Manage Trailing Stop to Lock in Profits based on percentages |
//+------------------------------------------------------------------+
void ManageTrailingStop(ulong positionID, double entryPrice, double
takeProfitPrice, double riskPercentage)
// Ensure trailing stop is enabled
if (!EnableTrailingStop)
LogMessage("Trailing stop is disabled.", LOG_LEVEL_INFO);
return;
// Select the position by ticket
if (!PositionSelectByTicket(positionID))
LogMessage("Failed to select position with ID " +
IntegerToString(positionID), LOG_LEVEL_ERROR, LOG_CAT_ERRORS);
return; // Do not remove the position here
// Find the position index
int index = FindOpenPositionIndex(positionID);
if (index == -1)
PrintFormat("Position %I64u not found in openPositions.", positionID);
return;
long tradeType = PositionGetInteger(POSITION_TYPE); // Buy or Sell
double currentSL = PositionGetDouble(POSITION_SL); // Current Stop
Loss
// Get the current price based on the position type
double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double currentPriceForCalc = (tradeType == POSITION_TYPE_BUY) ?
currentBid : currentAsk;
// Calculate total profit target and current profit achieved
double totalProfitTarget = 0.0;
double profitAchieved = 0.0;
if (tradeType == POSITION_TYPE_BUY)
totalProfitTarget = takeProfitPrice - entryPrice;
if (totalProfitTarget <= 0)
LogMessage("Total profit target is zero or negative; cannot
calculate profit achieved.", LOG_LEVEL_WARNING);
return;
profitAchieved = currentPriceForCalc - entryPrice;
else if (tradeType == POSITION_TYPE_SELL)
totalProfitTarget = entryPrice - takeProfitPrice;
if (totalProfitTarget <= 0)
Print("Total profit target is zero or negative, cannot calculate profit
achieved.");
return;
profitAchieved = entryPrice - currentPriceForCalc;
double currentProfitPercentage = (profitAchieved / totalProfitTarget) *
100.0;
// Log the percentage of the profit target achieved
LogMessage("Current profit target achieved: " +
DoubleToString(currentProfitPercentage, 2) + "%", LOG_LEVEL_DEBUG);
// Define profit levels
double profitLevels[] = {ProfitLevel1, ProfitLevel2, ProfitLevel3,
ProfitLevel4, ProfitLevel5};
int numLevels = ArraySize(profitLevels);
// Use profitLevelReached from the position data
int profitLevelReached = openPositions[index].profitLevelReached;
for (int i = 0; i < numLevels; i++)
if (currentProfitPercentage >= profitLevels[i] && profitLevelReached
< (i + 1))
double desiredSL = 0.0;
// Move SL to lock in profits based on profit levels
if (tradeType == POSITION_TYPE_BUY)
// Calculate desired SL price
desiredSL = entryPrice + ((profitLevels[i] / 100.0) *
totalProfitTarget) * 0.5; // Lock in 50% of the profit at this level
desiredSL = MathMax(desiredSL, entryPrice); // Ensure SL is at
least at entry price
desiredSL = NormalizeDouble(desiredSL, _Digits);
else if (tradeType == POSITION_TYPE_SELL)
{
desiredSL = entryPrice - ((profitLevels[i] / 100.0) *
totalProfitTarget) * 0.5; // Lock in 50% of the profit at this level
desiredSL = MathMin(desiredSL, entryPrice); // Ensure SL is at
least at entry price
desiredSL = NormalizeDouble(desiredSL, _Digits);
LogMessage("Profit Target: " +
DoubleToString(currentProfitPercentage, 2) + "% achieved, Adjusting SL to
" + DoubleToString(desiredSL, _Digits), LOG_LEVEL_DEBUG);
// Try to modify the SL, adjusting if necessary due to broker
constraints
if (ModifyPositionSL(positionID, desiredSL, tradeType))
openPositions[index].currentStopLoss = desiredSL; // Update
current stop loss in the position data
openPositions[index].profitLevelReached = i + 1; // Update profit
level only if SL modification is successful
// Set trailing stop activation
openPositions[index].trailingStopActivated = true;
openPositions[index].profitLevelAtTrailingStop =
profitLevels[i]; // Record the profit level
LogMessage("Successfully modified SL for position ID " +
IntegerToString(positionID) + " to " + DoubleToString(desiredSL, _Digits),
LOG_LEVEL_DEBUG);
else
// If unable to set SL to desired level, set it as close as possible
double adjustedSL = AdjustSLToBrokerLimits(desiredSL,
tradeType);
if (MathAbs(adjustedSL - currentSL) > (_Point * 0.1)) // Check if
there is an actual change
if (ModifyPositionSL(positionID, adjustedSL, tradeType, true))
openPositions[index].currentStopLoss = adjustedSL; //
Update current stop loss in the position data
openPositions[index].profitLevelReached = i + 1; // Update
profit level only if SL modification is successful
// Set trailing stop activation
openPositions[index].trailingStopActivated = true;
openPositions[index].profitLevelAtTrailingStop =
profitLevels[i]; // Record the profit level
LogMessage("Adjusted SL for position ID " +
IntegerToString(positionID) + " to closest allowed level " +
DoubleToString(adjustedSL, _Digits), LOG_LEVEL_INFO);
else
LogMessage("Failed to modify SL for position ID " +
IntegerToString(positionID) + " even after adjustment",
LOG_LEVEL_WARNING);
// Do not update profitLevelReached so the EA can retry
else
{
LogMessage("SL is already at the closest possible level. No
modification made.", LOG_LEVEL_INFO);
// Update profitLevelReached to prevent repeated attempts
openPositions[index].profitLevelReached = i + 1;
break; // Only attempt once per tick
//+------------------------------------------------------------------+
//| Draw Trailing Stop Line |
//+------------------------------------------------------------------+
void DrawTrailingStop(ulong positionID, double newSL)
string objName = "TrailingStop_" + IntegerToString((int)positionID);
if(ObjectFind(0, objName) < 0)
ObjectCreate(0, objName, OBJ_HLINE, 0, 0, newSL);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
else
ObjectSetDouble(0, objName, OBJPROP_PRICE, newSL);
}
//+------------------------------------------------------------------+
//| Modify Position Stop Loss and Preserve TP |
//+------------------------------------------------------------------+
bool ModifyPositionSL(ulong positionID, double desiredSL, long tradeType,
bool isAdjusted = false)
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
if (!PositionSelectByTicket(positionID))
PrintFormat("ModifyPositionSL: Failed to select position ID %I64u.",
positionID);
return false;
double currentTP = PositionGetDouble(POSITION_TP); // Preserve the
current TP
double currentPrice = (tradeType == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol,
SYMBOL_ASK);
double currentSL = PositionGetDouble(POSITION_SL); // Get the
current SL
// Get the minimum stop level in points
double minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_STOPS_LEVEL);
if (minStopLevelPoints == 0)
minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_FREEZE_LEVEL);
double minStopDistance = minStopLevelPoints * _Point;
double newSL = desiredSL;
// Adjust newSL to account for the minimum stop distance
if (tradeType == POSITION_TYPE_BUY)
double minValidSL = currentPrice - minStopDistance;
if (newSL >= minValidSL)
if (!isAdjusted)
// Cannot set SL to desired level due to broker constraints
return false;
else
// Already adjusted, accept the newSL
newSL = minValidSL - (_Point * 0.1); // Slightly more to ensure it
passes
if (newSL <= currentSL)
LogMessage("New SL is not better than the current SL for BUY
position.", LOG_LEVEL_WARNING);
return false;
else if (tradeType == POSITION_TYPE_SELL)
{
double maxValidSL = currentPrice + minStopDistance;
if (newSL <= maxValidSL)
if (!isAdjusted)
// Cannot set SL to desired level due to broker constraints
return false;
else
// Already adjusted, accept the newSL
newSL = maxValidSL + (_Point * 0.1); // Slightly more to ensure
it passes
if (newSL >= currentSL)
LogMessage("New SL is not better than the current SL for SELL
position.", LOG_LEVEL_WARNING);
return false;
// Normalize the newSL
newSL = NormalizeDouble(newSL, _Digits);
// Prepare trade request
request.action = TRADE_ACTION_SLTP;
request.position = positionID;
request.symbol = _Symbol;
request.sl = newSL;
request.tp = currentTP; // Preserve the existing TP
request.magic = MagicNumber;
// Send the trade request
if (!OrderSend(request, result))
PrintFormat("Failed to modify SL for position ID %I64u. Retcode: %d,
Description: %s", positionID, result.retcode,
ErrorDescription((int)result.retcode));
return false;
PrintFormat("Successfully modified SL for position ID %I64u to %.5f",
positionID, newSL);
return true;
//+------------------------------------------------------------------+
//| Centralized logging function with category and level filtering |
//+------------------------------------------------------------------+
void LogMessage(string message, LogLevelEnum messageLevel =
LOG_LEVEL_INFO, uint category = LOG_CAT_OTHER) {
// Check if the message level is within the set LogLevel
if (LogLevel == LOG_LEVEL_NONE || messageLevel > LogLevel) return;
// Check if the category is enabled using bitwise AND
if ((LogCategories & category) == 0) return;
// Prepare the log message with text labels
string levelLabel, symbol;
switch (messageLevel) {
case LOG_LEVEL_ERROR: levelLabel = "ERROR: "; symbol = "?? ";
break;
case LOG_LEVEL_WARNING: levelLabel = "WARNING: "; symbol = "??
"; break;
case LOG_LEVEL_INFO: levelLabel = "INFO: "; symbol = "?? "; break;
case LOG_LEVEL_DEBUG: levelLabel = "DEBUG: "; symbol = "?? ";
break;
string logMessage = symbol + levelLabel + message;
// Log to terminal
Print(logMessage);
// Log to database
LogToDatabase(message, messageLevel, category);
//+------------------------------------------------------------------+
//| Check Volatility Function |
//+------------------------------------------------------------------+
void CheckVolatility(double atrValue) {
if (atrValue >= atrVolatilityThreshold) {
isHighVolatility = true;
// Optionally, log only state changes
if (!wasHighVolatility) {
LogMessage("Volatility changed to High. ATR: " +
DoubleToString(atrValue, _Digits), LOG_LEVEL_DEBUG, LOG_CAT_ATR);
} else {
isHighVolatility = false;
if (wasHighVolatility) {
LogMessage("Volatility changed to Low. ATR: " +
DoubleToString(atrValue, _Digits), LOG_LEVEL_DEBUG, LOG_CAT_ATR);
wasHighVolatility = isHighVolatility;
//+------------------------------------------------------------------+
//| Timer event function |
//+------------------------------------------------------------------+
void OnTimer()
// Close trades before end of day to avoid swaps
if (CloseTradesBeforeEndOfDay && IsTimeToCloseTrades())
LogMessage("End of day detected on timer. Closing all positions to
avoid swaps.", LOG_LEVEL_INFO);
CloseAllPositions();
return; // Skip further processing if positions are closed
// Flush logs to database
FlushLogsToDatabase();
}
//+------------------------------------------------------------------+
//| Adjust SL to the closest allowed level according to broker limits |
//+------------------------------------------------------------------+
double AdjustSLToBrokerLimits(double desiredSL, long tradeType)
double currentPrice = (tradeType == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol,
SYMBOL_ASK);
// Get the minimum stop level in points
double minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_STOPS_LEVEL);
if (minStopLevelPoints == 0)
minStopLevelPoints = (double)SymbolInfoInteger(_Symbol,
SYMBOL_TRADE_FREEZE_LEVEL);
double minStopDistance = minStopLevelPoints * _Point;
double adjustedSL = desiredSL;
if (tradeType == POSITION_TYPE_BUY)
double minValidSL = currentPrice - minStopDistance;
adjustedSL = MathMin(desiredSL, minValidSL - (_Point * 0.1)); //
Slightly more to ensure it passes
adjustedSL = NormalizeDouble(adjustedSL, _Digits);
else if (tradeType == POSITION_TYPE_SELL)
double maxValidSL = currentPrice + minStopDistance;
adjustedSL = MathMax(desiredSL, maxValidSL + (_Point * 0.1)); //
Slightly more to ensure it passes
adjustedSL = NormalizeDouble(adjustedSL, _Digits);
return adjustedSL;
//+------------------------------------------------------------------+
//| Calculate Pivot Points |
//+------------------------------------------------------------------+
void CalculatePivotPoints(double &pivotPoint, double &support1, double
&resistance1, double &support2, double &resistance2)
// Use PivotTimeframe here
double high = iHigh(_Symbol, PivotTimeframe, 1);
double low = iLow(_Symbol, PivotTimeframe, 1);
double close = iClose(_Symbol, PivotTimeframe, 1);
if (high == 0 || low == 0 || close == 0) {
LogMessage("Failed to retrieve valid pivot point data.",
LOG_LEVEL_WARNING);
pivotPoint = support1 = resistance1 = support2 = resistance2 = 0.0;
return;
pivotPoint = (high + low + close) / 3.0;
resistance1 = (2 * pivotPoint) - low;
support1 = (2 * pivotPoint) - high;
resistance2 = pivotPoint + (high - low);
support2 = pivotPoint - (high - low);
}
//+------------------------------------------------------------------+
//| Check if Tick Volume is Increasing |
//+------------------------------------------------------------------+
bool IsTickVolumeIncreasing()
if (!EnableTickVolumeFilter)
LogMessage("Tick Volume Filter is disabled.", LOG_LEVEL_INFO);
return true; // If the filter is disabled, always return true
int periods = TickVolumePeriods; // Use input parameter
long volumes[]; // Array to store tick volumes
int copied = CopyTickVolume(_Symbol, TickVolumeTimeframe, 0,
periods + 1, volumes);
if (copied <= periods)
LogMessage("Failed to copy tick volume data or insufficient data.",
LOG_LEVEL_WARNING);
return false;
// Current period's volume is volumes[0], previous volumes are
volumes[1..periods]
// Calculate the average volume of previous periods
long avgVolume = 0;
for (int i = 1; i <= periods; i++)
{
avgVolume += volumes[i];
avgVolume /= periods;
// Calculate threshold
double threshold = avgVolume * TickVolumeMultiplier;
// Debug log for tick volume data
LogMessage("Current Volume: " + IntegerToString(volumes[0]) + ",
Average Volume: " + IntegerToString(avgVolume) + ", Threshold: " +
DoubleToString(threshold, 2), LOG_LEVEL_DEBUG);
// Return true if current volume is greater than threshold
if (volumes[0] > threshold)
LogMessage("Tick volume is increasing.", LOG_LEVEL_INFO);
return true;
else
LogMessage("Tick volume is not increasing.", LOG_LEVEL_INFO);
return false;
//+------------------------------------------------------------------+
//| Logging Info |
//+------------------------------------------------------------------+
void LogSLTPValidation(double sl, double tp, double minStopLevel,
ENUM_ORDER_TYPE orderType) {
string slType = (orderType == ORDER_TYPE_BUY) ? "Buy" : "Sell";
LogMessage("SL=" + DoubleToString(sl, _Digits) + ", TP=" +
DoubleToString(tp, _Digits), LOG_LEVEL_DEBUG, LOG_CAT_SLTP_VALUES);
bool IsLogEnabled(LogLevelEnum level) {
return (level <= LogLevel);
//+------------------------------------------------------------------+
//| Unified Error Logging Function |
//+------------------------------------------------------------------+
void LogError(string message, int errorCode = -1, bool
includeErrorDescription = true) {
string fullMessage = "ERROR: " + message;
if (errorCode >= 0) {
string errorDescription = includeErrorDescription ?
ErrorDescription(errorCode) : "";
fullMessage += " - " + FormatErrorMessage(errorCode,
errorDescription);
LogMessage(fullMessage, LOG_LEVEL_ERROR, LOG_CAT_ERRORS);
//+------------------------------------------------------------------+
//| Helper Function to Sanitize Strings for SQL Queries |
//+------------------------------------------------------------------+
string SanitizeForSQL(string text) {
StringReplace(text, "'", "''"); // Replace single quotes with two single
quotes for SQL compatibility
return text;
string FormatErrorMessage(int errorCode, string errorDescription) {
string formattedMessage = "Error Code: " + IntegerToString(errorCode);
if (errorDescription != "") {
formattedMessage += " - " + errorDescription;
return formattedMessage;
//+------------------------------------------------------------------+
//| Log to database with respect to input settings |
//+------------------------------------------------------------------+
void LogToDatabase(string message, LogLevelEnum logLevel, uint
category) {
// Check if database logging is enabled
if (!EnableDatabaseLogging) return;
// Sanitize message to remove special symbols
message = SanitizeForSQL(message);
// Get current date and time
string date = TimeToString(TimeCurrent(), TIME_DATE);
string time = TimeToString(TimeCurrent(), TIME_SECONDS |
TIME_MINUTES);
// Prepare log level and category strings
string logLevelStr = GetLogLevelString(logLevel);
string categoryStr = GetCategoryString(category);
// Create a new LogEntry
LogEntry entry;
entry.date = date;
entry.time = time;
entry.logLevel = logLevelStr;
entry.category = categoryStr;
entry.message = message;
// Add to the array
ArrayResize(logEntries, ArraySize(logEntries) + 1);
logEntries[ArraySize(logEntries) - 1] = entry;
//+------------------------------------------------------------------+
//| Helper Function to convert log level enum to string |
//+------------------------------------------------------------------+
string GetLogLevelString(LogLevelEnum logLevel) {
switch (logLevel) {
case LOG_LEVEL_ERROR: return "ERROR";
case LOG_LEVEL_WARNING: return "WARNING";
case LOG_LEVEL_INFO: return "INFO";
case LOG_LEVEL_DEBUG: return "DEBUG";
default: return "UNKNOWN";
}
//+------------------------------------------------------------------+
//| Helper Function to convert log category enum to string |
//+------------------------------------------------------------------+
string GetCategoryString(uint category) {
string categories = "";
if ((category & LOG_CAT_ATR) != 0) {
categories += "ATR|";
if ((category & LOG_CAT_TRADE_EXECUTION) != 0) {
categories += "TradeExecution|";
if ((category & LOG_CAT_SLTP_VALUES) != 0) {
categories += "SLTPValues|";
if ((category & LOG_CAT_RISK_MANAGEMENT) != 0) {
categories += "RiskManagement|";
if ((category & LOG_CAT_ERRORS) != 0) {
categories += "Errors|";
if ((category & LOG_CAT_OTHER) != 0) {
categories += "Other|";
// Remove the trailing '|'
if (StringLen(categories) > 0) {
categories = StringSubstr(categories, 0, StringLen(categories) - 1);
} else {
categories = "None";
return categories;
//+------------------------------------------------------------------+
//| Flush logs to database |
//+------------------------------------------------------------------+
void FlushLogsToDatabase()
// Declare variables
char errorMsg[256];
char query[4096];
char beginTransactionQuery[256];
char commitTransactionQuery[256];
// Convert strings to char arrays
StringToCharArray("BEGIN TRANSACTION;", beginTransactionQuery);
StringToCharArray("COMMIT;", commitTransactionQuery);
// Begin transaction
int execResult = ExecuteSQL(dbHandle, beginTransactionQuery,
errorMsg, sizeof(errorMsg));
if (execResult != 0)
PrintFormat("Error starting transaction: %s",
CharArrayToString(errorMsg));
return;
}
// Write each log entry
for (int i = 0; i < ArraySize(logEntries); i++)
LogEntry entry = logEntries[i];
// Construct SQL query
string insertLogQuery = StringFormat(
"INSERT INTO LogEntries (Date, Time, LogLevel, Category,
Message) VALUES ('%s', '%s', '%s', '%s', '%s');",
entry.date, entry.time, entry.logLevel, entry.category,
SanitizeForSQL(entry.message)
);
// Convert insertLogQuery to char array
StringToCharArray(insertLogQuery, query);
// Execute SQL query
execResult = ExecuteSQL(dbHandle, query, errorMsg,
sizeof(errorMsg));
if (execResult != 0)
PrintFormat("Error executing query: %s",
CharArrayToString(errorMsg));
// Optionally, handle the error (e.g., retry, log to file, etc.)
// Commit transaction
execResult = ExecuteSQL(dbHandle, commitTransactionQuery,
errorMsg, sizeof(errorMsg));
if (execResult != 0)
PrintFormat("Error committing transaction: %s",
CharArrayToString(errorMsg));
// Clear the array
ArrayResize(logEntries, 0);
//+------------------------------------------------------------------+
//| Process logic every 100 ticks |
//+------------------------------------------------------------------+
void ProcessTickChartLogic()
// Fetch the current ATR value + skip trading if ATR is below the
minimum threshold
double atrValue = GetATR(atrPeriod);
if (atrValue < atrMinThreshold)
LogMessage("ATR value below minimum threshold. No trade will be
placed. ATR: " + DoubleToString(atrValue, _Digits), LOG_LEVEL_WARNING,
LOG_CAT_ATR);
return;
// Check and log volatility status
CheckVolatility(atrValue);
{
LogMessage("ATR=" + DoubleToString(atrValue, _Digits) + ",
Volatility=" + (isHighVolatility ? "High" : "Low"), LOG_LEVEL_DEBUG,
LOG_CAT_ATR);
// Ensure trades are only processed within trading hours
if (!IsWithinTradingHours())
LogMessage("Outside trading hours.", LOG_LEVEL_WARNING);
return;
// Fetch Williams %R value for the current forming bar (index 0)
double wprValue[1];
LogMessage("Williams %R Value (Index 1): " +
DoubleToString(wprValue[0], 2), LOG_LEVEL_INFO);
if (CopyBuffer(wprHandle, 0, 1, 1, wprValue) <= 0)
LogMessage("Failed to retrieve valid Williams %R value for previous
bar.", LOG_LEVEL_WARNING);
LogFailedTrade("Williams %R Indicator Failure");
return;
wprValueGlobal = wprValue[0];
LogMessage("Williams %R Value: " + DoubleToString(wprValue[0], 2),
LOG_LEVEL_INFO);
}
bool buySignal = false, sellSignal = false;
double pivotPoint, support1, resistance1, support2, resistance2;
CalculatePivotPoints(pivotPoint, support1, resistance1, support2,
resistance2);
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// Debug log
LogMessage("Debug Info: WPR: " + DoubleToString(wprValueGlobal, 2)
+ " | CurrentPrice: " + DoubleToString(currentPrice, _Digits) + " |
Support1: " + DoubleToString(support1, _Digits) + " | Resistance1: " +
DoubleToString(resistance1, _Digits), LOG_LEVEL_DEBUG);
if (wprValueGlobal <= WPR_Oversold &&
currentPrice <= support1 * 1.002)
buySignal = true;
LogMessage("Buy Signal detected!", LOG_LEVEL_INFO,
LOG_CAT_TRADE_EXECUTION);
if (wprValueGlobal >= WPR_Overbought &&
currentPrice >= resistance1 * 0.998)
sellSignal = true;
LogMessage("Sell Signal detected!", LOG_LEVEL_INFO,
LOG_CAT_TRADE_EXECUTION);
}
if (!buySignal && !sellSignal)
LogMessage("No buy or sell signal triggered on this bar.",
LOG_LEVEL_DEBUG);
return;
if (TimeCurrent() - lastTradeTime < cooldownTime)
LogMessage("Overtrading protection activated. Waiting before
placing new trades.", LOG_LEVEL_WARNING);
return;
// Check if tick volume is increasing
bool tickVolumeIncreasing = IsTickVolumeIncreasing();
if (buySignal && tickVolumeIncreasing)
LogMessage("Executing Buy trade based on ATR, Williams %R, and
increasing tick volume.", LOG_LEVEL_INFO, LOG_CAT_TRADE_EXECUTION);
ExecuteTrade(ORDER_TYPE_BUY, atrValue, "ATR, WPR, and Tick
Volume Signal", wprValueGlobal);
else if (buySignal)
LogMessage("Buy signal detected but tick volume is not increasing.
Skipping trade.", LOG_LEVEL_INFO);
}
if (sellSignal && tickVolumeIncreasing)
LogMessage("Executing Sell trade based on ATR, Williams %R, and
increasing tick volume.", LOG_LEVEL_INFO, LOG_CAT_TRADE_EXECUTION);
ExecuteTrade(ORDER_TYPE_SELL, atrValue, "ATR, WPR, and Tick
Volume Signal", wprValueGlobal);
else if (sellSignal)
LogMessage("Sell signal detected but tick volume is not increasing.
Skipping trade.", LOG_LEVEL_INFO);
bool IsPositionLogged(ulong positionID)
for (int i = 0; i < ArraySize(loggedPositionIDs); i++)
if (loggedPositionIDs[i] == positionID)
return true;
return false;
//+------------------------------------------------------------------+
//| Find Last Deal for Position |
//+------------------------------------------------------------------+
ulong FindLastDealForPosition(ulong positionID)
// Ensure the history is refreshed
HistorySelect(0, TimeCurrent());
// Get the number of deals in history
int totalDeals = HistoryDealsTotal();
for (int i = 0; i < totalDeals; i++)
ulong dealTicket = HistoryDealGetTicket(i);
if (HistoryDealSelect(dealTicket))
ulong dealPositionID = HistoryDealGetInteger(dealTicket,
DEAL_POSITION_ID);
if (dealPositionID == positionID)
return dealTicket;
return 0; // No deal found
//+------------------------------------------------------------------+
//| Check if it's time to close trades before end of day |
//+------------------------------------------------------------------+
bool IsTimeToCloseTrades()
datetime currentTime = TimeCurrent();
MqlDateTime timeStruct;
TimeToStruct(currentTime, timeStruct);
if (timeStruct.hour > CloseTradesHour || (timeStruct.hour ==
CloseTradesHour && timeStruct.min >= CloseTradesMinute))
return true;
return false;
//+------------------------------------------------------------------+
//| Close all open positions |
//+------------------------------------------------------------------+
void CloseAllPositions()
int totalPositions = PositionsTotal();
for (int i = totalPositions - 1; i >= 0; i--)
ulong ticket = PositionGetTicket(i);
if (PositionSelectByTicket(ticket))
ulong positionID = PositionGetInteger(POSITION_IDENTIFIER);
long tradeType = PositionGetInteger(POSITION_TYPE);
double volume = PositionGetDouble(POSITION_VOLUME);
// Close the position
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
bool result = false;
if (tradeType == POSITION_TYPE_BUY)
{
result = trade.PositionClose(ticket);
else if (tradeType == POSITION_TYPE_SELL)
result = trade.PositionClose(ticket);
if (result)
LogMessage("Closed position ID " + IntegerToString(positionID)
+ " due to end of day.", LOG_LEVEL_INFO);
else
LogMessage("Failed to close position ID " +
IntegerToString(positionID) + ". Error: " +
trade.ResultRetcodeDescription(), LOG_LEVEL_ERROR);
//+------------------------------------------------------------------+
//| Determine Trend Direction |
//+------------------------------------------------------------------+
int GetTrendDirection()
// Get the last closed candle index
int lastClosedIndex = 1;
// Get the moving average handle
int maHandle = iMA(_Symbol, TrendTimeframe, TrendMAPeriod, 0,
TrendMAMethod, PRICE_CLOSE);
if (maHandle == INVALID_HANDLE)
LogMessage("Failed to create MA handle for trend detection.",
LOG_LEVEL_ERROR);
return 0; // Neutral trend if failed
// Copy the MA value
double maValue[1];
if (CopyBuffer(maHandle, 0, lastClosedIndex, 1, maValue) <= 0)
LogMessage("Failed to retrieve MA value for trend detection.",
LOG_LEVEL_ERROR);
IndicatorRelease(maHandle);
return 0; // Neutral trend if failed
// Get the close price of the last closed candle
double closePrice = iClose(_Symbol, TrendTimeframe, lastClosedIndex);
// Release the indicator handle
IndicatorRelease(maHandle);
if (closePrice > maValue[0])
LogMessage("Trend is UP.", LOG_LEVEL_DEBUG);
return 1; // Uptrend
else if (closePrice < maValue[0])
LogMessage("Trend is DOWN.", LOG_LEVEL_DEBUG);
return -1; // Downtrend
else
LogMessage("Trend is NEUTRAL.", LOG_LEVEL_DEBUG);
return 0; // Neutral