Trading Operations
Create and cancel orders via WebSocket
Overview
The WebSocket API allows you to create and cancel orders directly through the WebSocket connection. This provides lower latency compared to REST API calls and enables you to receive immediate order updates through the orders channel.
Authentication Required: You must authenticate before performing trading operations. See Authentication for details.
Create Order
Create a new order using the create_order operation.
Request Format
{
"op": "create_order",
"request_id": "my-request-123",
"data": {
"type": "limit",
"side": "buy",
"product_id": "BTC-VND",
"price": "3100000000",
"size": "0.1",
"time_in_force": "GTC",
"post_only": false,
"client_order_id": "my-order-456"
}
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
op | string | Yes | Must be "create_order" |
request_id | string | No | Unique identifier for this request (for tracking responses) |
data | object | Yes | Order parameters |
Order Data Fields
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Order type: "limit" or "market" |
side | string | Yes | Order side: "buy" or "sell" |
product_id | string | Yes | Trading pair (e.g., "BTC-VND") |
price | string | For limit | Order price (required for limit orders) |
size | string | Conditional | Order size in base currency |
quote_size | string | Conditional | Order size in quote currency (for market buy) |
exact_quote_size | string | Conditional | Exact quote size (for market buy) |
time_in_force | string | No | "GTC", "IOC", or "FOK" (default: "GTC") |
post_only | boolean | No | Post-only flag (default: false) |
stp | string | No | Self-trade prevention: "DC", "CO", "CN", "CB" |
client_order_id | string | No | Your custom order identifier |
stop_trigger_price | string | No | Stop order trigger price |
expired_at | integer | No | Order expiration timestamp (nanoseconds) |
scheduled_at | integer | No | Scheduled order activation time (nanoseconds) |
wait | boolean | No | Wait for order confirmation (default: false) |
Time In Force Options
- GTC (Good-Till-Cancelled) - Remains active until filled or cancelled
- IOC (Immediate-Or-Cancel) - Fills immediately or cancels unfilled portion
- FOK (Fill-Or-Kill) - Fills completely or cancels entirely
Self-Trade Prevention (STP)
- DC (Decrease and Cancel) - Reduce size of both orders
- CO (Cancel Oldest) - Cancel the older order
- CN (Cancel Newest) - Cancel the newer order
- CB (Cancel Both) - Cancel both orders
Response
Success
If the order is successfully submitted, you'll receive updates through the orders channel:
{
"channel": "orders",
"type": "update",
"data": [{
"id": "0x01234...",
"client_order_id": "my-order-456",
"status": "pending",
...
}]
}Error
If there's an error creating the order:
{
"channel": "orders",
"request_id": "my-request-123",
"type": "error",
"message": "insufficient balance",
"code": 400,
"data": {
"type": "limit",
"side": "buy",
"product_id": "BTC-VND",
"price": "3100000000",
"size": "0.1"
}
}Example: Create Limit Order
// Create a limit buy order
ws.send(JSON.stringify({
op: 'create_order',
request_id: `req_${Date.now()}`,
data: {
type: 'limit',
side: 'buy',
product_id: 'BTC-VND',
price: '3100000000',
size: '0.1',
time_in_force: 'GTC',
post_only: true,
client_order_id: `order_${Date.now()}`
}
}));Example: Create Market Order
// Market buy using quote size
ws.send(JSON.stringify({
op: 'create_order',
request_id: `req_${Date.now()}`,
data: {
type: 'market',
side: 'buy',
product_id: 'BTC-VND',
quote_size: '100000000', // 100M VND
time_in_force: 'IOC'
}
}));
// Market sell using size
ws.send(JSON.stringify({
op: 'create_order',
request_id: `req_${Date.now()}`,
data: {
type: 'market',
side: 'sell',
product_id: 'BTC-VND',
size: '0.05', // 0.05 BTC
time_in_force: 'IOC'
}
}));Cancel Order
Cancel an existing order using the cancel_order operation.
Request Format
{
"op": "cancel_order",
"request_id": "cancel-req-123",
"data": {
"order_id": "0x01234..."
}
}Or cancel by client order ID:
{
"op": "cancel_order",
"request_id": "cancel-req-123",
"data": {
"client_order_id": "my-order-456"
}
}Parameters
| Field | Type | Required | Description |
|---|---|---|---|
op | string | Yes | Must be "cancel_order" |
request_id | string | No | Unique identifier for this request |
data | object | Yes | Cancel parameters |
Cancel Data Fields
| Field | Type | Required | Description |
|---|---|---|---|
order_id | string | Conditional | Order ID to cancel |
client_order_id | string | Conditional | Client order ID to cancel |
You must provide either order_id or client_order_id, but not both.
Response
Success
If the cancel request is successful, you'll receive an update through the orders channel:
{
"channel": "orders",
"type": "update",
"data": [{
"id": "0x01234...",
"status": "cancelled",
"cancel_requested_at": "1698765440000000000",
...
}]
}Error
If there's an error cancelling the order:
{
"channel": "orders",
"request_id": "cancel-req-123",
"type": "error",
"message": "order not found",
"code": 404,
"data": {
"order_id": "0x01234..."
}
}Example: Cancel by Order ID
ws.send(JSON.stringify({
op: 'cancel_order',
request_id: `cancel_${Date.now()}`,
data: {
order_id: '0x01234567890abcdef'
}
}));Example: Cancel by Client Order ID
ws.send(JSON.stringify({
op: 'cancel_order',
request_id: `cancel_${Date.now()}`,
data: {
client_order_id: 'my-order-456'
}
}));Complete Trading Example
const WebSocket = require('ws');
const crypto = require('crypto');
class WebSocketTrader {
constructor(apiKey, apiSecret) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.ws = null;
this.authenticated = false;
this.orderCallbacks = new Map();
}
async connect() {
this.ws = new WebSocket('wss://ws.dev.mbhq.net/ws');
this.ws.on('open', () => {
console.log('Connected to WebSocket');
this.authenticate();
});
this.ws.on('message', (data) => {
this.handleMessage(JSON.parse(data));
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
this.ws.on('close', () => {
console.log('WebSocket closed');
this.authenticated = false;
});
}
authenticate() {
const timestamp = Math.floor(Date.now() / 1000);
const message = `${this.apiKey},${timestamp}`;
const signature = crypto
.createHmac('sha256', this.apiSecret)
.update(message)
.digest('hex');
this.ws.send(JSON.stringify({
op: 'auth',
data: {
key: this.apiKey,
timestamp: timestamp,
signature: signature
}
}));
}
handleMessage(message) {
// Handle authentication
if (message.channel === 'auth' && message.type === 'authenticated') {
console.log('Authenticated successfully');
this.authenticated = true;
this.subscribeToOrders();
return;
}
// Handle order updates
if (message.channel === 'orders') {
if (message.type === 'error') {
console.error('Order error:', message.message);
const callback = this.orderCallbacks.get(message.request_id);
if (callback) {
callback(new Error(message.message), null);
this.orderCallbacks.delete(message.request_id);
}
} else if (message.type === 'update') {
message.data.forEach(order => {
console.log(`Order ${order.id}: ${order.status}`);
if (order.client_order_id) {
const callback = this.orderCallbacks.get(order.client_order_id);
if (callback) {
callback(null, order);
if (order.status === 'filled' || order.status === 'cancelled') {
this.orderCallbacks.delete(order.client_order_id);
}
}
}
});
}
}
}
subscribeToOrders() {
this.ws.send(JSON.stringify({
op: 'sub',
channel: 'orders'
}));
}
createOrder(orderData, callback) {
if (!this.authenticated) {
return callback(new Error('Not authenticated'), null);
}
const requestId = `req_${Date.now()}_${Math.random()}`;
const clientOrderId = orderData.client_order_id || requestId;
this.orderCallbacks.set(clientOrderId, callback);
this.ws.send(JSON.stringify({
op: 'create_order',
request_id: requestId,
data: {
...orderData,
client_order_id: clientOrderId
}
}));
// Timeout after 30 seconds
setTimeout(() => {
if (this.orderCallbacks.has(clientOrderId)) {
this.orderCallbacks.delete(clientOrderId);
callback(new Error('Order timeout'), null);
}
}, 30000);
}
cancelOrder(orderIdOrClientId, callback) {
if (!this.authenticated) {
return callback(new Error('Not authenticated'), null);
}
const requestId = `cancel_${Date.now()}_${Math.random()}`;
const isOrderId = orderIdOrClientId.startsWith('0x');
this.ws.send(JSON.stringify({
op: 'cancel_order',
request_id: requestId,
data: isOrderId
? { order_id: orderIdOrClientId }
: { client_order_id: orderIdOrClientId }
}));
// Set up callback for cancel confirmation
this.orderCallbacks.set(orderIdOrClientId, callback);
}
}
// Usage
const trader = new WebSocketTrader('your_api_key', 'your_api_secret');
trader.connect();
// Wait for authentication, then create an order
setTimeout(() => {
trader.createOrder({
type: 'limit',
side: 'buy',
product_id: 'BTC-VND',
price: '3100000000',
size: '0.1',
post_only: true
}, (error, order) => {
if (error) {
console.error('Order creation failed:', error);
} else {
console.log('Order created:', order);
// Cancel the order after 5 seconds
setTimeout(() => {
trader.cancelOrder(order.id, (err, cancelledOrder) => {
if (err) {
console.error('Cancel failed:', err);
} else {
console.log('Order cancelled:', cancelledOrder);
}
});
}, 5000);
}
});
}, 2000);Error Codes
Common error codes returned by trading operations:
| Code | Description |
|---|---|
| 400 | Bad request (invalid parameters) |
| 401 | Unauthorized (authentication required or failed) |
| 403 | Forbidden (read-only API key, trading not allowed) |
| 404 | Not found (order not found) |
| 409 | Conflict (insufficient balance, position limit) |
| 500 | Internal server error |
Common Error Messages
Create Order Errors
"insufficient balance"- Not enough funds for the order"invalid product"- Product ID not supported"invalid price"- Price is invalid or out of range"invalid size"- Size is invalid or below minimum"post only would match"- Post-only order would match immediately"read-only api key"- API key doesn't have write permissions
Cancel Order Errors
"order not found"- Order ID or client order ID not found"order already done"- Order is already filled or cancelled"missing order id, client order id"- Neither ID provided
Best Practices
- Use Request IDs - Track requests with unique
request_idvalues - Use Client Order IDs - Track your orders with
client_order_id - Subscribe to Orders - Always subscribe to orders channel to receive updates
- Handle Errors - Implement proper error handling for all operations
- Validate Before Sending - Check balances and parameters before creating orders
- Implement Timeouts - Set timeouts for order operations
- Use Post-Only for Makers - Set
post_only: truefor maker orders - Check Read-Only Keys - Ensure API key has write permissions
Advantages Over REST API
- Lower Latency - No need to establish new HTTP connections
- Immediate Updates - Receive order updates through the same connection
- Connection Reuse - Single WebSocket for both trading and data feeds
- Reduced Overhead - Less protocol overhead compared to HTTP
- Bidirectional - Send and receive data simultaneously
Related Documentation
- Authentication - How to authenticate
- Orders Channel - Receive order updates
- Portfolio Channel - Monitor balances
- REST API Orders - Alternative REST API for orders