426 lines
40 KiB
MQL5
426 lines
40 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| TimeGMT.mqh |
|
|
//| Copyright © 2018, Amr Ali |
|
|
//| https://www.mql5.com/en/users/amrali |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef TIMEGMT_UNIQUE_HEADER_ID_H
|
|
#define TIMEGMT_UNIQUE_HEADER_ID_H
|
|
#property version "1.64"
|
|
|
|
// Updates:
|
|
// 2024.03.30 - v.1.25 : Fixed broker GMT offset. Currently, the library scans H1 bars only on the GOLD chart as it has the most accurate start times.
|
|
// 2024.04.09 - v.1.33 : Fixed potential issue in the calculation of the broker GMT offset during the Christmas holidays on GMT+0 brokers.
|
|
// 2024.04.12 - v.1.35 : Fixed issue in the calculation of the broker GMT offset on few brokers which do not provide gold trading.
|
|
// 2024.07.07 - v.1.36 : Fixed issue in proper detection of the GOLD symbol on some brokers.
|
|
// 2024.10.21 - v.1.40 : Improved detection of the GOLD symbol with a fallback to EURUSD symbol.
|
|
// 2024.10.22 - v.1.45 : Faster determination of the server's GMT offset during weekend in the strategy tester.
|
|
// 2024.10.26 - v.1.47 : Added better error-handling and debugging code. Renamed the HistoryBrokerOffset() method to HistoryServerGMTOffset().
|
|
// 2024.10.28 - v.1.49 : Converted all macros for dealing with time to functions to avoid double evaluation of parameters inside macro body. More code clean-up in other lines.
|
|
// 2024.10.30 - v.1.50 : Fixed issue of wrong estimation of GMT offset from XAUEUR quotes on some brokers.
|
|
// 2024.11.23 - v.1.55 : Added an option to switch off the default loading of Gold symbol for estimation of the server's TZ/DST. Call CTimeGMT::SetUsingGoldSymbol() with 'false' to use the current chart's symbol, instead.
|
|
// 2024.12.12 - v.1.60 : Improved performance of HistoryServerGMTOffset() function and other minor code changes.
|
|
// 2024.12.14 - v.1.61 : Improved performance of FindSymbol() function.
|
|
// 2024.12.17 - v.1.62 : Further optimization of HistoryServerGMTOffset() function.
|
|
// 2024.12.24 - v.1.63 : Fixed potential issue in HistoryServerGMTOffset() function.
|
|
// 2025.10.13 - v.1.64 : Minor improvements and bug fixes.
|
|
|
|
#include <Generic\HashMap.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Macros |
|
|
//+------------------------------------------------------------------+
|
|
#define Debug(message) PrintFormat(" <%s> %s(){ line %d: %s }", __FILE__, __FUNCTION__, __LINE__, message)
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Class CTimeGMT. |
|
|
//| Purpose: Class to fix the built-in TimeGMT() function in order |
|
|
//| to provide the true GMT time during testing in the |
|
|
//| strategy tester. |
|
|
//| |
|
|
//| Usage: Before testing an expert advisor that uses any time |
|
|
//| restrictions or News filter in the strategy tester, |
|
|
//| just add these three lines at the top, then recompile. |
|
|
//| |
|
|
//| // Uncomment the following line to see debug messages |
|
|
//| // #define PRINT_GMT_DETAILS |
|
|
//| #include "TimeGMT.mqh" |
|
|
//| |
|
|
//| Symbol for Estimation of The Server TZ/DST: |
|
|
//| By default, library uses XAUUSD as it provides reliable |
|
|
//| results (esp., brokers that follow EU DST schedule). |
|
|
//| To use the current chart's symbol instead of XAUUSD, |
|
|
//| call CTimeGMT::SetUsingGoldSymbol() with 'false'. |
|
|
//| If the XAUUSD symbol is used for estimation of the |
|
|
//| server's TZ/DST then dst switches will occur one hour |
|
|
//| later in the strategy tester. |
|
|
//+------------------------------------------------------------------+
|
|
class CTimeGMT
|
|
{
|
|
public:
|
|
static datetime TimeGMT(void);
|
|
static datetime TimeGMT(MqlDateTime &dt_struct);
|
|
static void SetUsingGoldSymbol(const bool enabled = true); // Sets the option to use Gold symbol for estimation of server TZ/DST
|
|
|
|
protected:
|
|
//--- internal methods
|
|
static int HistoryServerGMTOffset(const datetime srvtime); // estimate server TZ offset (UTC+DST) from GMT at a given time
|
|
static datetime FirstBarOfWeek(datetime &weekend, int depthLimit = 8); // Returns the time of the first H1 bar after a given weekend (skips abnormal weeks)
|
|
static string FindSymbol(string symbol);
|
|
static bool CheckLoadHistory(const string symbol, const ENUM_TIMEFRAMES period); // Checks presence of the history for symbol and timeframe
|
|
//--- static variables
|
|
static CHashMap<datetime,int>m_serveroffset; // hashmap of server tz offset
|
|
|
|
protected:
|
|
//--- methods for working with data of datetime type
|
|
static datetime StartOfWeek(const datetime time, const bool StartsOnMonday = false); // Return the start of a week for the given date
|
|
static string t2s(const datetime time, const int mode = TIME_DATE | TIME_MINUTES); // Formats time with the weekday name => "Wed, 2023.02.14 01:59"
|
|
|
|
//--- time unit constants
|
|
enum ENUM_TIME_UNITS
|
|
{
|
|
MINSECS = 60,
|
|
HOURSECS = 3600, // = 60 * 60,
|
|
DAYSECS = 86400, // = 60 * 60 * 24,
|
|
WEEKSECS = 604800, // = 60 * 60 * 24 * 7,
|
|
YEARSECS = 31536000 // = 60 * 60 * 24 * 365
|
|
};
|
|
|
|
private:
|
|
static string m_symbol;
|
|
static bool m_using_xauusd;
|
|
};
|
|
|
|
//--- Initialize static variables
|
|
CHashMap<datetime,int> CTimeGMT::m_serveroffset;
|
|
string CTimeGMT::m_symbol=NULL;
|
|
bool CTimeGMT::m_using_xauusd=true; // search for and load Gold symbol (default)
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the GMT, which is calculated by subtracting the server |
|
|
//| offset (UTC+DST) from the current time of the trading server in |
|
|
//| the client terminal. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeGMT::TimeGMT(void)
|
|
{
|
|
//--- In the strategy tester, TimeGMT() is always equal to TimeTradeServer() simulated server time.
|
|
//--- However, the "true" GMT can be estimated by analysis of H1 quotes history to get the server
|
|
//--- timezone offset. The server GMT offset is subtracted from server time to get the true GMT.
|
|
//--- (e.g., TimeTradeServer() = 23:00, and offset from H1 bars = GMT+3, then true GMT = 20:00).
|
|
|
|
datetime tts = ::TimeTradeServer();
|
|
return(tts - HistoryServerGMTOffset(tts)); // determine the server GMT offset from H1 quotes history
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| TimeGMT with MqlDateTime type parameter. |
|
|
//+------------------------------------------------------------------+
|
|
static datetime CTimeGMT::TimeGMT(MqlDateTime &dt_struct)
|
|
{
|
|
datetime t = TimeGMT();
|
|
TimeToStruct(t, dt_struct);
|
|
return(t);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets the option of using XAUUSD (Gold) symbol to estimate the |
|
|
//| server's TZ/DST by analysis of H1 quotes history. |
|
|
//| TRUE : search for and load Gold symbol (default behavior). |
|
|
//| FALSE : use the symbol of the current chart. |
|
|
//| |
|
|
//| XAUUSD can provide more reliable results (esp., for brokers |
|
|
//| that follow EU DST schedule) on weeks during US DST and EU |
|
|
//| DST schedules are out of sync (March and late October). |
|
|
//| if your broker follows the US DST schedule, or no schedule |
|
|
//| at all, then using the chart symbol is Ok. |
|
|
//| |
|
|
//| Note: as a side effect that XAUUSD starts an hour after Forex, |
|
|
//| dst switches occur one hour later (only in the strategy tester). |
|
|
//+------------------------------------------------------------------+
|
|
void CTimeGMT::SetUsingGoldSymbol(const bool enabled = true)
|
|
{
|
|
m_using_xauusd = enabled;
|
|
//--- removes all keys and values from the map.
|
|
m_serveroffset.Clear();
|
|
//--- force a new search for the symbol
|
|
m_symbol = enabled ? FindSymbol("XAUUSD") : _Symbol;
|
|
//--- Check the H1 history is loaded
|
|
CheckLoadHistory(m_symbol, PERIOD_H1);
|
|
//---
|
|
Print("CTimeGMT >> Using Gold symbol option: ", m_using_xauusd);
|
|
Print("CTimeGMT >> Loaded symbol: ", m_symbol, ",H1");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Estimates server TZ offset (UTC+DST) from GMT at a given time. |
|
|
//+------------------------------------------------------------------+
|
|
int CTimeGMT::HistoryServerGMTOffset(const datetime srvtime)
|
|
{
|
|
static datetime last_sunday = -1;
|
|
static int last_result = INT_MIN;
|
|
|
|
#ifdef PRINT_GMT_DETAILS
|
|
Debug(StringFormat("** Estimating server GMT offset of '%s' server for %s from %s,H1 quotes", AccountInfoString(ACCOUNT_SERVER), t2s(srvtime), m_symbol));
|
|
#endif
|
|
|
|
if(StartOfWeek(srvtime) == last_sunday && m_serveroffset.Count() > 0)
|
|
{
|
|
#ifdef PRINT_GMT_DETAILS
|
|
Debug(StringFormat("last_sunday: %s -> %d (GMT%+g)", t2s(last_sunday), last_result, last_result/(double)HOURSECS));
|
|
#endif
|
|
|
|
return(last_result);
|
|
}
|
|
|
|
//--- start at the last bar if 'srvtime' falls in the last weekend or holidays (no FstBarWk yet)
|
|
const datetime lastbar = (datetime)SeriesInfoInteger(m_symbol, PERIOD_H1, SERIES_LASTBAR_DATE);
|
|
|
|
datetime curtime = srvtime;
|
|
if(curtime == 0 || curtime > lastbar)
|
|
{
|
|
curtime = lastbar;
|
|
}
|
|
|
|
const datetime sunday = StartOfWeek(curtime); // the weekend "Sunday 00:00" before this time
|
|
|
|
//--- search this week in the broker gmt offset cache
|
|
int serveroffset = INT_MIN;
|
|
if(!m_serveroffset.TryGetValue(sunday, serveroffset))
|
|
{
|
|
const datetime firstbar = (datetime) SeriesInfoInteger(m_symbol, PERIOD_H1, SERIES_FIRSTDATE);
|
|
|
|
//--- weekend could be modified, if recursion had skipped to previous Sundays
|
|
datetime weekend = sunday;
|
|
|
|
if(weekend >= firstbar)
|
|
{
|
|
//--- find the time of the first hourly bar after this weekend "Sunday 00:00"
|
|
const datetime FstBarWk = FirstBarOfWeek(weekend);
|
|
if(FstBarWk == WRONG_VALUE)
|
|
{
|
|
Debug(StringFormat("Error in FirstBarOfWeek(%s) = WRONG_VALUE, srvtime = %s", t2s(weekend), t2s(srvtime)));
|
|
last_result = serveroffset = INT_MIN;
|
|
last_sunday = -1;
|
|
}
|
|
else
|
|
{
|
|
//--- Forex pairs start Sun 17:00 NY. Gold starts an hour later.
|
|
int beginNYC = 17 * HOURSECS;
|
|
if(StringFind(m_symbol, "XAU") == 0 || StringFind(m_symbol, "GOLD") == 0)
|
|
{
|
|
beginNYC = 18 * HOURSECS;
|
|
}
|
|
//--- US summer starts on the second Sunday of March (GMT-4),
|
|
//--- and ends on the first Sunday of November (GMT-5).
|
|
MqlDateTime dt;
|
|
TimeToStruct(weekend, dt);
|
|
MqlDateTime dt1 = {dt.year, 03, 08}; // the second Sunday of March for the US switch
|
|
MqlDateTime dt2 = {dt.year, 11, 01}; // the first Sunday of November for the US switch
|
|
datetime dst_start = StructToTime(dt1);
|
|
datetime dst_end = StructToTime(dt2);
|
|
datetime tNYC = weekend + beginNYC; // Sun, 18:00 NYC should co-incide with broker's 'FstBarWk' on GOLD chart.
|
|
datetime tGMT = tNYC - ((weekend>=dst_start && weekend<dst_end) ? -4 : -5) * HOURSECS;
|
|
serveroffset = (int)(FstBarWk - tGMT);
|
|
serveroffset = (int) MathRound(serveroffset/1800.)*1800; // round offset to multiples of 30-minutes
|
|
|
|
#ifdef PRINT_GMT_DETAILS
|
|
Debug(StringFormat("FstBarWk = %s", t2s(FstBarWk)));
|
|
Debug(StringFormat("tNYC = %s (GMT%+d)", t2s(tNYC), (tNYC>=dst_start && tNYC<dst_end) ? -4 : -5));
|
|
Debug(StringFormat("tGMT = %s", t2s(tGMT)));
|
|
Debug(StringFormat("m_serveroffset.Add(%s, %d (GMT%+g))", t2s(sunday), serveroffset, serveroffset/(double)HOURSECS));
|
|
#endif
|
|
|
|
//--- add this week to the server gmt offset cache
|
|
m_serveroffset.Add(sunday, serveroffset);
|
|
}
|
|
}
|
|
}
|
|
#ifdef PRINT_GMT_DETAILS
|
|
else
|
|
{
|
|
Debug(StringFormat("m_serveroffset.TryGetValue(%s) -> %d (GMT%+g)", t2s(sunday), serveroffset, serveroffset/(double)HOURSECS));
|
|
}
|
|
#endif
|
|
|
|
//--- remember the server gmt offset of this week
|
|
if(serveroffset != INT_MIN)
|
|
{
|
|
last_sunday = sunday;
|
|
last_result = serveroffset;
|
|
}
|
|
|
|
return(serveroffset);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the time of the first H1 bar after the given weekend. |
|
|
//| The function skips abnormal trading weeks around holidays. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeGMT::FirstBarOfWeek(datetime &weekend, int depthLimit = 8)
|
|
{
|
|
if(m_symbol==NULL)
|
|
{
|
|
//--- Gold has accurate start of trading weeks (esp. EU DST brokers)
|
|
m_symbol = m_using_xauusd ? FindSymbol("XAUUSD") : _Symbol;
|
|
//--- Check the H1 history is loaded
|
|
CheckLoadHistory(m_symbol, PERIOD_H1);
|
|
|
|
#ifdef PRINT_GMT_DETAILS
|
|
Print("CTimeGMT >> Using Gold symbol option: ", m_using_xauusd);
|
|
Print("CTimeGMT >> Loaded symbol: ", m_symbol, ",H1");
|
|
#endif
|
|
}
|
|
|
|
//--- find the first hourly bar after the specified weekend "Sunday 00:00"
|
|
const int barindex = iBarShift(m_symbol, PERIOD_H1, weekend, false);
|
|
const datetime bartime = iTime(m_symbol, PERIOD_H1, barindex - 1);
|
|
|
|
#ifdef PRINT_GMT_DETAILS
|
|
Debug(StringFormat("iBarShift(%s, %s)=%d, iTime(%d) = %s", m_symbol, t2s(weekend), barindex, barindex-1, t2s(bartime)));
|
|
#endif
|
|
|
|
if(barindex < 0 || depthLimit < 0)
|
|
{
|
|
if(MQLInfoInteger(MQL_PROGRAM_TYPE)!=PROGRAM_INDICATOR)
|
|
{
|
|
PrintFormat(">> iBarShift() FAILED: The requested date %s for %s,H1 is not found in the available history", t2s(weekend), m_symbol);
|
|
}
|
|
return(WRONG_VALUE);
|
|
}
|
|
|
|
//--- Skip long weekends or holidays (bartime > Monday 12:00 PM)
|
|
if(barindex == 0 || bartime > weekend + 36 * HOURSECS)
|
|
{
|
|
#ifdef PRINT_GMT_DETAILS
|
|
Debug(StringFormat("bartime = %s", t2s(bartime)));
|
|
Debug(StringFormat("weekend = %s", t2s(weekend)));
|
|
Debug("Detected long weekend / Christmas holidays, skip to previous weekend:");
|
|
#endif
|
|
weekend -= WEEKSECS; // change 'weekend' to the previous Sunday 00:00
|
|
return(FirstBarOfWeek(weekend, depthLimit - 1));
|
|
}
|
|
//---
|
|
return(bartime);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Find and select a symbol for further working with information. |
|
|
//+------------------------------------------------------------------+
|
|
string CTimeGMT::FindSymbol(string symbol)
|
|
{
|
|
bool symbol_found = false; // Store if we have found a valid symbol or not yet
|
|
|
|
//--- if the symbol is found in the list of standard symbols on the server
|
|
bool custom = false;
|
|
if(SymbolExist(symbol, custom) && !custom)
|
|
{
|
|
symbol_found = true;
|
|
}
|
|
|
|
//--- First try to find if the passed symbol exists
|
|
if(symbol_found == false)
|
|
for(int i=0; i < SymbolsTotal(0); i++)
|
|
if(StringFind(SymbolName(i,0),symbol)==0)
|
|
{
|
|
symbol = SymbolName(i,0); // symbol may have suffix
|
|
symbol_found = true;
|
|
break;
|
|
}
|
|
//--- Then try to find a symbol equal to "GOLD"
|
|
if(symbol_found == false)
|
|
for(int i=0; i < SymbolsTotal(0); i++)
|
|
if(SymbolName(i,0)=="GOLD")
|
|
{
|
|
symbol = SymbolName(i,0); // symbol may have suffix
|
|
symbol_found = true;
|
|
break;
|
|
}
|
|
//--- Then try to find a symbol that starts with GOLD
|
|
if(symbol_found == false)
|
|
for(int i=0; i < SymbolsTotal(0); i++)
|
|
if(StringFind(SymbolName(i,0),"GOLD")==0)
|
|
{
|
|
symbol = SymbolName(i,0); // symbol may have suffix
|
|
symbol_found = true;
|
|
break;
|
|
}
|
|
//--- And finally use the current chart's symbol for brokers that do not provide gold
|
|
if(symbol_found == false)
|
|
{
|
|
symbol = Symbol();
|
|
}
|
|
|
|
//--- we have found a valid symbol
|
|
SymbolSelect(symbol, true);
|
|
|
|
return(symbol);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks presence of the history for symbol and timeframe. |
|
|
//| http://www.mql5.com/en/docs/series/timeseries_access |
|
|
//+------------------------------------------------------------------+
|
|
bool CTimeGMT::CheckLoadHistory(const string symbol, const ENUM_TIMEFRAMES period)
|
|
{
|
|
//--- indicator shouldn't load its own symbol and timeframe
|
|
if(MQLInfoInteger(MQL_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
|
|
return(false);
|
|
//---
|
|
datetime times[];
|
|
datetime first_date=0;
|
|
int copied=0;
|
|
uint tick=GetTickCount();
|
|
//--- wait for timeseries build
|
|
while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped() && (GetTickCount()-tick<=6000))
|
|
//Sleep(5);
|
|
;
|
|
//--- second attempt
|
|
return(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date)
|
|
&& first_date>0
|
|
&& CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return the start of a week for the given date. |
|
|
//| By default, the week starts on Sunday, unless superseded. |
|
|
//| |
|
|
//| StartOfWeek (D'2017.08.03 11:30') => 2017.07.30 00:00:00 |
|
|
//+------------------------------------------------------------------+
|
|
datetime CTimeGMT::StartOfWeek(const datetime t, const bool StartsOnMonday = false)
|
|
{
|
|
return (t - (t + (4 - StartsOnMonday) * DAYSECS) % WEEKSECS);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Formats time with the weekday name => "Wed, 2023.02.14 01:59" |
|
|
//+------------------------------------------------------------------+
|
|
string CTimeGMT::t2s(const datetime t, const int mode = TIME_DATE | TIME_MINUTES)
|
|
{
|
|
const string days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
const int i = ((int)((uint)t / DAYSECS + 4) % 7); // i = DayOfWeek(t)
|
|
return days[i % 7] + ", " + TimeToString(t, mode);
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//+------------------------------------------------------------------+
|
|
//| Global API hook via macro substitution. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CustomTimeGMT()
|
|
{
|
|
//--- apply fix only in strategy tester, but use built-in TimeGMT() if running in normal mode.
|
|
if((bool)MQLInfoInteger(MQL_TESTER))
|
|
{
|
|
return CTimeGMT::TimeGMT();
|
|
}
|
|
else
|
|
return ::TimeGMT();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| TimeGMT with MqlDateTime type parameter. |
|
|
//+------------------------------------------------------------------+
|
|
datetime CustomTimeGMT(MqlDateTime &dt_struct)
|
|
{
|
|
//--- apply fix only in strategy tester, but use built-in TimeGMT() if running in normal mode.
|
|
if((bool)MQLInfoInteger(MQL_TESTER))
|
|
{
|
|
return CTimeGMT::TimeGMT(dt_struct);
|
|
}
|
|
else
|
|
return ::TimeGMT(dt_struct);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
#define TimeGMT CustomTimeGMT
|
|
|
|
|
|
#undef Debug
|
|
|
|
#endif // #ifndef TIMEGMT_UNIQUE_HEADER_ID_H
|