⬡ TECHNICAL TUTORIAL · NO AFFILIATE

Deriv API in Python: a WebSocket connection from scratch.

A technical programming tutorial. We'll connect to the Deriv API, authenticate, pull real-time candles and structure the foundation of a bot. Educational content — this page does not promote trading on Deriv.

By the RoboTraderIA Team· 12 min read· intermediate level
Content typeTECHNICAL
Tutorialprogramming
Learn the structure. Apply it where it makes sense.
Language Python 3.11+ Protocol WebSocket Libraries websockets, asyncio Account demo (free)
Skip to the code →
100% educational · no affiliate link

Why this tutorial exists (and what it isn't): Deriv has a genuinely public API and well-documented WebSocket, which is rare among platforms in this segment. It's excellent material for learning bot programming — authentication, data streaming, async processing. This page teaches the technology. It does not recommend trading Deriv's synthetic indices or fixed-odds contracts — these products are restricted or banned for retail clients in several jurisdictions and carry the risks typical of the segment. Use the WebSocket knowledge learned here to connect to any platform with an API.

01Why study the Deriv API

Three reasons it makes a good technical case: a public, clear documentation, a free sandbox/demo for testing (no deposit needed), and the WebSocket protocol — which is exactly what you'll need to understand to connect to any modern crypto exchange, MT5 via a bridge, or any platform with real-time data streaming. What you learn here transfers directly to Binance, Bybit, Deribit.

02Setting up the environment

You need Python 3.11+, a virtualenv and two libraries. In the terminal:

# Create the project and an isolated environment
mkdir deriv-bot && cd deriv-bot
python -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate     # Windows

# Install dependencies
pip install websockets asyncio python-dotenv

Next, create a demo account on Deriv (free, no deposit) just to get a read-only API token. You don't need to deposit or trade.

  1. Go to app.deriv.com and create a demo account
  2. Go to Settings → API Tokens
  3. Create a token with the Read scope (read-only) for this tutorial
  4. Copy the token and save it in a .env file in the project:
# .env
DERIV_TOKEN=your_token_here
DERIV_APP_ID=1089  # public test app_id

03Hello World: connecting to the WebSocket

Deriv's server uses WebSocket at wss://ws.derivws.com/websockets/v3. All communication is asynchronous — you send a JSON message, the server replies in JSON, possibly streaming. First contact:

# connect.py
import asyncio, json, os
import websockets
from dotenv import load_dotenv

load_dotenv()
APP_ID = os.getenv("DERIV_APP_ID")
URL    = f"wss://ws.derivws.com/websockets/v3?app_id={APP_ID}"

async def ping():
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({"ping": 1}))
        response = await ws.recv()
        print("Server replied:", response)

asyncio.run(ping())

Run it with python connect.py. You should see {"echo_req": {"ping": 1}, "msg_type": "pong", "ping": "pong"}. If you did, congratulations — you're connected. If not, check your firewall or proxy.

04Authentication

After the handshake, you authorize with the token:

# authenticate.py
async def authenticate():
    token = os.getenv("DERIV_TOKEN")
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({"authorize": token}))
        resp = json.loads(await ws.recv())
        if "error" in resp:
            print("Failed:", resp["error"]["message"])
        else:
            print("Account:", resp["authorize"]["loginid"])
            print("Balance:", resp["authorize"]["balance"])

Security best practice: never commit the token to Git. Always use .env + .gitignore. For production, use a secrets vault (AWS Secrets Manager, HashiCorp Vault).

05Streaming candles in real time

This is where WebSocket shines. Instead of polling REST every second, you subscribe to a stream and the server pushes updates on its own:

# candles.py
async def stream_candles(symbol="frxEURUSD", granularity=60):
    async with websockets.connect(URL) as ws:
        await ws.send(json.dumps({
            "ticks_history": symbol,
            "adjust_start_time": 1,
            "count": 100,
            "end": "latest",
            "style": "candles",
            "granularity": granularity,
            "subscribe": 1
        }))

        while True:
            msg = json.loads(await ws.recv())
            if msg.get("msg_type") == "ohlc":
                c = msg["ohlc"]
                print(f"{c['epoch']} O:{c['open']} H:{c['high']} L:{c['low']} C:{c['close']}")

asyncio.run(stream_candles())

The subscribe: 1 parameter is the magic — it keeps the connection open and pushes each new candle. You don't need to ask again.

06Structuring it like a real bot

The example above is instructional. A real bot has three separate layers: connection (auto-reconnect, heartbeat), strategy (pure, testable rules), and execution (sending orders, managing state). Recommended skeleton:

# structure/
# ├── client.py       # WebSocket + reconnect + heartbeat
# ├── strategy.py     # Pure functions: given a DataFrame, return a signal
# ├── executor.py     # Sends orders, manages the open position
# ├── risk.py         # Stop loss, take profit, sizing
# └── main.py         # Glues it all together

# strategy.py - example: moving-average crossover
import pandas as pd

def compute_signal(df: pd.DataFrame) -> str:
    df["fast_ma"] = df["close"].rolling(9).mean()
    df["slow_ma"] = df["close"].rolling(21).mean()

    last = df.iloc[-1]
    prev = df.iloc[-2]

    # crossover up
    if prev["fast_ma"] < prev["slow_ma"] and last["fast_ma"] > last["slow_ma"]:
        return "buy"
    # crossover down
    if prev["fast_ma"] > prev["slow_ma"] and last["fast_ma"] < last["slow_ma"]:
        return "sell"
    return "hold"

The separation matters: a pure-function strategy is testable with pytest in seconds, without connecting to anything. You run a backtest over 5 years of data, validate it, and only then plug it into the real executor.

07Error handling and reconnection

A WebSocket in production will drop. The server restarts, the network wobbles, a timeout fires. A bot that doesn't reconnect is a useless bot. Recommended pattern:

async def connect_with_retry():
    backoff = 1
    while True:
        try:
            async with websockets.connect(URL, ping_interval=20) as ws:
                print("Connected")
                backoff = 1  # reset
                await main_loop(ws)
        except (websockets.ConnectionClosed, OSError) as e:
            print(f"Connection dropped: {e}. Reconnecting in {backoff}s")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)  # exponential, capped at 60s

08Where to apply this knowledge

The WebSocket + asyncio pattern you learned works on almost every modern platform with streaming. Where it takes you:

Want the complete bot skeleton?

Download our sample project on GitHub — code commented line by line, with tests and a clean architecture.

Download the free bot →
MIT License · use, adapt, share