sifting/io
Developer Tutorials
5 min readSiftingIO Team

How to Get Real-Time Forex Prices in Python

Get real-time forex prices in Python: a requests call for a snapshot, a websockets feed for live updates, the official siftingio SDK with sync and async clients, and buffering ticks into a pandas DataFrame.

How to Get Real-Time Forex Prices in Python

The short answer#

There are two ways to read live forex prices in Python, and the right one depends on how often the number has to be fresh. For an occasional price, send one HTTP request with the requests library. For a screen or a process that should update as the market moves, open one WebSocket connection and let prices arrive on their own. Both use the same SiftingIO API key and the same pair symbols, so moving from one to the other later does not mean rewriting how the data is read. This post shows both with the plain requests and websockets libraries and with the official Python SDK, plus how to buffer live ticks into a pandas DataFrame.

Setup#

Two libraries cover both approaches. Install them and put the API key in an environment variable rather than in the source:

pip install requests websockets
export SIFTING_KEY="sft_your_key_here"

The key authenticates over the X-API-Key header for REST and as a query parameter for the WebSocket. A free key works for everything below.

A single price with requests#

When the price is only needed now and then, one request is enough. The quote endpoint takes the asset class and the pair and returns the current two-sided market:

import os
import requests

key = os.environ["SIFTING_KEY"]
resp = requests.get(
    "https://api.sifting.io/v1/last/quote/forex/EURUSD",
    headers={"X-API-Key": key},
    timeout=10,
)
resp.raise_for_status()
print(resp.json())

The pair is written EURUSD, six letters with no slash and no space; the forex product page lists the pairs available, from the majors down to the less traded crosses. The response body carries the bid, the ask, the mid, and the spread, so there is nothing to compute on the client. This is the right tool for a converter or a price card that refreshes every few seconds, where holding a connection open would be more work than it saves.

A live feed with websockets#

When prices should move on their own, polling the request above in a loop is the wrong approach: it spends a call on every check whether the price changed or not. Open one connection instead, subscribe to the pairs once, and react to each update as it lands.

import asyncio
import json
import os
import websockets

KEY = os.environ["SIFTING_KEY"]
URL = f"wss://stream.sifting.io/ws/v1?key={KEY}"

async def stream():
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({
            "op": "subscribe",
            "product": "fx",
            "symbols": ["EURUSD", "GBPUSD", "USDJPY"],
        }))
        async for message in ws:
            frame = json.loads(message)
            if frame.get("f") == "tick":
                print(frame["s"], "bid", frame["b"], "ask", frame["a"])

asyncio.run(stream())

Each update arrives as a tick frame: s is the symbol, b and a are the bid and ask, p is the price, and t is a millisecond timestamp. On subscribe, the server sends the last cached value first, so the feed is never blank while it waits for the next move. The server closes a connection that sends nothing for 90 seconds; the websockets library sends protocol-level keepalive pings on its own by default, which satisfies that. If those are disabled, send an application ping with {"op": "ping"} about once a minute instead.

Collecting ticks into a pandas DataFrame#

For analysis rather than display, accumulate the ticks and hand them to pandas. This collects a fixed number of updates, then builds a frame and converts the epoch timestamps to UTC datetimes:

import asyncio
import json
import os
import pandas as pd
import websockets

KEY = os.environ["SIFTING_KEY"]
URL = f"wss://stream.sifting.io/ws/v1?key={KEY}"

async def collect(n=50):
    rows = []
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({
            "op": "subscribe", "product": "fx", "symbols": ["EURUSD"],
        }))
        async for message in ws:
            frame = json.loads(message)
            if frame.get("f") == "tick":
                rows.append({"symbol": frame["s"], "bid": frame["b"],
                             "ask": frame["a"], "ts": frame["t"]})
                if len(rows) >= n:
                    break
    return pd.DataFrame(rows)

df = asyncio.run(collect())
df["ts"] = pd.to_datetime(df["ts"], unit="ms", utc=True)
print(df.head())

The timestamps arrive as integer milliseconds since the epoch, so unit="ms" with utc=True turns them into a proper time index without guessing the timezone.

The official SDK#

For a project that would rather call typed methods than build requests by hand, the official Python SDK wraps the same REST and WebSocket transports. It installs with pip and ships both a synchronous and an asynchronous client:

pip install siftingio

The synchronous client suits scripts and notebooks:

import os
from siftingio import SiftingClient

client = SiftingClient(api_key=os.environ["SIFTING_KEY"])
quote = client.last.quote("forex", "EURUSD")
print(quote)

The asynchronous client suits a backend that already runs an event loop:

import asyncio
import os
from siftingio import AsyncSiftingClient

async def main():
    async with AsyncSiftingClient(api_key=os.environ["SIFTING_KEY"]) as client:
        quote = await client.last.quote("forex", "EURUSD")
        print(quote)

asyncio.run(main())

The same call works for every market; change the asset class and the symbol, so client.last.quote("crypto", "BTCUSD") returns a crypto quote with no other change. The SDK also covers the WebSocket stream, with its method reference in the Python guide. The plain requests and websockets approach above is the better fit when a project would rather not add a dependency.

A few things worth knowing#

The forex market is closed on weekends. Outside trading hours no new prices are produced, and the snapshot endpoint can return a 503 with stale_snapshot, including last_t and server_now so the code can show a clear market-closed state rather than a stale number. Keep timestamps in UTC underneath and convert to local time only when showing a value to a person. On the free plan the live feed allows up to five symbol subscriptions; asking for a sixth returns an explicit error frame rather than dropping it silently.

Summary#

Real-time forex in Python comes down to two patterns: a requests call to the quote endpoint when a price is needed on demand, and a websockets connection subscribed to the fx product when prices should update on their own, with the live ticks dropping cleanly into pandas for analysis. Both read the same fields from the same key. The documentation lists the full pair set, the WebSocket frame formats, and the SDK reference. The free plan needs no credit card, so you can create a free account and stream live EURUSD in your terminal within a few minutes.

Tagsforex api pythonreal-time forexpython tutorialwebsocketsrequestspandascurrency apimarket data api
All postsLast updated June 7, 2026
Keep reading

Related posts