Hook
Last week a pipeline I maintain returned a clean execution. Exit code zero. No exception thrown, no timeout logged, no rate-limit warning. And every field empty.
Nine analytical dimensions. Zero populated values. The parser had done precisely what it was told โ look for structure, find none, report nothing rather than fail loudly. That is the most dangerous behavior any system can have.
Seventeen years in markets, and almost none of that time spent watching the pipes that carry the prices. Only the prices themselves. I paid for that blind spot twice, in 2020 and again in 2022. Neither loss came from a wrong number on a screen. Both came from an absent one.
A red number makes you act. A blank field makes you wait. Waiting is how accounts die in a drawdown.
Context
Every DeFi position you hold is a stack of claims on other people's data. Your wallet balance is an RPC response. Your liquidation price is an oracle print. Your yield is an indexer looping over event logs. Strip that away and a lending market is just a number that someone promises is true.
The bear market has been quietly defunding that layer for eighteen months.
Consider what has actually degraded. Free public RPC endpoints โ the kind most retail wallets ship with by default โ routinely rate-limit at a few hundred requests per second across an entire provider pool, and during volatility spikes they fail closed, returning stale block heights rather than errors. The Graph's hosted service, which at its peak indexed the majority of consumer-facing DeFi dashboards, was deprecated in favor of a decentralized network where a subgraph nobody pays to index simply stops advancing. Oracle feeds do not stop, but they do widen deviation thresholds and lengthen heartbeats when node operators find the gas economics unprofitable โ a parameter change announced nowhere your dashboard will show you.
None of this is a hack. All of it is silent degradation. And silent degradation is strictly worse than an outage, because an outage produces a support ticket while a stale feed produces a trade.
I audited three ICO contracts by hand in 2017 before I would touch a pre-sale allocation. The lesson I took from that work was not "read the code, it's safer." It was narrower and more useful: the failure you cannot see is the failure that gets you. An integer overflow is invisible in a whitepaper and obvious in a disassembler. A null price is invisible in a disassembler and obvious in a P&L.
History is just data waiting to be backtested โ but only if the rows survived to the backtest.
Core
Here is where most risk models break, and it is a one-line bug.
if price > liquidation_threshold: liquidate() โ in a language where price can be null, this fails open. In a language where null coerces to zero, it fails catastrophically: every position is instantly insolvent and the engine liquidates the book. Three-valued logic was solved in the 1970s, and the industry still ships two-valued assumptions into production.
The distinction I want on the table: 0 and NULL are not the same fact, and any system that renders them identically is a system you cannot trust with capital. A price of zero is a claim about the world. A null is an admission of ignorance. Conflating them means your dashboard cannot tell you which one you are looking at โ and neither can you.
I have run this experiment. In 2020, mid DeFi Summer, I had Python scripts watching Uniswap and Curve for slippage arbitrage. Forty percent annualized over six months, right up until a week when the RPC provider I was using began serving a cached head. Not an error. A stale one. My bot saw a block height roughly forty blocks behind reality and priced trades against pool state that no longer existed. It executed. It executed profitably, against a ghost. The loss materialized the next morning when the cache invalidated and my inventory was marked against the real chain.
The bug was not in the arbitrage logic. The arbitrage logic was correct. The bug was that my ingestion layer had no concept of freshness. I added one line that night: reject any head older than three seconds. That line has saved me more money than any signal I have ever built.
History is just data waiting to be backtested โ and this is the part people skip. Every backtest you have ever seen is a query against a table where some rows are missing. If the rows are missing at random, your Sharpe survives. If they are missing because the provider could not serve them during the exact volatility that would have triggered your stops, your backtest is not optimistic. It is fiction. Survivorship bias in on-chain data is not a rounding error; it is systematically concentrated in the tail events your strategy exists to survive.
May 2022 made the same point at protocol scale. I was carrying Terra exposure through the collapse and lost thirty percent of my portfolio. The interesting detail is not the death spiral โ everyone has that chart memorized. The interesting detail is that oracle feeds for LUNA were halted on the 12th, well before the terminal spiral completed. The oracle did not lie. It stopped answering. And a large population of contracts that treated "no price" as "last price" continued honoring a number that had already stopped being true.
The absence was the signal. Almost nobody was reading it.
I rebuilt after that: multi-signature cold storage, a protocol whitelist, and โ most importantly โ a rule that any feed returning null triggers a hard stop and a human page. Not a fallback value. A stop. A fallback value is how you convert a data outage into a position.
January 2024 sharpened this in a different direction. When I built the ETF arbitrage strategy against spot BTC, the entire edge lived in the gap between two data sources published on different clocks: the share price on a regulated exchange with a settlement calendar, and spot BTC on venues that never close. Neither feed was wrong. They were asynchronous. My first version lost money for six days because I trusted the displayed timestamps instead of measuring actual arrival latency between them. The fix was to timestamp everything at ingestion and discard any pairing wider than the observed p99 of the latency distribution.
Three incidents, one diagnosis: I was modeling prices and ignoring propagation.
So here is the diagnostic protocol I actually run now, and it is boring on purpose. Pull every external feed you depend on into a local table with an ingestion timestamp. Compute the rolling p50, p95, and p99 of feed age. Alert on p95, not on mean. Log every null as an event with the provider ID attached, because a null rate that clusters on one provider is a provider problem and a null rate that clusters on one timestamp is a chain problem. Reconcile against a second source and record the disagreement distribution. Then โ and this is the step everyone skips โ replay your last ninety days of decisions against the reconciled dataset and measure how many of them would have differed. If the answer is more than a handful, your live strategy is running on a dataset you have never actually seen.
Contrarian
The industry's entire risk vocabulary is built around adversarial events. Exploits, rugs, depegs, governance attacks, sandwich MEV. All of these have an attacker. All of them produce a headline. All of them are things you can at least insure against.
The data layer has no attacker and no headline. It fails because it is underfunded, and it is underfunded because it is invisible, and it is invisible because it works โ until the exact moment it matters most, which is always a volatility spike, which is always when every provider in the pool is simultaneously saturated.
The counterintuitive part is that more infrastructure has not made this better. It has made it worse. Fifty chains, dozens of L2s, hundreds of RPC providers, and the same small population of engineers maintaining ingestion. That is not redundancy. That is fragmentation of a scarce maintenance budget. Every new chain you support is another feed you must monitor for staleness, and almost nobody monitors staleness, because a stale feed does not page anyone.
Two years ago most serious desks ran their own nodes. Today most rent. I understand the economics โ running a node is expensive, loud, and unglamorous โ but the trade is real and the invoice arrives late. You have outsourced the freshness of your input, and therefore the integrity of your entire strategy, to a vendor whose SLA you have never read. Ask yourself honestly: if your primary RPC provider silently returned a head that was ninety seconds old during the next liquidation cascade, would you know? Not would you lose. Would you know.
History is just data waiting to be backtested, and a backtest over fragmented feeds is a backtest over a sample you did not choose.
Takeaway
The practical version, in order. Run at least two independent price sources and hard-fail on disagreement instead of averaging it away. Timestamp every value at ingestion and reject anything past your observed p99 freshness. Never let null coerce โ enforce it with a database constraint, not a code convention, because conventions decay and constraints do not. Treat a blank field on a dashboard as a red alert, not a loading state.
The next loss most people reading this will take will not come from a contract they should have read. It will come from a number that never arrived โ and from a system that was polite enough not to mention it.