Crypto-Native·On-Chain Signal Generation·Intermediate

Realized Price Analysis

Analyze Bitcoin realized price levels representing the on-chain aggregate cost basis of coins last moved within different time cohorts, identifying key psychological support and resistance levels where specific holder groups historical entry prices create behavioral anchoring effects.

cryptomarket-analysison-chain

Realized Price Analysis — Crypto-Native

Category: Crypto-Native | Subcategory: On-Chain Signals


What This Notebook Does

The Realized Price (also called Realized Cap / Circulating Supply) is the average on-chain cost basis of all BTC holders — the price at which each coin last moved, weighted by supply.

Realized Price = Realized Cap / Circulating Supply
                = Σ(price_when_last_moved × supply_at_that_price) / total_supply

It is the most important on-chain support/resistance level in crypto:

  • BTC Price > Realized Price: Market is in aggregate profit → bull market
  • BTC Price < Realized Price: Market is in aggregate loss → bear market
  • Realized Price itself: Acts as a gravitational level — prices tend to bounce or find support here

This notebook:

  1. Fetches Realized Price and BTC price data (Glassnode or synthetic)
  2. Computes the premium/discount of market price vs realized price
  3. Derives the Realized Price gradient as a trend signal
  4. Identifies historical realized price crossings — strong bull/bear regime signals
  5. Builds a composite signal: premium + gradient + crossing
  6. Backtests and exports the dataset

Resources

[ ]
!pip install numpy pandas matplotlib seaborn requests --quiet
[ ]
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import requests
import warnings

warnings.filterwarnings('ignore')
%matplotlib inline
plt.rcParams['figure.figsize'] = (14, 5)
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
print('Imports ready.')
Imports ready.

Section 1 — Configuration

[ ]
USE_SYNTHETIC = False
GLASSNODE_API_KEY = 'YOUR_API_KEY_HERE'
SIMULATION_DAYS = 1460
GRADIENT_WINDOW = 30   # days for realized price gradient
PREMIUM_BUY_ZONE  = -0.20  # buy when market is 20%+ below realized price
PREMIUM_SELL_ZONE =  1.50  # sell when market is 150%+ above realized price
print('Config ready.')
Config ready.

Section 2 — Data

[ ]
def fetch_realized_price(api_key: str) -> pd.DataFrame:
    """
    Fetch BTC realized price and market price from Glassnode.

    Returns
    -------
    pd.DataFrame  Columns: btc_price, realized_price  (daily, indexed by date).
    """
    base = 'https://api.glassnode.com/v1/metrics'
    params = {'a': 'BTC', 'i': '24h', 'api_key': api_key}
    rp_r  = requests.get(f'{base}/market/price_realized_usd',  params=params, timeout=30)
    mkt_r = requests.get(f'{base}/market/price_usd_close',     params=params, timeout=30)
    if rp_r.status_code != 200 or mkt_r.status_code != 200:
        return None
    rp  = pd.DataFrame(rp_r.json()).rename(columns={'t': 'date', 'v': 'realized_price'})
    mkt = pd.DataFrame(mkt_r.json()).rename(columns={'t': 'date', 'v': 'btc_price'})
    df  = rp.merge(mkt, on='date')
    df['date'] = pd.to_datetime(df['date'], unit='s')
    return df.set_index('date').sort_index()


def generate_synthetic_realized_price(n_days: int = 1460, seed: int = 42) -> pd.DataFrame:
    """
    Simulate a BTC price cycle with a realistic realized price curve.

    The realized price grows slowly (it only updates when coins move), roughly
    tracking a 180-day lagged and smoothed version of market price.

    Returns
    -------
    pd.DataFrame  Columns: btc_price, realized_price  (daily, indexed by date).
    """
    rng = np.random.default_rng(seed)
    t = np.linspace(0, 2 * np.pi, n_days)
    cycle = np.sin(t - np.pi / 2) * 0.5 + 0.5
    btc_price = 10_000 + 70_000 * cycle**1.5 + np.cumsum(rng.normal(0, 300, n_days))
    btc_price = np.maximum(btc_price, 4_000)
    # Realized price = 180-day EMA of market price (slow accumulation)
    rp_series = pd.Series(btc_price).ewm(span=180).mean().values
    rp_series += rng.normal(0, 100, n_days)  # small noise
    rp_series = np.maximum(rp_series, 3_000)
    idx = pd.date_range('2020-01-01', periods=n_days, freq='D')
    return pd.DataFrame({'btc_price': btc_price, 'realized_price': rp_series}, index=idx)


if USE_SYNTHETIC or not GLASSNODE_API_KEY or GLASSNODE_API_KEY == 'YOUR_API_KEY_HERE':
    df = generate_synthetic_realized_price(SIMULATION_DAYS)
    print(f'Synthetic data: {len(df)} days')
else:
    df = fetch_realized_price(GLASSNODE_API_KEY)
    if df is None:
        df = generate_synthetic_realized_price(SIMULATION_DAYS)
        print('API failed — synthetic fallback')
    else:
        print(f'Glassnode data: {len(df)} days')

print(df.tail())
Synthetic data: 1460 days
            btc_price  realized_price
2023-12-26     4000.0     5645.081321
2023-12-27     4000.0     5592.184602
2023-12-28     4000.0     5601.836149
2023-12-29     4000.0     5301.088007
2023-12-30     4000.0     5478.775794

fetch_realized_price Function

This function is responsible for fetching historical Bitcoin (BTC) realized price and market price data directly from the Glassnode API. It constructs API requests, retrieves JSON data, and processes it into a pandas DataFrame, indexed by date. This allows for direct use of real-world on-chain data for analysis.

generate_synthetic_realized_price Function

This function creates synthetic BTC price and realized price data. It's designed to simulate a realistic market cycle, providing a substitute dataset when Glassnode API access is unavailable or when the user prefers to work with simulated data for testing and demonstration purposes. It models the slow-moving nature of realized price relative to market price.

Section 3 — Derived Metrics

[ ]
# Premium: how far market price is above/below realized price
df['premium'] = (df['btc_price'] - df['realized_price']) / df['realized_price']

# Realized price gradient: 30-day percentage change in realized price
df['rp_gradient'] = df['realized_price'].pct_change(GRADIENT_WINDOW)

# Regime: above or below realized price
df['regime'] = np.where(df['btc_price'] > df['realized_price'], 'Bull', 'Bear')

# Crossings: price crossing realized price level
df['prev_regime'] = df['regime'].shift(1)
df['crossing'] = (df['regime'] != df['prev_regime']) & df['prev_regime'].notna()
df['cross_type'] = np.where(
    df['crossing'] & (df['regime'] == 'Bull'), 'Bull Cross',
    np.where(df['crossing'] & (df['regime'] == 'Bear'), 'Bear Cross', None)
)

bull_regime_pct = (df['regime'] == 'Bull').mean()
print(f'Bull regime: {bull_regime_pct:.0%} of days')
print(f'Bull crossings: {(df["cross_type"]=="Bull Cross").sum()}')
print(f'Bear crossings: {(df["cross_type"]=="Bear Cross").sum()}')
Bull regime: 50% of days
Bull crossings: 17
Bear crossings: 17

Section 4 — Signal Generation

[ ]
def generate_realized_price_signal(df: pd.DataFrame,
                                   premium_buy: float,
                                   premium_sell: float) -> pd.DataFrame:
    """
    Generate trading signals based on realized price premium.

    BUY  : price drops to premium_buy level (deep discount to realized)
    SELL : price reaches premium_sell level (large premium over realized)
    Also uses bull/bear crossings as confirmation.

    Parameters
    ----------
    df           : DataFrame with 'premium' and 'cross_type' columns.
    premium_buy  : Premium threshold for buy signal (negative = discount).
    premium_sell : Premium threshold for sell signal.

    Returns
    -------
    pd.DataFrame  Input with added 'signal' column: 1=buy, -1=sell, 0=hold.
    """
    df = df.copy()
    df['signal'] = 0
    # Buy: entering bull regime OR very low premium
    df.loc[df['cross_type'] == 'Bull Cross', 'signal'] = 1
    df.loc[(df['premium'] <= premium_buy) & (df['regime'] == 'Bear'), 'signal'] = 1
    # Sell: extreme premium OR entering bear regime
    df.loc[df['cross_type'] == 'Bear Cross', 'signal'] = -1
    df.loc[df['premium'] >= premium_sell, 'signal'] = -1
    return df

df = generate_realized_price_signal(df, PREMIUM_BUY_ZONE, PREMIUM_SELL_ZONE)
print(f"BUY signals : {(df['signal']==1).sum()}")
print(f"SELL signals: {(df['signal']==-1).sum()}")
BUY signals : 509
SELL signals: 17

generate_realized_price_signal Function

This function implements the core logic for generating trading signals (buy, sell, or hold) based on the realized price premium and market regime crossings. It identifies potential entry points when the market is significantly discounted to the realized price or when it crosses into a bull regime, and exit points when the market is at an extreme premium or crosses into a bear regime.

Section 5 — Visualization

[ ]
fig, axes = plt.subplots(3, 1, figsize=(14, 12), sharex=True)
fig.suptitle('Realized Price Analysis', fontsize=14, fontweight='bold')

# Panel 1: Price vs Realized Price
ax1 = axes[0]
ax1.plot(df.index, df['btc_price'],       color='#1976d2', lw=1.5, label='BTC Price')
ax1.plot(df.index, df['realized_price'],  color='#e53935', lw=2.0, ls='--', label='Realized Price')
buys  = df[df['signal'] ==  1]
sells = df[df['signal'] == -1]
ax1.scatter(buys.index,  buys['btc_price'],  marker='^', color='#43a047', s=80, zorder=5, label='BUY')
ax1.scatter(sells.index, sells['btc_price'], marker='v', color='#e53935', s=80, zorder=5, label='SELL')
bull = df[df['regime'] == 'Bull']
ax1.fill_between(df.index, df['btc_price'], df['realized_price'],
                  where=df['regime']=='Bull', alpha=0.08, color='green', label='Premium')
ax1.fill_between(df.index, df['btc_price'], df['realized_price'],
                  where=df['regime']=='Bear', alpha=0.08, color='red',   label='Discount')
ax1.set_ylabel('Price (USD)')
ax1.legend(fontsize=8)
ax1.set_title('BTC Price vs Realized Price')

# Panel 2: Premium %
ax2 = axes[1]
ax2.plot(df.index, df['premium'] * 100, color='#7b1fa2', lw=1.2)
ax2.axhline(0,                    color='black',   ls='--', lw=1)
ax2.axhline(PREMIUM_BUY_ZONE*100, color='#43a047', ls=':',  lw=1, label='Buy zone')
ax2.axhline(PREMIUM_SELL_ZONE*100,color='#e53935', ls=':',  lw=1, label='Sell zone')
ax2.set_ylabel('Premium over Realized Price (%)')
ax2.legend(fontsize=8)
ax2.set_title('Market Premium / Discount to Realized Price')

# Panel 3: Realized price gradient
ax3 = axes[2]
colors = ['#43a047' if v >= 0 else '#e53935' for v in df['rp_gradient'].fillna(0)]
ax3.bar(df.index, df['rp_gradient'] * 100, color=colors, width=1, alpha=0.7)
ax3.axhline(0, color='black', lw=0.8)
ax3.set_ylabel(f'Realized Price {GRADIENT_WINDOW}d Change (%)')
ax3.set_title('Realized Price Gradient (Accumulation Velocity)')

plt.tight_layout()
plt.show()
cell output

Section 6 — Export

[ ]
out = df[['btc_price', 'realized_price', 'premium', 'rp_gradient', 'regime', 'signal']]
out.to_csv('realized_price_analysis.csv')
print('Saved: realized_price_analysis.csv')
Saved: realized_price_analysis.csv

Conclusion

This notebook successfully implemented a realized price analysis framework for Bitcoin. It demonstrates how to fetch or simulate necessary data, derive key metrics like premium and gradient, and generate trading signals based on these on-chain indicators. The visualization clearly illustrates the relationship between market price and realized price, the premium/discount levels, and the realized price gradient, providing a comprehensive view of market cycles from an on-chain perspective. The generated signals can serve as a foundation for further backtesting and strategy development, offering insights into potential buy and sell opportunities based on Bitcoin's intrinsic value as represented by the realized price.