TL;DR
- CoinGecko API tracks tokenized stocks across major blockchains, using tokenized-stock categories for discovery and the /coins/markets endpoints to retrieve prices.
- GeckoTerminal’s onchain endpoints provide deeper market insights, including liquidity, holder concentration, and daily OHLCV data for tokenized assets across Solana, Ethereum, Robinhood, and 200+ other chains.
- CoinGecko’s RWA endpoints (/rwas/{id}) aggregate every issuer’s tokenized version of the same stock, including tokenized pre-IPO companies like OpenAI and SpaceX, into a single price object.
Tokenized stocks such as the ones issued by xStocks, Ondo Finance, and bStocks can trade onchain across Solana, Ethereum, Robinhood, and many more chains. These tokens support continuous trading, allowing you to access price, liquidity, and trading activity at any time.
In this guide, you’ll build a Python toolkit using the CoinGecko API to discover tokenized stocks, retrieve market, liquidity, holder, and historical data, and stream real-time updates via WebSockets.
What Are Tokenized Stocks and How Do You Track Them Onchain?
Tokenized stocks are blockchain tokens that represent exposure to real-world equities or ETFs. Issuers such as Backed (provider behind xStocks) hold the underlying assets and issue tokens designed to track their value, typically on a roughly 1:1 basis. These tokens are issued across multiple networks, including Solana as SPL tokens, Ethereum and other EVM-compatible chains as ERC-20 tokens, and the Robinhood chain. Because multiple issuers can tokenize the same equity across different blockchains, a single stock may exist as several distinct onchain tokens. For example, Tesla has separate tokenized versions from issuers including Backed, Ondo Finance, BTech Holdings, and Robinhood Europe.
Tokenized stocks offer several advantages compared to traditional equities, including 24/7 trading, fractional ownership that lowers the barrier to accessing higher-priced shares, and seamless integration with DeFi and other Web3 infrastructure. Their onchain settlement also enables them to be used as collateral, supplied to liquidity pools to earn trading fees, or deposited into lending protocols to mint stablecoins that can be staked for yield, and incorporated into decentralized applications.
Prerequisites & Setup
You’ll need a CoinGecko API key. If you don’t have one, follow the guide to get a free Demo API key.
Create a project folder with two files: requirements.txt lists the dependencies, and .env that stores your API key
1
2
3
4
5
6
requests
pandas
matplotlib
websocket-client
coingecko-sdk
python-dotenv
requirements.txt hosted with ❤ by GitHub view raw
Install with pip.
pip install -r requirements.txt
1
2
COINGECKO_API_KEY=CG-your_api_key_here
COINGECKO_PLAN=demo
.env hosted with ❤ by GitHub view raw
Next, set up a configuration module that reads your API key and plan from the environment, then builds the correct base URL and headers for each request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("COINGECKO_API_KEY", "")
# Switch to "pro" in your .env when you upgrade to a paid plan
PLAN = os.getenv("COINGECKO_PLAN", "demo").lower()
# Demo and paid plans use different base URLs and header names
if PLAN == "pro":
BASE_URL = "https://pro-api.coingecko.com/api/v3"
HEADERS = {"x-cg-pro-api-key": API_KEY}
else:
BASE_URL = "https://api.coingecko.com/api/v3"
HEADERS = {"x-cg-demo-api-key": API_KEY}
# Onchain (GeckoTerminal) endpoints live under the /onchain path on the same host
ONCHAIN_URL = f"{BASE_URL}/onchain"
config.py hosted with ❤ by GitHub view raw
Every endpoint in this guide can also be called using CoinGecko’s official Python SDK or TypeScript SDK instead of raw HTTP requests. The SDKs handle the base URL, headers, and authentication for you.
How to Discover Tokenized Stock Tokens
CoinGecko API enables you to discover tokenized stocks using category filtering to identify relevant assets. Start with the Coins Categories List endpoint to identify category names and IDs, including xstocks-ecosystem for Backed’s xStocks, robinhood-chain-stocks-ecosystem for Robinhood’s tokenized stocks, and the broader tokenized-stock category. Pass the selected category ID to /coins/markets to retrieve token prices, market caps, and 24-hour volumes, with up to 250 tokens returned per page. Then use /coins/{id} to retrieve each token’s cross-chain contract addresses from the detail_platforms field for subsequent onchain queries.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import requests
from config import BASE_URL, HEADERS
def get_category_ids():
url = f"{BASE_URL}/coins/categories/list"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.json()
# --- Optional: same request using the CoinGecko Python SDK ---
# Install it with: pip install coingecko-sdk
#
# from coingecko_sdk import Coingecko
#
# client = Coingecko(demo_api_key="YOUR_API_KEY", environment="demo")
# categories = client.coins.categories.get_list()
#
# On a paid plan, use: Coingecko(pro_api_key="YOUR_API_KEY", environment="pro")
def get_tokens_in_category(category_id):
# per_page accepts up to 250; page through if a category is larger
url = f"{BASE_URL}/coins/markets"
params = {"vs_currency": "usd", "category": category_id, "per_page": 250, "page": 1}
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
# SDK equivalent: client.coins.markets.get(vs_currency="usd", category="xstocks-ecosystem")
def get_contract_addresses(coin_id):
# tickers, market_data, community_data, and developer_data default to true
# on this endpoint; disabling them keeps the response small
url = f"{BASE_URL}/coins/{coin_id}"
params = {"localization": "false", "tickers": "false", "market_data": "false",
"community_data": "false", "developer_data": "false"}
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json().get("detail_platforms", {})
# SDK equivalent: client.coins.get_id("tesla-xstock", localization=False, tickers=False, market_data=False)
if __name__ == "__main__":
# Step 1: find the category ID for the tokenized stocks you want
categories = get_category_ids()
for c in categories:
if "stock" in c["category_id"]:
print(c["category_id"], "-", c["name"])
# Step 2: pull every token in that category, then resolve its contract addresses
tokens = get_tokens_in_category("xstocks-ecosystem")
rows = [
{"symbol": t["symbol"].upper(), "coingecko_id": t["id"],
"asset_platform": platform, "contract_address": details.get("contract_address")}
for t in tokens[:10]
for platform, details in get_contract_addresses(t["id"]).items()
]
# Fixed-width print so columns line up regardless of address length
print(f"{'SYMBOL':<8}{'COINGECKO_ID':<16}{'ASSET_PLATFORM':<16}{'CONTRACT_ADDRESS'}")
print("-" * 90)
for r in rows:
print(f"{r['symbol']:<8}{r['coingecko_id']:<16}{r['asset_platform']:<16}{r['contract_address']}")
discover_tokens.py hosted with ❤ by GitHub view raw
Running this script produces output like the following:
The output includes the tokenized-pre-ipo-stocks category. CoinGecko also tracks pre-IPO companies such as OpenAI, SpaceX, and Anthropic as tokenized assets under the stock classification rather than a separate pre-IPO category. You can query them directly via /rwas/openai-pre-ipo?tokens=true.
{ "id": "openai-pre-ipo", "symbol": "openai", "name": "OpenAI (Pre-IPO)", "asset_type": "stock", "tokens": [ { "id": "openai-republic-pre-ipo", "symbol": "preopai", "issuer_details": { "name": "Republic" } }, { "id": "openai-prestocks-2", "symbol": "openai", "issuer_details": { "name": "PreStocks" } }, { "id": "openai-tessera-pre-ipo", "symbol": "topenai", "issuer_details": { "name": "Tessera Tokenized Pre-IPO Assets" } }, { "id": "openai-gate-pre-ipo", "symbol": "openai", "issuer_details": { "name": "Gate" } } ] }
This same code also works for other tokenized real-world assets. Replace the category ID such as tokenized-commodities, tokenized-gold, or tokenized-exchange-traded-funds-etfs. For a deeper look at tokenized commodities, our guide on tracking gold, silver & tokenized commodities covers the topic in greater detail.
If you prefer working in spreadsheets, the same category IDs and logic work through CoinGecko’s official Google Sheets and Excel add-ons. See our guides on pulling crypto prices into Google Sheets and tokenized stock prices into Excel to get started.
How to Get a Tokenized Stock’s Price, Volume, and Liquidity Across Chains
CoinGecko’s aggregated market data endpoints provide price, market cap, and volume data, while GeckoTerminal provides onchain liquidity data through the same API credentials. The /coins/markets and /coins/{id} endpoints are the primary way to retrieve a tokenized stock’s price and market cap, as these figures are aggregated across all venues where the token trades. For a single token or short custom list, the /simple/price endpoint provides a simpler way to retrieve price data in a single request.
These endpoints work for any tokenized assets such as stocks, commodities, or ETF. The code below uses Tesla, Nvidia, and the S&P 500 ETF (SPYX). Simply replace the coin IDs with those identified during discovery.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
from config import BASE_URL, HEADERS
def get_simple_price(coin_ids):
# Lightweight price lookup for specific tokenized stocks by coin ID
url = f"{BASE_URL}/simple/price"
params = {
"ids": ",".join(coin_ids),
"vs_currencies": "usd",
"include_market_cap": "true",
"include_24hr_vol": "true",
"include_24hr_change": "true",
}
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
# SDK equivalent: client.simple.price.get(ids="tesla-xstock,nvidia-xstock,sp500-xstock", vs_currencies="usd", include_market_cap=True, include_24hr_vol=True, include_24hr_change=True)
if __name__ == "__main__":
prices = get_simple_price(["tesla-xstock", "nvidia-xstock", "sp500-xstock"])
for coin_id, data in prices.items():
print(coin_id, f"${data['usd']:,.2f}", f"{data['usd_24h_change']:+.2f}% 24h",
f"mcap ${data['usd_market_cap']:,.0f}", f"vol ${data['usd_24h_vol']:,.0f}")
track_price.py hosted with ❤ by GitHub view raw
Here is an example of how the output looks like:
If you’re specifically working with RWAs, you can use /rwas/{id} to aggregate every issuer’s version of the same underlying asset into a single object. For NVIDIA, GET /rwas/nvidia combines xStocks’ NVDAX with Ondo’s, Robinhood’s, Coinbase’s, and BTech’s tokenized versions into a single price:
{ "id": "nvidia", "symbol": "nvda", "name": "NVIDIA", "asset_type": "stock", "tokenized_market_data": { "current_price": 215.09 }, "tokens": [ { "id": "nvidia-xstock", "symbol": "nvdax", "issuer_details": { "name": "xStocks" } }, { "id": "nvidia-ondo-tokenized-stock", "symbol": "nvdaon", "issuer_details": { "name": "Ondo Finance" } }, { "id": "nvidia-robinhood-token", "symbol": "nvda", "issuer_details": { "name": "Robinhood Europe" } }, { "id": "nvidia-coinbase-tokenized-stock", "symbol": "nvdac", "issuer_details": { "name": "Coinbase" } } ] }
Neither aggregated endpoint provides liquidity data, because liquidity is measured at the individual pool level. To see where a tokenized stock trades and the liquidity available on each chain, use the contract addresses from detail_platforms with the /onchain/networks/{network}/tokens/{token_address}/pools endpoint.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import requests
from config import HEADERS, ONCHAIN_URL
def build_network_map():
# e.g. "ethereum" maps to "eth", "arbitrum-one" maps to "arbitrum"
network_map = {}
for page in range(1, 4):
response = requests.get(f"{ONCHAIN_URL}/networks", headers=HEADERS, params={"page": page})
response.raise_for_status()
for net in response.json()["data"]:
platform = net["attributes"].get("coingecko_asset_platform_id")
if platform:
network_map[platform] = net["id"]
return network_map
# SDK equivalent: client.onchain.networks.get(page=1)
def get_pools_for_token(network_id, token_address):
# ranked by a combination of liquidity and 24-hour volume
url = f"{ONCHAIN_URL}/networks/{network_id}/tokens/{token_address}/pools"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.json()["data"]
# SDK equivalent: client.onchain.networks.tokens.pools.get(token_address, network=network_id)
def get_liquidity_across_chains(detail_platforms, network_map):
rows = []
for asset_platform, details in detail_platforms.items():
network_id = network_map.get(asset_platform)
if not network_id:
continue
for pool in get_pools_for_token(network_id, details["contract_address"]):
rows.append({
"network": network_id,
"pool": pool["attributes"]["name"],
"pool_address": pool["attributes"]["address"],
# GeckoTerminal returns these as strings, cast to float and round for display
"liquidity_usd": round(float(pool["attributes"]["reserve_in_usd"]), 2),
"volume_24h": round(float(pool["attributes"]["volume_usd"]["h24"]), 2),
})
return rows
if __name__ == "__main__":
# detail_platforms comes from get_contract_addresses() in the discovery step
tesla_platforms = {
"solana": {"contract_address": "XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB"},
"ethereum": {"contract_address": "0x8ad3c73f833d3f9a523ab01476625f269aeb7cf0"},
}
rows = get_liquidity_across_chains(tesla_platforms, build_network_map())
print(f"{'NETWORK':<8}{'POOL':<18}{'POOL_ADDRESS':<16}{'LIQUIDITY_USD':>14}{'VOLUME_24H':>12}")
print("-" * 68)
for r in rows:
# Truncate the address for a readable print; rows still holds the full value for reuse
addr = r["pool_address"]
short_addr = f"{addr[:6]}...{addr[-4:]}"
print(f"{r['network']:<8}{r['pool']:<18}{short_addr:<16}{r['liquidity_usd']:>14,.2f}{r['volume_24h']:>12,.2f}")
track_liquidity.py hosted with ❤ by GitHub view raw
The response returns the following output:
💡 Pro Tip: If you only need a token’s price and liquidity on a known chain, the /onchain/simple/networks/{network}/token_price/{addresses} endpoint returns price, 24-hour volume, 24-hour change, and total liquidity (with include_total_reserve_in_usd=true) in a single call, without requiring individual pool lookups.
How to Track Holders of a Tokenized Stock
CoinGecko’s Token Info by Token Address endpoint returns a holder snapshot, including the total holder count and a breakdown of token supply distribution among the top holders.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
from config import HEADERS, ONCHAIN_URL
def get_holder_snapshot(network_id, token_address):
url = f"{ONCHAIN_URL}/networks/{network_id}/tokens/{token_address}/info"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.json()["data"]["attributes"]["holders"]
# SDK equivalent: client.onchain.networks.tokens.info.get(token_address, network=network_id)
if __name__ == "__main__":
holders = get_holder_snapshot("solana", "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh") # Nvidia xStock; any tokenized stock works
print("Total holders:", holders["count"])
print("Distribution:", holders["distribution_percentage"])
holders_snapshot.py hosted with ❤ by GitHub view raw
Here is an example of how the output looks like:
For deeper holder, wallet, and trading analysis, the following endpoints are available on the Analyst plan and above:
- Top Token Holders by Token Address: Ranked individual wallets with their balances and share of supply
- Historical Token Holders Chart by Token Address: Holder count over time, for charting growth trends
- Top Token Traders by Token Address: Wallets ranked by realized and unrealized profit
- Past 24 Hour Trades by Token Address: Returns a token’s last 300 trades across all pools in the past 24 hours, including buy/sell direction, token amounts, and USD volume
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import requests
from config import HEADERS, ONCHAIN_URL
def get_top_holders(network_id, token_address, limit=10):
url = f"{ONCHAIN_URL}/networks/{network_id}/tokens/{token_address}/top_holders"
response = requests.get(url, headers=HEADERS, params={"holders": limit})
response.raise_for_status()
return response.json()["data"]["attributes"]["holders"]
# SDK equivalent: client.onchain.networks.tokens.top_holders.get(token_address, network=network_id, holders="10")
def get_holders_chart(network_id, token_address, days="30"):
url = f"{ONCHAIN_URL}/networks/{network_id}/tokens/{token_address}/holders_chart"
response = requests.get(url, headers=HEADERS, params={"days": days})
response.raise_for_status()
return response.json()["data"]["attributes"]["token_holders_list"]
# SDK equivalent: client.onchain.networks.tokens.holders_chart.get(token_address, network=network_id, days="30")
def get_recent_trades(network_id, token_address, min_volume_usd=0):
url = f"{ONCHAIN_URL}/networks/{network_id}/tokens/{token_address}/trades"
params = {"trade_volume_in_usd_greater_than": min_volume_usd}
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()["data"]
# SDK equivalent: client.onchain.networks.tokens.trades.get(token_address, network=network_id)
if __name__ == "__main__":
# amount, percentage, and value come back as strings; round for display
print(f"{'RANK':<6}{'ADDRESS':<16}{'AMOUNT':>14}{'PERCENTAGE':>12}{'VALUE_USD':>16}")
print("-" * 64)
for h in get_top_holders("solana", "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh"):
addr = h["address"]
short_addr = f"{addr[:6]}...{addr[-4:]}"
pct = h["percentage"] + "%"
value = f"${round(float(h['value']), 2):,.2f}"
print(f"{h['rank']:<6}{short_addr:<16}{round(float(h['amount']), 2):>14,.2f}{pct:>12}{value:>16}")
chart = get_holders_chart("solana", "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh", days="30")
print(f"n{'TIMESTAMP':<22}{'HOLDER_COUNT':>14}")
print("-" * 36)
for timestamp, holder_count in chart[-3:]:
print(f"{timestamp:<22}{holder_count:>14}")
print(f"n{'KIND':<6}{'VOLUME_USD':>12} {'TIMESTAMP':<22}{'POOL':<16}")
print("-" * 60)
for t in get_recent_trades("solana", "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh", min_volume_usd=1000)[:5]:
attrs = t["attributes"]
pool = attrs["pool_address"]
short_pool = f"{pool[:6]}...{pool[-4:]}"
print(f"{attrs['kind']:<6}{round(float(attrs['volume_in_usd']), 2):>12,.2f} {attrs['block_timestamp']:<22}{short_pool:<16}")
holders_ranked.py hosted with ❤ by GitHub view raw
Running this script produces output like the following:
How to Get Historical Price and OHLCV Data for a Tokenized Stock
To retrieve historical price, market cap, and volume data for a tokenized stock, /coins/{id}/market_chart returns aggregated market data over time for tokens listed on CoinGecko, making it useful for tracking historical performance.
Note: The free Demo plan supports up to 365 days of historical data. To access a token’s full available history from its first trading date, upgrade to the Analyst plan and above.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import requests
from config import BASE_URL, HEADERS
def get_historical_price(coin_id, days="30"):
url = f"{BASE_URL}/coins/{coin_id}/market_chart"
params = {"vs_currency": "usd", "days": days}
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
# SDK equivalent: client.coins.market_chart.get("tesla-xstock", vs_currency="usd", days="30")
if __name__ == "__main__":
data = get_historical_price("tesla-xstock", days="30")
df = pd.DataFrame(data["prices"], columns=["timestamp", "price"])
df["date"] = pd.to_datetime(df["timestamp"], unit="ms")
print(f"{'DATE':<18}{'PRICE':>10}")
print("-" * 28)
for _, row in df[["date", "price"]].tail().iterrows():
print(f"{row['date'].strftime('%Y-%m-%d %H:%M'):<18}{row['price']:>10,.2f}")
historical_price.py hosted with ❤ by GitHub view raw
The response returns the following output:
For candlestick charting, query the /onchain/networks/{network}/pools/{pool_address}/ohlcv/{timeframe} endpoint to retrieve OHLCV data for a selected pool. Resolve a token’s most liquid pool from its contract address using the /onchain/networks/{network}/tokens/{token_address}/pools endpoint. The response ranks pools by liquidity and 24-hour volume, with the first result representing the highest-ranked pool.
Each OHLCV candle includes a timestamp, open, high, low, close, and volume, allowing direct use in pandas for charting or indicator calculations. Instead of manually aggregating data, you can use timeframe and aggregate to request the candle interval needed. Each request returns up to 1,000 candles over a maximum six-month window, with before_timestamp available for retrieving older history.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import pandas as pd
import requests
from config import HEADERS, ONCHAIN_URL
def get_top_pool_address(network_id, token_address):
url = f"{ONCHAIN_URL}/networks/{network_id}/tokens/{token_address}/pools"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
pools = response.json()["data"]
return pools[0]["attributes"]["address"] if pools else None # sorted by liquidity + 24h volume, so the first is the most liquid
# SDK equivalent: client.onchain.networks.tokens.pools.get(token_address, network=network_id)
def get_pool_ohlcv(network_id, pool_address, timeframe="day", limit=30):
url = f"{ONCHAIN_URL}/networks/{network_id}/pools/{pool_address}/ohlcv/{timeframe}"
response = requests.get(url, headers=HEADERS, params={"limit": limit})
response.raise_for_status()
return response.json()["data"]["attributes"]["ohlcv_list"]
# SDK equivalent: client.onchain.networks.pools.ohlcv.get_timeframe("day", network=network_id, pool_address=pool_address, limit=limit)
if __name__ == "__main__":
# Tesla xStock on Solana; resolve its most liquid pool from the token's contract address
network_id, token_address = "solana", "XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB"
pool_address = get_top_pool_address(network_id, token_address)
candles = get_pool_ohlcv(network_id, pool_address, timeframe="day", limit=30)
df = pd.DataFrame(candles, columns=["timestamp", "open", "high", "low", "close", "volume"])
df["date"] = pd.to_datetime(df["timestamp"], unit="s")
print(f"{'DATE':<12}{'OPEN':>10}{'HIGH':>10}{'LOW':>10}{'CLOSE':>10}{'VOLUME':>14}")
print("-" * 66)
for _, row in df.tail().iterrows():
print(f"{row['date'].strftime('%Y-%m-%d'):<12}{row['open']:>10,.2f}{row['high']:>10,.2f}{row['low']:>10,.2f}{row['close']:>10,.2f}{row['volume']:>14,.2f}")
historical_ohlcv.py hosted with ❤ by GitHub view raw
Running this script produces output like the following:
Charting this response makes trends easier to interpret at a glance. The chart below plots the same pool OHLCV data retrieved by get_pool_ohlcv():
To simplify your workflow, you can skip the pool-resolution step entirely and query OHLCV data directly by token contract using the Token OHLCV Chart by Token Address endpoint. It supports the same timeframe, aggregate, limit, and before_timestamp parameters.
Note: This endpoint is available exclusively on the Analyst plan and above.
With this same OHLCV data, you can go beyond plotting it and calculate technical indicators such as RSI, MACD, and Bollinger Bands. Our guide on automating crypto technical analysis with Python walks through the setup in detail.
How to Stream Tokenized Stock Prices, OHLCV, and Trades via Websockets
CoinGecko’s WebSocket streams real-time tokenized stock data by maintaining a persistent connection and pushing updates as they happen, instead of cached snapshots with REST API polling. It provides live price, trade, and candlestick updates with ultra-low latency, making it ideal for real-time dashboards, trading applications, and monitoring fast-moving tokenized assets.
Update as of June 2026: CoinGecko WebSockets are now available on the Basic plan starting at just $29/month.
Connect to wss://stream.coingecko.com/v1 with your API key, then subscribe to the channels and specify the tokens or pools you want to monitor. The example below subscribes to CGSimplePrice, OnchainOHLCV, and OnchainTrade in a single connection, streaming Tesla xStock’s aggregated price, live candlesticks, and pool-level swaps on Solana all at once.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import json
import websocket
from config import API_KEY
NETWORK_ID = "solana"
POOL_ADDRESS = "8aDaBQkTrS6HVMjyc6EZebgdiaXhLYGriDWKWWp1NpFF" # Tesla xStock / USDC
COIN_ID = "tesla-xstock"
def on_open(ws):
# Subscribe to all three channels, then tell each one what to watch
for channel in ("CGSimplePrice", "OnchainOHLCV", "OnchainTrade"):
ws.send(json.dumps({"command": "subscribe", "identifier": json.dumps({"channel": channel})}))
ws.send(json.dumps({
"command": "message",
"identifier": json.dumps({"channel": "CGSimplePrice"}),
"data": json.dumps({"coin_id": [COIN_ID], "vs_currencies": ["usd"], "action": "set_tokens"}),
}))
ws.send(json.dumps({
"command": "message",
"identifier": json.dumps({"channel": "OnchainOHLCV"}),
"data": json.dumps({
"network_id:pool_addresses": [f"{NETWORK_ID}:{POOL_ADDRESS}"],
"interval": "1m",
"token": "base",
"action": "set_pools",
}),
}))
ws.send(json.dumps({
"command": "message",
"identifier": json.dumps({"channel": "OnchainTrade"}),
"data": json.dumps({
"network_id:pool_addresses": [f"{NETWORK_ID}:{POOL_ADDRESS}"],
"action": "set_pools",
}),
}))
def on_message(ws, message):
data = json.loads(message)
# OnchainTrade payloads carry "ty" (b/s); OnchainOHLCV uses "ch" for channel type
# (not "c"); CGSimplePrice uses "c":"C1". Checking in this order avoids key collisions.
if "ty" in data:
side = "BUY" if data["ty"] == "b" else "SELL"
print(f"[TRADE] {side} {data['vo']:.2f} USD at ${data['pu']:.2f} tx={data['tx'][:10]}...")
elif "ch" in data:
print(f"[OHLCV] {data['i']} candle O:{data['o']:.2f} H:{data['h']:.2f} L:{data['l']:.2f} C:{data['c']:.2f}")
elif data.get("c") == "C1":
print(f"[PRICE] {data['i']} ${data['p']:.2f} {data['pp']:+.2f}% 24h")
else:
print("Status:", data.get("message", data))
if __name__ == "__main__":
url = f"wss://stream.coingecko.com/v1?x_cg_pro_api_key={API_KEY}"
ws = websocket.WebSocketApp(url, on_open=on_open, on_message=on_message)
ws.run_forever()
stream_data.py hosted with ❤ by GitHub view raw
The response returns the following output:
This same feed is visualized in real time on GeckoTerminal’s TSLAX pool:
Conclusion
With CoinGecko API, you can build a Python workflow for tracking tokenized stocks across the full data lifecycle from discovering assets and retrieving market data to mapping liquidity pools, analyzing holders, fetching historical data, and streaming real-time updates across various chains.
The same workflow extends to other tokenized real-world assets. You can swap the category ID to track tokenized commodities, treasuries, or ETFs, allowing a single codebase to support a broader range of RWA assets as more markets move onchain.
To build a user-facing application with this data, explore our guide on building a crypto portfolio dashboard in Python. To work with tokenized asset data programmatically, see our guide to discovering, analyzing, and comparing tokenized assets and issuers. For deeper holder analysis, our token holders deep dive covers concentration metrics in more detail.
Ready to start building? Get your free Demo API key and start exploring tokenized stock data today. As your needs grow, upgrade to an entry-level Basic plan (from $29/mo) to access the full suite of data delivery methods, includes REST APIs, webhook, WebSocket streams, and a commercial license, while the Analyst plan ($129/month) adds exclusive endpoints such as Pools Megafilter, more API call credits, and higher rate limits.



