Port from idoall's codebase (#161)

* 修复火币Post REST API方法不正确的问题,同时增加火币海带丝交易所

* add vendor folder

* 修改命名空间依赖

* 第一次提交分支

* 增加取消订单功能

* 修复binance.GetAccount方法

* 更新readme.md

* 增加 Gateio 交易所的支持,支持获取K线、支持的交易对、交易市场参数

* 替换HuobiHadax的参数

* 买/卖订单、取消订单

* OKEX 币币交易:增加获取用户信息,下订单,取消订单

* 测试ok kline

* 修复 Bitfinex 的 GetAccountInfo 方法

* 做一些不必要的删减

* 修复binfinex不返回错误的bug

* 统一我修改交易所的Kline获取方式

* Bitfinex 增加获取最新价格

* update main.go

* 更新GetSymbol方法

* 修改火币和海带丝的Kline编号ID类型

* 修改海带丝的默认配置大小写

* okex增加获取最新价格

*   调整okex的参数判断

* 调整比特儿的参数名称

* 修改火币、火币Hadax的参数全名

* 更新海带丝的配置名称

* 修改bintfinex的GetAccountInfo方法

* 去掉一行注释

* 支持zb交易所的部分功能

* 修复获取K线时没有设置参数的错误

* 增加 Binance 取消订单的方法,获取订单状态,获取所有打开的状态以及所有订单

* 修改获取深度和历史订单的数据

* 修改币安获取深度的参数

* 修改火币获取市场深度的参数

* 修改okex获取市场深度的参数

* 修改币安、OKex获取历史订单的参数

* 修复币安提交参数错误的问题

* merge upstrem

* merge后,调整一部分命名空间

* 修改ZB时间参数的命名方式

* 继续替换命名空间

* 命名空间的替换

* 继续命名空间的替换

* 测试

* Port code from idoall's PR

* Drop errors dep

* Start amending PR

* Fix commented code
* Translate text from Chinese to English (except for ZB). The reasning behind this is that it's a Chinese exchange and the structs are self explanatory in English, but would for other developers in China

* Translate Chinese text, basic formatting changes

* Remove commented lines and address feedback on PR
This commit is contained in:
Adrian Gallagher
2018-08-04 08:30:20 +10:00
committed by GitHub
parent ecac1e124c
commit c63f1b0ff6
40 changed files with 4304 additions and 255 deletions

View File

@@ -31,9 +31,11 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| COINUT | Yes | No | NA |
| Exmo | Yes | NA | NA |
| CoinbasePro | Yes | Yes | No|
| GateIO | Yes | No | NA |
| Gemini | Yes | No | No |
| HitBTC | Yes | Yes | No |
| Huobi.Pro | Yes | No |No |
| Huobi.Pro | Yes | No | NA |
| Huobi.Hadax | Yes | No | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | NA | NA |
| LakeBTC | Yes | No | NA |
@@ -45,6 +47,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Poloniex | Yes | Yes | NA |
| WEX | Yes | NA | NA |
| Yobit | Yes | NA | NA |
| ZB.COM | Yes | No | NA |
We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/).
@@ -145,7 +148,7 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Github|Contribution Amount|
|--|--|--|
| thrasher- | https://github.com/thrasher- | 453 |
| shazbert | https://github.com/shazbert | 140 |
| shazbert | https://github.com/shazbert | 141 |
| gloriousCode | https://github.com/gloriousCode | 122 |
| 140am | https://github.com/140am | 8 |
| marcofranssen | https://github.com/marcofranssen | 4 |

View File

@@ -41,6 +41,7 @@ const (
HashSHA256
HashSHA512
HashSHA512_384
HashMD5
SatoshisPerBTC = 100000000
SatoshisPerLTC = 100000000
WeiPerEther = 1000000000000000000
@@ -121,6 +122,10 @@ func GetHMAC(hashType int, input, key []byte) []byte {
{
hash = sha512.New384
}
case HashMD5:
{
hash = md5.New
}
}
hmac := hmac.New(hash, []byte(key))
@@ -128,11 +133,24 @@ func GetHMAC(hashType int, input, key []byte) []byte {
return hmac.Sum(nil)
}
// Sha1ToHex takes a string, sha1 hashes it and return a hex string of the
// result
func Sha1ToHex(data string) string {
h := sha1.New()
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// HexEncodeToString takes in a hexadecimal byte array and returns a string
func HexEncodeToString(input []byte) string {
return hex.EncodeToString(input)
}
// ByteArrayToString returns a string
func ByteArrayToString(input []byte) string {
return fmt.Sprintf("%x", input)
}
// Base64Decode takes in a Base64 string and returns a byte array and an error
func Base64Decode(input string) ([]byte, error) {
result, err := base64.StdEncoding.DecodeString(input)
@@ -538,3 +556,61 @@ func GetOSPathSlash() string {
}
return "/"
}
// UnixMillis converts a UnixNano timestamp to milliseconds
func UnixMillis(t time.Time) int64 {
return t.UnixNano() / int64(time.Millisecond)
}
// RecvWindow converts a supplied time.Duration to milliseconds
func RecvWindow(d time.Duration) int64 {
return int64(d) / int64(time.Millisecond)
}
// FloatFromString format
func FloatFromString(raw interface{}) (float64, error) {
str, ok := raw.(string)
if !ok {
return 0, fmt.Errorf("unable to parse, value not string: %T", raw)
}
flt, err := strconv.ParseFloat(str, 64)
if err != nil {
return 0, fmt.Errorf("unable to parse, value not string: %T", raw)
}
return flt, nil
}
// IntFromString format
func IntFromString(raw interface{}) (int, error) {
str, ok := raw.(string)
if !ok {
return 0, fmt.Errorf("unable to parse, value not string: %T", raw)
}
n, err := strconv.Atoi(str)
if err != nil {
return 0, fmt.Errorf("unable to parse as int: %T", raw)
}
return n, nil
}
// Int64FromString format
func Int64FromString(raw interface{}) (int64, error) {
str, ok := raw.(string)
if !ok {
return 0, fmt.Errorf("unable to parse, value not string: %T", raw)
}
n, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return 0, fmt.Errorf("unable to parse as int: %T", raw)
}
return n, nil
}
// TimeFromUnixTimestampFloat format
func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) {
ts, ok := raw.(float64)
if !ok {
return time.Time{}, fmt.Errorf("unable to parse, value not int64: %T", raw)
}
return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil
}

View File

@@ -87,13 +87,13 @@ type Response struct {
LastRead string `json:"last_read"`
Members []string `json:"members"`
Topic struct {
Value string `json:"string"`
Creator string `json:"string"`
Value string `json:"value"`
Creator string `json:"creator"`
LastSet int64 `json:"last_set"`
} `json:"topic"`
Purpose struct {
Value string `json:"string"`
Creator string `json:"string"`
Value string `json:"value"`
Creator string `json:"creator"`
LastSet int64 `json:"last_set"`
} `json:"purpose"`
} `json:"groups"`

View File

@@ -206,7 +206,7 @@ func TestGetEnabledExchanges(t *testing.T) {
}
exchanges := cfg.GetEnabledExchanges()
if len(exchanges) != 26 {
if len(exchanges) != 29 {
t.Error(
"Test failed. TestGetEnabledExchanges. Enabled exchanges value mismatch",
)
@@ -258,7 +258,7 @@ func TestGetDisabledExchanges(t *testing.T) {
}
func TestCountEnabledExchanges(t *testing.T) {
defaultEnabledExchanges := 26
defaultEnabledExchanges := 29
GetConfigEnabledExchanges := GetConfig()
err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile)
if err != nil {

File diff suppressed because one or more lines are too long

View File

@@ -19,9 +19,11 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/coinbasepro"
"github.com/thrasher-/gocryptotrader/exchanges/coinut"
"github.com/thrasher-/gocryptotrader/exchanges/exmo"
"github.com/thrasher-/gocryptotrader/exchanges/gateio"
"github.com/thrasher-/gocryptotrader/exchanges/gemini"
"github.com/thrasher-/gocryptotrader/exchanges/hitbtc"
"github.com/thrasher-/gocryptotrader/exchanges/huobi"
"github.com/thrasher-/gocryptotrader/exchanges/huobihadax"
"github.com/thrasher-/gocryptotrader/exchanges/itbit"
"github.com/thrasher-/gocryptotrader/exchanges/kraken"
"github.com/thrasher-/gocryptotrader/exchanges/lakebtc"
@@ -32,6 +34,7 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/poloniex"
"github.com/thrasher-/gocryptotrader/exchanges/wex"
"github.com/thrasher-/gocryptotrader/exchanges/yobit"
"github.com/thrasher-/gocryptotrader/exchanges/zb"
)
// vars related to exchange functions
@@ -156,12 +159,16 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
exch = new(exmo.EXMO)
case "coinbasepro":
exch = new(coinbasepro.CoinbasePro)
case "gateio":
exch = new(gateio.Gateio)
case "gemini":
exch = new(gemini.Gemini)
case "hitbtc":
exch = new(hitbtc.HitBTC)
case "huobi":
exch = new(huobi.HUOBI)
case "huobihadax":
exch = new(huobihadax.HUOBIHADAX)
case "itbit":
exch = new(itbit.ItBit)
case "kraken":
@@ -184,6 +191,8 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
exch = new(wex.WEX)
case "yobit":
exch = new(yobit.Yobit)
case "zb":
exch = new(zb.ZB)
default:
return ErrExchangeNotFound
}

View File

@@ -23,8 +23,8 @@ type Binance struct {
WebsocketConn *websocket.Conn
// valid string list that a required by the exchange
validLimits []string
validIntervals []string
validLimits []int
validIntervals []TimeInterval
}
const (
@@ -45,7 +45,10 @@ const (
// Authenticated endpoints
newOrderTest = "/api/v3/order/test"
newOrder = "/api/v3/order"
cancelOrder = "/api/v3/order"
queryOrder = "/api/v3/order"
openOrders = "/api/v3/openOrders"
allOrders = "/api/v3/allOrders"
// binance authenticated and unauthenticated limit rates
// to-do
@@ -130,21 +133,22 @@ func (b *Binance) GetExchangeInfo() (ExchangeInfo, error) {
// GetOrderBook returns full orderbook information
//
// OrderBookDataRequestParams contains the following members
// symbol: string of currency pair
// limit: returned limit amount
func (b *Binance) GetOrderBook(symbol string, limit int64) (OrderBook, error) {
func (b *Binance) GetOrderBook(obd OrderBookDataRequestParams) (OrderBook, error) {
orderbook, resp := OrderBook{}, OrderBookData{}
if err := b.CheckLimit(limit); err != nil {
if err := b.CheckLimit(obd.Limit); err != nil {
return orderbook, err
}
if err := b.CheckSymbol(symbol); err != nil {
if err := b.CheckSymbol(obd.Symbol); err != nil {
return orderbook, err
}
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
params.Set("limit", strconv.FormatInt(limit, 10))
params.Set("symbol", common.StringToUpper(obd.Symbol))
params.Set("limit", fmt.Sprintf("%d", obd.Limit))
path := fmt.Sprintf("%s%s?%s", apiURL, orderBookDepth, params.Encode())
@@ -189,22 +193,13 @@ func (b *Binance) GetOrderBook(symbol string, limit int64) (OrderBook, error) {
}
// GetRecentTrades returns recent trade activity
//
// symbol: string of currency pair
// limit: returned limit amount WARNING: MAX 500!
func (b *Binance) GetRecentTrades(symbol string, limit int64) ([]RecentTrade, error) {
// limit: Up to 500 results returned
func (b *Binance) GetRecentTrades(rtr RecentTradeRequestParams) ([]RecentTrade, error) {
resp := []RecentTrade{}
if err := b.CheckLimit(limit); err != nil {
return resp, err
}
if err := b.CheckSymbol(symbol); err != nil {
return resp, err
}
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
params.Set("limit", strconv.FormatInt(limit, 10))
params.Set("symbol", common.StringToUpper(rtr.Symbol))
params.Set("limit", fmt.Sprintf("%d", rtr.Limit))
path := fmt.Sprintf("%s%s?%s", apiURL, recentTrades, params.Encode())
@@ -214,21 +209,18 @@ func (b *Binance) GetRecentTrades(symbol string, limit int64) ([]RecentTrade, er
// GetHistoricalTrades returns historical trade activity
//
// symbol: string of currency pair
// limit: returned limit amount WARNING: MAX 500! (NOT REQUIRED)
// limit: Optional. Default 500; max 1000.
// fromID:
func (b *Binance) GetHistoricalTrades(symbol string, limit, fromID int64) ([]HistoricalTrade, error) {
func (b *Binance) GetHistoricalTrades(symbol string, limit int, fromID int64) ([]HistoricalTrade, error) {
resp := []HistoricalTrade{}
if err := b.CheckLimit(limit); err != nil {
return resp, err
}
if err := b.CheckSymbol(symbol); err != nil {
return resp, err
}
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
params.Set("limit", strconv.FormatInt(limit, 10))
params.Set("limit", strconv.Itoa(limit))
params.Set("fromid", strconv.FormatInt(fromID, 10))
path := fmt.Sprintf("%s%s?%s", apiURL, historicalTrades, params.Encode())
@@ -239,8 +231,8 @@ func (b *Binance) GetHistoricalTrades(symbol string, limit, fromID int64) ([]His
// GetAggregatedTrades returns aggregated trade activity
//
// symbol: string of currency pair
// limit: returned limit amount WARNING: MAX 500!
func (b *Binance) GetAggregatedTrades(symbol string, limit int64) ([]AggregatedTrade, error) {
// limit: Optional. Default 500; max 1000.
func (b *Binance) GetAggregatedTrades(symbol string, limit int) ([]AggregatedTrade, error) {
resp := []AggregatedTrade{}
if err := b.CheckLimit(limit); err != nil {
@@ -252,36 +244,37 @@ func (b *Binance) GetAggregatedTrades(symbol string, limit int64) ([]AggregatedT
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
params.Set("limit", strconv.FormatInt(limit, 10))
params.Set("limit", strconv.Itoa(limit))
path := fmt.Sprintf("%s%s?%s", apiURL, aggregatedTrades, params.Encode())
return resp, b.SendHTTPRequest(path, &resp)
}
// GetCandleStickData returns candle stick data
// GetSpotKline returns kline data
//
// symbol:
// limit:
// interval
func (b *Binance) GetCandleStickData(symbol, interval string, limit int64) ([]CandleStick, error) {
// KlinesRequestParams supports 5 parameters
// symbol: the symbol to get the kline data for
// limit: optinal
// interval: the interval time for the data
// startTime: startTime filter for kline data
// endTime: endTime filter for the kline data
func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) {
var resp interface{}
var kline []CandleStick
if err := b.CheckLimit(limit); err != nil {
return kline, err
}
if err := b.CheckSymbol(symbol); err != nil {
return kline, err
}
if err := b.CheckIntervals(interval); err != nil {
return kline, err
}
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
params.Set("limit", strconv.FormatInt(limit, 10))
params.Set("interval", interval)
params.Set("symbol", arg.Symbol)
params.Set("interval", string(arg.Interval))
if arg.Limit != 0 {
params.Set("limit", strconv.Itoa(arg.Limit))
}
if arg.StartTime != 0 {
params.Set("startTime", strconv.FormatInt(arg.StartTime, 10))
}
if arg.EndTime != 0 {
params.Set("endTime", strconv.FormatInt(arg.EndTime, 10))
}
path := fmt.Sprintf("%s%s?%s", apiURL, candleStick, params.Encode())
@@ -402,19 +395,31 @@ func (b *Binance) NewOrderTest() (interface{}, error) {
func (b *Binance) NewOrder(o NewOrderRequest) (NewOrderResponse, error) {
var resp NewOrderResponse
path := fmt.Sprintf("%s%s", apiURL, newOrderTest)
path := fmt.Sprintf("%s%s", apiURL, newOrder)
params := url.Values{}
params.Set("symbol", o.Symbol)
params.Set("side", o.Side)
params.Set("type", o.TradeType)
params.Set("timeInForce", o.TimeInForce)
params.Set("side", string(o.Side))
params.Set("type", string(o.TradeType))
params.Set("quantity", strconv.FormatFloat(o.Quantity, 'f', -1, 64))
params.Set("price", strconv.FormatFloat(o.Price, 'f', -1, 64))
params.Set("newClientOrderID", o.NewClientOrderID)
params.Set("stopPrice", strconv.FormatFloat(o.StopPrice, 'f', -1, 64))
params.Set("icebergQty", strconv.FormatFloat(o.IcebergQty, 'f', -1, 64))
params.Set("newOrderRespType", o.NewOrderRespType)
params.Set("timeInForce", string(o.TimeInForce))
if o.NewClientOrderID != "" {
params.Set("newClientOrderID", o.NewClientOrderID)
}
if o.StopPrice != 0 {
params.Set("stopPrice", strconv.FormatFloat(o.StopPrice, 'f', -1, 64))
}
if o.IcebergQty != 0 {
params.Set("icebergQty", strconv.FormatFloat(o.IcebergQty, 'f', -1, 64))
}
if o.NewOrderRespType != "" {
params.Set("newOrderRespType", o.NewOrderRespType)
}
if err := b.SendAuthHTTPRequest("POST", path, params, &resp); err != nil {
return resp, err
@@ -426,6 +431,71 @@ func (b *Binance) NewOrder(o NewOrderRequest) (NewOrderResponse, error) {
return resp, nil
}
// CancelOrder sends a cancel order to Binance
func (b *Binance) CancelOrder(symbol string, orderID int64, origClientOrderID string) (CancelOrderResponse, error) {
var resp CancelOrderResponse
path := fmt.Sprintf("%s%s", apiURL, cancelOrder)
params := url.Values{}
params.Set("symbol", symbol)
if orderID != 0 {
params.Set("orderId", strconv.FormatInt(orderID, 10))
}
if origClientOrderID != "" {
params.Set("origClientOrderId", origClientOrderID)
}
if err := b.SendAuthHTTPRequest("DELETE", path, params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// OpenOrders Current open orders
// Get all open orders on a symbol. Careful when accessing this with no symbol.
func (b *Binance) OpenOrders(symbol string) ([]QueryOrderData, error) {
var resp []QueryOrderData
path := fmt.Sprintf("%s%s", apiURL, openOrders)
params := url.Values{}
if symbol != "" {
params.Set("symbol", common.StringToUpper(symbol))
}
if err := b.SendAuthHTTPRequest("GET", path, params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// AllOrders Get all account orders; active, canceled, or filled.
// orderId optional param
// limit optional param, default 500; max 500
func (b *Binance) AllOrders(symbol, orderID, limit string) ([]QueryOrderData, error) {
var resp []QueryOrderData
path := fmt.Sprintf("%s%s", apiURL, allOrders)
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
if orderID != "" {
params.Set("orderId", orderID)
}
if limit != "" {
params.Set("limit", limit)
}
if err := b.SendAuthHTTPRequest("GET", path, params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// QueryOrder returns information on a past order
func (b *Binance) QueryOrder(symbol, origClientOrderID string, orderID int64) (QueryOrderData, error) {
var resp QueryOrderData
@@ -434,8 +504,12 @@ func (b *Binance) QueryOrder(symbol, origClientOrderID string, orderID int64) (Q
params := url.Values{}
params.Set("symbol", common.StringToUpper(symbol))
params.Set("origClientOrderId", origClientOrderID)
params.Set("orderId", strconv.FormatInt(orderID, 10))
if origClientOrderID != "" {
params.Set("origClientOrderId", origClientOrderID)
}
if orderID != 0 {
params.Set("orderId", strconv.FormatInt(orderID, 10))
}
if err := b.SendAuthHTTPRequest("GET", path, params, &resp); err != nil {
return resp, err
@@ -484,7 +558,7 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re
if params == nil {
params = url.Values{}
}
params.Set("recvWindow", strconv.FormatInt(5000, 10))
params.Set("recvWindow", strconv.FormatInt(common.RecvWindow(5*time.Second), 10))
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
signature := params.Encode()
@@ -494,7 +568,6 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re
headers := make(map[string]string)
headers["X-MBX-APIKEY"] = b.APIKey
headers["Content-Type"] = "application/x-www-form-urlencoded"
if b.Verbose {
log.Printf("sent path: \n%s\n", path)
@@ -505,16 +578,18 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re
}
// CheckLimit checks value against a variable list
func (b *Binance) CheckLimit(limit int64) error {
if !common.StringDataCompare(b.validLimits, strconv.FormatInt(limit, 10)) {
return errors.New("Incorrect limit values - valid values are 5, 10, 20, 50, 100, 500, 1000")
func (b *Binance) CheckLimit(limit int) error {
for x := range b.validLimits {
if b.validLimits[x] == limit {
return nil
}
}
return nil
return errors.New("Incorrect limit values - valid values are 5, 10, 20, 50, 100, 500, 1000")
}
// CheckSymbol checks value against a variable list
func (b *Binance) CheckSymbol(symbol string) error {
enPairs := b.GetEnabledCurrencies()
enPairs := b.GetAvailableCurrencies()
for x := range enPairs {
if exchange.FormatExchangeCurrency(b.Name, enPairs[x]).String() == symbol {
return nil
@@ -525,14 +600,32 @@ func (b *Binance) CheckSymbol(symbol string) error {
// CheckIntervals checks value against a variable list
func (b *Binance) CheckIntervals(interval string) error {
if !common.StringDataCompare(b.validIntervals, interval) {
return errors.New(`Incorrect interval values - valid values are "1m","3m","5m","15m","30m","1h","2h","4h","6h","8h","12h","1d","3d","1w","1M"`)
for x := range b.validIntervals {
if TimeInterval(interval) == b.validIntervals[x] {
return nil
}
}
return nil
return errors.New(`Incorrect interval values - valid values are "1m","3m","5m","15m","30m","1h","2h","4h","6h","8h","12h","1d","3d","1w","1M"`)
}
// SetValues sets the default valid values
func (b *Binance) SetValues() {
b.validLimits = []string{"5", "10", "20", "50", "100", "500", "1000"}
b.validIntervals = []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"}
b.validLimits = []int{5, 10, 20, 50, 100, 500, 1000}
b.validIntervals = []TimeInterval{
TimeIntervalMinute,
TimeIntervalThreeMinutes,
TimeIntervalFiveMinutes,
TimeIntervalFifteenMinutes,
TimeIntervalThirtyMinutes,
TimeIntervalHour,
TimeIntervalTwoHours,
TimeIntervalFourHours,
TimeIntervalSixHours,
TimeIntervalEightHours,
TimeIntervalTwelveHours,
TimeIntervalDay,
TimeIntervalThreeDays,
TimeIntervalWeek,
TimeIntervalMonth,
}
}

View File

@@ -43,7 +43,11 @@ func TestGetExchangeValidCurrencyPairs(t *testing.T) {
func TestGetOrderBook(t *testing.T) {
t.Parallel()
_, err := b.GetOrderBook("BTCUSDT", 5)
_, err := b.GetOrderBook(OrderBookDataRequestParams{
Symbol: "BTCUSDT",
Limit: 10,
})
if err != nil {
t.Error("Test Failed - Binance GetOrderBook() error", err)
}
@@ -51,7 +55,12 @@ func TestGetOrderBook(t *testing.T) {
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
_, err := b.GetRecentTrades("BTCUSDT", 5)
_, err := b.GetRecentTrades(RecentTradeRequestParams{
Symbol: "BTCUSDT",
Limit: 15,
})
if err != nil {
t.Error("Test Failed - Binance GetRecentTrades() error", err)
}
@@ -73,11 +82,15 @@ func TestGetAggregatedTrades(t *testing.T) {
}
}
func TestGetCandleStickData(t *testing.T) {
func TestGetSpotKline(t *testing.T) {
t.Parallel()
_, err := b.GetCandleStickData("BTCUSDT", "1d", 5)
_, err := b.GetSpotKline(KlinesRequestParams{
Symbol: "BTCUSDT",
Interval: TimeIntervalFiveMinutes,
Limit: 24,
})
if err != nil {
t.Error("Test Failed - Binance GetCandleStickData() error", err)
t.Error("Test Failed - Binance GetSpotKline() error", err)
}
}
@@ -123,16 +136,72 @@ func TestNewOrderTest(t *testing.T) {
func TestNewOrder(t *testing.T) {
t.Parallel()
_, err := b.NewOrder(NewOrderRequest{})
if testAPIKey == "" || testAPISecret == "" {
t.Skip()
}
_, err := b.NewOrder(NewOrderRequest{
Symbol: "BTCUSDT",
Side: BinanceRequestParamsSideSell,
TradeType: BinanceRequestParamsOrderLimit,
TimeInForce: BinanceRequestParamsTimeGTC,
Quantity: 0.01,
Price: 1536.1,
})
if err == nil {
t.Error("Test Failed - Binance NewOrder() error", err)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
if testAPIKey == "" || testAPISecret == "" {
t.Skip()
}
_, err := b.CancelOrder("BTCUSDT", 82584683, "")
if err == nil {
t.Error("Test Failed - Binance CancelOrder() error", err)
}
}
func TestQueryOrder(t *testing.T) {
t.Parallel()
_, err := b.QueryOrder("", "", 1337)
if err == nil {
if testAPIKey == "" || testAPISecret == "" {
t.Skip()
}
_, err := b.QueryOrder("BTCUSDT", "", 1337)
if err != nil {
t.Error("Test Failed - Binance QueryOrder() error", err)
}
}
func TestOpenOrders(t *testing.T) {
t.Parallel()
if testAPIKey == "" || testAPISecret == "" {
t.Skip()
}
_, err := b.OpenOrders("BTCUSDT")
if err != nil {
t.Error("Test Failed - Binance OpenOrders() error", err)
}
}
func TestAllOrders(t *testing.T) {
t.Parallel()
if testAPIKey == "" || testAPISecret == "" {
t.Skip()
}
_, err := b.AllOrders("BTCUSDT", "", "")
if err != nil {
t.Error("Test Failed - Binance AllOrders() error", err)
}
}

View File

@@ -1,6 +1,5 @@
package binance
import (
"encoding/json"
)
@@ -45,6 +44,12 @@ type ExchangeInfo struct {
} `json:"symbols"`
}
// OrderBookDataRequestParams represents Klines request data.
type OrderBookDataRequestParams struct {
Symbol string `json:"symbol"` // Required field; example LTCBTC,BTCUSDT
Limit int `json:"limit"` // Default 100; max 1000. Valid limits:[5, 10, 20, 50, 100, 500, 1000]
}
// OrderBookData is resp data from orderbook endpoint
type OrderBookData struct {
Code int `json:"code"`
@@ -68,6 +73,12 @@ type OrderBook struct {
}
}
// RecentTradeRequestParams represents Klines request data.
type RecentTradeRequestParams struct {
Symbol string `json:"symbol"` // Required field. example LTCBTC, BTCUSDT
Limit int `json:"limit"` // Default 500; max 500.
}
// RecentTrade holds recent trade data
type RecentTrade struct {
Code int `json:"code"`
@@ -75,16 +86,18 @@ type RecentTrade struct {
ID int64 `json:"id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
Time int64 `json:"time"`
Time float64 `json:"time"`
IsBuyerMaker bool `json:"isBuyerMaker"`
IsBestMatch bool `json:"isBestMatch"`
}
// MultiStreamData holds stream data
type MultiStreamData struct {
Stream string `json:"stream"`
Data json.RawMessage `json:"data"`
}
// TradeStream holds the trade stream data
type TradeStream struct {
EventType string `json:"e"`
EventTime int64 `json:"E"`
@@ -99,6 +112,7 @@ type TradeStream struct {
BestMatchPrice bool `json:"M"`
}
// KlineStream holds the kline stream data
type KlineStream struct {
EventType string `json:"e"`
EventTime int64 `json:"E"`
@@ -123,6 +137,7 @@ type KlineStream struct {
} `json:"k"`
}
// TickerStream holds the ticker stream data
type TickerStream struct {
EventType string `json:"e"`
EventTime int64 `json:"E"`
@@ -228,15 +243,21 @@ type BestPrice struct {
// NewOrderRequest request type
type NewOrderRequest struct {
Symbol string
Side string
TradeType string
TimeInForce string
// Symbol (currency pair to trade)
Symbol string
// Side Buy or Sell
Side RequestParamsSideType
// TradeType (market or limit order)
TradeType RequestParamsOrderType
// TimeInForce specifies how long the order remains in effect.
// Examples are (Good Till Cancel (GTC), Immediate or Cancel (IOC) and Fill Or Kill (FOK))
TimeInForce RequestParamsTimeForceType
// Quantity
Quantity float64
Price float64
NewClientOrderID string
StopPrice float64
IcebergQty float64
StopPrice float64 //Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders.
IcebergQty float64 //Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.
NewOrderRespType string
}
@@ -263,6 +284,14 @@ type NewOrderResponse struct {
} `json:"fills"`
}
// CancelOrderResponse is the return structured response from the exchange
type CancelOrderResponse struct {
Symbol string `json:"symbol"`
OrigClientOrderID string `json:"origClientOrderId"`
OrderID int64 `json:"orderId"`
ClientOrderID string `json:"clientOrderId"`
}
// QueryOrderData holds query order data
type QueryOrderData struct {
Code int `json:"code"`
@@ -279,7 +308,7 @@ type QueryOrderData struct {
Side string `json:"side"`
StopPrice float64 `json:"stopPrice,string"`
IcebergQty float64 `json:"icebergQty,string"`
Time int64 `json:"time"`
Time float64 `json:"time"`
IsWorking bool `json:"isWorking"`
}
@@ -302,3 +331,85 @@ type Account struct {
UpdateTime int64 `json:"updateTime"`
Balances []Balance `json:"balances"`
}
// RequestParamsSideType trade order side (buy or sell)
type RequestParamsSideType string
var (
// BinanceRequestParamsSideBuy buy order type
BinanceRequestParamsSideBuy = RequestParamsSideType("BUY")
// BinanceRequestParamsSideSell sell order type
BinanceRequestParamsSideSell = RequestParamsSideType("SELL")
)
// RequestParamsTimeForceType Time in force
type RequestParamsTimeForceType string
var (
// BinanceRequestParamsTimeGTC GTC
BinanceRequestParamsTimeGTC = RequestParamsTimeForceType("GTC")
// BinanceRequestParamsTimeIOC IOC
BinanceRequestParamsTimeIOC = RequestParamsTimeForceType("IOC")
// BinanceRequestParamsTimeFOK FOK
BinanceRequestParamsTimeFOK = RequestParamsTimeForceType("FOK")
)
// RequestParamsOrderType trade order type
type RequestParamsOrderType string
var (
// BinanceRequestParamsOrderLimit Limit order
BinanceRequestParamsOrderLimit = RequestParamsOrderType("LIMIT")
// BinanceRequestParamsOrderMarket Market order
BinanceRequestParamsOrderMarket = RequestParamsOrderType("MARKET")
// BinanceRequestParamsOrderStopLoss STOP_LOSS
BinanceRequestParamsOrderStopLoss = RequestParamsOrderType("STOP_LOSS")
// BinanceRequestParamsOrderStopLossLimit STOP_LOSS_LIMIT
BinanceRequestParamsOrderStopLossLimit = RequestParamsOrderType("STOP_LOSS_LIMIT")
// BinanceRequestParamsOrderTakeProfit TAKE_PROFIT
BinanceRequestParamsOrderTakeProfit = RequestParamsOrderType("TAKE_PROFIT")
// BinanceRequestParamsOrderTakeProfitLimit TAKE_PROFIT_LIMIT
BinanceRequestParamsOrderTakeProfitLimit = RequestParamsOrderType("TAKE_PROFIT_LIMIT")
// BinanceRequestParamsOrderLimitMarker LIMIT_MAKER
BinanceRequestParamsOrderLimitMarker = RequestParamsOrderType("LIMIT_MAKER")
)
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string // Required field; example LTCBTC, BTCUSDT
Interval TimeInterval // Time interval period
Limit int // Default 500; max 500.
StartTime int64
EndTime int64
}
// TimeInterval represents interval enum.
type TimeInterval string
// Vars related to time intervals
var (
TimeIntervalMinute = TimeInterval("1m")
TimeIntervalThreeMinutes = TimeInterval("3m")
TimeIntervalFiveMinutes = TimeInterval("5m")
TimeIntervalFifteenMinutes = TimeInterval("15m")
TimeIntervalThirtyMinutes = TimeInterval("30m")
TimeIntervalHour = TimeInterval("1h")
TimeIntervalTwoHours = TimeInterval("2h")
TimeIntervalFourHours = TimeInterval("4h")
TimeIntervalSixHours = TimeInterval("6h")
TimeIntervalEightHours = TimeInterval("8h")
TimeIntervalTwelveHours = TimeInterval("12h")
TimeIntervalDay = TimeInterval("1d")
TimeIntervalThreeDays = TimeInterval("3d")
TimeIntervalWeek = TimeInterval("1w")
TimeIntervalMonth = TimeInterval("1M")
)

View File

@@ -15,6 +15,7 @@ const (
binancePingPeriod = 20 * time.Second
)
// WebsocketClient starts and handles the websocket client connection
func (b *Binance) WebsocketClient() {
for b.Enabled && b.Websocket {
var Dialer websocket.Dialer

View File

@@ -106,7 +106,7 @@ func (b *Binance) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *Binance) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := b.GetOrderBook(exchange.FormatExchangeCurrency(b.Name, p).String(), 1000)
orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{Symbol: exchange.FormatExchangeCurrency(b.Name, p).String(), Limit: 1000})
if err != nil {
return orderBook, err
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
@@ -153,10 +153,21 @@ func (b *Bitfinex) GetPlatformStatus() (int, error) {
return int(response[0].(float64)), nil
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
func (b *Bitfinex) GetLatestSpotPrice(symbol string) (float64, error) {
res, err := b.GetTicker(symbol)
if err != nil {
return 0, err
}
return res.Mid, nil
}
// GetTicker returns ticker information
func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) {
func (b *Bitfinex) GetTicker(symbol string) (Ticker, error) {
response := Ticker{}
path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, values)
path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, url.Values{})
if err := b.SendHTTPRequest(path, &response, b.Verbose); err != nil {
return response, err
@@ -453,18 +464,33 @@ func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) {
}
// GetAccountInfo returns information about your account incl. trading fees
func (b *Bitfinex) GetAccountInfo() ([]AccountInfo, error) {
response := AccountInfoFull{}
func (b *Bitfinex) GetAccountInfo() (AccountInfo, error) {
var result AccountInfo
var response []interface{}
err := b.SendAuthenticatedHTTPRequest("POST", bitfinexAccountInfo, nil, &response)
err := b.SendAuthenticatedHTTPRequest("POST", bitfinexAccountFees, nil, &response)
if err != nil {
return response.Info, err
return result, err
}
if response.Message == "" {
return response.Info, errors.New(response.Message)
result = AccountInfo{}
result.MakerFees = response[0].(map[string]interface{})["maker_fees"].(string)
result.TakerFees = response[0].(map[string]interface{})["taker_fees"].(string)
feeslist := response[0].(map[string]interface{})["fees"].([]interface{})
for _, v := range feeslist {
item := v.(map[string]interface{})
result.Fees = append(result.Fees, AccountInfoFees{
Pairs: item["pairs"].(string),
MakerFees: item["maker_fees"].(string),
TakerFees: item["taker_fees"].(string),
})
}
return response.Info, nil
return result, nil
}
// GetAccountFees - NOT YET IMPLEMENTED
@@ -877,7 +903,7 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[
headers["X-BFX-PAYLOAD"] = PayloadBase64
headers["X-BFX-SIGNATURE"] = common.HexEncodeToString(hmac)
b.SendPayload(method, bitfinexAPIURL+path, headers, nil, result, true, b.Verbose)
err = b.SendPayload(method, bitfinexAPIURL+path, headers, nil, result, true, b.Verbose)
if err != nil {
return err
}

View File

@@ -52,14 +52,22 @@ func TestGetPlatformStatus(t *testing.T) {
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := b.GetLatestSpotPrice("BTCUSD")
if err != nil {
t.Error("Bitfinex GetLatestSpotPrice error: ", err)
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := b.GetTicker("BTCUSD", url.Values{})
_, err := b.GetTicker("BTCUSD")
if err != nil {
t.Error("BitfinexGetTicker init error: ", err)
}
_, err = b.GetTicker("wigwham", url.Values{})
_, err = b.GetTicker("wigwham")
if err == nil {
t.Error("Test Failed - GetTicker() error")
}

View File

@@ -133,14 +133,17 @@ type AccountInfoFull struct {
// AccountInfo general account information with fees
type AccountInfo struct {
MakerFees string `json:"maker_fees"`
TakerFees string `json:"taker_fees"`
Fees []AccountInfoFees `json:"fees"`
Message string `json:"message"`
}
// AccountInfoFees general account information with fees
type AccountInfoFees struct {
Pairs string `json:"pairs"`
MakerFees string `json:"maker_fees"`
TakerFees string `json:"taker_fees"`
Fees []struct {
Pairs string `json:"pairs"`
MakerFees string `json:"maker_fees"`
TakerFees string `json:"taker_fees"`
} `json:"fees"`
Message string `json:"message"`
}
// AccountFees stores withdrawal account fee data from Bitfinex
@@ -462,3 +465,22 @@ type WebsocketTradeExecuted struct {
type ErrorCapture struct {
Message string `json:"message"`
}
// TimeInterval represents interval enum.
type TimeInterval string
// TimeInvterval vars
var (
TimeIntervalMinute = TimeInterval("1m")
TimeIntervalFiveMinutes = TimeInterval("5m")
TimeIntervalFifteenMinutes = TimeInterval("15m")
TimeIntervalThirtyMinutes = TimeInterval("30m")
TimeIntervalHour = TimeInterval("1h")
TimeIntervalThreeHours = TimeInterval("3h")
TimeIntervalSixHours = TimeInterval("6h")
TimeIntervalTwelveHours = TimeInterval("12h")
TimeIntervalDay = TimeInterval("1d")
TimeIntervalSevenDays = TimeInterval("7d")
TimeIntervalFourteenDays = TimeInterval("14d")
TimeIntervalMonth = TimeInterval("1M")
)

View File

@@ -0,0 +1,9 @@
# GoCryptoTrader package gateio
This gateio package is part of the GoCryptoTrader codebase.
## Gateio Exchange
### Current Features
Gateio 交易所的支持支持获取K线、支持的交易对、交易市场参数、买/卖订单、取消订单

375
exchanges/gateio/gateio.go Normal file
View File

@@ -0,0 +1,375 @@
package gateio
import (
"encoding/json"
"errors"
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
gateioTradeURL = "https://api.gateio.io"
gateioMarketURL = "https://data.gateio.io"
gateioAPIVersion = "api2/1"
gateioSymbol = "pairs"
gateioMarketInfo = "marketinfo"
gateioKline = "candlestick2"
gateioOrder = "private"
gateioBalances = "private/balances"
gateioCancelOrder = "private/cancelOrder"
gateioTicker = "ticker"
gateioTickers = "tickers"
gateioOrderbook = "orderBook"
gateioAuthRate = 100
gateioUnauthRate = 100
)
// Gateio is the overarching type across this package
type Gateio struct {
exchange.Base
}
// SetDefaults sets default values for the exchange
func (g *Gateio) SetDefaults() {
g.Name = "GateIO"
g.Enabled = false
g.Verbose = false
g.Websocket = false
g.RESTPollingDelay = 10
g.RequestCurrencyPairFormat.Delimiter = "_"
g.RequestCurrencyPairFormat.Uppercase = false
g.ConfigCurrencyPairFormat.Delimiter = "_"
g.ConfigCurrencyPairFormat.Uppercase = true
g.AssetTypes = []string{ticker.Spot}
g.SupportsAutoPairUpdating = true
g.SupportsRESTTickerBatching = true
g.Requester = request.New(g.Name, request.NewRateLimit(time.Second*10, gateioAuthRate), request.NewRateLimit(time.Second*10, gateioUnauthRate), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
}
// Setup sets user configuration
func (g *Gateio) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
g.SetEnabled(false)
} else {
g.Enabled = true
g.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
g.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
g.APIAuthPEMKey = exch.APIAuthPEMKey
g.SetHTTPClientTimeout(exch.HTTPTimeout)
g.RESTPollingDelay = exch.RESTPollingDelay
g.Verbose = exch.Verbose
g.Websocket = exch.Websocket
g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := g.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = g.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
err = g.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
}
}
// GetSymbols returns all supported symbols
func (g *Gateio) GetSymbols() ([]string, error) {
var result []string
url := fmt.Sprintf("%s/%s/%s", gateioMarketURL, gateioAPIVersion, gateioSymbol)
err := g.SendHTTPRequest(url, &result)
if err != nil {
return nil, nil
}
return result, err
}
// GetMarketInfo returns information about all trading pairs, including
// transaction fee, minimum order quantity, price accuracy and so on
func (g *Gateio) GetMarketInfo() (MarketInfoResponse, error) {
type response struct {
Result string `json:"result"`
Pairs []interface{} `json:"pairs"`
}
url := fmt.Sprintf("%s/%s/%s", gateioMarketURL, gateioAPIVersion, gateioMarketInfo)
var res response
var result MarketInfoResponse
err := g.SendHTTPRequest(url, &res)
if err != nil {
return result, err
}
result.Result = res.Result
for _, v := range res.Pairs {
item := v.(map[string]interface{})
for itemk, itemv := range item {
pairv := itemv.(map[string]interface{})
result.Pairs = append(result.Pairs, MarketInfoPairsResponse{
Symbol: itemk,
DecimalPlaces: pairv["decimal_places"].(float64),
MinAmount: pairv["min_amount"].(float64),
Fee: pairv["fee"].(float64),
})
}
}
return result, nil
}
// GetLatestSpotPrice returns latest spot price of symbol
// updated every 10 seconds
//
// symbol: string of currency pair
func (g *Gateio) GetLatestSpotPrice(symbol string) (float64, error) {
res, err := g.GetTicker(symbol)
if err != nil {
return 0, err
}
return res.Last, nil
}
// GetTicker returns a ticker for the supplied symbol
// updated every 10 seconds
func (g *Gateio) GetTicker(symbol string) (TickerResponse, error) {
url := fmt.Sprintf("%s/%s/%s/%s", gateioMarketURL, gateioAPIVersion, gateioTicker, symbol)
var res TickerResponse
err := g.SendHTTPRequest(url, &res)
if err != nil {
return res, err
}
return res, nil
}
// GetTickers returns tickers for all symbols
func (g *Gateio) GetTickers() (map[string]TickerResponse, error) {
url := fmt.Sprintf("%s/%s/%s", gateioMarketURL, gateioAPIVersion, gateioTickers)
resp := make(map[string]TickerResponse)
err := g.SendHTTPRequest(url, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
// GetOrderbook returns the orderbook data for a suppled symbol
func (g *Gateio) GetOrderbook(symbol string) (Orderbook, error) {
url := fmt.Sprintf("%s/%s/%s/%s", gateioMarketURL, gateioAPIVersion, gateioOrderbook, symbol)
var resp OrderbookResponse
err := g.SendHTTPRequest(url, &resp)
if err != nil {
return Orderbook{}, err
}
if resp.Result != "true" {
return Orderbook{}, errors.New("result was not true")
}
var ob Orderbook
// Asks are in reverse order
for x := len(resp.Asks) - 1; x != 0; x-- {
data := resp.Asks[x]
price, err := strconv.ParseFloat(data[0], 64)
if err != nil {
continue
}
amount, err := strconv.ParseFloat(data[1], 64)
if err != nil {
continue
}
ob.Asks = append(ob.Asks, OrderbookItem{Price: price, Amount: amount})
}
for x := range resp.Bids {
data := resp.Bids[x]
price, err := strconv.ParseFloat(data[0], 64)
if err != nil {
continue
}
amount, err := strconv.ParseFloat(data[1], 64)
if err != nil {
continue
}
ob.Bids = append(ob.Bids, OrderbookItem{Price: price, Amount: amount})
}
ob.Result = resp.Result
ob.Elapsed = resp.Elapsed
return ob, nil
}
// GetSpotKline returns kline data for the most recent time period
func (g *Gateio) GetSpotKline(arg KlinesRequestParams) ([]*KLineResponse, error) {
url := fmt.Sprintf("%s/%s/%s/%s?group_sec=%d&range_hour=%d", gateioMarketURL, gateioAPIVersion, gateioKline, arg.Symbol, arg.GroupSec, arg.HourSize)
var rawKlines map[string]interface{}
err := g.SendHTTPRequest(url, &rawKlines)
if err != nil {
return nil, err
}
var result []*KLineResponse
if rawKlines == nil || rawKlines["data"] == nil {
return nil, fmt.Errorf("rawKlines is nil. Err: %s", err)
}
rawKlineDatasString, _ := json.Marshal(rawKlines["data"].([]interface{}))
rawKlineDatas := [][]interface{}{}
if err := json.Unmarshal(rawKlineDatasString, &rawKlineDatas); err != nil {
return nil, fmt.Errorf("rawKlines unmarshal failed. Err: %s", err)
}
for _, k := range rawKlineDatas {
otString, _ := strconv.ParseFloat(k[0].(string), 64)
ot, err := common.TimeFromUnixTimestampFloat(otString)
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.OpenTime. Err: %s", err)
}
_vol, err := common.FloatFromString(k[1])
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.Volume. Err: %s", err)
}
_id, err := common.FloatFromString(k[0])
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.Id. Err: %s", err)
}
_close, err := common.FloatFromString(k[2])
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.Close. Err: %s", err)
}
_high, err := common.FloatFromString(k[3])
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.High. Err: %s", err)
}
_low, err := common.FloatFromString(k[4])
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.Low. Err: %s", err)
}
_open, err := common.FloatFromString(k[5])
if err != nil {
return nil, fmt.Errorf("cannot parse Kline.Open. Err: %s", err)
}
result = append(result, &KLineResponse{
ID: _id,
KlineTime: ot,
Volume: _vol,
Close: _close,
High: _high,
Low: _low,
Open: _open,
})
}
return result, nil
}
// GetBalances obtains the users account balance
func (g *Gateio) GetBalances() (BalancesResponse, error) {
var result BalancesResponse
err := g.SendAuthenticatedHTTPRequest("POST", gateioBalances, "", &result)
if err != nil {
return result, err
}
return result, nil
}
// SpotNewOrder places a new order
func (g *Gateio) SpotNewOrder(arg SpotNewOrderRequestParams) (SpotNewOrderResponse, error) {
var result SpotNewOrderResponse
// Be sure to use the correct price precision before calling this
params := fmt.Sprintf("currencyPair=%s&rate=%s&amount=%s",
arg.Symbol,
strconv.FormatFloat(arg.Price, 'f', -1, 64),
strconv.FormatFloat(arg.Amount, 'f', -1, 64),
)
strRequestURL := fmt.Sprintf("%s/%s", gateioOrder, arg.Type)
err := g.SendAuthenticatedHTTPRequest("POST", strRequestURL, params, &result)
if err != nil {
return result, err
}
return result, nil
}
// CancelOrder cancels an order given the supplied orderID and symbol
// orderID order ID number
// symbol trade pair (ltc_btc)
func (g *Gateio) CancelOrder(orderID int64, symbol string) (bool, error) {
type response struct {
Result bool `json:"result"`
Code int `json:"code"`
Message string `json:"message"`
}
var result response
// Be sure to use the correct price precision before calling this
params := fmt.Sprintf("orderNumber=%d&currencyPair=%s",
orderID,
symbol,
)
err := g.SendAuthenticatedHTTPRequest("POST", gateioCancelOrder, params, &result)
if err != nil {
return false, err
}
if !result.Result {
return false, fmt.Errorf("code:%d message:%s", result.Code, result.Message)
}
return true, nil
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (g *Gateio) SendHTTPRequest(path string, result interface{}) error {
return g.SendPayload("GET", path, nil, nil, result, false, g.Verbose)
}
// SendAuthenticatedHTTPRequest sends authenticated requests to the Gateio API
// To use this you must setup an APIKey and APISecret from the exchange
func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, result interface{}) error {
if !g.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name)
}
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
headers["key"] = g.APIKey
hmac := common.GetHMAC(common.HashSHA512, []byte(param), []byte(g.APISecret))
headers["sign"] = common.ByteArrayToString(hmac)
url := fmt.Sprintf("%s/%s/%s", gateioTradeURL, gateioAPIVersion, endpoint)
return g.SendPayload(method, url, headers, strings.NewReader(param), result, true, g.Verbose)
}

View File

@@ -0,0 +1,141 @@
package gateio
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
// Please supply your own APIKEYS here for due diligence testing
const (
apiKey = ""
apiSecret = ""
)
var g Gateio
func TestSetDefaults(t *testing.T) {
g.SetDefaults()
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
gateioConfig, err := cfg.GetExchangeConfig("GateIO")
if err != nil {
t.Error("Test Failed - GateIO Setup() init error")
}
gateioConfig.AuthenticatedAPISupport = true
gateioConfig.APIKey = apiKey
gateioConfig.APISecret = apiSecret
g.Setup(gateioConfig)
}
func TestGetSymbols(t *testing.T) {
t.Parallel()
_, err := g.GetSymbols()
if err != nil {
t.Errorf("Test failed - Gateio TestGetSymbols: %s", err)
}
}
func TestGetMarketInfo(t *testing.T) {
t.Parallel()
_, err := g.GetMarketInfo()
if err != nil {
t.Errorf("Test failed - Gateio GetMarketInfo: %s", err)
}
}
func TestSpotNewOrder(t *testing.T) {
t.Parallel()
if apiKey == "" || apiSecret == "" {
t.Skip()
}
_, err := g.SpotNewOrder(SpotNewOrderRequestParams{
Symbol: "btc_usdt",
Amount: 1.1,
Price: 10.1,
Type: SpotNewOrderRequestParamsTypeSell,
})
if err != nil {
t.Errorf("Test failed - Gateio SpotNewOrder: %s", err)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
if apiKey == "" || apiSecret == "" {
t.Skip()
}
_, err := g.CancelOrder(917591554, "btc_usdt")
if err != nil {
t.Errorf("Test failed - Gateio CancelOrder: %s", err)
}
}
func TestGetBalances(t *testing.T) {
t.Parallel()
if apiKey == "" || apiSecret == "" {
t.Skip()
}
_, err := g.GetBalances()
if err != nil {
t.Errorf("Test failed - Gateio GetBalances: %s", err)
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := g.GetLatestSpotPrice("btc_usdt")
if err != nil {
t.Errorf("Test failed - Gateio GetLatestSpotPrice: %s", err)
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := g.GetTicker("btc_usdt")
if err != nil {
t.Errorf("Test failed - Gateio GetTicker: %s", err)
}
}
func TestGetTickers(t *testing.T) {
t.Parallel()
_, err := g.GetTickers()
if err != nil {
t.Errorf("Test failed - Gateio GetTicker: %s", err)
}
}
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := g.GetOrderbook("btc_usdt")
if err != nil {
t.Errorf("Test failed - Gateio GetTicker: %s", err)
}
}
func TestGetSpotKline(t *testing.T) {
t.Parallel()
_, err := g.GetSpotKline(KlinesRequestParams{
Symbol: "btc_usdt",
GroupSec: TimeIntervalFiveMinutes, // 5 minutes or less
HourSize: 1, // 1 hour data
})
if err != nil {
t.Errorf("Test failed - Gateio GetSpotKline: %s", err)
}
}

View File

@@ -0,0 +1,127 @@
package gateio
import "time"
// SpotNewOrderRequestParamsType order type (buy or sell)
type SpotNewOrderRequestParamsType string
var (
// SpotNewOrderRequestParamsTypeBuy buy order
SpotNewOrderRequestParamsTypeBuy = SpotNewOrderRequestParamsType("buy")
// SpotNewOrderRequestParamsTypeSell sell order
SpotNewOrderRequestParamsTypeSell = SpotNewOrderRequestParamsType("sell")
)
// TimeInterval Interval represents interval enum.
type TimeInterval int
// TimeInterval vars
var (
TimeIntervalMinute = TimeInterval(60)
TimeIntervalThreeMinutes = TimeInterval(60 * 3)
TimeIntervalFiveMinutes = TimeInterval(60 * 5)
TimeIntervalFifteenMinutes = TimeInterval(60 * 15)
TimeIntervalThirtyMinutes = TimeInterval(60 * 30)
TimeIntervalHour = TimeInterval(60 * 60)
TimeIntervalTwoHours = TimeInterval(2 * 60 * 60)
TimeIntervalFourHours = TimeInterval(4 * 60 * 60)
TimeIntervalSixHours = TimeInterval(6 * 60 * 60)
TimeIntervalDay = TimeInterval(60 * 60 * 24)
)
// MarketInfoResponse holds the market info data
type MarketInfoResponse struct {
Result string `json:"result"`
Pairs []MarketInfoPairsResponse `json:"pairs"`
}
// MarketInfoPairsResponse holds the market info response data
type MarketInfoPairsResponse struct {
Symbol string
// DecimalPlaces symbol price accuracy
DecimalPlaces float64
// MinAmount minimum order amount
MinAmount float64
// Fee transaction fee
Fee float64
}
// BalancesResponse holds the user balances
type BalancesResponse struct {
Result string `json:"result"`
Available map[string]string `json:"available"`
Locked map[string]string `json:"locked"`
}
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string // Required field; example LTCBTC,BTCUSDT
HourSize int // How many hours of data
GroupSec TimeInterval
}
// KLineResponse holds the kline response data
type KLineResponse struct {
ID float64
KlineTime time.Time
Open float64
Time float64
High float64
Low float64
Close float64
Volume float64
Amount float64 `db:"amount"`
}
// TickerResponse holds the ticker response data
type TickerResponse struct {
Result string `json:"result"`
Volume float64 `json:"baseVolume,string"` // Trading volume
High float64 `json:"high24hr,string"` // 24 hour high price
Open float64 `json:"highestBid,string"` // Openening price
Last float64 `json:"last,string"` // Last price
Low float64 `json:"low24hr,string"` // 24 hour low price
Close float64 `json:"lowestAsk,string"` // Closing price
PercentChange float64 `json:"percentChange,string"` // Percentage change
QuoteVolume float64 `json:"quoteVolume,string"` // Quote currency volume
}
// OrderbookResponse stores the orderbook data
type OrderbookResponse struct {
Result string `json:"result"`
Elapsed string `json:"elapsed"`
Asks [][]string
Bids [][]string
}
// OrderbookItem stores an orderbook item
type OrderbookItem struct {
Price float64
Amount float64
}
// Orderbook stores the orderbook data
type Orderbook struct {
Result string
Elapsed string
Bids []OrderbookItem
Asks []OrderbookItem
}
// SpotNewOrderRequestParams Order params
type SpotNewOrderRequestParams struct {
Amount float64 `json:"amount"` // Order quantity
Price float64 `json:"price"` // Order price
Symbol string `json:"symbol"` // Trading pair; btc_usdt, eth_btc......
Type SpotNewOrderRequestParamsType `json:"type"` // Order type (buy or sell),
}
// SpotNewOrderResponse Order response
type SpotNewOrderResponse struct {
OrderNumber int64 `json:"orderNumber"` // OrderID number
Price float64 `json:"rate,string"` // Order price
LeftAmount float64 `json:"leftAmount,string"` // The remaining amount to fill
FilledAmount float64 `json:"filledAmount,string"` // The filled amount
Filledrate float64 `json:"filledRate,string"` // FilledPrice
}

View File

@@ -0,0 +1,177 @@
package gateio
import (
"errors"
"log"
"sync"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the GateIO go routine
func (g *Gateio) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
g.Run()
wg.Done()
}()
}
// Run implements the GateIO wrapper
func (g *Gateio) Run() {
if g.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", g.GetName(), common.IsEnabled(g.Websocket), g.WebsocketURL)
log.Printf("%s polling delay: %ds.\n", g.GetName(), g.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", g.GetName(), len(g.EnabledPairs), g.EnabledPairs)
}
symbols, err := g.GetSymbols()
if err != nil {
log.Printf("%s Unable to fetch symbols.\n", g.GetName())
} else {
err = g.UpdateCurrencies(symbols, false, false)
if err != nil {
log.Printf("%s Failed to update available currencies.\n", g.GetName())
}
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (g *Gateio) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
result, err := g.GetTickers()
if err != nil {
return tickerPrice, err
}
for _, x := range g.GetEnabledCurrencies() {
currency := exchange.FormatExchangeCurrency(g.Name, x).String()
var tp ticker.Price
tp.Pair = x
tp.High = result[currency].High
tp.Last = result[currency].Last
tp.Last = result[currency].Last
tp.Low = result[currency].Low
tp.Volume = result[currency].Volume
ticker.ProcessTicker(g.Name, x, tp, assetType)
}
return ticker.GetTicker(g.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (g *Gateio) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType)
if err != nil {
return g.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (g *Gateio) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(g.GetName(), currency, assetType)
if err != nil {
return g.UpdateOrderbook(currency, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (g *Gateio) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
currency := exchange.FormatExchangeCurrency(g.Name, p).String()
orderbookNew, err := g.GetOrderbook(currency)
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
data := orderbookNew.Bids[x]
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
for x := range orderbookNew.Asks {
data := orderbookNew.Asks[x]
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
orderbook.ProcessOrderbook(g.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(g.Name, p, assetType)
}
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// ZB exchange
func (g *Gateio) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
return response, errors.New("not implemented")
}
// GetExchangeFundTransferHistory returns funding history, deposits and
// withdrawals
func (g *Gateio) GetExchangeFundTransferHistory() ([]exchange.FundHistory, error) {
var fundHistory []exchange.FundHistory
return fundHistory, errors.New("not supported on exchange")
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (g *Gateio) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
var resp []exchange.TradeHistory
return resp, errors.New("trade history not yet implemented")
}
// SubmitExchangeOrder submits a new order
func (g *Gateio) SubmitExchangeOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (int64, error) {
return 0, errors.New("not yet implemented")
}
// ModifyExchangeOrder will allow of changing orderbook placement and limit to
// market conversion
func (g *Gateio) ModifyExchangeOrder(orderID int64, action exchange.ModifyOrder) (int64, error) {
return 0, errors.New("not yet implemented")
}
// CancelExchangeOrder cancels an order by its corresponding ID number
func (g *Gateio) CancelExchangeOrder(orderID int64) error {
return errors.New("not yet implemented")
}
// CancelAllExchangeOrders cancels all orders associated with a currency pair
func (g *Gateio) CancelAllExchangeOrders() error {
return errors.New("not yet implemented")
}
// GetExchangeOrderInfo returns information on a current open order
func (g *Gateio) GetExchangeOrderInfo(orderID int64) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, errors.New("not yet implemented")
}
// GetExchangeDepositAddress returns a deposit address for a specified currency
func (g *Gateio) GetExchangeDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawCryptoExchangeFunds returns a withdrawal ID when a withdrawal is
// submitted
func (g *Gateio) WithdrawCryptoExchangeFunds(address string, cryptocurrency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawFiatExchangeFunds returns a withdrawal ID when a
// withdrawal is submitted
func (g *Gateio) WithdrawFiatExchangeFunds(currency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawFiatExchangeFundsToInternationalBank returns a withdrawal ID when a
// withdrawal is submitted
func (g *Gateio) WithdrawFiatExchangeFundsToInternationalBank(currency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}

View File

@@ -17,7 +17,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
@@ -116,17 +116,15 @@ func (h *HUOBI) GetFee() float64 {
return h.Fee
}
// GetKline returns kline data
func (h *HUOBI) GetKline(symbol, period, size string) ([]KlineItem, error) {
// GetSpotKline returns kline data
// KlinesRequestParams contains symbol, period and size
func (h *HUOBI) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("symbol", arg.Symbol)
vals.Set("period", string(arg.Period))
if period != "" {
vals.Set("period", period)
}
if size != "" {
vals.Set("size", size)
if arg.Size != 0 {
vals.Set("size", strconv.Itoa(arg.Size))
}
type response struct {
@@ -165,12 +163,12 @@ func (h *HUOBI) GetMarketDetailMerged(symbol string) (DetailMerged, error) {
}
// GetDepth returns the depth for the specified symbol
func (h *HUOBI) GetDepth(symbol, depthType string) (Orderbook, error) {
func (h *HUOBI) GetDepth(obd OrderBookDataRequestParams) (Orderbook, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("symbol", obd.Symbol)
if depthType != "" {
vals.Set("type", depthType)
if obd.Type != OrderBookDataRequestParamsTypeNone {
vals.Set("type", string(obd.Type))
}
type response struct {
@@ -210,6 +208,22 @@ func (h *HUOBI) GetTrades(symbol string) ([]Trade, error) {
return result.Tick.Data, err
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
func (h *HUOBI) GetLatestSpotPrice(symbol string) (float64, error) {
list, err := h.GetTradeHistory(symbol, "1")
if err != nil {
return 0, err
}
if len(list) == 0 {
return 0, errors.New("The length of the list is 0")
}
return list[0].Trades[0].Price, nil
}
// GetTradeHistory returns the trades for the specified symbol
func (h *HUOBI) GetTradeHistory(symbol, size string) ([]TradeHistory, error) {
vals := url.Values{}
@@ -338,23 +352,23 @@ func (h *HUOBI) GetAccountBalance(accountID string) ([]AccountBalanceDetail, err
return result.AccountBalanceData.AccountBalanceDetails, err
}
// PlaceOrder submits an order to Huobi
func (h *HUOBI) PlaceOrder(symbol, source, accountID, orderType string, amount, price float64) (int64, error) {
// SpotNewOrder submits an order to Huobi
func (h *HUOBI) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
vals := url.Values{}
vals.Set("account-id", accountID)
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
vals.Set("account-id", fmt.Sprintf("%d", arg.AccountID))
vals.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
// Only set price if order type is not equal to buy-market or sell-market
if orderType != "buy-market" && orderType != "sell-market" {
vals.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
if arg.Type != SpotNewOrderRequestTypeBuyMarket && arg.Type != SpotNewOrderRequestTypeSellMarket {
vals.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
}
if source != "" {
vals.Set("source", source)
if arg.Source != "" {
vals.Set("source", arg.Source)
}
vals.Set("symbol", symbol)
vals.Set("type", orderType)
vals.Set("symbol", arg.Symbol)
vals.Set("type", string(arg.Type))
type response struct {
Response

View File

@@ -14,15 +14,43 @@ import (
"github.com/thrasher-/gocryptotrader/config"
)
var h HUOBI
// Please supply your own APIKEYS here for due diligence testing
// Please supply you own test keys here for due diligence testing.
const (
apiKey = ""
apiSecret = ""
)
var h HUOBI
// getDefaultConfig returns a default huobi config
func getDefaultConfig() config.ExchangeConfig {
return config.ExchangeConfig{
Name: "Huobi",
Enabled: true,
Verbose: true,
Websocket: false,
UseSandbox: false,
RESTPollingDelay: 10,
HTTPTimeout: 15000000000,
AuthenticatedAPISupport: true,
APIKey: "",
APISecret: "",
ClientID: "",
AvailablePairs: "BTC-USDT,BCH-USDT",
EnabledPairs: "BTC-USDT",
BaseCurrencies: "USD",
AssetTypes: "SPOT",
SupportsAutoPairUpdates: false,
ConfigCurrencyPairFormat: &config.CurrencyPairFormatConfig{
Uppercase: true,
Delimiter: "-",
},
RequestCurrencyPairFormat: &config.CurrencyPairFormatConfig{
Uppercase: false,
},
}
}
func TestSetDefaults(t *testing.T) {
h.SetDefaults()
}
@@ -30,16 +58,16 @@ func TestSetDefaults(t *testing.T) {
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
huobiConfig, err := cfg.GetExchangeConfig("Huobi")
hConfig, err := cfg.GetExchangeConfig("Huobi")
if err != nil {
t.Error("Test Failed - Huobi Setup() init error")
}
huobiConfig.AuthenticatedAPISupport = true
huobiConfig.APIKey = apiKey
huobiConfig.APISecret = apiSecret
hConfig.AuthenticatedAPISupport = true
hConfig.APIKey = apiKey
hConfig.APISecret = apiSecret
h.Setup(huobiConfig)
h.Setup(hConfig)
}
func TestGetFee(t *testing.T) {
@@ -49,11 +77,15 @@ func TestGetFee(t *testing.T) {
}
}
func TestGetKline(t *testing.T) {
func TestGetSpotKline(t *testing.T) {
t.Parallel()
_, err := h.GetKline("btcusdt", "1week", "")
_, err := h.GetSpotKline(KlinesRequestParams{
Symbol: "btcusdt",
Period: TimeIntervalHour,
Size: 0,
})
if err != nil {
t.Errorf("Test failed - Huobi TestGetKline: %s", err)
t.Errorf("Test failed - Huobi TestGetSpotKline: %s", err)
}
}
@@ -67,7 +99,11 @@ func TestGetMarketDetailMerged(t *testing.T) {
func TestGetDepth(t *testing.T) {
t.Parallel()
_, err := h.GetDepth("btcusdt", "step1")
_, err := h.GetDepth(OrderBookDataRequestParams{
Symbol: "btcusdt",
Type: OrderBookDataRequestParamsTypeStep1,
})
if err != nil {
t.Errorf("Test failed - Huobi TestGetDepth: %s", err)
}
@@ -81,6 +117,14 @@ func TestGetTrades(t *testing.T) {
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := h.GetLatestSpotPrice("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi GetLatestSpotPrice: %s", err)
}
}
func TestGetTradeHistory(t *testing.T) {
t.Parallel()
_, err := h.GetTradeHistory("btcusdt", "50")
@@ -123,14 +167,11 @@ func TestGetTimestamp(t *testing.T) {
func TestGetAccounts(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.GetAccounts()
if err != nil {
t.Errorf("Test failed - Huobi GetAccounts: %s", err)
@@ -139,14 +180,11 @@ func TestGetAccounts(t *testing.T) {
func TestGetAccountBalance(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
result, err := h.GetAccounts()
if err != nil {
t.Errorf("Test failed - Huobi GetAccounts: %s", err)
@@ -159,39 +197,29 @@ func TestGetAccountBalance(t *testing.T) {
}
}
func TestPlaceOrder(t *testing.T) {
func TestSpotNewOrder(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.GetAccounts()
if err != nil {
t.Errorf("Test failed - Huobi GetAccounts: %s", err)
arg := SpotNewOrderRequestParams{
Symbol: "btcusdt",
AccountID: 000000,
Amount: 0.01,
Price: 10.1,
Type: SpotNewOrderRequestTypeBuyLimit,
}
/*
userID := strconv.FormatInt(result[0].ID, 10)
_, err = h.PlaceOrder("ethusdt", "api", userID, "buy-limit", 10.1, 100.1)
if err != nil {
t.Errorf("Test failed - Huobi TestPlaceOrder: %s", err)
}
*/
_, err := h.SpotNewOrder(arg)
if err != nil {
t.Errorf("Test failed - Huobi SpotNewOrder: %s", err)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.CancelOrder(1337)
if err == nil {
@@ -201,13 +229,7 @@ func TestCancelOrder(t *testing.T) {
func TestGetOrder(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.GetOrder(1337)
if err == nil {
t.Error("Test failed - Huobi TestCancelOrder: Invalid orderID returned true")
@@ -216,13 +238,11 @@ func TestGetOrder(t *testing.T) {
func TestGetMarginLoanOrders(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.GetMarginLoanOrders("btcusdt", "", "", "", "", "", "", "")
if err != nil {
t.Errorf("Test failed - Huobi TestGetMarginLoanOrders: %s", err)
@@ -231,13 +251,11 @@ func TestGetMarginLoanOrders(t *testing.T) {
func TestGetMarginAccountBalance(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.GetMarginAccountBalance("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi TestGetMarginAccountBalance: %s", err)
@@ -246,13 +264,7 @@ func TestGetMarginAccountBalance(t *testing.T) {
func TestCancelWithdraw(t *testing.T) {
t.Parallel()
if apiKey == "" && apiSecret == "" {
t.Skip()
}
h.APIKey = apiKey
h.APISecret = apiSecret
h.AuthenticatedAPISupport = true
_, err := h.CancelWithdraw(1337)
if err == nil {
t.Error("Test failed - Huobi TestCancelWithdraw: Invalid withdraw-ID was valid")

View File

@@ -11,7 +11,7 @@ type Response struct {
// KlineItem stores a kline item
type KlineItem struct {
ID int `json:"id"`
ID int64 `json:"id"`
Open float64 `json:"open"`
Close float64 `json:"close"`
Low float64 `json:"low"`
@@ -29,6 +29,26 @@ type DetailMerged struct {
Bid []float64 `json:"bid"`
}
// OrderBookDataRequestParamsType var for request param types
type OrderBookDataRequestParamsType string
// vars for OrderBookDataRequestParamsTypes
var (
OrderBookDataRequestParamsTypeNone = OrderBookDataRequestParamsType("")
OrderBookDataRequestParamsTypeStep0 = OrderBookDataRequestParamsType("step0")
OrderBookDataRequestParamsTypeStep1 = OrderBookDataRequestParamsType("step1")
OrderBookDataRequestParamsTypeStep2 = OrderBookDataRequestParamsType("step2")
OrderBookDataRequestParamsTypeStep3 = OrderBookDataRequestParamsType("step3")
OrderBookDataRequestParamsTypeStep4 = OrderBookDataRequestParamsType("step4")
OrderBookDataRequestParamsTypeStep5 = OrderBookDataRequestParamsType("step5")
)
// OrderBookDataRequestParams represents Klines request data.
type OrderBookDataRequestParams struct {
Symbol string `json:"symbol"` // Required; example LTCBTC,BTCUSDT
Type OrderBookDataRequestParamsType `json:"type"` // step0, step1, step2, step3, step4, step5 (combined depth 0-5); when step0, no depth is merged
}
// Orderbook stores the orderbook data
type Orderbook struct {
ID int64 `json:"id"`
@@ -172,3 +192,56 @@ type MarginAccountBalance struct {
RiskRate string `json:"risk-rate"`
List []AccountBalance `json:"list"`
}
// SpotNewOrderRequestParams holds the params required to place
// an order
type SpotNewOrderRequestParams struct {
AccountID int `json:"account-id"` // Account ID, obtained using the accounts method. Curency trades use the accountid of the spot account; for loan asset transactions, please use the accountid of the margin account.
Amount float64 `json:"amount"` // The limit price indicates the quantity of the order, the market price indicates how much to buy when the order is paid, and the market price indicates how much the coin is sold when the order is sold.
Price float64 `json:"price"` // Order price, market price does not use this parameter
Source string `json:"source"` // Order source, api: API call, margin-api: loan asset transaction
Symbol string `json:"symbol"` // The symbol to use; example btcusdt, bccbtc......
Type SpotNewOrderRequestParamsType `json:"type"` // 订单类型, buy-market: 市价买, sell-market: 市价卖, buy-limit: 限价买, sell-limit: 限价卖
}
// SpotNewOrderRequestParamsType order type
type SpotNewOrderRequestParamsType string
var (
// SpotNewOrderRequestTypeBuyMarket buy market order
SpotNewOrderRequestTypeBuyMarket = SpotNewOrderRequestParamsType("buy-market")
// SpotNewOrderRequestTypeSellMarket sell market order
SpotNewOrderRequestTypeSellMarket = SpotNewOrderRequestParamsType("sell-market")
// SpotNewOrderRequestTypeBuyLimit buy limit order
SpotNewOrderRequestTypeBuyLimit = SpotNewOrderRequestParamsType("buy-limit")
// SpotNewOrderRequestTypeSellLimit sell lmit order
SpotNewOrderRequestTypeSellLimit = SpotNewOrderRequestParamsType("sell-limit")
)
//-----------
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string // Symbol to be used; example btcusdt, bccbtc......
Period TimeInterval // Kline time interval; 1min, 5min, 15min......
Size int // Size; [1-2000]
}
// TimeInterval base type
type TimeInterval string
// TimeInterval vars
var (
TimeIntervalMinute = TimeInterval("1min")
TimeIntervalFiveMinutes = TimeInterval("5min")
TimeIntervalFifteenMinutes = TimeInterval("15min")
TimeIntervalThirtyMinutes = TimeInterval("30min")
TimeIntervalHour = TimeInterval("60min")
TimeIntervalDay = TimeInterval("1day")
TimeIntervalWeek = TimeInterval("1week")
TimeIntervalMohth = TimeInterval("1mon")
TimeIntervalYear = TimeInterval("1year")
)

View File

@@ -122,7 +122,10 @@ func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := h.GetDepth(exchange.FormatExchangeCurrency(h.Name, p).String(), "step1")
orderbookNew, err := h.GetDepth(OrderBookDataRequestParams{
Symbol: exchange.FormatExchangeCurrency(h.Name, p).String(),
Type: OrderBookDataRequestParamsTypeStep1,
})
if err != nil {
return orderBook, err
}

View File

@@ -0,0 +1,796 @@
package huobihadax
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"net/url"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
huobihadaxAPIURL = "https://api.hadax.com"
huobihadaxAPIVersion = "1"
huobihadaxMarketHistoryKline = "market/history/kline"
huobihadaxMarketDetail = "market/detail"
huobihadaxMarketDetailMerged = "market/detail/merged"
huobihadaxMarketDepth = "market/depth"
huobihadaxMarketTrade = "market/trade"
huobihadaxMarketTradeHistory = "market/history/trade"
huobihadaxSymbols = "common/symbols"
huobihadaxCurrencies = "common/currencys"
huobihadaxTimestamp = "common/timestamp"
huobihadaxAccounts = "account/accounts"
huobihadaxAccountBalance = "account/accounts/%s/balance"
huobihadaxOrderPlace = "order/orders/place"
huobihadaxOrderCancel = "order/orders/%s/submitcancel"
huobihadaxOrderCancelBatch = "order/orders/batchcancel"
huobihadaxGetOrder = "order/orders/%s"
huobihadaxGetOrderMatch = "order/orders/%s/matchresults"
huobihadaxGetOrders = "order/orders"
huobihadaxGetOrdersMatch = "orders/matchresults"
huobihadaxMarginTransferIn = "dw/transfer-in/margin"
huobihadaxMarginTransferOut = "dw/transfer-out/margin"
huobihadaxMarginOrders = "margin/orders"
huobihadaxMarginRepay = "margin/orders/%s/repay"
huobihadaxMarginLoanOrders = "margin/loan-orders"
huobihadaxMarginAccountBalance = "margin/accounts/balance"
huobihadaxWithdrawCreate = "dw/withdraw/api/create"
huobihadaxWithdrawCancel = "dw/withdraw-virtual/%s/cancel"
huobihadaxAuthRate = 100
huobihadaxUnauthRate = 100
)
// HUOBIHADAX is the overarching type across this package
type HUOBIHADAX struct {
exchange.Base
}
// SetDefaults sets default values for the exchange
func (h *HUOBIHADAX) SetDefaults() {
h.Name = "HuobiHadax"
h.Enabled = false
h.Fee = 0
h.Verbose = false
h.Websocket = false
h.RESTPollingDelay = 10
h.RequestCurrencyPairFormat.Delimiter = ""
h.RequestCurrencyPairFormat.Uppercase = false
h.ConfigCurrencyPairFormat.Delimiter = "-"
h.ConfigCurrencyPairFormat.Uppercase = true
h.AssetTypes = []string{ticker.Spot}
h.SupportsAutoPairUpdating = true
h.SupportsRESTTickerBatching = false
h.Requester = request.New(h.Name, request.NewRateLimit(time.Second*10, huobihadaxAuthRate), request.NewRateLimit(time.Second*10, huobihadaxUnauthRate), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
}
// Setup sets user configuration
func (h *HUOBIHADAX) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
h.SetEnabled(false)
} else {
h.Enabled = true
h.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
h.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
h.APIAuthPEMKey = exch.APIAuthPEMKey
h.SetHTTPClientTimeout(exch.HTTPTimeout)
h.RESTPollingDelay = exch.RESTPollingDelay
h.Verbose = exch.Verbose
h.Websocket = exch.Websocket
h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := h.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = h.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
err = h.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
}
}
// GetFee returns Huobi fee
func (h *HUOBIHADAX) GetFee() float64 {
return h.Fee
}
// GetSpotKline returns kline data
// KlinesRequestParams holds the Kline request params
func (h *HUOBIHADAX) GetSpotKline(arg KlinesRequestParams) ([]KlineItem, error) {
vals := url.Values{}
vals.Set("symbol", arg.Symbol)
vals.Set("period", string(arg.Period))
if arg.Size != 0 {
vals.Set("size", strconv.Itoa(arg.Size))
}
type response struct {
Response
Data []KlineItem `json:"data"`
}
var result response
url := fmt.Sprintf("%s/%s", huobihadaxAPIURL, huobihadaxMarketHistoryKline)
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Data, err
}
// GetMarketDetailMerged returns the ticker for the specified symbol
func (h *HUOBIHADAX) GetMarketDetailMerged(symbol string) (DetailMerged, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
type response struct {
Response
Tick DetailMerged `json:"tick"`
}
var result response
url := fmt.Sprintf("%s/%s", huobihadaxAPIURL, huobihadaxMarketDetailMerged)
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
if result.ErrorMessage != "" {
return result.Tick, errors.New(result.ErrorMessage)
}
return result.Tick, err
}
// GetDepth returns the depth for the specified symbol
func (h *HUOBIHADAX) GetDepth(symbol, depthType string) (Orderbook, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
if depthType != "" {
vals.Set("type", depthType)
}
type response struct {
Response
Depth Orderbook `json:"tick"`
}
var result response
url := fmt.Sprintf("%s/%s", huobihadaxAPIURL, huobihadaxMarketDepth)
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
if result.ErrorMessage != "" {
return result.Depth, errors.New(result.ErrorMessage)
}
return result.Depth, err
}
// GetTrades returns the trades for the specified symbol
func (h *HUOBIHADAX) GetTrades(symbol string) ([]Trade, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
type response struct {
Response
Tick struct {
Data []Trade `json:"data"`
} `json:"tick"`
}
var result response
url := fmt.Sprintf("%s/%s", huobihadaxAPIURL, huobihadaxMarketTrade)
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Tick.Data, err
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
func (h *HUOBIHADAX) GetLatestSpotPrice(symbol string) (float64, error) {
list, err := h.GetTradeHistory(symbol, "1")
if err != nil {
return 0, err
}
if len(list) == 0 {
return 0, errors.New("The length of the list is 0")
}
return list[0].Trades[0].Price, nil
}
// GetTradeHistory returns the trades for the specified symbol
func (h *HUOBIHADAX) GetTradeHistory(symbol, size string) ([]TradeHistory, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
if size != "" {
vals.Set("size", size)
}
type response struct {
Response
TradeHistory []TradeHistory `json:"data"`
}
var result response
url := fmt.Sprintf("%s/%s", huobihadaxAPIURL, huobihadaxMarketTradeHistory)
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.TradeHistory, err
}
// GetMarketDetail returns the ticker for the specified symbol
func (h *HUOBIHADAX) GetMarketDetail(symbol string) (Detail, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
type response struct {
Response
Tick Detail `json:"tick"`
}
var result response
url := fmt.Sprintf("%s/%s", huobihadaxAPIURL, huobihadaxMarketDetail)
err := h.SendHTTPRequest(common.EncodeURLValues(url, vals), &result)
if result.ErrorMessage != "" {
return result.Tick, errors.New(result.ErrorMessage)
}
return result.Tick, err
}
// GetSymbols returns an array of symbols supported by Huobi
func (h *HUOBIHADAX) GetSymbols() ([]Symbol, error) {
type response struct {
Response
Symbols []Symbol `json:"data"`
}
var result response
url := fmt.Sprintf("%s/v%s/%s", huobihadaxAPIURL, huobihadaxAPIVersion, huobihadaxSymbols)
err := h.SendHTTPRequest(url, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Symbols, err
}
// GetCurrencies returns a list of currencies supported by Huobi
func (h *HUOBIHADAX) GetCurrencies() ([]string, error) {
type response struct {
Response
Currencies []string `json:"data"`
}
var result response
url := fmt.Sprintf("%s/v%s/%s", huobihadaxAPIURL, huobihadaxAPIVersion, huobihadaxCurrencies)
err := h.SendHTTPRequest(url, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Currencies, err
}
// GetTimestamp returns the Huobi server time
func (h *HUOBIHADAX) GetTimestamp() (int64, error) {
type response struct {
Response
Timestamp int64 `json:"data"`
}
var result response
url := fmt.Sprintf("%s/v%s/%s", huobihadaxAPIURL, huobihadaxAPIVersion, huobihadaxTimestamp)
err := h.SendHTTPRequest(url, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.Timestamp, err
}
// GetAccounts returns the Huobi user accounts
func (h *HUOBIHADAX) GetAccounts() ([]Account, error) {
type response struct {
Response
AccountData []Account `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest("GET", huobihadaxAccounts, url.Values{}, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.AccountData, err
}
// GetAccountBalance returns the users Huobi account balance
func (h *HUOBIHADAX) GetAccountBalance(accountID string) ([]AccountBalanceDetail, error) {
type response struct {
Response
AccountBalanceData AccountBalance `json:"data"`
}
var result response
endpoint := fmt.Sprintf(huobihadaxAccountBalance, accountID)
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.AccountBalanceData.AccountBalanceDetails, err
}
// SpotNewOrder submits an order to Huobi
func (h *HUOBIHADAX) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
vals := make(map[string]string)
vals["account-id"] = fmt.Sprintf("%d", arg.AccountID)
vals["amount"] = strconv.FormatFloat(arg.Amount, 'f', -1, 64)
// Only set price if order type is not equal to buy-market or sell-market
if arg.Type != SpotNewOrderRequestTypeBuyMarket && arg.Type != SpotNewOrderRequestTypeSellMarket {
vals["price"] = strconv.FormatFloat(arg.Price, 'f', -1, 64)
}
if arg.Source != "" {
vals["source"] = arg.Source
}
vals["symbol"] = arg.Symbol
vals["type"] = string(arg.Type)
type response struct {
Response
OrderID int64 `json:"data,string"`
}
// The API indicates that for the POST request, the parameters of each method are not signed and authenticated. That is, only the AccessKeyId, SignatureMethod, SignatureVersion, and Timestamp parameters are required for the POST request. The other parameters are placed in the body.
// So re-encode the Post parameter
bytesParams, _ := json.Marshal(vals)
postBodyParams := string(bytesParams)
if h.Verbose {
fmt.Println("Post params:", postBodyParams)
}
var result response
err := h.SendAuthenticatedHTTPPostRequest("POST", huobihadaxOrderPlace, postBodyParams, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.OrderID, err
}
// CancelOrder cancels an order on Huobi
func (h *HUOBIHADAX) CancelOrder(orderID int64) (int64, error) {
type response struct {
Response
OrderID int64 `json:"data,string"`
}
var result response
endpoint := fmt.Sprintf(huobihadaxOrderCancel, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest("POST", endpoint, url.Values{}, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.OrderID, err
}
// CancelOrderBatch cancels a batch of orders -- to-do
func (h *HUOBIHADAX) CancelOrderBatch(orderIDs []int64) (CancelOrderBatch, error) {
type response struct {
Status string `json:"status"`
Data CancelOrderBatch `json:"data"`
}
// Used to send param formatting
type postBody struct {
List []int64 `json:"order-ids"`
}
// Format to JSON
bytesParams, _ := common.JSONEncode(&postBody{List: orderIDs})
postBodyParams := string(bytesParams)
var result response
err := h.SendAuthenticatedHTTPPostRequest("POST", huobihadaxOrderCancelBatch, postBodyParams, &result)
if len(result.Data.Failed) != 0 {
errJSON, _ := common.JSONEncode(result.Data.Failed)
return CancelOrderBatch{}, errors.New(string(errJSON))
}
return result.Data, err
}
// GetOrder returns order information for the specified order
func (h *HUOBIHADAX) GetOrder(orderID int64) (OrderInfo, error) {
type response struct {
Response
Order OrderInfo `json:"data"`
}
var result response
endpoint := fmt.Sprintf(huobihadaxGetOrder, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, &result)
if result.ErrorMessage != "" {
return result.Order, errors.New(result.ErrorMessage)
}
return result.Order, err
}
// GetOrderMatchResults returns matched order info for the specified order
func (h *HUOBIHADAX) GetOrderMatchResults(orderID int64) ([]OrderMatchInfo, error) {
type response struct {
Response
Orders []OrderMatchInfo `json:"data"`
}
var result response
endpoint := fmt.Sprintf(huobihadaxGetOrderMatch, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
}
// GetOrders returns a list of orders
func (h *HUOBIHADAX) GetOrders(symbol, types, start, end, states, from, direct, size string) ([]OrderInfo, error) {
type response struct {
Response
Orders []OrderInfo `json:"data"`
}
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("states", states)
if types != "" {
vals.Set("types", types)
}
if start != "" {
vals.Set("start-date", start)
}
if end != "" {
vals.Set("end-date", end)
}
if from != "" {
vals.Set("from", from)
}
if direct != "" {
vals.Set("direct", direct)
}
if size != "" {
vals.Set("size", size)
}
var result response
err := h.SendAuthenticatedHTTPRequest("GET", huobihadaxGetOrders, vals, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
}
// GetOrdersMatch returns a list of matched orders
func (h *HUOBIHADAX) GetOrdersMatch(symbol, types, start, end, from, direct, size string) ([]OrderMatchInfo, error) {
type response struct {
Response
Orders []OrderMatchInfo `json:"data"`
}
vals := url.Values{}
vals.Set("symbol", symbol)
if types != "" {
vals.Set("types", types)
}
if start != "" {
vals.Set("start-date", start)
}
if end != "" {
vals.Set("end-date", end)
}
if from != "" {
vals.Set("from", from)
}
if direct != "" {
vals.Set("direct", direct)
}
if size != "" {
vals.Set("size", size)
}
var result response
err := h.SendAuthenticatedHTTPRequest("GET", huobihadaxGetOrdersMatch, vals, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
}
// MarginTransfer transfers assets into or out of the margin account
func (h *HUOBIHADAX) MarginTransfer(symbol, currency string, amount float64, in bool) (int64, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("currency", currency)
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
path := huobihadaxMarginTransferIn
if !in {
path = huobihadaxMarginTransferOut
}
type response struct {
Response
TransferID int64 `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest("POST", path, vals, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.TransferID, err
}
// MarginOrder submits a margin order application
func (h *HUOBIHADAX) MarginOrder(symbol, currency string, amount float64) (int64, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("currency", currency)
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
type response struct {
Response
MarginOrderID int64 `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest("POST", huobihadaxMarginOrders, vals, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.MarginOrderID, err
}
// MarginRepayment repays a margin amount for a margin ID
func (h *HUOBIHADAX) MarginRepayment(orderID int64, amount float64) (int64, error) {
vals := url.Values{}
vals.Set("order-id", strconv.FormatInt(orderID, 10))
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
type response struct {
Response
MarginOrderID int64 `json:"data"`
}
var result response
endpoint := fmt.Sprintf(huobihadaxMarginRepay, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest("POST", endpoint, vals, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.MarginOrderID, err
}
// GetMarginLoanOrders returns the margin loan orders
func (h *HUOBIHADAX) GetMarginLoanOrders(symbol, currency, start, end, states, from, direct, size string) ([]MarginOrder, error) {
vals := url.Values{}
vals.Set("symbol", symbol)
vals.Set("currency", currency)
if start != "" {
vals.Set("start-date", start)
}
if end != "" {
vals.Set("end-date", end)
}
if states != "" {
vals.Set("states", states)
}
if from != "" {
vals.Set("from", from)
}
if direct != "" {
vals.Set("direct", direct)
}
if size != "" {
vals.Set("size", size)
}
type response struct {
Response
MarginLoanOrders []MarginOrder `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest("GET", huobihadaxMarginLoanOrders, vals, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.MarginLoanOrders, err
}
// GetMarginAccountBalance returns the margin account balances
func (h *HUOBIHADAX) GetMarginAccountBalance(symbol string) ([]MarginAccountBalance, error) {
type response struct {
Response
Balances []MarginAccountBalance `json:"data"`
}
vals := url.Values{}
if symbol != "" {
vals.Set("symbol", symbol)
}
var result response
err := h.SendAuthenticatedHTTPRequest("GET", huobihadaxMarginAccountBalance, vals, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Balances, err
}
// Withdraw withdraws the desired amount and currency
func (h *HUOBIHADAX) Withdraw(address, currency, addrTag string, amount, fee float64) (int64, error) {
type response struct {
Response
WithdrawID int64 `json:"data"`
}
vals := url.Values{}
vals.Set("address", address)
vals.Set("currency", currency)
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
if fee != 0 {
vals.Set("fee", strconv.FormatFloat(fee, 'f', -1, 64))
}
if currency == "XRP" {
vals.Set("addr-tag", addrTag)
}
var result response
err := h.SendAuthenticatedHTTPRequest("POST", huobihadaxWithdrawCreate, vals, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.WithdrawID, err
}
// CancelWithdraw cancels a withdraw request
func (h *HUOBIHADAX) CancelWithdraw(withdrawID int64) (int64, error) {
type response struct {
Response
WithdrawID int64 `json:"data"`
}
vals := url.Values{}
vals.Set("withdraw-id", strconv.FormatInt(withdrawID, 10))
var result response
endpoint := fmt.Sprintf(huobihadaxWithdrawCancel, strconv.FormatInt(withdrawID, 10))
err := h.SendAuthenticatedHTTPRequest("POST", endpoint, vals, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.WithdrawID, err
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (h *HUOBIHADAX) SendHTTPRequest(path string, result interface{}) error {
return h.SendPayload("GET", path, nil, nil, result, false, h.Verbose)
}
// SendAuthenticatedHTTPPostRequest sends authenticated requests to the HUOBI API
func (h *HUOBIHADAX) SendAuthenticatedHTTPPostRequest(method, endpoint, postBodyValues string, result interface{}) error {
if !h.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
}
signatureParams := url.Values{}
signatureParams.Set("AccessKeyId", h.APIKey)
signatureParams.Set("SignatureMethod", "HmacSHA256")
signatureParams.Set("SignatureVersion", "2")
signatureParams.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05"))
endpoint = fmt.Sprintf("/v%s/%s", huobihadaxAPIVersion, endpoint)
payload := fmt.Sprintf("%s\napi.huobi.pro\n%s\n%s",
method, endpoint, signatureParams.Encode())
headers := make(map[string]string)
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36"
headers["Content-Type"] = "application/json"
headers["Accept-Language"] = "zh-cn"
hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret))
signatureParams.Set("Signature", common.Base64Encode(hmac))
url := fmt.Sprintf("%s%s", huobihadaxAPIURL, endpoint)
url = common.EncodeURLValues(url, signatureParams)
return h.SendPayload(method, url, headers, bytes.NewBufferString(postBodyValues), result, true, h.Verbose)
}
// SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API
func (h *HUOBIHADAX) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error {
if !h.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
}
values.Set("AccessKeyId", h.APIKey)
values.Set("SignatureMethod", "HmacSHA256")
values.Set("SignatureVersion", "2")
values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05"))
endpoint = fmt.Sprintf("/v%s/%s", huobihadaxAPIVersion, endpoint)
payload := fmt.Sprintf("%s\napi.huobi.pro\n%s\n%s",
method, endpoint, values.Encode())
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret))
values.Set("Signature", common.Base64Encode(hmac))
url := fmt.Sprintf("%s%s", huobihadaxAPIURL, endpoint)
url = common.EncodeURLValues(url, values)
return h.SendPayload(method, url, headers, bytes.NewBufferString(""), result, true, h.Verbose)
}

View File

@@ -0,0 +1,277 @@
package huobihadax
import (
"fmt"
"strconv"
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
// Please supply your own APIKEYS here for due diligence testing
const (
apiKey = ""
apiSecret = ""
)
var h HUOBIHADAX
// getDefaultConfig returns a default huobi config
func getDefaultConfig() config.ExchangeConfig {
return config.ExchangeConfig{
Name: "huobihadax",
Enabled: true,
Verbose: true,
Websocket: false,
UseSandbox: false,
RESTPollingDelay: 10,
HTTPTimeout: 15000000000,
AuthenticatedAPISupport: true,
APIKey: "",
APISecret: "",
ClientID: "",
AvailablePairs: "BTC-USDT,BCH-USDT",
EnabledPairs: "BTC-USDT",
BaseCurrencies: "USD",
AssetTypes: "SPOT",
SupportsAutoPairUpdates: false,
ConfigCurrencyPairFormat: &config.CurrencyPairFormatConfig{
Uppercase: true,
Delimiter: "-",
},
RequestCurrencyPairFormat: &config.CurrencyPairFormatConfig{
Uppercase: false,
},
}
}
func TestSetDefaults(t *testing.T) {
h.SetDefaults()
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
hadaxConfig, err := cfg.GetExchangeConfig("HuobiHadax")
if err != nil {
t.Error("Test Failed - HuobiHadax Setup() init error")
}
hadaxConfig.AuthenticatedAPISupport = true
hadaxConfig.APIKey = apiKey
hadaxConfig.APISecret = apiSecret
h.Setup(hadaxConfig)
}
func TestGetFee(t *testing.T) {
t.Parallel()
if h.GetFee() != 0 {
t.Errorf("test failed - Huobi GetFee() error")
}
}
func TestGetSpotKline(t *testing.T) {
t.Parallel()
_, err := h.GetSpotKline(KlinesRequestParams{
Symbol: "btcusdt",
Period: TimeIntervalHour,
Size: 0,
})
if err != nil {
t.Errorf("Test failed - Huobi TestGetSpotKline: %s", err)
}
}
func TestGetMarketDetailMerged(t *testing.T) {
t.Parallel()
_, err := h.GetMarketDetailMerged("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi TestGetMarketDetailMerged: %s", err)
}
}
func TestGetDepth(t *testing.T) {
t.Parallel()
_, err := h.GetDepth("btcusdt", "step1")
if err != nil {
t.Errorf("Test failed - Huobi TestGetDepth: %s", err)
}
}
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := h.GetTrades("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi TestGetTrades: %s", err)
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := h.GetLatestSpotPrice("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi GetLatestSpotPrice: %s", err)
}
}
func TestGetTradeHistory(t *testing.T) {
t.Parallel()
_, err := h.GetTradeHistory("btcusdt", "50")
if err != nil {
t.Errorf("Test failed - Huobi TestGetTradeHistory: %s", err)
}
}
func TestGetMarketDetail(t *testing.T) {
t.Parallel()
_, err := h.GetMarketDetail("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi TestGetTradeHistory: %s", err)
}
}
func TestGetSymbols(t *testing.T) {
t.Parallel()
_, err := h.GetSymbols()
if err != nil {
t.Errorf("Test failed - Huobi TestGetSymbols: %s", err)
}
}
func TestGetCurrencies(t *testing.T) {
t.Parallel()
_, err := h.GetCurrencies()
if err != nil {
t.Errorf("Test failed - Huobi TestGetCurrencies: %s", err)
}
}
func TestGetTimestamp(t *testing.T) {
t.Parallel()
_, err := h.GetTimestamp()
if err != nil {
t.Errorf("Test failed - Huobi TestGetTimestamp: %s", err)
}
}
func TestGetAccounts(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
_, err := h.GetAccounts()
if err != nil {
t.Errorf("Test failed - Huobi GetAccounts: %s", err)
}
}
func TestGetAccountBalance(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
result, err := h.GetAccounts()
if err != nil {
t.Errorf("Test failed - Huobi GetAccounts: %s", err)
}
userID := strconv.FormatInt(result[0].ID, 10)
_, err = h.GetAccountBalance(userID)
if err != nil {
t.Errorf("Test failed - Huobi GetAccountBalance: %s", err)
}
}
func TestSpotNewOrder(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
arg := SpotNewOrderRequestParams{
Symbol: "btcusdt",
AccountID: 000000,
Amount: 0.01,
Price: 10.1,
Type: SpotNewOrderRequestTypeBuyLimit,
}
newOrderID, err := h.SpotNewOrder(arg)
if err != nil {
t.Errorf("Test failed - Huobi SpotNewOrder: %s", err)
} else {
fmt.Println(newOrderID)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
_, err := h.CancelOrder(1337)
if err == nil {
t.Error("Test failed - Huobi TestCancelOrder: Invalid orderID returned true")
}
}
func TestGetOrder(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
_, err := h.GetOrder(1337)
if err == nil {
t.Error("Test failed - Huobi TestCancelOrder: Invalid orderID returned true")
}
}
func TestGetMarginLoanOrders(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
_, err := h.GetMarginLoanOrders("btcusdt", "", "", "", "", "", "", "")
if err != nil {
t.Errorf("Test failed - Huobi TestGetMarginLoanOrders: %s", err)
}
}
func TestGetMarginAccountBalance(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
_, err := h.GetMarginAccountBalance("btcusdt")
if err != nil {
t.Errorf("Test failed - Huobi TestGetMarginAccountBalance: %s", err)
}
}
func TestCancelWithdraw(t *testing.T) {
t.Parallel()
if h.APIKey == "" || h.APISecret == "" || h.APIAuthPEMKey == "" {
t.Skip()
}
_, err := h.CancelWithdraw(1337)
if err == nil {
t.Error("Test failed - Huobi TestCancelWithdraw: Invalid withdraw-ID was valid")
}
}

View File

@@ -0,0 +1,225 @@
package huobihadax
// Response stores the Huobi response information
type Response struct {
Status string `json:"status"`
Channel string `json:"ch"`
Timestamp int64 `json:"ts"`
ErrorCode string `json:"err-code"`
ErrorMessage string `json:"err-msg"`
}
// KlineItem stores a kline item
type KlineItem struct {
ID int64 `json:"id"`
Open float64 `json:"open"`
Close float64 `json:"close"`
Low float64 `json:"low"`
High float64 `json:"high"`
Amount float64 `json:"amount"`
Vol float64 `json:"vol"`
Count int `json:"count"`
}
// DetailMerged stores the ticker detail merged data
type DetailMerged struct {
Detail
Version int `json:"version"`
Ask []float64 `json:"ask"`
Bid []float64 `json:"bid"`
}
// Orderbook stores the orderbook data
type Orderbook struct {
ID int64 `json:"id"`
Timetstamp int64 `json:"ts"`
Bids [][]float64 `json:"bids"`
Asks [][]float64 `json:"asks"`
}
// Trade stores the trade data
type Trade struct {
ID float64 `json:"id"`
Price float64 `json:"price"`
Amount float64 `json:"amount"`
Direction string `json:"direction"`
Timestamp int64 `json:"ts"`
}
// TradeHistory stores the the trade history data
type TradeHistory struct {
ID int64 `json:"id"`
Timestamp int64 `json:"ts"`
Trades []Trade `json:"data"`
}
// Detail stores the ticker detail data
type Detail struct {
Amount float64 `json:"amount"`
Open float64 `json:"open"`
Close float64 `json:"close"`
High float64 `json:"high"`
Timestamp int64 `json:"timestamp"`
ID int `json:"id"`
Count int `json:"count"`
Low float64 `json:"low"`
Volume float64 `json:"vol"`
}
// Symbol stores the symbol data
type Symbol struct {
BaseCurrency string `json:"base-currency"`
QuoteCurrency string `json:"quote-currency"`
PricePrecision int `json:"price-precision"`
AmountPrecision int `json:"amount-precision"`
SymbolPartition string `json:"symbol-partition"`
}
// Account stores the account data
type Account struct {
ID int64 `json:"id"`
Type string `json:"type"`
State string `json:"working"`
UserID int64 `json:"user-id"`
}
// AccountBalance stores the user all account balance
type AccountBalance struct {
ID int64 `json:"id"`
Type string `json:"type"`
State string `json:"state"`
AccountBalanceDetails []AccountBalanceDetail `json:"list"`
}
// AccountBalanceDetail stores the user account balance
type AccountBalanceDetail struct {
Currency string `json:"currency"`
Type string `json:"type"`
Balance float64 `json:"balance,string"`
}
// CancelOrderBatch stores the cancel order batch data
type CancelOrderBatch struct {
Success []string `json:"success"`
Failed []struct {
OrderID int64 `json:"order-id,string"`
ErrorCode string `json:"err-code"`
ErrorMessage string `json:"err-msg"`
} `json:"failed"`
}
// OrderInfo stores the order info
type OrderInfo struct {
ID int `json:"id"`
Symbol string `json:"symbol"`
AccountID int `json:"account-id"`
Amount string `json:"amount"`
Price string `json:"price"`
CreatedAt int64 `json:"created-at"`
Type string `json:"type"`
FieldAmount string `json:"field-amount"`
FieldCashAmount string `json:"field-cash-amount"`
FieldFees string `json:"field-fees"`
FinishedAt int64 `json:"finished-at"`
UserID int `json:"user-id"`
Source string `json:"source"`
State string `json:"state"`
CanceledAt int `json:"canceled-at"`
Exchange string `json:"exchange"`
Batch string `json:"batch"`
}
// OrderMatchInfo stores the order match info
type OrderMatchInfo struct {
ID int `json:"id"`
OrderID int `json:"order-id"`
MatchID int `json:"match-id"`
Symbol string `json:"symbol"`
Type string `json:"type"`
Source string `json:"source"`
Price string `json:"price"`
FilledAmount string `json:"filled-amount"`
FilledFees string `json:"filled-fees"`
CreatedAt int64 `json:"created-at"`
}
// MarginOrder stores the margin order info
type MarginOrder struct {
Currency string `json:"currency"`
Symbol string `json:"symbol"`
AccruedAt int64 `json:"accrued-at"`
LoanAmount string `json:"loan-amount"`
LoanBalance string `json:"loan-balance"`
InterestBalance string `json:"interest-balance"`
CreatedAt int64 `json:"created-at"`
InterestAmount string `json:"interest-amount"`
InterestRate string `json:"interest-rate"`
AccountID int `json:"account-id"`
UserID int `json:"user-id"`
UpdatedAt int64 `json:"updated-at"`
ID int `json:"id"`
State string `json:"state"`
}
// MarginAccountBalance stores the margin account balance info
type MarginAccountBalance struct {
ID int `json:"id"`
Type string `json:"type"`
State string `json:"state"`
Symbol string `json:"symbol"`
FlPrice string `json:"fl-price"`
FlType string `json:"fl-type"`
RiskRate string `json:"risk-rate"`
List []AccountBalance `json:"list"`
}
// SpotNewOrderRequestParams holds the params required to place
// an order
type SpotNewOrderRequestParams struct {
AccountID int `json:"account-id"` // Account ID, obtained using the accounts method. Curency trades use the accountid of the spot account; for loan asset transactions, please use the accountid of the margin account.
Amount float64 `json:"amount"` // The limit price indicates the quantity of the order, the market price indicates how much to buy when the order is paid, and the market price indicates how much the coin is sold when the order is sold.
Price float64 `json:"price"` // Order price, market price does not use this parameter
Source string `json:"source"` // Order source, api: API call, margin-api: loan asset transaction
Symbol string `json:"symbol"` // The symbol to use; example btcusdt, bccbtc......
Type SpotNewOrderRequestParamsType `json:"type"` // Order type as listed below (buy-market, sell-market etc)
}
// SpotNewOrderRequestParamsType order types
type SpotNewOrderRequestParamsType string
var (
// SpotNewOrderRequestTypeBuyMarket buy market order
SpotNewOrderRequestTypeBuyMarket = SpotNewOrderRequestParamsType("buy-market")
// SpotNewOrderRequestTypeSellMarket sell market order
SpotNewOrderRequestTypeSellMarket = SpotNewOrderRequestParamsType("sell-market")
// SpotNewOrderRequestTypeBuyLimit buy limit order
SpotNewOrderRequestTypeBuyLimit = SpotNewOrderRequestParamsType("buy-limit")
// SpotNewOrderRequestTypeSellLimit sell limit order
SpotNewOrderRequestTypeSellLimit = SpotNewOrderRequestParamsType("sell-limit")
)
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string // Symbol; btcusdt, bccbtc......
Period TimeInterval // Kline data time interval; 1min, 5min, 15min......
Size int // Size [1-2000]
}
// TimeInterval base value
type TimeInterval string
// TimeInterval vars
var (
TimeIntervalMinute = TimeInterval("1min")
TimeIntervalFiveMinutes = TimeInterval("5min")
TimeIntervalFifteenMinutes = TimeInterval("15min")
TimeIntervalThirtyMinutes = TimeInterval("30min")
TimeIntervalHour = TimeInterval("60min")
TimeIntervalDay = TimeInterval("1day")
TimeIntervalWeek = TimeInterval("1week")
TimeIntervalMohth = TimeInterval("1mon")
TimeIntervalYear = TimeInterval("1year")
)

View File

@@ -0,0 +1,178 @@
package huobihadax
import (
"errors"
"log"
"sync"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the OKEX go routine
func (h *HUOBIHADAX) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
h.Run()
wg.Done()
}()
}
// Run implements the OKEX wrapper
func (h *HUOBIHADAX) Run() {
if h.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket), h.WebsocketURL)
log.Printf("%s polling delay: %ds.\n", h.GetName(), h.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", h.GetName(), len(h.EnabledPairs), h.EnabledPairs)
}
exchangeProducts, err := h.GetSymbols()
if err != nil {
log.Printf("%s Failed to get available symbols.\n", h.GetName())
} else {
var currencies []string
for x := range exchangeProducts {
newCurrency := exchangeProducts[x].BaseCurrency + "-" + exchangeProducts[x].QuoteCurrency
currencies = append(currencies, newCurrency)
}
err = h.UpdateCurrencies(currencies, false, false)
if err != nil {
log.Printf("%s Failed to update available currencies.\n", h.GetName())
}
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (h *HUOBIHADAX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := h.GetMarketDetailMerged(exchange.FormatExchangeCurrency(h.Name, p).String())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Low = tick.Low
tickerPrice.Last = tick.Close
tickerPrice.Volume = tick.Volume
tickerPrice.High = tick.High
tickerPrice.Ask = tick.Ask[0]
tickerPrice.Bid = tick.Bid[0]
ticker.ProcessTicker(h.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(h.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (h *HUOBIHADAX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType)
if err != nil {
return h.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (h *HUOBIHADAX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(h.GetName(), p, assetType)
if err != nil {
return h.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (h *HUOBIHADAX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := h.GetDepth(exchange.FormatExchangeCurrency(h.Name, p).String(), "step1")
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
data := orderbookNew.Bids[x]
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]})
}
for x := range orderbookNew.Asks {
data := orderbookNew.Asks[x]
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
}
orderbook.ProcessOrderbook(h.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(h.Name, p, assetType)
}
//GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// HUOBIHADAX exchange - to-do
func (h *HUOBIHADAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = h.GetName()
return response, nil
}
// GetExchangeFundTransferHistory returns funding history, deposits and
// withdrawals
func (h *HUOBIHADAX) GetExchangeFundTransferHistory() ([]exchange.FundHistory, error) {
var fundHistory []exchange.FundHistory
return fundHistory, errors.New("not supported on exchange")
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (h *HUOBIHADAX) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
var resp []exchange.TradeHistory
return resp, errors.New("trade history not yet implemented")
}
// SubmitExchangeOrder submits a new order
func (h *HUOBIHADAX) SubmitExchangeOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (int64, error) {
return 0, errors.New("not yet implemented")
}
// ModifyExchangeOrder will allow of changing orderbook placement and limit to
// market conversion
func (h *HUOBIHADAX) ModifyExchangeOrder(orderID int64, action exchange.ModifyOrder) (int64, error) {
return 0, errors.New("not yet implemented")
}
// CancelExchangeOrder cancels an order by its corresponding ID number
func (h *HUOBIHADAX) CancelExchangeOrder(orderID int64) error {
return errors.New("not yet implemented")
}
// CancelAllExchangeOrders cancels all orders associated with a currency pair
func (h *HUOBIHADAX) CancelAllExchangeOrders() error {
return errors.New("not yet implemented")
}
// GetExchangeOrderInfo returns information on a current open order
func (h *HUOBIHADAX) GetExchangeOrderInfo(orderID int64) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, errors.New("not yet implemented")
}
// GetExchangeDepositAddress returns a deposit address for a specified currency
func (h *HUOBIHADAX) GetExchangeDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawCryptoExchangeFunds returns a withdrawal ID when a withdrawal is
// submitted
func (h *HUOBIHADAX) WithdrawCryptoExchangeFunds(address string, cryptocurrency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawFiatExchangeFunds returns a withdrawal ID when a
// withdrawal is submitted
func (h *HUOBIHADAX) WithdrawFiatExchangeFunds(currency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawFiatExchangeFundsToInternationalBank returns a withdrawal ID when a
// withdrawal is submitted
func (h *HUOBIHADAX) WithdrawFiatExchangeFundsToInternationalBank(currency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}

View File

@@ -49,10 +49,10 @@ const (
// Spot requests
// Unauthenticated
spotPrice = "ticker"
spotDepth = "depth"
spotTrades = "trades"
spotCandstickData = "kline"
spotPrice = "ticker"
spotDepth = "depth"
spotTrades = "trades"
spotKline = "kline"
// Authenticated
spotUserInfo = "userinfo"
@@ -608,6 +608,87 @@ func (o *OKEX) GetContractFuturesTradeHistory(symbol, date string, since int) er
return nil
}
// GetUserInfo returns the user info
func (o *OKEX) GetUserInfo() (SpotUserInfo, error) {
strRequestURL := fmt.Sprintf("%s%s%s.do", apiURL, apiVersion, spotUserInfo)
var res SpotUserInfo
err := o.SendAuthenticatedHTTPRequest(strRequestURL, url.Values{}, &res)
if err != nil {
return res, err
}
return res, nil
}
// SpotNewOrder creates a new spot order
func (o *OKEX) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
type response struct {
Result bool `json:"result"`
OrderID int64 `json:"order_id"`
}
var res response
strRequestURL := fmt.Sprintf("%s%s%s.do", apiURL, apiVersion, spotTrade)
params := url.Values{}
params.Set("symbol", arg.Symbol)
params.Set("type", string(arg.Type))
params.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
err := o.SendAuthenticatedHTTPRequest(strRequestURL, params, &res)
if err != nil {
return res.OrderID, err
}
return res.OrderID, nil
}
// SpotCancelOrder cancels a spot order
// symbol such as ltc_btc
// orderID orderID
// returns orderID or an error
func (o *OKEX) SpotCancelOrder(symbol string, argOrderID int64) (int64, error) {
type response struct {
Result bool `json:"result"`
OrderID string `json:"order_id"`
ErrorCode int `json:"error_code"`
}
var res response
strRequestURL := fmt.Sprintf("%s%s%s.do", apiURL, apiVersion, spotCancelTrade)
params := url.Values{}
params.Set("symbol", symbol)
params.Set("order_id", strconv.FormatInt(argOrderID, 10))
err := o.SendAuthenticatedHTTPRequest(strRequestURL, params, &res)
var returnOrderID int64
if err != nil && res.ErrorCode != 0 {
return returnOrderID, err
}
if res.ErrorCode != 0 {
return returnOrderID, fmt.Errorf("ErrCode:%d ErrMsg:%s", res.ErrorCode, o.ErrorCodes[strconv.Itoa(res.ErrorCode)])
}
returnOrderID, _ = common.Int64FromString(res.OrderID)
return returnOrderID, nil
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
func (o *OKEX) GetLatestSpotPrice(symbol string) (float64, error) {
spotPrice, err := o.GetSpotTicker(symbol)
if err != nil {
return 0, err
}
return spotPrice.Ticker.Last, nil
}
// GetSpotTicker returns Price Ticker
func (o *OKEX) GetSpotTicker(symbol string) (SpotPrice, error) {
var resp SpotPrice
@@ -628,13 +709,13 @@ func (o *OKEX) GetSpotTicker(symbol string) (SpotPrice, error) {
}
//GetSpotMarketDepth returns Market Depth
func (o *OKEX) GetSpotMarketDepth(symbol, size string) (ActualSpotDepth, error) {
func (o *OKEX) GetSpotMarketDepth(asd ActualSpotDepthRequestParams) (ActualSpotDepth, error) {
resp := SpotDepth{}
fullDepth := ActualSpotDepth{}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("size", size)
values.Set("symbol", asd.Symbol)
values.Set("size", fmt.Sprintf("%d", asd.Size))
path := fmt.Sprintf("%s%s%s.do?%s", apiURL, apiVersion, "depth", values.Encode())
@@ -685,13 +766,13 @@ func (o *OKEX) GetSpotMarketDepth(symbol, size string) (ActualSpotDepth, error)
}
// GetSpotRecentTrades returns recent trades
func (o *OKEX) GetSpotRecentTrades(symbol, since string) ([]ActualSpotTradeHistory, error) {
func (o *OKEX) GetSpotRecentTrades(ast ActualSpotTradeHistoryRequestParams) ([]ActualSpotTradeHistory, error) {
actualTradeHistory := []ActualSpotTradeHistory{}
var resp interface{}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("since", since)
values.Set("symbol", ast.Symbol)
values.Set("since", fmt.Sprintf("%d", ast.Since))
path := fmt.Sprintf("%s%s%s.do?%s", apiURL, apiVersion, "trades", values.Encode())
@@ -719,18 +800,21 @@ func (o *OKEX) GetSpotRecentTrades(symbol, since string) ([]ActualSpotTradeHisto
return actualTradeHistory, nil
}
// GetSpotCandleStick returns candlestick data
//
func (o *OKEX) GetSpotCandleStick(symbol, typeInput string, size, since int) ([]CandleStickData, error) {
// GetSpotKline returns candlestick data
func (o *OKEX) GetSpotKline(arg KlinesRequestParams) ([]CandleStickData, error) {
var candleData []CandleStickData
values := url.Values{}
values.Set("symbol", symbol)
values.Set("type", typeInput)
values.Set("size", strconv.FormatInt(int64(size), 10))
values.Set("since", strconv.FormatInt(int64(since), 10))
values.Set("symbol", arg.Symbol)
values.Set("type", string(arg.Type))
if arg.Size != 0 {
values.Set("size", strconv.FormatInt(int64(arg.Size), 10))
}
if arg.Since != 0 {
values.Set("since", strconv.FormatInt(int64(arg.Since), 10))
}
path := fmt.Sprintf("%s%s%s.do?%s", apiURL, apiVersion, "kline", values.Encode())
path := fmt.Sprintf("%s%s%s.do?%s", apiURL, apiVersion, spotKline, values.Encode())
var resp interface{}
if err := o.SendHTTPRequest(path, &resp); err != nil {

View File

@@ -188,6 +188,14 @@ func TestGetContractFuturesTradeHistory(t *testing.T) {
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := o.GetLatestSpotPrice("ltc_btc")
if err != nil {
t.Error("Test failed - okex GetLatestSpotPrice() error", err)
}
}
func TestGetSpotTicker(t *testing.T) {
t.Parallel()
_, err := o.GetSpotTicker("ltc_btc")
@@ -198,7 +206,10 @@ func TestGetSpotTicker(t *testing.T) {
func TestGetSpotMarketDepth(t *testing.T) {
t.Parallel()
_, err := o.GetSpotMarketDepth("eth_btc", "2")
_, err := o.GetSpotMarketDepth(ActualSpotDepthRequestParams{
Symbol: "eth_btc",
Size: 2,
})
if err != nil {
t.Error("Test failed - okex GetSpotMarketDepth() error", err)
}
@@ -206,16 +217,68 @@ func TestGetSpotMarketDepth(t *testing.T) {
func TestGetSpotRecentTrades(t *testing.T) {
t.Parallel()
_, err := o.GetSpotRecentTrades("ltc_btc", "0")
_, err := o.GetSpotRecentTrades(ActualSpotTradeHistoryRequestParams{
Symbol: "ltc_btc",
Since: 0,
})
if err != nil {
t.Error("Test failed - okex GetSpotRecentTrades() error", err)
}
}
func TestGetSpotCandleStick(t *testing.T) {
func TestGetSpotKline(t *testing.T) {
t.Parallel()
_, err := o.GetSpotCandleStick("ltc_btc", "1min", 2, 0)
arg := KlinesRequestParams{
Symbol: "ltc_btc",
Type: TimeIntervalFiveMinutes,
Size: 100,
}
_, err := o.GetSpotKline(arg)
if err != nil {
t.Error("Test failed - okex GetSpotCandleStick() error", err)
}
}
func TestSpotNewOrder(t *testing.T) {
t.Parallel()
if o.APIKey == "" || o.APISecret == "" {
t.Skip()
}
_, err := o.SpotNewOrder(SpotNewOrderRequestParams{
Symbol: "ltc_btc",
Amount: 1.1,
Price: 10.1,
Type: SpotNewOrderRequestTypeBuy,
})
if err != nil {
t.Error("Test failed - okex SpotNewOrder() error", err)
}
}
func TestSpotCancelOrder(t *testing.T) {
t.Parallel()
if o.APIKey == "" || o.APISecret == "" {
t.Skip()
}
_, err := o.SpotCancelOrder("ltc_btc", 519158961)
if err != nil {
t.Error("Test failed - okex SpotCancelOrder() error", err)
}
}
func TestGetUserInfo(t *testing.T) {
t.Parallel()
if o.APIKey == "" || o.APISecret == "" {
t.Skip()
}
_, err := o.GetUserInfo()
if err != nil {
t.Error("Test failed - okex GetUserInfo() error", err)
}
}

View File

@@ -135,6 +135,12 @@ type SpotDepth struct {
Error interface{} `json:"error_code"`
}
// ActualSpotDepthRequestParams represents Klines request data.
type ActualSpotDepthRequestParams struct {
Symbol string `json:"symbol"` // Symbol; example ltc_btc
Size int `json:"size"` // value: 1-200
}
// ActualSpotDepth better manipulated structure to return
type ActualSpotDepth struct {
Asks []struct {
@@ -147,6 +153,12 @@ type ActualSpotDepth struct {
}
}
// ActualSpotTradeHistoryRequestParams represents Klines request data.
type ActualSpotTradeHistoryRequestParams struct {
Symbol string `json:"symbol"` // Symbol; example ltc_btc
Since int `json:"since"` // TID; transaction record ID (return data does not include the current TID value, returning up to 600 items)
}
// ActualSpotTradeHistory holds contract trade history
type ActualSpotTradeHistory struct {
Amount float64 `json:"amount"`
@@ -156,3 +168,61 @@ type ActualSpotTradeHistory struct {
TID float64 `json:"tid"`
Type string `json:"buy"`
}
// SpotUserInfo holds the spot user info
type SpotUserInfo struct {
Result bool `json:"result"`
Info map[string]map[string]map[string]string `json:"info"`
}
// SpotNewOrderRequestParams holds the params for making a new spot order
type SpotNewOrderRequestParams struct {
Amount float64 `json:"amount"` // Order quantity
Price float64 `json:"price"` // Order price
Symbol string `json:"symbol"` // Symbol; example btc_usdt, eth_btc......
Type SpotNewOrderRequestType `json:"type"` // Order type (see below)
}
// SpotNewOrderRequestType order type
type SpotNewOrderRequestType string
var (
// SpotNewOrderRequestTypeBuy buy order
SpotNewOrderRequestTypeBuy = SpotNewOrderRequestType("buy")
// SpotNewOrderRequestTypeSell sell order
SpotNewOrderRequestTypeSell = SpotNewOrderRequestType("sell")
// SpotNewOrderRequestTypeBuyMarket buy market order
SpotNewOrderRequestTypeBuyMarket = SpotNewOrderRequestType("buy_market")
// SpotNewOrderRequestTypeSellMarket sell market order
SpotNewOrderRequestTypeSellMarket = SpotNewOrderRequestType("sell_market")
)
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string // Symbol; example btcusdt, bccbtc......
Type TimeInterval // Kline data time interval; 1min, 5min, 15min......
Size int // Size; [1-2000]
Since int64 // Since timestamp, return data after the specified timestamp (for example, 1417536000000)
}
// TimeInterval represents interval enum.
type TimeInterval string
// vars for time intervals
var (
TimeIntervalMinute = TimeInterval("1min")
TimeIntervalThreeMinutes = TimeInterval("3min")
TimeIntervalFiveMinutes = TimeInterval("5min")
TimeIntervalFifteenMinutes = TimeInterval("15min")
TimeIntervalThirtyMinutes = TimeInterval("30min")
TimeIntervalHour = TimeInterval("1hour")
TimeIntervalFourHours = TimeInterval("4hour")
TimeIntervalSixHours = TimeInterval("6hour")
TimeIntervalTwelveHours = TimeInterval("12hour")
TimeIntervalDay = TimeInterval("1day")
TimeIntervalThreeDays = TimeInterval("3day")
TimeIntervalWeek = TimeInterval("1week")
)

View File

@@ -125,7 +125,10 @@ func (o *OKEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook
currency = exchange.FormatExchangeCurrency(o.Name, p).String()
}
orderbookNew, err := o.GetSpotMarketDepth(currency, "200")
orderbookNew, err := o.GetSpotMarketDepth(ActualSpotDepthRequestParams{
Symbol: currency,
Size: 200,
})
if err != nil {
return orderBook, err
}

View File

@@ -245,9 +245,9 @@ func (r *Requester) DoRequest(req *http.Request, method, path string, headers ma
if r.RequiresRateLimiter() {
r.DecrementRequests(authRequest)
}
return err
}
if resp == nil {
if r.RequiresRateLimiter() {
r.DecrementRequests(authRequest)

9
exchanges/zb/README.md Normal file
View File

@@ -0,0 +1,9 @@
# GoCryptoTrader package zb
This zb package is part of the GoCryptoTrader codebase.
## ZB Exchange
### Current Features
ZB 交易所的支持支持获取K线、买/卖订单、取消订单

318
exchanges/zb/zb.go Normal file
View File

@@ -0,0 +1,318 @@
package zb
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
zbTradeURL = "http://api.zb.com/data"
zbMarketURL = "https://trade.zb.com/api"
zbAPIVersion = "v1"
zbAccountInfo = "getAccountInfo"
zbMarkets = "markets"
zbKline = "kline"
zbOrder = "order"
zbCancelOrder = "cancelOrder"
zbTicker = "ticker"
zbTickers = "allTicker"
zbDepth = "depth"
zbAuthRate = 100
zbUnauthRate = 100
)
// ZB is the overarching type across this package
// 47.91.169.147 api.zb.com
// 47.52.55.212 trade.zb.com
type ZB struct {
exchange.Base
}
// SetDefaults sets default values for the exchange
func (z *ZB) SetDefaults() {
z.Name = "ZB"
z.Enabled = false
z.Fee = 0
z.Verbose = false
z.Websocket = false
z.RESTPollingDelay = 10
z.RequestCurrencyPairFormat.Delimiter = "_"
z.RequestCurrencyPairFormat.Uppercase = false
z.ConfigCurrencyPairFormat.Delimiter = "_"
z.ConfigCurrencyPairFormat.Uppercase = true
z.AssetTypes = []string{ticker.Spot}
z.SupportsAutoPairUpdating = true
z.SupportsRESTTickerBatching = true
z.Requester = request.New(z.Name, request.NewRateLimit(time.Second*10, zbAuthRate), request.NewRateLimit(time.Second*10, zbUnauthRate), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
}
// Setup sets user configuration
func (z *ZB) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
z.SetEnabled(false)
} else {
z.Enabled = true
z.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
z.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
z.APIAuthPEMKey = exch.APIAuthPEMKey
z.SetHTTPClientTimeout(exch.HTTPTimeout)
z.RESTPollingDelay = exch.RESTPollingDelay
z.Verbose = exch.Verbose
z.Websocket = exch.Websocket
z.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
z.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
z.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := z.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = z.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
err = z.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
}
}
// SpotNewOrder submits an order to ZB
func (z *ZB) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
var result SpotNewOrderResponse
vals := url.Values{}
vals.Set("accesskey", z.APIKey)
vals.Set("method", "order")
vals.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
vals.Set("currency", arg.Symbol)
vals.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
vals.Set("tradeType", string(arg.Type))
err := z.SendAuthenticatedHTTPRequest("GET", zbOrder, vals, &result)
if err != nil {
return 0, err
}
if result.Code != 1000 {
}
newOrderID, err := strconv.ParseInt(result.ID, 10, 64)
if err != nil {
return 0, err
}
return newOrderID, nil
}
// CancelOrder cancels an order on Huobi
func (z *ZB) CancelOrder(orderID int64, symbol string) error {
type response struct {
Code int `json:"code"` // Result code
Message string `json:"message"` // Result Message
}
vals := url.Values{}
vals.Set("accesskey", z.APIKey)
vals.Set("method", "cancelOrder")
vals.Set("id", strconv.FormatInt(orderID, 10))
vals.Set("currency", symbol)
var result response
err := z.SendAuthenticatedHTTPRequest("GET", zbCancelOrder, vals, &result)
if err != nil {
return err
}
if result.Code != 1000 {
return errors.New(result.Message)
}
return nil
}
// GetAccountInfo returns account information including coin information
// and pricing
func (z *ZB) GetAccountInfo() (AccountsResponse, error) {
var result AccountsResponse
vals := url.Values{}
vals.Set("accesskey", z.APIKey)
vals.Set("method", "getAccountInfo")
err := z.SendAuthenticatedHTTPRequest("GET", zbAccountInfo, vals, &result)
if err != nil {
return result, err
}
return result, nil
}
// GetMarkets returns market information including pricing, symbols and
// each symbols decimal precision
func (z *ZB) GetMarkets() (map[string]MarketResponseItem, error) {
url := fmt.Sprintf("%s/%s/%s", zbTradeURL, zbAPIVersion, zbMarkets)
var res interface{}
err := z.SendHTTPRequest(url, &res)
if err != nil {
return nil, err
}
list := res.(map[string]interface{})
result := map[string]MarketResponseItem{}
for k, v := range list {
item := v.(map[string]interface{})
result[k] = MarketResponseItem{
AmountScale: item["amountScale"].(float64),
PriceScale: item["priceScale"].(float64),
}
}
return result, nil
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
// 获取最新价格
func (z *ZB) GetLatestSpotPrice(symbol string) (float64, error) {
res, err := z.GetTicker(symbol)
if err != nil {
return 0, err
}
return res.Ticker.Last, nil
}
// GetTicker returns a ticker for a given symbol
func (z *ZB) GetTicker(symbol string) (TickerResponse, error) {
url := fmt.Sprintf("%s/%s/%s?market=%s", zbTradeURL, zbAPIVersion, zbTicker, symbol)
var res TickerResponse
err := z.SendHTTPRequest(url, &res)
if err != nil {
return res, err
}
return res, nil
}
// GetTickers returns ticker data for all supported symbols
func (z *ZB) GetTickers() (map[string]TickerChildResponse, error) {
url := fmt.Sprintf("%s/%s/%s", zbTradeURL, zbAPIVersion, zbTickers)
resp := make(map[string]TickerChildResponse)
err := z.SendHTTPRequest(url, &resp)
if err != nil {
return resp, err
}
return resp, nil
}
// GetOrderbook returns the orderbook for a given symbol
func (z *ZB) GetOrderbook(symbol string) (OrderbookResponse, error) {
url := fmt.Sprintf("%s/%s/%s?market=%s", zbTradeURL, zbAPIVersion, zbDepth, symbol)
var res OrderbookResponse
err := z.SendHTTPRequest(url, &res)
if err != nil {
return res, err
}
// reverse asks data
var data [][]float64
for x := len(res.Asks) - 1; x != 0; x-- {
data = append(data, res.Asks[x])
}
res.Asks = data
return res, nil
}
// GetSpotKline returns Kline data
func (z *ZB) GetSpotKline(arg KlinesRequestParams) (KLineResponse, error) {
vals := url.Values{}
vals.Set("type", string(arg.Type))
vals.Set("market", arg.Symbol)
if arg.Since != "" {
vals.Set("since", arg.Since)
}
if arg.Size != 0 {
vals.Set("size", fmt.Sprintf("%d", arg.Size))
}
url := fmt.Sprintf("%s/%s/%s?%s", zbTradeURL, zbAPIVersion, zbKline, vals.Encode())
var res KLineResponse
var rawKlines map[string]interface{}
err := z.SendHTTPRequest(url, &rawKlines)
if err != nil {
return res, err
}
if rawKlines == nil || rawKlines["symbol"] == nil {
return res, errors.New("zb GetSpotKline rawKlines is nil")
}
res.Symbol = rawKlines["symbol"].(string)
res.MoneyType = rawKlines["moneyType"].(string)
rawKlineDatasString, _ := json.Marshal(rawKlines["data"].([]interface{}))
rawKlineDatas := [][]interface{}{}
if err := json.Unmarshal(rawKlineDatasString, &rawKlineDatas); err != nil {
return res, errors.New("zb rawKlines unmarshal failed")
}
for _, k := range rawKlineDatas {
ot, err := common.TimeFromUnixTimestampFloat(k[0])
if err != nil {
return res, errors.New("zb cannot parse Kline.OpenTime")
}
res.Data = append(res.Data, &KLineResponseData{
ID: k[0].(float64),
KlineTime: ot,
Open: k[1].(float64),
High: k[2].(float64),
Low: k[3].(float64),
Close: k[4].(float64),
Volume: k[5].(float64),
})
}
return res, nil
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (z *ZB) SendHTTPRequest(path string, result interface{}) error {
return z.SendPayload("GET", path, nil, nil, result, false, z.Verbose)
}
// SendAuthenticatedHTTPRequest sends authenticated requests to the zb API
func (z *ZB) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error {
if !z.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, z.Name)
}
headers := make(map[string]string)
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36"
mapParams2Sign := url.Values{}
mapParams2Sign.Set("accesskey", z.APIKey)
mapParams2Sign.Set("method", values.Get("method"))
values.Set("sign", common.HexEncodeToString(common.GetHMAC(common.HashMD5, []byte(values.Encode()), []byte(common.Sha1ToHex(z.APISecret)))))
values.Set("reqTime", fmt.Sprintf("%d", time.Now().UnixNano()/1e6))
url := fmt.Sprintf("%s/%s?%s", zbMarketURL, endpoint, values.Encode())
return z.SendPayload(method, url, headers, strings.NewReader(""), result, true, z.Verbose)
}

136
exchanges/zb/zb_test.go Normal file
View File

@@ -0,0 +1,136 @@
package zb
import (
"fmt"
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
// Please supply you own test keys here for due diligence testing.
const (
apiKey = ""
apiSecret = ""
)
var z ZB
func TestSetDefaults(t *testing.T) {
z.SetDefaults()
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
zbConfig, err := cfg.GetExchangeConfig("ZB")
if err != nil {
t.Error("Test Failed - ZB Setup() init error")
}
zbConfig.AuthenticatedAPISupport = true
zbConfig.APIKey = apiKey
zbConfig.APISecret = apiSecret
z.Setup(zbConfig)
}
func TestSpotNewOrder(t *testing.T) {
t.Parallel()
if z.APIKey == "" || z.APISecret == "" {
t.Skip()
}
arg := SpotNewOrderRequestParams{
Symbol: "btc_usdt",
Type: SpotNewOrderRequestParamsTypeSell,
Amount: 0.01,
Price: 10246.1,
}
orderid, err := z.SpotNewOrder(arg)
if err != nil {
t.Errorf("Test failed - ZB SpotNewOrder: %s", err)
} else {
fmt.Println(orderid)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
if z.APIKey == "" || z.APISecret == "" {
t.Skip()
}
err := z.CancelOrder(20180629145864850, "btc_usdt")
if err != nil {
t.Errorf("Test failed - ZB CancelOrder: %s", err)
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := z.GetLatestSpotPrice("btc_usdt")
if err != nil {
t.Errorf("Test failed - ZB GetLatestSpotPrice: %s", err)
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := z.GetTicker("btc_usdt")
if err != nil {
t.Errorf("Test failed - ZB GetTicker: %s", err)
}
}
func TestGetTickers(t *testing.T) {
t.Parallel()
_, err := z.GetTickers()
if err != nil {
t.Errorf("Test failed - ZB GetTicker: %s", err)
}
}
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := z.GetOrderbook("btc_usdt")
if err != nil {
t.Errorf("Test failed - ZB GetTicker: %s", err)
}
}
func TestGetMarkets(t *testing.T) {
t.Parallel()
_, err := z.GetMarkets()
if err != nil {
t.Errorf("Test failed - ZB GetMarkets: %s", err)
}
}
func TestGetAccountInfo(t *testing.T) {
t.Parallel()
if z.APIKey == "" || z.APISecret == "" {
t.Skip()
}
_, err := z.GetAccountInfo()
if err != nil {
t.Errorf("Test failed - ZB GetAccountInfo: %s", err)
}
}
func TestGetSpotKline(t *testing.T) {
t.Parallel()
arg := KlinesRequestParams{
Symbol: "btc_usdt",
Type: TimeIntervalFiveMinutes,
Size: 10,
}
_, err := z.GetSpotKline(arg)
if err != nil {
t.Errorf("Test failed - ZB GetSpotKline: %s", err)
}
}

137
exchanges/zb/zb_type.go Normal file
View File

@@ -0,0 +1,137 @@
package zb
import "time"
// OrderbookResponse holds the orderbook data for a symbol
type OrderbookResponse struct {
Timestamp int64 `json:"timestamp"`
Asks [][]float64 `json:"asks"`
Bids [][]float64 `json:"bids"`
}
// AccountsResponseCoin holds the accounts coin details
type AccountsResponseCoin struct {
Freez string `json:"freez"` //冻结资产
EnName string `json:"enName"` //币种英文名
UnitDecimal int `json:"unitDecimal"` //保留小数位
UnName string `json:"cnName"` //币种中文名
UnitTag string `json:"unitTag"` //币种符号
Available string `json:"available"` //可用资产
Key string `json:"key"` //币种
}
// AccountsBaseResponse holds basic account details
type AccountsBaseResponse struct {
UserName string `json:"username"` //用户名
TradePasswordEnabled bool `json:"trade_password_enabled"` //是否开通交易密码
AuthGoogleEnabled bool `json:"auth_google_enabled"` //是否开通谷歌验证
AuthMobileEnabled bool `json:"auth_mobile_enabled"` //是否开通手机验证
}
// AccountsResponse 用户基本信息
type AccountsResponse struct {
Result struct {
Coins []AccountsResponseCoin `json:"coins"`
Base AccountsBaseResponse `json:"base"`
} `json:"result"` //用户名
AssetPerm bool `json:"assetPerm"` //是否开通交易密码
LeverPerm bool `json:"leverPerm"` //是否开通谷歌验证
EntrustPerm bool `json:"entrustPerm"` //是否开通手机验证
MoneyPerm bool `json:"moneyPerm"` // 资产列表
}
// MarketResponseItem stores market data
type MarketResponseItem struct {
AmountScale float64 `json:"amountScale"`
PriceScale float64 `json:"priceScale"`
}
// TickerResponse holds the ticker response data
type TickerResponse struct {
Date string `json:"date"`
Ticker TickerChildResponse `json:"ticker"`
}
// TickerChildResponse holds the ticker child response data
type TickerChildResponse struct {
Vol float64 `json:"vol,string"` //成交量(最近的24小时)
Last float64 `json:"last,string"` //最新成交价
Sell float64 `json:"sell,string"` //卖一价
Buy float64 `json:"buy,string"` //买一价
High float64 `json:"high,string"` //最高价
Low float64 `json:"low,string"` //最低价
}
// SpotNewOrderRequestParamsType ZB 交易类型
type SpotNewOrderRequestParamsType string
var (
// SpotNewOrderRequestParamsTypeBuy 买
SpotNewOrderRequestParamsTypeBuy = SpotNewOrderRequestParamsType("1")
// SpotNewOrderRequestParamsTypeSell 卖
SpotNewOrderRequestParamsTypeSell = SpotNewOrderRequestParamsType("0")
)
// SpotNewOrderRequestParams is the params used for placing an order
type SpotNewOrderRequestParams struct {
Amount float64 `json:"amount"` // 交易数量
Price float64 `json:"price"` // 下单价格,
Symbol string `json:"currency"` // 交易对, btcusdt, bccbtc......
Type SpotNewOrderRequestParamsType `json:"tradeType"` // 订单类型, buy-market: 市价买, sell-market: 市价卖, buy-limit: 限价买, sell-limit: 限价卖
}
// SpotNewOrderResponse stores the new order response data
type SpotNewOrderResponse struct {
Code int `json:"code"` //返回代码
Message string `json:"message"` //提示信息
ID string `json:"id"` //委托挂单号
}
// //-------------Kline
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string //交易对, zb_qc,zb_usdt,zb_btc...
Type TimeInterval //K线类型, 1min, 3min, 15min, 30min, 1hour......
Since string //从这个时间戳之后的
Size int //返回数据的条数限制(默认为1000如果返回数据多于1000条那么只返回1000条)
}
// KLineResponseData Kline Data
type KLineResponseData struct {
ID float64 `json:"id"` // K线ID
KlineTime time.Time `json:"klineTime"`
Open float64 `json:"open"` // 开盘价
Close float64 `json:"close"` // 收盘价, 当K线为最晚的一根时, 时最新成交价
Low float64 `json:"low"` // 最低价
High float64 `json:"high"` // 最高价
Volume float64 `json:"vol"` // 成交量
}
// KLineResponse K线返回类型
type KLineResponse struct {
// Data string `json:"data"` // 买入货币
MoneyType string `json:"moneyType"` // 卖出货币
Symbol string `json:"symbol"` // 内容说明
Data []*KLineResponseData `json:"data"` // KLine数据
}
// TimeInterval represents interval enum.
type TimeInterval string
// TimeInterval vars
var (
TimeIntervalMinute = TimeInterval("1min")
TimeIntervalThreeMinutes = TimeInterval("3min")
TimeIntervalFiveMinutes = TimeInterval("5min")
TimeIntervalFifteenMinutes = TimeInterval("15min")
TimeIntervalThirtyMinutes = TimeInterval("30min")
TimeIntervalHour = TimeInterval("1hour")
TimeIntervalTwoHours = TimeInterval("2hour")
TimeIntervalFourHours = TimeInterval("4hour")
TimeIntervalSixHours = TimeInterval("6hour")
TimeIntervalTwelveHours = TimeInterval("12hour")
TimeIntervalDay = TimeInterval("1day")
TimeIntervalThreeDays = TimeInterval("3day")
TimeIntervalWeek = TimeInterval("1week")
)

186
exchanges/zb/zb_wrapper.go Normal file
View File

@@ -0,0 +1,186 @@
package zb
import (
"errors"
"log"
"sync"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the OKEX go routine
func (z *ZB) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
z.Run()
wg.Done()
}()
}
// Run implements the OKEX wrapper
func (z *ZB) Run() {
if z.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", z.GetName(), common.IsEnabled(z.Websocket), z.WebsocketURL)
log.Printf("%s polling delay: %ds.\n", z.GetName(), z.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", z.GetName(), len(z.EnabledPairs), z.EnabledPairs)
}
markets, err := z.GetMarkets()
if err != nil {
log.Printf("%s Unable to fetch symbols.\n", z.GetName())
} else {
var currencies []string
for x := range markets {
currencies = append(currencies, x)
}
err = z.UpdateCurrencies(currencies, false, false)
if err != nil {
log.Printf("%s Failed to update available currencies.\n", z.GetName())
}
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (z *ZB) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
result, err := z.GetTickers()
if err != nil {
return tickerPrice, err
}
for _, x := range z.GetEnabledCurrencies() {
currencySplit := common.SplitStrings(exchange.FormatExchangeCurrency(z.Name, x).String(), "_")
currency := currencySplit[0] + currencySplit[1]
var tp ticker.Price
tp.Pair = x
tp.High = result[currency].High
tp.Last = result[currency].Last
tp.Ask = result[currency].Sell
tp.Bid = result[currency].Buy
tp.Last = result[currency].Last
tp.Low = result[currency].Low
tp.Volume = result[currency].Vol
ticker.ProcessTicker(z.Name, x, tp, assetType)
}
return ticker.GetTicker(z.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (z *ZB) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(z.GetName(), p, assetType)
if err != nil {
return z.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (z *ZB) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(z.GetName(), currency, assetType)
if err != nil {
return z.UpdateOrderbook(currency, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (z *ZB) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
currency := exchange.FormatExchangeCurrency(z.Name, p).String()
orderbookNew, err := z.GetOrderbook(currency)
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
data := orderbookNew.Bids[x]
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]})
}
for x := range orderbookNew.Asks {
data := orderbookNew.Asks[x]
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
}
orderbook.ProcessOrderbook(z.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(z.Name, p, assetType)
}
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// ZB exchange
func (z *ZB) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
return response, errors.New("not implemented")
}
// GetExchangeFundTransferHistory returns funding history, deposits and
// withdrawals
func (z *ZB) GetExchangeFundTransferHistory() ([]exchange.FundHistory, error) {
var fundHistory []exchange.FundHistory
return fundHistory, errors.New("not supported on exchange")
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (z *ZB) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
var resp []exchange.TradeHistory
return resp, errors.New("trade history not yet implemented")
}
// SubmitExchangeOrder submits a new order
func (z *ZB) SubmitExchangeOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (int64, error) {
return 0, errors.New("not yet implemented")
}
// ModifyExchangeOrder will allow of changing orderbook placement and limit to
// market conversion
func (z *ZB) ModifyExchangeOrder(orderID int64, action exchange.ModifyOrder) (int64, error) {
return 0, errors.New("not yet implemented")
}
// CancelExchangeOrder cancels an order by its corresponding ID number
func (z *ZB) CancelExchangeOrder(orderID int64) error {
return errors.New("not yet implemented")
}
// CancelAllExchangeOrders cancels all orders associated with a currency pair
func (z *ZB) CancelAllExchangeOrders() error {
return errors.New("not yet implemented")
}
// GetExchangeOrderInfo returns information on a current open order
func (z *ZB) GetExchangeOrderInfo(orderID int64) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, errors.New("not yet implemented")
}
// GetExchangeDepositAddress returns a deposit address for a specified currency
func (z *ZB) GetExchangeDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawCryptoExchangeFunds returns a withdrawal ID when a withdrawal is
// submitted
func (z *ZB) WithdrawCryptoExchangeFunds(address string, cryptocurrency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawFiatExchangeFunds returns a withdrawal ID when a
// withdrawal is submitted
func (z *ZB) WithdrawFiatExchangeFunds(currency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}
// WithdrawFiatExchangeFundsToInternationalBank returns a withdrawal ID when a
// withdrawal is submitted
func (z *ZB) WithdrawFiatExchangeFundsToInternationalBank(currency pair.CurrencyItem, amount float64) (string, error) {
return "", errors.New("not yet implemented")
}

File diff suppressed because one or more lines are too long

View File

@@ -32,9 +32,11 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| COINUT | Yes | No | NA |
| Exmo | Yes | NA | NA |
| CoinbasePro | Yes | Yes | No|
| GateIO | Yes | No | NA |
| Gemini | Yes | No | No |
| HitBTC | Yes | Yes | No |
| Huobi.Pro | Yes | No |No |
| Huobi.Pro | Yes | No | NA |
| Huobi.Hadax | Yes | No | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | NA | NA |
| LakeBTC | Yes | No | NA |
@@ -46,6 +48,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Poloniex | Yes | Yes | NA |
| WEX | Yes | NA | NA |
| Yobit | Yes | NA | NA |
| ZB.COM | Yes | No | NA |
We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/).

View File

@@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"log"
"net/url"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
@@ -128,7 +127,7 @@ func main() {
}
} else {
bf := bitfinex.Bitfinex{}
ticker, errf := bf.GetTicker(y.Coin+"USD", url.Values{})
ticker, errf := bf.GetTicker(y.Coin + "USD")
if errf != nil {
log.Println(errf)
} else {