用go语言开发简易量化框架(四)--数字货币交易所API对接

用go语言开发简易量化框架(四)--数字货币交易所API对接

  • 相关概念
  • restful接口
  • websocket接口
  • 如何使用

数字货币交易所有几百家,各家接口都有差异,头部几家的接口相对完善,技术支持也比较好,而一些小交易所的接口就比较恼火了。
本人之前对接过火币、okex、币安、gate、zb、coinex几家交易所现货接口,有一定经验积累,本文做一些简单介绍。目前只支持现货的rest,websocket接口,已开源到仓库https://github.com/betterjun/exapi。

相关概念

交易所 目前数字货币交易所有几百家,基本都提供了api接口,打开官网,在网页底部一般都会有api文档的链接。如果需要接入,自己对照文档操作。

交易对 交易所里面,要进行数字货币交易,一般叫做币币交易。会用一种货币充当钱,另一种货币充当货物,形成一个交易对。比如BTC/USDT交易对,货物是BTC(在前),钱是USDT(在后)。比较常见的作为钱的货币有USDT/USDC,BTC,ETH,还有各个交易所的平台币。

tick 每个交易对的买卖实时行情,一般指过去24小时的开盘价,收盘价(最新价),最高价,最低价,成交量,成交额,当前卖一,买一价。这些数据影响用户进行交易,需要高度关注。

深度 当前市场上未成交的所有订单,分为买盘和卖盘深度。交易所进行撮合交易时,会按价格优先,时间优先的顺序排序进行撮合。当用户观察到市场深度后,可以调整自己的下单价格数量。

成交记录 这个很简单,当交易所撮合一次买卖交易后,就形成了一次成交记录。

k线 交易所按时间整理的市场行情走势,用户根据这个可以进行各种技术分析,决定自己炒币的策略。

账户余额 因为实行撮合交易所,账户上必须要有足够的相应币种才行,下单时交易所会冻结币种余额。比如账户余额有3个比特币,用户下单卖出1个比特币,账户可用余额为2个,冻结余额1个比特币,直到订单成交或取消为止。

现货 币币交易,交易所一般会分为三个区,现货,杠杆,合约。本人接触比较多的是现货,这个很好理解,就是手上有什么,有多少,就交易什么,交易多少。比如账户有1个BTC,那么最多也就只能卖1个BTC。杠杆和合约就不介绍了,本文的接口目前还不支持,在后续版本中会添加支持。

API Key 用户一般是通过交易所的APP,网址访问交易所,进行买卖的。但是为了方便,交易所都提供了API访问方式,需要用户在交易所去申请API Key,有了API Key后,就可以通过程序进行下单交易了。如果没有API Key,那就只能访问交易所公开的那些接口,主要是行情接口(tick,深度,成交记录,k线)。

restful接口

上面介绍了一些基本概念,接口封装其实也就围绕那些基本概念展开。restful接口都封装到接口SpotAPI中,各个交易所再分别实现此接口。

下面代码获取交易所的所有交易对,以及交易对状况

	// 获取支持的交易对
	GetAllCurrencyPair() (map[string]SymbolSetting, error)
	// 获取此币种是否可以充提币
	GetCurrencyStatus(currency Currency) (CurrencyStatus, error)
	// 获取所有币种是否可以充提币
	GetAllCurrencyStatus() (map[string]CurrencyStatus, error)

下面代码获取交易对的公共行情,构建SpotAPI时,可以使用空白的api key构建

	// 公共行情
	// 获取单一币种对的行情
	GetTicker(pair CurrencyPair) (*Ticker, error)
	// 获取所有币种对的行情
	GetAllTicker() ([]Ticker, error)
	//获取单一币种对的深度
	GetDepth(pair CurrencyPair, size int, step int) (*Depth, error)
	// 获取单一币种对的成交记录
	GetTrades(pair CurrencyPair, size int) ([]Trade, error)
	// 获取单一币种对的K线记录,按时间升序排列
	GetKlineRecords(pair CurrencyPair, period KlinePeriod, size, since int) ([]Kline, error)

下面代码操作用户账号进行交易,构建SpotAPI时,必须使用api key构建

	// 交易相关
	// 限价单
	LimitBuy(pair CurrencyPair, price, amount string) (*Order, error)
	LimitSell(pair CurrencyPair, price, amount string) (*Order, error)
	// 市价单,下单后返回的数据不准,需要调用GetOrder获取准确信息
	// 买入金额,单位为计价货币
	MarketBuy(pair CurrencyPair, amount string) (*Order, error)
	// 卖出数量,单位为基础货币
	MarketSell(pair CurrencyPair, amount string) (*Order, error)
	// 撤单
	Cancel(orderId string, pair CurrencyPair) (bool, error)
	// 获取订单详情
	GetOrder(orderId string, pair CurrencyPair) (*Order, error)
	// 获取当前未完成订单列表
	GetPendingOrders(pair CurrencyPair) ([]Order, error)
	// 获取最近完成订单
	GetFinishedOrders(pair CurrencyPair) ([]Order, error)
	// 获取订单的成交明细
	GetOrderDeal(orderId string, pair CurrencyPair) ([]OrderDeal, error)
	// 获取单一币种对的最近成交明细
	GetUserTrades(pair CurrencyPair) ([]Trade, error)
	// 获取账户余额
	GetAccount() (*Account, error)

websocket接口

websocket目前只封装了公共行情相关的订阅,账户订单和余额暂时未添加。使用websocket可以有效降低restful接口的调用,降低被交易所封ip的机率。

type SpotWebsocket interface {
	// 订阅或取消行情,cb传nil为取消,目前cb只有最后一次设置的生效
	SubTicker(pair CurrencyPair, cb func(*Ticker) error) (err error)
	SubDepth(pair CurrencyPair, cb func(*Depth) error) (err error)
	SubTrade(pair CurrencyPair, cb func([]Trade) error) (err error)
}

如何使用

使用示例

import (
	"github.com/betterjun/exapi"
	"github.com/betterjun/exapi/builder"
	"log"
	"time"
)

func main() {
	type exchangeCfgs struct {
		ex, accessKey, secretKey, apiPass string
	}
	exArr := []exchangeCfgs{
		// TODO put your keys here
		exchangeCfgs{exapi.HUOBI, "", "", ""},
	}

	for _, v := range exArr {
		spot_api_test(v.ex, v.accessKey, v.secretKey, v.apiPass, "socks5://127.0.0.1:1060")
		spot_ws_test(v.ex, "socks5://127.0.0.1:1060")
	}
}

// 使用restful接口的示例
func spot_api_test(ex, accessKey, secretKey, apiPass, proxy string) {
	apiBuilder := builder.NewAPIBuilder()
	apiBuilder.HttpProxy(proxy).APIKey(accessKey).APISecretkey(secretKey)
	if ex == exapi.OKEX {
		apiBuilder.ApiPassphrase(apiPass)
	}
	api := apiBuilder.BuildSpot(ex)

	log.Println(ex, "ExchangeName", api.GetExchangeName())
}

// 使用websocket接口的示例
func spot_ws_test(ex, proxy string) {
	apiBuilder := builder.NewAPIBuilder()
	spotws, err := apiBuilder.BuildSpotWebsocket(ex, proxy)
	if err != nil {
		log.Fatalf("创建%v websocket失败:%v", ex, err)
	}

	markets := []string{
		"xrp/usdt",
	}

	for _, v := range markets {
		spotws.SubTicker(exapi.NewCurrencyPairFromString(v), onTicker)
		spotws.SubDepth(exapi.NewCurrencyPairFromString(v), onDepth)
		spotws.SubTrade(exapi.NewCurrencyPairFromString(v), onTrade)
	}

	time.Sleep(time.Second * 60)

	for _, v := range markets {
		spotws.SubTicker(exapi.NewCurrencyPairFromString(v), nil)
		spotws.SubDepth(exapi.NewCurrencyPairFromString(v), nil)
		spotws.SubTrade(exapi.NewCurrencyPairFromString(v), nil)
	}

	time.Sleep(time.Second * 1)
	spotws.Close()
}

func onTicker(ticker *exapi.Ticker) error {
	log.Println("onTicker", ticker)

	return nil
}

func onDepth(depth *exapi.Depth) error {
	log.Println("onDepth", depth)

	depth.TS = depth.TS / 1000
	st := time.Now().Unix()
	log.Printf("systemTime:%v, depthTime:%v, diff:%vs\n", st, depth.TS, st-depth.TS)
	return nil
}

func onTrade(trades []exapi.Trade) error {
	log.Println("onTrade", trades)

	return nil
}

你可能感兴趣的:(量化开发)