Bitcoin rainbow chart model
Bitcoin's price on a log scale against a regression line fitted to its whole history, with nine coloured bands around it. Blue is read as "cheap", red as "expensive". Updated every few hours.
Now (price as of 21 Oct 2026, 20:03 UTC): BTC at $84,455 sits in band 4 of 9 - "Still cheap" on this chart. Fitted only on data known before this month, it would be band 4 ("Still cheap"). Does buying blue and selling red actually work? Tested without hindsight: no.
Band prices today
| Band | Name | From | To |
|---|---|---|---|
| 9 | Maximum bubble | $369,432 | $506,120 |
| 8 | Sell | $269,660 | $369,432 |
| 7 | Is this a bubble? | $196,833 | $269,660 |
| 6 | Hold | $143,674 | $196,833 |
| 5 | Fair | $104,872 | $143,674 |
| 4 | Still cheap | $76,550 | $104,872 |
| 3 | Accumulate | $55,876 | $76,550 |
| 2 | Buy | $40,785 | $55,876 |
| 1 | Fire sale | $29,771 | $40,785 |
How the chart is built
- Daily BTC/USD closes from Bitstamp since December 2012.
- A straight line is fitted to log10(price) against log10(days since the genesis block, 3 January 2009).
- Bands are drawn at 0.5 standard deviations of the distance from that line: band 5 is the line ±0.25 sd, bands 1 and 9 are beyond ±1.75 sd.
Popular rainbow charts use the same idea with slightly different curves and band widths. None of the bands is fixed: the fitted curve moves with every new price. Monthly returns since 2013 are on the bitcoin monthly returns page.
Use it on TradingView (free Pine Script)
The same model as an open-source indicator: both versions of the bands on one chart - fitted with hindsight (translucent) and refitted bar by bar on past data only (solid) - plus a table with today's band under each.
- Open a daily BITSTAMP:BTCUSD chart on TradingView and switch the price scale to logarithmic (Alt+L).
- Open Pine Editor, replace its contents with the code below and click "Add to chart".
- Zoom out with the mouse wheel (the 5Y/All buttons switch to weekly or monthly bars) and compare 2017 and 2021 in both versions.
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © edgeornot
//@version=6
indicator("Bitcoin Rainbow: with vs without hindsight [Edge or Not]", shorttitle = "Rainbow hindsight test", overlay = true,
max_polylines_count = 20, max_bars_back = 5000)
// ─── Inputs ─────────────────────────────────────────────────────────────────────────────
showHonest = input.bool(true, "Bands without hindsight (fit on past bars only)", group = "Show")
showHindsight = input.bool(true, "Bands with hindsight (one fit on all bars)", group = "Show")
minBars = input.int(1000, "Bars before the honest fit starts", minval = 100, group = "Fit")
stepBars = input.int(5, "Hindsight bands: draw every N bars", minval = 1, maxval = 30, group = "Fit")
showTable = input.bool(true, "Show current band table", group = "Show")
fitStart = input.time(timestamp("2012-12-01T00:00:00"), "Fit start date (earlier bars ignored)", group = "Fit",
tooltip = "Default matches the Edge or Not live chart and test (Bitstamp data from Dec 2012). Popular rainbow charts often start in 2010-2011.")
// ─── Model: log10(price) = a + b * log10(days since genesis block), 9 bands by residual z ─
GENESIS = timestamp("UTC", 2009, 1, 3, 0, 0)
var array<float> Z = array.from(-2.25, -1.75, -1.25, -0.75, -0.25, 0.25, 0.75, 1.25, 1.75, 2.25)
var array<color> COL = array.from(#2b3a98, #3570d0, #36aecb, #37c48a, #9fd24a, #f0cf3a, #f39b37, #ec6436, #d63737)
var array<string> NAMES = array.from("Fire sale", "Buy", "Accumulate", "Still cheap", "Fair", "Hold", "Is this a bubble?", "Sell", "Maximum bubble")
if not timeframe.isdaily
runtime.error("Use a daily chart of BTCUSD (for example BITSTAMP:BTCUSD).")
days = (time - GENESIS) / 86400000.0
x = math.log10(math.max(days, 1))
y = math.log10(close)
// running sums of all bars BEFORE the current one -> the honest fit never sees the current or future bars
var float n = 0.0
var float sx = 0.0
var float sy = 0.0
var float sxx = 0.0
var float sxy = 0.0
var float syy = 0.0
fit(float _n, float _sx, float _sy, float _sxx, float _sxy, float _syy) =>
sxxC = _sxx - _sx * _sx / _n
sxyC = _sxy - _sx * _sy / _n
syyC = _syy - _sy * _sy / _n
b = sxyC / sxxC
a = (_sy - b * _sx) / _n
sig = math.sqrt(math.max(syyC - b * sxyC, 0) / (_n - 2))
[a, b, sig]
bandOf(float z) =>
int k = 1
for i = 1 to 8
if z > Z.get(i)
k += 1
k
inFit = time >= fitStart
float ha = na
float hb = na
float hs = na
if inFit and n >= minBars
[a0, b0, s0] = fit(n, sx, sy, sxx, sxy, syy)
ha := a0
hb := b0
hs := s0
honestEdge(int k) => showHonest and not na(ha) ? math.pow(10, ha + hb * x + Z.get(k) * hs) : na
p0 = plot(honestEdge(0), "Honest edge 0", color.new(COL.get(0), 60), display = display.pane)
p1 = plot(honestEdge(1), "Honest edge 1", color.new(COL.get(0), 60), display = display.pane)
p2 = plot(honestEdge(2), "Honest edge 2", color.new(COL.get(1), 60), display = display.pane)
p3 = plot(honestEdge(3), "Honest edge 3", color.new(COL.get(2), 60), display = display.pane)
p4 = plot(honestEdge(4), "Honest edge 4", color.new(COL.get(3), 60), display = display.pane)
p5 = plot(honestEdge(5), "Honest edge 5", color.new(COL.get(4), 60), display = display.pane)
p6 = plot(honestEdge(6), "Honest edge 6", color.new(COL.get(5), 60), display = display.pane)
p7 = plot(honestEdge(7), "Honest edge 7", color.new(COL.get(6), 60), display = display.pane)
p8 = plot(honestEdge(8), "Honest edge 8", color.new(COL.get(7), 60), display = display.pane)
p9 = plot(honestEdge(9), "Honest edge 9", color.new(COL.get(8), 60), display = display.pane)
fill(p0, p1, color.new(COL.get(0), 78))
fill(p1, p2, color.new(COL.get(1), 78))
fill(p2, p3, color.new(COL.get(2), 78))
fill(p3, p4, color.new(COL.get(3), 78))
fill(p4, p5, color.new(COL.get(4), 78))
fill(p5, p6, color.new(COL.get(5), 78))
fill(p6, p7, color.new(COL.get(6), 78))
fill(p7, p8, color.new(COL.get(7), 78))
fill(p8, p9, color.new(COL.get(8), 78))
// add the current bar to the sums only AFTER using them
if inFit
n += 1
sx += x
sy += y
sxx += x * x
sxy += x * y
syy += y * y
// keep x and time of every bar for the hindsight drawing on the last bar
var array<float> xs = array.new<float>()
var array<int> ts = array.new<int>()
if inFit
xs.push(x)
ts.push(time)
if barstate.islast and n > 3
[fa, fb, fs] = fit(n, sx, sy, sxx, sxy, syy)
if showHindsight
for k = 0 to 8
// sample every stepBars-th bar plus the last one, so the right edge of each band is vertical
idx = array.new<int>()
for i = 0 to xs.size() - 1 by stepBars
idx.push(i)
if idx.last() != xs.size() - 1
idx.push(xs.size() - 1)
pts = array.new<chart.point>()
for j = 0 to idx.size() - 1
i = idx.get(j)
pts.push(chart.point.from_time(ts.get(i), math.pow(10, fa + fb * xs.get(i) + Z.get(k + 1) * fs)))
for j = idx.size() - 1 to 0
i = idx.get(j)
pts.push(chart.point.from_time(ts.get(i), math.pow(10, fa + fb * xs.get(i) + Z.get(k) * fs)))
polyline.new(pts, closed = true, xloc = xloc.bar_time, line_color = color.new(COL.get(k), 100), fill_color = color.new(COL.get(k), 88))
if showTable
zH = (y - (fa + fb * x)) / fs
string honestTxt = "n/a"
if not na(ha)
zO = (y - (ha + hb * x)) / hs
kO = bandOf(zO)
honestTxt := str.tostring(kO) + " - " + NAMES.get(kO - 1)
kH = bandOf(zH)
tb = table.new(position.top_right, 2, 4, bgcolor = color.new(#0e1117, 10), border_color = color.new(color.gray, 70), border_width = 1)
tb.cell(0, 0, "Band today", text_color = color.white)
tb.cell(1, 0, "1 = cheapest, 9 = most expensive", text_color = color.gray)
tb.cell(0, 1, "With hindsight", text_color = color.white)
tb.cell(1, 1, str.tostring(kH) + " - " + NAMES.get(kH - 1), text_color = COL.get(kH - 1))
tb.cell(0, 2, "Without hindsight", text_color = color.white)
tb.cell(1, 2, honestTxt, text_color = color.white)
tb.cell(0, 3, "Edge or Not", text_color = #ffb020)
tb.cell(1, 3, "Hindsight bands fit past tops by construction", text_color = color.gray)
Mozilla Public License 2.0. Default fit start: 1 Dec 2012, as in our test; change it in the inputs.
FAQ
What is the bitcoin rainbow chart?
A logarithmic regression of bitcoin's price against time, with coloured bands around the fitted line. Lower bands are read as cheap, upper bands as expensive.
Does the bitcoin rainbow chart work?
Only in hindsight. With bands fitted on all data, buying in bands 1-3 and selling in 7-9 turned $1 into $398 from 2016 to 2026 against $194 for buy-and-hold. With bands fitted only on data known at the time, the same rule made $22.
Is it a price prediction?
No. It assumes bitcoin keeps growing along the same curve; the curve itself changes as new prices arrive. Not investment advice.