第3章 CHAPTER 3 交易所API介绍 3.1API功能简述 API是Application Programming Interface的缩写,中文名称为应用程序编程接口。交易所一般会提供平台基本信息、市场行情、用户的账户信息、现货下单、合约下单等功能的API,有了这些API就可以编写代码,用程序执行策略,实现自动化交易。 API连接方式有普通HTTP请求和WebSocket两种。WebSocket是在HTTP的基础上升级的一种新的协议(Protocol),它实现了全双工通信,一般用来获取行情数据,比普通HTTP方式速度更快,延迟更低,连接开销更小。 API按权限分为两大类: 公共API和私有API。公共API包括交易所相关信息、费率信息、行情数据等,私有API包括用户钱包信息、订单信息、下单操作等功能。私有API访问需要带上API Key和Secret Key,同时要求程序运行的云服务器IP地址是受信任的(参考第2章API设置界面)。 API按功能可分为现货、杠杆、U本位合约、币本位合约、期权等几类,本书只介绍现货和U本位合约中的账户查询、行情数据、交易等最基本的几个API,读者完全掌握后,可以去交易所官网查看API文档,了解更多的功能。 编写交易策略程序,可以使用交易所官方提供的软件开发套件(Software Development Kit,SDK),币安官方SDK支持的编程语言有Python、Node.js、Java、PHP、Go、C#。欧易没有官方的SDK,只推荐了两个第三方的SDK,这两个第三方的SDK支持编程语言Python、Java等主流编程语言。本书将教大家使用Python语言编写交易程序,即使没学过Python也不必担心,第4章将带大家掌握Python基础语法知识。 3.2币安API 使用币安API前要先注册API Key和Secret Key。 安装币安Python SDK,在命令行下输入的指令如下: pip install binance-connector 本节将用这个SDK编写代码来演示API的使用方法。 以/api/*网址开头的HTTP接口(行情接口和交易接口)访问频率限制: 每分钟6000次。 以/sapi/*网址开头的HTTP接口(钱包和合约相关接口)访问频率限制: 每分钟180000次。 WebSocket接口访问频率限制: 每IP地址每分钟最多可以发送60次连接请求。 币安的现货API与合约API是两个不同的模块,下面将分别介绍它们的使用方法。 3.2.1币安现货API 币安的现货API需要在程序的开头导入SDK的Spot包,代码如下: #导入现货模块 from binance.spot import Spot 3.2.2查询现货钱包余额API 第1步,创建现货的客户端对象client,输入apikey和secret参数,为base_url参数设置一个默认值https://api1.binance.com/,这是币安API的一个基础网址,代码如下: #现货账户 #apikey key = "你的API Key" #密钥 secret = "你的Secret" #现货账户 client = Spot(key, secret, base_url="https://api1.binance.com/") 第2步,调用SDK现货模块中的user_asset方法获取账户余额,recvWindow参数是时间空窗值,单位是毫秒,用来判断请求的最小延迟,如果延迟超过此值,则会被交易所认为无效,代码如下: response = client.user_asset(asset="USDT", recvWindow=5000) print(response) 图31币安账户返回结果 从币安交易所的API返回账户余额信息是一个数组包裹的字典数据,数组中只有一个元素,可以用response[0]方法获取第1个元素,第1个元素中的资产名称用response[0]["asset"]方法获取,第1个元素中的余额用response[0]["free"]方法获取,如图31所示。 查询用户的交易所账户余额的完整代码如下: #文件名:binanceSpotBalance.py from binance.spot import Spot #账户余额 def getBalance(symbol): key = "你的key" secret = "你的secret" print(key, secret) #现货账户 client = Spot(key, secret, base_url="https://api1.binance.com/") response = client.user_asset(asset=symbol, recvWindow=5000) #返回的结果集是列表数据 if len(response) > 0: return response[0]["free"] return 0 #主函数 def main(): SpotBalance = getBalance("USDT") print("现货账户USDT的余额是", SpotBalance) if __name__ == "__main__": main() #运行结果:现货账户USDT的余额是425.31667294 3.2.3现货深度信息API 交易所深度信息,也称为市场深度或交易深度,深度信息就是交易页面中订单簿里的数据,展示了买方和卖方愿意以不同价格进行交易的数量,币安的深度API最多可以返回5000条数据,通过观察深度数据,交易者可以了解市场的供需情况,判断价格的走势、潜在的支撑和阻力水平。深度信息参数见表31。 表31深度信息参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT limit Int 否 默认值为100,最大值为5000,可选值为[5,10,20,50,100,500,1000,5000] 查询深度信息,需要调用SDK现货模块中的depth方法,代码如下: Symbol = "BTCUSDT"#交易对 Limit = 2#数量 arr = client.depth(symbol,limit) print(arr) 运行后返回的结果是字典数据(如果不明白字典数据的含义,则可以参考第4章内容),bids是买入价数组的键值名称,数组内容的每个元素包含两个子元素,第1个数值是买入价格,第2个数值是挂单数量,按价格从高到低的顺序排列,价格高的在数组的最前面。asks是卖出价数组的键值,数组内容的每个元素也包含两个子元素,第1个数值是卖出价格,第2个数值是挂单数量,按价格从低到高的顺序排列,价格最低的在数组的最前面。第1个买入价是arr["bids"][0][0],最后一个卖出价是arr["asks"][-1][0],如图32所示。 图32返回的深度数据 查询深度信息的完整代码如下: #文件名:binanceSpotDepth.py #导入现货模块 from binance.spot import Spot #现货账户 client = Spot() #深度信息函数 def getDepth(symbol,limit): arr = client.depth(symbol, limit) print("买入价", arr["bids"][0][0])#取第1个买入价 print("卖出价", arr["asks"][-1][0])#取最后一个卖出价 #主函数 if __name__ == "__main__": #调用深度信息的函数,传入交易对和数量 getDepth("BTCUSDT",2) #运行结果如下 #买入价 61505.58 #卖出价 61509.26 3.2.4现货有限深度信息WebSocket API 有限深度信息的WebSocket方式的接口效率更高,以每秒或者每100ms推送数据,延迟更低,HTTP方式每次都要和交易所建立连接,而WebSocket只需建立一次长连接,就能不断地接收交易所的推送数据,所以连接开销更小。其参数与HTTP接口参数名称略有不同,见表32。 表32有限深度信息参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT level Int 否 默认值为100,最大值为5000,可选值为[5,10,20,50,100,500,1000,5000] speed Int 是 接收数据频率,单位为毫秒 查询有限深度信息,需要加载SDK现货模块的websocket_stream包和json包,使用SpotWebsocketStreamClient方法创建一个客户端变量,并指定一个名称为message_handler的函数接收推送数据,然后使用partial_book_depth方法发出请求,得到的推送数据是JSON格式,还需要导入json包进行解析,代码如下: #文件名:binanceSpotWsDepth.py From binance.websocket.spot.websocket_stream import SpotWebsocketStreamClient import json 创建客户端变量client,并指定接收推送数据的函数名message_handler,代码如下: client = SpotWebsocketStreamClient(on_message=message_handler) 然后使用partial_book_depth方法发出请求,代码如下: #发送请求,参数:交易对=btcusdt,数量=5,频率=1000ms client.partial_book_depth(symbol="btcusdt", level=5, speed=1000) 定义一个message_handler函数,用于接收推送数据,代码如下: #接收推送数据 def message_handler(_, msg): data = json.loads(msg)#将收到的JSON格式数据转换为字典格式数据 print(data) 查询有限深度信息的完整代码如下: #文件名:binanceSpotWsDepth.py From binance.websocket.spot.websocket_stream import SpotWebsocketStreamClient import json #接收深度推送数据 def message_handler(_, msg): #把交易所推送过来的JSON数据转换为字典数据 data = json.loads(msg) print("第1个买入价", data["bids"][0][0]) print("最后1个卖出价", data["asks"][-1][0]) #主函数 def main(): client = SpotWebsocketStreamClient(on_message=message_handler) #参数:交易对=btcusdt,数量=5,频率=1000ms client.partial_book_depth(symbol="btcusdt", level=5, speed=1000) #运行结果如下 #买入价 61505.58 #卖出价 61509.26 if __name__ == "__main__": main() 3.2.5现货K线数据 API K线图是一种以图形化方式呈现给定时间范围内资产价格变化的金融图表。它由许多烛台图案组成,每个烛台图案表示一段相同的时间。烛台图案可以代表任何虚拟的时间范围,短到数秒,长到数天。K线图的历史可以追溯到17世纪,最早由日本米商发明,后来经过改进和优化,成为现代技术分析的重要工具之一。 K线图直接反映了资产的价格走势,帮助交易者了解市场的供需情况和价格变化。 K线图显示了多空之间的博弈,通过观察不同形态的烛台图案,交易者可以判断市场的力量变化。 通过分析K线图,交易者可以预测价格的趋势,制定合适的交易策略。 查询K线数据需要使用SDK现货模块中的klines方法,参数见表33。 表33K线数据参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT interval Enum 否 K线间隔,可选值[1s,1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,1M] limit Int 是 接收数据数量 查询有限深度信息,需要加载SDK现货模块的klines方法,代码如下: #文件名:binanceSpotKline.py from binance.spot import Spot client = Spot()#创建现货账户变量 symbol = "BTCUSDT"#交易对 interval = "1m"#更新频率为1min limit = 10#K线数量 #获得K线数据 arr = client.klines(symbol, interval, limit=limit) print(arr) K线数据返回的结果是二维数组数据,运行结果如图33所示。 图33返回的K线数据 查询K线数据的完整代码如下: #文件名:binanceSpotKline.py from binance.spot import Spot #现货账户 client = Spot() def getKlines(symbol, interval, limit): arr = client.klines(symbol, interval, limit=limit) lastId = len(arr) - 1#最后的K线数据,也就是最新数据 print("开盘价", arr[lastId][1])#开盘价 print("最高价", arr[lastId][2])#最高价 print("最低价", arr[lastId][3])#最低价 print("收盘价", arr[lastId][4])#收盘价 #运行结果 #开盘价 61740.30 #最高价 61740.30 #最低价 61659.72 #收盘价 61666.68 #主函数 def main(): symbol = "BTCUSDT"#交易对 interval = "1m"#更新频率为1min limit = 10#K线数量 getKlines(symbol, interval, limit) if __name__ == "__main__": main() 3.2.6现货K线数据WebSocket API 逐秒推送的K线数据需要使用SDK现货模块中的websocket_stream 包中的kline方法,参数见表34。 表34K线数据参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT interval Enum 否 K线间隔,可选值为[1s,1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,1M] limit Int 是 接收数量 查询有限深度信息,需要加载SDK现货模块的kline方法,得到的数据格式是JSON,还需要导入json包,代码如下: #文件名:binanceSpotKline.py from binance.websocket.spot.websocket_stream import SpotWebsocketStreamClient import json def message_handler(_, msg): data = json.loads(msg) print(data) client = SpotWebsocketStreamClient(on_message=message_handler) #订阅btcusdt最新K线数据,参数:交易对=btcusdt,频率=1s client.kline(symbol="btcusdt", interval="1m") 运行结果如图34所示。 查询K线数据的完整代码如下: #文件名:binanceSpotKline.py from binance.websocket.spot.websocket_stream import SpotWebsocketStreamClient import json #接收和处理K线推送数据 def message_handler(_, msg): data = json.loads(msg) print("交易对", data["k"]["s"]) print("最后1笔成交价", data["k"]["c"]) #主函数 def main(): client = SpotWebsocketStreamClient(on_message=message_handler) #订阅btcusdt最新K线数据,参数:交易对=btcusdt,频率=1s client.kline(symbol="btcusdt", interval="1m") #运行结果 #交易对 BTCUSDT #最后1笔成交价 61350.44000000 if __name__ == "__main__": main() 图34返回的K线数据 3.2.7现货下单API 下单操作需要用真实的数字币,如果代码有误,则会损失数字币和手续费。幸运的是,币安提供了测试网,可以使用免费的测试网来测试代码。代码无误后,把测试网址换成正式的网址。 为了更好地管理正式环境和测试环境的API Key,避免每写一个程序都以明文方式写出API Key,新建一个config.ini文件来保存实盘、现货测试网、合约测试网的API Key和Secret。注意: 文件内容不要包含单引号和双引号,文件的内容如下: #文件名:config.ini [keys] apiKey=aaaaaaaa#正式环境的apiKey apiSecret=bbbbbbbbbb#正式环境的apiSecret testKey=ccccccc#测试环境现货apiKey testSecret=ddddddddd#测试环境现货apiSecret testFuturesKey=eeeeee#测试环境合约apiKey testFuturesSecret=fffff#测试环境合约apiSecret 然后我们写一个从配置文件config.ini中读取API Key和Secret的函数,文件的内容如下: #文件名:env.py import os import pathlib from configparser import ConfigParser #获取apikey和secret def getApiKey(k,s): config = ConfigParser() #获取config.ini文件的路径 config_file_path = os.path.join( pathlib.Path(__file__).parent.resolve(), ".", "config.ini" ) #读取config.ini文件内容,放入字典中 config.read(config_file_path) return config["keys"][k], config["keys"][s] 现货下单主要有限价单和市价单两种类型,限价单是达到指定价格才执行的订单,市价单是以当前市场价格立即执行的订单,参数见表35。 表35下单参数 参 数 名 称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT side String 是 方向: 买入(BUY)或卖出(SELL) type String 是 类型: 限价(Limit)或市价(Market) price String 否 价格,类型是限价时必须带此参数 quantity String 是 基准币数量 quoteOrderQty String 否 计价币数量,当type=Market时才可使用 timeInForce String 否 有效方式,当类型为限价时必须带此参数 (1) 限价下单,首先获取正式环境的API Key。注意: 正式环境API的基础URL是https://api.binance.com。代码如下: key, secret = getApiKey("apiKey", "apiSecret") client = Spot(key, secret, base_url="https://api.binance.com/") 如果用测试环境的API Key,则基础URL是https://testnet.binance.vision,代码如下: key, secret = getApiKey("testKey", "testSecret") client = Spot(key, secret, base_url="https://testnet.binance.vision") 希望以380的价格购买0.05个BNB,下限价单的方法用new_order函数。注意: 下单价格不能距离市场价太近,否则会被平台拒绝执行,必填参数为symbol、side、type、timeInForce、quantity、price,代码如下: #文件名:binanceSpotKline.py #参数表都是必选项 params = { "symbol": "BNBUSDT", //交易对 "side": "BUY", //方向是买 "type": "LIMIT", //类型是限价 "timeInForce": "GTC", "quantity": "0.05", //BNB数量 "price": "350", //价格 } #下单并返回结果 response = client.new_order(**params) print(response) 程序运行后,交易所API返回的是一个字典数据,其中最重要的3个参数是订单ID、订单状态和成交数量。 订单ID用response["orderId"]方法获得,获得订单ID后可以用订单查询API进一步查询订单的详情。 订单状态用response["status"]方法获得,如果订单状态为NEW,则表示未成交,如果订单状态为FILLED,则表示完全成交,如果订单状态为PARTIALLY_FILLED,则表示部分成交。 成交数量用response["executedQty"]方法获得,返回结果如图35所示。 图35限价单返回数据 下限价单的完整代码如下: #文件名:binanceSpotLimitOrd.py from binance.spot import Spot from env import getApiKey #下一个限价单 def limitOrder(symbol, side, qty, price): #正式环境 #key, secret = getApiKey("apiKey", "apiSecret") #client = Spot(key, secret, base_url="https://api.binance.com") #测试网 testKey, testSecret = getApiKey("testKey", "testSecret") client = Spot(testKey, testSecret, base_url="https://testnet.binance.vision") #参数字典 params = { "symbol": symbol, //交易对 "side": side, //方向 "type": "LIMIT", //类型 "timeInForce": "GTC", "quantity": qty, //数量 "price": price, //价格 } #下单 response = client.new_order(**params) print(response) #主函数 def main(): #限价方式以380为价格,购买0.05个BNB limitOrder("BNBUSDT", "BUY", "0.05", "380") if __name__ == "__main__": main() (2) 以基准币市价下单,方法也是用new_order函数,必填参数为symbol、side、type、quantity,代码如下: #文件名:binanceSpotOrder.py from binance.spot import Spot from env import getApiKey #下一个市价单 def marketOrder(symbol, side, qty): #测试网 key, secret = getApiKey("testKey", "testSecret") client = Spot(key, secret, base_url="https://testnet.binance.vision") params = { "symbol": symbol, //交易对 "side": side, //方向 "type": "MARKET", //类型 "quantity": qty //数量 } response = client.new_order(**params) print(response) print("成交价",response["fills"][0]["price"]) print("成交数量",response["fills"][0]["qty"]) def main(): #以市价购买0.1个BNB,订单立刻生效 marketOrder("BNBUSDT", "BUY", "0.1") if __name__ == "__main__": main() 程序运行后,交易所返回一个字典数据,和限价单不同的是fills字段里多了一个数组数据,里面保存的是交易结果数据,成交价格用response["fills"][0][ "price"]方法获得,成交数量用response["fills"][0][ "qty"]方法获得,结果如图36所示。 图36市价单返回数据 (3) 以计价币市价下单,方法也是用new_order函数,必填参数为symbol、side、type、quoteOrderQty,代码如下: #文件名:binanceSpotOrder.py from binance.spot import Spot from env import getApiKey  #用180个USDT,下一个市价单 def marketOrder(symbol, side, qty): #key, secret = getApiKey("apiKey","apiSecret") #client = Spot(key, secret, base_url="https://api.binance.com") #测试网 testKey, testSecret = getApiKey("testKey", "testSecret") client = Spot(testKey, testSecret, base_url="https://testnet.binance.vision") params = {"symbol": symbol, "side": side, "type": "MARKET", "quoteOrderQty": qty} response = client.new_order(**params) print(response) def main(): #用180个USDT以市价换成尽可能多的BNB下单 marketOrder("BNBUSDT", "BUY", "180") if __name__ == "__main__": main() 运行结果如图37所示。 图37市价单返回数据 3.2.8现货查询订单信息API 下单操作后平台并不能马上返回结果,想要查询订单状态,需要调用查询订单信息的API。参数见表36。 表36查询订单参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT orderId String 否 订单ID 代码如下: ord = client.get_order("BTCUSDT", orderId="9783716") print(ord) 程序运行后,交易所会返回一个字典数据,里面的重要参数有: price,表示下单价格; origQty,表示下单数量; status,表示订单状态; executedQty,表示成交数量; type,表示订单类型; side,表示买卖方向。 运行结果如图38所示。 图38查询订单返回数据 3.2.9现货取消订单API 取消订单API只能取消限价订单,并且订单的状态是NEW(表示订单未成交),否则无法取消。参数见表37。 表37取消订单参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT orderId String 否 订单ID 代码如下: ord = client.cancel_order("BTCUSDT", orderId="9783716") print(ord) 3.2.10应用示例: 现货API综合应用 本节讲解了现货API中的查询行情、下单,以及查询订单、取消订单的基本操作,现在综合应用这些API写两个示例程序。 第1个示例程序的主要功能是下一个限价买单: 以60000的价格购买0.001个BTC,下单成功后会返回一个orderId,然后用这个orderId查询订单的状态。如果状态等于NEW,则调用取消订单API,取消成功后返回结果字典中的状态值为CANCELED,表示取消操作成功。 Python语法部分,用到了函数定义和函数调用,我们把下单操作定义为newLimitOrd函数,将查询订单操作定义为getOrder函数,将取消订单操作定义为cancelOrder函数。主函数为main,这是程序运行的起点。函数命名使用了驼峰规则,也就是函数名首字母小写,后面的每个单词首字母大写,这样的命名规则让人一看函数名就知其意。 第1个示例程序的完整代码如下: 文件名:binanceSpotGetOrd.py from binance.spot import Spot from env import getApiKey #测试网 key, secret = getApiKey("testKey", "testSecret") client = Spot(key, secret, base_url="https://testnet.binance.vision") #限价单 def newLimitOrd(symbol, side, price, qty): params = { "symbol": symbol, "side": side, "type": "LIMIT", "timeInForce": "GTC", "quantity": qty, "price": price, } response = client.new_order(**params)#返回的订单信息 ordId = response["orderId"]#订单ID return ordId #查询订单 def getOrder(symbol, ordId): ord = client.get_order(symbol, orderId=ordId) print(ord) print("订单状态", ord["status"]) return ord #取消订单 def cancelOrder(symbol, ordId): response = client.cancel_order(symbol, orderId=ordId) #取消成功,状态是CANCELED print("取消订单结果", response["status"]) def main(): #限价单,以60000的价格购买0.001个BTC symbol = "BTCUSDT" orderId = newLimitOrd(symbol, "BUY", "60000", "0.001") ord = getOrder(symbol, orderId) print("限价单状态", ord["status"]) if ord["status"] == "NEW": cancelOrder(symbol, orderId) if __name__ == "__main__": main() 第2个示例程序更加接近实际应用,先获取行情数据,获得当前市价,确定一个有机会盈利的下单价格,下单价格=市价-市价×0.005%,以此价格下一个限价买单。 然后在循环结构中不断地查询订单状态,如果订单状态为NEW,则表示未成交,程序就休眠10s。如果订单状态为FILLED,则表示完全成交。如果状态为PARTIALLY_FILLED,则表示部分成交,立即下一个限价卖单,卖单价格为买入价+利润,卖出单的下单数量从查询订单接口返回数据中取executedQty字段值,表示实际成交数量,调用API部分的代码都使用try/except包裹起来,防止因API出错而导致程序中断运行。 精度计算也是一个非常重要的知识点,下单数量精度和价格精度(也就是小数位数),必须满足交易对的精度要求,否则下单会失败。例如下单数量qty为“2.001”,小数点后有3位小数,精度就是3,如何计算精度呢?可以使用Python的split函数对下单数量字符进行分隔,分隔符是“.”,arr=qty.split(".")得到的就是一个数组,数组名为arr,arr数组的第1个元素是整数部分“2”,arr数组的第2个元素是小数点后的部分“001”,然后用Python的len函数计算“001”的长度,len(arr[1])就等于3。注意: arr[1]指第2个元素,arr[0]指第1个元素。程序代码如下: 文件名:binanceSpotGetOrd.py #测试币安买和卖流程 import json import time from binance.spot import Spot from binance.websocket.spot.websocket_stream import SpotWebsocketStreamClient from env import getApiKey #获取API Key和Secret testKey, testSecret = getApiKey("testKey", "testSecret") client = Spot(testKey, testSecret, base_url="https://testnet.binance.vision") #限价单 def newLimitOrd(symbol, side, price, qty): params = { "symbol": symbol, "side": side, "type": "LIMIT", "timeInForce": "GTC", "quantity": qty, "price": price, } print(params) response = client.new_order(**params)#返回的订单信息 ordId = response["orderId"]#订单ID return ordId #查询订单 def getOrder(symbol, ordId): try: ord = client.get_order(symbol, orderId=ordId) print(ord) print("订单状态", ord["status"]) return ord except Exception as e: print(f"查询订单错误: {e}") return {} #取消订单 def cancelOrder(symbol, ordId): try: response = client.cancel_order(symbol, orderId=ordId) #取消成功,状态是CANCELED print("取消订单结果", response["status"]) except Exception as e: print(f"取消订单错误: {e}") #取消所有订单 def cancelAllOrder(symbol): try: response = client.cancel_open_orders(symbol) #取消成功,状态是CANCELED print("取消所有订单", response["status"]) except Exception as e: print(f"取消所有订单错误: {e}") #计算下单数量的精度,也就是小数位 def setqtyDecimalNum(qty): global qtyDecimalNum arr = qty.split(".") if len(arr) > 0: qtyDecimalNum = len(arr[1]) else: qtyDecimalNum = 0 print(f"将小数位长度设置为{qtyDecimalNum}") #计算价格的精度,也就是小数位 def getPriceDecimalNum(price): arr = price.split(".") if len(arr) > 0: num = arr[1].rstrip("0") numLen = len(num) return numLen else: return 0 def getKlines(symbol, interval, limit): arr = client.klines(symbol, interval, limit=limit) #print(arr) #返回数据格式[[开盘时间,开盘价,最高价,最低价,收盘价,成交量],[开盘时间,开盘价,最高 #价,最低价,收盘价,成交量]...] #[[1499040000000', "61740.30","61740.30","61659.72","61666.68","148976.11427815"]] lastId = len(arr) - 1#最后的K线数据,也就是最新数据 print("开盘价", arr[lastId][1])#开盘价 print("最高价", arr[lastId][2])#最高价 print("最低价", arr[lastId][3])#最低价 print("收盘价", arr[lastId][4])#收盘价 return arr[lastId][4] def main(): symbol = "BTCUSDT"#交易对 interval = "1m"#将频率更新为1min limit = 10#K线数量 cancelAllOrder(symbol)#先取消所有订单 close = getKlines(symbol, interval, limit) print("市价", close) closeF = float(close) #按低于市价0.03%价格购买 closeF -= closeF * 0.0003 #价格精度 priceDecimalNum = getPriceDecimalNum(close) closeF = round(closeF, priceDecimalNum) qty = 0.001 qtyDecimalNum = 3 ordId = newLimitOrd(symbol, "BUY", f"{closeF}", qty) print(f"挂买单:订单id={ordId},交易对={symbol},方向=BUY,价格={closeF},数量={qty}") while True: try: #检查订单是否成交 ord = getOrder(symbol, ordId) #如果状态是NEW,则表示未成交 if ord["status"] == "NEW": time.sleep(10) elif ord["status"] == "FILLED" or ord["status"] == "PARTIALLY_FILLED": #命中 print(f"订单id={ordId}命中") #获得订单执行价格 orderPrice = float(ord["price"]) #价格精度 priceDecimalNum = getPriceDecimalNum(ord["price"]) #计算盈利价格 profitPrice = orderPrice + orderPrice * 0.0005 #保持正确的小数位 profitPrice = round(profitPrice, priceDecimalNum) #开始卖出 #获取订单的执行数量 executedQty = float(ord["executedQty"]) print("执行数量", executedQty) #cummulativeQuoteQty = float(ord["cummulativeQuoteQty"]) #保持精度 executedQty = round(executedQty, qtyDecimalNum) print("执行数量,保持精度", executedQty) try: ordId2 = newLimitOrd( symbol, "SELL", f"{profitPrice}", f"{executedQty}" ) print( f"挂单信息:订单id={ordId2},交易对={symbol},方向=SELL,价格={profitPrice},数量={executedQty}" ) print("=======完成挂卖出单,等待成交获利=======") print("======退出======") break except Exception as e: print("=======挂卖出单错误=======") print(e) except Exception as e: print(f"查询订单错误: {e}") time.sleep(10) if __name__ == "__main__": print("测试币安买和卖的流程") main() 3.2.11币安合约API 现在的加密货币交易所,使用合约交易的用户比现货交易的用户多很多,从API限制频率来看: 现货API交易每分钟被限制为6000次,而合约API交易每分钟被限制为180000次。手续费也是合约交易远低于现货交易,也就是平台鼓励用户使用合约交易。 合约API需要在程序的开头导入SDK的UMFutures包,代码如下: #导入合约模块 from binance.um_futures import UMFutures 新建一个合约对象变量,代码如下: client = UMFutures() 3.2.12合约深度信息API 获取合约深度信息的方法是depth,需要的参数见表38。 表38合约深度信息参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT limit Int 否 默认值为500,最大值为1000,可选值为[5,10,20,50,100,500,1000] 使用depth方法传入交易对和数量两个参数,代码如下: symbol = "BTCUSDT" params = { "limit": 10, } arr = client.depth(symbol , **params) print(arr) 返回的深度数据如图39所示。 图39合约深度数据 完整代码如下: 文件名:binanceFuturesDepth.py from binance.um_futures import UMFutures client = UMFutures() symbol = "BTCUSDT" params = { "limit": 10, } arr = client.depth(symbol, **params) print("买入价", arr["bids"][0][0])#取第1个买入价 print("卖出价", arr["asks"][-1][0])#取最后一个卖出价 #返回结果 #买入价 61867.80 #卖出价 61868.40 3.2.13合约有限深度信息WebSocket API 有限深度信息的WebSocket方式的接口效率更高,以每秒或者每100ms推送数据,延迟更低,与HTTP接口参数名称略有不同,参数见表39。 表39合约有限深度信息参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT level Int 否 默认值为100,最大值为5000,可选值为[5,10,20,50,100,500,1000,5000] speed Int 是 更新频率,单位为毫秒,可选值为[100,250,500] 查询有限深度信息,需要加载SDK合约模块的websocket_client包和json包,代码如下: #文件名:binanceSpotWsDepth.py from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient import json 使用UMFuturesWebsocketStreamClient方法创建一个合约对象变量,并指定一个名称为message_handler的函数接收推送数据,代码如下: client = SpotWebsocketStreamClient(on_message=message_handler) 然后使用partial_book_depth方法发出请求,得到的推送数据是JSON格式,还需要导入json包进行解析,代码如下: client.partial_book_depth( symbol="BTCUSDT",#交易对 level=20,#数量 speed=100,#更新速度,单位为毫秒 ) 接收推送数据,并用json包进行解析,代码如下: #接收推送数据 def message_handler(_, msg): data = json.loads(msg) print(data) WebSocket接口返回的字段一般用简写,用e代替event、用s代替symbol、用b代替bid、用a代替ask,这样做的目的是最大限度地减少数据传输量,返回的深度数据如图310所示。 完整代码如下: #文件名:binanceFuturesWsDepth.py import time from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient import json #接收推送数据 def message_handler(_, msg): data = json.loads(msg) #print(data) print("第1个买入价", data["b"][0][0]) print("最后一个卖出价", data["a"][-1][0]) #运行结果 #第1个买入价 62066.30 #最后一个卖出价 62070.80 client = UMFuturesWebsocketClient(on_message=message_handler) client.partial_book_depth( symbol="BTCUSDT",#交易对 level=20,#数量 speed=100,#更新速度,单位为毫秒 ) #time.sleep(10)#休眠10s #client.stop()#停止接收推送 print(data) 图310合约WebSocket深度数据 3.2.14合约K线API 获取合约K线数据的方法是klines,需要的参数见表310。 表310合约K线API参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT interval String 否 时间间隔,可选值为[1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w] limit Int 否 默认值为500,最大值为1000,可选值为[5,10,20,50,100,500,1000] kline使用方法,完整代码如下: 文件名:binanceFuturesKline.py from binance.um_futures import UMFutures client = UMFutures() symbol = "BTCUSDT" params = { "interval": "1m", //间隔频率1min "limit": 10 } arr = client.klines(symbol, **params) print(arr) lastId = len(arr) - 1#最后的K线数据,也就是最新数据 print("开盘价", arr[lastId][1])#开盘价 print("最高价", arr[lastId][2])#最高价 print("最低价", arr[lastId][3])#最低价 print("收盘价", arr[lastId][4])#收盘价 #运行结果 #开盘价 62059.60 #最高价 62065.30 #最低价 62059.60 #收盘价 62065.20 返回的K线数据如图311所示。 图311合约K线数据 3.2.15合约K线数据WebSocket API 合约K线数据需要的参数见表311。 表311合约K线数据参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT level Int 否 默认值为100,最大值为5000,可选值为[5,10,20,50,100,500,1000,5000] speed Int 是 更新频率,单位为毫秒,可选值为[100,250,500] 创建一个合约对象,然后用kline方法获得K线推送数据,并指定一个名称为message_handler的函数接收推送数据,代码如下: client = UMFuturesWebsocketClient(on_message=message_handler) client.kline( symbol="BTCUSDT", interval="1m", ) message_handler函数接收到推送数据后解析JSON数据,代码如下: def message_handler(_, msg): data = json.loads(msg) #print(data) 推送的K线数据如图312所示。 图312推送的合约K线数据 完整代码如下: #文件名:binanceFuturesWsKline.py import time import json from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient #接收推送数据 def message_handler(_, msg): data = json.loads(msg) print("交易对", data["k"]["s"]) print("第1笔成交价", data["k"]["o"]) print("最后一笔成交价", data["k"]["c"]) print("最高成交价", data["k"]["h"]) print("最低成交价", data["k"]["l"]) print("成交量", data["k"]["v"]) #运行结果 #交易对 BTCUSDT #第1笔成交价 62040.00 #最后一笔成交价 62060.70 #最高成交价 62060.70 #最低成交价 62039.90 #成交量 41.368 client = UMFuturesWebsocketClient(on_message=message_handler) client.kline( symbol="BTCUSDT", interval="1m", ) #time.sleep(10)#休眠10s #client.stop()#停止接收推送 3.2.16合约查询余额API 合约账户包括多种资产,数据格式如图313所示。 对于U本位合约,只要查资产是USDT的余额即可,示例代码如下: 文件名:binanceFuturesBalance.py from binance.um_futures import UMFutures from env import getApiKey key, secret = getApiKey("apiKey", "apiSecret") #合约账户 client = UMFutures(key=key, secret=secret) #合约账户余额 def getBalance(asset): arr = client.balance(ecvWindow=5000) print(arr) for item in arr: if item["asset"] == asset: return float(item["availableBalance"]) return 0.00000000 def main(): balance = getBalance("USDT") print("合约账户USDT余额", balance) if __name__ == "__main__": main() 图313合约账户数据格式 3.2.17合约设置逐仓全仓API 设置逐仓全仓API需要的参数见表312。 表312设置逐仓全仓API需要的参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT marginType String 是 可选值为[CROSSED全仓,ISOLATED逐仓] 示例代码如下: from binance.um_futures import UMFutures from env import getApiKey key, secret = getApiKey("apiKey", "apiSecret") client = UMFutures(key=key, secret=secret) #marginType 值选项:[CROSSED全仓,ISOLATED逐仓] response = client.change_margin_type( symbol="BTCUSDT", marginType="CROSSED", recvWindow=6000 ) print(response) 3.2.18合约设置杠杆倍数API 设置杠杆倍数API需要的参数见表313。 表313设置杠杆倍数API需要的参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT leverage Int 是 杠杆倍数 示例代码如下: from binance.um_futures import UMFutures from env import getApiKey key, secret = getApiKey("apiKey", "apiSecret") client = UMFutures(key=key, secret=secret) response = client.change_leverage( symbol="BTCUSDT", leverage=10, recvWindow=6000 ) print(response) #返回结果 #leverage 杠杆倍数 #maxNotionalValue 最大名义价值 #{'symbol': 'BTCUSDT', 'leverage': 10, 'maxNotionalValue': '150000000'} 3.2.19合约下单API 合约下单API的方法是 new_order,这是一个难点,很多新手在这里容易写错,涉及如何开仓、平仓、新建委托订单等操作,交易所文档只是罗列了参数,没有讲解如何使用,参数比较多,见表314,我们将以示例代码的方式详细解读。 表314设置杠杆倍数API需要的参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT side ENUM 是 买卖方向为SELL或BUY positionSide ENUM 是 持仓方向,默认双持仓模式下的选择项为[做多LONG,做空SHOAT] type ENUM 是 订单类型为[LIMIT,MARKET,TAKE_PROFIT_MARKET,TRAILING_STOP_MARKET,TAKE_PROFIT] reduceOnly String 否 非双开模式选True或False,双开模式不选此参数 quantity DECIMAL 否 下单数量,使用closePosition时不填此参数 price DECIMAL 否 委托价格 stopPrice DECIMAL 否 触发价格 activatePrice DECIMAL 否 追踪止损激活价格,仅当type= TRAILING_STOP_MARKET时使用 callbackRate DECIMAL 否 追踪止损回调比例,取值范围为[0.1,10],仅当type= TRAILING_STOP_MARKET时使用 开仓做多,side一定要是BUY,同时positionSide一定要是LONG,示例代码如下: #开仓做多 response = client.new_order( symbol="BTCUSDT",#交易对 side="BUY",#交易方向 type="MARKET",#类型 quantity=0.01,#数量 positionSide="LONG",#持仓方向 ) print("开仓做多") print(response) 开仓做空,side一定要是SELL,同时positionSide一定要是SHORT,示例代码如下: #开仓做多 response = client.new_order( symbol="BTCUSDT",#交易对 side="SELL",#交易方向 type="MARKET",#类型 quantity=0.01,#数量 positionSide="SHORT",#持仓方向 ) print("开仓做空") print(response) 返回的订单数据如图314所示。 图314订单数据 合仓: 如果第2次执行new_order,交易方向、持仓方向都和之前仓位一致,则将进行合仓操作,也就是合并在同一个仓位,两次的quantity值累加,代码如下: #第1次开仓做多 Response1 = client.new_order( symbol="BTCUSDT",#交易对 side="SELL",#交易方向 type="MARKET",#类型 quantity=0.01,#数量 positionSide="SHORT",#持仓方向 ) print("开仓做空1") print(response1) #第2次开仓做多 Response2 = client.new_order( symbol="BTCUSDT",#交易对 side="SELL",#交易方向 type="MARKET",#类型 quantity=0.01,#数量 positionSide="SHORT",#持仓方向 ) print("开仓做空2") print(response2) #此时仓位中的quantity值是0.02 平仓: 交易所没有专门的平仓函数,需要再下一个订单,买卖方向和之前仓位买卖方向相反即可,代码如下: #开仓做多 response = client.new_order( symbol="BTCUSDT",#交易对 side="BUY",#交易方向 type="MARKET",#类型 quantity=0.01,#数量 positionSide="LONG",#持仓方向 ) print("开仓做多") print(response) #平仓操作 Response2 = client.new_order( symbol="BTCUSDT",#交易对 side="SELL",#交易方向和上面的相反即可平仓 type="MARKET",#类型 quantity=0.01,#数量 positionSide="LONG",#持仓方向要和上面一致 ) print("平仓") print(response2) 设置止盈和止损要点: 买卖方向要和开仓买卖方向相反,持仓方向要一致,然后设置一个触发价格,当市场价到达这个触发价格时自动平仓,代码如下: #开仓做多 response = client.new_order( symbol="BTCUSDT",#交易对 side="BUY",#交易方向为买入 type="MARKET",#类型 quantity=0.01,#数量 positionSide="LONG",#持仓方向为做多 ) print("开仓做多") print(response) #设置止盈 response = client.new_order( symbol="BTCUSDT", side="SELL",#交易方向为卖出 type="TAKE_PROFIT_MARKET",#订单类型为止盈 timeInForce="GTC", stopPrice=64100,#止盈触发价格 quantity=qty,#数量 positionSide="LONG",#持仓方向为做多 ) print("设置止盈") print(response) #设置止损,买卖方向要和开仓买卖方向相反,持仓方向要一致 response = client.new_order( symbol="BTCUSDT", side="SELL",#交易方向为卖出 type="TRAILING_STOP_MARKET",#订单类型为止损 timeInForce="GTC", activatePrice=62500,#止损激活价格 callbackRate=1,#止损回调比例 quantity=qty,#数量 positionSide="LONG",#持仓方向为做多 ) print("设置止损") print(response) 3.2.20合约查询订单API 查询订单API需要的参数见表315。 表315查询订单API需要的参数 参数名称 类型 是否是必需的 描述 symbol String 是 交易对名称,例如BTCUSDT orderId String 是 订单ID 代码如下: symbol = "BTCUSDT" orderId = "12345" #查询单个订单 ord = client.query_order(symbol, orderId) print(ord) 3.2.21合约取消订单API 查询订单API有两个,一个是按订单ID取消订单,另一个是取消所有打开的订单,代码如下: #取消订单 response = client.cancel_order( symbol="BTCUSDT", orderId="123456", recvWindow=2000 ) print("取消订单") print(response) #取消所有打开的订单 response = client.cancel_open_orders( symbol="BTCUSDT", recvWindow=2000 ) print("取消所有打开的订单") print(response) 3.2.22应用示例: 合约API综合应用 把前面介绍的合约API结合在一起,做一个综合应用,代码如下: #文件名:binanceFuturesCancel.py from binance.um_futures import UMFutures from env import getApiKey import time #正式环境 #key, secret = getApiKey("apiKey","apiSecret") #client = UMFutures(key=key, secret=secret, base_url="https://api.binance.com") #测试网 key, secret = getApiKey("testFuturesKey", "testFuturesSecret") client = UMFutures(key=key, secret=secret, base_url="https://testnet.binancefuture.com") qty = 0.01#数量 #开仓做多 response = client.new_order( symbol="BTCUSDT",#交易对 side="BUY",#买卖方向为买入 type="MARKET",#订单类型为市价 quantity=qty,#数量 positionSide="LONG",#持仓方向为做多 #timeInForce="GTC", ) print("开仓") print(response) #设置止盈 response = client.new_order( symbol="BTCUSDT",#交易对 side="SELL",#买卖方向为卖出 type="TAKE_PROFIT_MARKET",#订单类型为市价止盈 timeInForce="GTC", stopPrice=64100,#触发价格 quantity=qty,#数量 positionSide="LONG",#持仓方向为做多 ) print("设置止盈") print(response) ordId = response["orderId"] print(f"止盈订单id{ordId}") #休眠10s time.sleep(10) #查询订单 response = client.query_order(symbol="BTCUSDT", orderId=ordId) print("查询订单结果") print(response) #订单状态 status = response["status"] #如果订单状态为NEW,则取消订单 if status == "NEW": response = client.cancel_order(symbol="BTCUSDT", orderId=ordId, recvWindow=2000) print("取消订单") print(response) #取消所有打开的订单 #response = client.cancel_open_orders( #symbol="BTCUSDT", recvWindow=2000 #) #print("取消所有打开的订单") #print(response) 3.3欧易API 和币安API不同,欧易API的现货和合约是同一套接口,欧易也有模拟盘,大部分API可以在模拟盘上进行测试,使用API前要先注册好实盘和模拟盘的API Key、Secret Key、Passphrase,然后统一写到config.ini文件中,示例如下: [keys] #币安 apiKey=u3YMZu8pdzWXoy2rugrp301nEsgOjmYgxVkeEqn2QfOriMMwquryxpmTw6WCyf9O apiSecret=olc6INfXgUbBNeZT3niNXsQ7OrcNpOD1EJvAaExD1jGcpdH5wKmt6N980OL1HUom testKey=NaaGgBf104k0j5eU7bA6rmFmIQLChaRpUloYY7s84r6C13IEIjmxJcjhcgUxOzzu testSecret=cRnSNAZYJsqp6e3cSy72BTfuXL9JypgEha0DJbDSRIUhJDIE9hv9Lm8LKNxOwQPv testFuturesKey=80fcfa736fc58faab1fe7a737afeef8ca0b3869fe853806ef9636463c1467bf3 testFuturesSecret=b9b688e52833da771a49ab2ed4df4eb4c5f9794c761438f77e8e8e986bb6b34a #欧易 okApiKey=fa2c486a-9387-483f-b074-6f3479ff9c59 okApiSecret=C0D68E91FB2AB0B6D7A1BFB16D8A30C9 okTestKey=2a076334-82ca-94a8-9971-fbf556863d44 okTestSecret=EE51E9F072DE9B6DB7A41F4EF5E2CFB5 passphrase=Abcd8888! 欧易没有官方的SDK,只推荐了两个第三方SDK: 第1个是PythonOKX,这个SDK只支持Python语言; 第2个是OpenAPISDKV5,支持Java、C#、PHP、Python等主流编程语言,但是这个SDK的Python版需要指定在3.6~3.8,对3.11版本支持不好,所以推荐大家使用PythonOKX,在命令行窗口下输入下面的指令: pip install python-okx 本节将讲解用这个SDK编写代码来演示欧易API的使用方法。 3.3.1查询钱包余额API 欧易查询账户余额API参数见表316。 表316欧易查询账户余额API参数 参数名称 类型 是否是必需的 描述 ccy String 是 币种,例如USDT 第1步,导入SDK的账户模块,代码如下: from okx import Account 第2步,导入apiKey、apiSecretKey、passphrase,代码如下: from env import getOkApiKey apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) 第3步,使用账户模块的get_account_balance方法查询余额,代码如下: result=accountAPI.get_account_balance(ccy="USDT") print(result) 返回的账户余额数据如图315所示。 查询钱包余额的完整代码如下: #文件名:okBalance.py from okx import Account from env import getOkApiKey apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) #0为实盘,1为模拟盘 accountAPI = Account.AccountAPI(apiKey, apiSecretKey, passphrase, False, flag="1") result = accountAPI.get_account_balance(ccy="USDT") print(result) print("可用余额:", result["data"][0]["details"][0]["availBal"]) 图315账户余额数据 3.3.2设置逐仓模式API 欧易设置逐仓模式API参数见表317。 表317欧易设置逐仓模式API参数 参数名称 类型 是否是必需的 描述 isoMode String 是 逐仓保证金划转模式,automatic为开仓自动划转 type String 是 业务类型,MARGIN为币币; CONTRACTS为合约 设置逐仓模式,代码如下: #文件名:okMargin.py from okx import Account from env import getOkApiKey apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) #flag:0为实盘;flag:1为模拟盘 accountAPI = Account.AccountAPI(apiKey, apiSecretKey, passphrase, False, flag="1") #isoMode:逐仓保证金划转模式;type:业务线类型(MARGIN 币币杠杆,CONTRACTS 合约) result = accountAPI.set_isolated_mode(isoMode="automatic", type="CONTRACTS") print(result) #返回结果 #{'code': '0', 'data': [{'isoMode': 'automatic'}], 'msg': ''} 3.3.3设置杠杆倍数API 欧易设置杠杆倍数API参数见表318。 表318欧易设置杠杆倍数API参数 参数名称 类型 是否是必需的 描述 instId String 是 产品ID,例如BTCUSDT lever String 是 杠杆倍数 mgnMode String 是 保证金模式,isolated为逐仓; cross为全仓 设置杠杆倍数,代码如下: #文件名:okLeverage.py from okx import Account from env import getOkApiKey apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) #flag:0为实盘;flag:1为模拟盘 accountAPI = Account.AccountAPI(apiKey, apiSecretKey, passphrase, False, flag="1") #instId为交易对,lever为杠杆倍数, mgnMode为逐仓模式 result = accountAPI.set_leverage(instId="BTC-USDT", lever="5", mgnMode="isolated") print(result) #返回结果 #{'code': '0', 'data': [{'instId': 'BTC-USDT', 'lever': '5', 'mgnMode': 'isolated', 'posSide': ''}], 'msg': ''} 3.3.4获取深度信息API 欧易的深度数据需要通过WebSocket的订阅方式获取,参数见表319。 表319欧易获取深度信息API参数 参数名称 类型 是否是必需的 描述 op String 是 操作,subscribe为订阅; unsubscribe为取消 args Array 是 频道列表,可以包含若干频道 >channel String 是 频道1,例如books: 深度数据; indexcandle: K线数据 >instType String 是 产品1类型,spot: 币币; swap: 永续合约 第1步,导入SDK的WebSocket公共数据模块,代码如下: from okx.websocket.WsPublicAsync import WsPublicAsync 第2步,导入Python的异步IO框架和json模块,代码如下: import asyncio import json 第3步,创建公共数据的WebSocket连接,如果实盘和模拟盘的URL网址不同,则需要根据实际情况进行切换,我们的代码使用模拟盘的URL,代码如下: #模拟盘的链接 url = "wss://wspap.okex.com:8443/ws/v5/public?brokerId=9999" #实盘的链接 #url = "wss://ws.okx.com:8443/ws/v5/business" ws = WsPublicAsync(url=url) await ws.start() 第4步,新建一个频道列表,然后创建一个频道名为books的深度数据频道,加入频道列表中,进行订阅操作,指定publicCallback函数来接收交易的深度数据的推送消息,代码如下: #频道列表 args = [] #第1个频道:深度信息 arg1 = {"channel": "books", "instType": "SPOT", "instId": "BTC-USDT"} #将第1个频道加到频道列表里 args.append(arg1) #订阅,指定publicCallback函数来接收推送数据 await ws.subscribe(args, publicCallback) 第5步,创建接收推送数据的函数,并处理推送过来的数据,代码如下: def publicCallback(message): msg = json.loads(message) print(msg) 接收的深度数据推送结果如图316所示。 第6步,交易所推送的数据是JSON字符串格式,需要使用json包的json.loads方法转换为字典数据,方便提取特定键值,代码如下: def publicCallback(message): msg = json.loads(message) print("交易对",arg["instId"]) data = msg.get("data") print("卖出订单",data["ask"]) print("卖出订单",data["ask"]) 图316接收的深度数据推送结果 第7步,启动异步函数的方法,代码如下: asyncio.run(main()) 第8步,main函数前面要加上async关键词,代码如下: async def main(): 第9步,在将JOSN数据转换为字典数据时,经常会出现找不到键值的错误,原因是推送数据是递增的,有时会缺少一些键值,从而导致程序出现异常错误,因此可以在代码中加入try except 方式来捕捉异常,这样程序更加健壮,代码如下: try: msg = json.loads(message) arg = msg.get("arg") #判断键值是否存在 if arg is not None and "instId" in arg: print(arg["instId"]) data = msg.get("data") if data is not None: if "ask" in data: print(data["ask"]) if "bid" in data: print(data["bid"]) except json.jsonDecodeError as e: print("JSON 解码错误:", e) except KeyError as e: print(f"键值错误: {e} - the key is not in the JSON structure") 完整的获取深度信息的代码如下: #文件名:okDepth.py import asyncio import json from okx.websocket.WsPublicAsync import WsPublicAsync def publicCallback(message): try: msg = json.loads(message) arg = msg.get("arg") if arg is not None and "instId" in arg: print("产品ID", arg["instId"]) data = msg.get("data") if data is not None: if "ask" in data: print("卖出订单", data["ask"]) if "bid" in data: print("买入订单", data["bid"]) except json.jsonDecodeError as e: print("JSON解码错误:", e) except KeyError as e: print(f"键值错误: {e} - the key is not in the JSON structure") async def main(): #模拟盘URL url = "wss://wspap.okex.com:8443/ws/v5/public?brokerId=9999" #实盘URL #url = "wss://ws.okx.com:8443/ws/v5/business" ws = WsPublicAsync(url=url) await ws.start() args = [] arg1 = {"channel": "books", "instType": "SPOT", "instId": "BTC-USDT"} args.append(arg1) await ws.subscribe(args, publicCallback) while True: await asyncio.sleep(1)  if __name__ == "__main__": asyncio.run(main()) 3.3.5获取K线数据API 欧易的K线数据获取方式和获取深度数据的方式完全一样,不同之处是推送的数据不同。订阅1个K线频道,代码如下: url = "wss://wsaws.okx.com:8443/ws/v5/business" ws = WsPublicAsync(url=url) await ws.start() args = [] #产品ID:BTC-USDT arg1 = {"channel": "index-candle1m", "instType": "SPOT", "instId": "BTC-USDT"} args.append(arg1) await ws.subscribe(args, publicCallback) K线产品名为indexcandle,后面加上时间粒度1m,表示推送时间的间隔是1min,更多的时间粒度有[1m/3m/5m/15m/30m/1h/2h/4h],m是分钟,h是小时。 推送的K线数据如图317所示。 图317推送的K线数据 完整的获取K线数据的代码如下: #文件名:okTicker.py import asyncio import json from okx.websocket.WsPublicAsync import WsPublicAsync def publicCallback(message): try: msg = json.loads(message) print("数据", msg) print("产品ID",msg["arg"]["instId"]) print("开盘价",msg["data"][0][1]) print("最高价",msg["data"][0][2]) print("最低价",msg["data"][0][3]) print("收盘价",msg["data"][0][4]) except json.jsonDecodeError as e: print("JSON解码错误:", e) except KeyError as e: print(f"键值错误: {e} - the key is not in the JSON structure") async def main(): #url = "wss://wspap.okx.com:8443/ws/v5/business?brokerId=9999" url = "wss://wsaws.okx.com:8443/ws/v5/business" ws = WsPublicAsync(url=url) await ws.start() args = [] arg1 = {"channel": "index-candle1m", "instType": "SPOT", "instId": "BTC-USDT"} args.append(arg1) arg2 = {"channel": "index-candle1m", "instType": "SPOT", "instId": "ETH-BTC"} args.append(arg2) arg3 = {"channel": "index-candle1m", "instType": "SPOT", "instId": "ETH-USDT"} args.append(arg3) await ws.subscribe(args, publicCallback) while True: await asyncio.sleep(1) if __name__ == "__main__": asyncio.run(main()) 3.3.6币币市价下单API 欧易的下单API只有一个,通过不同的参数组合,可以满足币币市价下单、币币限价下单、合约市价下单、合约限价下单等需求,下单API常用参数见表320。 表320欧易下单API常用参数 参数名称 类型 是否是必需的 描述 instId String 是 产品ID,例如BTCUSDT tdMode String 是 交易模式,isolated为逐仓,cross为全仓,cash为非保证金 ccy String 是 保证金币种 side String 是 订单方向,buy为买,sell为卖 posSide String 可选 持仓方向,long为做多,short为做空 ordType String 是 订单类型,market为市价单,limit为限价单 sz String 是 委托数量,单位是交易货币数量 px String 是 委托价格 第1步,导入SDK的交易模块,代码如下: from okx import Trade 第2步,用交易模块的place_order方法下币币市价单,币币下单的参数组合为tdMode="cash",ordType="market",不用填px价格参数。 币币下单,交易方向是买入,代码如下: result = tradeAPI.place_order( instId="BTC-USDT", #交易对 tdMode="cash",#模式为币币交易 side="buy",#买卖方向为买入 ordType="market",#订单类型为市价单 sz="20",#下单数量,单位是USDT ) print("币币市价下单结果", result) 下单后返回的结果,如果code为0,则代表下单成功,如图318所示。 图318欧易币币下市价单结果 币币下单,交易方向是卖出,代码如下: result2 = tradeAPI.place_order( instId=symbol,#交易对 tdMode="cash",#模式为币币交易 side="sell",#交易方向为卖出 ordType="market",#订单类型为市价单 sz=fillSz,#下单数量,单位为BTC ) print("币币市价卖出下单结果", result2) 注意: 如果交易方向是买入,则用USDT换BTC,下单数量为USDT,如果交易方向是卖出,则用BTC换USDT,下单数量为BTC。 欧易币币下市价单的完整代码如下: #文件名:okNewSpotMarketOrd.py from okx import Trade from okx import Account from env import getOkApiKey apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) #币币市价下单 def main(symbol, sz): accountAPI = Account.AccountAPI(apiKey, apiSecretKey, passphrase, False, flag="1") acc = accountAPI.get_account_balance(ccy="USDT") usdtBalance = float(acc["data"][0]["details"][0]["availBal"]) print("USDT余额:", usdtBalance) tradeAPI = Trade.TradeAPI( apiKey, apiSecretKey, passphrase, False, flag="1"#0为实盘,1为模拟盘 ) result = tradeAPI.place_order( instId=symbol,#交易对 tdMode="cash",#模式为币币交易 side="buy",#买卖方向为买入 ordType="market",#订单类型为市价单 sz=sz,#下单数量:20个USDT ) print("币币市价买入下单结果", result) fillPx, fillSz = getOrd(symbol, result["data"][0]["ordId"]) print("买入成交价格", fillPx, "成交数量", fillSz) result2 = tradeAPI.place_order( instId=symbol,#交易对 tdMode="cash",#模式为币币交易 side="sell",#买卖方向为卖出 ordType="market",#订单类型为市价单 sz=fillSz,#下单数量,单位为BTC ) print("币币市价卖出下单结果", result2) fillPx, fillSz = getOrd(symbol, result2["data"][0]["ordId"]) print("卖出成交价格", fillPx, "成交数量", fillSz) #卖出成交价格为69789.4,成交数量为0.00028657 acc2 = accountAPI.get_account_balance(ccy="USDT") usdtBalance2 = float(acc2["data"][0]["details"][0]["availBal"]) print("买卖后盈利:", usdtBalance2 - usdtBalance, "个USDT") #买卖后盈利: -0.02002820535835781 个USDT #获取订单信息 def getOrd(instId, orderId): tradeAPI = Trade.TradeAPI( apiKey, apiSecretKey, passphrase, False, flag="1"#0为实盘,1为模拟盘 ) result = tradeAPI.get_order(instId, orderId) print("获取订单信息", result) fillPx = 0#成交价 fillSz = 0#成交数量 for i in range(len(result["data"])): fillPx += float(result["data"][i]["fillPx"])#累计成交价 fillSz += float(result["data"][i]["fillSz"])#累计成交数量 return fillPx / len(result["data"]), fillSz if __name__ == "__main__": #下单20个USDT main("BTC-USDT", 20) 3.3.7币币限价下单API 欧易币币限价下单的参数组合为tdMode="cash",ordType="limit",px=委托价格。 欧易币币下限价单的完整代码如下: #文件名:okNewSpotLimitOrd.py from okx import Trade from env import getOkApiKey apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) #币币限价下单 def main(): tradeAPI = Trade.TradeAPI( apiKey, apiSecretKey, passphrase, False, flag="1"#0为实盘,1为模拟盘 ) result = tradeAPI.place_order( instId="BTC-USDT",#交易对 tdMode="cash",#币币交易 side="buy",#买入 ordType="limit",#限价 sz="0.01",#数量 px="60000",#委托价格 ) print("币币限价下单结果", result) if __name__ == "__main__": main() 下单后返回的结果,如果code为0,则代表下单成功,如图319所示。 图319欧易币币下限价单结果 3.3.8合约市价开仓和平仓API 欧易合约开仓下单的参数组合比较复杂,新手经常因不知如何正确地选择参数组合,而导致开仓或平仓出错,开仓的参数组合为: 做多side ="buy" 并且posSide="long"; 做空side ="sell"并且posSide="short"。 市价开仓参数: ordType="market"; sz参数的单位是张数,1张=0.001BTC; px为委托价格,不填。 如果欧易交易对后面有SWAP,则表示这是永续合约交易对,合约的市价开仓做多的代码如下: result = tradeAPI.place_order( instId="BTC-USDT-SWAP",#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="buy",#交易方向为买入 posSide="long",#持仓方向,long为做多,short为做空 ordType="market",#订单类型为市价单 sz=qty,#下单数量,单位是张数,1张是0.001个BTC ) print("市价开仓做多结果", result) 合约的市价开仓做空的代码如下: result = tradeAPI.place_order( instId="BTC-USDT-SWAP",#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="sell",#交易方向为卖出 posSide="short",#持仓方向,long为做多,short为做空 ordType="market",#订单类型为市价单 sz=qty,#下单数量,单位为张数,1张是0.001个BTC ) print("市价开仓做空结果", result) 刚看欧易API时会发现根本没有专门的平仓API,后来才知道是用下单的API实现平仓,欧易合约平仓下单的参数组合为: 平多side ="sell",posSide="long"; 平空side ="buy",posSide="short"。 合约的平多代码如下: result = tradeAPI.place_order( instId="BTC-USDT-SWAP",#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="sell",#平仓多单 posSide="long",#持仓方向,long为平多,short为平空 ordType="market",#订单类型为市价单 sz=qty,#平仓数量 ) print("市价开仓平多结果", result) 合约的平空代码如下: result = tradeAPI.place_order( instId="BTC-USDT-SWAP",#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="sell",#平仓多单 posSide="long",#持仓方向,long为平多,short为平空 ordType="market",#订单类型为市价单 sz=qty,#平仓数量 ) print("市价开仓平空结果", result) 欧易合约市价开仓平仓的完整代码如下: #文件名:okNewSwapMarketOrd.py from okx import Trade from env import getOkApiKey import time apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) tradeAPI = Trade.TradeAPI( apiKey, apiSecretKey, passphrase, False, flag="1"#0为实盘,1为模拟盘 ) #市价开仓平多 def NewOrd(qty): result = tradeAPI.place_order( instId="BTC-USDT-SWAP",#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="buy",#买卖方向为买入 posSide="long",#持仓方向,long为平多,short为平空 ordType="market",#订单类型为市价单 sz=qty,#下单数量 ) print("市价开仓结果", result) #市价平仓平多 def CloseOrd(qty): result = tradeAPI.place_order( instId="BTC-USDT-SWAP",#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="sell",#买卖方向为卖出 posSide="long",#持仓方向,long为做多,short为做空 ordType="market",#订单类型为市价单 sz=qty,#下单数量 ) print("市价开仓结果", result) #合约市价下单 def main(): qty = "1"#下单数量为张数 NewOrd(qty)#开仓 time.sleep(20)#休眠20s CloseOrd(qty)#平仓 if __name__ == "__main__": main() 3.3.9合约限价开仓API 限价开仓参数: ordType="limit",px委托价格必填。需要注意的是,委托价格不能距离市场成交价过近,否则下单会失败。 合约的限价开仓做多的代码如下: result = tradeAPI.place_order( instId=instId,#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="buy",#买卖方向为买入 posSide="long",#持仓方向,long为做多,short为做空 ordType="limit",#订单类型为市价单 px="63000",#委托价格 sz=qty,#下单数量 ) print("限价做多结果", result) 3.3.10合约止盈止损单API 欧易的合约止盈止损单,需要在开仓后才可下单,合约的止盈止损单参数见表321。 表321欧易合约的止盈止损单参数 参 数 名 称 类型 是否是必需的 描述 tpTriggerPx String 是 止盈触发价格 tpOrdPx String 是 止盈价格 tpTriggerPxType String 是 止盈触发价类型: last为最新价格,index为指数价格,mark为标记价格 slTriggerPx String 是 止损触发价格 slOrdPx String 是 止损价格 slTriggerPxType String 是 止损触发价类型: last为最新价格,index为指数价格,mark为标记价格 合约的止盈止损单的代码如下: result = tradeAPI.place_order( instId=instId, tdMode="isolated", side="buy", posSide="long", ordType="limit", sz=qty,#数量 px="63100.0", #attachAlgoOrds=arr tpTriggerPx="63110.0",#触发价格 tpOrdPx="64000.0",#止盈价格 tpTriggerPxType="last",#止盈触发价类型:last为最新价格,index为指数价格,mark为标 #记价格 slTriggerPx="63000.0",#触发价格 slOrdPx="62000.0",#止损价格 slTriggerPxType="last",#止损触发价类型:last为最新价格,index为指数价格,mark为标 #记价格 ) print("止盈单结果", result) 3.3.11查询订单信息API 下单后并不能马上得到订单的完整数据,需要通过这个查询订单API再次查询,才可以获得开仓平均价格、订单成交状态等订单详细信息,查询订单信息的参数见表322。 表322欧易查询订单信息的参数 参数名称 类型 是否是必需的 描述 instId String 是 币种,例如USDTBTCSWAP orderId String 是 订单ID 查询订单详情,代码如下: result = tradeAPI.get_order("BTC-USDT-SWAP", "689808341957931012") print("获取订单信息", result) 订单详细数据,如图320所示。 图320欧易查询订单结果 3.3.12取消订单API 取消订单API,可以取消未成交的委托订单,取消订单的参数见表323。 表323欧易取消订单的参数 参数名称 类型 是否是必需的 描述 instId String 是 币种,例如USDTBTCSWAP orderId String 是 订单ID 取消订单代码如下: result = tradeAPI.cancel_order("BTC-USDT-SWAP", "689808341957931012") print("取消订单结果", result) 3.3.13应用示例 综合应用前面介绍过的开仓、下单、查询订单、取消订单的API。 程序的基本逻辑: 下一个合约的限价单,也就是限价开仓,委托价格是63000,数量为10,买卖方向为买入,持仓方向为做多,杠杆倍数为3。开仓后查询订单状态,如果状态为live,获取状态的方法ordInfo["data"][0]["state"],则使用Python的time.sleep(10)方法,休眠10s,10s后取消订单。最后下一个止盈订单,查询订单详情如图321所示。 图321查询合约订单结果 示例代码如下: #文件名:okNewSwapLimitOrd.py from okx import Trade from env import getOkApiKey import time apiKey, apiSecretKey, passphrase = getOkApiKey( "okTestKey", "okTestSecret", "passphrase" ) tradeAPI = Trade.TradeAPI( apiKey, apiSecretKey, passphrase, False, flag="1"#0为实盘,1为模拟盘 ) #限价开仓 def newOrd(instId, qty): result = tradeAPI.place_order( instId=instId,#交易对 ccy="USDT",#保证金币种 tdMode="isolated",#模式为逐仓 side="buy",#买卖方向为买入 posSide="long",#持仓方向,long为做多,short为做空 ordType="limit",#订单类型为市价单 px="63000",#委托价格 sz=qty,#下单数量 ) print("下限价单结果", result) return result #止盈单 def newProfitStoplossOrd(instId, qty): result = tradeAPI.place_order( instId=instId, tdMode="isolated",#逐仓 side="buy",#买入 posSide="long",#做多 ordType="limit",#限价单 sz=qty,#数量 px="63100.0", #attachAlgoOrds=arr tpTriggerPx="63110.0",#触发价格 tpOrdPx="64000.0",#止盈价格 #止盈触发价类型:last为最新价格,index为指数价格,mark为标记价格 tpTriggerPxType="last", slTriggerPx="63000.0",#触发价格 slOrdPx="62000.0",#止损价格 #止损触发价类型:last为最新价格,index为指数价格,mark为标记价格 slTriggerPxType="last", ) print("止盈单结果", result) return result #获取订单信息 def getOrd(instId, orderId): result = tradeAPI.get_order(instId, orderId) print("获取订单信息", result) return result #取消订单 def cancelOrd(instId, orderId): result = tradeAPI.cancel_order(instId, orderId) print("取消订单结果", result) return result #合约限价下单 def main(): instId = "BTC-USDT-SWAP"#交易对 qty = "10"#下单数量10个USDT result = newOrd(instId, qty) data = result["data"] if len(data) > 0: orderId = data[0]["ordId"] ordInfo = getOrd(instId, orderId) print("订单详情", ordInfo) if len(ordInfo["data"]) > 0: state = ordInfo["data"][0]["state"] print("订单状态", state) if state == "live": time.sleep(10)#休眠10s res = cancelOrd(instId, orderId)#取消订单 print(res) #canceled:撤单成功;live:等待成交;partially_filled:部分成交;filled:完全成交 #止盈订单 newProfitStoplossOrd(instId,qty) if __name__ == "__main__": main()