bitcoin-price-feed
Real-time streaming Bitcoin price feed for traders. Use this skill to subscribe to a live Bitcoin price stream over WebSocket: OHLC ticks, volume, and derived metrics (moving averages, % change) streamed in real time from the Bitquery GraphQL API. ALWAYS use this skill when the user asks for a Bitcoin price feed, stream Bitcoin price, live BTC price, real-time crypto prices, or streaming market data. Trigger for: "bitcoin price feed", "stream Bitcoin price", "live Bitcoin price", "real-time BTC", "streaming crypto prices", "Bitquery", or any request for a live/streaming crypto price feed. Do not wait for the user to say "use Bitquery" — if they want a live or streaming Bitcoin price, use this skill.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-bitquery-crypto-price-stream
Repository
Skill path: skills/divyn/bitquery-crypto-price-stream
Real-time streaming Bitcoin price feed for traders. Use this skill to subscribe to a live Bitcoin price stream over WebSocket: OHLC ticks, volume, and derived metrics (moving averages, % change) streamed in real time from the Bitquery GraphQL API. ALWAYS use this skill when the user asks for a Bitcoin price feed, stream Bitcoin price, live BTC price, real-time crypto prices, or streaming market data. Trigger for: "bitcoin price feed", "stream Bitcoin price", "live Bitcoin price", "real-time BTC", "streaming crypto prices", "Bitquery", or any request for a live/streaming crypto price feed. Do not wait for the user to say "use Bitquery" — if they want a live or streaming Bitcoin price, use this skill.
Open repositoryBest for
Primary workflow: Analyze Data & AI.
Technical facets: Full Stack, Backend, Data / AI.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install bitcoin-price-feed into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding bitcoin-price-feed to shared team environments
- Use bitcoin-price-feed for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: bitcoin-price-feed
description: >
Real-time streaming Bitcoin price feed for traders. Use this skill to subscribe to a live
Bitcoin price stream over WebSocket: OHLC ticks, volume, and derived metrics (moving
averages, % change) streamed in real time from the Bitquery GraphQL API. ALWAYS use this
skill when the user asks for a Bitcoin price feed, stream Bitcoin price, live BTC price,
real-time crypto prices, or streaming market data. Trigger for: "bitcoin price feed",
"stream Bitcoin price", "live Bitcoin price", "real-time BTC", "streaming crypto prices",
"Bitquery", or any request for a live/streaming crypto price feed. Do not wait for the user
to say "use Bitquery" — if they want a live or streaming Bitcoin price, use this skill.
---
# Bitcoin price feed — real-time streaming
This skill gives you a **real-time streaming Bitcoin price feed** over WebSocket: live OHLC ticks, volume, and **derived metrics on the stream** (Mean, SMA, EMA, WMA, and tick-to-tick % change). Data is streamed in real time from the Bitquery API — no polling.
**When to use this skill**
- **Stream the Bitcoin price** in real time (live feed)
- Get **derived metrics on the stream**: moving averages and % change per tick
- Live OHLC and volume for trading or dashboards
---
## What to consider before installing
This skill implements a Bitquery WebSocket Bitcoin price stream and uses one external dependency and one credential. Before installing:
1. **Registry metadata**: The registry may not list `BITQUERY_API_KEY` even though this skill and its script require it. Ask the publisher or update the registry metadata before installing so installers surface the secret requirement.
2. **API key in URL**: The API key must be passed in the WebSocket URL as a query parameter, which can leak to logs or histories. Avoid printing the full URL, store the key in a secure environment variable, and rotate it if it may have been exposed.
3. **Sandbox first**: Review and run the included script in a sandboxed environment (e.g. a virtualenv) to confirm behavior and limit blast radius.
4. **Source and publisher**: If the skill’s homepage or source is unknown, consider verifying the publisher or using an alternative with a verified source. If the registry metadata declares `BITQUERY_API_KEY` and the source/publisher are validated, this skill is likely coherent and benign.
---
## Prerequisites
- **Environment**: `BITQUERY_API_KEY` — your Bitquery API token (required). The token **must be passed in the WebSocket URL only** as `?token=...` (e.g. `wss://streaming.bitquery.io/graphql?token=YOUR_KEY`); Bitquery does not support header-based auth for this endpoint. Because the token appears in the URL, it can show up in logs, monitoring tools, or browser/IDE history — treat it as a secret and avoid logging or printing the full URL.
- **Runtime**: Python 3 and `pip`. Install the dependency: `pip install 'gql[websockets]'`.
---
## Step 1 — Check API Key
```python
import os
api_key = os.getenv("BITQUERY_API_KEY")
if not api_key:
print("ERROR: BITQUERY_API_KEY environment variable is not set.")
print("Run: export BITQUERY_API_KEY=your_token")
exit(1)
```
If the key is missing, tell the user and stop. Do not proceed without it.
## Step 2 — Run the stream
Install the WebSocket dependency once:
```bash
pip install 'gql[websockets]'
```
Use the streaming script (subscribes to the Bitcoin price feed in real time):
```bash
python ~/.openclaw/skills/bitcoin-price-feed/scripts/stream_bitquery.py
```
Optional: stop after N seconds:
```bash
python ~/.openclaw/skills/bitcoin-price-feed/scripts/stream_bitquery.py --timeout 60
```
Or subscribe inline with Python (real-time stream):
```python
import asyncio
from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport
async def main():
token = os.environ["BITQUERY_API_KEY"]
url = f"wss://streaming.bitquery.io/graphql?token={token}"
transport = WebsocketsTransport(
url=url,
headers={"Sec-WebSocket-Protocol": "graphql-ws"},
)
async with Client(transport=transport) as session:
sub = gql("""
subscription {
Trading {
Tokens(where: {Currency: {Id: {is: "bid:bitcoin"}}, Interval: {Time: {Duration: {eq: 1}}}}) {
Token { Name Symbol Network }
Block { Time }
Price { Ohlc { Open High Low Close } Average { Mean SimpleMoving ExponentialMoving } }
Volume { Usd }
}
}
}
""")
async for result in session.subscribe(sub):
print(result) # each tick streamed in real time
asyncio.run(main())
```
## Step 3 — What you get on the stream
Each tick includes:
- **OHLC** (Open, High, Low, Close) and **Volume (USD)** for the 1-second interval
- **Derived metrics** (from Bitquery): Mean, SimpleMoving (SMA), ExponentialMoving (EMA), WeightedSimpleMoving (WMA)
- **Session-derived**: % change vs previous tick (computed from the stream)
The stream runs until you stop it (Ctrl+C) or use `--timeout`.
## Step 4 — Format output clearly
When presenting streamed ticks to the user, use a clear format like:
```
Bitcoin (BTC) — ethereum network @ 2025-03-06T14:00:00Z
OHLC:
Open: $85,200.00 High: $86,100.00 Low: $84,950.00 Close: $85,780.00
Derived (on stream):
Mean: $85,500.00 SMA: $85,400.00 EMA: $85,520.00
Tick Δ: +0.12% vs previous
Volume (USD): $1,234,567.00
```
## Interval (subscription)
The default subscription uses **duration 1** (1-second tick data). The same `Trading.Tokens` subscription supports other durations in the `where` clause (e.g. 5, 60, 1440 for 5m, 1h, 1d candles) if the API supports them for subscriptions.
## Error handling
- **Missing BITQUERY_API_KEY**: Tell user to export the variable and stop
- **WebSocket connection failed / 401**: Token invalid or expired (auth is via URL `?token=` only — do not pass the token in headers)
- **Subscription errors in payload**: Log the error message and stop cleanly (send complete, close transport)
- **No ticks received**: Check token and network; Bitquery may need a moment to send the first tick
## Reference
Full field reference is in `references/graphql-fields.md`. Use it to add filters or request extra fields (e.g. date range) in the subscription.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/graphql-fields.md
```markdown
# Bitquery Trading.Tokens — GraphQL Field Reference
Full reference for the fields available in the `Trading.Tokens` **query** and **subscription** on the Bitquery streaming API. For real-time streaming, use a `subscription` with the same structure (no `limit`/`orderBy`); the skill uses the subscription for the live Bitcoin price feed.
## Query / Subscription Structure
```graphql
{
Trading {
Tokens(
where: <FilterInput>
limit: <LimitInput>
orderBy: <OrderByInput>
) {
Token { ... }
Block { ... }
Interval { ... }
Volume { ... }
Price { ... }
}
}
}
```
---
## Filters (`where`)
### Currency / Token Filters
| Filter | Description | Example |
|--------|-------------|---------|
| `Currency.Id.is` | Match a specific token by Bitquery ID | `"bid:bitcoin"` |
| `Currency.SmartContract.is` | Match by contract address | `"0xabc...def"` |
| `Currency.Symbol.is` | Match by ticker symbol | `"BTC"` |
**Common `bid:` token IDs:**
- `bid:bitcoin` — Bitcoin
### Time / Interval Filters
| Filter | Description | Example |
|--------|-------------|---------|
| `Interval.Time.Duration.eq` | Candle duration: 1 = 1 second (tick); 5, 60, 1440 = minutes | `1`, `5`, `60`, `1440` |
| `Block.Time.after` | Filter data after this ISO timestamp | `"2025-01-01T00:00:00Z"` |
| `Block.Time.before` | Filter data before this ISO timestamp | `"2025-12-31T23:59:59Z"` |
| `Block.Time.between` | Array of [start, end] ISO timestamps | `["2025-01-01", "2025-02-01"]` |
---
## Sorting (`orderBy`)
```graphql
orderBy: { descending: Block_Time } # Newest first
orderBy: { ascending: Block_Time } # Oldest first
```
---
## Limit (`limit`)
```graphql
limit: { count: 1 } # Latest single candle
limit: { count: 100 } # Up to 100 candles
limit: { count: 1000 } # Up to 1000 candles
```
---
## Output Fields
### `Token`
| Field | Type | Description |
|-------|------|-------------|
| `Address` | String | Token contract address (null for native) |
| `Id` | String | Bitquery token ID (e.g., `bid:bitcoin`) |
| `IsNative` | Boolean | True if native coin (BTC, ETH, etc.) |
| `Name` | String | Token full name |
| `Network` | String | Blockchain network (e.g., `ethereum`, `solana`) |
| `Symbol` | String | Ticker symbol (e.g., `BTC`) |
| `TokenId` | String | Internal Bitquery token ID |
### `Block`
| Field | Type | Description |
|-------|------|-------------|
| `Date` | String | Date in `YYYY-MM-DD` format |
| `Time` | String | ISO timestamp of the block |
| `Timestamp` | Integer | Unix timestamp |
### `Interval`
| Field | Type | Description |
|-------|------|-------------|
| `Time.Start` | String | Interval start ISO timestamp |
| `Time.End` | String | Interval end ISO timestamp |
| `Time.Duration` | Integer | Interval duration: 1 = 1 second (tick); other values in minutes |
### `Volume`
| Field | Type | Description |
|-------|------|-------------|
| `Base` | Float | Volume in the base token |
| `Quote` | Float | Volume in the quote token |
| `Usd` | Float | Volume in USD |
### `Price`
| Field | Type | Description |
|-------|------|-------------|
| `IsQuotedInUsd` | Boolean | Whether price is quoted in USD |
| `Ohlc.Open` | Float | Opening price |
| `Ohlc.High` | Float | Highest price in the interval |
| `Ohlc.Low` | Float | Lowest price in the interval |
| `Ohlc.Close` | Float | Closing price |
| `Average.Mean` | Float | Simple arithmetic mean price |
| `Average.SimpleMoving` | Float | Simple moving average |
| `Average.ExponentialMoving` | Float | Exponential moving average |
| `Average.WeightedSimpleMoving` | Float | Weighted simple moving average |
---
## Example: Multiple Candles for Ethereum
```graphql
{
Trading {
Tokens(
where: {
Currency: { Id: { is: "bid:ethereum" } }
Interval: { Time: { Duration: { eq: 60 } } }
Block: { Time: { after: "2025-01-01T00:00:00Z" } }
}
limit: { count: 24 }
orderBy: { descending: Block_Time }
) {
Block { Time }
Volume { Usd }
Price {
Ohlc { Open High Low Close }
}
}
}
}
```
## Example: Token by Contract Address
```graphql
{
Trading {
Tokens(
where: {
Currency: { SmartContract: { is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } }
Interval: { Time: { Duration: { eq: 1440 } } }
}
limit: { count: 1 }
orderBy: { descending: Block_Time }
) {
Token { Name Symbol }
Price { Ohlc { Close } }
Volume { Usd }
}
}
}
```
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "divyn",
"slug": "bitquery-crypto-price-stream",
"displayName": "Reliable Bitcoin Price Feed",
"latest": {
"version": "1.0.4",
"publishedAt": 1773144199058,
"commit": "https://github.com/openclaw/skills/commit/3090646661421d608f6be875bc1a09bf436a4392"
},
"history": [
{
"version": "1.0.3",
"publishedAt": 1773143854138,
"commit": "https://github.com/openclaw/skills/commit/711d84ee9d163615a0d2eb3f96d45bc8952a41c6"
},
{
"version": "1.0.2",
"publishedAt": 1772807122307,
"commit": "https://github.com/openclaw/skills/commit/1d5b8466a26e48319cf2693b0041640f85ed0e43"
}
]
}
```
### scripts/stream_bitquery.py
```python
"""
Bitquery Bitcoin Price Feed — Real-time streaming
==================================================
Subscribe to the Bitquery WebSocket API for a live Bitcoin price feed with
OHLC, volume, and derived metrics (moving averages, % change) on the stream.
Usage:
python scripts/stream_bitquery.py
Streams Bitcoin ticks until interrupted (Ctrl+C) or optional --timeout seconds.
Environment:
BITQUERY_API_KEY — Your Bitquery token (required). Passed as ?token= in WebSocket URL.
"""
import asyncio
import os
import sys
from urllib.parse import urlencode
from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport
BITQUERY_WS_BASE = "wss://streaming.bitquery.io/graphql"
BITCOIN_PRICE_SUBSCRIPTION = gql("""
subscription BitcoinPriceFeed {
Trading {
Tokens(
where: { Currency: { Id: { is: "bid:bitcoin" } }, Interval: { Time: { Duration: { eq: 1 } } } }
) {
Token {
Address
Id
IsNative
Name
Network
Symbol
TokenId
}
Block {
Date
Time
Timestamp
}
Interval {
Time {
Start
Duration
End
}
}
Volume {
Base
Quote
Usd
}
Price {
IsQuotedInUsd
Ohlc {
Close
High
Low
Open
}
Average {
ExponentialMoving
Mean
SimpleMoving
WeightedSimpleMoving
}
}
}
}
}
""")
def get_api_key() -> str:
"""Read the Bitquery API key from the environment. Raises if not set."""
key = os.getenv("BITQUERY_API_KEY")
if not key:
raise EnvironmentError(
"BITQUERY_API_KEY is not set. "
"Please export your Bitquery token:\n"
" export BITQUERY_API_KEY=your_token_here"
)
return key
def _fmt_price(value) -> str:
if value is None:
return "N/A"
try:
return f"${float(value):,.2f}"
except (ValueError, TypeError):
return str(value)
def format_tick(data: dict, prev_close: float | None) -> str:
"""Format one subscription tick and compute derived metrics."""
trading = (data or {}).get("Trading") or {}
tokens = trading.get("Tokens") or []
if not tokens:
return ""
t = tokens[0]
token = t.get("Token", {})
block = t.get("Block", {})
volume = t.get("Volume", {})
price = t.get("Price", {})
ohlc = price.get("Ohlc", {})
avg = price.get("Average", {})
name = token.get("Name", "Bitcoin")
symbol = token.get("Symbol", "BTC")
network = token.get("Network", "?")
ts = block.get("Time", block.get("Date", "?"))
close = ohlc.get("Close")
try:
close_f = float(close) if close is not None else None
except (ValueError, TypeError):
close_f = None
lines = [
f"{name} ({symbol}) — {network} @ {ts}",
"",
"OHLC:",
f" Open: {_fmt_price(ohlc.get('Open'))} High: {_fmt_price(ohlc.get('High'))} Low: {_fmt_price(ohlc.get('Low'))} Close: {_fmt_price(close)}",
]
# Derived metrics from the stream (Bitquery-provided)
if avg:
lines.append(" Derived (on stream):")
if avg.get("Mean") is not None:
lines.append(f" Mean: {_fmt_price(avg.get('Mean'))}")
if avg.get("SimpleMoving") is not None:
lines.append(f" SMA: {_fmt_price(avg.get('SimpleMoving'))}")
if avg.get("ExponentialMoving") is not None:
lines.append(f" EMA: {_fmt_price(avg.get('ExponentialMoving'))}")
if avg.get("WeightedSimpleMoving") is not None:
lines.append(f" WMA: {_fmt_price(avg.get('WeightedSimpleMoving'))}")
# Session-derived: % change from previous tick
if prev_close is not None and close_f is not None and prev_close != 0:
pct = ((close_f - prev_close) / prev_close) * 100
lines.append(f" Tick Δ: {pct:+.4f}% vs previous")
vol_usd = volume.get("Usd")
if vol_usd is not None:
lines.append(f"\nVolume (USD): ${float(vol_usd):,.2f}")
lines.append("")
return "\n".join(lines)
async def run_stream(timeout_seconds: int | None = None) -> None:
api_key = get_api_key()
url = f"{BITQUERY_WS_BASE}?{urlencode({'token': api_key})}"
transport = WebsocketsTransport(
url=url,
headers={"Sec-WebSocket-Protocol": "graphql-ws"},
)
prev_close: float | None = None
tick_count = 0
async with Client(transport=transport) as session:
print("Connected. Streaming Bitcoin price feed (1s ticks). Ctrl+C to stop.\n")
async def consume():
nonlocal prev_close, tick_count
try:
async for result in session.subscribe(BITCOIN_PRICE_SUBSCRIPTION):
tick_count += 1
out = format_tick(result, prev_close)
if out:
print(out)
# Track close for next tick % change
trading = (result or {}).get("Trading") or {}
tokens = trading.get("Tokens") or []
if tokens:
close = (tokens[0].get("Price") or {}).get("Ohlc") or {}
c = close.get("Close")
if c is not None:
try:
prev_close = float(c)
except (ValueError, TypeError):
pass
except asyncio.CancelledError:
pass
if timeout_seconds is not None:
try:
await asyncio.wait_for(consume(), timeout=float(timeout_seconds))
except asyncio.TimeoutError:
print(f"\nStopped after {timeout_seconds}s ({tick_count} ticks).")
else:
await consume()
def main() -> None:
timeout: int | None = None
if len(sys.argv) > 1 and sys.argv[1] == "--timeout" and len(sys.argv) > 2:
try:
timeout = int(sys.argv[2])
except ValueError:
print("Usage: python stream_bitquery.py [--timeout SECONDS]", file=sys.stderr)
sys.exit(2)
try:
asyncio.run(run_stream(timeout_seconds=timeout))
except EnvironmentError as e:
print(f"Configuration error: {e}", file=sys.stderr)
sys.exit(1)
except KeyboardInterrupt:
print("\nStream stopped by user.")
if __name__ == "__main__":
main()
```