Skip to content

Developers

Private WebSocket

Trading on margin can lose you more than you put in. Past results, including backtests, do not predict future results. Cloud9Trader provides tools and education, not financial advice.

The private socket is the public socket plus your own account: balances, positions, orders and executions from every broker you have connected, and order submission. It uses the same address, the same messages and the same public topics.

Orders sent here go to your real broker account. Connect a practice account first: see Connect an OANDA practice account. Your demo account’s balances, positions, orders and executions stream here too, tagged with the broker Demo, but it takes no orders through the API.

You need an API key and its secret, from API Access in your account settings. See Authentication and keys.

Send three headers with the connection request:

Header Value
x-c9t-key Your API key
x-c9t-nonce The current time in milliseconds since 1 January 1970, as an integer
x-c9t-signature HMAC-SHA256 of "/" followed by the nonce, keyed with your secret decoded from base64, as lowercase hex

The server refuses a connection when:

  • the nonce is more than 2 seconds from its clock, so keep your clock in sync;
  • the nonce is not larger than the last one used with that key in the last 5 seconds, so never reuse one;
  • the request has an Origin or Referer header. A web browser always sends one, so a private socket can only be opened from a server or script, where the secret stays safe.
TypeScript (Node.js, ws)
import { createHmac } from "node:crypto"
import WebSocket from "ws"
const key = process.env.C9T_API_KEY!
const secret = process.env.C9T_API_SECRET!
const nonce = Date.now().toString()
const signature = createHmac("sha256", Buffer.from(secret, "base64")).update("/" + nonce).digest("hex")
const socket = new WebSocket("wss://sockets.cloud9trader.com/", {
headers: { "x-c9t-key": key, "x-c9t-nonce": nonce, "x-c9t-signature": signature },
})
socket.on("open", () => socket.send(JSON.stringify(["subscribe", "positions"])))
socket.on("message", (raw) => console.log(JSON.parse(raw.toString())))
socket.on("unexpected-response", (_request, response) => console.error("Refused:", response.statusCode))
Python (websockets 14 or later)
import asyncio, base64, hashlib, hmac, json, os, time, websockets
key = os.environ["C9T_API_KEY"]
secret = os.environ["C9T_API_SECRET"]
async def main():
nonce = str(int(time.time() * 1000))
signature = hmac.new(base64.b64decode(secret), ("/" + nonce).encode(), hashlib.sha256).hexdigest()
headers = {"x-c9t-key": key, "x-c9t-nonce": nonce, "x-c9t-signature": signature}
async with websockets.connect("wss://sockets.cloud9trader.com/", additional_headers=headers) as socket:
await socket.send(json.dumps(["subscribe", "positions"]))
async for raw in socket:
print(json.loads(raw))
asyncio.run(main())

Make a new nonce and signature for every connection, including every reconnect.

The server answers the connection request with an HTTP status instead of opening the socket:

Status Message Fix
401 'x-c9t-signature' is a required header for HMAC authentication Send the signature header.
401 'x-c9t-nonce' is a required header for HMAC authentication Send the nonce header.
401 'x-c9t-nonce' value must be an integer Send milliseconds as a whole number.
401 'x-c9t-nonce' value must be current time in milliseconds elapsed since January 1, 1970. Ensure your system clock is accurate Sync your clock.
401 'x-c9t-nonce' value was too small Use a new, larger nonce.
401 Key is incorrect Check the key, or create a new one.
401 Key or signature are incorrect Check the secret and how you sign.
403 Authenticated connections must not be made from web browsers Remove the Origin and Referer headers, and connect from a server.
500 Server error, Please retry Try again shortly.
Topic Request Subscribe Data
balances Yes Yes Each account’s balance and margin
positions Yes Yes Open positions
orders Yes Yes Working orders and their changes
executions Yes Yes Fills
messages No Automatic Notices from your broker connections
logs No Automatic Log lines about your connections

A request, or the first message of a subscription, is a snapshot: an array of items from every connected broker, each with snapshot: true. A third element says which brokers answered:

["positions", [{ "broker": "OANDA", "id": "1234", "instrumentId": "EUR_USD:OANDA", "symbol": "EUR_USD", "quantity": 1000, "direction": "LONG", "status": "OPEN", "snapshot": true }], { "connected": ["OANDA"], "disconnected": [] }]

After that, updates arrive as [topic, items], each item with its broker. With several brokers, the snapshot arrives as one message per broker.

messages and logs start by themselves when you connect. Private topics cannot be unsubscribed; close the socket to stop them.

Every item has broker. The other fields depend on the broker; these are the ones to rely on.

Topic Fields
balances broker, accountNo, balance, equity, available, used, marginRatio, marginCall
positions broker, id, accountNo, instrumentId, symbol, quantity, direction (LONG or SHORT), status (OPEN or CLOSED), openPrice, profit
orders broker, id, instrumentId, symbol, side (BUY or SELL), type, quantity, quantityFilled, quantityRemaining, price, stopPrice, timeInForce, status (NEW, PARTIALLY_FILLED, FILLED, CANCELED, REJECTED or EXPIRED)
executions broker, id, instrumentId, symbol, time, side, quantity, price, orderId, orderType, orderStatus, fee, feeCurrency
["submit", "order", { "broker": "Bitfinex", "symbol": "BTCUSD", "side": "BUY", "type": "LIMIT", "quantity": 0.01, "limitPrice": 60000 }, "my-order-1"]
Field Required Values
broker Yes A broker you have connected, as it appears in balances, such as OANDA or Bitfinex
symbol Yes The broker’s symbol, 3 to 20 characters, such as EUR_USD or BTCUSD
side Yes BUY or SELL
type Yes MARKET, LIMIT, STOP or STOP_LIMIT
quantity Yes A number, in units of the instrument
limitPrice For LIMIT and STOP_LIMIT A number
stopPrice For STOP and STOP_LIMIT A number
timeInForce No GTC, DAY, IOC, FOK or OPG

What each broker takes through the API:

Broker Order types Time in force
OANDA MARKET only. Other types are refused with “Only market orders are currently supported”. FOK or IOC
Bitfinex MARKET, LIMIT, STOP and STOP_LIMIT, on the exchange wallet As the exchange allows

With a requestId, the answer is ["my-order-1", null, order] when the broker accepts it (OANDA sends no order), or ["my-order-1", "reason"] when it does not. Without one, success arrives as ["message", "Order submitted successfully"], failure as ["error", reason], and the order itself on orders. Always send a requestId, so you know which order an answer is about.

Closing positions through the API is not working at the moment. The request below is accepted, but on OANDA and Bitfinex no position is closed and no answer comes back. Close positions on your broker’s own site until this is fixed.

["submit", "close-position", { "broker": "OANDA", "brokerPositionId": "1234" }, "close-1"]
Field Required Values
broker Yes The position’s broker
brokerPositionId Yes The position’s id from positions

When it works, the answer will be ["close-1", null], or ["close-1", "reason"] if the broker refuses.

The reason in an order or close answer is one of these. <field> and <broker> stand for the value concerned; several validation messages arrive together, one per line.

Message Meaning
<field> is a required field A required field is missing.
<field> value not recognized A field is not one of its allowed values.
<field> must be a number quantity, limitPrice or stopPrice is not a number.
<field> must be a string A text field is not a string.
<field> cannot be less than 0 A quantity or price is negative.
Instrument symbol must be at least 3 characters long symbol is too short.
Instrument symbol must be under 20 characters long symbol is too long.
<field> is required for <type> orders limitPrice or stopPrice is missing for that order type.
No connection available for <broker>. Please provide API keys You have no live connection to that broker. Add its keys in Exchange API Keys.
Error submitting order: <reason> The broker refused the order or the close. The reason is the broker’s.

Errors fetching a snapshot from one broker arrive as ["error", "Error fetching <topic> for <broker>"]; the other brokers’ items still arrive.