> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qfex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 订阅订单响应

订阅 **`order_responses`** 频道以接收订单响应。消息**实时**推送。

## 1. 身份认证

连接 `wss://trade.qfex.com?api_key=YOUR_API_KEY` 后立即发送：

```json theme={null}
{ "type": "auth", "params": { "hmac": { "public_key": "qfex_pub_xxxxx", "nonce": "c0ffee...", "unix_ts": 1760545414, "signature": "5f2e..." } } }
```

## 2. 订阅

```json theme={null}
{ "type": "subscribe", "params": { "channels": ["order_responses"] } }
```

## 示例代码

```python python theme={null}
# Python (websocket-client)
# pip install websocket-client
import json, websocket

API_KEY = "YOUR_API_KEY"

def send(ws, obj): ws.send(json.dumps(obj))

def on_open(ws):
    # 1) Authenticate
    # https://docs.qfex.com/websocket/channels/trade/authenticate#how-it-works
    send(ws, {"type": "auth", "params": { "hmac": { "public_key": "qfex_pub_xxxxx", "nonce": "c0ffee...", "unix_ts": 1760545414, "signature": "5f2e..." }}})
    # 2) Subscribe to order responses
    send(ws, {"type": "subscribe", "params": {"channels": ["order_responses"]}})

ws = websocket.WebSocketApp(
    "wss://trade.qfex.com?api_key=YOUR_API_KEY",
    on_open=on_open,
    on_message=lambda _, m: print("Message:", m),
    on_error=lambda _, e: print("Error:", e),
    on_close=lambda *_: print("Closed"),
)
ws.run_forever()
```

```javascript node theme={null}
// Node.js (ws)
// npm i ws
import WebSocket from "ws";

const API_KEY = "YOUR_API_KEY";
const ws = new WebSocket("wss://trade.qfex.com?api_key=YOUR_API_KEY");

ws.on("open", () => {
  ws.send(JSON.stringify({ type: "auth", params: { api_key: API_KEY } }));
  ws.send(
    JSON.stringify({
      type: "subscribe",
      params: { channels: ["order_responses"] },
    })
  );
});

ws.on("message", (msg) => console.log("Message:", msg.toString()));
ws.on("error", (e) => console.error("WS error:", e));
```

```go go theme={null}
// Go (gorilla/websocket)
// go get github.com/gorilla/websocket
package main

import (
	"log"
	"github.com/gorilla/websocket"
)

func mustWriteJSON(c *websocket.Conn, v any) {
	if err := c.WriteJSON(v); err != nil { log.Fatal("write:", err) }
}

func main() {
	c, _, err := websocket.DefaultDialer.Dial("wss://trade.qfex.com?api_key=YOUR_API_KEY", nil)
	if err != nil { log.Fatal("dial:", err) }
	defer c.Close()

	// 1) Authenticate
	mustWriteJSON(c, map[string]any{
		"type": "auth",
		"params": map[string]any{"hmac": map[string]any{"public_key": "qfex_pub_xxxxx", "nonce": "c0ffee...", "unix_ts": 1760545414, "signature": "5f2e..."}}
	})

	// 2) Subscribe to order responses
	mustWriteJSON(c, map[string]any{
		"type": "subscribe",
		"params": map[string]any{"channels": []string{"order_responses"}},
	})

	// Read messages (optional)
	for {
		_, data, err := c.ReadMessage()
		if err != nil { log.Fatal("read:", err) }
		log.Println(string(data))
	}
}
```

```java java theme={null}
// Java (OkHttp WebSocket)
// Gradle: implementation("com.squareup.okhttp3:okhttp:4.12.0")
import java.util.concurrent.TimeUnit;
import okhttp3.*;

public class BalancesSub {
  public static void main(String[] args) {
    OkHttpClient client = new OkHttpClient.Builder()
        .pingInterval(20, TimeUnit.SECONDS).build();

    Request req = new Request.Builder().url("wss://trade.qfex.com?api_key=YOUR_API_KEY").build();

    WebSocketListener li = new WebSocketListener() {
      @Override public void onOpen(WebSocket ws, Response r) {
        ws.send("{\"type\": \"auth\", \"params\": { \"hmac\": { \"public_key\": \"qfex_pub_xxxxx\", \"nonce\": \"c0ffee...\..\", \"unix_ts\": 1760545414, \"signature\": \"5f2e...\" }}}");
        ws.send("{\"type\":\"subscribe\",\"params\":{\"channels\":[\"order_responses\"]}}");
      }
      @Override public void onMessage(WebSocket ws, String text) {
        System.out.println(text);
      }
      @Override public void onFailure(WebSocket ws, Throwable t, Response r) {
        System.err.println("WS error: " + t.getMessage());
      }
    };

    client.newWebSocket(req, li);
    try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException ignored) {}
  }
}
```

## 响应示例

```json theme={null}
{ "subscribed": "order_responses" }
```

```json theme={null}
{
  "order_response": {
    "order_id": "5b309929-206f-40ec-804d-cbe46e81afc1",
    "symbol": "AAPL-USD",
    "status": "ACK",
    "quantity": 1.0,
    "price": 200.0,
    "take_profit": 0.0,
    "stop_loss": 0.0,
    "side": "BUY",
    "type": "LIMIT",
    "time_in_force": "GTC",
    "user_id": "0020ce8e-eaee-480e-8d7f-b9241d756ee5",
    "client_order_id": "",
    "quantity_remaining": 1.0,
    "trade_id": null
  }
}
```

> [!NOTE]
>
> 订单响应为**实时**数据。

## 自动转换为只减仓

在[软清算及订单保证金释放](https://docs.qfex.com/cn/qfex/liquidation-margin-call.md#order-margin-release-and-soft-liquidation)期间，QFEX 可在没有客户端请求的情况下，将挂单转换为只减仓订单。`order_responses` 订阅者会对每个新转换订单收到一条 `MODIFIED` 更新。已经是只减仓的订单不会收到额外转换更新。

以下示例仅展示相关字段，省略其他标准订单响应字段：

```json theme={null}
{
  "order_response": {
    "order_id": "5b309929-206f-40ec-804d-cbe46e81afc1",
    "client_order_id": "maker-sell-001",
    "symbol": "AAPL-USD",
    "status": "MODIFIED",
    "order_origin": "SET_REDUCE_ONLY_ALL",
    "reduce_only": true,
    "side": "SELL",
    "type": "LIMIT",
    "time_in_force": "GTC",
    "quantity": 8.0,
    "quantity_remaining": 8.0,
    "price": 200.0,
    "update_time": 1792756800.123,
    "trade_id": null
  }
}
```

| 字段 | 本次更新中的含义 |
| - | - |
| `status` | `MODIFIED` 表示现有订单已改变，不是成交或撤单。 |
| `order_origin` | `SET_REDUCE_ONLY_ALL` 标识交易所发起的转换。 |
| `reduce_only` | `true` 表示之后的任何成交必须减少当前持仓。 |
| `order_id`、`client_order_id` | 此次转换不改变这些值。 |
| `price`、`quantity`、`quantity_remaining` | 此次转换不改变这些值，包括之前部分成交后的剩余数量。 |
| `update_time` | 订单更新时间戳，以 Unix 秒表示。 |

转换保留价格／时间优先级。与客户端请求的[改单](https://docs.qfex.com/cn/websocket/channels/trade/modify_order.md)不同，它不会替换订单 ID。

### 生命周期处理

1. 依赖实时订单状态之前，先订阅频道。处理账户的主动推送；不要仅因 `MODIFIED` 不对应待处理请求就丢弃它。
2. 更新本地订单的 `reduce_only` 标志，并保留转换事件用于诊断。不要假定该值一直等于 `add_order` 中发送的值，也不要假定保证金恢复后会自动变回 `false`。
3. 继续处理成交和撤单。转换后的订单，只能成交执行时仍可减少的持仓数量。若会增加或反转持仓，不能成交的数量会以 `status: "CANCELLED"` 取消。这可能在委托价本来可以撮合时发生，包括其他订单或清算已经减少或平掉持仓之后。应结合较早的转换更新诊断撤单；单独的 `CANCELLED` 不能说明原因。
4. 修改已转换订单时，发送 `reduce_only: true`。发送旧的 `false` 会以 `CANNOT_MODIFY_NO_SUCH_ORDER` 拒绝；客户端改单不能关闭只减仓。
5. 重连后，重新认证并订阅，再使用[获取用户订单](https://docs.qfex.com/cn/websocket/channels/trade/get_user_orders.md)或[获取单笔订单](https://docs.qfex.com/cn/websocket/channels/trade/get_order.md)核对当前委托，并读取每笔返回订单的当前 `reduce_only`。实时更新不会重放错过的事件，查询也不包含已完全成交或完全取消的订单。

例如，面对 5 单位多头，一笔已转换的 8 单位卖单最多成交 5 单位，随后剩余部分取消。如果尝试撮合时持仓已经为零，订单可能在没有成交的情况下收到 `CANCELLED`。最初的订单确认不保证其始终有资格执行原始数量。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.