A market data feed can stop delivering updates without anything on your screen changing. The last price holds. The chart keeps its shape. No error dialog, no red banner, no reconnect spinner. The feed is dead, and the display looks exactly the way it looks on a slow afternoon.

Key takeaway

A frozen market data feed and a genuinely quiet market look identical on screen, because both render a last price that is not changing and an update that never arrives produces no visual signal. Staleness cannot be detected from the price display itself. It must be detected by a separate channel whose silence is measurable, which in practice means connection level heartbeats with a client side timeout, sequence number gaps, and staleness timers tuned per symbol and per session.

Why can you not see a dead feed by looking at the price?

Because an update that never arrives produces no visual event. A price display renders the last value it received, whether that value arrived 40 milliseconds ago or 40 minutes ago. Freshness is metadata the display throws away.

Every other failure in a trading stack announces itself. A rejected order returns a reject. A failed login returns a message. A stale quote returns a correct-looking answer to the wrong question: it tells you the last received price while you read it as the current price.

Detection therefore cannot come from the price channel, which expresses failure and calm identically. It has to come from a second channel that produces traffic on a known schedule, so that the absence of traffic can be measured against a clock.

What does a stale quote actually cost?

Point drift times the contract multiplier, charged twice: once to the entry, once to the stop. A trader acts on an E-mini S&P 500 (ES) quote that froze 90 seconds earlier during a fast move, and the live market has travelled 6.00 index points from the frozen display. ES is $50 per index point, so the pricing error at the moment of decision is 6.00 x $50 = $300.00 per contract. Cross-check in ticks: 6.00 / 0.25 = 24 ticks, and 24 x $12.50 = $300.00. On five contracts, 6.00 x $50 x 5 = $1,500.00. That is the error before the trade thesis has been tested at all. Contract specs are stable but amendable, so verify current ES specifications and trading hours with CME Group before relying on them.

A stale quote does not just misprice the entry. It silently rescales the stop.

The stop is the part people miss. The trader intends a 6.00 point stop, which is 6.00 x $50 = $300.00 of risk per contract. If the market already drifted 6.00 points toward that stop, the level now sits behind the live price and fills essentially on arrival. If it drifted 6.00 points the other way, the stop sits 12.00 points from the live market: 12.00 x $50 = $600.00 per contract, or $600.00 / $300.00 = exactly twice the intended risk. Same frozen quote, same intended stop, and realized risk is either zero distance or double distance depending only on which way the market moved while you were not being told.

The same drift corrupts anything computed from price. At an illustrative index level of 5,900.00, notional per contract is 5,900.00 x $50 = $295,000.00, while a risk engine reading a quote stale by 6.00 points computes 5,894.00 x $50 = $294,700.00, a gap of $300.00. Notional error equals point drift times the multiplier, so one stale price corrupts the entry and the exposure check by identical amounts.

How does a feed go stale without throwing an error?

Four mechanisms cover almost every case, and only one of them looks like a disconnection.

The half-open TCP connection. RFC 9293, the current TCP specification, calls an established connection half-open "if one of the TCP peers has closed or aborted the connection at its end without the knowledge of the other," and adds that such connections become reset automatically only if an attempt is made to send data in either direction.

Read that conditional carefully. The reset fires only if you send. A market data subscriber sends its subscription request at the start of the session and is receive-only for the rest of it. It has no reason to transmit, so it never provokes the RST and never learns the peer is gone.

Name the components, because that is where the failure hides. Data flows from the distributor into your machine's socket, out of the socket's receive buffer, through a decode thread that parses each message, and onto the chart. Healthy failure: the far end dies, a FIN or RST travels back up that path, the socket leaves ESTABLISHED, and the platform announces a disconnection. Half-open failure: exactly one thing differs, the FIN never arrives. The socket stays in ESTABLISHED, the receive buffer sits empty, the decode thread has nothing to parse, and the chart keeps painting the last price it ever received.

EXACTLY ONE THING DIFFERS HEALTHY FAILURE distributor socket decode chart FIN/RST socket leaves ESTABLISHED platform warns you HALF-OPEN FAILURE distributor socket decode chart no FIN ever arrives stays ESTABLISHED buffer empty chart looks normal
Both paths carry the same components in the same order. The only difference is whether a FIN or RST travels back up the chain. When it does not, the socket stays open, the decode thread has nothing to parse, and the chart keeps painting the last price it ever received.

One qualifier. NAT boxes, firewalls, and load balancers often time out idle flows and emit an RST of their own, so a half-open state clears itself within minutes on some network paths and persists until you restart the application on others. You do not get to know which in advance.

The application stopped draining its receive buffer. Bytes arrive, the OS accepts and acknowledges them, but the decode or render thread is blocked, so the displayed price stops advancing while the connection stays healthy by every measurable test. The status indicator shows green, correctly. Only a timer watching data arrival, not link state, catches this one.

The silently dropped subscription. The session is up, heartbeats keep flowing, twenty other instruments keep ticking, and one symbol was unsubscribed upstream without an error reaching you. Connection level monitoring offers zero protection here by construction, since nothing about the connection is wrong. This is the whole reason per instrument staleness timers need to exist.

The partial upstream outage. A distributor problem hits one instrument or one channel rather than the whole feed. Partial outages are more dangerous than total ones: a total outage is loud and immediately actionable, while a partial outage is indistinguishable from a contract nobody is trading right now.

Why is sending heartbeats the easy half?

Because all of the protection lives in the client side timeout, not in the message. A protocol that emits heartbeats to a client that never checks for their absence provides exactly zero protection. Sending is cheap. Timing out is the half that detects.

A heartbeat is a message a protocol sends for no reason other than to make silence measurable. RFC 6455, the WebSocket specification, treats a Ping frame as a keepalive or a means of verifying that the remote endpoint is still responsive, and an unsolicited Pong frame as a unidirectional heartbeat. The FIX Heartbeat message (MsgType 0) exists, per the FIX Trading Community specification, because it "monitors the status of the communication link and identifies when the last of a string of messages was not received."

FIX gives the mature worked design. The interval is tag 108, HeartBtInt, expressed in seconds and agreed by both sides at Logon. When nothing has been received for HeartBtInt plus a reasonable transmission time, the peer sends a TestRequest (MsgType 1), which the specification says "forces a heartbeat from the opposing application" and "checks sequence numbers or verifies communication line status." The reply carries TestReqID (tag 112), required whenever the heartbeat is the result of a Test Request, so the answer is provably tied to that probe rather than being any passing message. If no heartbeat comes back after another such interval, the session is treated as lost. Many engines also read a HeartBtInt of 0 as "generate no heartbeats at all," so confirm what your engine and your counterparty's engine do with that value before someone helpfully optimizes it.

That two step design has a consequence most people get wrong: worst case detection latency is roughly twice the heartbeat interval, because there are two sequential waits. With HeartBtInt = 30 s and an illustrative 2 s transmission allowance, silence is first acted on at 30 + 2 = 32 seconds when the TestRequest goes out, and the link is declared dead at 32 + 32 = 64 seconds. The specification leaves "reasonable transmission time" unquantified, so 64 is what this arithmetic yields under stated assumptions, not a mandated figure. Tighten to HeartBtInt = 5 s with a 1 s allowance and you get (5 + 1) + (5 + 1) = 12 seconds worst case. Against the earlier example, the 30 second setup would have declared the link dead 90 - 64 = 26 seconds before that trade was placed.

TCP keepalive will not save you

RFC 9293 requires TCP keep-alives to default to off, requires the interval to be configurable and to default to no less than two hours, and forbids an implementation from interpreting failure to answer any specific probe as a dead connection. On common Linux defaults (7200 s idle, then 9 probes 75 s apart) the worst case is 7200 + 675 = 7,875 seconds, about 2 hours 11 minutes, or 262.5 times slower than a 30 second application heartbeat.

Unconfigured, it does not fire at all. Those Linux values are tunable via sysctl and overridable per socket, and other operating systems ship different defaults, so check yours. The lesson holds regardless: application layer heartbeats exist because the transport layer declines to do this job on a timescale trading cares about.

Which detection mechanisms work, and what does each miss?

Four, in descending order of reliability, each covering a gap the others leave.

MechanismWhat it provesWhat it missesRetail buildable
Connection heartbeat with client timeoutThe link is alive and the peer is responsive nowA dropped subscription on a healthy link; a blocked decode threadOnly if the platform exposes it
Sequence numbersPositive proof a specific message was lostSilence, since a stream that stops leaves no gap to findRarely, unless you consume the feed directly
Per instrument staleness timerThis symbol has not updated for N secondsCannot separate fault from quiet without tuningYes
Cross source comparisonTwo sources disagreeWhich of the two is correctYes, at the cost of a second feed

Sequence numbers earn second place. FIX numbers every message in a session in a continuous, incrementing series, and that continuity is the point: a gap is proof something was lost, where a timeout is only evidence that nothing arrived. Sequence numbers detect loss, heartbeats detect silence, and neither substitutes for the other. For how these session guarantees differ across the wire protocols retail platforms actually use, see our breakdown of WebSocket, FIX, and REST in a trade copier.

Per instrument staleness timers are the one item you can build today without vendor cooperation. MetaQuotes documents SymbolInfoTick returning an MqlTick whose fields include time and time_msc, and the symbol property enumeration exposes SYMBOL_TIME for the time of the last quote plus SYMBOL_TIME_MSC for that same timestamp in milliseconds. That is everything needed to compute time since last tick per symbol and raise your own alert. Field availability differs between MQL4 and MQL5 and across builds, so check the version you actually run.

Cross source comparison is the weakest primary detector. A disagreement tells you the sources differ, not which is right, and it adds a second feed that can itself be the stale one. It is a tiebreaker. If you are choosing what that second source should be, consolidated and direct exchange data differ more than most traders expect, which we cover in Barchart versus Rithmic market data.

Why can one global staleness threshold never work?

Because the correct threshold scales with the instrument's normal update rate, and that rate varies by orders of magnitude across a single watchlist. Five seconds of silence in front-month ES during US cash hours is a near-certain fault. The same five seconds in a back-month contract at 3 a.m. is normal. Both are true at once, so no single number satisfies both.

Quantify the spread. Suppose instrument A, a front month in its liquid session, averages one update every 200 ms, and instrument B, a back month overnight, averages one every 120 seconds. These are illustrative assumptions, not measured statistics for any named contract. Apply five seconds of silence to each. For A, 5,000 ms / 200 ms = 25 expected updates failed to arrive, which is overwhelming evidence of a fault. For B, 5,000 ms / 120,000 ms = 0.0417 of one expected interval, which is evidence of nothing.

Derive both thresholds from one rule, say flag at 25 times the normal update interval. For A that is 25 x 0.2 s = 5 seconds. For B it is 25 x 120 s = 3,000 seconds, or 50 minutes. The two correct thresholds differ by 3,000 / 5 = 600x. Set a global value at 5 seconds and instrument B alarms all night. Set it at 3,000 seconds and a dead front-month feed goes unnoticed for the better part of an hour.

Two caveats. Feed arrangements differ by prop firm and broker, with some routing consolidated data and some conflating or throttling updates, so measure your own baseline rather than importing a number from an article. And conflated or snapshot style feeds publish on a fixed cadence rather than on every change, so tune the timer to the publication cadence, not to market activity.

Connection level liveness beats per instrument inference

Per instrument timers conflate two questions, "is the link alive" and "is this symbol simply quiet," and answer both with one clock. A connection heartbeat answers the first with certainty and removes it from the second, leaving the timer a narrower question it can actually answer.

The two topologies differ in what reaches the trader. Under per instrument monitoring, every subscribed symbol carries its own clock, each clock trips on its own, and every trip merges into one alert stream where a quiet back month and a dead front month arrive looking identical.

WHERE THE LIVENESS QUESTION GETS ANSWERED PER INSTRUMENT ONLY quiet back month quiet back month DEAD front month one alert stream all look identical CONNECTION LEVEL client probe reply server liveness answered once timers become diagnostics
A per-instrument timer answers two questions with one clock, so a quiet back month and a dead front month arrive looking the same. A connection heartbeat settles liveness on its own, which leaves each symbol timer a narrower question it can actually answer.

Under connection level monitoring, a single probe and reply loop between client and server answers the liveness question once, and the per instrument timers sit underneath it, demoted from alarms to diagnostics. Same symbols, same timers, one alert surface instead of many.

Alert volume makes the case. Take 200 subscribed symbols and assume, purely for illustration, a 1% chance per symbol per hour of a spurious trip. That is 200 x 0.01 = 2.0 per hour, and across a 6.5 hour US cash session, 2.0 x 6.5 = 13 false alarms per session, or 13 x 5 = 65 per week. Nobody keeps respecting an alert that cries wolf 65 times a week. Monitor one connection heartbeat at the same 1% hourly rate and you get 1 x 0.01 x 6.5 = 0.065 expected false alarms per session, roughly one every 1 / 0.065 = 15.4 sessions, or about once every 3.1 weeks. The monitored surface collapses from 200 objects to 1.

What does this mean for a trade copier?

Two problems, and the second is far worse. A copier reading stale prices for sizing or risk checks decides on old information: sizing against a stale price yields the wrong contract count, and a risk check against a stale price approves or blocks the wrong trade. That is the $300 per contract error from earlier, applied automatically and at speed.

The sharper problem is the master link. A copier whose connection to the master has gone stale sees no new fills, so it does nothing. Doing nothing is correct when the master is not trading and catastrophic when the master is trading into a dead link. From the follower's side those states are indistinguishable, because both present as an absence of fills. Absence of fills is not evidence of absence of trading.

So a copier must require positive proof of liveness rather than inferring it from quiet. Silence on the master link is suspicious, never "no news." That means a heartbeat on the copier's own transport with a client side timeout, and an explicit visible state for "master link healthy, master flat" that differs from "master link unknown." Two states that produce the same behavior still need different labels, because only one of them should let you go make coffee. Network path quality decides how often that link wobbles, which is part of why VPS placement for futures copy trading is a risk decision rather than a convenience one.

What can you do without writing any code?

Run a canary. Keep a known-liquid instrument's time and sales visible in a corner of the screen. Continuous prints there prove the pipe is moving even when the contract you trade is quiet. It is a manual connection heartbeat built out of somebody else's order flow, and it costs one small window.

Keep an independent second source open. A different provider, and a different network path if you can manage it, since a second feed riding the same VPS and the same upstream is not independent in the way that matters.

Learn what your status indicator reports. NinjaTrader 8 documents five states, including separate Connection Lost (Price Server) and Connection Lost (Order Server) entries, a distinction most platforms do not make. Note what "Connected" claims: that NinjaTrader is fully connected. It is an assertion about the connection, not a guarantee that any specific instrument is updating, and it is fully consistent with a silently dropped subscription. Green lights reliably catch total disconnection and nothing narrower. Other platforms expose coarser status, so check your own.

Pull the plug, while flat. Disconnect your network for a few seconds and watch whether the platform announces the loss or silently keeps painting the last price. Do this with no open positions and no working orders, on a simulated or demo connection first. Almost nobody runs this test, and it is the only way to learn which behavior your specific platform and feed combination has. Ten seconds on a quiet Sunday replaces a permanent assumption with a fact.

When does staleness detection make things worse?

When it fires often enough to be dismissed. False alarms train the trader to click through reflexively, and a detector that is habitually ignored is strictly worse than no detector, because it consumes attention and manufactures false confidence that the problem is covered. The correct posture is few alerts, each of which halts trading. Tune thresholds to fire rarely, then trust them absolutely, which is the opposite of the instinct to catch everything.

A second case argues against reaching for more software. If the underlying problem is an unreliable feed, adding a trade copier does not fix it and makes it worse. A copier propagates intent, not correctness. It will replicate a decision made on a bad price faithfully, quickly, and across every follower account, turning one mispriced trade into several. Fix the feed first, then add anything downstream of it. Cross source comparison deserves the same caveat: it is not a repair for a bad primary feed, only a way to find out sooner that you have one.

Frequently asked questions

How can I tell if my market data feed is frozen or the market is just quiet?

You cannot tell from the price display, because a frozen quote and a quiet market both render a last price that is not changing. Use a separate signal instead: a connection heartbeat with a client side timeout, or a known-liquid instrument's time and sales kept open as a canary. If prints are still arriving on the liquid instrument, the pipe is moving and your quiet symbol is genuinely quiet.

Why does my platform show Connected while prices have stopped updating?

Because a connection indicator reports link state, not data freshness, and those are different facts. NinjaTrader 8, for example, documents Connected as meaning NinjaTrader is fully connected, which stays true even if one subscription was silently dropped upstream or the decode thread inside the application is blocked. A green light reliably catches total disconnection and nothing narrower.

What is a half-open TCP connection and why does it freeze a feed?

A half-open connection is one where the remote peer has closed or aborted the connection without the local end learning about it, per RFC 9293. The specification adds that such connections become reset only if an attempt is made to send data in either direction, and a market data subscriber is receive-only after it subscribes, so it never sends, never triggers the reset, and never learns the peer is gone. The socket stays in ESTABLISHED while no data arrives.

Does TCP keepalive detect a dead market data feed?

No, not on any timescale trading cares about. RFC 9293 requires keep-alives to default to off, requires the interval to default to no less than two hours, and forbids treating any single unanswered probe as a dead connection, while common Linux defaults (7200 s idle, then 9 probes 75 s apart) give a worst case of 7,875 seconds. That is roughly 262 times slower than a 30 second application layer heartbeat, and on an unconfigured socket it never fires at all.

How long does a heartbeat actually take to detect a dead connection?

Roughly twice the heartbeat interval, not once, because the standard FIX design has two sequential waits. With HeartBtInt = 30 seconds and a 2 second transmission allowance, a TestRequest goes out at 32 seconds and the link is treated as lost at 64 seconds if no heartbeat answers it. Traders who set a 30 second interval and expect 30 second detection are off by a factor of two.

What is a good staleness threshold for futures quotes?

There is no single correct number, because the right threshold depends on the instrument's normal update rate and the session. A rule such as flag at 25 times the normal update interval gives 5 seconds for an instrument updating every 200 ms and 3,000 seconds for one updating every 120 seconds, a 600x spread. Measure your own baseline per symbol and per session rather than adopting a global value.

Do sequence numbers replace heartbeats for detecting a stale feed?

No, they detect a different failure. A sequence number gap is positive proof that a specific message was lost, while a heartbeat timeout is evidence that nothing is arriving at all. A stream that stops completely produces no gap to find, so heartbeats catch silence and sequence numbers catch loss, and a serious design uses both.

Can a trade copier keep working correctly if its data feed is stale?

No, and the more dangerous case is a stale link to the master account rather than stale prices. A copier that sees no new fills does nothing, which is correct when the master is flat and catastrophic when the master is trading into a dead link, and the follower cannot distinguish those two states from the absence of fills alone. Require a heartbeat as positive proof of liveness instead of inferring it from quiet.

How do I test whether my platform announces a lost connection?

Disconnect your network for a few seconds and watch whether the platform raises a warning or silently keeps painting the last price. Do this while completely flat, with no working orders, and ideally on a simulated or demo connection first. Almost nobody runs this test, and it is the only reliable way to learn which behavior your specific platform and data provider combination has.

Can I build a staleness detector on MetaTrader without a developer?

Yes, per instrument staleness timers are buildable on mainstream retail platforms without vendor cooperation. MetaQuotes documents SymbolInfoTick returning an MqlTick whose fields include time and time_msc, and the symbol property enumeration exposes SYMBOL_TIME for the time of the last quote plus SYMBOL_TIME_MSC for that timestamp in milliseconds. Comparing that value against the clock gives time since last tick per symbol, which is all a staleness alert needs, though field availability differs between MQL4 and MQL5 and across builds, so check the version you run.