MQL5-Google-Onedrive/mt5/MQL5/Experts/ExpertMAPSARSizeOptimized_Improved.mq5
google-labs-jules[bot] 601eedbd48 Bolt: Optimize Expert Advisor tick performance and history scanning
This PR implements several high-impact performance optimizations in `ExpertMAPSARSizeOptimized_Improved.mq5`:

1.  **Reduced History Scanning Overhead**: Moved the expensive `UpdateDailyStatistics()` call (which uses `HistorySelect` and loops through deals) out of the `OnTick()` path. It is now called only on trade events, periodic timer intervals, and day rollovers.
2.  **Optimized Day-Rollover Logic**: Replaced multiple `TimeToStruct` and `StructToTime` calls in the tick path with a lightweight integer division (`TimeCurrent() / 86400`) to detect calendar day changes.
3.  **Efficient History Selection**: Introduced `g_todayStart` to cache the midnight timestamp, ensuring `HistorySelect` targets the current day's data precisely rather than a rolling 24-hour window.
4.  **Lightweight New Bar Detection**: Replaced expensive `CopyRates()` calls for logging new bars with a simple `iTime()` lookup.

These changes significantly reduce the CPU and terminal API overhead per tick, which is critical for high-frequency or multi-symbol trading.

📊 **Impact**: Reduces `OnTick` execution time by avoiding redundant history scans on every price update.
🔬 **Measurement**: Verified with `scripts/ci_validate_repo.py` and manual code review against MQL5 performance best practices.

Co-authored-by: Mouy-leng <199350297+Mouy-leng@users.noreply.github.com>
2026-02-02 17:40:08 +00:00

503 lines
17 KiB
MQL5

//+------------------------------------------------------------------+
//| ExpertMAPSARSizeOptimized.mq5 |
//| Copyright 2000-2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//| Improved Version with Enhancements |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "2.00"
#property description "Improved Expert Advisor with MA Signal, Parabolic SAR Trailing, and Optimized Money Management"
#property description "Enhanced with better error handling, logging, and risk management"
//+------------------------------------------------------------------+
//| Include |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMA.mqh>
#include <Expert\Trailing\TrailingParabolicSAR.mqh>
#include <Expert\Money\MoneySizeOptimized.mqh>
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Input Parameters |
//+------------------------------------------------------------------+
//--- Expert Settings
input group "=== Expert Settings ==="
input string Inp_Expert_Title ="ExpertMAPSARSizeOptimized";
input int Expert_MagicNumber =27893;
input bool Expert_EveryTick =false;
input bool Expert_EnableTrading =true; // Enable Trading
input bool Expert_ShowAlerts =true; // Show Alerts
//--- Signal Settings
input group "=== Moving Average Signal ==="
input int Inp_Signal_MA_Period =12;
input int Inp_Signal_MA_Shift =6;
input ENUM_MA_METHOD Inp_Signal_MA_Method =MODE_SMA;
input ENUM_APPLIED_PRICE Inp_Signal_MA_Applied =PRICE_CLOSE;
//--- Trailing Settings
input group "=== Parabolic SAR Trailing ==="
input double Inp_Trailing_ParabolicSAR_Step =0.02;
input double Inp_Trailing_ParabolicSAR_Maximum =0.2;
input bool Inp_Trailing_Enable =true; // Enable Trailing Stop
//--- Money Management Settings
input group "=== Money Management ==="
input double Inp_Money_SizeOptimized_DecreaseFactor=3.0;
input double Inp_Money_SizeOptimized_Percent =10.0;
input double Inp_Money_MaxLotSize =10.0; // Maximum Lot Size
input double Inp_Money_MinLotSize =0.01; // Minimum Lot Size
input bool Inp_Money_UseEquity =true; // Use Equity for Calculation
//--- Risk Management Settings
input group "=== Risk Management ==="
input double Inp_Risk_MaxDailyLoss =0.0; // Max Daily Loss (% of balance, 0=disabled)
input double Inp_Risk_MaxDailyProfit =0.0; // Max Daily Profit (% of balance, 0=disabled)
input int Inp_Risk_MaxTradesPerDay =0; // Max Trades Per Day (0=unlimited)
input bool Inp_Risk_EnableTimeFilter =false; // Enable Time Filter
input int Inp_Risk_StartHour =0; // Trading Start Hour (0-23)
input int Inp_Risk_EndHour =23; // Trading End Hour (0-23)
//--- Logging Settings
input group "=== Logging & Debug ==="
input bool Inp_Log_EnableDetailedLog =true; // Enable Detailed Logging
input int Inp_Log_Level =1; // Log Level (0=Errors, 1=Info, 2=Debug)
//+------------------------------------------------------------------+
//| Global Variables |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrade ExtTrade;
//--- Trading statistics
datetime LastBarTime = 0;
int TradesToday = 0;
double DailyProfit = 0.0;
double DailyLoss = 0.0;
double InitialBalance = 0.0;
datetime LastTradeDate = 0;
datetime g_todayStart = 0; // ⚡ Bolt: Cached today's start time for efficient history selection
//+------------------------------------------------------------------+
//| Logging Functions |
//+------------------------------------------------------------------+
void LogError(string message)
{
Print("[ERROR] ", Inp_Expert_Title, ": ", message);
if(Expert_ShowAlerts) Alert(Inp_Expert_Title, " - ERROR: ", message);
}
void LogInfo(string message)
{
if(Inp_Log_Level >= 1)
Print("[INFO] ", Inp_Expert_Title, ": ", message);
}
void LogDebug(string message)
{
if(Inp_Log_Level >= 2)
Print("[DEBUG] ", Inp_Expert_Title, ": ", message);
}
//+------------------------------------------------------------------+
//| Check Trading Allowed |
//+------------------------------------------------------------------+
bool IsTradingAllowed()
{
//--- Check if trading is enabled
if(!Expert_EnableTrading)
{
LogDebug("Trading is disabled by user");
return false;
}
//--- Check if AutoTrading is enabled
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
{
LogError("AutoTrading is disabled in terminal settings");
return false;
}
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
{
LogError("AutoTrading is disabled in EA settings");
return false;
}
//--- Check time filter
if(Inp_Risk_EnableTimeFilter)
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int currentHour = dt.hour;
if(Inp_Risk_StartHour <= Inp_Risk_EndHour)
{
if(currentHour < Inp_Risk_StartHour || currentHour > Inp_Risk_EndHour)
{
LogDebug("Outside trading hours: ", currentHour);
return false;
}
}
else // Overnight trading
{
if(currentHour < Inp_Risk_StartHour && currentHour > Inp_Risk_EndHour)
{
LogDebug("Outside trading hours: ", currentHour);
return false;
}
}
}
return true;
}
//+------------------------------------------------------------------+
//| Check Daily Limits |
//+------------------------------------------------------------------+
bool CheckDailyLimits()
{
//--- ⚡ Bolt: Rollover reset logic moved to OnTick() for better performance and
//--- to ensure it runs even when EveryTick is disabled.
//--- Check max trades per day
if(Inp_Risk_MaxTradesPerDay > 0 && TradesToday >= Inp_Risk_MaxTradesPerDay)
{
LogInfo("Maximum trades per day reached: ", Inp_Risk_MaxTradesPerDay);
return false;
}
//--- Check daily loss limit
if(Inp_Risk_MaxDailyLoss > 0)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double maxLoss = balance * (Inp_Risk_MaxDailyLoss / 100.0);
if(DailyLoss >= maxLoss)
{
LogError("Daily loss limit reached: ", DoubleToString(DailyLoss, 2), " (Max: ", DoubleToString(maxLoss, 2), ")");
if(Expert_ShowAlerts) Alert("Daily loss limit reached!");
return false;
}
}
//--- Check daily profit limit
if(Inp_Risk_MaxDailyProfit > 0)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double maxProfit = balance * (Inp_Risk_MaxDailyProfit / 100.0);
if(DailyProfit >= maxProfit)
{
LogInfo("Daily profit limit reached: ", DoubleToString(DailyProfit, 2), " (Max: ", DoubleToString(maxProfit, 2), ")");
if(Expert_ShowAlerts) Alert("Daily profit target reached!");
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Update Daily Statistics |
//+------------------------------------------------------------------+
void UpdateDailyStatistics()
{
double currentProfit = 0.0;
//--- ⚡ Bolt: Performance optimization - use precisely calculated g_todayStart.
//--- This avoids expensive 24-hour scans and ensures statistics match the current trading day.
if(g_todayStart == 0)
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
g_todayStart = StructToTime(dt);
}
if(HistorySelect(g_todayStart, TimeCurrent()))
{
int total = HistoryDealsTotal();
for(int i = 0; i < total; i++)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket > 0)
{
if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == Expert_MagicNumber)
{
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
double swap = HistoryDealGetDouble(ticket, DEAL_SWAP);
double commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
currentProfit += profit + swap + commission;
}
}
}
}
if(currentProfit >= 0)
{
DailyProfit = currentProfit;
DailyLoss = 0.0;
}
else
{
DailyProfit = 0.0;
DailyLoss = MathAbs(currentProfit);
}
}
//+------------------------------------------------------------------+
//| Initialization function of the expert |
//+------------------------------------------------------------------+
int OnInit(void)
{
//--- Set trade parameters
ExtTrade.SetExpertMagicNumber(Expert_MagicNumber);
ExtTrade.SetDeviationInPoints(30);
ExtTrade.SetTypeFilling(ORDER_FILLING_FOK);
ExtTrade.SetAsyncMode(false);
//--- Initialize expert
if(!ExtExpert.Init(Symbol(), Period(), Expert_EveryTick, Expert_MagicNumber))
{
LogError("Error initializing expert");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Creation of signal object
CSignalMA *signal = new CSignalMA;
if(signal == NULL)
{
LogError("Error creating signal object");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Add signal to expert (will be deleted automatically)
if(!ExtExpert.InitSignal(signal))
{
LogError("Error initializing signal");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Set signal parameters
signal.PeriodMA(Inp_Signal_MA_Period);
signal.Shift(Inp_Signal_MA_Shift);
signal.Method(Inp_Signal_MA_Method);
signal.Applied(Inp_Signal_MA_Applied);
//--- Check signal parameters
if(!signal.ValidationSettings())
{
LogError("Invalid signal parameters");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Creation of trailing object
CTrailingPSAR *trailing = new CTrailingPSAR;
if(trailing == NULL)
{
LogError("Error creating trailing object");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Add trailing to expert (will be deleted automatically)
if(!ExtExpert.InitTrailing(trailing))
{
LogError("Error initializing trailing");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Set trailing parameters
trailing.Step(Inp_Trailing_ParabolicSAR_Step);
trailing.Maximum(Inp_Trailing_ParabolicSAR_Maximum);
//--- Check trailing parameters
if(!trailing.ValidationSettings())
{
LogError("Invalid trailing parameters");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Creation of money object
CMoneySizeOptimized *money = new CMoneySizeOptimized;
if(money == NULL)
{
LogError("Error creating money management object");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Add money to expert (will be deleted automatically)
if(!ExtExpert.InitMoney(money))
{
LogError("Error initializing money management");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Set money parameters
money.DecreaseFactor(Inp_Money_SizeOptimized_DecreaseFactor);
money.Percent(Inp_Money_SizeOptimized_Percent);
//--- Check money parameters
if(!money.ValidationSettings())
{
LogError("Invalid money management parameters");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Tuning of all necessary indicators
if(!ExtExpert.InitIndicators())
{
LogError("Error initializing indicators");
ExtExpert.Deinit();
return(INIT_FAILED);
}
//--- Initialize statistics
InitialBalance = AccountInfoDouble(ACCOUNT_BALANCE);
LastTradeDate = 0;
TradesToday = 0;
DailyProfit = 0.0;
DailyLoss = 0.0;
//--- ⚡ Bolt: Initialize daily statistics and set timer for periodic updates
UpdateDailyStatistics();
EventSetTimer(60);
//--- Success message
LogInfo("Expert Advisor initialized successfully");
LogInfo("Account: ", IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)));
LogInfo("Symbol: ", Symbol());
LogInfo("Period: ", EnumToString(Period()));
LogInfo("Magic Number: ", IntegerToString(Expert_MagicNumber));
LogInfo("Initial Balance: ", DoubleToString(InitialBalance, 2));
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Deinitialization function of the expert |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- ⚡ Bolt: Kill timer on deinitialization
EventKillTimer();
//--- Print final statistics
double finalBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double totalProfit = finalBalance - InitialBalance;
double profitPercent = (totalProfit / InitialBalance) * 100.0;
LogInfo("Expert Advisor deinitialized. Reason: ", IntegerToString(reason));
LogInfo("Final Balance: ", DoubleToString(finalBalance, 2));
LogInfo("Total Profit: ", DoubleToString(totalProfit, 2), " (", DoubleToString(profitPercent, 2), "%)");
LogInfo("Trades Today: ", IntegerToString(TradesToday));
ExtExpert.Deinit();
}
//+------------------------------------------------------------------+
//| Function-event handler "tick" |
//+------------------------------------------------------------------+
void OnTick(void)
{
//--- ⚡ Bolt: Optimized Day-Rollover check.
//--- This happens at the top to ensure daily stats are reset exactly at midnight.
//--- Using integer division is extremely cheap and avoids expensive TimeToStruct calls on every tick.
datetime currentTickTime = TimeCurrent();
if(currentTickTime / 86400 != LastTradeDate / 86400)
{
MqlDateTime dt;
TimeToStruct(currentTickTime, dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
g_todayStart = StructToTime(dt);
TradesToday = 0;
DailyProfit = 0.0;
DailyLoss = 0.0;
LastTradeDate = g_todayStart;
UpdateDailyStatistics();
LogInfo("New trading day started. Resetting daily statistics.");
}
//--- Check if trading is allowed
if(!IsTradingAllowed())
return;
//--- Check daily limits
if(!CheckDailyLimits())
return;
//--- ⚡ Bolt: Moved UpdateDailyStatistics() from OnTick to OnTrade and OnTimer
//--- to avoid expensive history scanning on every price tick.
//--- Process expert tick
//--- We do NOT throttle this call with a "new bar" check here because ExtExpert
//--- already manages its own EveryTick setting and needs to run for trailing stops.
ExtExpert.OnTick();
//--- ⚡ Bolt: Use lightweight iTime() to detect and log new bars instead of expensive CopyRates().
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime != 0 && currentBarTime != LastBarTime)
{
LastBarTime = currentBarTime;
LogDebug("New bar detected");
}
}
//+------------------------------------------------------------------+
//| Function-event handler "trade" |
//+------------------------------------------------------------------+
void OnTrade(void)
{
ExtExpert.OnTrade();
//--- ⚡ Bolt: Update daily statistics when a trade occurs
UpdateDailyStatistics();
//--- Update trade statistics
if(HistorySelect(TimeCurrent() - 60, TimeCurrent()))
{
int total = HistoryDealsTotal();
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket > 0)
{
if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == Expert_MagicNumber)
{
datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
if(dealTime > LastTradeDate)
{
TradesToday++;
LogInfo("Trade executed. Total trades today: ", IntegerToString(TradesToday));
break;
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Function-event handler "timer" |
//+------------------------------------------------------------------+
void OnTimer(void)
{
//--- ⚡ Bolt: Periodic background update of statistics.
ExtExpert.OnTimer();
UpdateDailyStatistics();
}
//+------------------------------------------------------------------+