Data Analytic Investments
On-ChainIntermediate

MVRV

Market Value to Realised Value

Compares Bitcoin's market cap to the aggregate cost basis of all coins — the most reliable macro cycle indicator.

Daily, Weekly (on-chain)
Bitcoin (BTC)
Python 3

What is it?

MVRV was developed by Murad Mahmudov and David Puell in 2018. Market Value (MV) is the current market capitalisation. Realised Value (RV) is the aggregate cost basis of all coins — calculated by valuing each coin at the price it last moved on-chain (its 'realised price'). MVRV = MV / RV. When MVRV is above 3.5, the average holder is sitting on 250%+ unrealised profit — historically a zone of extreme greed and cycle tops. When MVRV is below 1.0, the average holder is underwater — historically a zone of maximum fear and cycle bottoms. MVRV Z-Score normalises MVRV by its historical standard deviation for more precise extreme detection.

When to use it

  • Macro cycle positioning: MVRV above 3.5 is a signal to reduce long exposure and take profits; below 1.0 is a signal to accumulate.
  • MVRV Z-Score above 7: historically marks the final blow-off top of a Bitcoin bull cycle — a high-conviction sell signal for long-term holders.
  • MVRV Z-Score below 0 (negative): historically marks the deepest bear market lows — a high-conviction accumulation zone.
  • Trend confirmation: rising MVRV in the 1.0–3.0 range confirms a healthy bull market with room to run.
  • Combining with NUPL: MVRV and NUPL together provide a more complete picture of market sentiment and unrealised profit distribution.

Common pitfalls

  • MVRV is a Bitcoin-native metric. For altcoins, the realised value calculation is less reliable due to different UTXO models and token distribution mechanics.
  • MVRV is a slow-moving, macro indicator. It is not useful for timing short-term trades — it operates on weekly and monthly timeframes.
  • Lost coins (coins that have not moved in 5+ years) are included in the realised value calculation, which may overstate the true cost basis of active market participants.
  • Each cycle, the MVRV peak has been lower than the previous cycle (3.7 in 2013, 4.7 in 2017, 3.9 in 2021). Extrapolating past thresholds to future cycles may be unreliable.
  • MVRV data is available from Glassnode, CryptoQuant, and LookIntoBitcoin. Different providers may use slightly different methodologies — always specify your source.

Free code template

Requires Python 3.9+ and the listed pip packages. See inline comments for usage.

Python 3
# MVRV Analysis — DAI Python Template
# Data source: Glassnode API (requires API key)
# pip install requests pandas matplotlib

import requests
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from datetime import datetime

GLASS_API_KEY = 'YOUR_GLASSNODE_API_KEY'

def fetch_glassnode(metric: str, asset: str = 'BTC', resolution: str = '24h') -> pd.DataFrame:
    url = f'https://api.glassnode.com/v1/metrics/{metric}'
    params = {'a': asset, 'i': resolution, 'api_key': GLASS_API_KEY, 'f': 'JSON'}
    r = requests.get(url, params=params)
    r.raise_for_status()
    df = pd.DataFrame(r.json())
    df['t'] = pd.to_datetime(df['t'], unit='s')
    df = df.set_index('t').rename(columns={'v': metric.split('/')[-1]})
    return df

def plot_mvrv_zscore(df: pd.DataFrame):
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True)
    fig.patch.set_facecolor('#0d1f3c')

    # Price
    ax1.semilogy(df.index, df['price'], color='#3b82f6', linewidth=1.5, label='BTC Price')
    ax1.set_facecolor('#0d1f3c'); ax1.tick_params(colors='white')
    ax1.set_ylabel('Price (log)', color='white'); ax1.legend(facecolor='#1B3A6B', labelcolor='white')

    # MVRV Z-Score with colour zones
    zscore = df['mvrv_z_score']
    ax2.plot(df.index, zscore, color='#f59e0b', linewidth=1.5, label='MVRV Z-Score')
    ax2.axhline(7,   color='#ef4444', linestyle='--', alpha=0.7, label='Extreme Greed (7)')
    ax2.axhline(0,   color='#94a3b8', linestyle=':',  alpha=0.5, label='Neutral (0)')
    ax2.axhline(-0.5,color='#22c55e', linestyle='--', alpha=0.7, label='Extreme Fear (-0.5)')
    ax2.fill_between(df.index, zscore, 7,   where=(zscore >= 7),    alpha=0.3, color='#ef4444')
    ax2.fill_between(df.index, zscore, -0.5,where=(zscore <= -0.5), alpha=0.3, color='#22c55e')
    ax2.set_facecolor('#0d1f3c'); ax2.tick_params(colors='white')
    ax2.set_ylabel('MVRV Z-Score', color='white'); ax2.legend(facecolor='#1B3A6B', labelcolor='white')

    plt.tight_layout()
    plt.savefig('mvrv_zscore.png', dpi=150, bbox_inches='tight')
    plt.show()

# Fetch and plot
# price_df = fetch_glassnode('market/price_usd_close')
# mvrv_df  = fetch_glassnode('market/mvrv_z_score')
# df = price_df.join(mvrv_df)
# plot_mvrv_zscore(df)