← Back to blog

Negative Oil and the Assertion That a Number Can't Go There

Oil hit −$37.63 and a line of code that had been correct for thirty-seven years became the most expensive bug in the building. The same bet is in your stack right now.

Published June 2026 · 9 min read

On the afternoon of April 20, 2020, the price of a barrel of oil did something it had never done in the thirty-seven years since the contract began trading in 1983: it fell through zero, and kept going. By the close, the May NYMEX WTI crude contract settled at minus thirty-seven dollars and sixty-three cents a barrel — it had traded as low as −$40.32 — and the minus sign was not a rendering glitch. For the first time in history, you could not give a barrel of crude away. You had to pay someone $37.63 to take it off your hands.

Somewhere that afternoon, in data centers you will never see, a line of code that had been correct for thirty-seven years became the most expensive bug in the building. if (price < 0) throw InvalidPriceError. A monitoring dashboard whose y-axis started at zero quietly clipped the most important number of the day off the bottom of the chart. A database column typed UNSIGNED physically could not store what was true. The market had gone somewhere the software had been told it could never go — and most of the software was not ready.

One exchange was. And the difference between the systems that survived that afternoon and the systems that threw exceptions, mis-booked millions, or silently produced garbage came down to a single question that someone at CME had bothered to ask, in writing, days earlier.

When physics breaks finance

The number went negative because the physical world and the financial world briefly disagreed, and the physical world won.

WTI is not a number on a screen. It is a contract to take delivery of actual crude oil at actual tanks in Cushing, Oklahoma — the delivery point, with about 76 million barrels of working storage. In the spring of 2020 the pandemic erased oil demand almost overnight while producers kept pumping, and the barrels had to go somewhere. They went into storage, and storage filled. By the time the May contract neared expiry, Cushing was around 80% full on paper and filling fast — on track to roughly 83% within days — and the remaining capacity was already leased or committed, so for a financial trader holding a contract with no tank of their own, it was effectively full. There was nowhere to put the oil.

Now remember what a futures contract actually is. If you are long the May contract at expiry, you are obligated to take delivery — to accept physical barrels. The May contract expired the next day, April 21. Financial traders — funds, ETFs, speculators who never intend to receive a single barrel — were holding contracts they had to get out of, and the usual exit (sell to someone else) required a someone else who could actually store the oil. There wasn't one. So the only way out of the obligation was to pay another party to assume it. The price you pay to escape is, definitionally, a negative price.

Here is the part that matters: −$37.63 was not an error. It was the correct price. Paying $37.63 to be relieved of an obligation to receive oil you had nowhere to store was the rational economic move, and the market found that number the same way it finds every other one. The assumption price ≥ 0 had lived inside pricing models, risk systems, and dashboards for decades — not because anyone proved it, but because for thirty-seven years it had simply always been true. A physical constraint that pure financial models never encoded — you cannot not-deliver — reached up and falsified it. The system that rejected −$37.63 was not protecting itself from a bad value. It was rejecting the truth.

The question someone asked, days early

On April 15, 2020 — five days before the price went negative — CME Group issued a notice (Chadv20-160) inviting members to test negative futures and strike prices in its “New Release” environment. On April 20, as the market headed toward zero, the CME Global Command Center informed trading members directly that prices could trade negative, with no lower limit. By then it was a non-event for CME's own plumbing, because, as the exchange put it, support for zero or negative futures prices was already standard throughout CME systems — every file format and message schema could carry the minus sign.

CME did not scramble that afternoon. It had asked, ahead of time, the only question that mattered — can this number go where we have always assumed it can't? — and it had built and switched on the answer while the question was still hypothetical.

And the detail that should stay with you is this: the capability had been there all along. CME's systems already handled negative numbers without blinking, because spread contracts — a contract whose value is just the difference between two prices — go negative all the time; a difference is naturally signed. The machinery to represent a negative price was sitting right there in the formats. It simply had never been enabled for outright crude, because outright crude had never needed it. The tail-handling capacity your system needs on its worst day very often already exists somewhere in the stack. Someone just has to turn it on before the day it's needed instead of during it.

The firms that weren't ready weren't running bad software

When the price crossed zero, systems that had encoded price ≥ 0 did exactly what you'd fear. Interactive Brokers, the retail brokerage, later disclosed that its software could not process the negative print — and it took a loss of roughly $104 million on customer accounts that went negative in real time while the systems mishandled them (disclosed in the broker's 2020 SEC filings: an initial ~$88 million provision, later revised to ~$104 million after it chose to compensate affected customers). The United States Oil Fund (USO), the largest oil ETF, was forced to restructure away from front-month contracts in the days after, because its whole mechanical strategy assumed a market that no longer existed.

It would be comforting to call these failures of competence. They weren't. They were failures built on an assumption that was historically justified — oil had never, in thirty-seven years of trading, printed a negative price. The engineers who wrote price ≥ 0 were encoding the entire observed history of the instrument. And they were still wrong, because here is the trap at the center of this whole story: the absence of a tail event is not evidence that the tail cannot happen. price ≥ 0 was a perfectly accurate statement about the past and a catastrophically false statement about the future. Thirty-seven years of confirming data did not make the bound real. It just made everyone stop checking.

There's a usable, slightly uncomfortable heuristic buried in that. The bounds most likely to be wrong are precisely the ones that feel most unthinkable — because “unthinkable” is, in practice, just a synonym for “long-confirmed,” and long-confirmed is exactly the assumption nobody has re-examined in years. A bound that got violated last quarter is on everyone's mind and well-defended with retries and alerts. A bound that has held for thirty-seven years is invisible, deeply load-bearing, and completely untested, because the testing stopped the moment everyone became sure. So when you go looking, don't start with the assumptions that feel shaky — start with the ones that feel certain. The sentence “oil can't go negative” and the sentence “that can't happen” are, structurally, the same sentence, and the second one may be the most expensive sentence in engineering. Rank your bounds not by how likely they seem to break, but by how confident everyone is that they won't — and audit the top of that list first.

The same bug is in your stack right now

You almost certainly do not price crude oil. You also almost certainly ship this exact bug, because every software system is packed with quiet assertions about what values can't be — assumptions so deep they've stopped looking like assumptions at all:

Each one is a claim about reality that reality never signed. And they hide in a small, greppable set of places: in your types (unsigned, where the real-world quantity is not), in your validators (if (x < 0) throw), in your schemas (UNSIGNED INT, which physically cannot store the truth), in your dashboards (a y-axis pinned at zero), in your tests (which never feed the extreme value, so never catch the failure), and — most stubbornly of all — in the mental models of the people who “know” it can't happen and therefore never write the branch for when it does.

Two failure modes worth naming

Two of these deserve to be called out by name, because they are the ones that bite hardest and look most like virtue right up until they don't.

The validator that rejects the truth is the bug. if (price < 0) throw Error("invalid price") reads like defensive programming — and most days it is. But on the day the real price is −$37.63, that line does not shield your system from a bad value; it rejects a true one, and the guard you were proud of becomes the outage. There is a crucial distinction your validators usually blur: rejecting input that is malformed (a price of "banana", a negative count of physical apples) is correct, but rejecting input that is merely surprising-but-true is how you build a system that fails precisely when the world gets interesting. Before you write a bound, ask which kind of bound it is.

The dashboard that clips at zero blinds you when it matters most. A monitoring chart whose y-axis starts at zero literally cannot draw −$37 — the line just pins to the floor. The operators staring at it during the most important hour of the year could not see what was happening, because an assumption baked into the visualization had quietly amputated reality below the axis. This is the more dangerous of the two, because it fails silently: no exception, no alert, just a flat line and a room full of people trusting it. Your monitoring's hidden bounds are every bit as load-bearing as your code's.

The move: hunt the bounds, support the tail before it arrives

CME's playbook ports directly, and it's four steps:

  1. Audit your bounds. Grep for the fingerprints: >= 0, unsigned, MIN_VALUE, MAX_VALUE, if (x < 0) throw, enum sets with no unknown/other case, dashboard axes pinned to zero. Every hit is a hypothesis your system is making about reality. Most you've never consciously decided.
  2. Ask the physical question. For each bound that matters, ask not “is this likely?” but “what real-world constraint would force this past the line?” Negative oil took full storage plus forced delivery. Your negative balance takes a settlement delay plus two concurrent withdrawals. Your multi-second latency takes one cold start behind one DNS timeout. The mechanism is almost always mundane and entirely possible — you just have to go looking for it on purpose.
  3. Build, or just enable, the tail. Widen the type, store the signed value, loosen the validator to reject malformed-not-surprising, let the dashboard render below zero, add the extreme-value test that's currently missing. As often as not — exactly as with CME — the capability is already present and one config flag away.
  4. Do it before the incident. This is the entire lesson, and it is the only part that's hard. CME enabled negative prices on a quiet day with a test environment and a memo to members. Everyone else “enabled” negative prices at −$37.63, in production, in real time, with money draining out of customer accounts. Same change. Wildly different day to make it.

The minus sign in front of $37.63 was not an anomaly to be filtered out. It was the most honest number the oil market printed all year — and the systems that treated it as an error were the ones that had quietly stopped listening to reality in favor of their own assumptions. Every >= 0 in your codebase is a small bet that the world will go on being polite, and most days that bet pays off, which is exactly why nobody re-examines it. The discipline here isn't paranoia about every bound; it's knowing which of your assumptions, if it broke, would break you — and then spending one quiet afternoon supporting that tail while it's still hypothetical. Ask CME's question early: can this number go where we swear it can't — and what happens to us when it does? Then go turn on the negative prices yourself, before the market turns them on for you.


Sources

  1. NYMEX/CME WTI May 2020 contract settled at −$37.63 on April 20, 2020 (intraday low −$40.32); first negative settle since WTI futures began trading in 1983. Widely reported (CME Group; contemporaneous market coverage).
  2. CME Group clearing advisory Chadv20-160, “Testing Opportunities in CME's ‘New Release’ Environment for Negative Prices and Strikes for Certain NYMEX Energy Contracts,” dated April 15, 2020. CME has stated support for zero/negative futures prices was already standard across its systems.
  3. Cushing, OK working storage ~76 million barrels; EIA weekly data showed Cushing filling through April 2020 (~80% by the April 20–21 expiry, ~83% by the week of April 24), with remaining capacity already leased/committed (Pipeline & Gas Journal, April 2020; U.S. EIA).
  4. Interactive Brokers Group SEC filings (Form 10-Q, 2020): initial provisional loss ~$88 million from the negative-price event, revised to ~$104 million after compensating affected customers.
  5. United States Oil Fund (USO) restructured away from front-month WTI futures in late April 2020 (USO SEC filings/prospectus supplements).

The most-certain bound is the one nobody recorded deciding. Record what your system actually does.

The bounds that break you are the ones so long-confirmed that no one remembers choosing them — there's no record of the assumption, so there's no way to audit it against reality. The same gap shows up the moment an AI agent hits a state its assumptions said was impossible: if all you have is the agent's own after-the-fact account, you can't tell a surprising-but-true event from a malformed one. Chain of Consciousness anchors every agent action to a verifiable external record — so when reality goes where your code swore it couldn't, you have the receipt of what actually happened, not a dashboard that clipped it off the bottom of the chart.

See a verified provenance chain · Hosted Chain of Consciousness

pip install chain-of-consciousness  ·  npm install chain-of-consciousness