At 03:14 UTC on a Tuesday, our risk dashboard went quiet. Not red. Not amber. Green.
Every open position showed zero exposure. Zero delta. Zero liquidation distance. The system had not decided we were safe β it had stopped receiving input. Somewhere in the pipeline, a null had been coerced into a zero, and zero is a perfectly valid number to multiply by.
We caught it in eleven minutes because one engineer had a habit of printing raw feed timestamps to a terminal. Eleven minutes of a live book governed by a number that meant "I don't know."
I have spent four years building and breaking systems like this one. The most expensive lesson was not about leverage or slippage. It was this: the dangerous output is not the wrong number. It is the confident number produced from no data at all. That distinction now sits at the center of how I audit anything that touches a market.
Context β The Chain of Custody
A crypto trading desk's data pipeline is a chain of custody. A validator produces a block. An RPC node serves it. An indexer parses it. An API shapes it. A dashboard renders it. A strategy consumes it. Seven hops, at minimum.
Every hop carries an implicit failure contract. When the hop has no data, does it return an error, a default, or nothing at all? Almost nobody writes that contract down. When it is left unspecified, the language runtime writes it for you, and the runtime writes it badly.
JavaScript coerces null to 0 under arithmetic. Python raises on None. Rust refuses to compile an unhandled Option. Solidity is worse than all of them, because it has no null at all. A uint256 cannot be missing. On-chain, an uninitialized value is zero, and zero is a legal price, a legal balance, a legal timestamp.
This is not academic. In 2021 I lost sixty percent of a fifteen-thousand-dollar staking position to a bridge exploit. The check that was supposed to run "if the price is valid" had been written as "if the price is non-zero." A manipulated pool returned exactly zero for a few blocks. That was my principal. I spent three nights in Etherscan learning that a contract's silence is indistinguishable from its agreement.
The ledger remembers what the code tries to hide. It simply does not volunteer the memory. Since then I have treated every data boundary as an adversarial surface, and the null is the most reliable attacker in the set.
Core β Five Places the Null Enters
The sources of missing data in a crypto pipeline are finite and predictable. Here is where I look, in order.
Source one: the reorg. A node that served you a block at height 19,400,221 may serve you a different block at the same height ninety seconds later. Indexers that don't handle this either double-count or, more commonly, rewind to a prior cursor and return an empty page. An empty page is not an error. It is shaped exactly like "no activity."
Source two: the RPC timeout. eth_getLogs against a load-balanced endpoint under stress returns a 200 with an empty array far more often than it returns a 503. The endpoint is up. It answered. It answered with nothing. Uptime is a promise; downtime is the truth, and this is downtime wearing uptime's clothes.
Source three: the oracle round. Chainlink's latestRoundData() returns a five-tuple, and four of those fields are routinely ignored. The one that matters is updatedAt. If your contract does not enforce a heartbeat check β require(updatedAt >= block.timestamp - HEARTBEAT) β then when the feed stalls, and feeds stall during exactly the volatility you care about, you keep consuming the last price the aggregator bothered to publish. In my audits, the missing staleness check is the single most common production defect I find, and it is invisible in unit tests because tests use fresh feeds.
Source four: the imputation layer. This is the newest and the one I care about most. In 2025 I led a team integrating autonomous agents into our execution stack. The agent's feature pipeline had a preprocessing step that filled missing values with zeros. Sensible in a research notebook. Catastrophic in production. A zero-filled feature is a signal, not a gap. The model had been trained on real zeros, so when the feed died, it received a legitimate-looking neutral observation and sized a position on it.
We found the exposure in a stress test, not in live trading. I fed the pipeline a null on purpose and watched the agent open a trade. That is the test everyone skips. The agent did not fail because it was wrong about the market. It failed because it was never told the market was missing.
Source five: the human layer. When an analysis engine returns an empty result set, the temptation is to conclude there was nothing to find. Absence of evidence gets rendered as evidence of absence, and the output is a clean, formatted, confident report that describes a market nobody actually looked at.
The Terra depeg is where I learned this from the other direction. In May 2022 I was a junior analyst coding a script to track exchange inflows of TerraClassic. My first version called a public API and plotted the response. When the API rate-limited me and returned an empty array, the chart showed zero inflow β which, in the framing I had chosen, read as bullish. It wasn't data. It was the absence of a response, drawn as a fact. I rewrote the script to separate three states: value, null, and error. The distribution I found in the first two hours after the depeg was not a panic. It was orderly, and it was large. That distinction is what let me short it.
A year later, when Solana halted for thirteen hours, I built a crude RPC health checker that polled latency across a handful of endpoints. The first version reported 0ms for every dead node. Zero milliseconds is not speed. It is a missing response rendered as a perfect one. Once I separated timeout from latency, I could see which endpoints were actually syncing, and I routed entries around the recovery curve. The outage was a software bug, not a decentralization failure. The edge was in the telemetry, not the thesis.
The structural fix is unglamorous. Every number that crosses a boundary carries a second value: its age and its provenance. Null is not a special case to be handled; it is a first-class type that must propagate. If your price has no timestamp, it is not a price. If your balance has no block height, it is not a balance. If your feed has no health flag, it is a rumor with a decimal point.
The Redundancy Trap
The market's answer to all of this is to buy more data. A second oracle. A third indexer. A dedicated data availability layer. A "unified" analytics provider. The pitch is always redundancy, and it always sounds like risk management.
Redundancy does not fix a consumer that cannot represent absence. If two oracles both return the last round and your code cannot ask how old that round is, you now hold two stale prices and twice the confidence. The failure is not at the source. It is at the type boundary, and it sits downstream of everything you can buy.
This is why the data availability arms race leaves me cold. A rollup publishing forty kilobytes of calldata per hour does not have a data availability problem. It has a marketing relationship with a category. Sellers do not pitch DA to chains that generate data, because those chains don't feel the pain. They pitch it to chains that already cannot read their own feed, and the buyer never notices, because the null was never labeled.
Liquidity fragmentation gets the same treatment. The narrative says fragmentation degrades price discovery. What actually degrades price discovery is a router that treats a pool with zero reserves and a pool with thin reserves as the same object, because both return a number. Every rug pull has a receipt in the logs, and the receipt is almost always an unchecked null.
The blind spot runs the other way too. Teams that do add a staleness check often add it at the wrong altitude β inside the contract but not inside the bot, inside the indexer but not inside the model. The check has to live at the boundary where a decision is made, because that is the only boundary where the null can still kill you.
Takeaway
Look at your stack. Not the strategy β the plumbing. One question: if I feed this system a null, does its output change?
If the answer is no, you do not have a risk engine. You have a renderer. The green you are staring at is not a measurement of your safety. It is a measurement of your feed's silence.
Three things I do now, every time. Assert on the null and halt by default. Log raw timestamps beside every derived number, so a stale input becomes visible before it becomes expensive. And fault-inject on purpose β feed the null yourself, in a test, before the market does it for you at three in the morning.
Trust the math, verify the chain, ignore the hype. I trade the gap between expectation and execution, and increasingly that gap is not about price.
It is about the number that was never there.