The first time I ran a backtest that mixed a crypto asset with an equity, the equity curve came out looking so clean that I almost believed it. Smooth, low drawdown, the kind of Sharpe you screenshot. Then I spent an afternoon pulling the thing apart and found that almost none of the smoothness was real. It came from how I had lined up the data, not from any edge in the strategy. That is the trap with cross-asset backtests. Single-asset tests fail loudly. Cross-asset tests fail quietly, and the failure usually points the wrong way, making a mediocre idea look like a good one.
The reason is that the two assets do not live on the same clock, do not settle on the same calendar, and often are not even priced in the same currency. Every one of those gaps is a place where your data-alignment code can quietly fabricate returns. Below is how I think about the main ones, and a checklist I run before I trust any number from a mixed-asset test.
The 24/7 versus session-bound clash
Crypto trades continuously. A stock trades during a session, roughly six and a half hours on a US exchange, then goes dark until the next open. When you join those two series on a shared time index, you have to decide what the equity is doing during all the hours the market is closed. Most naive joins forward-fill the last equity price across the gap. That feels harmless. It is not.
Forward-filling a closed market means your backtest can rebalance, or measure a signal, at a timestamp where one leg has a live price and the other has a stale one that nobody could have traded at. If your strategy reacts to the crypto move over a weekend and adjusts the equity leg, you are trading against a price that did not exist until Monday's open. The PnL you book is imaginary. Worse, it tends to be favorable, because the fill assumption is frictionless and instant.
The rule I follow is that a leg can only transact when its own market is open. If I want a daily cross-asset series, I resample everything to the equity's trading calendar and let the crypto leg aggregate its 24-hour activity into that same bar. If I want intraday, I mask the equity leg entirely outside session hours and forbid any trade or signal evaluation there. It is less elegant than one tidy joined dataframe, but it stops the machine from trading in the dark.
Timezones and holidays, the boring bugs that cost the most
Almost every cross-asset alignment bug I have chased came down to a timezone that was implicitly wrong. Crypto exchanges report in UTC. Equity data vendors often report in the exchange's local time, sometimes with daylight-saving shifts baked in and sometimes not. If you join on a naive timestamp and one series is UTC while the other is Eastern, you have introduced a several-hour offset that lines up today's crypto bar with a slightly different slice of the equity day. The backtest still runs. The numbers are just wrong in a way no error message will ever tell you about.
Holidays are the same story with a longer tail. Crypto has none. Equity markets close for national holidays, and those calendars differ by country, so a strategy pairing a US name with a London or Tokyo name has three different sets of closed days to reconcile. Half-days are the sneakiest. An early close looks like a full session to any code that assumes a fixed bar count, and you end up attributing a full day of crypto drift against a half day of equity exposure.
Two habits fix most of this. Store every timestamp in UTC internally and only convert to local time at the exact moment you need to ask whether a specific market was open. And drive your calendar off an actual exchange holiday schedule rather than inferring open days from whether the data happens to have rows, because missing data and a market holiday look identical to a row-counting heuristic and they are not the same thing.
Corporate actions and the phantom gap
Equities split, pay dividends, and occasionally do stranger things like spinoffs and rights issues. Every one of those events moves the raw quoted price without moving the economic value you actually held. A two-for-one split halves the price overnight. If your equity series is not adjusted for it, your backtest sees a fifty percent overnight crash that never happened, and depending on which side your strategy is on, that phantom gap becomes a phantom loss or a phantom windfall.
Crypto has its own version. Token migrations, chain splits, redenominations, and rebasing tokens all move the printed number without a matching move in what you own. It is rarer than an equity split but it exists, and it will not be in whatever adjustment routine your equity vendor uses.
The practical guidance is to know exactly which price series you are holding. A total-return or fully-adjusted series bakes dividends and splits back in, which is what you usually want for strategy PnL. A raw or close-only series does not. Mixing an adjusted equity series with a raw crypto series in the same test is fine as long as you did it on purpose and understand that the equity leg already includes reinvested distributions the crypto leg cannot. The failure mode is not knowing, and then wondering why one leg has mysterious overnight jumps clustered on ex-dividend dates.
PnL in one currency, finally
If one leg prices in dollars and the other prices in euros or a stablecoin that is not quite a dollar, your combined PnL is contaminated by an exchange rate you never chose to trade. This one is subtle because it can look like alpha. A strategy that appears to make money might just be long a currency that drifted your way over the sample.
Pick one accounting currency up front, convert every leg's PnL into it using the FX rate at the same timestamp as the trade, and keep the currency exposure as its own line item so you can see whether your returns came from the strategy or from the FX drift underneath it. Convert at trade time, not once at the end, or you will smear the whole period's currency move across every position evenly and hide where it actually landed.
The pre-flight checklist
Before I trust a cross-asset backtest, I walk this list. It takes ten minutes and it has saved me from shipping garbage more than once.
- Are all timestamps stored in UTC, converted to local only at the open-check?
- Is each leg forbidden from trading when its own market is closed, with no forward-filled fills across the gap?
- Is the equity calendar driven by a real exchange holiday schedule, including half-days, rather than inferred from present rows?
- Is every equity leg either consistently adjusted or consistently raw, and do I know which, and does that match the crypto leg's convention?
- Is all PnL converted to one accounting currency at trade-time FX, with currency exposure tracked separately?
- Does the number of trading bars per leg match the real number of sessions in the window, not a fixed assumption?
None of this is exotic. It is plumbing, and plumbing is exactly the sort of thing that gets skipped when the equity curve already looks good. The tell is almost always the same. A cross-asset result that looks better than either asset does on its own deserves suspicion before it deserves capital, because clean alignment tends to produce honest, slightly disappointing numbers, and a silent bug tends to produce beautiful ones. When we built the strategy backtesting inside Blockcircle, most of the hard engineering went into this layer rather than the signal logic, because the signals were never the part that lied to us. The calendar was.