Economic calendar releases turned from categories into numbers in native MQL5: actual minus forecast standardised by each indicator's own dispersion, and anchored to the minute bar that actually moved by a server clock recovered from price. Includes the per-event response regression that says which releases move a symbol and which, payrolls among them, do not.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
ayantrader 7043b65974 Select the first print by revision index, not array order
Pull() kept whichever row for a period arrived first in the array returned by
CalendarValueHistoryByEvent. The terminal does return them oldest first, but
nothing in the API promises that, so the first-print rule quietly depended on
the ordering and would have been wrong without ever failing visibly. It now
keeps the row carrying the lowest revision index, breaking a tie on the
earlier stamp.

Re-running the study produces byte-identical output across all 176 event by
symbol by horizon cells, so no published number changes.
2026-09-24 00:28:05 +05:00
Include/CalSurprise Select the first print by revision index, not array order 2026-09-24 00:28:05 +05:00
Indicators/CalSurprise Draw SurprisePanel as a real panel instead of stacked labels 2026-09-21 09:50:53 +05:00
Scripts/CalSurprise Add the CalSurprise library, study script and indicators 2026-09-20 15:05:53 +05:00
README.md Add the CalSurprise library, study script and indicators 2026-09-20 15:05:53 +05:00

CalSurprise

An economic calendar release read as a number rather than a category: actual minus forecast, standardised by each indicator's own dispersion, anchored to the minute bar the market actually reacted in, and regressed against price to measure what the event does to a symbol.

Companion code for the MQL5 article: https://www.mql5.com/en/articles/24850

What it does

A calendar filter usually asks whether news is due. That treats every CPI print as the same object, when the thing the market trades is the gap between the number and what was expected of it. A release of 0.4% is enormous where consensus was 0.1% and unremarkable where consensus was 0.4%, and the two are indistinguishable to anything keyed on the event id alone.

The gap itself is not comparable across indicators. Nonfarm Payrolls surprises are measured in thousands of jobs, Core CPI in tenths of a percent, and the ISM surveys in index points, so CCalendarSurprise divides each one by the dispersion of that indicator's own past surprises. The result is a z, and a z of 2 means the same thing on every event in the table. Three standardisers are available; the expanding and robust ones look only at releases before the one being scored, so a z printed on the day carries no look-ahead.

Two things have to be right before any of that is trustworthy, and both were found the hard way. The first is that the terminal returns calendar timestamps in GMT until it connects and in server time afterwards, flipping silently mid run. On a GMT+3 server that is a 180 minute error with nothing logged, so CalendarProbe gates every reader on the connection flag plus two matching back-to-back reads. It holds no state between calls, which is what lets an indicator use it from OnCalculate where Sleep is refused.

The second is daylight saving. A historical stamp expressed with today's server offset lands an hour from its own bar whenever the release sits across a changeover. CCalendarClock recovers the broker's schedule from price instead of assuming it: each release votes for the offset of the largest absolute minute return near its stamp, but only when that move clears eight times the median minute of its window, since a quiet print has nothing to say about where the clock sits. The votes are pooled into one twelve-month table. Pooling is the part that matters. An earlier version calibrated per event and was visibly wrong, because one event offers about nine releases a month and the two ISM events ended up disagreeing about which months shift despite releasing on the same clock. Alignment also needs no forecast, only an actual, so the voting pool is larger than the pool usable for standardisation.

Anchoring is worth more than any modelling choice here. Over eleven US events, four symbols and four horizons, the number of cells clearing |t| >= 1.96 rises from 16 to 49 of 176 when the offset is applied, and mean |t| rises from 0.91 to 1.41. Nothing else changed between those two runs.

The consensus feed also carries forecasts that cannot be real. Among the payrolls rows is a consensus of 78,356 thousand jobs, against a median absolute surprise of 121. A median and MAD scale stops rows like that inflating sigma, but it does not remove them, and left in they arrive at several hundred sigma and one of them alone dominates a regression. Both the robust scale and an explicit impossible-value gate are needed; discovering that took three rounds of an engine that reported everything as flat.

What the measurement finds is mixed, and the negative half is the more interesting one. Core CPI m/m is significant on all four symbols, with the sign correctly inverting on USDJPY where the dollar is the base currency: -8.50 pips per sigma on EURUSD at t = -4.33, -9.54 on GBPUSD, +12.16 on USDJPY, -196.6 on XAUUSD. Nonfarm Payrolls, the most watched release on the calendar, is flat on every symbol and every horizon tested, with a maximum |t| of 1.46, while still moving EURUSD 21.5 pips on average. It moves price and the direction is not predictable from the surprise. That result survived all three data fixes above, so it is reported as a finding rather than explained away.

There is no Expert Advisor here and no claim about returns. The library measures a response and says when it cannot find one.

Layout

Include/CalSurprise/CalSurpriseTypes.mqh   shared vocabulary: event, release and response structures, standardiser and rejection enums
Include/CalSurprise/CalSurpriseClock.mqh   CCalendarClock: the broker's daylight saving schedule recovered from price by pooled voting
Include/CalSurprise/CalSurpriseCore.mqh    CCalendarSurprise: settling gate, first-print loading, robust standardisation, response regression
Scripts/CalSurprise/SurpriseLab.mq5        the study: calibrates the clock, then writes the clock table, per-event summary and response table as CSV
Indicators/CalSurprise/SurpriseMeter.mq5   separate-window histogram of the standardised surprise per bar, magnitude and event id exposed for iCustom
Indicators/CalSurprise/SurprisePanel.mq5   chart panel of upcoming releases with their measured response profile, dimming rows whose band spans zero

Run SurpriseLab.mq5 first. It writes surprise_lab.csv to the common files folder and reproduces every number quoted above. Its InpMode default must stay SURPRISE_STD_ROBUST: the expanding mean and standard deviation run is materially weaker on the same data, Core CPI on EURUSD coming out at t = -2.40 rather than -4.33, because the impossible forecasts are still inflating sigma. Setting InpAnchor to false disables the clock and reproduces the raw-stamp comparison.

Substituting your own events is a single edit. EventIds[] in SurpriseLab and InpEvents on both indicators are plain lists of calendar event ids, and nothing downstream knows what an event means. The eleven shipped are the US releases carrying both an actual and a forecast since May 2017, which is where the terminal's forecast history begins; rate decisions carry no forecast at all and cannot be used. Horizons are InpHorizons, in minutes.

Disclaimer

Educational code. This measures whether a release moves a symbol, which is not the same as a way to trade it: the significant cells describe an average response over a sample, not an entry, and nothing here accounts for the spread widening and execution risk around a release. The most famous event on the calendar comes out unmeasurable. Test on your own data, broker and server clock before drawing conclusions, since the alignment the library recovers is a property of the broker you run it against.