I keep running into the same quiet failure in crypto backtests, and it almost never shows up as an error. Someone builds a rotation strategy, tests it on the current top 100 by market cap, and gets a curve that looks incredible. The strategy is fine. The universe is the problem. That list of top 100 coins is a list of things that survived long enough to be worth ranking today, which means every backtest that starts from it has already quietly filtered out most of the ways you could have lost money.
This is survivorship bias, and in crypto it is worse than in equities because the death rate is higher and the data is messier. Thousands of tokens have gone to zero, been delisted, or turned into ghost chains with no liquidity. If your universe only contains the ones that are still around, you are not testing a strategy. You are testing whether picking from a pre-filtered set of winners makes money, and it always does.
What point-in-time actually means here
The fix is to build a universe that answers a narrow question for every rebalance date in your test: what could I have actually bought on this day, at a size that mattered, given only information available at the time. Not what looks investable now. What was investable then.
That has three moving parts, and each one is a place people cut corners.
- Existence. A coin should only enter the universe on or after the date it was genuinely tradeable, not the date its whitepaper came out and not some backfilled listing date a data vendor stamped on it later.
- Death. Delisted, rugged, and abandoned tokens have to stay in the universe up to the point they died, then leave. If they silently vanish from your data, every loss they would have caused vanishes with them.
- Liquidity. Even among coins that existed and were alive, most were not investable at meaningful size on any given day. The universe has to be gated on liquidity as it stood on the rebalance date, not on liquidity as it looks in hindsight.
Reconstructing listing dates without lying to yourself
The naive move is to pull a coin's price history and use the first row as its birthday. That is roughly right and occasionally very wrong. A lot of vendors backfill early history from thin or synthetic sources, so you get a smooth price series stretching back before the asset had any real venue, any depth, or any way for you to have touched it.
What I do instead is anchor listing dates to when a real, reachable venue started quoting the asset with actual volume. Practically that means taking the earliest date where a major exchange reports non-trivial daily volume, not just a price. If a coin shows a price but essentially zero volume for its first stretch of history, I treat that stretch as not investable and push the effective listing date forward to where volume becomes real. It is more conservative, and conservative is the correct direction to be wrong in when you are trying not to fool yourself.
The subtle trap is timezone and settlement boundaries. Daily bars from different sources close at different times, and a rebalance that reads a bar which had not actually closed yet is a small lookahead leak. Pick one close convention, usually UTC midnight, and force everything onto it before it touches the universe logic.
Keeping the dead in the file
This is the part almost everyone skips, and it is the part that matters most. You need the tokens that failed. Every coin that got delisted from its main venue, drifted to zero volume, or collapsed has to be present in your history right up to its last tradeable day, then marked as gone.
Getting this list is genuinely annoying because dead things stop generating data, so nobody maintains clean records of them. A workable approach is to build the universe as a union over time rather than a snapshot. Instead of asking "what are the top 100 today," you walk forward month by month and record the top N as they stood at each of those historical moments, then take the union of every coin that ever qualified. A coin that was top 50 for two years and then died is in your file for exactly the window it belonged, and it exits when its liquidity does. That single change tends to be the difference between a backtest that survives contact with reality and one that does not.
Gating on liquidity as it stood
Existence and death get you a candidate list. Liquidity turns it into an investable one. For each rebalance date, apply the same filters you would actually trade against, using only the trailing data available on that date. A reasonable default is a minimum on trailing dollar volume, computed over something like a trailing thirty day window, plus a check that the asset has enough continuous history to be measured at all.
Two rules of thumb keep this honest. First, size your volume floor to the capital you are pretending to deploy. A strategy that clears at ten thousand dollars of notional is a different universe from one at ten million, and testing a large book against a universe that only a small book could fill is one of the most common ways a great looking backtest turns out to be untradeable. Second, apply the filter on the rebalance date and hold the roster until the next one. Re-checking liquidity every single day quietly injects future information about which names stayed liquid.
A recipe you can actually follow
Here is the sequence I use when I build one of these from scratch.
- Pull the widest candidate set you can, including things that no longer trade. Do not start from a current top list.
- For each candidate, derive a real listing date from first meaningful volume, not first price, on a reachable venue.
- For each candidate, derive a death date: the last day it had real liquidity before delisting or going dark. Leave it in the file until then.
- Normalize everything to one close convention so no rebalance ever reads a bar that had not closed.
- Walk forward date by date. On each rebalance date, filter to coins that exist, are not yet dead, and clear a trailing liquidity floor sized to your real book.
- Write the surviving roster per date to a universe file, keyed by date, with existence, death, and liquidity flags preserved so you can audit why any coin was in or out.
The output is boring on purpose. It is a table of dates and the coins that were genuinely tradeable on each, with the failures included at full weight. When I run a strategy against a universe built this way, the equity curve almost always gets worse, sometimes a lot worse, and that is the point. The drawdowns you see are drawdowns you would have actually lived through. Inside Blockcircle we treat this universe file as the foundation everything else sits on, because a scoring model or a rotation rule is only as honest as the set of things it was allowed to choose from.
If you only take one thing from this, make it the union-over-time step. Building the universe as everything that ever qualified, rather than everything that qualifies now, does more to fix a crypto backtest than any amount of tuning on the strategy itself. The winners will still be in there. So will everything that did not make it, which is the whole reason the number at the end of the test means anything.