Last active
February 6, 2026 14:13
-
-
Save szmeku/5670eae695438de2a61241e2f44509ec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //+------------------------------------------------------------------+ | |
| //| TimeSync_Checker.mq5 | | |
| //| Time Synchronization Analyzer | | |
| //| | | |
| //| PURPOSE: | | |
| //| This script measures time synchronization between: | | |
| //| 1. Your local PC clock | | |
| //| 2. The broker's trade server clock | | |
| //| 3. True internet time (via HTTP API) | | |
| //| | | |
| //| It also calculates the TRUE NETWORK LATENCY - how long it | | |
| //| takes for a tick to travel from the server to your PC. | | |
| //| | | |
| //| WHY THIS MATTERS: | | |
| //| - Server clocks can run fast/slow (affects execution times) | | |
| //| - Network latency affects how "stale" your prices are | | |
| //| - Clock drift can cause issues with time-sensitive strategies | | |
| //| | | |
| //| IMPORTANT LIMITATIONS: | | |
| //| - MQ5 has NO real-time server time query function | | |
| //| - TimeCurrent() returns the timestamp of the LAST RECEIVED TICK | | |
| //| - If no ticks for 5 seconds, that time is 5 seconds stale | | |
| //| - We use HTTP time APIs as the "true time" reference | | |
| //| | | |
| //| SETUP REQUIRED FOR HTTP TIME CHECK: | | |
| //| Go to: Tools -> Options -> Expert Advisors | | |
| //| Check: "Allow WebRequest for listed URL" | | |
| //| Add: http://worldtimeapi.org | | |
| //| http://timeapi.io | | |
| //| | | |
| //| SAFETY: This script is READ-ONLY. It does NOT: | | |
| //| - Place any trades or orders | | |
| //| - Modify any settings | | |
| //| - Write any files | | |
| //| - Change anything on your account | | |
| //+------------------------------------------------------------------+ | |
| #property copyright "Time Sync Checker" | |
| #property version "3.00" | |
| #property script_show_inputs | |
| #property strict | |
| //--- Input Parameters | |
| input int SampleCount = 10; // Number of tick samples to collect | |
| input int SampleDelayMs = 500; // Delay between samples (ms) | |
| input bool UseHTTPTime = true; // Check time via HTTP API (requires WebRequest permission) | |
| input string CustomSymbol = ""; // Symbol for tick analysis (empty = current chart) | |
| //--- Global variables for timezone calculations | |
| long g_serverGmtOffsetMs = 0; // Server timezone offset from GMT in milliseconds | |
| long g_serverClockDriftMs = 0; // How much server clock differs from true time (ms) | |
| bool g_serverDriftKnown = false; // Whether we successfully measured server drift | |
| bool g_tickLatencyMeasured = false; // Whether we got valid tick latency measurements | |
| long g_measuredTickLatencyMs = 0; // Actual measured tick latency (if available) | |
| long g_tickAgeMs = 0; // How old is the last tick | |
| //+------------------------------------------------------------------+ | |
| //| Script program start function | | |
| //+------------------------------------------------------------------+ | |
| void OnStart() | |
| { | |
| string symbol = (CustomSymbol == "") ? Symbol() : CustomSymbol; | |
| Print("============================================================="); | |
| Print(" TIME SYNCHRONIZATION & LATENCY ANALYZER v3.0 "); | |
| Print("============================================================="); | |
| Print("Analysis started at: ", TimeToString(TimeLocal(), TIME_DATE|TIME_SECONDS)); | |
| Print("Symbol for analysis: ", symbol); | |
| //========================================================================= | |
| // SECTION 1: BASIC TIME READINGS | |
| // Collect timestamps from all available sources | |
| //========================================================================= | |
| datetime localTime = TimeLocal(); // Your PC's local time (with timezone) | |
| datetime serverTime = TimeCurrent(); // Last tick's server timestamp (may be stale!) | |
| datetime gmtTime = TimeGMT(); // Your PC's time converted to GMT/UTC | |
| // Try to get the freshest server time by checking multiple symbols | |
| datetime freshestServerTime = GetFreshestServerTime(); | |
| string freshestSource = "TimeCurrent()"; | |
| if(freshestServerTime > serverTime) | |
| { | |
| serverTime = freshestServerTime; | |
| freshestSource = "Multi-symbol scan"; | |
| } | |
| //========================================================================= | |
| // SECTION 2: TIMEZONE DETECTION | |
| // Calculate timezone offsets to normalize all times to GMT | |
| //========================================================================= | |
| long localGmtOffsetSec = (long)localTime - (long)gmtTime; // Your PC's timezone | |
| long serverGmtOffsetSec = (long)serverTime - (long)gmtTime; // Server's timezone | |
| g_serverGmtOffsetMs = serverGmtOffsetSec * 1000; | |
| int localTzHours = (int)(localGmtOffsetSec / 3600); | |
| int serverTzHours = (int)(serverGmtOffsetSec / 3600); | |
| Print(""); | |
| Print(">>> SECTION 1: TIME READINGS <<<"); | |
| Print("Your PC Local Time: ", TimeToString(localTime, TIME_DATE|TIME_SECONDS)); | |
| Print("Your PC as GMT: ", TimeToString(gmtTime, TIME_DATE|TIME_SECONDS)); | |
| Print("Trade Server Time: ", TimeToString(serverTime, TIME_DATE|TIME_SECONDS)); | |
| Print("Server time source: ", freshestSource); | |
| Print(""); | |
| Print("IMPORTANT: Server time is from the LAST RECEIVED TICK."); | |
| Print(" MQ5 cannot query real-time server clock!"); | |
| Print(""); | |
| Print(">>> SECTION 2: TIMEZONE INFO <<<"); | |
| Print("Your PC Timezone: GMT", FormatTimezone(localGmtOffsetSec)); | |
| Print("Server Timezone: GMT", FormatTimezone(serverGmtOffsetSec)); | |
| Print("Difference: ", serverTzHours - localTzHours, " hours"); | |
| //========================================================================= | |
| // SECTION 3: CLOCK DRIFT (PC vs Server, both normalized to GMT) | |
| // This removes timezone differences to show actual clock drift | |
| //========================================================================= | |
| long localAsGmt = (long)localTime - localGmtOffsetSec; | |
| long serverAsGmt = (long)serverTime - serverGmtOffsetSec; | |
| long pcServerDriftSec = localAsGmt - serverAsGmt; | |
| Print(""); | |
| Print(">>> SECTION 3: PC vs SERVER CLOCK DRIFT <<<"); | |
| Print("(Both times normalized to GMT, timezone removed)"); | |
| Print("Drift: ", pcServerDriftSec, " seconds"); | |
| if(MathAbs(pcServerDriftSec) <= 1) | |
| Print("Status: SYNCHRONIZED - Both clocks agree"); | |
| else if(pcServerDriftSec > 0) | |
| Print("Status: Your PC is ", pcServerDriftSec, "s AHEAD of server"); | |
| else | |
| Print("Status: Your PC is ", MathAbs(pcServerDriftSec), "s BEHIND server"); | |
| Print(""); | |
| Print("NOTE: This only shows if PC and server agree with each other,"); | |
| Print(" NOT whether either is correct! Use HTTP check for truth."); | |
| //========================================================================= | |
| // SECTION 4: TRUE TIME CHECK VIA HTTP | |
| // Query internet time APIs to get the REAL time and measure drift | |
| //========================================================================= | |
| if(UseHTTPTime) | |
| { | |
| Print(""); | |
| Print(">>> SECTION 4: TRUE INTERNET TIME CHECK <<<"); | |
| Print("Querying internet time servers for ground truth..."); | |
| CheckInternetTime(serverGmtOffsetSec); | |
| } | |
| //========================================================================= | |
| // SECTION 5: TICK LATENCY ANALYSIS | |
| // Measure how long ticks take to travel from server to your PC | |
| //========================================================================= | |
| Print(""); | |
| Print(">>> SECTION 5: TICK LATENCY ANALYSIS <<<"); | |
| Print("Symbol: ", symbol); | |
| Print(""); | |
| Print("WHAT WE'RE MEASURING:"); | |
| Print(" Tick Latency = Time when you RECEIVE tick - Time when server CREATED tick"); | |
| Print(""); | |
| Print("HOW TO INTERPRET:"); | |
| Print(" Positive latency = Normal network delay (tick took X ms to reach you)"); | |
| Print(" Negative latency = IMPOSSIBLE in physics, means server clock is FAST"); | |
| Print(" High variance = Network instability or stale ticks (low liquidity)"); | |
| AnalyzeTickLatency(symbol, serverGmtOffsetSec); | |
| //========================================================================= | |
| // SECTION 6: TERMINAL AND ACCOUNT INFO | |
| //========================================================================= | |
| Print(""); | |
| Print(">>> SECTION 6: CONNECTION INFO <<<"); | |
| long pingUs = TerminalInfoInteger(TERMINAL_PING_LAST); | |
| Print("Network Ping: ", pingUs, " us (", DoubleToString((double)pingUs / 1000.0, 2), " ms)"); | |
| Print(" This is MT5's internal ping measurement"); | |
| Print("Connected: ", TerminalInfoInteger(TERMINAL_CONNECTED) ? "Yes" : "No"); | |
| Print("Trade Allowed: ", TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) ? "Yes" : "No"); | |
| Print(""); | |
| Print("Account Server: ", AccountInfoString(ACCOUNT_SERVER)); | |
| Print("Account Company: ", AccountInfoString(ACCOUNT_COMPANY)); | |
| Print("Account Login: ", AccountInfoInteger(ACCOUNT_LOGIN)); | |
| //========================================================================= | |
| // SECTION 7: FINAL SUMMARY | |
| //========================================================================= | |
| Print(""); | |
| Print(">>> SECTION 7: FINAL SUMMARY <<<"); | |
| PrintFinalSummary(pingUs); | |
| Print(""); | |
| Print("============================================================="); | |
| Print(" ANALYSIS COMPLETE "); | |
| Print("============================================================="); | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Analyze tick latency with proper clock drift compensation | | |
| //+------------------------------------------------------------------+ | |
| void AnalyzeTickLatency(string symbol, long serverGmtOffsetSec) | |
| { | |
| MqlTick lastTick; | |
| if(!SymbolInfoTick(symbol, lastTick)) | |
| { | |
| Print("ERROR: Could not get tick data for ", symbol); | |
| Print(" Market may be closed or symbol not available."); | |
| return; | |
| } | |
| //--- Check how OLD the last tick is | |
| long tickTimeGmtMs = (long)lastTick.time_msc - g_serverGmtOffsetMs; | |
| datetime nowGmt = TimeGMT(); | |
| long nowGmtMs = (long)nowGmt * 1000 + (long)(GetTickCount64() % 1000); | |
| g_tickAgeMs = nowGmtMs - tickTimeGmtMs; | |
| // Show last tick info | |
| PrintTickInfo(lastTick, "Last available tick", serverGmtOffsetSec); | |
| // WARN if tick is old | |
| Print(""); | |
| if(g_tickAgeMs > 60000) // More than 1 minute old | |
| { | |
| long ageSeconds = g_tickAgeMs / 1000; | |
| long ageMinutes = ageSeconds / 60; | |
| long ageHours = ageMinutes / 60; | |
| Print("!!! WARNING: LAST TICK IS VERY OLD !!!"); | |
| if(ageHours > 0) | |
| Print(" Tick age: ", ageHours, " hours, ", ageMinutes % 60, " minutes"); | |
| else | |
| Print(" Tick age: ", ageMinutes, " minutes, ", ageSeconds % 60, " seconds"); | |
| Print(" Market is likely CLOSED for this symbol!"); | |
| Print(" Run this script on an ACTIVE symbol (EURUSD, XAUUSD, BTCUSD)"); | |
| Print(""); | |
| } | |
| else if(g_tickAgeMs > 5000) // More than 5 seconds | |
| { | |
| Print("NOTE: Last tick is ", g_tickAgeMs / 1000, " seconds old."); | |
| Print(" Symbol may have low liquidity."); | |
| Print(""); | |
| } | |
| if(SampleCount < 1) return; | |
| Print(""); | |
| Print("Collecting ", SampleCount, " tick samples (", SampleDelayMs, "ms apart)..."); | |
| Print(""); | |
| // Arrays to store measurements | |
| long latencies[]; | |
| ArrayResize(latencies, 0); | |
| long totalRawLatency = 0; | |
| long minRawLatency = LONG_MAX; | |
| long maxRawLatency = LONG_MIN; | |
| int validSamples = 0; | |
| int staleSamples = 0; | |
| for(int i = 0; i < SampleCount; i++) | |
| { | |
| Sleep(SampleDelayMs); | |
| MqlTick tick; | |
| if(!SymbolInfoTick(symbol, tick)) | |
| continue; | |
| // Calculate raw latency (before drift compensation) | |
| // tick.time_msc = server time when tick was created (in ms, server timezone) | |
| // We convert to GMT for comparison | |
| long tickTimeGmtMs = (long)tick.time_msc - g_serverGmtOffsetMs; | |
| // Current time in GMT (ms precision using GetTickCount64 for sub-second) | |
| datetime nowGmt = TimeGMT(); | |
| ulong tickCount = GetTickCount64(); | |
| long nowGmtMs = (long)nowGmt * 1000 + (long)(tickCount % 1000); | |
| // Raw latency = now - tick creation time | |
| long rawLatencyMs = nowGmtMs - tickTimeGmtMs; | |
| // Check if this is a new tick or stale | |
| bool isNewTick = (tick.time_msc != lastTick.time_msc); | |
| if(isNewTick) | |
| { | |
| // Store for statistics | |
| int idx = ArraySize(latencies); | |
| ArrayResize(latencies, idx + 1); | |
| latencies[idx] = rawLatencyMs; | |
| totalRawLatency += rawLatencyMs; | |
| if(rawLatencyMs < minRawLatency) minRawLatency = rawLatencyMs; | |
| if(rawLatencyMs > maxRawLatency) maxRawLatency = rawLatencyMs; | |
| validSamples++; | |
| Print("Sample ", StringFormat("%2d", i+1), ": Raw=", StringFormat("%6d", rawLatencyMs), " ms", | |
| " | Bid=", DoubleToString(tick.bid, 5), | |
| " | Ask=", DoubleToString(tick.ask, 5), | |
| " | NEW TICK"); | |
| } | |
| else | |
| { | |
| staleSamples++; | |
| Print("Sample ", StringFormat("%2d", i+1), ": Raw=", StringFormat("%6d", rawLatencyMs), " ms", | |
| " | (stale - same tick as before)"); | |
| } | |
| lastTick = tick; | |
| } | |
| // Statistics | |
| Print(""); | |
| Print("--- RAW LATENCY STATISTICS ---"); | |
| Print("(Raw = Your PC time - Server tick timestamp, before any correction)"); | |
| Print(""); | |
| if(validSamples == 0) | |
| { | |
| Print("No new ticks received during sampling period!"); | |
| Print("Market may be closed or symbol has very low liquidity."); | |
| return; | |
| } | |
| long avgRawLatency = totalRawLatency / validSamples; | |
| Print("New ticks received: ", validSamples, " out of ", SampleCount, " samples"); | |
| Print("Stale tick samples: ", staleSamples, " (no new data arrived)"); | |
| Print(""); | |
| Print("Average raw latency: ", avgRawLatency, " ms"); | |
| Print("Minimum raw latency: ", minRawLatency, " ms"); | |
| Print("Maximum raw latency: ", maxRawLatency, " ms"); | |
| Print("Jitter (range): ", maxRawLatency - minRawLatency, " ms"); | |
| // Calculate standard deviation for jitter analysis | |
| if(validSamples > 1) | |
| { | |
| double variance = 0; | |
| for(int i = 0; i < ArraySize(latencies); i++) | |
| { | |
| double diff = (double)(latencies[i] - avgRawLatency); | |
| variance += diff * diff; | |
| } | |
| variance /= validSamples; | |
| double stdDev = MathSqrt(variance); | |
| Print("Std deviation: ", DoubleToString(stdDev, 1), " ms"); | |
| } | |
| //========================================================================= | |
| // TRUE LATENCY CALCULATION | |
| // If we know the server clock drift, we can calculate REAL network latency | |
| //========================================================================= | |
| Print(""); | |
| Print("--- TRUE NETWORK LATENCY ---"); | |
| if(g_serverDriftKnown) | |
| { | |
| // True latency = Raw latency - Server clock drift | |
| // If server is 200ms fast, raw shows -200ms, true = -200 - (-200) = 0 + network delay | |
| long trueAvgLatency = avgRawLatency - g_serverClockDriftMs; | |
| long trueMinLatency = minRawLatency - g_serverClockDriftMs; | |
| long trueMaxLatency = maxRawLatency - g_serverClockDriftMs; | |
| // Store for final summary | |
| g_tickLatencyMeasured = true; | |
| g_measuredTickLatencyMs = trueAvgLatency; | |
| Print("Server clock drift: ", g_serverClockDriftMs, " ms (from HTTP check)"); | |
| Print(""); | |
| Print("TRUE Average latency: ", trueAvgLatency, " ms"); | |
| Print("TRUE Minimum latency: ", trueMinLatency, " ms"); | |
| Print("TRUE Maximum latency: ", trueMaxLatency, " ms"); | |
| Print(""); | |
| Print("INTERPRETATION:"); | |
| Print("This is the REAL network delay - how long ticks take to reach you."); | |
| Print("You should expect your prices to be ~", trueAvgLatency, " ms behind the market."); | |
| if(trueAvgLatency < 50) | |
| Print("EXCELLENT: Sub-50ms latency is very good for retail."); | |
| else if(trueAvgLatency < 100) | |
| Print("GOOD: Under 100ms is normal for retail connections."); | |
| else if(trueAvgLatency < 200) | |
| Print("FAIR: 100-200ms is acceptable but not ideal."); | |
| else | |
| Print("POOR: Over 200ms latency may affect execution quality."); | |
| } | |
| else | |
| { | |
| Print("Server clock drift is UNKNOWN (HTTP check failed or disabled)."); | |
| Print(""); | |
| Print("Without true time reference, we cannot separate:"); | |
| Print(" - Actual network latency"); | |
| Print(" - Server clock drift"); | |
| Print(""); | |
| Print("ESTIMATED based on raw data:"); | |
| if(avgRawLatency < 0) | |
| { | |
| Print("Negative raw latency indicates server clock is FAST."); | |
| Print("Server appears to be ~", MathAbs(avgRawLatency), " ms ahead of true time."); | |
| Print("True network latency is likely 50-150ms (typical for retail)."); | |
| } | |
| else if(avgRawLatency < 500) | |
| { | |
| Print("Raw latency ", avgRawLatency, " ms could be:"); | |
| Print(" - Pure network delay (if server clock is accurate)"); | |
| Print(" - Mix of network delay + slow server clock"); | |
| } | |
| else | |
| { | |
| Print("High raw latency (", avgRawLatency, " ms) likely includes stale ticks."); | |
| Print("Minimum value (", minRawLatency, " ms) is more representative."); | |
| } | |
| Print(""); | |
| Print("Enable HTTP time check for accurate measurement!"); | |
| } | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Check time against Internet time API for ground truth | | |
| //+------------------------------------------------------------------+ | |
| void CheckInternetTime(long serverGmtOffsetSec) | |
| { | |
| // HTTP Time API endpoints (return UTC/GMT time) | |
| string urls[] = { | |
| "http://worldtimeapi.org/api/timezone/Etc/UTC", | |
| "http://timeapi.io/api/Time/current/zone?timeZone=UTC" | |
| }; | |
| string urlNames[] = {"WorldTimeAPI", "TimeAPI.io"}; | |
| bool gotTime = false; | |
| for(int u = 0; u < ArraySize(urls); u++) | |
| { | |
| if(gotTime) break; | |
| string url = urls[u]; | |
| string cookie = "", headers = ""; | |
| char post[], result[]; | |
| int timeout = 5000; | |
| Print(""); | |
| Print("Querying ", urlNames[u], "..."); | |
| //--- PRECISE TIMING: Capture local time BEFORE and AFTER request | |
| // This lets us calculate the midpoint when the server generated its response | |
| ulong startTickCount = GetTickCount64(); | |
| datetime startGmt = TimeGMT(); | |
| long startGmtMs = (long)startGmt * 1000 + (long)(startTickCount % 1000); | |
| int res = WebRequest("GET", url, cookie, NULL, timeout, post, 0, result, headers); | |
| ulong endTickCount = GetTickCount64(); | |
| datetime endGmt = TimeGMT(); | |
| long endGmtMs = (long)endGmt * 1000 + (long)(endTickCount % 1000); | |
| ulong httpRoundtripMs = endTickCount - startTickCount; | |
| // The server processed our request at approximately the midpoint | |
| // (assuming symmetric upload/download latency) | |
| long midpointLocalGmtMs = startGmtMs + (long)(httpRoundtripMs / 2); | |
| //--- Handle errors | |
| if(res == -1) | |
| { | |
| int err = GetLastError(); | |
| if(err == 4014) | |
| { | |
| Print("ERROR: WebRequest not allowed!"); | |
| Print("TO FIX: Go to Tools -> Options -> Expert Advisors"); | |
| Print(" Check 'Allow WebRequest for listed URL'"); | |
| Print(" Add: ", url); | |
| } | |
| else | |
| { | |
| Print("ERROR: WebRequest failed (code ", err, ")"); | |
| } | |
| continue; | |
| } | |
| if(res != 200) | |
| { | |
| Print("ERROR: HTTP ", res, " response"); | |
| continue; | |
| } | |
| //--- Parse response | |
| string response = CharArrayToString(result); | |
| datetime httpUtcTime = 0; | |
| long httpUtcMs = 0; | |
| if(u == 0) // WorldTimeAPI returns unixtime | |
| { | |
| httpUtcTime = ParseUnixTime(response); | |
| httpUtcMs = ParseUnixTimeMs(response); | |
| } | |
| else // TimeAPI.io returns ISO datetime | |
| { | |
| httpUtcTime = ParseISOTime(response); | |
| httpUtcMs = (long)httpUtcTime * 1000; // No ms precision | |
| } | |
| if(httpUtcTime == 0) | |
| { | |
| Print("ERROR: Could not parse time from response"); | |
| continue; | |
| } | |
| if(httpUtcMs == 0) | |
| httpUtcMs = (long)httpUtcTime * 1000; | |
| gotTime = true; | |
| //========================================================================= | |
| // CALCULATE DRIFT VALUES | |
| //========================================================================= | |
| // PC drift = Our local GMT time at midpoint - True internet time | |
| // Positive = PC is ahead, Negative = PC is behind | |
| long pcDriftMs = midpointLocalGmtMs - httpUtcMs; | |
| // For server drift, get fresh tick and compensate for elapsed time | |
| datetime freshServerTime = GetFreshestServerTime(); | |
| long freshServerGmtMs = ((long)freshServerTime - serverGmtOffsetSec) * 1000; | |
| long elapsedSinceMidpoint = endGmtMs - midpointLocalGmtMs; | |
| long serverAtMidpointMs = freshServerGmtMs - elapsedSinceMidpoint; | |
| long serverDriftMs = serverAtMidpointMs - httpUtcMs; | |
| // Store for tick latency correction | |
| g_serverClockDriftMs = serverDriftMs; | |
| g_serverDriftKnown = true; | |
| //========================================================================= | |
| // DISPLAY RESULTS | |
| //========================================================================= | |
| Print(""); | |
| Print("--- ", urlNames[u], " Results ---"); | |
| Print("HTTP Roundtrip: ", httpRoundtripMs, " ms"); | |
| Print(" (Request -> Server -> Response took ", httpRoundtripMs, " ms total)"); | |
| Print("One-way estimate: ~", httpRoundtripMs/2, " ms"); | |
| Print(" (Assuming symmetric latency, each direction ~", httpRoundtripMs/2, " ms)"); | |
| Print(""); | |
| Print("True Internet Time: ", TimeToString(httpUtcTime, TIME_DATE|TIME_SECONDS), | |
| ".", StringFormat("%03d", (int)(httpUtcMs % 1000))); | |
| Print("Your PC at midpoint: ", TimeToString((datetime)(midpointLocalGmtMs/1000), TIME_DATE|TIME_SECONDS), | |
| ".", StringFormat("%03d", (int)(midpointLocalGmtMs % 1000))); | |
| Print(""); | |
| Print(">>> YOUR PC CLOCK ACCURACY <<<"); | |
| Print("Drift from true time: ", pcDriftMs, " ms"); | |
| if(MathAbs(pcDriftMs) < 100) | |
| Print("Status: EXCELLENT - Your PC clock is very accurate"); | |
| else if(MathAbs(pcDriftMs) < 500) | |
| Print("Status: GOOD - Minor drift, acceptable"); | |
| else if(pcDriftMs > 0) | |
| Print("Status: Your PC is ", pcDriftMs, " ms AHEAD of true time"); | |
| else | |
| Print("Status: Your PC is ", MathAbs(pcDriftMs), " ms BEHIND true time"); | |
| Print(""); | |
| Print(">>> TRADE SERVER CLOCK ACCURACY <<<"); | |
| Print("Drift from true time: ", serverDriftMs, " ms"); | |
| if(MathAbs(serverDriftMs) < 100) | |
| Print("Status: EXCELLENT - Server clock is accurate"); | |
| else if(serverDriftMs > 0) | |
| { | |
| Print("Status: SERVER CLOCK IS FAST by ", serverDriftMs, " ms!"); | |
| Print(" Ticks are timestamped in the 'future'"); | |
| } | |
| else | |
| { | |
| Print("Status: SERVER CLOCK IS SLOW by ", MathAbs(serverDriftMs), " ms"); | |
| Print(" Ticks are timestamped in the 'past'"); | |
| } | |
| Print(""); | |
| Print(">>> WHAT THIS MEANS FOR TRADING <<<"); | |
| long relDrift = serverDriftMs - pcDriftMs; | |
| Print("Server relative to your PC: ", relDrift, " ms"); | |
| if(MathAbs(relDrift) < 100) | |
| { | |
| Print("Your PC and server are well synchronized."); | |
| Print("Tick timestamps should be reliable."); | |
| } | |
| else if(relDrift > 0) | |
| { | |
| Print("Server is ", relDrift, " ms FASTER than your PC."); | |
| Print("Raw tick latency will appear ", relDrift, " ms too LOW (or negative)."); | |
| } | |
| else | |
| { | |
| Print("Server is ", MathAbs(relDrift), " ms SLOWER than your PC."); | |
| Print("Raw tick latency will appear ", MathAbs(relDrift), " ms too HIGH."); | |
| } | |
| } | |
| if(!gotTime) | |
| { | |
| Print(""); | |
| Print("Could not verify time from any HTTP source."); | |
| Print(""); | |
| Print("TO ENABLE HTTP TIME CHECK:"); | |
| Print("1. Go to: Tools -> Options -> Expert Advisors"); | |
| Print("2. Check: 'Allow WebRequest for listed URL'"); | |
| Print("3. Add these URLs to the list:"); | |
| Print(" http://worldtimeapi.org"); | |
| Print(" http://timeapi.io"); | |
| Print("4. Click OK and run this script again"); | |
| } | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Print final summary with actionable insights | | |
| //+------------------------------------------------------------------+ | |
| void PrintFinalSummary(long pingUs) | |
| { | |
| Print("Network Ping (MT5): ", pingUs/1000, " ms"); | |
| //--- Check if tick data was too old to be useful | |
| bool tickDataUsable = (g_tickAgeMs < 60000); // Less than 1 minute old | |
| if(!tickDataUsable) | |
| { | |
| Print(""); | |
| Print("!!! TICK DATA NOT USABLE !!!"); | |
| Print("Last tick is ", g_tickAgeMs / 1000 / 60, " minutes old!"); | |
| Print("Cannot measure true latency on a CLOSED market."); | |
| Print(""); | |
| Print("TO GET ACCURATE RESULTS:"); | |
| Print(" Run this script on an ACTIVE symbol:"); | |
| Print(" - EURUSD (forex, 24h Mon-Fri)"); | |
| Print(" - XAUUSD (gold, 24h Mon-Fri)"); | |
| Print(" - BTCUSD (crypto, 24/7)"); | |
| Print(""); | |
| Print("The numbers below are NOT reliable!"); | |
| Print("-------------------------------------------"); | |
| } | |
| if(g_serverDriftKnown) | |
| { | |
| Print("Server Clock Drift: ", g_serverClockDriftMs, " ms vs true time"); | |
| if(MathAbs(g_serverClockDriftMs) > 500) | |
| { | |
| Print(" NOTE: Large drift may be measurement error"); | |
| Print(" (HTTP API has only 1-second precision)"); | |
| } | |
| else if(g_serverClockDriftMs > 100) | |
| Print(" WARNING: Server clock is FAST!"); | |
| else if(g_serverClockDriftMs < -100) | |
| Print(" WARNING: Server clock is SLOW!"); | |
| else | |
| Print(" Server clock is accurate"); | |
| } | |
| else | |
| { | |
| Print("Server Clock Drift: UNKNOWN (enable HTTP check)"); | |
| } | |
| //--- TRUE TICK LATENCY - the main number user wants | |
| Print(""); | |
| Print("=== YOUR TRUE TICK LATENCY ==="); | |
| if(g_tickLatencyMeasured && tickDataUsable) | |
| { | |
| // We have actual measurements from fresh ticks | |
| Print("MEASURED: ", g_measuredTickLatencyMs, " ms"); | |
| Print(" (Based on actual tick arrival times)"); | |
| Print(""); | |
| Print("This means your prices are ~", g_measuredTickLatencyMs, " ms behind the market."); | |
| if(g_measuredTickLatencyMs < 50) | |
| Print("Rating: EXCELLENT"); | |
| else if(g_measuredTickLatencyMs < 100) | |
| Print("Rating: GOOD"); | |
| else if(g_measuredTickLatencyMs < 200) | |
| Print("Rating: FAIR"); | |
| else | |
| Print("Rating: POOR - consider VPS closer to broker"); | |
| } | |
| else if(tickDataUsable && g_serverDriftKnown) | |
| { | |
| // No new ticks but market should be open - estimate from ping | |
| long estimatedLatency = pingUs/1000 + 20; | |
| Print("ESTIMATED: ~", estimatedLatency, " ms"); | |
| Print(" (Based on ping, no fresh ticks received)"); | |
| } | |
| else | |
| { | |
| Print("UNKNOWN - Market is closed or no ticks received"); | |
| Print(" Cannot measure latency without fresh ticks!"); | |
| Print(""); | |
| Print("Ping suggests: ~", pingUs/1000 + 20, " ms (rough estimate only)"); | |
| } | |
| Print(""); | |
| Print("KEY TAKEAWAYS:"); | |
| if(pingUs < 100000) | |
| Print("- Network ping is FAST (<100ms)"); | |
| else if(pingUs < 200000) | |
| Print("- Network ping is NORMAL for retail (100-200ms)"); | |
| else | |
| Print("- Network ping is SLOW (>200ms) - consider VPS closer to server"); | |
| if(!tickDataUsable) | |
| { | |
| Print("- MARKET IS CLOSED - run on active symbol for accurate results!"); | |
| } | |
| else if(g_tickLatencyMeasured) | |
| { | |
| if(g_measuredTickLatencyMs < 150) | |
| Print("- Your tick latency is acceptable for most trading strategies"); | |
| else | |
| Print("- High latency may affect scalping/HFT strategies"); | |
| } | |
| if(g_serverDriftKnown && MathAbs(g_serverClockDriftMs) > 200) | |
| { | |
| Print("- Server clock has drift (", g_serverClockDriftMs, " ms) - timestamps may be off"); | |
| } | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Get freshest server time by checking multiple active symbols | | |
| //+------------------------------------------------------------------+ | |
| datetime GetFreshestServerTime() | |
| { | |
| datetime freshest = TimeCurrent(); | |
| // Check popular liquid symbols for fresher tick times | |
| string symbols[] = { | |
| "EURUSD", "GBPUSD", "USDJPY", "XAUUSD", "BTCUSD", | |
| "US30", "US500", "NAS100", "GER40", "AUDUSD" | |
| }; | |
| // Check current symbol | |
| datetime currentSymbolTime = (datetime)SymbolInfoInteger(Symbol(), SYMBOL_TIME); | |
| if(currentSymbolTime > freshest) | |
| freshest = currentSymbolTime; | |
| // Check other liquid symbols | |
| for(int i = 0; i < ArraySize(symbols); i++) | |
| { | |
| if(SymbolInfoInteger(symbols[i], SYMBOL_EXIST) == 0) continue; | |
| if(SymbolInfoInteger(symbols[i], SYMBOL_SELECT) == 0) continue; | |
| datetime symTime = (datetime)SymbolInfoInteger(symbols[i], SYMBOL_TIME); | |
| if(symTime > freshest) | |
| freshest = symTime; | |
| } | |
| return freshest; | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Parse Unix timestamp from WorldTimeAPI JSON | | |
| //+------------------------------------------------------------------+ | |
| datetime ParseUnixTime(string json) | |
| { | |
| int pos = StringFind(json, "\"unixtime\":"); | |
| if(pos < 0) return 0; | |
| pos += 11; | |
| int endPos = pos; | |
| while(endPos < StringLen(json)) | |
| { | |
| ushort ch = StringGetCharacter(json, endPos); | |
| if(ch < '0' || ch > '9') break; | |
| endPos++; | |
| } | |
| return (datetime)StringToInteger(StringSubstr(json, pos, endPos - pos)); | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Parse Unix timestamp with milliseconds from WorldTimeAPI | | |
| //+------------------------------------------------------------------+ | |
| long ParseUnixTimeMs(string json) | |
| { | |
| datetime unixSec = ParseUnixTime(json); | |
| if(unixSec == 0) return 0; | |
| // Extract milliseconds from datetime field | |
| int pos = StringFind(json, "\"datetime\":\""); | |
| if(pos < 0) return (long)unixSec * 1000; | |
| int dotPos = StringFind(json, ".", pos); | |
| if(dotPos < 0) return (long)unixSec * 1000; | |
| string msStr = StringSubstr(json, dotPos + 1, 3); | |
| return (long)unixSec * 1000 + StringToInteger(msStr); | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Parse ISO 8601 datetime from TimeAPI.io JSON | | |
| //+------------------------------------------------------------------+ | |
| datetime ParseISOTime(string json) | |
| { | |
| int pos = StringFind(json, "\"dateTime\":\""); | |
| if(pos < 0) | |
| { | |
| pos = StringFind(json, "\"datetime\":\""); | |
| if(pos < 0) return 0; | |
| } | |
| pos += 12; | |
| string dtStr = StringSubstr(json, pos, 19); | |
| // Convert from ISO (YYYY-MM-DDTHH:MM:SS) to MT5 format | |
| string mt5Str = StringSubstr(dtStr, 0, 4) + "." + | |
| StringSubstr(dtStr, 5, 2) + "." + | |
| StringSubstr(dtStr, 8, 2) + " " + | |
| StringSubstr(dtStr, 11, 2) + ":" + | |
| StringSubstr(dtStr, 14, 2) + ":" + | |
| StringSubstr(dtStr, 17, 2); | |
| return StringToTime(mt5Str); | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Format timezone offset as readable string | | |
| //+------------------------------------------------------------------+ | |
| string FormatTimezone(long offsetSeconds) | |
| { | |
| string sign = offsetSeconds >= 0 ? "+" : "-"; | |
| offsetSeconds = MathAbs(offsetSeconds); | |
| int hours = (int)(offsetSeconds / 3600); | |
| int mins = (int)((offsetSeconds % 3600) / 60); | |
| if(mins == 0) | |
| return StringFormat("%s%d", sign, hours); | |
| else | |
| return StringFormat("%s%d:%02d", sign, hours, mins); | |
| } | |
| //+------------------------------------------------------------------+ | |
| //| Print detailed tick information | | |
| //+------------------------------------------------------------------+ | |
| void PrintTickInfo(MqlTick &tick, string label, long serverGmtOffsetSec) | |
| { | |
| long tickGmtMs = (long)tick.time_msc - (serverGmtOffsetSec * 1000); | |
| datetime tickGmt = (datetime)(tickGmtMs / 1000); | |
| Print(""); | |
| Print("--- ", label, " ---"); | |
| Print("Server timestamp: ", TimeToString(tick.time, TIME_DATE|TIME_SECONDS), | |
| ".", StringFormat("%03d", (int)(tick.time_msc % 1000))); | |
| Print("As GMT: ", TimeToString(tickGmt, TIME_DATE|TIME_SECONDS), | |
| ".", StringFormat("%03d", (int)(tickGmtMs % 1000))); | |
| Print("Bid: ", DoubleToString(tick.bid, 5), | |
| " | Ask: ", DoubleToString(tick.ask, 5), | |
| " | Spread: ", DoubleToString((tick.ask - tick.bid) / SymbolInfoDouble(Symbol(), SYMBOL_POINT), 1), " pts"); | |
| } | |
| //+------------------------------------------------------------------+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment