Change Log
Introduction
OX.FUN offers a REST API and streaming WebSocket API that allows traders and developers to integrate their algorithms, trading strategies, risk management systems, and order execution software with our trading platform. OX.FUN's API emphasizes performance and security, enabling users to access the full range of our platform's features: order placement, account/subaccount management, and market data.
REST API
OX.FUN's REST API allows users to execute trades, manage their accounts, and access market data. With extensive documentation and sample python code, the REST API is easy to use and can be integrated with a wide variety of trading software. The REST API also offers SSL/TLS connections, rate limiting, and IP whitelisting, ensuring that all communication between the user and the server is secure.
WebSocket API
OX.FUN's WebSocket API provides real-time market data and order updates with ultra-low latency. With the WebSocket API, users can subscribe to real-time price updates and order book changes, enabling them to make faster and more informed trading decisions. The Websocket API also offers a real-time streaming interface for order placement and cancellation, enabling users to respond to market changes quickly and efficiently.
To get started register for a TEST account at stg.ox.fun or a LIVE account at ox.fun
API Key Management
An API key is required to make an authenticated API command. API keys (public and corresponding secret key) can be generated via the OX.FUN GUI within a clients account.
By default, API Keys are read-only and can only read basic account information, such as positions, orders, and trades. They cannot be used to trade such as placing, modifying or cancelling orders.
If you wish to execute orders with your API Key, clients must select the Can Trade
permission upon API key creation.
API keys are also only bound to a single sub-account, defined upon creation. This means that an API key will only ever interact and return account information for a single sub-account.
Rate Limit
OX.FUN's APIs allows our clients to access and control their accounts or view our market data using custom-written software. To protect the performance of the system, we impose certain limits:
Type | Limit |
---|---|
Rest API | 100 per second |
Rest API | 2500 per 5 mins |
Initialising Websocket Connection | 200 per minute |
Websocket API (Auth) | 50 per second |
Websocket API (No Auth) | 1 per second |
Websocket API
Subscription request format
{
"op": "<value>",
"tag": "<value>",
"args": ["<value1>", "<value2>",.....]
}
Subscription success response format
{
"event": "<opValue>",
"success": True,
"tag": "<value>",
"channel": "<argsValue>",
"timestamp": "1592491945368"
}
Subscription failure response format
{
"event": "<opValue>",
"success": False,
"tag": "<value>",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592498745368"
}
Command request format
{
"op": "<value>",
"tag": "<value>",
"data": {"<key1>": "<value1>",.....}
}
Command success response format
{
"event": "<opValue>",
"success": True
"tag": "<value>",
"timestamp": "1592498745368",
"data": {"<key1>": "<value1>",.....}
}
Command failure response format
{
"event": "<opValue>",
"success": False,
"message": "<errorMessage>",
"code": "<codeCode>",
"timestamp": "1592498745368",
"data": {"<key1>": "<value1>",.....}
}
TEST site
wss://stgapi.ox.fun/v2/websocket
LIVE site
wss://api.ox.fun/v2/websocket
OX.FUN's application programming interface (API) provides our clients programmatic access to control aspects of their accounts and to place orders on OX.FUN. The API is accessible via WebSocket connection to the URIs listed above. Commands, replies, and notifications all traverse the WebSocket in text frames with JSON-formatted payloads.
Websocket commands can be sent in either of the following two formats:
For subscription based requests
{"op": "<value>", "args": ["<value1>", "<value2>",.....]}
op
: can either be:
- subscribe
- unsubscribe
args
: the value(s) will be the instrument ID(s) or asset ID(s), for example:
- order:BTC-USD-SWAP-LIN
- depth:ETH-USDT-SWAP-LIN
- position:all
All other commands
{"op": "<command>", "data": {"<key1>":"<value1>",.....}}
op
: can be:
- login
- placeorder
- cancelorder
- modifyorder
data
: JSON string of the request object containing the required parameters
Further information regarding the error codes and corresponding error messages from a failed subscription or order command request can be found in a later section of this documentation Error Codes.
Authentication
Request format
{
"op": "login",
"tag": "<value>",
"data": {
"apiKey": "<string>",
"timestamp": "<string>",
"signature": "<string>"
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = 'API-KEY'
api_secret = 'API-SECRET'
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
msg_auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
async def subscribe():
async with websockets.connect('wss://stgapi.ox.fun/v2/websocket') as ws:
await ws.send(json.dumps(msg_auth))
while ws.open:
resp = await ws.recv()
print(resp)
asyncio.get_event_loop().run_until_complete(subscribe())
const CryptoJS = require("crypto-js");
const WebSocket = require('ws');
var apiKey = "API-KEY";
var secretKey = "API-SECRET";
const ts = '' + Date.now();
var sign = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(ts +'GET/auth/self/verify', secretKey));
var msg = JSON.stringify({
"op": "login",
"tag": 1,
"data": {
"apiKey": apiKey,
"timestamp": ts,
"signature": sign
}
});
var ws = new WebSocket('wss://stgapi.ox.fun/v2/websocket');
ws.onmessage = function (e) {
console.log('websocket message from server : ', e.data);
};
ws.onopen = function () {
ws.send(msg);
};
Success response format
{
"event": "login",
"success": true,
"tag": "<value>",
"timestamp": "1592491803978"
}
{
"event": "login",
"success": true,
"tag": "1",
"timestamp": "1592491808328"
}
{
"event": "login",
"success": true,
"tag": "1",
"timestamp": "1592491808329"
}
Failure response format
{
"event": "login",
"success": false,
"code": "<errorCode>",
"message": "<errorMessage>",
"tag": "1",
"timestamp": "1592492069732"
}
{
"event": "login",
"success": false,
"code": "<errorCode>",
"message": "<errorMessage>",
"tag": "1",
"timestamp": "1592492031972"
}
{
"event": "login",
"success": false,
"code": "<errorCode>",
"message": "<errorMessage>",
"tag": "1",
"timestamp": "1592492031982"
}
The Websocket API consists of public and private methods. The public methods do not require authentication. The private methods requires an authenticated websocket connection.
To autenticate a websocket connection a "login" message must be sent containing the clients signature.
The signature is constructed using a HMAC SHA256 operation to get a hash value, which in turn requires the clients API Secret as the key and a constructed message string as the value for the HMAC operation. This hash value is then encoded as a BASE-64 value which becomes the signature used for authentication.
API keys (public and corresponding secret key) can be generated via the GUI within the clients account.
The message string used in the HMAC SHA256 operation is constructed in the following way:
current millisecond timestamp + 'GET/auth/self/verify'
The signature can therefore be summarised by the following:
Base64(HmacSHA256(current_ms_timestamp + 'GET/auth/self/verify', API-Secret))
Request Parameters
Parameter | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | 'login' |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
apiKey | STRING | Yes | Clients public API key, visible in the GUI when created |
timestamp | STRING | Yes | Current millisecond timestamp |
signature | STRING | Yes | Base64(HmacSHA256(current_ms_timestamp + 'GET/auth/self/verify', API-Secret)) |
Session Keep Alive
To maintain an active WebSocket connection it is imperative to either be subscribed to a channel that pushes data at least once per minute (Depth) or periodically send a text “ping” (without the quotation mark) to the server within an minute and then the server will reply text “pong” (without quotation mark) to you.
Self Trade Prevention Modes
Self trade prevention (STP) helps traders avoid costly self-matching within an account and between subaccounts.
There are 4 STP modes available to traders NONE
, EXPIRE_MAKER
, EXPIRE_TAKER
, EXPIRE_BOTH
.
NONE
- has no protection meaning self-matching is possible.
EXPIRE_MAKER
- cancels the resting (would-be maker) order regardless of its STP mode, the aggressing order continues to match and/or is placed into the book.
EXPIRE_TAKER
- cancels the aggressing order to avoid self-matching, acts similar to an IOC that is cancelled in the event of a self-match.
EXPIRE_BOTH
- cancels both the agressing order and the resting (would-be maker) order regardless of the resting order's STP mode.
Note that the STP system uses the aggressing (would-be taker) order to decide which action to take, for example:
Placing an EXPIRE_TAKER
order which collides with a resting EXPIRE_MAKER
order causes the aggressing EXPIRE_TAKER
order to be cancelled, while the EXPIRE_MAKER
order will remain.
Order Commands
Place Limit Order
Request format
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "BTC-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 1.5,
"timeInForce": "GTC",
"price": 9431.48
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
place_order = \
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "BTC-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 1.5,
"timeInForce": "GTC",
"price": 9431.48
}
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(place_order))
elif 'event' in data and data['event'] == 'placeorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "placeorder",
"submitted": True,
"tag": "123",
"timestamp": "1592491945248",
"data": {
"clientOrderId": "1",
"marketCode": "BTC-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "1.5",
"timeInForce": "GTC",
"orderId": "1000000700008",
"price": "9431.48",
"limitPrice": "9431.48",
"source": 0
}
}
Failure response format
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491945248",
"data": {
"clientOrderId": "1",
"marketCode": "BTC-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "1.5",
"timeInForce": "GTC",
"price": "9431.48",
"limitPrice": "9431.48",
"source": 0
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderOpened, OrderMatched etc......).
Request Parameters
Parameter | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | placeorder |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
clientOrderId | ULONG | No | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | Yes | Market code e.g. BTC-USD-SWAP-LIN |
orderType | STRING | Yes | LIMIT |
price | FLOAT | Yes | Price |
quantity | FLOAT | Yes | Quantity (denominated by contractValCurrency) |
displayQuantity | FLOAT | NO | If given, the order becomes an iceberg order, and denotes the quantity to show on the book |
side | STRING | Yes | BUY or SELL |
timeInForce | ENUM | No |
|
timestamp | LONG | No | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | No | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
selfTradePreventionMode | STRING | No | NONE , EXPIRE_MAKER , EXPIRE_TAKER , EXPIRE_BOTH |
Place Market Order
Request format
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "SELL",
"orderType": "MARKET",
"quantity": 5
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
place_order = \
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "SELL",
"orderType": "MARKET",
"quantity": 5
}
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(place_order))
elif 'event' in data and data['event'] == 'placeorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "placeorder",
"submitted": True,
"tag": "123",
"timestamp": "1592491945248",
"data": {
"clientOrderId": "1",
"marketCode": "ETH-USD-SWAP-LIN",
"side": "SELL",
"orderType": "MARKET",
"quantity": "5",
"orderId": "1000000700008",
"limitPrice": "1700.00",
"source": 0
}
}
Failure response format
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491503359",
"data": {
"clientOrderId": "1",
"marketCode": "ETH-USD-SWAP-LIN",
"side": "SELL",
"orderType": "MARKET",
"quantity": "5",
"limitPrice": "1700.00",
"source": 0
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderOpened, OrderMatched etc......).
Request Parameters
Parameter | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | placeorder |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
clientOrderId | ULONG | No | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | Yes | Market code e.g. BTC-USD-SWAP-LIN |
orderType | STRING | Yes | MARKET |
quantity | FLOAT | Yes | Quantity (denominated by contractValCurrency), not required if an amount is provided |
amount | STRING | NO | An amount of USDT can be specified instead of a quantity. Only valid for spot market buy orders |
side | STRING | Yes | BUY or SELL |
timestamp | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
selfTradePreventionMode | STRING | No | NONE , EXPIRE_MAKER , EXPIRE_TAKER , EXPIRE_BOTH |
Place Stop Limit Order
Request format
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "STOP_LIMIT",
"quantity": 10,
"timeInForce": "MAKER_ONLY_REPRICE",
"stopPrice": 100,
"limitPrice": 120
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
place_order = \
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "STOP_LIMIT",
"quantity": 10,
"timeInForce": "MAKER_ONLY_REPRICE",
"stopPrice": 100,
"limitPrice": 120
}
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(place_order))
elif 'event' in data and data['event'] == 'placeorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "placeorder",
"submitted": True,
"tag": "123",
"timestamp": "1607639739098",
"data": {
"clientOrderId": "1",
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "STOP_LIMIT",
"quantity": "10",
"timeInForce": "MAKER_ONLY_REPRICE",
"price": "120",
"limitPrice": "120",
"stopPrice": "100",
"orderId": "1000000700008",
"source": 0
"triggerType": "MARK_PRICE"
}
}
Failure response format
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491503359",
"data": {
"clientOrderId": "1",
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "STOP_LIMIT",
"quantity": "10",
"timeInForce": "MAKER_ONLY_REPRICE",
"price": "120",
"stopPrice": "100",
"limitPrice": "120",
"source": 0,
"triggerType": "MARK_PRICE"
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderOpened, OrderMatched etc......).
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | placeorder |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
clientOrderId | ULONG | No | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | Yes | Market code e.g. ETH-USD-SWAP-LIN |
orderType | STRING | Yes | STOP_LIMIT for stop-limit orders |
quantity | FLOAT | Yes | Quantity (denominated by contractValCurrency) |
side | STRING | Yes | BUY or SELL |
limitPrice | FLOAT | Yes | Limit price for the stop-limit order. For BUY the limit price must be greater or equal to the stop price. For SELL the limit price must be less or equal to the stop price. |
stopPrice | FLOAT | Yes | Stop price for the stop-limit order. Triggered by the best bid price for the SELL stop-limit order. Triggered by the best ask price for the BUY stop-limit order. |
timeInForce | ENUM | No |
|
timestamp | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
selfTradePreventionMode | STRING | No | NONE , EXPIRE_MAKER , EXPIRE_TAKER , EXPIRE_BOTH |
Place Stop Market Order
Stop market orders are only available in Perp markets.
Request format
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1679907302693,
"recvWindow": 500,
"clientOrderId": 1679907301552,
"marketCode": "BTC-USD-SWAP-LIN",
"side": "SELL",
"orderType": "STOP_MARKET",
"quantity": 0.012,
"stopPrice": 22279.29
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
place_order = \
{
"op": "placeorder",
"tag": 123,
"data": {
"timestamp": 1679907302693,
"recvWindow": 500,
"clientOrderId": 1679907301552,
"marketCode": "BTC-USD-SWAP-LIN",
"side": "SELL",
"orderType": "STOP_MARKET",
"quantity": 0.001,
"stopPrice": 22279.29
}
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(place_order))
elif 'event' in data and data['event'] == 'placeorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "placeorder",
"submitted": True,
"tag": "123",
"timestamp": "1607639739098",
"data": {
"clientOrderId": "1679907301552",
"marketCode": "BTC-USD-SWAP-LIN",
"side": "SELL",
"orderType": "STOP_MARKET",
"quantity": "0.012",
"timeInForce": "IOC",
"price": "25000",
"limitPrice": "25000",
"stopPrice": "22279.29",
"orderId": "1000001680990",
"triggerType": "MARK_PRICE",
"source": 0
}
}
Failure response format
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1679907302693",
"data": {
"clientOrderId": "1679907301552",
"marketCode": "BTC-USD-SWAP-LIN",
"side": "SELL",
"orderType": "STOP_MARKET",
"quantity": "0.012",
"timeInForce": "IOC",
"price": "25000",
"limitPrice": "25000",
"stopPrice": "22279.29"
"triggerType": "MARK_PRICE",
"source": 0
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderOpened, OrderMatched etc......).
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | placeorder |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
clientOrderId | ULONG | No | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | Yes | Market code e.g. BTC-USD-SWAP-LIN |
orderType | STRING | Yes | STOP_MARKET |
quantity | FLOAT | Yes | Quantity (denominated by contractValCurrency) |
side | STRING | Yes | BUY or SELL |
stopPrice | FLOAT | Yes | Stop price for the stop-market order. Triggered by the best bid price for the SELL stop-market order. Triggered by the best ask price for the BUY stop-market order. |
timestamp | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
selfTradePreventionMode | STRING | No | NONE , EXPIRE_MAKER , EXPIRE_TAKER , EXPIRE_BOTH |
Place Batch Orders
Request format
{
"op": "placeorders",
"tag": 123,
"dataArray": [{
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 10,
"timeInForce": "MAKER_ONLY",
"price": 100
},
{
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 2,
"marketCode": "BTC-USDT",
"side": "SELL",
"orderType": "MARKET",
"quantity": 0.2
}]
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
place_batch_order =\
{
"op": "placeorders",
"tag": 123,
"dataArray": [{
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 1,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": 10,
"timeInForce": "MAKER_ONLY",
"price": 100
},
{
"timestamp": 1638237934061,
"recvWindow": 500,
"clientOrderId": 2,
"marketCode": "BTC-USDT",
"side": "SELL",
"orderType": "MARKET",
"quantity": 0.2
}]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(place_batch_order))
elif 'event' in data and data['event'] == 'placeorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "placeorder",
"submitted": True,
"tag": "123",
"timestamp": "1607639739098",
"data": {
"clientOrderId": "1",
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"timeInForce": "MAKER_ONLY",
"price": "100",
"limitPrice": "100",
"orderId": "1000003700008",
"source": 0
}
}
AND
{
"event": "placeorder",
"submitted": True,
"tag": "123",
"timestamp": "1607639739136",
"data": {
"clientOrderId": "2",
"marketCode": "BTC-USDT",
"side": "SELL",
"orderType": "MARKET",
"quantity": "0.2",
"orderId": "1000004700009",
"limitPrice": "20000",
"source": 0
}
}
Failure response format
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491503359",
"data": {
"clientOrderId": "1",
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderType": "LIMIT",
"quantity": "10",
"timeInForce": "MAKER_ONLY",
"price": "100",
"limitPrice": "100",
"source": 0
}
}
AND
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491503457",
"data": {
"clientOrderId": "2",
"marketCode": "BTC-USDT",
"side": "SELL",
"orderType": "MARKET",
"quantity": "0.2",
"limitPrice": "20000",
"source": 0
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderOpened, OrderMatched etc......).
All existing single order placement methods are supported:-
- LIMIT
- MARKET
- STOP LIMIT
- STOP MARKET
The websocket reply from the exchange will repond to each order in the batch separately, one order at a time, and has the same message format as the reponse for the single order placement method.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | placeorders |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
dataArray | LIST of dictionaries | Yes | A list of orders with each order in JSON format, the same format/parameters as the request for placing a single order. The max number of orders is still limited by the message length validation so by default up to 20 orders can be placed in a batch, assuming that each order JSON has 200 characters. |
timestamp | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
Cancel Order
Request format
{
"op": "cancelorder",
"tag": 456,
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": 12
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
cancel_order = \
{
"op": "cancelorder",
"tag": 456,
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": 12
}
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(cancel_order))
elif 'event' in data and data['event'] == 'cancelorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "cancelorder",
"submitted": True,
"tag": "456",
"timestamp": "1592491173964",
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"clientOrderId": "1",
"orderId": "12"
}
}
Failure response format
{
"event": "cancelorder",
"submitted": False,
"tag": "456",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491173964",
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": "12"
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderClosed etc......).
This command can also be actioned via the trading GUI using the Cancel button next to an open order in the Open Orders blotter for both Spot and Derivative markets.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | cancelorder |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
marketCode | STRING | Yes | Market code e.g. BTC-USD-SWAP-LIN |
orderId | INTEGER | Yes | Unique order ID from the exchange |
Cancel Batch Orders
Request format
{
"op": "cancelorders",
"tag": 456,
"dataArray": [{
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": 12
},
{
"marketCode": "BCH-USDT",
"orderId": 34
}]
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
cancel_batch_order = \
{
"op": "cancelorders",
"tag": 456,
"dataArray": [{
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": 12
},
{
"marketCode": "BCH-USDT",
"orderId": 34
}]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(cancel_batch_order))
elif 'event' in data and data['event'] == 'cancelorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "cancelorder",
"submitted": True,
"tag": "456",
"timestamp": "1592491173964",
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"clientOrderId": "1",
"orderId": "12"
}
}
AND
{
"event": "cancelorder",
"submitted": True,
"tag": "456",
"timestamp": "1592491173978",
"data": {
"marketCode": "BCH-USDT",
"orderId": "34"
}
}
Failure response format
{
"event": "cancelorder",
"submitted": False,
"tag": "456",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491173964",
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": "12"
}
}
AND
{
"event": "cancelorder",
"submitted": False,
"tag": "456",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491173989",
"data": {
"marketCode": "BCH-USDT",
"orderId": "12"
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderClosed etc......).
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | cancelorders |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
dataArray | LIST of dictionaries | Yes | A list of orders with each order in JSON format, the same format/parameters as the request for cancelling a single order. The max number of orders is still limited by the message length validation so by default up to 20 orders can be placed in a batch, assuming that each order JSON has 200 characters. |
Modify Order
Request format
{
"op": "modifyorder",
"tag": 1,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": 888,
"side": "BUY",
"price": 9800,
"quantity": 2
}
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
modify_order = \
{
"op": "modifyorder",
"tag": 1,
"data": {
"timestamp": 1638237934061,
"recvWindow": 500,
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": 888,
"side": "BUY",
"price": 9800,
"quantity": 2
}
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(modify_order))
elif 'event' in data and data['event'] == 'modifyorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "modifyorder",
"submitted": True,
"tag": "1",
"timestamp": "1592491032427",
"data":{
"clientOrderId": "1",
"orderId": "888",
"side": "BUY",
"quantity": "2"
"price": "9800",
"limitPrice": "9800",
"orderType": "LIMIT",
"marketCode": "BTC-USD-SWAP-LIN"
}
}
Failure response format
{
"event": "modifyorder",
"submitted": False,
"tag": "1",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491032427",
"data": {
"orderId": "888",
"side": "BUY",
"quantity": "2",
"price": "9800",
"limitPrice": "9800",
"marketCode": "BTC-USD-SWAP-LIN"
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderModified etc......).
Currently only LIMIT orders are supported by the modify order command.
- The price and/or quantity of an order can be modified.
- Reducing the quantity will leave the modified orders position in the order queue unchanged.
- Increasing the quantity will always move the modified order to the back of the order queue.
- Modifying the price will always move the modified order to the back of the order queue.
Modified orders retain their original orderId.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | modifyorder |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
data | DICTIONARY object | Yes | |
marketCode | STRING | Yes | Market code e.g. BTC-USD-SWAP-LIN |
orderId | INTEGER | Yes | Unique order ID from the exchange |
side | STRING | No | BUY or SELL |
price | FLOAT | No | Price for limit orders |
quantity | FLOAT | No | Quantity (denominated by contractValCurrency ) |
timestamp | LONG | No | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | No | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
Modify Batch Orders
Request format
{
"op": "modifyorders",
"tag": 123,
"dataArray": [{
"timestamp": 1638237934061,
"recvWindow": 500,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderID": 304304315061932310,
"price": 101,
},
{
"timestamp": 1638237934061,
"recvWindow": 500,
"marketCode": "BTC-USDT",
"orderID": 304304315061864646,
"price": 10001,
"quantity": 0.21
}]
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
modify_batch_order = \
{
"op": "modifyorders",
"tag": 123,
"dataArray": [{
"timestamp": 1638237934061,
"recvWindow": 500,
"marketCode": "ETH-USD-SWAP-LIN",
"side": "BUY",
"orderID": 304304315061932310,
"price": 101,
},
{
"timestamp": 1638237934061,
"recvWindow": 500,
"marketCode": "BTC-USDT",
"orderID": 304304315061864646,
"price": 10001,
"quantity": 0.21
}]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(modify_batch_order))
elif 'event' in data and data['event'] == 'modifyorder':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "modifyorder",
"submitted": True,
"tag": "123",
"timestamp": "1607639739098",
"data": {
"clientOrderId": "100"
"orderId": "304304315061932310",
"side": "BUY",
"quantity": "5",
"price": "101",
"limitPrice": "101",
"orderType": "LIMIT",
"marketCode": "ETH-USD-SWAP-LIN"
}
}
AND
{
"event": "modifyorder",
"submitted": True,
"tag": "123",
"timestamp": "1607639739136",
"data": {
"clientOrderId": "200"
"orderId": "304304315061864646",
"side": "SELL",
"quantity": "0.21",
"price": "10001",
"limitPrice": "10001",
"orderType": "LIMIT",
"marketCode": "BTC-USDT"
}
}
Failure response format
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491503359",
"data": {
"orderID": "304304315061932310",
"side": "BUY",
"price": "101",
"limitPrice": "101",
"marketCode": "ETH-USD-SWAP-LIN"
}
}
AND
{
"event": "placeorder",
"submitted": False,
"tag": "123",
"message": "<errorMessage>",
"code": "<errorCode>",
"timestamp": "1592491503457",
"data": {
"orderID": "304304315061864646",
"quantity": "0.21",
"price": "10001",
"limitPrice": "10001",
"marketCode": "BTC-USDT"
}
}
Requires an authenticated websocket connection. Please also subscribe to the User Order Channel to receive push notifications for all message updates in relation to an account or sub-account (e.g. OrderOpened, OrderMatched etc......).
The websocket responses from the exchange will come separately for each order in the batch, one order at a time, and the message has the same format as the single modifyorder
method.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | modifyorders |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
dataArray | LIST of dictionaries | Yes | A list of orders with each order in JSON format, the same format/parameters as the request for modifying a single order. The max number of orders is still limited by the message length validation so by default up to 20 orders can be modified in a batch, assuming that each order JSON has 200 characters. |
timestamp | LONG | No | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
recvWindow | LONG | No | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected. |
Subscriptions - Private
All subscriptions to private account channels requires an authenticated websocket connection.
Multiple subscriptions to different channels both public and private can be made within a single subscription command:
{"op": "subscribe", "args": ["<value1>", "<value2>",.....]}
Balance Channel
Request format
{
"op": "subscribe",
"args": ["balance:all"],
"tag": 101
}
OR
{
"op": "subscribe",
"args": ["balance:USDT", "balance:OX", ........],
"tag": 101
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
balance = \
{
"op": "subscribe",
"args": ["balance:all"],
"tag": 101
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(balance))
elif 'event' in data and data['event'] == 'balance':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"success": True,
"tag": "101",
"event": "subscribe",
"channel": "<args value>",
"timestamp": "1607985371401"
}
Balance channel format
{
"table": "balance",
"accountId": "<Your account ID>",
"timestamp": "1599693365059",
"tradeType": "STANDARD",
"data":[
{
"total": "10000",
"reserved": "1000",
"instrumentId": "USDT",
"available": "9000",
"locked": "0"
"quantityLastUpdated": "1599694369431",
},
{
"total": "100000",
"reserved": "0",
"instrumentId": "OX",
"available": "100000",
"locked": "0"
"quantityLastUpdated": "1599694343242",
}
]
}
Channel Update Frequency : On update
The websocket will reply with the shown success response format for subscribed assets with changed balances.
If a subscription has been made to balance:all, the data array in the message from this balance channel will contain a JSON list, otherwise the data array will contain a single JSON corresponding to one spot asset per asset channel subscription.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
args | LIST | Yes | balance:all or a list of individual assets balance:<assetId> |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | balance |
accountId | STRING | Account identifier |
timestamp | STRING | Current millisecond timestamp |
tradeType | STRING | LINEAR , STANDARD , PORTFOLIO |
data | LIST of dictionaries | |
total | STRING | Total spot asset balance |
reserved | STRING | Reserved asset balance for working spot and repo orders |
instrumentId | STRING | Base asset ID e.g. BTC |
available | STRING | Remaining available asset balance (total - reserved) |
locked | STRING | Temporarily locked asset balance |
quantityLastUpdated | STRING | Millisecond timestamp |
Position Channel
Request format
{
"op": "subscribe",
"args": ["position:all"],
"tag": 102
}
OR
{
"op": "subscribe",
"args": ["position:BTC-USD-SWAP-LIN", "position:BCH-USD-SWAP-LIN", ........],
"tag": 102
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
position = \
{
"op": "subscribe",
"args": ["position:all"],
"tag": 102
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(position))
elif 'event' in data and data['event'] == 'position':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"success": True,
"tag": "102",
"event": "subscribe",
"channel": "<args value>",
"timestamp": "1607985371401"
}
Position channel format
{
"table": "position",
"accountId": "<Your account ID>",
"timestamp": "1607985371481",
"data":[ {
"instrumentId": "ETH-USD-SWAP-LIN",
"quantity" : "0.1",
"lastUpdated": "1616053755423",
"contractValCurrency": "ETH",
"entryPrice": "1900.0",
"positionPnl": "-566.80",
"estLiquidationPrice": "0",
"margin": "0",
"leverage": "0"
},
{
"instrumentId": "ETH-USD-SWAP-LIN",
"quantity" : "50.54",
"lastUpdated": "1617099855968",
"contractValCurrency": "ETH",
"entryPrice": "2000.0",
"positionPnl": "1220.9494164000000",
"estLiquidationPrice": "1317.2",
"margin": "0",
"leverage": "0"
},
...
]
}
Channel Update Frequency : real-time, on position update
The websocket will reply with the shown success response format for each position channel which has been successfully subscribed to.
If a subscription has been made to position:all, the data array in the message from this position channel will contain a JSON list. Each JSON will contain position details for a different instrument. Otherwise the data array will contain a single JSON corresponding to one instrument.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
args | LIST | Yes | position:all or a list of individual instruments position:<instrumentId> |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | position |
accountId | STRING | Account identifier |
timestamp | STRING | Current millisecond timestamp |
data | LIST of dictionaries | |
instrumentId | STRING | e.g. ETH-USD-SWAP-LIN |
quantity | STRING | Position size (+/-) |
lastUpdated | STRING | Millisecond timestamp |
contractValCurrency | STRING | Base asset ID e.g. ETH |
entryPrice | STRING | Average entry price of total position (Cost / Size) |
positionPnl | STRING | Postion profit and lost in OX |
estLiquidationPrice | STRING | Estimated liquidation price, return 0 if it is negative(<0) |
margin | STRING | Currently always reports 0 |
leverage | STRING | Currently always reports 0 |
Order Channel
Request format
{
"op": "subscribe",
"args": ["order:all"],
"tag": 102
}
OR
{
"op": "subscribe",
"args": ["order:OX-USDT", "order:ETH-USD-SWAP-LIN", .....],
"tag": 102
}
import websockets
import asyncio
import time
import hmac
import base64
import hashlib
import json
api_key = ''
api_secret = ''
ts = str(int(time.time() * 1000))
sig_payload = (ts+'GET/auth/self/verify').encode('utf-8')
signature = base64.b64encode(hmac.new(api_secret.encode('utf-8'), sig_payload, hashlib.sha256).digest()).decode('utf-8')
auth = \
{
"op": "login",
"tag": 1,
"data": {
"apiKey": api_key,
"timestamp": ts,
"signature": signature
}
}
order = \
{
"op": "subscribe",
"args": ["order:all"],
"tag": 102
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(auth))
elif 'event' in data and data['event'] == 'login':
if data['success'] == True:
await ws.send(json.dumps(order))
elif 'event' in data and data['event'] == 'order':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"success": True,
"tag": "102",
"event": "subscribe",
"channel": "<args value>",
"timestamp": "1607985371401"
}
Channel Update Frequency : real-time, on order update
Every order update for a particular sub-account will be relayed to all of its active connections. This implies that for each sub-account, the order responses for all connected markets will be sent to all active subscriptions, even if a specific connection is only subscribed to a single market.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
args | LIST | Yes | order:all or a list of individual markets order:<marketCode> |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
OrderOpened
OrderOpened message format - LIMIT order
{
"table": "order",
"data": [
{
"notice": "OrderOpened",
"accountId": "<Your account ID>",
"clientOrderId": "16",
"orderId" : "123",
"price": "9600",
"limitPrice": "9600",
"quantity": "2",
"remainQuantity": "2",
"amount": "0.0",
"side": "BUY",
"status": "OPEN",
"marketCode": "BTC-USD-SWAP-LIN",
"timeInForce": "MAKER_ONLY",
"timestamp": "1594943491077",
"orderType": "LIMIT",
"isTriggered": "False",
"displayQuantity": "2"
}
]
}
OrderOpened message format - STOP MARKET order
{
"table": "order",
"data": [
{
"accountId": "<Your account ID>",
"clientOrderId": "1",
"orderId": "1000021706785",
"price": "12000.0",
"quantity": "0.001",
"amount": "0.0",
"side": "BUY",
"status": "OPEN",
"marketCode": "BTC-USD-SWAP-LIN",
"timeInForce": "IOC",
"timestamp": "1680042503604",
"remainQuantity": "0.001",
"stopPrice": "10000.0",
"limitPrice": "12000.0",
"notice": "OrderOpened",
"orderType": "STOP_MARKET",
"isTriggered": "false",
"triggerType": "MARK_PRICE",
"displayQuantity": "0.001"
}
]
}
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | order |
data | LIST of dictionary | |
notice | STRING | OrderOpened |
accountId | STRING | Account identifier |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
orderId | STRING | Unique order ID from the exchange |
price | STRING | Limit price submitted (only applicable for LIMIT order types) |
quantity | STRING | Quantity submitted |
amount | STRING | "0.0" if not provided in the request |
side | STRING | BUY or SELL |
status | STRING | Order status |
marketCode | STRING | Market code e.g. OX-USDT |
timeInForce | STRING | Client submitted time in force, GTC by default |
timestamp | STRING | Current millisecond timestamp |
remainQuantity | STRING | Working quantity |
orderType | STRING | LIMIT , STOP_LIMIT , or STOP_MARKET |
stopPrice | STRING | Stop price submitted (only applicable for STOP order types) |
limitPrice | STRING | Limit price submitted |
isTriggered | STRING | False or True |
triggerType | STRING | Stops are triggered on MARK_PRICE |
displayQuantity | STRING | Quantity displayed in the book, primarily used for iceberg orders, otherwise echos the quantity field |
OrderClosed
OrderClosed message format - LIMIT order
{
"table": "order",
"data": [
{
"accountId": "<Your account ID>",
"clientOrderId": "1",
"orderId": "1000021764611",
"price": "20000.0",
"quantity": "0.001",
"amount": "0.0",
"side": "BUY",
"status": "CANCELED_BY_USER",
"marketCode": "BTC-USD-SWAP-LIN",
"timeInForce": "GTC",
"timestamp": "1680043402806",
"remainQuantity": "0.001",
"limitPrice": "20000.0",
"notice": "OrderClosed",
"orderType": "LIMIT",
"isTriggered": "false",
"displayQuantity": "0.001"
}
]
}
OrderClosed message format - STOP LIMIT order
{
"table": "order",
"data": [
{
"accountId": "<Your account ID>",
"clientOrderId": "1",
"orderId": "1000021852415",
"price": "21000.0",
"quantity": "0.001",
"amount": "0.0",
"side": "BUY",
"status": "CANCELED_BY_USER",
"marketCode": "BTC-USD-SWAP-LIN",
"timeInForce": "GTC",
"timestamp": "1680044038047",
"remainQuantity": "0.001",
"stopPrice": "20000.0",
"limitPrice": "21000.0",
"notice": "OrderClosed",
"orderType": "LIMIT",
"isTriggered": "true",
"triggerType": "MARK_PRICE",
"displayQuantity": "0.001"
}
]
}
There are multiple scenarios in which an order is closed as described by the status field in the OrderClosed message. In summary orders can be closed by:-
CANCELED_BY_USER
- the client themselves initiating this action or the liquidation engine on the clients behalfCANCELED_BY_MAKER_ONLY
- if a maker-only order is priced such that it would actually be an agressing taker trade, the order is automatically canceled to prevent this order from matching as a takerCANCELED_BY_FOK
- since fill-or-kill orders requires all of the order quantity to immediately take and match at the submitted limit price or better, if no such match is possible then the whole order quantity is canceledCANCELED_ALL_BY_IOC
- since immediate-or-cancel orders also requires an immediate match at the specified limit price or better, if no such match price is possible for any of the submitted order quantity then the whole order quantity is canceledCANCELED_PARTIAL_BY_IOC
- since immediate-or-cancel orders only requires some of the submitted order quantity to immediately take and match at the specified limit price or better, if a match is possible for only a partial quantity then only the remaining order quantity which didn't immediately match is canceledCANCELED_BY_SELF_TRADE_PROTECTION
- orders canceled by your selected selfTradePreventionMode settings
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | order |
data | LIST of dictionary | |
notice | STRING | OrderClosed |
accountId | STRING | Account identifier |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
orderId | STRING | Unique order ID from the exchange |
price | STRING | Limit price of closed order (only applicable for LIMIT order types) |
quantity | STRING | Original order quantity of closed order |
amount | STRING | "0.0" if not provided in the request |
side | STRING | BUY or SELL |
status | STRING |
|
marketCode | STRING | Market code e.g. BTC-USD-SWAP-LIN |
timeInForce | STRING | Time in force of closed order |
timestamp | STRING | Current millisecond timestamp |
remainQuantity | STRING | Historical remaining order quantity of closed order |
stopPrice | STRING | Stop price of closed stop order (only applicable for STOP order types) |
limitPrice | STRING | Limit price |
ordertype | STRING | LIMIT or STOP_LIMIT |
isTriggered | STRING | False or True |
triggerType | STRING | Stops are triggered on MARK_PRICE |
displayQuantity | STRING | Quantity displayed in the book, primarily used for iceberg orders, otherwise echos the quantity field |
OrderClosed Failure
OrderClosed failure message format
{
"event": "CANCEL",
"submitted": False,
"message": "Order request was rejected : REJECT_CANCEL_ORDER_ID_NOT_FOUND",
"code": "100004",
"timestamp": "0",
"data": {
"clientOrderId": 3,
"orderId": 3330802124194931673,
"displayQuantity": 0.0,
"lastMatchPrice": 0.0,
"lastMatchQuantity": 0.0,
"lastMatchedOrderId": 0,
"lastMatchedOrderId2": 0,
"matchedId": 0,
"matchedType": "MAKER",
"remainQuantity": 0.0,
"side": "BUY",
"status": "REJECT_CANCEL_ORDER_ID_NOT_FOUND",
"timeCondition": "GTC",
"marketCode": "BTC-USD-SWAP-LIN",
"timestampEpochMs": 1615377638518,
"orderType": "LIMIT",
"price": 0.0,
"quantity": 0.0,
"isTriggered": False
}
}
This order message can occur if:-
- an order has already been matched by the time the cancel order command is recieved and processed by the exchange which means this order is no longer active and therefore cannot be closed.
multiple cancel order commands for the same orderID have been sent in quick sucession to the exchange by mistake and only the first cancel order command is accepted and processed by the exchange which means this order is no longer active and therefore cannot be closed again.
For more error messages and code, you can see them here Error Codes
Channel Update Fields
Fields | Type | Description |
---|---|---|
event | STRING | |
submitted | BOOL | |
message | STRING | |
code | STRING | |
timestamp | STRING | |
data | LIST of dictionary | |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
orderId | STRING | Unique order ID from the exchange |
displayQuantity | DECIMAL | |
lastMatchPrice | DECIMAL | |
lastMatchQuantity | DECIMAL | |
lastMatchedOrderId | DECIMAL | |
lastMatchedOrderId2 | DECIMAL | |
matchedId | DECIMAL | |
matchedType | STRING | |
remainQuantity | DECIMAL | Historical remaining quantity of the closed order |
side | STRING | |
status | STRING | |
timeCondition | STRING | |
marketCode | STRING | |
timestampEpochMs | LONG | |
orderType | STRING | |
price | DECIMAL | LIMIT or STOP_LIMIT |
quantity | DECIMAL | |
isTriggered | BOOL | False or True |
OrderModified
OrderModified message format
{
"table": "order",
"data": [
{
"accountId": "<Your account ID>",
"clientOrderId": "1",
"orderId": "1000021878849",
"price": "30.0",
"quantity": "0.001",
"amount": "0.0",
"side": "BUY",
"status": "OPEN",
"marketCode": "BTC-USD-SWAP-LIN",
"timeInForce": "GTC",
"timestamp": "1680044356374",
"remainQuantity": "0.001",
"limitPrice": "30.0",
"notice": "OrderModified",
"orderType": "LIMIT",
"isTriggered": "false",
"displayQuantity": "0.001"
}
]
}
As described in a previous section Order Commands - Modify Order, the Modify Order command can potentially affect the queue position of the order depending on which parameter of the original order has been modified. Orders retain their orderIds after modification.
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | order |
data | LIST of dictionary | |
notice | STRING | OrderModified |
accountId | STRING | Account identifier |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
orderId | STRING | Unique order ID from the exchange |
price | STRING | Limit price of modified order (only applicable for LIMIT order types) |
quantity | STRING | Quantity of modified order |
remainQuantity | STRING | Working quantity |
amount | STRING | "0.0" if not provided in the request |
side | STRING | BUY or SELL |
status | STRING | Order status |
marketCode | STRING | Market code e.g. BTC-USD-SWAP-LIN |
timeInForce | STRING | Client submitted time in force, GTC by default |
timestamp | STRING | Current millisecond timestamp |
orderType | STRING | LIMIT or STOP_LIMIT |
stopPrice | STRING | Stop price of modified order (only applicable for STOP order types) |
limitPrice | STRING | Limit price of modified order |
isTriggered | STRING | False or True |
triggerType | STRING | Stops are triggered on MARK_PRICE |
displayQuantity | STRING | Quantity displayed in the book, primarily used for iceberg orders, otherwise echos the quantity field |
OrderModified Failure
OrderModified failure message format
{
"event": "AMEND",
"submitted": False,
"message": "Order request was rejected : REJECT_AMEND_ORDER_ID_NOT_FOUND",
"code": "100004",
"timestamp": "0",
"data": {
"clientOrderId": 3,
"orderId": 3330802124194931673,
"displayQuantity": 917.5,
"lastMatchPrice": 0.0,
"lastMatchQuantity": 0.0,
"lastMatchedOrderId": 0,
"lastMatchedOrderId2": 0,
"matchedId": 0,
"matchedType": "MAKER",
"remainQuantity": 0.0,
"side": "BUY",
"status": "REJECT_AMEND_ORDER_ID_NOT_FOUND",
"timeCondition": "GTC",
"marketCode": "BTC-USD-SWAP-LIN",
"timestampEpochMs": 1615377638518,
"orderType": "LIMIT",
"price": 22,
"quantity": 917.5,
"isTriggered": False
}
}
This order message can occur if an order has already been matched by the time the modify order command is recieved and processed by the exchange which means this order is no longer active and therefore cannot be modified. For more error messages and code, you can see them here Error Codes
Channel Update Fields
Fields | Type | Description |
---|---|---|
event | STRING | |
submitted | BOOL | |
message | STRING | |
code | STRING | |
timestamp | STRING | |
data | LIST of dictionary | |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
orderId | STRING | Unique order ID from the exchange |
displayQuantity | DECIMAL | |
lastMatchPrice | DECIMAL | |
lastMatchQuantity | DECIMAL | |
lastMatchedOrderId | DECIMAL | |
lastMatchedOrderId2 | DECIMAL | |
matchedId | DECIMAL | |
matchedType | STRING | |
remainQuantity | DECIMAL | |
side | STRING | |
status | STRING | |
timeCondition | STRING | |
marketCode | STRING | |
timestampEpochMs | LONG | |
orderType | STRING | |
price | DECIMAL | |
quantity | DECIMAL | |
isTriggered | BOOL |
OrderMatched
OrderMatched message format
{
"table": "order",
"data": [
{
"accountId": "<Your account ID>",
"clientOrderId": "1680044888401",
"orderId": "1000021937814",
"price": "27282.73",
"quantity": "0.004",
"amount": "0.0",
"side": "BUY",
"status": "FILLED",
"marketCode": "BTC-USD-SWAP-LIN",
"timeInForce": "GTC",
"timestamp": "1680044888565",
"matchId": "300016799886154670",
"matchPrice": "27279.39",
"matchQuantity": "0.001",
"orderMatchType": "TAKER",
"remainQuantity": "0.0",
"limitPrice": "27282.73",
"notice": "OrderMatched",
"orderType": "LIMIT",
"fees": "0.019095573",
"feeInstrumentId": "USDT",
"isTriggered": "false",
"displayQuantity": "0.004"
}
]
}
Channel Update Fields
Fields | Type | Required |
---|---|---|
table | STRING | order |
data | LIST of dictionary | |
notice | STRING | OrderMatched |
accountId | STRING | Account identifier |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
orderId | STRING | Unique order ID from the exchange |
price | STRING | Limit price submitted (only applicable for LIMIT order types) |
stopPrice | STRING | Stop price submitted (only applicable for STOP order types) |
limitPrice | STRING | Limit price submitted |
quantity | STRING | Order quantity submitted |
amount | STRING | "0.0" if not provided in the request |
side | STRING | BUY or SELL |
status | STRING | FILLED or PARTIAL_FILL |
marketCode | STRING | Market code i.e. BTC-USD-SWAP-LIN |
timeInForce | STRING | Client submitted time in force (only applicable for LIMIT and STOP LIMIT order types) |
timestamp | STRING | Millisecond timestamp of order match |
matchID | STRING | Exchange match ID |
matchPrice | STRING | Match price of order from this match ID |
matchQuantity | STRING | Match quantity of order from this match ID |
orderMatchType | STRING | MAKER or TAKER |
remainQuantity | STRING | Remaining order quantity |
orderType | STRING |
STOP_LIMIT and STOP_MARKET orders are converted to LIMIT and MARKET orders respectively |
fees | STRING | Amount of fees paid from this match ID |
feeInstrumentId | STRING | Instrument ID of fees paid from this match ID |
isTriggered | STRING | False (or True for STOP order types) |
triggerType | STRING | Stops are triggered on MARK_PRICE |
displayQuantity | STRING | Quantity displayed in the book, primarily used for iceberg orders, otherwise echos the quantity field |
Subscriptions - Public
All subscriptions to public channels do not require an authenticated websocket connection.
Multiple subscriptions to different channels both public and private can be made within a single subscription command:
{"op": "subscribe", "args": ["<value1>", "<value2>",.....]}
Fixed Size Order Book
Request format
{
"op": "subscribe",
"tag": 103,
"args": ["depthL10:BTC-USD-SWAP-LIN"]
}
import websockets
import asyncio
import json
orderbook_depth = \
{
"op": "subscribe",
"tag": 103,
"args": ["depthL10:BTC-USD-SWAP-LIN"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(orderbook_depth))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"success": true,
"tag": "103",
"event": "subscribe",
"channel": "depthL10:BTC-USD-SWAP-LIN",
"timestamp": "1665454814275"
}
Order Book depth channel format
{
"table": "depthL10",
"data": {
"seqNum": 2166539633781384,
"asks": [
[
19024.0,
1.0
],
[
19205.0,
4.207
],
[
19395.0,
8.414
]
],
"bids": [
[
18986.0,
1.0
],
[
18824.0,
4.207
],
[
18634.0,
8.414
]
],
"marketCode": "BTC-USD-SWAP-LIN",
"timestamp": "1665454814328"
},
"action": "partial"
}
Channel Update Frequency: 100ms
This order book depth channel sends a snapshot of the entire order book every 50ms.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | LIST | Yes | List of individual markets <depth>:<marketCode> e.g: [depthL10:BTC-USD-SWAP-LIN] , valid book sizes are: depthL5 depthL10 depthL25 |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | depthL10 |
data | DICTIONARY | |
seqNum | INTEGER | Sequence number of the order book snapshot |
asks | LIST of floats | Sell side depth;
|
bids | LIST of floats | Buy side depth;
|
marketCode | STRING | marketCode |
timestamp | STRING | Millisecond timestamp |
action | STRING |
Full Order Book
Request format
{
"op": "subscribe",
"tag": 103,
"args": ["depth:BTC-USD-SWAP-LIN"]
}
import websockets
import asyncio
import json
orderbook_depth = \
{
"op": "subscribe",
"tag": 103,
"args": ["depth:BTC-USD-SWAP-LIN"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(orderbook_depth))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"success": true,
"tag": "103",
"event": "subscribe",
"channel": "depth:BTC-USD-SWAP-LIN",
"timestamp": "1665454814275"
}
Order book depth channel format
{
"table": "depth",
"data": {
"seqNum": 2166539633781384,
"asks": [
[
19024.0,
1.0
],
[
19205.0,
4.207
],
[
19395.0,
8.414
]
],
"bids": [
[
18986.0,
1.0
],
[
18824.0,
4.207
],
[
18634.0,
8.414
]
],
"checksum": 3475315026,
"marketCode": "BTC-USD-SWAP-LIN",
"timestamp": 1665454814328
},
"action": "partial"
}
Channel Update Frequency: 100ms
This order book depth channel sends a snapshot of the entire order book every 100ms.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | LIST | Yes | List of individual markets <depth>:<marketCode> e.g: [depth:BTC-USD-SWAP-LIN] |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | depth |
data | DICTIONARY | |
seqNum | INTEGER | Sequence number of the order book snapshot |
asks | LIST of floats | Sell side depth;
|
bids | LIST of floats | Buy side depth;
|
checksum | INTEGER | checksum |
marketCode | STRING | marketCode |
timestamp | INTEGER | Millisecond timestamp |
action | STRING |
Incremental Order Book
Request format
{
"op": "subscribe",
"tag": "test1",
"args": [
"depthUpdate:BTC-USD-SWAP-LIN"
]
}
Success response format
{
"success": true,
"tag": "test1",
"event": "subscribe",
"channel": "depthUpdate:BTC-USD-SWAP-LIN",
"timestamp": "1665456142779"
}
** depth update channel format**
{
"table": "depthUpdate-diff",
"data": {
"seqNum": 2166539633794590,
"asks": [],
"bids": [],
"checksum": 364462986,
"marketCode": "BTC-USD-SWAP-LIN",
"timestamp": "1665456142843"
},
"action": "increment"
}
{
"table": "depthUpdate",
"data": {
"seqNum": 2166539633794591,
"asks": [
[
19042.0,
1.0
]
],
"bids": [
[
19003.0,
1.0
]
],
"checksum": 2688268653,
"marketCode": "BTC-USD-SWAP-LIN",
"timestamp": "1665456142843"
},
"action": "partial"
}
Channel Update Frequency: 100ms
Incremental order book stream
Usage Instructions: 1. Connect to websocket wss://api.ox.fun/v2/websocket 2. Subscribe to depthUpdate and you will get a message reply saying your subscription is successful 3. Afterwards you will get a snapshot of the book with table:depthUpdate 4. If you receive a reply with table:depthUpdate-diff first, keep it locally and wait for snapshot reply in step 3 5. The first incremental depthUpdate-diff message should have the same seqNum as the depthUpdate snapshot 6. After that, each new incremental update should have an incrementally larger seqNum to the previous update 7. The data in each event represents the absolute quantity for a price level. 8. If the quantity is 0, remove the price level.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply |
args | LIST | Yes | List of individual markets <depthUpdate>:<marketCode> e.g: ["depthUpdate:BTC-USD-SWAP-LIN"] |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | depthUpdate-diff depthUpdate |
data | DICTIONARY | |
seqNum | INTEGER | Sequence number of the order book snapshot |
asks | LIST of floats | Sell side depth;
|
bids | LIST of floats | Buy side depth;
|
marketCode | STRING | marketCode |
checksum | LONG | |
timestamp | STRING | Millisecond timestamp |
action | STRING | partial increment |
Best Bid/Ask
Request format
{
"op": "subscribe",
"tag": "test1",
"args": [
"bestBidAsk:BTC-USD-SWAP-LIN"
]
}
Success response format
{
"success": true,
"tag": "test1",
"event": "subscribe",
"channel": "bestBidAsk:BTC-USD-SWAP-LIN",
"timestamp": "1665456882918"
}
** depth update channel format**
{
"table": "bestBidAsk",
"data": {
"ask": [
19045.0,
1.0
],
"checksum": 3790706311,
"marketCode": "BTC-USD-SWAP-LIN",
"bid": [
19015.0,
1.0
],
"timestamp": "1665456882928"
}
}
Channel Update Frequency: Real-time
This websocket subscription streams the best bid and ask in real-time. Messages are pushed on every best bid/ask update in real-time
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply |
args | LIST | Yes | List of individual markets <bestBidAsk>:<marketCode> e.g: ["bestBidAsk:BTC-USD-SWAP-LIN"] |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | bestBidAsk |
data | DICTIONARY | |
ask | LIST of floats | Sell side depth;
|
bid | LIST of floats | Buy side depth;
|
checksum | LONG | |
marketCode | STRING | marketCode |
timestamp | STRING | Millisecond timestamp |
Trade
Request format
{
"op": "subscribe",
"tag": 1,
"args": ["trade:BTC-USD-SWAP-LIN"]
}
import websockets
import asyncio
import json
trade = \
{
"op": "subscribe",
"tag": 1,
"args": ["trade:BTC-USD-SWAP-LIN"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(trade))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "subscribe",
"channel": ["trade:BTC-USD-SWAP-LIN"],
"success": True,
"tag": "1",
"timestamp": "1594299886880"
}
Trade channel format
{
"table": "trade",
"data": [ {
"side": "buy",
"tradeId": "2778148208082945",
"price": "5556.91",
"quantity": "5",
"matchType": "MAKER",
"marketCode": "BTC-USD-SWAP-LIN",
"timestamp": "1594299886890"
} ]
}
Channel Update Frequency: real-time, with every order matched event
This trade channel sends public trade information whenever an order is matched on the order book.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | LIST | Yes | list of individual markets trade:<marketCode> |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | trade |
data | LIST of dictionary | |
tradeId | STRING | Transaction Id |
price | STRING | Matched price |
quantity | STRING | Matched quantity |
matchType | STRING | TAKER or MAKER , orders that match via the implied mechanism show as MAKERs in their respective markets |
side | STRING | Matched side |
timestamp | STRING | Matched timestamp |
marketCode | STRING | Market code |
Ticker
Request format
{
"op": "subscribe",
"tag": 1,
"args": ["ticker:all"]
}
OR
{
"op": "subscribe",
"tag": 1,
"args": ["ticker:OX-USDT", ........]
}
import websockets
import asyncio
import json
ticker = \
{
"op": "subscribe",
"tag": 1,
"args": ["ticker:all"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(ticker))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "subscribe",
"channel": "<args value>",
"success": True,
"tag": "1",
"timestamp": "1594299886890"
}
Channel update format
{
"table": "ticker",
"data": [
{
"last": "0",
"open24h": "2.80500000",
"high24h": "3.39600000",
"low24h": "2.53300000",
"volume24h": "0",
"currencyVolume24h": "0",
"openInterest": "0",
"marketCode": "1INCH-USDT",
"timestamp": "1622020931049",
"lastQty": "0",
"markPrice": "3.304",
"lastMarkPrice": "3.304",
"indexPrice": "3.304"
},
{
"last": "0",
"open24h": "2.80600000",
"high24h": "3.39600000",
"low24h": "2.53300000",
"volume24h": "0",
"currencyVolume24h": "0",
"openInterest": "0",
"marketCode": "1INCH-USD-SWAP-LIN",
"timestamp": "1622020931046",
"lastQty": "0",
"markPrice": "3.304",
"lastMarkPrice": "3.304",
"indexPrice": "3.304"
},
...
]
}
Channel Update Frequency: 500 ms
The ticker channel pushes live price and volume information about the contract.
The websocket will reply with the shown success response format for each ticker channel which has been successfully subscribed to.
The data array in the message from this ticker channel will contain a single JSON corresponding to one ticker subscription.
If you subcribe "ticker:all", you would get one whole message containing all markets.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | LIST | Yes | ticker:all or a list of individual markets ticker:<marketCode> |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | ticker |
data | LIST of dictionary | |
marketCode | STRING | Market code |
last | STRING | Last traded price |
markPrice | STRING | Mark price |
open24h | STRING | 24 hour rolling opening price |
volume24h | STRING | 24 hour rolling trading volume in OX |
currencyVolume24h | STRING | 24 hour rolling trading volume in Contracts |
high24h | STRING | 24 hour highest price |
low24h | STRING | 24 hour lowest price |
openInterest | STRING | Open interest in Contracts |
lastQty | STRING | Last traded price amount |
timestamp | STRING | Millisecond timestamp |
lastMarkPrice | STRING | Previous mark price reading |
indexPrice | STRING | Index price |
Candles
Request format
{
"op": "subscribe",
"tag": 1,
"args": ["candles60s:BTC-USD-SWAP-LIN"]
}
import websockets
import asyncio
import json
candles = \
{
"op": "subscribe",
"tag": 1,
"args": ["candles60s:BTC-USD-SWAP-LIN"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(candles))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "subscribe",
"channel": ["candles60s:BTC-USD-SWAP-LIN"],
"success": True,
"tag": "1",
"timestamp": "1594313762698"
}
Channel update format
{
"table": "candle60s",
"data": [ {
"marketCode": "BTC-USD-SWAP-LIN",
"candle": [
"1594313762698", //timestamp
"9633.1", //open
"9693.9", //high
"9238.1", //low
"9630.2", //close
"45247", //volume in OX
"5.3" //volume in Contracts
]
} ]
}
Channel Update Frequency: 500ms
Granularity: 60s, 180s, 300s, 900s, 1800s, 3600s, 7200s, 14400s, 21600s, 43200s, 86400s
The candles channel pushes candlestick data for the current candle.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | LIST | Yes | list of individual candle granularity and market candles<granularity>:<marketCode> |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | candles<granularity> |
data | LIST of dictionary | |
marketCode | STRING | Market code |
candle | LIST of strings |
|
Liquidation RFQ
Request format
{
"op": "subscribe",
"tag": 1,
"args": ["liquidationRFQ"]
}
import websockets
import asyncio
import json
liquidation = \
{
"op": "subscribe",
"tag": 1,
"args": ["liquidationRFQ"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(liquidation))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "subscribe",
"channel": "liquidationRFQ",
"success": True,
"tag": "1",
"timestamp": "1613774604469"
}
Channel update format
{
"table": "liquidationRFQ",
"data": [ {
"marketCode": "BTC-USD-SWAP-LIN"
"timestamp": "1613774607889"
} ]
}
Channel Update Frequency: real-time, whenever there is an upcoming liquidation.
The liquidation RFQ (request for quotes) channel publishes a message 500ms before a liquidation event is due to occur. A liquidation event can be classed as one of the following:-
- liquidation of a clients perp position (in the perp books)
- liquidation of a clients spot collateral (in the spot books)
The message will contain the market code and is designed to give liquidity providers and traders an opportunity to make a 2-way market for the upcoming liquidation event.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | Single element LIST | Yes | liquidationRFQ |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | liquidationRFQ |
data | LIST of dictionary | |
marketCode | STRING | Market code of liquidation |
timestamp | STRING | Millisecond timestamp |
Market
Request format
{
"op": "subscribe",
"tag": 1,
"args": ["market:all"]
}
OR
{
"op": "subscribe",
"tag": 1,
"args": ["market:OX-USDT", ........]
}
import websockets
import asyncio
import json
market = \
{
"op": "subscribe",
"tag": 1,
"args": ["market:all"]
}
url= 'wss://stgapi.ox.fun/v2/websocket'
async def subscribe():
async with websockets.connect(url) as ws:
while True:
if not ws.open:
print("websocket disconnected")
ws = await websockets.connect(url)
response = await ws.recv()
data = json.loads(response)
print(data)
if 'nonce' in data:
await ws.send(json.dumps(market))
elif 'success' in data and data['success'] == 'True':
continue
asyncio.get_event_loop().run_until_complete(subscribe())
Success response format
{
"event": "subscribe",
"channel": "<args value>",
"success": True,
"tag": "1",
"timestamp": "1594299886890"
}
Channel update format
{
"table": "market",
"data": [
{
"marketId": "3001000000000",
"marketCode": "OX-USDT",
"name": "OX/USDT Spot",
"referencePair": "OX/USDT",
"base": "OX",
"counter": "USDT",
"type": "SPOT",
"exclusive": "false",
"tickSize": "0.001",
"qtyIncrement": "0.1",
"marginCurrency": "USDT",
"contractValCurrency": "OX",
"upperPriceBound": "0.0495",
"lowerPriceBound": "0.041",
"marketPrice": "0.045",
},
........
]
}
Channel Update Frequency: 1s
The market channel pushes live information about the market such as the current market price and the lower & upper sanity bounds as well as reference data related to the market.
The websocket will reply with the shown success response format for each market which has been successfully subscribed to.
If a subscription has been made to market:all, the data array in the message from this channel will contain a JSON list of all markets. Each JSON will contain information for each market seperately. Otherwise the data array will contain a single JSON corresponding to one market per market channel subscription.
Request Parameters
Parameters | Type | Required | Description |
---|---|---|---|
op | STRING | Yes | subscribe |
tag | INTEGER or STRING | No | If given it will be echoed in the reply and the max size of tag is 32 |
args | LIST | Yes | market:all or a list of individual markets market:<marketCode> |
Channel Update Fields
Fields | Type | Description |
---|---|---|
table | STRING | market |
data | LIST of dictionaries | |
marketPrice | STRING | Mark price |
qtyIncrement | STRING | Quantity increment |
upperPriceBound | STRING | Upper sanity price bound |
lowerPriceBound | STRING | Lower sanity price bound |
counter | STRING | |
type | STRING | |
marketId | STRING | |
referencePair | STRING | |
tickSize | STRING | Tick size |
marketPriceLastUpdated | STRING | Millisecond timestamp |
contractValCurrency | STRING | |
name | STRING | |
marketCode | STRING | |
marginCurrency | STRING | |
base | STRING |
Other Responses
By subscribing to an authenticated websocket there may be instances when a REST method will also generate a websocket reponse in addition to the REST reply. There are also some GUI commands which will generate a websocket reponse.
Cancel All Open Orders
Success response format
{
"event": "CANCEL",
"submitted": True,
"timestamp": "1612476498953"
}
Documentation for the REST method for cancelling all open orders for an account can be found here Cancel All Orders.
In both these instances a successful action will generate the shown repsonse in an authenticated websocket.
This action can also be executed via the trading GUI using the Cancel All button on the Open Orders blotter for both Spot and Derivative markets.
Error Codes
Failure response format
{
"event": "<opValue>",
"message": "<errorMessage>",
"code": "<errorCode>",
"success": False
}
Both subscription and order command requests sent via websocket can be rejected and the failure response will return an error code and a corresponding error message explaining the reason for the rejection.
Code | Error Message |
---|---|
05001 | Your operation authority is invalid |
20000 | Signature is invalid |
20001 | Operation failed, please contact system administrator |
20002 | Unexpected error, please check if your request data complies with the specification. |
20003 | Unrecognized operation |
20005 | Already logged in |
20006 | Quantity must be greater than zero |
20007 | You are accessing server too rapidly |
20008 | clientOrderId must be greater than zero if provided |
20009 | JSON data format is invalid |
20010 | Either clientOrderId or orderId is required |
20011 | marketCode is required |
20012 | side is required |
20013 | orderType is required |
20014 | clientOrderId is not long type |
20015 | marketCode is invalid |
20016 | side is invalid |
20017 | orderType is invalid |
20018 | timeInForce is invalid |
20019 | orderId is invalid |
20020 | stopPrice or limitPrice is invalid |
20021 | price is invalid |
20022 | price is required for LIMIT order |
20023 | timestamp is required |
20024 | timestamp exceeds the threshold |
20025 | API key is invalid |
20026 | Token is invalid or expired |
20027 | The length of the message exceeds the maximum length |
20028 | price or stopPrice or limitPrice must be greater than zero |
20029 | stopPrice must be less than limitPrice for Buy Stop Order |
20030 | limitPrice must be less than stopPrice for Sell Stop Order |
20031 | The marketCode is closed for trading temporarily |
20032 | Failed to submit due to timeout in server side |
20033 | triggerType is invalid |
20034 | The size of tag must be less than 32 |
20034 | The size of tag must be less than 32 |
20050 | selfTradePreventionMode is invalid |
300001 | Invalid account status xxx, please contact administration if any questions |
300011 | Repo market orders are not allowed during the auction window |
300012 | Repo bids above 0 and offers below 0 are not allowed during the auction window |
100005 | Open order not found |
100006 | Open order is not owned by the user |
100008 | Quantity cannot be less than the quantity increment xxx |
100015 | recvWindow xxx has expired |
200050 | The market is not active |
710001 | System failure, exception thrown -> xxx |
710002 | The price is lower than the minimum |
710003 | The price is higher than the maximum |
710004 | Position quantity exceeds the limit |
710005 | Insufficient margin |
710006 | Insufficient balance |
710007 | Insufficient position |
000101 | Internal server is unavailable temporary, try again later |
000201 | Trade service is busy, try again later |
REST API V3
TEST SITE
https://stg.ox.fun
https://stgapi.ox.fun
LIVE SITE
https://ox.fun
https://api.ox.fun
OX.FUN offers a powerful RESTful API to empower traders.
RESTful Error Codes
Code | Description |
---|---|
429 | Rate limit reached |
10001 | General networking failure |
20001 | Invalid parameter |
30001 | Missing parameter |
40001 | Alert from the server |
50001 | Unknown server error |
20031 | The marketCode is closed for trading temporarily |
Rate Limits
Each IP is limited to:
- 100 requests per second
- 20 POST v3/orders requests per second
- 2500 requests over 5 minutes
Certain endpoints have extra IP restrictions:
s
denotes a second- Requests limited to
1/s
&2/10s
&4/10s
- Only 1 request is permitted per second and only 2 requests are permitted within 10 seconds
- Request limit
1/10s
- The endpoint will block for 10 seconds after an incorrect 2FA code is provided (if the endpoint requires a 2FA code)
Affected APIs:
Rest Api Authentication
Request
{
"Content-Type": "application/json",
"AccessKey": "<string>",
"Timestamp": "<string>",
"Signature": "<string>",
"Nonce": "<string>"
}
import requests
import hmac
import base64
import hashlib
import datetime
import json
# rest_url = 'https://api.ox.fun'
# rest_path = 'api.ox.fun'
rest_url = 'https://stgapi.ox.fun'
rest_path = 'stgapi.ox.fun'
api_key = "API-KEY"
api_secret = "API-SECRET"
ts = datetime.datetime.utcnow().isoformat()
nonce = 123
method = "API-METHOD"
# Optional and can be omitted depending on the REST method being called
body = json.dumps({'key1': 'value1', 'key2': 'value2'})
if body:
path = method + '?' + body
else:
path = method
msg_string = '{}\n{}\n{}\n{}\n{}\n{}'.format(ts, nonce, 'GET', rest_path, method, body)
sig = base64.b64encode(hmac.new(api_secret.encode('utf-8'), msg_string.encode('utf-8'), hashlib.sha256).digest()).decode('utf-8')
header = {'Content-Type': 'application/json', 'AccessKey': api_key,
'Timestamp': ts, 'Signature': sig, 'Nonce': str(nonce)}
resp = requests.get(rest_url + path, headers=header)
# When calling an endpoint that uses body
# resp = requests.post(rest_url + method, data=body, headers=header)
print(resp.json())
Public market data methods do not require authentication, however private methods require a Signature to be sent in the header of the request. These private REST methods use HMAC SHA256 signatures.
The HMAC SHA256 signature is a keyed HMAC SHA256 operation using a client's API Secret as the key and a message string as the value for the HMAC operation.
The message string is constructed as follows:-
msgString = f'{Timestamp}\n{Nonce}\n{Verb}\n{URL}\n{Path}\n{Body}'
Component | Required | Example | Description |
---|---|---|---|
Timestamp | Yes | 2020-04-30T15:20:30 | YYYY-MM-DDThh:mm:ss |
Nonce | Yes | 123 | User generated |
Verb | Yes | GET | Uppercase |
Path | Yes | stgapi.ox.fun | |
Method | Yes | /v3/positions | Available REST methods |
Body | No | marketCode=BTC-oUSD-SWAP-LIN | Optional and dependent on the REST method being called |
The constructed message string should look like:-
2020-04-30T15:20:30\n
123\n
GET\n
stgapi.ox.fun\n
/v3/positions\n
marketCode=BTC-oUSD-SWAP-LIN
Note the newline characters after each component in the message string. If Body is omitted it's treated as an empty string.
Finally, you must use the HMAC SHA256 operation to get the hash value using the API Secret as the key, and the constructed message string as the value for the HMAC operation. Then encode this hash value with BASE-64. This output becomes the signature for the specified authenticated REST API method.
The signature must then be included in the header of the REST API call like so:
header = {'Content-Type': 'application/json', 'AccessKey': API-KEY, 'Timestamp': TIME-STAMP, 'Signature': SIGNATURE, 'Nonce': NONCE}
Account & Wallet - Private
GET /v3/account
Get account information
Request
GET v3/account?subAcc={subAcc},{subAcc}
Successful response format
{
"success": true,
"data": [
{
"accountId": "21213",
"name": "main",
"accountType": "PORTFOLIO",
"balances": [
{
"asset": "OX",
"total": "100000",
"available": "100000",
"reserved": "0",
"lastUpdatedAt": "1593627415234"
},
{
"asset": "USDT",
"total": "1585.890",
"available": "325.890",
"reserved": "1260.0",
"lastUpdatedAt": "1593627415123"
}
],
"positions": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"baseAsset": "BTC",
"counterAsset": "USD",
"position": "0.00030",
"entryPrice": "43976.700",
"markPrice": "43788.1",
"positionPnl": "-5.6580",
"estLiquidationPrice": "2.59",
"lastUpdatedAt": "1637876701404",
}
],
"collateral": "100000.0",
"notionalPositionSize": "1313.643",
"portfolioVarMargin": "131.3643",
"maintenanceMargin": "65.68215",
"marginRatio": "0.065682",
"riskRatio": "761.241829",
"liquidating": false,
"feeTier": "2",
"createdAt": "1611665624601"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
subAcc | STRING | NO | Name of sub account. If no subAcc is given, then the response contains only the account linked to the API-Key. Multiple subAccs can be separated with a comma, maximum of 10 subAccs, e.g. subone,subtwo |
Response Field | Type | Description |
---|---|---|
accountId | STRING | Account ID |
name | STRING | Account name |
accountType | STRING | Account type LINEAR , STANDARD , PORTFOLIO |
balances | LIST of dictionaries | |
asset | STRING | Asset name |
total | STRING | Total balance |
available | STRING | Available balance |
reserved | STRING | Reserved balance |
lastUpdatedAt | STRING | Last balance update timestamp |
positions | LIST of dictionaries | Positions - only returned if the account has open positions |
marketCode | STRING | Market code |
baseAsset | STRING | Base asset |
counterAsset | STRING | Counter asset |
position | STRING | Position size |
entryPrice | STRING | Entry price |
markPrice | STRING | Mark price |
positionPnl | STRING | Position PNL |
estLiquidationPrice | STRING | Estimated liquidation price |
lastUpdatedAt | STRING | Last position update timestamp |
marginBalance | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin. Isolated margin + Unrealized position PnL |
maintenanceMargin | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin |
marginRatio | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin |
leverage | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin |
collateral | STRING | Total collateral balance |
notionalPositionSize | STRING | Notional position size in OX |
portfolioVarMargin | STRING | Initial margin |
maintenanceMargin | STRING | Maintenance margin. The minimum amount of collateral required to avoid liquidation |
marginRatio | STRING | Margin ratio. Orders are rejected/cancelled if the margin ratio reaches 50, and liquidation occurs if the margin ratio reaches 100 |
riskRatio | STRING | Ignore. |
liquidating | BOOL | Available values: true and false |
feeTier | STRING | Fee tier |
createdAt | STRING | Timestamp indicating when the account was created |
GET /v3/account/names
Get sub account information
Request
GET v3/account/names
Successful response format
{
"success": true,
"data": [
{
"accountId": "21213",
"name": "Test 1"
},
{
"accountId": "21214",
"name": "Test 2"
}
]
}
Response Field | Type | Description |
---|---|---|
accountId | STRING | Account ID |
name | STRING | Account name |
GET /v3/wallet
Get account or sub-account wallet
Request
GET v3/wallet?subAcc={name1},{name2}&type={type}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"accountId": "21213",
"name": "main",
"walletHistory": [
{
"id": "810583329159217160",
"asset": "USDT",
"type": "DEPOSIT",
"amount": "10",
"createdAt": "162131535213"
}
]
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
subAcc | STRING | NO | Max 5 |
type | STRING | NO | DEPOSIT, WITHDRAWAL, etc, default return all, most recent first |
limit | LONG | NO | Default 200, max 500 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
accountId | STRING | Account ID |
name | STRING | Account name |
walletHistory | LIST of dictionaries | |
id | STRING | A unique ID |
amount | STRING | Amount |
asset | STRING | Asset name |
type | STRING | |
createdAt/lastUpdatedAt | STRING | Millisecond timestamp created time or updated time |
POST /v3/transfer
Sub-account balance transfer
Request
POST /v3/transfer
{
"asset": "USDT",
"quantity": "1000",
"fromAccount": "14320",
"toAccount": "15343"
}
Successful response format
{
"success": true,
"data": {
"asset": "USDT",
"quantity": "1000",
"fromAccount": "14320",
"toAccount": "15343",
"transferredAt": "1635038730480"
}
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | YES | |
quantity | STRING | YES | |
fromAccount | STRING | YES | |
toAccount | STRING | YES |
Response Field | Type | Description |
---|---|---|
asset | STRING | |
quantity | STRING | |
fromAccount | STRING | |
toAccount | STRING | |
transferredAt | STRING | Millisecond timestamp |
GET /v3/transfer
Sub-account balance transfer history
Request
GET /v3/transfer?asset={asset}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"asset": "USDT",
"quantity": "1000",
"fromAccount": "14320",
"toAccount": "15343",
"id": "703557273590071299",
"status": "COMPLETED",
"transferredAt": "1634779040611"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | NO | Default all assets |
limit | LONG | NO | Default 50, max 200 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
asset | STRING | |
quantity | STRING | |
fromAccount | STRING | |
toAccount | STRING | |
id | STRING | |
status | STRING | |
transferredAt | STRING | Millisecond timestamp |
GET /v3/balances
Request
GET /v3/balances?subAcc={name1},{name2}&asset={asset}
Successful response format
{
"success": true,
"data": [
{
"accountId": "21213",
"name": "main",
"balances": [
{
"asset": "USDT",
"total": "4468.823",
"available": "4468.823",
"reserved": "0",
"lastUpdatedAt": "1593627415234"
},
{
"asset": "OX",
"total": "100000.20",
"available": "100000.20",
"reserved": "0",
"lastUpdatedAt": "1593627415123"
}
]
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | NO | Default all assets |
subAcc | STRING | NO | Name of sub account. If no subAcc is given, then the response contains only the account linked to the API-Key. Multiple subAccs can be separated with a comma, maximum of 10 subAccs, e.g. subone,subtwo |
Response Field | Type | Description |
---|---|---|
accountId | STRING | Account ID |
name | STRING | The parent account is named "main" and comes first |
balances | LIST of dictionaries | |
asset | STRING | Asset name |
total | STRING | Total balance (available + reserved) |
available | STRING | Available balance |
reserved | STRING | Reserved balance |
lastUpdatedAt | STRING | Timestamp of updated at |
GET /v3/positions
Request
GET /v3/positions?subAcc={name1},{name2}&marketCode={marketCode}
Successful response format
{
"success": True,
"data": [
{
"accountId": "1234",
"name": "main",
"positions": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"baseAsset": "BTC",
"counterAsset": "USD",
"position": "-0.00030",
"entryPrice": "43976.7",
"markPrice": "43706.3",
"positionPnl": "8.112",
"estLiquidationPrice": "23611539.7",
"lastUpdatedAt": "1673231134601",
}
]
}
]
}
Returns position data
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | Default all markets |
subAcc | STRING | NO | Name of sub account. If no subAcc is given, then the response contains only the account linked to the API-Key. Multiple subAccs can be separated with a comma, maximum of 10 subAccs, e.g. subone,subtwo |
Response Fields | Type | Description |
---|---|---|
accountId | STRING | Account ID |
name | STRING | The parent account is named "main" and comes first |
positions | LIST of dictionaries | |
marketCode | STRING | Contract symbol, e.g. 'BTC-oUSD-SWAP-LIN' |
baseAsset | STRING | |
counterAsset | STRING | |
position | STRING | Position size, e.g. '0.94' |
entryPrice | STRING | Average entry price |
markPrice | STRING | |
positionPnl | STRING | Postion profit and lost |
estLiquidationPrice | STRING | Estimated liquidation price, return 0 if it is negative(<0) |
lastUpdated | STRING | Timestamp when position was last updated |
marginBalance | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin. Isolated margin + Unrealized position PnL |
maintenanceMargin | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin |
marginRatio | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin |
leverage | STRING | [Currently Unavailable] Appears in the position section only for positions using isolated margin |
GET /v3/funding
Get funding payments by marketCode and sorted by time in descending order.
Request
GET v3/funding?marketCode={marketCode}&limit={limit}&startTime={startTime}&endTime={endTime}
Request Parameters | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | e.g. BTC-oUSD-SWAP-LIN |
limit | LONG | NO | default is 200 , max is 500 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is EXCLUSIVE |
SUCCESSFUL RESPONSE
{
"success": true,
"data": [
{
"id": "810583329213284361",
"marketCode": "BTC-oUSD-SWAP-LIN",
"payment": "-122.17530872",
"fundingRate": "-0.00005",
"position": "-61.093",
"indexPrice": "39996.5",
"createdAt": "1627617632190"
}
]
}
Response Fields | Type | Description |
---|---|---|
id | STRING | A unique ID |
marketCode | STRING | Market code |
payment | STRING | Funding payment |
fundingRate | STRING | Funding rate |
position | STRING | Position |
indexPrice | STRING | index price |
createdAt | STRING | Timestamp of this response |
Deposits & Withdrawals - Private
GET /v3/deposit-addresses
Deposit addresses
Request
GET /v3/deposit-addresses?asset={asset}&network={network}
Successful response format
{
"success": true,
"data": {
"address":"0xD25bCD2DBb6114d3BB29CE946a6356B49911358e"
}
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | YES | |
network | STRING | YES |
Response Field | Type | Description |
---|---|---|
address | STRING | Deposit address |
memo | STRING | Memo (tag) if applicable |
GET /v3/deposit
Deposit history
Request
GET /v3/deposit?asset={asset}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"asset": "USDT",
"network": "ERC20",
"address": "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B",
"quantity": "100.0",
"id": "651573911056351237",
"status": "COMPLETED",
"txId": "0x2b2f01a3cbe5165c883e3b338441182f309ddb8f504b52a2e9e15f17ea9af044",
"creditedAt": "1617940800000"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | NO | Default all assets |
limit | LONG | NO | Default 50, max 200 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
asset | STRING | |
network | STRING | |
address | STRING | Deposit address |
memo | STRING | Memo (tag) if applicable |
quantity | STRING | |
id | STRING | |
status | STRING | |
txId | STRING | |
creditedAt | STRING | Millisecond timestamp |
GET /v3/withdrawal-addresses
Withdrawal addresses
Request
GET /v3/withdrawal-addresses?asset={asset}&network={network}
Successful response format
{
"success": true,
"data": [
{
"asset": "USDT",
"network": "ERC20",
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"label": "farming",
"whitelisted": true
}
]
}
Provides a list of all saved withdrawal addresses along with their respected labels, network, and whitelist status
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | NO | Default all assets |
network | STRING | NO | Default all networks |
Response Field | Type | Description |
---|---|---|
asset | STRING | |
network | STRING | |
address | STRING | |
memo | STRING | Memo (tag) if applicable |
label | STRING | Withdrawal address label |
whitelisted | BOOL |
GET /v3/withdrawal
Withdrawal history
Request
GET /v3/withdrawal?id={id}&asset={asset}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"id": "651573911056351237",
"asset": "USDT",
"network": "ERC20",
"address": "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B",
"quantity": "1000.0",
"fee": "0.000000000",
"status": "COMPLETED",
"txId": "0x2b2f01a3cbe5165c883e3b338441182f309ddb8f504b52a2e9e15f17ea9af044",
"requestedAt": "1617940800000",
"completedAt": "16003243243242"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
id | STRING | NO | |
asset | STRING | NO | Default all assets |
limit | LONG | NO | Default 50, max 200 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. This filter applies to "requestedAt". startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. This filter applies to "requestedAt". endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
id | STRING | |
asset | STRING | |
network | STRING | |
address | STRING | |
memo | STRING | Memo (tag) if applicable |
quantity | STRING | |
fee | STRING | |
status | STRING | COMPLETED , PROCESSING , IN SWEEPING , PENDING , ON HOLD , CANCELED , or FAILED |
txId | STRING | |
requestedAt | STRING | Millisecond timestamp |
completedAt | STRING | Millisecond timestamp |
POST /v3/withdrawal
Withdrawal request
Request
POST /v3/withdrawal
{
"asset": "USDT",
"network": "ERC20",
"address": "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B",
"quantity": "100",
"externalFee": true,
"tfaType": "GOOGLE",
"code": "743249"
}
Successful response format
{
"success": true,
"data": {
"id": "752907053614432259",
"asset": "USDT",
"network": "ERC20",
"address": "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B",
"quantity": "100.0",
"externalFee": true,
"fee": "0",
"status": "PENDING",
"requestedAt": "1617940800000"
}
}
Withdrawals may only be initiated by API keys that are linked to the parent account and have withdrawals enabled. If the wrong 2fa code is provided the endpoint will block for 10 seconds.
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | YES | |
network | STRING | YES | |
address | STRING | YES | |
memo | STRING | NO | Memo is required for chains that support memo tags |
quantity | STRING | YES | |
externalFee | BOOL | YES | If false, then the fee is taken from the quantity, also with the burn fee for asset SOLO |
tfaType | STRING | NO | GOOGLE, or AUTHY_SECRET, or YUBIKEY |
code | STRING | NO | 2fa code if required by the account |
Response Field | Type | Description |
---|---|---|
id | STRING | |
asset | STRING | |
network | STRING | |
address | STRING | |
memo | STRING | |
quantity | STRING | |
externalFee | BOOL | If false, then the fee is taken from the quantity |
fee | STRING | |
status | STRING | |
requestedAt | STRING | Millisecond timestamp |
GET /v3/withdrawal-fee
Withdrawal fee estimate
Request
GET /v3/withdrawal-fee?asset={asset}&network={network}&address={address}&memo={memo}&quantity={quantity}&externalFee={externalFee}
Successful response format
{
"success": true,
"data": {
"asset": "USDT",
"network": "ERC20",
"address": "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B",
"quantity": "1000.0",
"externalFee": true,
"estimatedFee": "0.01"
}
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | YES | |
network | STRING | YES | |
address | STRING | YES | |
memo | STRING | NO | Required only for 2 part addresses (tag or memo) |
quantity | STRING | YES | |
externalFee | BOOL | NO | Default false. If false, then the fee is taken from the quantity |
Response Field | Type | Description |
---|---|---|
asset | STRING | |
network | STRING | |
address | STRING | |
memo | STRING | Memo (tag) if applicable |
quantity | STRING | |
externalFee | BOOL | If false, then the fee is taken from the quantity |
estimatedFee | STRING |
Orders - Private
GET /v3/orders/status
Get latest order status
Request
GET /v3/orders/status?orderId={orderId}&clientOrderId={clientOrderId}
Successful response format
{
"success": true,
"data": {
"orderId": "1000387920513",
"clientOrderId": "1612249737434",
"marketCode": "OX-USDT",
"status": "FILLED",
"side": "BUY",
"price": "0.0200",
"isTriggered": false,
"remainQuantity": "0",
"totalQuantity": "12",
"cumulativeMatchedQuantity": "12",
"avgFillPrice": "0.0200",
"orderType": "LIMIT",
"timeInForce": "GTC",
"source": "11",
"createdAt": "1655980336520",
"lastModifiedAt": "1655980393780",
"lastMatchedAt": "1655980622848"
}
}
Request Parameter | Type | Required | Description |
---|---|---|---|
orderId | LONG | YES if no clientOrderId | Order ID |
clientOrderId | LONG | YES if no orderId | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
Response Field | Type | Description |
---|---|---|
orderId | STRING | Order ID |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | Market code |
status | STRING | Available values: CANCELED , OPEN , PARTIAL_FILL , FILLED |
side | STRING | Side of the order, BUY or SELL |
price | STRING | Price or limit price in the case of a STOP order |
stopPrice | STRING | Trigger price for a STOP order |
amount | STRING | Amount (only allow amount field when market is spot and direction is BUY) |
displayQuantity | STRING | |
triggerType | STRING | |
isTriggered | BOOL | true for a STOP order |
remainQuantity | STRING | Remaining quantity |
totalQuantity | STRING | Total quantity |
cumulativeMatchedQuantity | STRING | Cumulative quantity of the matches |
avgFillPrice | STRING | Average of filled price |
fees | LIST of dictionaries | Overall fees with instrument ID, if you don't hold enough OX to cover the fee then USDT will be charged instead |
orderType | STRING | Type of the order, availabe values: MARKET , LIMIT , STOP_LIMIT ,STOP_MARKET |
timeInForce | STRING | Client submitted time in force.
|
source | STRING | Source of the request, available values: 0 , 2 , 10 , 11 , 13 , 22 , 31 , 32 , 33 , 101 , 102 , 103 , 104 , 108 , 111 , 150 . Enumeration: |
createdAt | STRING | Millisecond timestamp of the order created time |
lastModifiedAt | STRING | Millisecond timestamp of the order last modified time |
lastMatchedAt | STRING | Millisecond timestamp of the order last matched time |
canceledAt | STRING | Millisecond timestamp of the order canceled time |
GET /v3/orders/working
Returns all the open orders of the account connected to the API key initiating the request.
Request
GET /v3/orders/working?marketCode={marketCode}&orderId={orderId}&clientOrderId={clientOrderId}
Successful response format
{
"success": True,
"data": [
{
"orderId": "1000026408953",
"clientOrderId": "1",
"marketCode": "BTC-USDT",
"status": "OPEN",
"side": "BUY",
"price": "1000.0",
"isTriggered": True,
"quantity": "10.0",
"remainQuantity": "10.0",
"matchedQuantity": "0.0",
"orderType": "LIMIT",
"timeInForce": "GTC",
"source": "11",
"createdAt": "1680113440852",
"lastModifiedAt": "1680113440875"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | default most recent orders first |
orderId | LONG | NO | Client assigned ID to help manage and identify orders |
clientOrderId | LONG | NO | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
Response Field | Type | Description |
---|---|---|
orderId | STRING | Order ID |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | Market code |
status | STRING | Available values: OPEN , PARTIALLY_FILLED |
side | STRING | Side of the order, BUY or SELL |
price | STRING | Price or limit price in the case of a STOP order |
stopPrice | STRING | Trigger price for a STOP order |
isTriggered | BOOL | Returns true if a STOP order has been triggered |
quantity | STRING | Quantity |
remainQuantity | STRING | Remaining quantity |
matchedQuantity | STRING | Matched Quantity |
amount | STRING | Amount (only allow amount field when market is spot and direction is BUY) |
displayQuantity | STRING | |
triggerType | STRING | |
orderType | STRING | Type of the order, availabe values: MARKET , LIMIT , STOP_LIMIT ,STOP_MARKET |
timeInForce | STRING | Client submitted time in force.
|
source | STRING | Source of the request, available values: 0 , 2 , 10 , 11 , 13 , 22 , 31 , 32 , 33 , 101 , 102 , 103 , 104 , 108 , 111 , 150 . Enumeration: |
createdAt | STRING | Millisecond timestamp of the order created time |
lastModifiedAt | STRING | Millisecond timestamp of the order last modified time |
lastMatchedAt | STRING | Millisecond timestamp of the order last matched time |
POST /v3/orders/place
Request
POST /v3/orders/place
{
"recvWindow": 20000,
"responseType": "FULL",
"timestamp": 1615430912440,
"orders": [
{
"clientOrderId": 1612249737724,
"marketCode": "BTC-USD-SWAP-LIN",
"side": "SELL",
"quantity": "0.001",
"timeInForce": "GTC",
"orderType": "LIMIT",
"price": "50007"
},
{
"clientOrderId": 1612249737724,
"marketCode": "BTC-USD-SWAP-LIN",
"side": "BUY",
"quantity": "0.002",
"timeInForce": "GTC",
"orderType": "LIMIT",
"price": "54900"
}
]
}
Successful response format
{
"success": true,
"data": [
{
"code": "710006",
"message": "FAILED balance check as balance (0E-9) < value (0.001)",
"submitted": false,
"clientOrderId": "1612249737724",
"marketCode": "BTC-USD-SWAP-LIN",
"side": "SELL",
"price": "52888.0",
"quantity": "0.001",
"orderType": "LIMIT",
"timeInForce": "GTC",
"createdAt": "16122497377340",
"source": "0"
},
{
"notice": "OrderOpened",
"accountId": "1076",
"orderId": "1000132664173",
"submitted": true,
"clientOrderId": "1612249737724",
"marketCode": "BTC-USD-SWAP-LIN",
"status": "OPEN",
"price": "23641.0",
"stopPrice": null,
"isTriggered": false,
"quantity": "0.01",
"amount": "0.0",
"remainQuantity": null,
"matchId": null,
"matchPrice": null,
"matchQuantity": null,
"feeInstrumentId": null,
"fees": null,
"orderType": "LIMIT",
"timeInForce": "GTC",
"createdAt": "1629192975532",
"lastModifiedAt": null,
"lastMatchedAt": null
}
]
}
Place orders.
Request Parameters | Type | Required | Description |
---|---|---|---|
recvWindow | LONG | NO | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used. If recvWindow is provided with no timestamp, then the request will not be rejected. If neither timestamp nor recvWindow are provided, then the request will not be rejected |
timestamp | STRING | YES | In milliseconds. If an order reaches the matching engine and the current timestamp exceeds timestamp + recvWindow, then the order will be rejected. |
responseType | STRING | YES | FULL or ACK |
orders | LIST | YES | |
clientOrderId | ULONG | YES | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | YES | Market code |
side | STRING | YES | BUY or SELL |
quantity | STRING | YES | Quantity |
amount | STRING | NO | Amount (only allow amount field when market is spot and direction is BUY) |
displayQuantity | STRING | NO | displayQuantity (For an iceberg order, pass both quantity and displayQuantity fields in the order request.) |
timeInForce | STRING | NO | Default GTC |
orderType | STRING | YES | LIMIT or MARKET or STOP_LIMIT or STOP_MARKET |
price | STRING | NO | Limit price for the limit order |
stopPrice | STRING | NO | Stop price for the stop order |
limitPrice | STRING | NO | Limit price for the stop limit order |
selfTradePreventionMode | STRING | No | NONE , EXPIRE_MAKER , EXPIRE_TAKER , EXPIRE_BOTH for more info check here Self Trade Prevention Modes |
Response Fields | Type | Description |
---|---|---|
notice | STRING | OrderClosed or OrderMatched or OrderOpened |
accountId | STRING | Account ID |
code | STRING | Error code |
message | STRING | Error message |
submitted | BOOL | Denotes whether the order was submitted to the matching engine or not |
orderId | STRING | |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | |
status | STRING | Order status |
side | STRING | SELL or BUY |
price | STRING | |
stopPrice | STRING | |
isTriggered | BOOL | false (can be true for STOP order types) |
quantity | STRING | |
amount | STRING | |
displayQuantity | STRING | |
remainQuantity | STRING | Remaining quantity |
matchId | STRING | |
matchPrice | STRING | |
matchQuantity | STRING | Matched quantity |
feeInstrumentId | STRING | Instrument ID of fees paid from this match ID |
fees | STRING | Amount of fees paid from this match ID |
orderType | STRING | MARKET or LIMIT or STOP_LIMIT or or STOP_MARKET |
triggerType | STRING | |
timeInForce | STRING | |
source | STRING | Source of the request, available values: 0 , 2 , 10 , 11 , 13 , 22 , 101 , 102 , 103 , 104 , 111 . Enumeration: |
createdAt | STRING | Millisecond timestamp of the order created time |
lastModifiedAt | STRING | Millisecond timestamp of the order last modified time |
lastMatchedAt | STRING | Millisecond timestamp of the order last matched time |
DELETE /v3/orders/cancel
Request
DELETE /v3/orders/cancel
{
"recvWindow": 200000,
"responseType": "FULL",
"timestamp": 1615454880374,
"orders": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"orderId": "304384250571714215",
"clientOrderId": 1615453494726
},
{
"marketCode": "BTC-USD-SWAP-LIN",
"clientOrderId": 1612249737724
}
]
}
Successful response format
{
"success": true,
"data": [
{
"notice": "OrderClosed",
"accountId": "12005486",
"orderId": "304384250571714215",
"submitted": true,
"clientOrderId": "1615453494726",
"marketCode": "BTC-USD-SWAP-LIN",
"status": "CANCELED_BY_USER",
"side": "BUY",
"price": "4870.0",
"stopPrice": null,
"isTriggered": false,
"quantity": "0.001",
"amount": "0.0",
"remainQuantity": "0.001",
"orderType": "LIMIT",
"timeInForce": "GTC",
"closedAt": "1629712561919"
},
{
"code": "40035",
"message": "Open order not found with id",
"submitted": false,
"orderId": "204285250571714316",
"clientOrderId": "1612249737724",
"marketCode": "BTC-oUSD-SWAP-LIN",
"closedAt": "1615454881433"
}
]
}
Cancel orders.
Request Parameters | Type | Required | Description |
---|---|---|---|
recvWindow | LONG | NO | |
timestamp | LONG | YES | |
responseType | STRING | YES | FULL or ACK |
orders | LIST | YES | |
marketCode | STRING | YES | |
orderId | STRING | Either one of orderId or clientOrderId is required | |
clientOrderId | ULONG | Either one of orderId or clientOrderId is required | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
Response Fields | Type | Description |
---|---|---|
submitted | BOOL | Denotes if the cancel request was submitted to the matching engine or not |
notice | STRING | OrderClosed |
accountId | STRING | Account ID |
code | STRING | Error code |
message | STRING | Error message |
orderId | STRING | |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
marketCode | STRING | |
side | STRING | SELL or BUY |
price | STRING | |
stopPrice | STRING | |
isTriggered | BOOL | false (can be true for STOP order types) |
quantity | STRING | |
amount | STRING | |
displayQuantity | STRING | |
remainQuantity | STRING | Remaining quantity |
orderType | STRING | MARKET or LIMIT or STOP or STOP_MARKET |
triggerType | STRING | |
timeInForce | STRING | |
closedAt | STRING | Millisecond timestamp of the order close time |
DELETE /v3/orders/cancel-all
Request
DELETE /v3/orders/cancel-all
{
"marketCode": "BTC-USD-SWAP-LIN"
}
Successful response format
{
"success": true,
"data":
{
"notice": "Orders queued for cancelation"
}
}
Cancel orders.
Request Parameters | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO |
Response Fields | Type | Description |
---|---|---|
notice | STRING | Orders queued for cancelation or No working orders found” |
Trades - Private
GET /v3/trades
Returns your most recent trades.
Request
GET /v3/trades?marketCode={marketCode}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"orderId": "160067484555913076",
"clientOrderId": "123",
"matchId": "160067484555913077",
"marketCode": "OX-USDT",
"side": "SELL",
"matchedQuantity": "0.1",
"matchPrice": "0.065",
"total": "0.0065",
"orderMatchType": "TAKER",
"feeAsset": "OX",
"fee":"0.0196",
"source": "10",
"matchedAt": "1595514663626"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | String | default most recent trades first | |
limit | LONG | NO | max 500, default 200 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
orderId | STRING | Order ID |
clientOrderId | STRING | Client assigned ID to help manage and identify orders with max value 9223372036854775807 |
matchId | STRING | Match ID |
marketCode | STRING | Market code |
side | STRING | Side of the order, BUY or SELL |
matchedQuantity | STRING | Match quantity |
matchPrice | STRING | Match price |
total | STRING | Total price |
orderMatchType | STRING | TAKER ,MAKER |
feeAsset | STRING | Instrument ID of the fees |
fee | STRING | Fees |
source | STRING | Source of the request, available values: 0 , 2 , 10 , 11 , 13 , 22 , 101 , 102 , 103 , 104 , 111 . Enumeration: |
matchedAt | STRING | Millisecond timestamp of the order matched time |
Market Data - Public
GET /v3/markets
Get a list of markets by OX.FUN.
Request
GET /v3/markets?marketCode={marketCode}
Successful response format
{
"success": true,
"data": [
{
"marketCode": "BTC-USDT",
"name": "BTC/USDT",
"referencePair": "BTC/USDT",
"base": "BTC",
"counter": "USDT",
"type": "SPOT",
"tickSize": "0.1",
"minSize": "0.001",
"listedAt": "1593345600000",
"upperPriceBound": "65950.5",
"lowerPriceBound": "60877.3",
"markPrice": "63413.9",
"lastUpdatedAt": "1635848576163"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | Market Code |
name | STRING | Name of the contract |
referencePair | STRING | Reference pair |
base | STRING | Base asset |
counter | STRING | Counter asset |
type | STRING | Type of the contract |
tickSize | STRING | Tick size of the contract |
minSize | STRING | Minimum tradable quantity and quantity increment |
listedAt | STRING | Listing date of the contract |
settlementAt | STRING | Timestamp of settlement if applicable i.e. Quarterlies and Spreads |
upperPriceBound | STRING | Sanity bound |
lowerPriceBound | STRING | Sanity bound |
markPrice | STRING | Mark price |
indexPrice | STRING | index price |
lastUpdatedAt | STRING |
GET /v3/assets
Get a list of assets supported by OX.FUN
Request
GET /v3/assets?asset={asset}
Successful response format
{
"success": true,
"data": [
{
"asset": "OX",
"isCollateral": true,
"loanToValue": "1.000000000",
"loanToValueFactor": "0",
"networkList": [
{
"network": "ERC20",
"transactionPrecision": "6",
"isWithdrawalFeeChargedToUser": true,
"canDeposit": true,
"canWithdraw": true,
"minDeposit": "0.0001",
"minWithdrawal": "1"
}
]
},
{
"asset": "LINK",
"isCollateral": false,
"networkList": [
{
"network": "ERC20",
"tokenId": "0x514910771af9ca656af840dff83e8264ecf986ca",
"transactionPrecision": "18",
"isWithdrawalFeeChargedToUser": true,
"canDeposit": true,
"canWithdraw": true,
"minDeposit": "0.0001",
"minWithdrawal": "0.0001"
}
]
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
asset | STRING | NO | Asset name |
Response Field | Type | Description |
---|---|---|
asset | STRING | Asset name |
isCollateral | BOOL | Indicates if the asset can be used as collateral to trade perps |
loanToValue | STRING | Ignore |
loanToValueFactor | STRING | Ignore |
networkList | LIST | List of dictionaries |
network | STRING | Network for deposit and withdrawal |
tokenId | STRING | Token ID |
transactionPrecision | STRING | Precision for the transaction |
isWithdrawalFeeChargedToUser | BOOL | Indicates if there is a withdrawal fee |
canDeposit | BOOL | Indicates can deposit or not |
canWithdraw | BOOL | Indicates can withdraw or not |
minDeposit | STRING | Minimum deposit amount |
minWithdrawal | STRING | Minimum withdrawal amount |
GET /v3/tickers
Get tickers.
Request
GET /v3/tickers?marketCode={marketCode}
Successful response format
{
"success": true,
"data": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"markPrice": "41512.4",
"open24h": "41915.3",
"high24h": "42662.2",
"low24h": "41167.0",
"volume24h": "22206.50440",
"currencyVolume24h": "0.004780",
"openInterest": "0.001300",
"lastTradedPrice": "41802.5",
"lastTradedQuantity": "0.001",
"lastUpdatedAt": "1642585256002"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | Market code |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | Market code |
markPrice | STRING | Mark price |
open24h | STRING | Rolling 24 hour opening price |
high24h | STRING | Rolling 24 hour highest price |
low24h | STRING | Rolling 24 hour lowest price |
volume24h | STRING | Rolling 24 hour notional trading volume in OX terms |
currencyVolume24h | STRING | Rolling 24 hour trading volume in Contracts |
openInterest | STRING | Open interest in Contracts |
lastTradedPrice | STRING | Last traded price |
lastTradedQuantity | STRIN | Last traded quantity |
lastUpdatedAt | STRING | Millisecond timestamp of lastest update |
GET /v3/funding/estimates
Request
GET /v3/funding/estimates?marketCode={marketCode}
Successful response format
{
"success": true,
"data": [
{
"marketCode": "ETH-USD-SWAP-LIN",
"fundingAt": "1667012400000",
"estFundingRate": "0"
},
{
"marketCode": "BTC-USD-SWAP-LIN",
"fundingAt": "1667012400000",
"estFundingRate": "0"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | Market code |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | Market code |
estFundingRate | STRING | Estimates funding rate |
fundingAt | STRING | Millisecond timestamp |
GET /v3/candles
Get candles.
Request
GET /v3/candles?marketCode={marketCode}&timeframe={timeframe}&limit={limit}
&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"timeframe": "3600s",
"data": [
{
"open": "35888.80000000",
"high": "35925.30000000",
"low": "35717.00000000",
"close": "35923.40000000",
"volume": "0",
"currencyVolume": "0",
"openedAt": "1642932000000"
},
{
"open": "35805.50000000",
"high": "36141.50000000",
"low": "35784.90000000",
"close": "35886.60000000",
"volume": "0",
"currencyVolume": "0",
"openedAt": "1642928400000"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | YES | Market code |
timeframe | STRING | NO | Available values: 60s ,300s ,900s ,1800s ,3600s ,7200s ,14400s ,86400s , default is 3600s |
limit | LONG | NO | Default 200, max 500 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
timeframe | STRING | Available values: 60s ,300s ,900s ,1800s ,3600s ,7200s ,14400s ,86400s |
open | STRING | Opening price |
high | STRING | Highest price |
low | STRING | Lowest price |
close | STRING | Closing price |
volume | STRING | Trading volume in OX terms |
currencyVolume | STRING | Trading volume in Contract terms |
openedAt | STRING | Millisecond timestamp of the candle open |
GET /v3/depth
Get depth.
Request
GET /v3/depth?marketCode={marketCode}&level={level}
Successful response format
{
"success": true,
"level": "5",
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"lastUpdatedAt": "1643016065958",
"asks": [
[
39400,
0.261
],
[
41050.5,
0.002
],
[
41051,
0.094
],
[
41052.5,
0.002
],
[
41054.5,
0.002
]
],
"bids": [
[
39382.5,
0.593
],
[
39380.5,
0.009
],
[
39378,
0.009
],
[
39375.5,
0.009
],
[
39373,
0.009
]
]
}
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | YES | Market code |
level | LONG | NO | Default 5, max 100 |
Response Field | Type | Description |
---|---|---|
level | LONG | Level |
marketCode | STRING | Market code |
lastUpdatedAt | STRING | Millisecond timestamp of the lastest depth update |
asks | LIST of floats | Sell side depth: [price, quantity] |
bids | LIST of floats | Buy side depth: [price, quantity] |
GET /v3/markets/operational
Get markets operational.
Request
GET /v3/markets/operational?marketCode={marketCode}
Successful response format
{
"success": true,
"data": {
"marketCode": "BTC-USD-SWAP-LIN",
"operational": true
}
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | YES | Market code |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | Market code |
operational | BOOL | whether the market of the marketCode is operational |
GET /v3/exchange-trades
Request
GET /v3/exchange-trades?marketCode={marketCode}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"matchPrice": "9600.00000" ,
"matchQuantity": "0.100000" ,
"side": "BUY" ,
"matchType": "TAKER" ,
"matchedAt": "1662207330439"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | Market code |
limit | LONG | NO | Default 200, max 500 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | |
matchPrice | STRING | |
matchQuantity | STRING | |
side | STRING | |
matchType | STRING | |
matchedAt | STRING |
GET /v3/funding/rates
Get all historical funding rates
Request
GET /v3/funding/rates?marketCode={marketCode}&limit={limit}&startTime={startTime}&endTime={endTime}
Successful response format
{
"success": true,
"data": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"fundingRate": "0.0",
"createdAt": "1628362803134"
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | Market code |
limit | LONG | NO | Default 200, max 500 |
startTime | LONG | NO | Millisecond timestamp. Default 24 hours ago. startTime and endTime must be within 7 days of each other. startTime is INCLUSIVE |
endTime | LONG | NO | Millisecond timestamp. Default time now. startTime and endTime must be within 7 days of each other. endTime is INCLUSIVE |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | Market code |
fundingRate | STRING | Funding rate |
createdAt | STRING | Millisecond timestamp |
GET /v3/leverage/tiers
Get markets leverage tiers
Request
GET /v3/leverage/tiers?marketCode={marketCode}
Successful response format
{
"success": true,
"data": [
{
"marketCode": "BTC-USD-SWAP-LIN",
"tiers": [
{
"tier": 1,
"leverage": "10",
"positionFloor": "0",
"positionCap": "100",
"initialMargin": "0.1",
"maintenanceMargin": "0.05"
},
{
"tier": 2,
"leverage": "5",
"positionFloor": "100",
"positionCap": "250",
"initialMargin": "0.2",
"maintenanceMargin": "0.1"
},
{
"tier": 3,
"leverage": "4",
"positionFloor": "250",
"positionCap": "1000",
"initialMargin": "0.25",
"maintenanceMargin": "0.125"
}
]
}
]
}
Request Parameter | Type | Required | Description |
---|---|---|---|
marketCode | STRING | NO | Market code |
Response Field | Type | Description |
---|---|---|
marketCode | STRING | Market code |
tier | STRING | Leverage tier |
leverage | STRING | Market leverage |
positionFloor | STRING | The lower position limit |
positionCap | STRING | The upper position limit |
initialMargin | STRING | Initial margin |
maintenanceMargin | STRING | Maintenance margin |