websocket 流式传输 交易订单更新


对于从事加密货币行业的任何人来说,使用 REST api 从交易所查询实时数据并不总是最佳做法,原因有很多:

  • 效率低下:每个查询都需要时间,并且会显着影响性能,尤其是对于高频策略。
  • 交易所施加的限制很容易被打破,例如Binance的硬限制为每分钟 1200 个请求权重。
  • 您只能检索有限数量的历史数据,这也限制了回测的可能性。

因此,本文将介绍一种使用Websocket的替代方法,与 REST 相比,它具有更高的效率,并让您有机会从交易所流式传输实时数据并存储它以供将来分析或回测。

那么下面,就让我们一起来使用websocket来推送我们的账户信息


1.获取Binance API和Secret

如果您熟悉这些步骤,则可以跳过此会话。只需确保您将其设置为预期目的:现货和保证金交易或期货交易或两者兼而有之。

  1. 登录您的币安账户。
  2. 转到您的个人资料,然后在 API 管理页面下,输入新的 API 密钥名称以“创建”。
  3. Binance 会从您的电子邮件中索取代码,并从您的身份验证应用程序中索取代码。输入代码并单击“提交”。
  4. 现在已创建新的 API 密钥。请注意,密钥仅在这一步对您可见,之后将被隐藏。因此,请记住妥善保存它以备将来参考。
  5. 编辑 API 密钥权限。
  6. 通过从您的身份验证应用程序输入代码来保存更改。

2. 流式订单更新

与用于市场数据流的端点和通道不同,要流式传输订单和帐户更新,您需要创建一个具有唯一监听密钥的用户数据流,每次启动新连接时都需要生成它。

2.1. 生成监听键

Spot 和 Futures 的基本 API 端点不同:

  • 币安现货和保证金:https 😕/api.binance.com
  • 币安合约: https 😕/fapi.binance.com

在这里,我们使用币安的golang版本的的SDk

  • 安装
go get github.com/adshao/go-binance/v2
  • 设置

API 服务的初始化客户端。从您的币安账户获取 APIKey/SecretKey。

var (
    apiKey = "your api key"
    secretKey = "your secret key"
)
client := binance.NewClient(apiKey, secretKey)
futuresClient := binance.NewFuturesClient(apiKey, secretKey)    // USDT-M Futures
deliveryClient := binance.NewDeliveryClient(apiKey, secretKey)  // Coin-M Futures

一个服务实例代表一个 REST API 端点,由 client.NewClient 函数初始化。

只需以链式方式调用 API。最后调用 Do() 发送 HTTP 请求。

  • 获取用户流监听key
func GetListenKey(t *testing.T) {
	client := binance.NewClient(apiKey, secretKey)

	listenKey, err := client.NewStartUserStreamService().Do(context.Background())
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(listenKey)
}
  • curl获取listenKey
# curl -H "X-MBX-APIKEY: MY-APIKEY" -X 'POST' https://api.binance.com/api/v3/userDataStream
{"listenKey":"listenKey"}

2.2. Websocket 端点

Spot 和 Futures 的基本 websocket 端点:

  • 币安现货和保证金:wss://stream.binance.com:9443
  • 币安期货:wss://fstream.binance.com或wss://fstream-auth.binance.com

2.3. 流媒体连接

使用listen_key生成的和符合流媒体需求的 websocket 端点,详细代码如下:

func TestWsUserDataServe(t *testing.T) {
	wsHandler := func(event *binance.WsUserDataEvent) {
		fmt.Println(event)
	}
	errHandler := func(err error) {
		fmt.Println(err)
	}
	doneC, _, err := binance.WsUserDataServe(listenKey, wsHandler, errHandler)
	if err != nil {
		fmt.Println(err)
		return
	}
	<-doneC
}

将第 8 行替换listenKey为第 1 步中生成的 API 密钥。

2.4. 消息处理

2.4.1 订单更新

收到的消息将采用以下格式。对于事件类型"executionReport"(订单更新):

{
  "e": "executionReport",        // 事件类型
  "E": 1499405658658,            // 事件时间
  "s": "ETHBTC",                 // 交易对
  "c": "mUvoqJxFIILMdfAW5iGSOW", // clientOrderId
  "S": "BUY",                    // 订单方向
  "o": "LIMIT",                  // 订单类型
  "f": "GTC",                    // 有效方式
  "q": "1.00000000",             // 订单原始数量
  "p": "0.10264410",             // 订单原始价格
  "P": "0.00000000",             // 止盈止损单触发价格
  "d": 4,                        // 追踪止损(Trailing Delta) 只有在追踪止损订单中才会推送.
  "F": "0.00000000",             // 冰山订单数量
  "g": -1,                       // OCO订单 OrderListId
  "C": "",                       // 原始订单自定义ID(原始订单,指撤单操作的对象。撤单本身被视为另一个订单)
  "x": "NEW",                    // 本次事件的具体执行类型
  "X": "NEW",                    // 订单的当前状态
  "r": "NONE",                   // 订单被拒绝的原因
  "i": 4293153,                  // orderId
  "l": "0.00000000",             // 订单末次成交量
  "z": "0.00000000",             // 订单累计已成交量
  "L": "0.00000000",             // 订单末次成交价格
  "n": "0",                      // 手续费数量
  "N": null,                     // 手续费资产类别
  "T": 1499405658657,            // 成交时间
  "t": -1,                       // 成交ID
  "I": 8641984,                  // 请忽略
  "w": true,                     // 订单是否在订单簿上?
  "m": false,                    // 该成交是作为挂单成交吗?
  "M": false,                    // 请忽略
  "O": 1499405658657,            // 订单创建时间
  "Z": "0.00000000",             // 订单累计已成交金额
  "Y": "0.00000000",              // 订单末次成交金额
  "Q": "0.00000000"              // Quote Order Qty
  "j": 1,                        // Strategy ID; 下单时填上字段才会返回
  "J": 1000000                   // Strategy Type; 下单时填上字段才会返回
}

可能的执行类型:

  • NEW 新订单
  • CANCELED 订单被取消
  • REPLACED (保留字段,当前未使用)
  • REJECTED 新订单被拒绝 (这信息只会在撤消挂单再下单中发生,下新订单被拒绝但撤消挂单请求成功。)
  • TRADE 订单有新成交
  • EXPIRED 订单失效(根据订单的Time In Force参数)

如果订单是OCO(二选一订单),则除了显示"executionReport"事件外,还将显示一个名为"ListStatus"的事件。

{
  "e": "listStatus",                // 事件类型
  "E": 1564035303637,               // 事件时间
  "s": "ETHBTC",                    // 交易对
  "g": 2,                           // OrderListId
  "c": "OCO",                       // Contingency Type
  "l": "EXEC_STARTED",              // List Status Type
  "L": "EXECUTING",                 // List Order Status
  "r": "NONE",                      // List 被拒绝的原因
  "C": "F4QN4G8DlFATFlIUQ0cjdD",    // List Client Order ID
  "T": 1564035303625,               // 成交时间
  "O": [                           
    {
      "s": "ETHBTC",                // 交易对
      "i": 17,                      // orderId
      "c": "AJYsMjErWJesZvqlJCTUgL" // clientOrderId
    },
    {
      "s": "ETHBTC",
      "i": 18,
      "c": "bfYPSQdLoqAJeNrOr9adzq"
    }
  ]
}

2.4.2 余额更新

"balanceUpdate"从账户存款或取款的事件类型:

当下列情形发生时更新:

  • 账户发生充值或提取
  • 交易账户之间发生划转(例如 现货向杠杆账户划转)
{
  "e": "balanceUpdate",         // 事件类型
  "E": 1573200697110,           // 事件时间
  "a": "ABC",                   // 资产
  "d": "100.00000000",          // 余额变更
  "T": 1573200697068            // 具体时间
}

2.4.3 账户更新

每当帐户余额发生更改时,都会发送一个事件outboundAccountPosition,其中包含可能由生成余额变动的事件而变动的资产。

{
  "e": "outboundAccountPosition", // 事件类型
  "E": 1564034571105,             // 事件时间
  "u": 1564034571073,             // 账户末次更新时间戳
  "B": [                          // 余额
    {
      "a": "ETH",                 // 资产名称
      "f": "10000.000000",        // 可用余额
      "l": "0.000000"             // 冻结余额
    }
  ]
}

2.5 事件处理

按照不同的事件类型,处理消息,代码如下


func TestWsUserDataServe(t *testing.T) {
	wsHandler := func(event *binance.WsUserDataEvent) {
		fmt.Println(event)
		//账户更新事件
		if event.Event == "outboundAccountPosition" {
			AccountUpdate := event.AccountUpdate
			fmt.Println(AccountUpdate)
		}
		//余额更新事件
		if event.Event == "balanceUpdate" {
			balanceUpdate := event.BalanceUpdate
			fmt.Println(balanceUpdate)
			//现货交易订单更新事件
			if event.Event == "executionReport" {
				orderUpdate := event.OrderUpdate
				fmt.Println(orderUpdate)
			}
			//OCO交易订单更新事件
			if event.Event == "outboundAccountPosition" {
				OCOUpdate := event.OCOUpdate
				fmt.Println(OCOUpdate)
			}
		}
	}
	errHandler := func(err error) {
		fmt.Println(err)
	}
	doneC, _, err := binance.WsUserDataServe(listenKey, wsHandler, errHandler)
	if err != nil {
		fmt.Println(err)
		return
	}
	<-doneC
}

2.6 补充说明

  • 用户数据流在创建后60 分钟内有效listenKey。为防止超时并使其保持活动状态,建议大约每 30 分钟发送一次 ping。

  • 在 a PUT上执行 a listenKey将其有效期延长 60 分钟。

  • 在 a DELETE上执行 a listenKey将关闭流并使listenKey.
    stream.binance.com的单次连接 仅在24 小时内有效;确实希望在 24 小时标记断开连接。

具体代码如下

  • 延长60分钟有效期
//定时30分钟给listenKey续期
time.Sleep(1800 * time.Second)
func TestPutListenKey(t *testing.T) {
	client := binance.NewClient(apiKey, secretKey)


	err := client.NewKeepaliveUserStreamService().ListenKey(listenKey).Do(context.Background())
	if err != nil {
		fmt.Println(err)
	}
}
  • 立即关闭当前数据流
func TestCloseListenKey(t *testing.T) {
	client := binance.NewClient(apiKey, secretKey)

	err := client.NewCloseUserStreamService().ListenKey(listenKey).Do(context.Background())
	if err != nil {
		fmt.Println(err)
	}
}

参考链接:

  • Binance Websocket账户信息推送
    官方文档:https://binance-docs.github.io/apidocs/spot/cn/#websocket-2
  • Binance golang sdk:https://github.com/adshao/go-binance
  • Binance websocket账户接口:https://github.com/binance/binance-spot-api-docs/blob/master/user-data-stream_CN.md
  • 使用 Python 流式传输 Binance 订单更新:https://medium.com/coinmonks/streaming-binance-order-updates-using-python-90520e0d307f#ef2e
<think>好的,用户想了解如何实现实时订单数据更新的方法,包括技术方案和常用工具。首先,我需要回忆一下实时数据处理的相关知识。根据用户提供的引用,特别是引用[3]和[4],里面提到了实时数据传输的工具和场景,比如OGG、Canal、Kafka等,这些应该用得上。 首先,实时订单更新的核心需求应该是数据的高效捕获、传输和处理。所以需要分步骤来考虑:数据捕获、数据传输、数据存储与处理,以及最终的接口提供。 数据捕获方面,常见的工具有数据库的日志解析工具,比如Canal或者OGG。这些工具可以实时读取数据库的变更日志,比如MySQL的binlog,然后将这些变更事件发布到消息队列中。这一步是关键,因为要保证数据实时性,必须及时捕获变更。 接下来是数据传输,消息队列如Kafka或RocketMQ可以在这里发挥作用。它们能高效处理高吞吐量的数据流,并确保数据的有序和可靠传输。Kafka的持久化和分区特性非常适合这种场景,确保数据不会丢失,并且可以并行处理。 然后是数据存储和处理部分。实时数仓如Flink或Storm可以用来处理流数据。Flink的流处理能力很强,可以进行窗口计算、状态管理等,适合实时更新订单状态。同时,可能需要将处理后的数据写入OLAP数据库,比如ClickHouse或Doris,方便快速查询和分析。另外,缓存层如Redis可以用来存储最新的订单状态,加速前端访问。 接口层的话,如果需要对外提供实时数据,可以用WebSocket或者HTTP长轮询。但根据引用[4],实时数据接口多数在交易系统中完成,所以可能需要考虑API网关和鉴权机制,确保安全。 技术选型方面,用户提到的工具如Canal、Kafka、Flink、Redis都是常用组合。另外,数据一致性也很重要,可能需要事务性消息或者幂等处理来避免重复或丢失数据。监控和报警也是不可少的,比如Prometheus和Grafana来监控系统健康状态。 用户可能没有明确提到的问题包括数据一致性如何保证,系统的高可用性,以及如何处理高并发下的性能问题。这些也需要在回答中提及,确保方案的全面性。同时,根据引用[2],维度建模可能用于分析型数据库,但实时订单更新更侧重流处理,不过存储部分可能需要结合数仓的结构。 总结来说,整个流程应该是:数据库变更日志被捕获工具读取,发布到消息队列,流处理引擎消费并处理数据,更新存储和缓存,最后通过接口提供实时数据。需要确保每个环节的可靠性和性能,选择合适的工具和架构。</think>### 实时订单数据更新的实现方法 实时订单数据更新的核心目标是在订单状态变更时,快速完成数据同步、处理与展示,通常涉及以下技术方案和工具: --- #### 一、技术方案 1. **数据变更捕获(CDC)** - **原理**:通过监听数据库日志(如MySQL的binlog、Oracle的归档日志)实时捕获订单表的增删改操作。 - **工具**: - **Canal**:阿里开源的MySQL binlog解析工具,支持将数据变更推送到消息队列[^3]。 - **Debezium**:基于Kafka Connect的分布式CDC工具,支持多种数据库。 - **示例流程**: $$ \text{订单数据库} \xrightarrow{\text{binlog}} \text{Canal} \xrightarrow{\text{Kafka}} \text{下游处理系统} $$ 2. **实时消息队列** - **作用**:缓冲和分发订单变更事件,确保高吞吐量和低延迟。 - **工具**: - **Kafka**:高吞吐分布式消息队列,适合日志类数据传输。 - **RocketMQ**:阿里开源的消息队列,支持事务消息和顺序消息。 3. **流式数据处理** - **场景**:对订单数据进行实时计算(如统计、状态更新)。 - **工具**: - **Apache Flink**:支持事件时间和状态管理,适用于复杂流处理。 - **Spark Streaming**:微批处理框架,适合高吞吐场景。 4. **数据存储与查询** - **OLAP数据库**:如ClickHouse、Doris,支持实时聚合查询。 - **缓存层**:使用Redis存储实时订单状态,加速前端访问[^4]。 --- #### 二、典型架构 ```plaintext 订单数据库 → CDC工具(Canal/Debezium) → Kafka → Flink流处理 → Redis/OLAP数据库 → 前端展示 ``` --- #### 三、关键优化点 1. **数据一致性** - 使用事务性消息(如RocketMQ事务消息)确保CDC到消息队列的数据不丢失。 2. **高可用性** - Kafka和Flink均支持分布式部署,避免单点故障。 3. **低延迟** - 通过内存计算(如Flink状态后端)、异步写入缓存(Redis)缩短响应时间。 --- #### 四、应用场景示例 - **电商订单跟踪**:用户下单后实时更新库存、物流状态[^1]。 - **金融交易系统**:实时计算订单手续费并推送至交易终端[^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰哥的技术杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值