Dune Query API with Python: On-Chain Data for Traders
Learn how to use the Dune Analytics Query API with Python to pull on-chain blockchain data, automate analysis, and build smarter crypto trading strategies.
Learn how to use the Dune Analytics Query API with Python to pull on-chain blockchain data, automate analysis, and build smarter crypto trading strategies.
On-chain data is one of the most underused edges in crypto trading. While retail traders are glued to price charts on Binance and Bybit, the real signal often lives in the raw blockchain — wallet flows, liquidity shifts, smart contract activity. Dune Analytics makes that data accessible through SQL queries, and their API lets you pull it directly into Python for automation, alerting, and strategy building.
The Dune Query API gives you programmatic access to any query saved on Dune's platform — including thousands of community-built dashboards tracking DEX volumes, whale wallets, stablecoin flows, and more. Pair that with Python and you have a lightweight but powerful data pipeline that most traders aren't using.
Dune Analytics is a platform where analysts write SQL queries against indexed blockchain data — Ethereum, Base, Solana, Arbitrum, Polygon, and more. Instead of syncing your own node or paying for expensive data providers, you write SQL and Dune handles the infrastructure.
The Query API extends this by letting you execute queries and retrieve results programmatically. This matters because manual dashboard checks don't scale. If you're watching 20 tokens, checking whale accumulation patterns, or monitoring liquidity on OKX or Coinbase's on-chain activity, you need automation — not a browser tab.
Dune's free tier allows a limited number of API calls per month and query executions may be throttled. For production pipelines, the Premium plan gives you faster execution and higher rate limits. Always cache results locally to avoid burning your quota on repeated identical calls.
Getting started requires a Dune account and an API key. Head to your Dune profile settings and generate an API key under the API section. Keep this key in an environment variable — never hardcode it in scripts you might share or push to GitHub.
pip install requests pandas python-dotenv
Create a `.env` file in your project root and store your key there. Then load it in your Python script using dotenv. Here's the base setup that every Dune API script should start with:
import os
import requests
import pandas as pd
from dotenv import load_dotenv
from time import sleep
load_dotenv()
API_KEY = os.getenv("DUNE_API_KEY")
BASE_URL = "https://api.dune.com/api/v1"
HEADERS = {
"X-Dune-API-Key": API_KEY,
"Content-Type": "application/json"
}
def get(endpoint: str, params: dict = None) -> dict:
url = f"{BASE_URL}{endpoint}"
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
def post(endpoint: str, payload: dict = None) -> dict:
url = f"{BASE_URL}{endpoint}"
response = requests.post(url, headers=HEADERS, json=payload or {})
response.raise_for_status()
return response.json()
The Dune API works in two steps: first you trigger a query execution, then you poll for results. Queries don't always run instantly — especially complex ones joining multiple blockchain tables. The execution model is async by design, so your code needs to handle the wait.
Every Dune query has a numeric ID visible in its URL — for example, `dune.com/queries/3141592` has query ID `3141592`. You'll use this ID to trigger runs and fetch results. Here's a complete function that handles the full execute-poll-return cycle:
def execute_query(query_id: int, parameters: dict = None) -> pd.DataFrame:
"""
Execute a Dune query and return results as a DataFrame.
Handles async polling with exponential backoff.
"""
# Trigger execution
payload = {}
if parameters:
payload["query_parameters"] = parameters
exec_response = post(f"/query/{query_id}/execute", payload)
execution_id = exec_response["execution_id"]
print(f"Execution started: {execution_id}")
# Poll for completion
wait = 2
max_attempts = 30
for attempt in range(max_attempts):
status_response = get(f"/execution/{execution_id}/status")
state = status_response["state"]
print(f"Attempt {attempt + 1}: state = {state}")
if state == "QUERY_STATE_COMPLETED":
break
elif state in ("QUERY_STATE_FAILED", "QUERY_STATE_CANCELLED"):
raise RuntimeError(f"Query {query_id} failed with state: {state}")
sleep(min(wait, 30)) # cap wait at 30s
wait *= 1.5
else:
raise TimeoutError(f"Query {query_id} did not complete after {max_attempts} attempts")
# Fetch results
result_response = get(f"/execution/{execution_id}/results")
rows = result_response["result"]["rows"]
columns = result_response["result"]["metadata"]["column_names"]
return pd.DataFrame(rows, columns=columns)
# Example: fetch latest ETH whale transfers (replace with any public query ID)
df = execute_query(query_id=3141592)
print(df.head())
Instead of re-executing a query every time, use the `/query/{id}/results` endpoint to fetch the latest cached results without burning an execution. This is much faster and costs fewer API credits — only trigger a fresh execution when you need truly up-to-date data.
Raw data fetching is just the start. The real value is connecting on-chain signals to your trading workflow. Here are patterns that traders actually use in production.
One of the most powerful use cases is tracking net exchange inflows and outflows. When large amounts of BTC or ETH move to Binance, selling pressure typically follows. When assets leave exchanges to cold wallets, it signals accumulation. You can build a Dune query for this and poll it hourly via Python.
VoiceOfChain already surfaces derived trading signals from on-chain activity in real time — but for traders who want to build custom signals around specific wallets, protocols, or tokens, the Dune API is the right tool. You can cross-reference VoiceOfChain signals with your own Dune data to validate before entering a position on Bybit or OKX.
import time
import smtplib
from email.message import EmailMessage
def fetch_latest_results(query_id: int) -> pd.DataFrame:
"""Fetch cached results without triggering a new execution."""
response = get(f"/query/{query_id}/results")
rows = response["result"]["rows"]
columns = response["result"]["metadata"]["column_names"]
return pd.DataFrame(rows, columns=columns)
def monitor_exchange_inflows(query_id: int, threshold_usd: float = 5_000_000):
"""
Poll exchange inflow query every 5 minutes.
Alert if net inflow exceeds threshold (bearish signal).
"""
print(f"Monitoring exchange inflows (alert at >${threshold_usd:,.0f})...")
while True:
try:
df = fetch_latest_results(query_id)
# Assume query returns columns: symbol, net_inflow_usd
high_inflow = df[df["net_inflow_usd"] > threshold_usd]
if not high_inflow.empty:
for _, row in high_inflow.iterrows():
print(f"ALERT: {row['symbol']} net inflow = ${row['net_inflow_usd']:,.0f}")
# Add your notification logic here (Telegram, email, webhook)
except requests.HTTPError as e:
print(f"API error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
time.sleep(300) # poll every 5 minutes
# Replace with your actual query ID tracking exchange inflows
monitor_exchange_inflows(query_id=1234567, threshold_usd=5_000_000)
One underused feature of the Dune API is parameterized queries. Instead of hardcoding a wallet address or token contract in your SQL, you define parameters and pass them at execution time via the API. This turns a single Dune query into a reusable function.
For example, you could have a single query that analyzes any ERC-20 token's holder distribution, and call it with different contract addresses as you screen tokens. This is especially useful for traders on platforms like Gate.io or KuCoin that list new tokens frequently — you can run due diligence on holder concentration before entering a position.
def analyze_token_holders(query_id: int, token_address: str, min_balance_usd: float = 1000) -> pd.DataFrame:
"""
Run a parameterized Dune query for a specific token.
The Dune query must define {{token_address}} and {{min_balance_usd}} as parameters.
"""
parameters = {
"token_address": token_address,
"min_balance_usd": str(min_balance_usd)
}
df = execute_query(query_id=query_id, parameters=parameters)
if df.empty:
print(f"No holder data found for {token_address}")
return df
total_holders = len(df)
top10_pct = df.nlargest(10, "balance_usd")["balance_usd"].sum() / df["balance_usd"].sum() * 100
print(f"Token: {token_address}")
print(f"Holders above ${min_balance_usd:,.0f}: {total_holders}")
print(f"Top 10 holders control: {top10_pct:.1f}% of supply")
if top10_pct > 80:
print("WARNING: Highly concentrated — potential rug risk")
elif top10_pct < 40:
print("INFO: Well distributed supply")
return df
# Example: screen a new token before buying on KuCoin or Gate.io
df = analyze_token_holders(
query_id=9876543, # your parameterized holder query
token_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC as example
min_balance_usd=5000
)
| Endpoint | Method | Purpose |
|---|---|---|
| /query/{id}/execute | POST | Trigger a new query execution |
| /execution/{id}/status | GET | Check execution state |
| /execution/{id}/results | GET | Fetch results by execution ID |
| /query/{id}/results | GET | Fetch latest cached results |
| /query/{id} | GET | Get query metadata and SQL |
The Dune Query API bridges the gap between raw blockchain data and actionable trading intelligence. With a few hundred lines of Python you can build monitoring systems that catch whale movements before they show up in price action, screen new token listings for concentration risk before buying on Binance or Bitget, and automate the kind of on-chain due diligence that used to take hours of manual dashboard work.
The pattern is consistent regardless of what you're tracking: find or write a Dune query, save it, grab the query ID, and call it from Python on whatever schedule makes sense for your strategy. Start simple — poll one query, log the results, check for anomalies. Add complexity only when the simple version proves its value.
For traders who want on-chain signals without building their own pipeline, VoiceOfChain delivers pre-processed signals in real time — covering market structure, sentiment shifts, and volume anomalies across major pairs. The Dune API and platforms like VoiceOfChain are complementary: one gives you raw data flexibility, the other gives you speed. The best-informed traders use both.
Start with public community queries before writing your own SQL. Dune's discovery page has thousands of battle-tested queries tracking DEX volumes, stablecoin flows, NFT activity, and more. Fork one, call it via the API, and iterate from there — it's much faster than starting from a blank SQL editor.