Learn how to build a Bittensor TAO subnet monitor using CoinMarketCap API for AI token discovery, price and momentum tracking, DEX liquidity validation, and macro regime filtering.
Introduction
Bittensor is building a decentralized market for artificial intelligence.
Its subnet architecture divides the network into 128+ focused AI marketplaces, each rewarding miners for producing a specific type of digital commodity — from LLM inference to image generation to data indexing. Validators score miner output, and TAO emissions flow to participants based on performance via the Yuma Consensus algorithm.
Every subnet operates its own AMM with two reserves: TAO and a subnet-specific alpha token. As TAO holders stake into subnets, they vote on which AI services deserve more emissions. This creates a continuous, on-chain market for AI output quality.
For developers, the challenge is not understanding the protocol. It is knowing which market conditions favor TAO and subnet tokens before acting.
CoinMarketCap API solves that. It gives you a structured data layer to track TAO price and momentum, discover AI narrative trends, validate DEX liquidity for wTAO, and apply macro regime filters — all before you interact with the Bittensor network on-chain.
- CoinMarketCap API powers the signal engine
- Bittensor SDK and Subtensor RPC handle on-chain subnet state validation
Why Use CoinMarketCap API for a Bittensor Subnet Monitor?
Bittensor produces AI. CoinMarketCap tells you how the market values it.
Instead of reacting to price moves after they happen, you can use CoinMarketCap to:
- track TAO price, volume, and momentum across timeframes
- discover subnet alpha tokens that have gained CMC listings
- validate wTAO DEX liquidity on Ethereum (Uniswap)
- identify AI narrative trends and capital rotation into the sector
- apply macro regime filters to avoid deploying capital in adverse conditions
- monitor CEX market pair quality as a proxy for institutional interest
This turns your monitor from a price ticker into a market intelligence system.
System Architecture
CoinMarketCap API (Signal Layer)
├─ Asset Discovery (TAO, wTAO, subnet alpha tokens)
├─ Price + Momentum (quotes, percent change, volume)
├─ DEX Liquidity (wTAO on Uniswap / Ethereum)
├─ AI Narrative Trends (listings, tag filtering)
└─ Macro Regime (fear/greed, altcoin season)
↓
Subnet Signal Engine
↓
Bittensor SDK / Subtensor RPC (Validation Layer)
↓
Subnet Emissions + Alpha Token AMM State
Architecture Clarification
The CoinMarketCap API acts strictly as an off-chain Signal Layer for TAO price monitoring, subnet token discovery, AI narrative trend detection, and market regime filtering. It is not a subnet state monitor, emissions oracle, or validator performance engine.
Real subnet emissions, miner and validator weights, alpha token prices, staking yields, and Yuma Consensus state must be validated directly on-chain via the Bittensor SDK or Subtensor RPC. CMC data reflects market conditions with cache delays and does not track individual subnet performance, registration costs, or alpha token AMM state.
Project Setup
Python Dependencies
import os
import time
import requests
import pandas as pd
import numpy as np
Environment Variables
CMC_API_KEY = os.getenv("CMC_API_KEY")
CMC_BASE_URL = "https://pro-api.coinmarketcap.com"
Headers
HEADERS = {
"Accept": "application/json",
"X-CMC_PRO_API_KEY": CMC_API_KEY,
}
Target Assets
# Core assets to monitor
BITTENSOR_ASSETS = ["TAO", "wTAO"]
# AI ecosystem tags to filter locally — do NOT pass these as query params (returns 400)
AI_TAGS = {"ai-big-data", "artificial-intelligence", "bittensor-ecosystem"}
# wTAO DEX config on Ethereum
WTAO_NETWORK = "ethereum"
WTAO_DEX = "uniswap-v3"
Step 1: Map Assets to CoinMarketCap IDs
Resolve TAO and any tracked subnet tokens to their CoinMarketCap IDs. Use IDs — not symbols — for all subsequent Core API calls.
Endpoint
GET /v1/cryptocurrency/map
def map_assets(symbols="TAO"):
url = f"{CMC_BASE_URL}/v1/cryptocurrency/map"
params = {"symbol": symbols}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Important notes on TAO asset structure:
- Native TAO is a Layer 1 coin on the Subtensor chain. Its CMC entry will have platform: null — there is no EVM contract address for the native token.
- Wrapped TAO (wTAO) is an ERC-20 token on Ethereum. Its CMC entry will have platform.name = "Ethereum" and a valid platform.token_address.
- Subnet alpha tokens will have their own CMC IDs if they have listed liquidity on a CMC-tracked exchange. Use /v1/dex/search with the contract address to locate them.
Step 2: Fetch Quotes and Momentum Signals
Pull price and momentum data for TAO and tracked subnet tokens.
Endpoint
GET /v3/cryptocurrency/quotes/latest
def fetch_quotes(ids):
url = f"{CMC_BASE_URL}/v3/cryptocurrency/quotes/latest"
params = {"id": ",".join(str(i) for i in ids)}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
def parse_quote(asset):
# quote is a LIST in v3 — use next() to find USD entry by symbol
usd = next(
(q for q in asset.get("quote", []) if q.get("symbol") == "USD"),
{}
)
return {
"id": asset.get("id"),
"symbol": asset.get("symbol"),
"price": usd.get("price"),
"volume_24h": usd.get("volume_24h"),
"market_cap": usd.get("market_cap"),
"fdv": usd.get("fully_diluted_market_cap"),
"pct_change_1h": usd.get("percent_change_1h"),
"pct_change_24h": usd.get("percent_change_24h"),
"pct_change_7d": usd.get("percent_change_7d"),
"tvl": usd.get("tvl"), # will be null for TAO — not a DeFi lending protocol
}
Note: tvl will almost certainly return null for TAO. Bittensor is a Proof-of-Intelligence network, not a lending or AMM protocol. Always parse defensively — never index tvl directly.
The data field is a list. Build the lookup dict by iterating:
# raw_quotes is a list — build dict keyed by string ID
quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}
Step 3: Score TAO and Subnet Tokens
Rank tracked assets by momentum and market strength signals.
def compute_tao_score(quote):
score = 0
# Price momentum
pct_1h = quote.get("pct_change_1h") or 0
pct_24h = quote.get("pct_change_24h") or 0
pct_7d = quote.get("pct_change_7d") or 0
if pct_24h > 10: score += 30
elif pct_24h > 5: score += 20
elif pct_24h > 2: score += 10
elif pct_24h < -15: score -= 25
if pct_7d > 20: score += 20
elif pct_7d > 10: score += 10
if pct_1h > 2: score += 15
elif pct_1h > 0.5: score += 8
# Volume signal
vol = quote.get("volume_24h") or 0
if vol > 100_000_000: score += 20
elif vol > 20_000_000: score += 10
# Market cap — validates asset size
mcap = quote.get("market_cap") or 0
if mcap > 1_000_000_000: score += 15
return score
Step 4: Validate wTAO DEX Liquidity on Ethereum
TAO does not have native DEX liquidity on EVM chains. Use Wrapped TAO (wTAO) on Ethereum for DEX-level signals.
Endpoint
GET /v4/dex/spot-pairs/latest
# dex_slug is required alongside network_slug. Passing only network_slug returns a 400 error.
def fetch_wtao_pairs(wtao_contract_address):
url = f"{CMC_BASE_URL}/v4/dex/spot-pairs/latest"
params = {
"network_slug": WTAO_NETWORK, #
"ethereum" "dex_slug": WTAO_DEX, #
"uniswap-v3"
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status() pairs = r.json()["data"]
# filter for wTAO pairs by contract address
return [
p for p in pairs
if wtao_contract_address.lower() in (
(p.get("base_asset_contract_address") or "").lower(),
(p.get("quote_asset_contract_address") or "").lower()
)
]
# Price, liquidity, and volume live inside the quote array. Filter by convert_id == "2781" for USD values:
def parse_pair_quote(pair):
quotes = pair.get("quote", [])
usd = next(
(q for q in quotes if str(q.get("convert_id")) == "2781"),
{}
)
return {
"dex": pair.get("dex_slug"),
"price": usd.get("price"),
"liquidity": usd.get("liquidity"),
"volume_24h": usd.get("volume_24h"),
}
Step 5: Get wTAO Pool-Level Data
Validate pool depth and creation history for the wTAO Uniswap pool.
Endpoint
GET /v1/dex/token/pools
def fetch_wtao_pools(wtao_contract_address):
url = f"{CMC_BASE_URL}/v1/dex/token/pools"
params = {
"address": wtao_contract_address,
"platform": "ethereum"
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Key fields per pool: exn (DEX name), liqUsd (liquidity USD), v24 (24h volume), addr (pool address), pubAt (creation timestamp).
Note: lr and br fields are documented in the schema but are frequently absent in live EVM pool data. Do not rely on them. For LP lock validation on Ethereum, query the locker contract directly on-chain.
def get_best_pool(pools, min_liquidity=50_000):
valid = [
p for p in pools
if (p.get("liqUsd") or 0) >= min_liquidity
]
return max(valid, key=lambda p: p.get("liqUsd", 0)) if valid else None
Step 6: Get Pool-Level Price and Reserves
Fetch precise pool-level pricing for the wTAO pool.
Endpoint
GET /v4/dex/pairs/quotes/latest
def fetch_pool_quote(pool_address, network_slug="ethereum"):
url = f"{CMC_BASE_URL}/v4/dex/pairs/quotes/latest"
params = {
"network_slug": network_slug, # required — disambiguates contracts across chains
"contract_address": pool_address,
"aux": "pool_base_asset,pool_quote_asset,buy_tax,sell_tax"
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
network_slug is required alongside contract_address on all chains. Omitting it returns a 400 error.
Step 7: Discover AI Narrative Trends
Identify which AI and Bittensor ecosystem tokens are attracting the most capital flow.
Trending Endpoints (Paid Only)
GET /v1/cryptocurrency/trending/latest
GET /v1/cryptocurrency/trending/gainers-losers
Paid Endpoint Warning
Error Code: 1006 [API_KEY_PLAN_NOT_AUTHORIZED]
Message: "Your API Key subscription plan doesn't support this endpoint."
Both return HTTP 403 on the Basic plan.
Basic Plan Fallback
def fetch_ai_listings():
url = f"{CMC_BASE_URL}/v3/cryptocurrency/listings/latest"
params = {
"sort": "volume_24h",
"sort_dir": "desc",
"limit": 200,
"percent_change_24h_min": 2,
"volume_24h_min": 5_000_000,
# Do NOT pass tag="ai-big-data" — returns 400
# Valid tag query values: "all", "defi", "filesharing" only
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
def filter_ai_assets(assets):
# Filter locally by inspecting the tags array in each asset object
# CMC applies tags like "ai-big-data", "artificial-intelligence",
# and "bittensor-ecosystem" to qualifying assets
results = []
for asset in assets:
tags = set(asset.get("tags") or [])
if tags & AI_TAGS or asset.get("symbol") in BITTENSOR_ASSETS:
results.append(asset)
return results
Step 8: CEX Liquidity Quality
Paid Endpoint Warning
/v2/cryptocurrency/market-pairs/latest returns HTTP 403 on the Basic plan.
Error Code: 1006 [API_KEY_PLAN_NOT_AUTHORIZED]
Despite documentation listing Basic as supported, live testing confirms 403.
Basic Plan Fallback
TAO is listed on major exchanges including Binance, OKX, and Bybit. Use volume_24h and num_market_pairs from quotes as a CEX liquidity proxy:
def estimate_cex_liquidity(quote):
volume = quote.get("volume_24h") or 0
mcap = quote.get("market_cap") or 0
return {
"volume_24h": volume,
"market_cap": mcap,
"liquidity_signal": "high" if volume > 50_000_000
else "medium" if volume > 10_000_000
else "low",
}
If you have a paid plan, use /v2/cryptocurrency/market-pairs/latest with aux="effective_liquidity,market_score,market_reputation".
Step 9: Apply Macro Regime Filters
TAO and AI tokens are high-beta assets — macro regime filters matter significantly.
Endpoints
GET /v3/fear-and-greed/latest
GET /v1/altcoin-season-index/latest
def fetch_macro_regime():
fg_url = f"{CMC_BASE_URL}/v3/fear-and-greed/latest"
as_url = f"{CMC_BASE_URL}/v1/altcoin-season-index/latest"
fg = requests.get(fg_url, headers=HEADERS).json()["data"]
as_idx = requests.get(as_url, headers=HEADERS).json()["data"]
return {
"fear_greed_value": fg.get("value"),
"fear_greed_classification": fg.get("value_classification"),
"altcoin_index": as_idx.get("altcoin_index"),
}
def is_regime_favorable(regime):
fg = regime.get("fear_greed_value") or 0
as_i = regime.get("altcoin_index") or 0
# TAO is high-beta — require stronger macro confirmation
return fg > 45 and as_i >= 55
Fear & Greed updates every 15 minutes. Altcoin Season Index: ≥75 signals Altcoin Season, <25 signals Bitcoin Season. Both endpoints are available on the Basic plan.
Step 10: Sub-Minute Momentum Detection (wTAO)
For wTAO pools on Ethereum, sub-minute candle data is available.
Endpoint
GET /v1/k-line/candles
def fetch_candles(token_address, platform="ethereum", interval="5min"):
url = f"{CMC_BASE_URL}/v1/k-line/candles"
params = {
"platform": platform,
"address": token_address,
"interval": interval # 1s, 5s, 30s, 1min, 3min
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Each candle is a positional array of 7 elements — not a dict:
Index
Field
[0]
open
[1]
high
[2]
low
[3]
close
[4]
volume
[5]
timestamp, UNIX seconds
[6]
traders, unique trader count
Do not call .get() on a candle. Use parse_candle() to convert:
def parse_candle(c):
return {
"open": c[0],
"high": c[1],
"low": c[2],
"close": c[3],
"volume": c[4],
"timestamp": c[5],
"traders": c[6],
}
Note: /v4/dex/pairs/ohlcv/historical returns 500 in production. Use /v1/k-line/candles for all DEX candle data.
Step 11: Historical Backtesting
Paid Endpoint Warning
/v3/cryptocurrency/quotes/historical returns HTTP 403 on the Basic plan.
Despite documentation listing Basic as supported, any time-series request returns 403.
Backtesting requires a paid CMC plan.
Cache: 5 minutes. Cost: 1 credit per 100 datapoints.
def fetch_historical_quotes(asset_id, time_start, time_end, interval="1h"):
url = f"{CMC_BASE_URL}/v3/cryptocurrency/quotes/historical"
params = {
"id": asset_id,
"time_start": time_start,
"time_end": time_end,
"interval": interval, # "1h", "4h", "daily"
}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return r.json()["data"]
Step 12: Minimal End-to-End Flow
def run_tao_subnet_monitor(asset_ids, wtao_contract_address):
# 1. Macro regime — poll every 15 min
regime = fetch_macro_regime()
# 2. Quotes — raw_quotes is a list, build dict by id
raw_quotes = fetch_quotes(list(asset_ids.values()))
quotes = {str(a["id"]): parse_quote(a) for a in raw_quotes}
# 3. AI narrative — discover trending assets locally
try:
listings = fetch_ai_listings()
ai_assets = filter_ai_assets(listings)
except Exception:
ai_assets = []
results = []
for symbol, asset_id in asset_ids.items():
quote = quotes.get(str(asset_id), {})
# 4. Score the asset
score = compute_tao_score(quote)
# 5. Regime filter
if not is_regime_favorable(regime):
score -= 20
# 6. wTAO pool validation (Ethereum DEX)
pool_liq = None
if symbol in ("TAO", "wTAO") and wtao_contract_address:
try:
pools = fetch_wtao_pools(wtao_contract_address)
best = get_best_pool(pools)
pool_liq = (best or {}).get("liqUsd")
except Exception:
pass
# 7. CEX liquidity proxy
liq = estimate_cex_liquidity(quote)
results.append({
"symbol": symbol,
"score": score,
"price": quote.get("price"),
"pct_24h": quote.get("pct_change_24h"),
"pct_7d": quote.get("pct_change_7d"),
"volume_24h": quote.get("volume_24h"),
"market_cap": quote.get("market_cap"),
"liquidity_signal": liq["liquidity_signal"],
"dex_pool_liq": pool_liq,
"regime_favorable": is_regime_favorable(regime),
})
# 8. AI narrative context
ai_trending = [
{"symbol": a.get("symbol"), "pct_24h": (a.get("quote") or [{}])[0].get("percent_change_24h")}
for a in ai_assets[:10]
]
return {
"signals": sorted(results, key=lambda x: -x["score"]),
"ai_trending": ai_trending,
"regime": regime,
}
Pass high-scoring signals to your Bittensor SDK layer for on-chain subnet state validation and staking decisions.
Rate Limits and Polling
CoinMarketCap API is REST-only. There is no WebSocket streaming.
Cache Intervals
Endpoint Group
Cache Interval
Quotes, /v3/cryptocurrency/quotes/latest
60 seconds
Listings, /v3/cryptocurrency/listings/latest
60 seconds
DEX pairs, /v4/dex/spot-pairs/latest
60 seconds
Pool data, /v1/dex/token/pools
60 seconds
Candles, /v1/k-line/candles
60 seconds
Fear & Greed, Altcoin Season
15 minutes
Historical quotes
5 minutes, paid plan only
Best Practices
- poll every 60 seconds for all price and market data endpoints
- poll macro endpoints every 15 minutes
- cache responses locally between polls
- use exponential backoff for HTTP 429 errors, rate reset at 60 seconds
def request_with_backoff(fn, retries=3, base_delay=2):
for attempt in range(retries):
try:
return fn()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
time.sleep(base_delay ** attempt)
else:
raise
raise Exception("Max retries exceeded")
Common Mistakes
Using symbols instead of IDs
Symbols are ambiguous — multiple tokens share the same symbol. Always resolve to a CMC id via /v1/cryptocurrency/map and use that for all Core API calls.
Expecting tvl to be populated for TAO
Bittensor is a Proof-of-Intelligence network, not a DeFi lending or AMM protocol. tvl will almost always return null for TAO. Never index it directly.
Parsing quote as a dict in v3
In /v3/cryptocurrency/quotes/latest, quote is a list. Using asset["quote"]["USD"] raises an AttributeError. The correct pattern is:
next((q for q in asset.get("quote", []) if q.get("symbol") == "USD"), {})
Iterating raw_quotes as a dict
The data field from /v3/cryptocurrency/quotes/latest is a list. Build the lookup dict by iterating:
{str(a["id"]): parse_quote(a) for a in raw_quotes}
Querying native TAO via DEX API
Native TAO lives on the Subtensor chain, which is not an EVM chain indexed by the CMC DEX API. Use Wrapped TAO (wTAO) on Ethereum for all DEX-level signals.
Passing tag="ai-big-data" as a query parameter
Returns a 400 error. The tag query parameter only accepts "all", "defi", or "filesharing". Filter AI ecosystem assets locally by inspecting the tags array in each response object.
Passing only network_slug to /v4/dex/spot-pairs/latest
dex_slug is required. Omitting it returns a 400 error.
Omitting network_slug from pool quotes
/v4/dex/pairs/quotes/latest requires network_slug alongside contract_address. Omitting it returns a 400 error.
Assuming /v2/cryptocurrency/market-pairs/latest works on Basic
Returns 403 in practice. Use volume_24h from quotes as a CEX liquidity proxy.
Assuming /v3/cryptocurrency/quotes/historical works on Basic
Returns 403 for any time-series request. Backtesting requires a paid plan.
Using /v4/dex/pairs/ohlcv/historical
Returns 500 in production. Use /v1/k-line/candles for all DEX candle data.
Calling .get() on candle arrays
Candles from /v1/k-line/candles are positional arrays. Use candle[6] for traders — not candle.get("traders").
Treating CMC as a subnet state monitor
CMC does not track subnet emissions, validator weights, miner rankings, or alpha token AMM state. Always validate subnet state via Bittensor SDK or Subtensor RPC before any staking or registration decision.
Final Thoughts
Bittensor is creating a decentralized market for AI output. TAO emissions flow to wherever the network believes intelligence is being produced most effectively.
CoinMarketCap API gives you the structured signal layer to detect when market conditions favor TAO and the broader AI narrative — before you interact with the Bittensor network on-chain.
The key separation:
- CoinMarketCap identifies market conditions and AI narrative momentum
- Bittensor SDK and Subtensor RPC validate subnet state and execution
Better signals lead to better staking decisions.
Next Steps
- add per-subnet alpha token tracking as they gain CMC listings
- set alerts for TAO score threshold breaches across timeframes
- integrate Bittensor SDK to pull live subnet emission rates and validator stakes
- cross-reference CMC momentum signals with on-chain subnet registration cost changes
- track AI narrative rotation — which sectors (inference, storage, data) are gaining emissions
- store snapshots locally to build rolling TAO market signal history
