Comparing two coins: rebase, then correlate returns, not prices
I wanted a simple "BTC vs ETH" panel on a side project. Two price lines on one chart looked useless straight away: one is around 80,000 dollars and the other around 2,300, so one line is flat and the other is noise. Here is what I ended up doing instead, with numbers from about three weeks of data (100 points per coin, 6 to 29 October). 1. Join on the timestamp, never on the index Both series came back from the API with the same 100 timestamps. That is luck, not a contract. Any gap in one feed shifts every point after it by one slot, and the chart still renders, just wrong. So I join first: function join(a, b) { const byTs = new Map(b.map(p => [p.ts, p.price])); return a .filter(p => byTs.has(p.ts)) .map(p => ({ ts: p.ts, a: p.price, b: byTs.get(p.ts) })); } If the joined length is much smaller than either input, you have an alignment problem to fix before you draw anything. 2. Rebase both to 100 The fix for the scale problem is to divide ea
I wanted a simple "BTC vs ETH" panel on a side project. Two price lines on one chart looked useless straight away: one is around 80,000 dollars and the other around 2,300, so one line is flat and the other is noise. Here is what I ended up doing instead, with numbers from about three weeks of data (100 points per coin, 6 to 29 October).
1. Join on the timestamp, never on the index
Both series came back from the API with the same 100 timestamps. That is luck, not a contract. Any gap in one feed shifts every point after it by one slot, and the chart still renders, just wrong. So I join first:
function join(a, b) {
const byTs = new Map(b.map(p => [p.ts, p.price]));
return a
.filter(p => byTs.has(p.ts))
.map(p => ({ ts: p.ts, a: p.price, b: byTs.get(p.ts) }));
}
If the joined length is much smaller than either input, you have an alignment problem to fix before you draw anything.
2. Rebase both to 100
The fix for the scale problem is to divide each series by its first value:
const rebase = xs => xs.map(x => (100 * x) / xs[0]);
Now both lines start at 100 and the chart answers the question people actually ask: who did better since the start of the window. On my data BTC ended at 93.7 and ETH at 85.8. That reads instantly, where 80,103 and 2,318.83 do not.
One catch: the result depends entirely on the start point. Move the window by a day and the gap changes. I show the start date next to the chart for that reason.
3. Correlate returns, not prices
This is the mistake I almost shipped. Correlating the two price series gives 0.95 here. It looks like a strong statement about how tied the coins are, but most of it just says both went down over the same three weeks. Two unrelated series that both trend will correlate on price.
Correlate period returns instead:
const logReturns = xs => xs.slice(1).map((x, i) => Math.log(x / xs[i]));
function corr(x, y) {
const mx = x.reduce((s, v) => s + v, 0) / x.length;
const my = y.reduce((s, v) => s + v, 0) / y.length;
let sxy = 0, sxx = 0, syy = 0;
for (let i = 0; i < x.length; i++) {
sxy += (x[i] - mx) * (y[i] - my);
sxx += (x[i] - mx) ** 2;
syy += (y[i] - my) ** 2;
}
return sxy / Math.sqrt(sxx * syy);
}
On the same data the return correlation is 0.91. Still high, because these two really do move together, but now the number means something: when BTC has a bad few hours, ETH usually has one too. With points spaced unevenly, as mine were, treat it as a rough figure rather than a precise one.
4. Plot the ratio as a third line
The most useful single line turned out to be ETH divided by BTC. It went from 0.0316 to 0.0289 over the window, down about 8%. That one line tells you ETH lost ground against BTC, which the two rebased lines only hint at.
What I'd check before trusting any comparison page
- The start date: everything rebased depends on it.
- The join: are the timestamps matched, or just the array positions?
- The correlation: is it on prices or on returns?
If you want a page that already lays two coins side by side (price, market cap, supply, performance), there is one at https://www.thecoinanalysis.com/compare. It's handy when you want the static facts next to the chart you built.