数字货币交易所有几百家,各家接口都有差异,头部几家的接口相对完善,技术支持也比较好,而一些小交易所的接口就比较恼火了。
本人之前对接过火币、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接口都封装到接口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可以有效降低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
}