Articles

MetaTrader n’est pas en UTC : c’est l’horloge du courtier

7 min de lecture

Si vous récupérez des bougies MetaTrader 5 en Python, la colonne time ressemble à un timestamp Unix. Ce n’en est pas un. C’est l’horloge du serveur du courtier, écrite en secondes depuis 1970 sur cette horloge. Passez-la à pd.to_datetime(unit="s") et vos bougies se retrouvent à deux ou trois heures de leur moment réel.

La confusion commence dans la documentation. La page officielle de copy_rates_from affirme que les données renvoyées par le terminal sont en UTC (documentation MQL5 (Site externe)). Dans un fil de forum de 2021 intitulé « python api does not return UTC times », un modérateur a reconnu que la documentation Python contredit celle de MQL5 et a précisé que les heures sont celles du serveur du courtier (forum MQL5, 2021 (Site externe)). La question est revenue en octobre 2026, avec des heures de ticks brutes mesurées environ 10 799 secondes en avance sur l’UTC, et la même réponse d’un modérateur (forum MQL5, 2026 (Site externe)).

J’ai publié une petite bibliothèque Python pour cela, brokerclock (Site externe). Avant l’API, voici pourquoi la correction habituelle en une ligne ne tient pas.

L’horloge que vous ne voyez pas

Beaucoup de courtiers forex règlent leur serveur pour que minuit tombe à 17 h à New York, la fin conventionnelle de la journée de trading forex. Cela les place à GMT+2 pendant l’hiver new-yorkais et à GMT+3 pendant l’été new-yorkais : ils changent d’heure aux dates américaines, pas européennes (explication de l’heure serveur des courtiers (Site externe)).

Le paquet Python de MetaTrader n’a aucun appel qui renvoie le décalage du courtier ni la règle qui le fixe. Alors on en code un en dur.

Un +3 fixe est faux tout l’hiver. Un +2 fixe est faux tout l’été. Une règle d’heure d’été européenne semble juste la majeure partie de l’année, puis casse deux fois, parce que les États-Unis et l’Europe changent d’heure des dimanches différents : environ trois semaines en mars, après le changement américain et avant l’européen, et une semaine vers début novembre. En 2025, les États-Unis ont changé le 9 mars et l’Europe le 30 mars. Chaque bougie de cette période est décalée d’une heure, et aucune erreur n’apparaît.

Le problème existe aussi dans l’autre sens. Les arguments de date de copy_rates_range et copy_ticks_range, ainsi que le champ expiration d’un ordre, sont lus en heure serveur. Un datetime naïf passe par le fuseau local du PC en chemin, ce qui ajoute une troisième horloge au problème.

Une horloge est une règle, pas un nombre

La solution consiste à ne plus stocker un décalage mais la règle. brokerclock modélise l’horloge du serveur comme un fuseau IANA plus un décalage. L’horloge de courtier habituelle, GMT+2/+3 aux dates américaines, correspond à l’heure locale de New York plus sept heures :

from brokerclock import ServerZone

zone = ServerZone.us()    # GMT+2/+3 (US daylight saving)
zone.to_utc(1751371200)   # 2025-07-01 12:00 on the server clock
# datetime(2025, 7, 1, 9, 0, tzinfo=timezone.utc)

Chaque conversion demande à la base des fuseaux horaires le décalage en vigueur à cet instant, si bien que l’écart de mars et la semaine de novembre tombent juste sans cas particulier. ServerZone.eu(), ServerZone.au() et ServerZone.fixed(0) couvrent les autres configurations courantes. Toute autre horloge est une clé IANA plus un décalage par pas de 15 minutes.

L’heure qui se répète à la fin de l’heure d’été est une vraie ambiguïté : to_utc lève donc AmbiguousTimeError sauf si vous passez ambiguous="earlier" ou "later". L’heure sautée au printemps lève NonexistentTimeError. Je préfère une exception à une bougie qui se déplace en silence.

Retrouver la règle dans l’historique des prix

Savoir qu’il faut une règle ne dit pas laquelle votre courtier utilise. Vous pouvez demander au support. L’historique des prix est un meilleur témoin.

La semaine forex se termine le vendredi à 17 h, heure de New York. Dans l’historique, cette clôture est la dernière bougie avant un trou de 40 heures ou plus. Son heure de fin sur l’horloge du serveur, comparée au vrai vendredi 17 h, donne le décalage du serveur pour cette semaine. Une année de clôtures hebdomadaires, c’est une année de décalages. Chaque horloge candidate (fixe, ou heure d’été américaine, européenne ou australienne, à n’importe quel décalage de base) en prédit une partie, et la meilleure l’emporte.

import MetaTrader5 as mt5
import brokerclock

mt5.initialize()
rates = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_H1, 0, 6500)
result = brokerclock.detect(rates["time"], timeframe=3600)
print(result.zone)  # GMT+2/+3 (US daylight saving)
print(f"{result.matched} of {result.total} weeks agree")

detect renvoie ses preuves avec la réponse. Les semaines qui ne correspondent pas à la gagnante, en général des clôtures anticipées les jours fériés, reviennent dans outliers. Quand les données ne permettent pas de départager deux horloges, par exemple des bougies d’été uniquement où les règles américaine et européenne coïncident, zone vaut None et candidates liste l’égalité. C’est la réponse honnête, et la solution est une période de données plus longue.

Les courtiers ferment à 17 h ou avant, jamais après, donc chaque décalage hebdomadaire est arrondi aux 15 minutes supérieures. Les bougies H1 sont l’entrée la plus sûre, car la dernière bougie H1 de la semaine se termine toujours à 17 h.

La vérifier avec des ticks en direct

L’historique vous donne la règle. Un tick en direct vous dit si elle tient toujours aujourd’hui :

tick = mt5.symbol_info_tick("EURUSD")
brokerclock.check(zone, [tick.time_msc / 1000])

check convertit le tick selon le fuseau et le compare à l’horloge du PC. Un tick venu du futur lève ZoneMismatchError. Un tick récent et cohérent renvoie True. Un tick ancien renvoie False, car un marché fermé ne peut rien confirmer. Sans fuseau, measure_offset renvoie le décalage actuel, et lève StaleTickError plutôt que de deviner quand le tick est ancien.

Des colonnes entières, et le chemin du retour

Avec l’extra pandas, frame_to_utc prend le tableau structuré de copy_rates_* et renvoie un DataFrame dont la colonne time est en UTC explicite. Pour les requêtes et les expirations, zone.server_timestamp(instant) transforme un datetime UTC en l’entier sur l’horloge du serveur qu’attend MetaTrader, ce qui garde aussi le fuseau du PC hors de la requête.

pip install "brokerclock[pandas]"

La bibliothèque n’importe jamais MetaTrader5. Elle n’a besoin que de la bibliothèque standard, plus tzdata sous Windows, et elle s’exécute et se teste donc sous Linux et macOS, où le paquet du terminal ne s’installe pas. La suite compte 260 tests sur Python 3.10 à 3.14.

D’autres approches

Dans MQL5, le sujet a déjà été traité. La bibliothèque TimeGMT d’Amr Ali estime le décalage du serveur à partir de la première bougie H1 après chaque week-end, surtout pour corriger TimeGMT() dans le testeur de stratégie (TimeGMT (Site externe)). L’article de Kabelo Frans Mampa détecte le calendrier d’heure d’été à partir de la volatilité des Non-Farm Payrolls dans des bougies M15, et il ne peut pas tourner dans le testeur de stratégie, car le calendrier économique n’y renvoie aucune donnée (Monochronic Trading, partie 1 (Site externe)).

Les deux vivent dans le terminal. brokerclock répond à la même question en Python, renvoie la règle elle-même pour que les heures futures se convertissent aussi, et gère le chemin du retour vers MetaTrader.

Ce qu’elle ne résout pas

Le moment où un courtier change l’heure des CFD crypto ouverts en continu dépend du courtier, donc les bougies situées juste au changement peuvent encore être décalées d’une heure. Les métaux et les indices suivent les horaires de leur bourse : détectez la règle sur une paire forex majeure. Les serveurs démo et réel d’un même courtier peuvent avoir des horloges différentes. Et un courtier qui change d’horloge peut réécrire son historique dans la nouvelle, donc refaites la détection après un changement.

Si vous stockez déjà des données MT5, je vérifierais d’abord une semaine : fin mars. Comparez vos bougies à une autre source. Si elles sont décalées d’une heure, le décalage a été enregistré comme un nombre alors qu’il fallait une règle.

Sources

Partager cet article