mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
ZB: Remove exchange implementation (#1450)
This commit is contained in:
@@ -44,7 +44,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| Okx | Yes | Yes | NA |
|
||||
| Poloniex | Yes | Yes | NA |
|
||||
| Yobit | Yes | NA | NA |
|
||||
| ZB.COM | Yes | Yes | NA |
|
||||
|
||||
We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/).
|
||||
However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk).
|
||||
|
||||
@@ -67,7 +67,6 @@ _b in this context is an `IBotExchange` implemented struct_
|
||||
| Okx | Yes | Yes | Yes |
|
||||
| Poloniex | Yes | Yes | Yes |
|
||||
| Yobit | Yes | NA | No |
|
||||
| ZB.COM | Yes | Yes | No |
|
||||
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
{{define "exchanges zb" -}}
|
||||
{{template "header" .}}
|
||||
## ZB Exchange
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST functions
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### How to do REST public/private calls
|
||||
|
||||
+ If enabled via "configuration".json file the exchange will be added to the
|
||||
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
|
||||
the wrapper interface functions for accessing exchange data. View routines.go
|
||||
for an example of integration usage with GoCryptoTrader. Rudimentary example
|
||||
below:
|
||||
|
||||
main.go
|
||||
```go
|
||||
var z exchange.IBotExchange
|
||||
|
||||
for i := range bot.Exchanges {
|
||||
if bot.Exchanges[i].GetName() == "ZB" {
|
||||
z = bot.Exchanges[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Public calls - wrapper functions
|
||||
|
||||
// Fetches current ticker information
|
||||
tick, err := z.FetchTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := z.FetchOrderbook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
|
||||
// set and AuthenticatedAPISupport is set to true
|
||||
|
||||
// Fetches current account information
|
||||
accountInfo, err := z.GetAccountInfo()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
+ If enabled via individually importing package, rudimentary example below:
|
||||
|
||||
```go
|
||||
// Public calls
|
||||
|
||||
// Fetches current ticker information
|
||||
ticker, err := z.GetTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := z.GetOrderBook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - make sure your APIKEY and APISECRET are set and
|
||||
// AuthenticatedAPISupport is set to true
|
||||
|
||||
// GetUserInfo returns account info
|
||||
accountInfo, err := z.GetUserInfo(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Submits an order and the exchange and returns its tradeID
|
||||
tradeID, err := z.Trade(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### How to do LongPolling public/private calls
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
{{template "contributions"}}
|
||||
{{template "donations" .}}
|
||||
{{end}}
|
||||
@@ -45,7 +45,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| Okx | Yes | Yes | NA |
|
||||
| Poloniex | Yes | Yes | NA |
|
||||
| Yobit | Yes | NA | NA |
|
||||
| ZB.COM | Yes | Yes | NA |
|
||||
|
||||
We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/).
|
||||
However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk).
|
||||
|
||||
@@ -2388,84 +2388,6 @@
|
||||
"supportedCurrencies": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ZB",
|
||||
"enabled": true,
|
||||
"verbose": false,
|
||||
"httpTimeout": 15000000000,
|
||||
"websocketResponseCheckTimeout": 30000000,
|
||||
"websocketResponseMaxLimit": 7000000000,
|
||||
"websocketTrafficTimeout": 30000000000,
|
||||
"websocketOrderbookBufferLimit": 5,
|
||||
"baseCurrencies": "USD",
|
||||
"currencyPairs": {
|
||||
"requestFormat": {
|
||||
"uppercase": false,
|
||||
"delimiter": "_"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "_"
|
||||
},
|
||||
"useGlobalFormat": true,
|
||||
"assetTypes": [
|
||||
"spot"
|
||||
],
|
||||
"pairs": {
|
||||
"spot": {
|
||||
"enabled": "BTC_USDT,ETH_USDT",
|
||||
"available": "NXWC_QC,LBTC_USDT,CDC_QC,EPC_QC,BSV_USDT,AE_QC,EDO_USDT,HLC_QC,KPG_QC,NEO_QC,PAX_QC,HC_BTC,BTN_QC,UFO_USDT,TRUE_BTC,DNA_QC,GUSD_QC,BTP_QC,VBT_QC,OMG_QC,HSR_QC,ACC_USDT,KAN_BTC,LBTC_QC,BITE_BTC,GUCS_USDT,BCHABC_QC,QTUM_USDT,BTS_USDT,ETH_QC,XLM_USDT,QTUM_QC,SNT_USDT,UBTC_QC,ZB_QC,ZB_BTC,XRP_USDT,USDC_USDT,LUCKY_USDT,BTC_USDT,NEO_BTC,PAX_USDT,QUN_QC,LVN_USDT,EOS_BTC,ADA_QC,BAT_QC,ETH_USDT,DASH_BTC,VSYS_BTC,MTL_USDT,TSR_USDT,BCHABC_USDT,EOS_USDT,XMR_QC,XTZ_USDT,BCH_QC,DOGE_USDT,BTS_BTC,GUSD_USDT,LTC_QC,XWC_USDT,NXWC_USDT,TRUE_QC,ZB_USDT,BCHSV_USDT,ADA_BTC,BDS_QC,ETC_USDT,DASH_USDT,KAN_QC,HNS_USDT,CDC_USDT,DDM_QC,LTC_BTC,HC_USDT,TRUE_USDT,GRIN_QC,HOTC_QC,ETZ_USDT,SLT_QC,XWC_QC,BITCNY_QC,BTC_QC,CRO_USDT,CRO_QC,BSV_QC,ADA_USDT,DASH_QC,TRX_BTC,TOPC_USDT,USDT_QC,BCH_USDT,TRX_QC,LTG_USDT,PDX_QC,GST_USDT,GRAM_QC,BTH_QC,AAA_QC,LVN_QC,DOGE_QC,HSR_USDT,DNA_USDT,ETZ_QC,CHAT_USDT,B91_QC,DDM_USDT,DOGE_BTC,BTS_QC,SAFE_QC,TUSD_USDT,UFO_QC,TOPC_QC,XRP_BTC,HC_QC,MCO_QC,BTM_QC,HPY_QC,FN_QC,OMG_USDT,ICX_QC,LEO_USDT,BRC_QC,BCHSV_QC,LTC_USDT,NEO_USDT,ZRX_QC,VSYS_QC,XRP_QC,EOS_QC,XLM_QC,HX_QC,TV_QC,TRX_USDT,QTUM_BTC,MANA_QC,ETH_BTC,XEM_QC,GNT_USDT,USDC_QC,SAFE_USDT,GRAM_USDT,LUCKY_QC,OMG_BTC,HSR_BTC,KNC_QC,ETC_BTC,BTH_USDT,EPC_BTC,XLM_BTC,XMR_USDT,ETC_QC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authenticatedSupport": false,
|
||||
"authenticatedWebsocketApiSupport": false,
|
||||
"endpoints": {
|
||||
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
|
||||
},
|
||||
"credentials": {
|
||||
"key": "Key",
|
||||
"secret": "Secret"
|
||||
},
|
||||
"credentialsValidator": {
|
||||
"requiresKey": true,
|
||||
"requiresSecret": true
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"supports": {
|
||||
"restAPI": true,
|
||||
"restCapabilities": {
|
||||
"tickerBatching": true,
|
||||
"autoPairUpdates": true
|
||||
},
|
||||
"websocketAPI": true,
|
||||
"websocketCapabilities": {}
|
||||
},
|
||||
"enabled": {
|
||||
"autoPairUpdates": true,
|
||||
"websocketAPI": false
|
||||
}
|
||||
},
|
||||
"bankAccounts": [
|
||||
{
|
||||
"enabled": false,
|
||||
"bankName": "",
|
||||
"bankAddress": "",
|
||||
"bankPostalCode": "",
|
||||
"bankPostalCity": "",
|
||||
"bankCountry": "",
|
||||
"accountName": "",
|
||||
"accountNumber": "",
|
||||
"swiftCode": "",
|
||||
"iban": "",
|
||||
"supportedCurrencies": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"bankAccounts": [
|
||||
|
||||
@@ -221,7 +221,6 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor
|
||||
| Okx | Yes | Yes | NA |
|
||||
| Poloniex | Yes | Yes | NA |
|
||||
| Yobit | Yes | NA | NA |
|
||||
| ZB.COM | Yes | Yes | NA |
|
||||
```
|
||||
|
||||
#### Add exchange to the list of [supported exchanges](../exchanges/support.go):
|
||||
@@ -252,7 +251,6 @@ var Exchanges = []string{
|
||||
"okx",
|
||||
"poloniex",
|
||||
"yobit",
|
||||
"zb",
|
||||
```
|
||||
|
||||
#### Increment the default number of supported exchanges in [config/config_test.go](../config/config_test.go):
|
||||
|
||||
@@ -68,4 +68,3 @@ $ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9
|
||||
| Okx | Yes | Yes | |
|
||||
| Poloniex | Yes | Yes | |
|
||||
| Yobit | No | No | |
|
||||
| ZB.COM | Yes | No | Addresses must be created via their website first |
|
||||
|
||||
@@ -89,5 +89,4 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
|
||||
| Okcoin | Y |
|
||||
| Okx | Y |
|
||||
| Poloniex | Y |
|
||||
| Yobit | |
|
||||
| ZB | Y |
|
||||
| Yobit | |
|
||||
|
||||
@@ -56,7 +56,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/yobit"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/zb"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctscript/vm"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -1045,8 +1044,6 @@ func NewSupportedExchangeByName(name string) (exchange.IBotExchange, error) {
|
||||
return new(poloniex.Poloniex), nil
|
||||
case "yobit":
|
||||
return new(yobit.Yobit), nil
|
||||
case "zb":
|
||||
return new(zb.ZB), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("'%s', %w", name, ErrExchangeNotFound)
|
||||
}
|
||||
|
||||
@@ -39,5 +39,4 @@ var Exchanges = []string{
|
||||
"okx",
|
||||
"poloniex",
|
||||
"yobit",
|
||||
"zb",
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ _b in this context is an `IBotExchange` implemented struct_
|
||||
| Okx | Yes | Yes | Yes |
|
||||
| Poloniex | Yes | Yes | Yes |
|
||||
| Yobit | Yes | NA | No |
|
||||
| ZB.COM | Yes | Yes | No |
|
||||
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
# GoCryptoTrader package Zb
|
||||
|
||||
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/zb)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This zb package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
|
||||
|
||||
## ZB Exchange
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST functions
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### How to do REST public/private calls
|
||||
|
||||
+ If enabled via "configuration".json file the exchange will be added to the
|
||||
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
|
||||
the wrapper interface functions for accessing exchange data. View routines.go
|
||||
for an example of integration usage with GoCryptoTrader. Rudimentary example
|
||||
below:
|
||||
|
||||
main.go
|
||||
```go
|
||||
var z exchange.IBotExchange
|
||||
|
||||
for i := range bot.Exchanges {
|
||||
if bot.Exchanges[i].GetName() == "ZB" {
|
||||
z = bot.Exchanges[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Public calls - wrapper functions
|
||||
|
||||
// Fetches current ticker information
|
||||
tick, err := z.FetchTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := z.FetchOrderbook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
|
||||
// set and AuthenticatedAPISupport is set to true
|
||||
|
||||
// Fetches current account information
|
||||
accountInfo, err := z.GetAccountInfo()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
+ If enabled via individually importing package, rudimentary example below:
|
||||
|
||||
```go
|
||||
// Public calls
|
||||
|
||||
// Fetches current ticker information
|
||||
ticker, err := z.GetTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := z.GetOrderBook()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - make sure your APIKEY and APISECRET are set and
|
||||
// AuthenticatedAPISupport is set to true
|
||||
|
||||
// GetUserInfo returns account info
|
||||
accountInfo, err := z.GetUserInfo(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Submits an order and the exchange and returns its tradeID
|
||||
tradeID, err := z.Trade(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### How to do LongPolling public/private calls
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
|
||||
@@ -1,49 +0,0 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
zbRateInterval = time.Second
|
||||
zbAuthLimit = 60
|
||||
zbUnauthLimit = 60
|
||||
|
||||
zbKlineDataInterval = time.Second * 2
|
||||
zbKlineDataLimit = 1
|
||||
|
||||
// Used to match endpoints to rate limits
|
||||
klineFunc request.EndpointLimit = iota
|
||||
)
|
||||
|
||||
// RateLimit implements the request.Limiter interface
|
||||
type RateLimit struct {
|
||||
Auth *rate.Limiter
|
||||
UnAuth *rate.Limiter
|
||||
KlineData *rate.Limiter
|
||||
}
|
||||
|
||||
// Limit limits the outbound requests
|
||||
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case request.Auth:
|
||||
return r.Auth.Wait(ctx)
|
||||
case klineFunc:
|
||||
return r.KlineData.Wait(ctx)
|
||||
default:
|
||||
return r.UnAuth.Wait(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
func SetRateLimit() *RateLimit {
|
||||
return &RateLimit{
|
||||
Auth: request.NewRateLimit(zbRateInterval, zbAuthLimit),
|
||||
UnAuth: request.NewRateLimit(zbRateInterval, zbUnauthLimit),
|
||||
KlineData: request.NewRateLimit(zbKlineDataInterval, zbKlineDataLimit),
|
||||
}
|
||||
}
|
||||
@@ -1,592 +0,0 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
)
|
||||
|
||||
const (
|
||||
zbTradeURL = "https://api.zb.com"
|
||||
zbMarketURL = "https://trade.zb.com/api"
|
||||
zbAPIVersion = "v1"
|
||||
zbData = "data"
|
||||
zbAccountInfo = "getAccountInfo"
|
||||
zbMarkets = "markets"
|
||||
zbKline = "kline"
|
||||
zbOrder = "order"
|
||||
zbCancelOrder = "cancelOrder"
|
||||
zbTicker = "ticker"
|
||||
zbTrades = "trades"
|
||||
zbTickers = "allTicker"
|
||||
zbDepth = "depth"
|
||||
zbUnfinishedOrdersIgnoreTradeType = "getUnfinishedOrdersIgnoreTradeType"
|
||||
zbGetOrdersGet = "getOrders"
|
||||
zbGetOrder = "getOrder"
|
||||
zbWithdraw = "withdraw"
|
||||
zbDepositAddress = "getUserAddress"
|
||||
zbMultiChainDepositAddress = "getPayinAddress"
|
||||
zbWithdrawalRecords = "getWithdrawRecord"
|
||||
zbDepositRecords = "getChargeRecord"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SpotNewOrder submits an order to ZB
|
||||
func (z *ZB) SpotNewOrder(ctx context.Context, arg SpotNewOrderRequestParams) (int64, error) {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var result SpotNewOrderResponse
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
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(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if result.Code != 1000 {
|
||||
return 0, fmt.Errorf("%w unsuccessful new order, message: %s code: %d", request.ErrAuthRequestFailed, result.Message, result.Code)
|
||||
}
|
||||
newOrderID, err := strconv.ParseInt(result.ID, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOrderID, nil
|
||||
}
|
||||
|
||||
// GetDepositRecords returns the deposit records
|
||||
func (z *ZB) GetDepositRecords(ctx context.Context, arg *WalletRecordsRequest) (*DepositRecordsResponse, error) {
|
||||
if arg == nil {
|
||||
return nil, fmt.Errorf("%w WalletRecordsRequest", common.ErrNilPointer)
|
||||
}
|
||||
var resp DepositRecordsResponse
|
||||
vals := url.Values{}
|
||||
vals.Set("method", "getChargeRecord")
|
||||
vals.Set("currency", arg.Currency.String())
|
||||
if arg.PageSize > 0 {
|
||||
vals.Set("pageIndex", strconv.FormatInt(arg.PageIndex, 10))
|
||||
}
|
||||
if arg.PageIndex > 0 {
|
||||
vals.Set("pageSize", strconv.FormatInt(arg.PageSize, 10))
|
||||
}
|
||||
return &resp, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
|
||||
}
|
||||
|
||||
// GetWithdrawalRecords returns the withdrawal records
|
||||
func (z *ZB) GetWithdrawalRecords(ctx context.Context, arg *WalletRecordsRequest) (*WithdrawalRecordsResponse, error) {
|
||||
if arg == nil {
|
||||
return nil, fmt.Errorf("%w WalletRecordsRequest", common.ErrNilPointer)
|
||||
}
|
||||
var resp WithdrawalRecordsResponse
|
||||
vals := url.Values{}
|
||||
vals.Set("method", "getWithdrawRecord")
|
||||
vals.Set("currency", arg.Currency.String())
|
||||
vals.Set("pageIndex", strconv.FormatInt(arg.PageIndex, 10))
|
||||
vals.Set("pageSize", strconv.FormatInt(arg.PageSize, 10))
|
||||
return &resp, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
|
||||
}
|
||||
|
||||
// CancelExistingOrder cancels an order
|
||||
func (z *ZB) CancelExistingOrder(ctx context.Context, orderID int64, symbol string) error {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Code int `json:"code"` // Result code
|
||||
Message string `json:"message"` // Result Message
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
vals.Set("method", "cancelOrder")
|
||||
vals.Set("id", strconv.FormatInt(orderID, 10))
|
||||
vals.Set("currency", symbol)
|
||||
|
||||
var result response
|
||||
err = z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Code != 1000 {
|
||||
return fmt.Errorf("%w %v", request.ErrAuthRequestFailed, result.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAccountInformation returns account information including coin information
|
||||
// and pricing
|
||||
func (z *ZB) GetAccountInformation(ctx context.Context) (AccountsResponse, error) {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return AccountsResponse{}, err
|
||||
}
|
||||
|
||||
var result AccountsResponse
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
vals.Set("method", "getAccountInfo")
|
||||
|
||||
return result, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
|
||||
}
|
||||
|
||||
// GetUnfinishedOrdersIgnoreTradeType returns unfinished orders
|
||||
func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(ctx context.Context, currency string, pageindex, pagesize int64) ([]Order, error) {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []Order
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
vals.Set("method", zbUnfinishedOrdersIgnoreTradeType)
|
||||
vals.Set("currency", currency)
|
||||
vals.Set("pageIndex", strconv.FormatInt(pageindex, 10))
|
||||
vals.Set("pageSize", strconv.FormatInt(pagesize, 10))
|
||||
|
||||
return result, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
|
||||
}
|
||||
|
||||
// GetOrders returns finished orders
|
||||
func (z *ZB) GetOrders(ctx context.Context, currency string, pageindex, side int64) ([]Order, error) {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []Order
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
vals.Set("method", zbGetOrdersGet)
|
||||
vals.Set("currency", currency)
|
||||
vals.Set("pageIndex", strconv.FormatInt(pageindex, 10))
|
||||
vals.Set("tradeType", strconv.FormatInt(side, 10))
|
||||
return response, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &response, request.Auth)
|
||||
}
|
||||
|
||||
// GetSingleOrder Get single buy order or sell order
|
||||
func (z *ZB) GetSingleOrder(ctx context.Context, orderID, customerOrderID string, currency currency.Pair) (*Order, error) {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response Order
|
||||
pFmt, err := z.GetPairFormat(asset.Spot, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
vals.Set("method", zbGetOrder)
|
||||
vals.Set("currency", pFmt.Format(currency))
|
||||
if orderID != "" {
|
||||
vals.Set("id", orderID)
|
||||
}
|
||||
if customerOrderID != "" {
|
||||
vals.Set("customerOrderId", customerOrderID)
|
||||
}
|
||||
|
||||
return &response, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &response, request.Auth)
|
||||
}
|
||||
|
||||
// GetMarkets returns market information including pricing, symbols and
|
||||
// each symbols decimal precision
|
||||
func (z *ZB) GetMarkets(ctx context.Context) (map[string]MarketResponseItem, error) {
|
||||
endpoint := fmt.Sprintf("/%s/%s/%s", zbData, zbAPIVersion, zbMarkets)
|
||||
|
||||
var res map[string]MarketResponseItem
|
||||
err := z.SendHTTPRequest(ctx, exchange.RestSpot, endpoint, &res, request.UnAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetLatestSpotPrice returns latest spot price of symbol
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
// 获取最新价格
|
||||
func (z *ZB) GetLatestSpotPrice(ctx context.Context, symbol string) (float64, error) {
|
||||
res, err := z.GetTicker(ctx, symbol)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.Ticker.Last, nil
|
||||
}
|
||||
|
||||
// GetTicker returns a ticker for a given symbol
|
||||
func (z *ZB) GetTicker(ctx context.Context, symbol string) (TickerResponse, error) {
|
||||
urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbTicker, symbol)
|
||||
var res TickerResponse
|
||||
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// GetTrades returns trades for a given symbol
|
||||
func (z *ZB) GetTrades(ctx context.Context, symbol string) (TradeHistory, error) {
|
||||
urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbTrades, symbol)
|
||||
var res TradeHistory
|
||||
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// GetTickers returns ticker data for all supported symbols
|
||||
func (z *ZB) GetTickers(ctx context.Context) (map[string]TickerChildResponse, error) {
|
||||
urlPath := fmt.Sprintf("/%s/%s/%s", zbData, zbAPIVersion, zbTickers)
|
||||
resp := make(map[string]TickerChildResponse)
|
||||
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &resp, request.UnAuth)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetOrderbook returns the orderbook for a given symbol
|
||||
func (z *ZB) GetOrderbook(ctx context.Context, symbol string) (*OrderbookResponse, error) {
|
||||
urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbDepth, symbol)
|
||||
var res OrderbookResponse
|
||||
|
||||
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.Asks) == 0 {
|
||||
return nil, errors.New("ZB GetOrderbook asks is empty")
|
||||
}
|
||||
|
||||
if len(res.Bids) == 0 {
|
||||
return nil, errors.New("ZB GetOrderbook bids is empty")
|
||||
}
|
||||
|
||||
// reverse asks data
|
||||
eLen := len(res.Asks)
|
||||
var target int
|
||||
for i := eLen/2 - 1; i >= 0; i-- {
|
||||
target = eLen - 1 - i
|
||||
(res.Asks)[i], (res.Asks)[target] = (res.Asks)[target], (res.Asks)[i]
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// GetSpotKline returns Kline data
|
||||
func (z *ZB) GetSpotKline(ctx context.Context, arg KlinesRequestParams) (KLineResponse, error) {
|
||||
vals := url.Values{}
|
||||
vals.Set("type", arg.Type)
|
||||
vals.Set("market", arg.Symbol)
|
||||
if arg.Since > 0 {
|
||||
vals.Set("since", strconv.FormatInt(arg.Since, 10))
|
||||
}
|
||||
if arg.Size != 0 {
|
||||
vals.Set("size", fmt.Sprintf("%d", arg.Size))
|
||||
}
|
||||
|
||||
urlPath := fmt.Sprintf("/%s/%s/%s?%s", zbData, zbAPIVersion, zbKline, vals.Encode())
|
||||
|
||||
var res KLineResponse
|
||||
resp := struct {
|
||||
Data [][]float64 `json:"data"`
|
||||
MoneyType string `json:"moneyType"`
|
||||
Symbol string `json:"symbol"`
|
||||
}{}
|
||||
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &resp, klineFunc)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if resp.Data == nil || resp.Symbol == "" || resp.MoneyType == "" {
|
||||
return res, errors.New("GetSpotKline received empty data")
|
||||
}
|
||||
res.MoneyType = resp.MoneyType
|
||||
res.Symbol = resp.Symbol
|
||||
|
||||
for x := range resp.Data {
|
||||
if len(resp.Data[x]) < 6 {
|
||||
return res, errors.New("unexpected kline data length")
|
||||
}
|
||||
|
||||
timestamp, err := convert.TimeFromUnixTimestampFloat(resp.Data[x][0])
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.Data = append(res.Data, &KLineResponseData{
|
||||
KlineTime: timestamp,
|
||||
Open: resp.Data[x][1],
|
||||
High: resp.Data[x][2],
|
||||
Low: resp.Data[x][3],
|
||||
Close: resp.Data[x][4],
|
||||
Volume: resp.Data[x][5],
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetCryptoAddress fetches and returns the deposit address
|
||||
// NOTE - PLEASE BE AWARE THAT YOU NEED TO GENERATE A DEPOSIT ADDRESS VIA
|
||||
// LOGGING IN AND NOT BY USING THIS ENDPOINT OTHERWISE THIS WILL GIVE YOU A
|
||||
// GENERAL ERROR RESPONSE.
|
||||
func (z *ZB) GetCryptoAddress(ctx context.Context, currency currency.Code) (*UserAddress, error) {
|
||||
var resp UserAddress
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("method", zbDepositAddress)
|
||||
vals.Set("currency", currency.Lower().String())
|
||||
|
||||
if err := z.SendAuthenticatedHTTPRequest(ctx,
|
||||
exchange.RestSpotSupplementary,
|
||||
http.MethodGet,
|
||||
vals,
|
||||
&resp,
|
||||
request.Auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Message.IsSuccessful {
|
||||
return nil, errors.New(resp.Message.Description)
|
||||
}
|
||||
|
||||
if strings.Contains(resp.Message.Data.Address, "_") {
|
||||
splitter := strings.Split(resp.Message.Data.Address, "_")
|
||||
resp.Message.Data.Address, resp.Message.Data.Tag = splitter[0], splitter[1]
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetMultiChainDepositAddress returns deposit addresses for a given currency
|
||||
func (z *ZB) GetMultiChainDepositAddress(ctx context.Context, currency currency.Code) ([]MultiChainDepositAddress, error) {
|
||||
var resp MultiChainDepositAddressResponse
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("method", zbMultiChainDepositAddress)
|
||||
vals.Set("currency", currency.Lower().String())
|
||||
|
||||
if err := z.SendAuthenticatedHTTPRequest(ctx,
|
||||
exchange.RestSpotSupplementary,
|
||||
http.MethodGet,
|
||||
vals,
|
||||
&resp,
|
||||
request.Auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Message.IsSuccessful {
|
||||
return nil, errors.New(resp.Message.Description)
|
||||
}
|
||||
return resp.Message.Data, nil
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an unauthenticated HTTP request
|
||||
func (z *ZB) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}, f request.EndpointLimit) error {
|
||||
endpoint, err := z.API.Endpoints.GetURL(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item := &request.Item{
|
||||
Method: http.MethodGet,
|
||||
Path: endpoint + path,
|
||||
Result: result,
|
||||
Verbose: z.Verbose,
|
||||
HTTPDebugging: z.HTTPDebugging,
|
||||
HTTPRecording: z.HTTPRecording,
|
||||
}
|
||||
|
||||
return z.SendPayload(ctx, f, func() (*request.Item, error) {
|
||||
return item, nil
|
||||
}, request.UnauthenticatedRequest)
|
||||
}
|
||||
|
||||
// SendAuthenticatedHTTPRequest sends authenticated requests to the zb API
|
||||
func (z *ZB) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, httpMethod string, params url.Values, result interface{}, f request.EndpointLimit) error {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint, err := z.API.Endpoints.GetURL(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params.Set("accesskey", creds.Key)
|
||||
|
||||
hex, err := crypto.Sha1ToHex(creds.Secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hmac, err := crypto.GetHMAC(crypto.HashMD5,
|
||||
[]byte(params.Encode()),
|
||||
[]byte(hex))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var intermediary json.RawMessage
|
||||
newRequest := func() (*request.Item, error) {
|
||||
params.Set("reqTime", strconv.FormatInt(time.Now().UnixMilli(), 10))
|
||||
params.Set("sign", fmt.Sprintf("%x", hmac))
|
||||
|
||||
urlPath := fmt.Sprintf("%s/%s?%s",
|
||||
endpoint,
|
||||
params.Get("method"),
|
||||
params.Encode())
|
||||
|
||||
return &request.Item{
|
||||
Method: httpMethod,
|
||||
Path: urlPath,
|
||||
Result: &intermediary,
|
||||
Verbose: z.Verbose,
|
||||
HTTPDebugging: z.HTTPDebugging,
|
||||
HTTPRecording: z.HTTPRecording,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err = z.SendPayload(ctx, f, newRequest, request.AuthenticatedRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errCap := struct {
|
||||
Code int64 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(intermediary, &errCap)
|
||||
if err == nil {
|
||||
if errCap.Code > 1000 {
|
||||
return fmt.Errorf("%w error code: %d error code message: %s error message: %s",
|
||||
request.ErrAuthRequestFailed,
|
||||
errCap.Code,
|
||||
errorCode[errCap.Code],
|
||||
errCap.Message)
|
||||
}
|
||||
}
|
||||
|
||||
return json.Unmarshal(intermediary, result)
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
func (z *ZB) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
var fee float64
|
||||
switch feeBuilder.FeeType {
|
||||
case exchange.CryptocurrencyTradeFee:
|
||||
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
|
||||
case exchange.CryptocurrencyWithdrawalFee:
|
||||
fee = getWithdrawalFee(feeBuilder.Pair.Base)
|
||||
case exchange.OfflineTradeFee:
|
||||
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
|
||||
}
|
||||
if fee < 0 {
|
||||
fee = 0
|
||||
}
|
||||
|
||||
return fee, nil
|
||||
}
|
||||
|
||||
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
||||
func getOfflineTradeFee(price, amount float64) float64 {
|
||||
return 0.002 * price * amount
|
||||
}
|
||||
|
||||
func calculateTradingFee(purchasePrice, amount float64) (fee float64) {
|
||||
fee = 0.002
|
||||
return fee * amount * purchasePrice
|
||||
}
|
||||
|
||||
func getWithdrawalFee(c currency.Code) float64 {
|
||||
return WithdrawalFees[c]
|
||||
}
|
||||
|
||||
var errorCode = map[int64]string{
|
||||
1000: "Successful call",
|
||||
1001: "General error message",
|
||||
1002: "internal error",
|
||||
1003: "Verification failed",
|
||||
1004: "Financial security password lock",
|
||||
1005: "The fund security password is incorrect. Please confirm and re-enter.",
|
||||
1006: "Real-name certification is awaiting review or review",
|
||||
1009: "This interface is being maintained",
|
||||
1010: "Not open yet",
|
||||
1012: "Insufficient permissions",
|
||||
1013: "Can not trade, if you have any questions, please contact online customer service",
|
||||
1014: "Cannot be sold during the pre-sale period",
|
||||
2002: "Insufficient balance in Bitcoin account",
|
||||
2003: "Insufficient balance of Litecoin account",
|
||||
2005: "Insufficient balance in Ethereum account",
|
||||
2006: "Insufficient balance in ETC currency account",
|
||||
2007: "Insufficient balance of BTS currency account",
|
||||
2009: "Insufficient account balance",
|
||||
3001: "Pending order not found",
|
||||
3002: "Invalid amount",
|
||||
3003: "Invalid quantity",
|
||||
3004: "User does not exist",
|
||||
3005: "Invalid parameter",
|
||||
3006: "Invalid IP or inconsistent with the bound IP",
|
||||
3007: "Request time has expired",
|
||||
3008: "Transaction history not found",
|
||||
4001: "API interface is locked",
|
||||
4002: "Request too frequently",
|
||||
}
|
||||
|
||||
// Withdraw transfers funds
|
||||
func (z *ZB) Withdraw(ctx context.Context, currency, address, safepassword string, amount, fees float64, itransfer bool) (string, error) {
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Code int `json:"code"` // Result code
|
||||
Message string `json:"message"` // Result Message
|
||||
ID string `json:"id"` // Withdrawal ID
|
||||
}
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("accesskey", creds.Key)
|
||||
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
|
||||
vals.Set("currency", currency)
|
||||
vals.Set("fees", strconv.FormatFloat(fees, 'f', -1, 64))
|
||||
vals.Set("itransfer", strconv.FormatBool(itransfer))
|
||||
vals.Set("method", "withdraw")
|
||||
vals.Set("receiveAddr", address)
|
||||
vals.Set("safePwd", safepassword)
|
||||
|
||||
var resp response
|
||||
err = z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.Code != 1000 {
|
||||
return "", fmt.Errorf("%w %v", request.ErrAuthRequestFailed, resp.Message)
|
||||
}
|
||||
|
||||
return resp.ID, nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//go:build mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is parsed and will do live testing
|
||||
// using all tests in (exchange)_test.go
|
||||
package zb
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
var mockTests = false
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
log.Fatal("ZB load config error", err)
|
||||
}
|
||||
zbConfig, err := cfg.GetExchangeConfig("ZB")
|
||||
if err != nil {
|
||||
log.Fatal("ZB Setup() init error", err)
|
||||
}
|
||||
zbConfig.API.AuthenticatedSupport = true
|
||||
zbConfig.API.Credentials.Key = apiKey
|
||||
zbConfig.API.Credentials.Secret = apiSecret
|
||||
z.SetDefaults()
|
||||
z.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = z.Setup(zbConfig)
|
||||
if err != nil {
|
||||
log.Fatal("ZB setup error", err)
|
||||
}
|
||||
log.Printf(sharedtestvalues.LiveTesting, z.Name)
|
||||
z.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
z.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
//go:build !mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is not parsed and will try to mock
|
||||
// all tests in _test.go
|
||||
package zb
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/mock"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
const mockfile = "../../testdata/http_mock/zb/zb.json"
|
||||
|
||||
var mockTests = true
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
log.Fatal("ZB load config error", err)
|
||||
}
|
||||
var zbConfig *config.Exchange
|
||||
zbConfig, err = cfg.GetExchangeConfig("ZB")
|
||||
if err != nil {
|
||||
log.Fatal("ZB Setup() init error", err)
|
||||
}
|
||||
zbConfig.API.AuthenticatedSupport = true
|
||||
zbConfig.API.AuthenticatedWebsocketSupport = true
|
||||
zbConfig.API.Credentials.Key = apiKey
|
||||
zbConfig.API.Credentials.Secret = apiSecret
|
||||
z.SkipAuthCheck = true
|
||||
z.SetDefaults()
|
||||
z.Websocket = sharedtestvalues.NewTestWebsocket()
|
||||
err = z.Setup(zbConfig)
|
||||
if err != nil {
|
||||
log.Fatal("ZB setup error", err)
|
||||
}
|
||||
|
||||
serverDetails, newClient, err := mock.NewVCRServer(mockfile)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock server error %s", err)
|
||||
}
|
||||
|
||||
err = z.SetHTTPClient(newClient)
|
||||
if err != nil {
|
||||
log.Fatalf("Mock server error %s", err)
|
||||
}
|
||||
endpoints := z.API.Endpoints.GetURLMap()
|
||||
for k := range endpoints {
|
||||
err = z.API.Endpoints.SetRunning(k, serverDetails)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
log.Printf(sharedtestvalues.MockTesting,
|
||||
z.Name)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,307 +0,0 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
)
|
||||
|
||||
// OrderbookResponse holds the orderbook data for a symbol
|
||||
type OrderbookResponse struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Asks [][2]float64 `json:"asks"`
|
||||
Bids [][2]float64 `json:"bids"`
|
||||
}
|
||||
|
||||
// AccountsResponseCoin holds the accounts coin details
|
||||
type AccountsResponseCoin struct {
|
||||
Freeze 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"` // 是否开通手机验证
|
||||
}
|
||||
|
||||
// Order is the order details for retrieving all orders
|
||||
type Order struct {
|
||||
Currency string `json:"currency"`
|
||||
ID string `json:"id"`
|
||||
Price float64 `json:"price"`
|
||||
Status int64 `json:"status"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
TradeAmount float64 `json:"trade_amount"`
|
||||
TradeDate int64 `json:"trade_date"`
|
||||
TradeMoney float64 `json:"trade_money"`
|
||||
Type int64 `json:"type"`
|
||||
Fees float64 `json:"fees,omitempty"`
|
||||
TradePrice float64 `json:"trade_price,omitempty"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Volume 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"` // 委托挂单号
|
||||
}
|
||||
|
||||
// KlinesRequestParams represents Klines request data.
|
||||
type KlinesRequestParams struct {
|
||||
Symbol string // 交易对, zb_qc,zb_usdt,zb_btc...
|
||||
Type string // K线类型, 1min, 3min, 15min, 30min, 1hour......
|
||||
Since int64 // 从这个时间戳之后的
|
||||
Size int64 // 返回数据的条数限制(默认为1000,如果返回数据多于1000条,那么只返回1000条)
|
||||
}
|
||||
|
||||
// KLineResponseData Kline Data
|
||||
type KLineResponseData struct {
|
||||
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数据
|
||||
}
|
||||
|
||||
// UserAddress defines Users Address for depositing funds
|
||||
type UserAddress struct {
|
||||
Code int64 `json:"code"`
|
||||
Message struct {
|
||||
Description string `json:"des"`
|
||||
IsSuccessful bool `json:"isSuc"`
|
||||
Data struct {
|
||||
Address string `json:"key"`
|
||||
Tag string // custom field we populate
|
||||
} `json:"datas"`
|
||||
} `json:"message"`
|
||||
}
|
||||
|
||||
// MultiChainDepositAddress stores an individual multichain deposit item
|
||||
type MultiChainDepositAddress struct {
|
||||
Blockchain string `json:"blockChain"`
|
||||
IsUseMemo bool `json:"isUseMemo"`
|
||||
Account string `json:"account"`
|
||||
Address string `json:"address"`
|
||||
Memo string `json:"memo"`
|
||||
CanDeposit bool `json:"canDeposit"`
|
||||
CanWithdraw bool `json:"canWithdraw"`
|
||||
}
|
||||
|
||||
// MultiChainDepositAddressResponse stores the multichain deposit address response
|
||||
type MultiChainDepositAddressResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Message struct {
|
||||
Description string `json:"des"`
|
||||
IsSuccessful bool `json:"isSuc"`
|
||||
Data []MultiChainDepositAddress `json:"datas"`
|
||||
} `json:"message"`
|
||||
}
|
||||
|
||||
// WithdrawalFees the large list of predefined withdrawal fees
|
||||
// Prone to change, using highest value
|
||||
var WithdrawalFees = map[currency.Code]float64{
|
||||
currency.ZB: 5,
|
||||
currency.BTC: 0.001,
|
||||
currency.BCH: 0.0006,
|
||||
currency.LTC: 0.005,
|
||||
currency.ETH: 0.01,
|
||||
currency.ETC: 0.01,
|
||||
currency.BTS: 3,
|
||||
currency.EOS: 0.1,
|
||||
currency.QTUM: 0.01,
|
||||
currency.HC: 0.001,
|
||||
currency.XRP: 0.1,
|
||||
currency.QC: 5,
|
||||
currency.DASH: 0.002,
|
||||
currency.BCD: 0,
|
||||
currency.UBTC: 0.001,
|
||||
currency.SBTC: 0,
|
||||
currency.INK: 60,
|
||||
currency.BTH: 0.01,
|
||||
currency.LBTC: 0.01,
|
||||
currency.CHAT: 20,
|
||||
currency.BITCNY: 20,
|
||||
currency.HLC: 100,
|
||||
currency.BTP: 0.001,
|
||||
currency.TOPC: 200,
|
||||
currency.ENT: 50,
|
||||
currency.BAT: 40,
|
||||
currency.FIRST: 30,
|
||||
currency.SAFE: 0.001,
|
||||
currency.QUN: 200,
|
||||
currency.BTN: 0.005,
|
||||
currency.TRUE: 5,
|
||||
currency.CDC: 1,
|
||||
currency.DDM: 1,
|
||||
currency.HOTC: 150,
|
||||
currency.USDT: 5,
|
||||
currency.XUC: 1,
|
||||
currency.EPC: 40,
|
||||
currency.BDS: 3,
|
||||
currency.GRAM: 5,
|
||||
currency.DOGE: 20,
|
||||
currency.NEO: 0,
|
||||
currency.OMG: 0.5,
|
||||
currency.BTM: 4,
|
||||
currency.SNT: 60,
|
||||
currency.AE: 3,
|
||||
currency.ICX: 3,
|
||||
currency.ZRX: 10,
|
||||
currency.EDO: 4,
|
||||
currency.FUN: 250,
|
||||
currency.MANA: 70,
|
||||
currency.RCN: 70,
|
||||
currency.MCO: 0.6,
|
||||
currency.MITH: 10,
|
||||
currency.KNC: 5,
|
||||
currency.XLM: 0.1,
|
||||
currency.GNT: 20,
|
||||
currency.MTL: 3,
|
||||
currency.SUB: 20,
|
||||
currency.XEM: 4,
|
||||
currency.EOSDAC: 0,
|
||||
currency.KAN: 350,
|
||||
currency.AAA: 1,
|
||||
currency.XWC: 1,
|
||||
currency.PDX: 1,
|
||||
currency.SLT: 100,
|
||||
currency.ADA: 1,
|
||||
currency.HPY: 100,
|
||||
currency.PAX: 5,
|
||||
currency.XTZ: 0.1,
|
||||
}
|
||||
|
||||
// orderSideMap holds order type info based on Alphapoint data
|
||||
var orderSideMap = map[int64]order.Side{
|
||||
0: order.Buy,
|
||||
1: order.Sell,
|
||||
}
|
||||
|
||||
// TradeHistory defines a slice of historic trades
|
||||
type TradeHistory []struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Date int64 `json:"date"`
|
||||
Price float64 `json:"price,string"`
|
||||
Tid int64 `json:"tid"`
|
||||
TradeType string `json:"trade_type"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// WalletRecordsRequest hold request params
|
||||
type WalletRecordsRequest struct {
|
||||
Currency currency.Code `json:"method"`
|
||||
PageIndex int64 `json:"pageIndex"`
|
||||
PageSize int64 `json:"pageSize"`
|
||||
}
|
||||
|
||||
// DepositRecordsResponse holds response data
|
||||
type DepositRecordsResponse struct {
|
||||
List []DepositRecord `json:"list"`
|
||||
PageIndex int64 `json:"pageIndex"`
|
||||
PageSize int64 `json:"pageSize"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// DepositRecord holds details of a deposit
|
||||
type DepositRecord struct {
|
||||
Address string `json:"address"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
ConfirmTimes int64 `json:"confirmTimes"`
|
||||
Currency string `json:"currency"`
|
||||
Description string `json:"description"`
|
||||
Hash string `json:"hash"`
|
||||
ID int64 `json:"id"`
|
||||
InternalTransfer int64 `json:"itransfer"`
|
||||
Status int64 `json:"status"`
|
||||
SubmitTime int64 `json:"submit_time,string"`
|
||||
}
|
||||
|
||||
// WithdrawalRecordsResponse holds response data
|
||||
type WithdrawalRecordsResponse struct {
|
||||
List []WithdrawalRecord `json:"list"`
|
||||
PageIndex int64 `json:"pageIndex"`
|
||||
PageSize int64 `json:"pageSize"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// WithdrawalRecord holds details of a withdrawal
|
||||
type WithdrawalRecord struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Fees float64 `json:"fees"`
|
||||
ID int64 `json:"id"`
|
||||
ManageTime int64 `json:"manageTime"`
|
||||
Status int64 `json:"status"`
|
||||
SubmitTime int64 `json:"submitTime"`
|
||||
ToAddress string `json:"toAddress"`
|
||||
}
|
||||
@@ -1,778 +0,0 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
)
|
||||
|
||||
const (
|
||||
zbWebsocketAPI = "wss://api.zb.com/websocket"
|
||||
zWebsocketAddChannel = "addChannel"
|
||||
zbWebsocketRateLimit = 20
|
||||
)
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (z *ZB) WsConnect() error {
|
||||
if !z.Websocket.IsEnabled() || !z.IsEnabled() {
|
||||
return errors.New(stream.WebsocketNotEnabled)
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := z.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.Websocket.Wg.Add(1)
|
||||
go z.wsReadData()
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsReadData handles all the websocket data coming from the websocket
|
||||
// connection
|
||||
func (z *ZB) wsReadData() {
|
||||
defer z.Websocket.Wg.Done()
|
||||
for {
|
||||
resp := z.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
err := z.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
z.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (z *ZB) wsHandleData(respRaw []byte) error {
|
||||
fixedJSON := z.wsFixInvalidJSON(respRaw)
|
||||
var result Generic
|
||||
err := json.Unmarshal(fixedJSON, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.No > 0 {
|
||||
if z.Websocket.Match.IncomingWithData(result.No, fixedJSON) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if result.Code > 0 && result.Code != 1000 {
|
||||
return fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
result.Message,
|
||||
wsErrCodes[result.Code])
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(result.Channel, "markets"):
|
||||
var markets Markets
|
||||
err := json.Unmarshal(result.Data, &markets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case strings.Contains(result.Channel, "ticker"):
|
||||
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
|
||||
var wsTicker WsTicker
|
||||
err := json.Unmarshal(fixedJSON, &wsTicker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(cPair[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: z.Name,
|
||||
Close: wsTicker.Data.Last,
|
||||
Volume: wsTicker.Data.Volume24Hr,
|
||||
High: wsTicker.Data.High,
|
||||
Low: wsTicker.Data.Low,
|
||||
Last: wsTicker.Data.Last,
|
||||
Bid: wsTicker.Data.Buy,
|
||||
Ask: wsTicker.Data.Sell,
|
||||
LastUpdated: time.UnixMilli(wsTicker.Date),
|
||||
AssetType: asset.Spot,
|
||||
Pair: p,
|
||||
}
|
||||
case strings.Contains(result.Channel, "depth"):
|
||||
var depth WsDepth
|
||||
err := json.Unmarshal(fixedJSON, &depth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter)
|
||||
cPair, err := currency.NewPairFromString(channelInfo[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
book := orderbook.Base{
|
||||
Bids: make(orderbook.Items, len(depth.Bids)),
|
||||
Asks: make(orderbook.Items, len(depth.Asks)),
|
||||
Asset: asset.Spot,
|
||||
Pair: cPair,
|
||||
Exchange: z.Name,
|
||||
VerifyOrderbook: z.CanVerifyOrderbook,
|
||||
LastUpdated: time.Now(), // This is temp to pass test as the API is broken.
|
||||
}
|
||||
|
||||
for i := range depth.Asks {
|
||||
amt, ok := depth.Asks[i][1].(float64)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("float64", depth.Asks[i][1], "ask amount")
|
||||
}
|
||||
price, ok := depth.Asks[i][0].(float64)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("float64", depth.Asks[i][0], "ask price")
|
||||
}
|
||||
book.Asks[i] = orderbook.Item{
|
||||
Amount: amt,
|
||||
Price: price,
|
||||
}
|
||||
}
|
||||
for i := range depth.Bids {
|
||||
amt, ok := depth.Bids[i][1].(float64)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("float64", depth.Bids[i][1], "bid amount")
|
||||
}
|
||||
price, ok := depth.Bids[i][0].(float64)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("float64", depth.Bids[i][0], "bid price")
|
||||
}
|
||||
book.Bids[i] = orderbook.Item{
|
||||
Amount: amt,
|
||||
Price: price,
|
||||
}
|
||||
}
|
||||
|
||||
book.Asks.Reverse() // Reverse asks for correct alignment
|
||||
|
||||
err = z.Websocket.Orderbook.LoadSnapshot(&book)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case strings.Contains(result.Channel, "_order"):
|
||||
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
|
||||
var o WsSubmitOrderResponse
|
||||
err := json.Unmarshal(fixedJSON, &o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !o.Success {
|
||||
return fmt.Errorf("%s - Order %v failed to be placed. %s",
|
||||
z.Name,
|
||||
o.Data.EntrustID,
|
||||
respRaw)
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(cPair[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var a asset.Item
|
||||
a, err = z.GetPairAssetType(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.Websocket.DataHandler <- &order.Detail{
|
||||
Exchange: z.Name,
|
||||
OrderID: strconv.FormatInt(o.Data.EntrustID, 10),
|
||||
Pair: p,
|
||||
AssetType: a,
|
||||
}
|
||||
case strings.Contains(result.Channel, "_cancelorder"):
|
||||
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
|
||||
var o WsSubmitOrderResponse
|
||||
err := json.Unmarshal(fixedJSON, &o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !o.Success {
|
||||
return fmt.Errorf("%s - Order %v failed to be cancelled. %s",
|
||||
z.Name,
|
||||
o.Data.EntrustID,
|
||||
respRaw)
|
||||
}
|
||||
|
||||
p, err := currency.NewPairFromString(cPair[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.Websocket.DataHandler <- &order.Detail{
|
||||
Exchange: z.Name,
|
||||
OrderID: strconv.FormatInt(o.Data.EntrustID, 10),
|
||||
Pair: p,
|
||||
Status: order.Cancelled,
|
||||
}
|
||||
case strings.Contains(result.Channel, "trades"):
|
||||
if !z.IsSaveTradeDataEnabled() {
|
||||
return nil
|
||||
}
|
||||
var tradeData WsTrades
|
||||
err := json.Unmarshal(fixedJSON, &tradeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var trades []trade.Data
|
||||
for i := range tradeData.Data {
|
||||
channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter)
|
||||
cPair, err := currency.NewPairFromString(channelInfo[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tSide order.Side
|
||||
tSide, err = order.StringToOrderSide(tradeData.Data[i].Type)
|
||||
if err != nil {
|
||||
return &order.ClassificationError{
|
||||
Exchange: z.Name,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
trades = append(trades, trade.Data{
|
||||
Timestamp: time.Unix(tradeData.Data[i].Date, 0),
|
||||
CurrencyPair: cPair,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: z.Name,
|
||||
Price: tradeData.Data[i].Price,
|
||||
Amount: tradeData.Data[i].Amount,
|
||||
Side: tSide,
|
||||
TID: strconv.FormatInt(tradeData.Data[i].TID, 10),
|
||||
})
|
||||
}
|
||||
return trade.AddTradesToBuffer(z.Name, trades...)
|
||||
default:
|
||||
z.Websocket.DataHandler <- stream.UnhandledMessageWarning{
|
||||
Message: z.Name +
|
||||
stream.UnhandledMessage +
|
||||
string(respRaw)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (z *ZB) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
|
||||
var subscriptions []subscription.Subscription
|
||||
// market configuration is its own channel
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: "markets",
|
||||
})
|
||||
channels := []string{"%s_ticker", "%s_depth", "%s_trades"}
|
||||
enabledCurrencies, err := z.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
subscriptions = append(subscriptions, subscription.Subscription{
|
||||
Channel: fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()),
|
||||
Pair: enabledCurrencies[j].Lower(),
|
||||
Asset: asset.Spot,
|
||||
})
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (z *ZB) Subscribe(channelsToSubscribe []subscription.Subscription) error {
|
||||
var errs error
|
||||
for i := range channelsToSubscribe {
|
||||
subscriptionRequest := Subscription{
|
||||
Event: zWebsocketAddChannel,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
}
|
||||
err := z.Websocket.Conn.SendJSONMessage(subscriptionRequest)
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
}
|
||||
z.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
|
||||
}
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsGenerateSignature(secret string, request interface{}) (string, error) {
|
||||
jsonResponse, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hex, err := crypto.Sha1ToHex(secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hmac, err := crypto.GetHMAC(crypto.HashMD5,
|
||||
jsonResponse,
|
||||
[]byte(hex))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%x", hmac), nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsFixInvalidJSON(json []byte) []byte {
|
||||
invalidZbJSONRegex := `(\"\[|\"\{)(.*)(\]\"|\}\")`
|
||||
regexChecker := regexp.MustCompile(invalidZbJSONRegex)
|
||||
matchingResults := regexChecker.Find(json)
|
||||
if matchingResults == nil {
|
||||
return json
|
||||
}
|
||||
// Remove first quote character
|
||||
capturedInvalidZBJSON := strings.Replace(string(matchingResults), "\"", "", 1)
|
||||
// Remove last quote character
|
||||
fixedJSON := capturedInvalidZBJSON[:len(capturedInvalidZBJSON)-1]
|
||||
return []byte(strings.Replace(string(json), string(matchingResults), fixedJSON, 1))
|
||||
}
|
||||
|
||||
func (z *ZB) wsAddSubUser(ctx context.Context, username, password string) (*WsGetSubUserListResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request := WsAddSubUserRequest{
|
||||
Memo: "memo",
|
||||
Password: password,
|
||||
SubUserName: username,
|
||||
}
|
||||
request.Channel = "addSubUser"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
request.No = z.Websocket.Conn.GenerateMessageID(true)
|
||||
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var genericResponse Generic
|
||||
err = json.Unmarshal(resp, &genericResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if genericResponse.Code > 0 && genericResponse.Code != 1000 {
|
||||
return nil,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
genericResponse.Message,
|
||||
wsErrCodes[genericResponse.Code])
|
||||
}
|
||||
var response WsGetSubUserListResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsGetSubUserList(ctx context.Context) (*WsGetSubUserListResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request := WsAuthenticatedRequest{}
|
||||
request.Channel = "getSubUserList"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
request.No = z.Websocket.Conn.GenerateMessageID(true)
|
||||
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsGetSubUserListResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsDoTransferFunds(ctx context.Context, pair currency.Code, amount float64, fromUserName, toUserName string) (*WsRequestResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := WsDoTransferFundsRequest{
|
||||
Amount: amount,
|
||||
Currency: pair,
|
||||
FromUserName: fromUserName,
|
||||
ToUserName: toUserName,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
request.Channel = "doTransferFunds"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsRequestResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsCreateSubUserKey(ctx context.Context, assetPerm, entrustPerm, leverPerm, moneyPerm bool, keyName, toUserID string) (*WsRequestResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request := WsCreateSubUserKeyRequest{
|
||||
AssetPerm: assetPerm,
|
||||
EntrustPerm: entrustPerm,
|
||||
KeyName: keyName,
|
||||
LeverPerm: leverPerm,
|
||||
MoneyPerm: moneyPerm,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
ToUserID: toUserID,
|
||||
}
|
||||
request.Channel = "createSubUserKey"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsRequestResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsSubmitOrder(ctx context.Context, pair currency.Pair, amount, price float64, tradeType int64) (*WsSubmitOrderResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request := WsSubmitOrderRequest{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
TradeType: tradeType,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
request.Channel = pair.String() + "_order"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsSubmitOrderResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsCancelOrder(ctx context.Context, pair currency.Pair, orderID int64) (*WsCancelOrderResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := WsCancelOrderRequest{
|
||||
ID: orderID,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
request.Channel = pair.String() + "_cancelorder"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsCancelOrderResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsGetOrder(ctx context.Context, pair currency.Pair, orderID int64) (*WsGetOrderResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request := WsGetOrderRequest{
|
||||
ID: orderID,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
request.Channel = pair.String() + "_getorder"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsGetOrderResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsGetOrders(ctx context.Context, pair currency.Pair, pageIndex, tradeType int64) (*WsGetOrdersResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := WsGetOrdersRequest{
|
||||
PageIndex: pageIndex,
|
||||
TradeType: tradeType,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
request.Channel = pair.String() + "_getorders"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsGetOrdersResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsGetOrdersIgnoreTradeType(ctx context.Context, pair currency.Pair, pageIndex, pageSize int64) (*WsGetOrdersIgnoreTradeTypeResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := WsGetOrdersIgnoreTradeTypeRequest{
|
||||
PageIndex: pageIndex,
|
||||
PageSize: pageSize,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
request.Channel = pair.String() + "_getordersignoretradetype"
|
||||
request.Event = zWebsocketAddChannel
|
||||
request.Accesskey = creds.Key
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsGetOrdersIgnoreTradeTypeResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (z *ZB) wsGetAccountInfoRequest(ctx context.Context) (*WsGetAccountInfoResponse, error) {
|
||||
if !z.IsWebsocketAuthenticationSupported() {
|
||||
return nil,
|
||||
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
|
||||
}
|
||||
creds, err := z.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := WsAuthenticatedRequest{
|
||||
Channel: "getaccountinfo",
|
||||
Event: zWebsocketAddChannel,
|
||||
Accesskey: creds.Key,
|
||||
No: z.Websocket.Conn.GenerateMessageID(true),
|
||||
}
|
||||
|
||||
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsGetAccountInfoResponse
|
||||
err = json.Unmarshal(resp, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Code > 0 && response.Code != 1000 {
|
||||
return &response,
|
||||
fmt.Errorf("%v request failed, message: %v, error code: %v",
|
||||
z.Name,
|
||||
response.Message,
|
||||
wsErrCodes[response.Code])
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
package zb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
// Subscription defines an initial subscription type to be sent
|
||||
type Subscription struct {
|
||||
Event string `json:"event"`
|
||||
Channel string `json:"channel"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
}
|
||||
|
||||
// Generic defines a generic fields associated with many return types
|
||||
type Generic struct {
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Message interface{} `json:"message"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// Markets defines market data
|
||||
type Markets map[string]struct {
|
||||
AmountScale int64 `json:"amountScale"`
|
||||
PriceScale int64 `json:"priceScale"`
|
||||
}
|
||||
|
||||
// WsTicker defines websocket ticker data
|
||||
type WsTicker struct {
|
||||
Date int64 `json:"date,string"`
|
||||
Data struct {
|
||||
Volume24Hr float64 `json:"vol,string"`
|
||||
High float64 `json:"high,string"`
|
||||
Low float64 `json:"low,string"`
|
||||
Last float64 `json:"last,string"`
|
||||
Buy float64 `json:"buy,string"`
|
||||
Sell float64 `json:"sell,string"`
|
||||
} `json:"ticker"`
|
||||
}
|
||||
|
||||
// WsDepth defines websocket orderbook data
|
||||
type WsDepth struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Asks [][]interface{} `json:"asks"`
|
||||
Bids [][]interface{} `json:"bids"`
|
||||
}
|
||||
|
||||
// WsTrades defines websocket trade data
|
||||
type WsTrades struct {
|
||||
Data []struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
TID int64 `json:"tid"`
|
||||
Date int64 `json:"date"`
|
||||
Type string `json:"type"`
|
||||
TradeType string `json:"trade_type"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedRequest base request type
|
||||
type WsAuthenticatedRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
}
|
||||
|
||||
// WsAddSubUserRequest data to add sub users
|
||||
type WsAddSubUserRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
Memo string `json:"memo"`
|
||||
Password string `json:"password"`
|
||||
SubUserName string `json:"subUserName"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
}
|
||||
|
||||
// WsCreateSubUserKeyRequest data to add sub user keys
|
||||
type WsCreateSubUserKeyRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
AssetPerm bool `json:"assetPerm,string"`
|
||||
Channel string `json:"channel"`
|
||||
EntrustPerm bool `json:"entrustPerm,string"`
|
||||
Event string `json:"event"`
|
||||
KeyName string `json:"keyName"`
|
||||
LeverPerm bool `json:"leverPerm,string"`
|
||||
MoneyPerm bool `json:"moneyPerm,string"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
ToUserID string `json:"toUserId"`
|
||||
}
|
||||
|
||||
// WsDoTransferFundsRequest data to transfer funds
|
||||
type WsDoTransferFundsRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Channel string `json:"channel"`
|
||||
Currency currency.Code `json:"currency"`
|
||||
Event string `json:"event"`
|
||||
FromUserName string `json:"fromUserName"`
|
||||
No int64 `json:"no,string"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
ToUserName string `json:"toUserName"`
|
||||
}
|
||||
|
||||
// WsGetSubUserListResponse data response from GetSubUserList
|
||||
type WsGetSubUserListResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Message []WsGetSubUserListResponseData `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
}
|
||||
|
||||
// WsGetSubUserListResponseData user data
|
||||
type WsGetSubUserListResponseData struct {
|
||||
IsOpenAPI bool `json:"isOpenApi,omitempty"`
|
||||
Memo string `json:"memo,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
UserID int64 `json:"userId,omitempty"`
|
||||
IsFreez bool `json:"isFreez,omitempty"`
|
||||
}
|
||||
|
||||
// WsRequestResponse generic response data
|
||||
type WsRequestResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Message interface{} `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
}
|
||||
|
||||
// WsSubmitOrderRequest creates an order via ws
|
||||
type WsSubmitOrderRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
No int64 `json:"no,string,omitempty"`
|
||||
Price float64 `json:"price,string"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
TradeType int64 `json:"tradeType,string"`
|
||||
}
|
||||
|
||||
// WsSubmitOrderResponse data about submitted order
|
||||
type WsSubmitOrderResponse struct {
|
||||
Message string `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
Data struct {
|
||||
EntrustID int64 `json:"intrustID"`
|
||||
} `json:"data"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
// WsCancelOrderRequest order cancel request
|
||||
type WsCancelOrderRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
ID int64 `json:"id"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
No int64 `json:"no,string"`
|
||||
}
|
||||
|
||||
// WsCancelOrderResponse order cancel response
|
||||
type WsCancelOrderResponse struct {
|
||||
Message string `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
// WsGetOrderRequest Get specific order details
|
||||
type WsGetOrderRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
ID int64 `json:"id"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
No int64 `json:"no,string"`
|
||||
}
|
||||
|
||||
// WsGetOrderResponse contains order data
|
||||
type WsGetOrderResponse struct {
|
||||
Message string `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Success bool `json:"success"`
|
||||
Data []Order `json:"data"`
|
||||
}
|
||||
|
||||
// WsGetOrdersRequest get more orders, with no orderID filtering
|
||||
type WsGetOrdersRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
No int64 `json:"no,string"`
|
||||
PageIndex int64 `json:"pageIndex"`
|
||||
TradeType int64 `json:"tradeType"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
}
|
||||
|
||||
// WsGetOrdersResponse contains orders data
|
||||
type WsGetOrdersResponse struct {
|
||||
Message string `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Success bool `json:"success"`
|
||||
Data []Order `json:"data"`
|
||||
}
|
||||
|
||||
// WsGetOrdersIgnoreTradeTypeRequest ws request
|
||||
type WsGetOrdersIgnoreTradeTypeRequest struct {
|
||||
Accesskey string `json:"accesskey"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:"event"`
|
||||
No int64 `json:"no,string"`
|
||||
PageIndex int64 `json:"pageIndex"`
|
||||
PageSize int64 `json:"pageSize"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
}
|
||||
|
||||
// WsGetOrdersIgnoreTradeTypeResponse contains orders data
|
||||
type WsGetOrdersIgnoreTradeTypeResponse struct {
|
||||
Message string `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Success bool `json:"success"`
|
||||
Data []Order `json:"data"`
|
||||
}
|
||||
|
||||
// WsGetAccountInfoResponse contains account data
|
||||
type WsGetAccountInfoResponse struct {
|
||||
Message string `json:"message"`
|
||||
No int64 `json:"no,string"`
|
||||
Data struct {
|
||||
Coins []AccountsResponseCoin `json:"coins"`
|
||||
Base AccountsBaseResponse `json:"base"`
|
||||
} `json:"data"`
|
||||
Code int64 `json:"code"`
|
||||
Channel string `json:"channel"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
var wsErrCodes = map[int64]string{
|
||||
1000: "Successful call",
|
||||
1001: "General error message",
|
||||
1002: "internal error",
|
||||
1003: "Verification failed",
|
||||
1004: "Financial security password lock",
|
||||
1005: "The fund security password is incorrect. Please confirm and re-enter.",
|
||||
1006: "Real-name certification is awaiting review or review",
|
||||
1007: "Channel is empty",
|
||||
1008: "Event is empty",
|
||||
1009: "This interface is being maintained",
|
||||
1011: "Not open yet",
|
||||
1012: "Insufficient permissions",
|
||||
1013: "Can not trade, if you have any questions, please contact online customer service",
|
||||
1014: "Cannot be sold during the pre-sale period",
|
||||
2002: "Insufficient balance in Bitcoin account",
|
||||
2003: "Insufficient balance of Litecoin account",
|
||||
2005: "Insufficient balance in Ethereum account",
|
||||
2006: "Insufficient balance in ETC currency account",
|
||||
2007: "Insufficient balance of BTS currency account",
|
||||
2008: "Insufficient balance in EOS currency account",
|
||||
2009: "Insufficient account balance",
|
||||
3001: "Pending order not found",
|
||||
3002: "Invalid amount",
|
||||
3003: "Invalid quantity",
|
||||
3004: "User does not exist",
|
||||
3005: "Invalid parameter",
|
||||
3006: "Invalid IP or inconsistent with the bound IP",
|
||||
3007: "Request time has expired",
|
||||
3008: "Transaction history not found",
|
||||
4001: "API interface is locked",
|
||||
4002: "Request too frequently",
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
78
testdata/configtest.json
vendored
78
testdata/configtest.json
vendored
@@ -2307,84 +2307,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ZB",
|
||||
"enabled": true,
|
||||
"verbose": false,
|
||||
"httpTimeout": 15000000000,
|
||||
"websocketResponseCheckTimeout": 30000000,
|
||||
"websocketResponseMaxLimit": 7000000000,
|
||||
"websocketTrafficTimeout": 30000000000,
|
||||
"websocketOrderbookBufferLimit": 5,
|
||||
"baseCurrencies": "USD",
|
||||
"currencyPairs": {
|
||||
"requestFormat": {
|
||||
"uppercase": false,
|
||||
"delimiter": "_"
|
||||
},
|
||||
"configFormat": {
|
||||
"uppercase": true,
|
||||
"delimiter": "_"
|
||||
},
|
||||
"useGlobalFormat": true,
|
||||
"assetTypes": [
|
||||
"spot"
|
||||
],
|
||||
"pairs": {
|
||||
"spot": {
|
||||
"enabled": "BTC_USDT,ETH_USDT",
|
||||
"available": "TRUE_BTC,LTC_USDT,ACC_USDT,MANA_BTC,GRIN_USDT,HC_USDT,ADA_BTC,SLT_BTC,TRX_QC,CRO_USDT,BCHABC_USDT,MANA_QC,OMG_USDT,BTS_QC,TRUE_USDT,BCW_QC,TOPC_QC,ZB_BTC,SAFE_QC,BTH_USDT,MCO_USDT,UBTC_QC,XEM_USDT,BTC_QC,B91_QC,BCW_USDT,MCO_QC,BTP_QC,CHAT_USDT,VSYS_BTC,HSR_USDT,DDM_USDT,MANA_USDT,XLM_BTC,ETC_QC,KAN_QC,ZRX_USDT,TUSD_USDT,SBTC_USDT,NXWC_USDT,BRC_BTC,PDX_BTC,XUC_QC,ETH_QC,EOSDAC_USDT,BTN_USDT,GNT_QC,BRC_USDT,LTC_BTC,SUB_QC,INK_USDT,EOSDAC_QC,TRUE_QC,XLM_USDT,BTS_BTC,HLC_QC,YTNB_USDT,AE_BTC,PDX_QC,DOGE_USDT,XWC_USDT,ADA_QC,BSV_USDT,XEM_BTC,DDM_QC,LBTC_USDT,SAFE_USDT,OMG_QC,EDO_USDT,XMR_QC,MITH_QC,TV_QC,TOPC_USDT,CRO_QC,LVN_USDT,HOTC_QC,TRX_USDT,XLM_QC,HLC_USDT,QUN_USDT,BTM_BTC,TV_USDT,NEO_BTC,QTUM_USDT,ZRX_QC,SNT_USDT,XWC_QC,HC_QC,ENTC_USDT,XTZ_USDT,BITCNY_QC,EPC_QC,KAN_BTC,AAA_QC,PAX_USDT,BCHABC_QC,QTUM_BTC,HPY_QC,GNT_USDT,BCD_QC,SLT_QC,BAT_BTC,HC_BTC,BCHSV_QC,HSR_BTC,AE_QC,HX_QC,INK_QC,LBTC_QC,BDS_QC,XRP_BTC,VSYS_ZB,ETC_USDT,OMG_BTC,1ST_USDT,SLT_USDT,BAR_USDT,NEO_USDT,BCD_USDT,MTL_USDT,XEM_QC,BSV_QC,BTP_USDT,TRX_BTC,XRP_QC,ETZ_QC,LVN_QC,BTM_QC,DASH_BTC,BTN_QC,LBTC_BTC,EPC_BTC,FN_QC,PAX_QC,NWT_USDT,XMR_USDT,ICX_BTC,1ST_QC,BCX_USDT,EOS_USDT,KNC_QC,EOS_BTC,BTM_USDT,USDT_QC,BAT_USDT,NEO_QC,LTC_QC,ETZ_USDT,CDC_QC,ICX_QC,RCN_USDT,BTC_USDT,BRC_QC,TSR_USDT,ETH_BTC,GRIN_QC,SNT_QC,ICX_USDT,ETC_BTC,TV_BTC,ADA_USDT,GNT_BTC,CDC_USDT,B91_USDT,DASH_QC,PDX_USDT,GRAM_USDT,GRAM_QC,HSR_QC,HOTC_USDT,XRP_USDT,VSYS_QC,LEO_USDT,HX_USDT,QTUM_QC,ZB_QC,ETH_USDT,BCHSV_USDT,QUN_QC,BCX_QC,ZB_USDT,HPY_USDT,DOGE_BTC,BCX_BTC,SNT_BTC,DASH_USDT,ZRX_BTC,BTH_QC,KNC_USDT,UBTC_USDT,BTS_USDT,BITE_BTC,DOGE_QC,BAT_QC,EOS_QC,GRAM_BTC"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authenticatedSupport": false,
|
||||
"authenticatedWebsocketApiSupport": false,
|
||||
"endpoints": {
|
||||
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
|
||||
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
|
||||
},
|
||||
"credentials": {
|
||||
"key": "Key",
|
||||
"secret": "Secret"
|
||||
},
|
||||
"credentialsValidator": {
|
||||
"requiresKey": true,
|
||||
"requiresSecret": true
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"supports": {
|
||||
"restAPI": true,
|
||||
"restCapabilities": {
|
||||
"tickerBatching": true,
|
||||
"autoPairUpdates": true
|
||||
},
|
||||
"websocketAPI": true,
|
||||
"websocketCapabilities": {}
|
||||
},
|
||||
"enabled": {
|
||||
"autoPairUpdates": true,
|
||||
"websocketAPI": false
|
||||
}
|
||||
},
|
||||
"bankAccounts": [
|
||||
{
|
||||
"enabled": false,
|
||||
"bankName": "",
|
||||
"bankAddress": "",
|
||||
"bankPostalCode": "",
|
||||
"bankPostalCity": "",
|
||||
"bankCountry": "",
|
||||
"accountName": "",
|
||||
"accountNumber": "",
|
||||
"swiftCode": "",
|
||||
"iban": "",
|
||||
"supportedCurrencies": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Bitmex",
|
||||
"enabled": true,
|
||||
|
||||
3
testdata/exchangelist.csv
vendored
3
testdata/exchangelist.csv
vendored
@@ -22,5 +22,4 @@ lbank,
|
||||
okcoin,
|
||||
okx,
|
||||
poloniex,
|
||||
yobit,
|
||||
zb,
|
||||
yobit,
|
||||
|
3689
testdata/http_mock/zb/zb.json
vendored
3689
testdata/http_mock/zb/zb.json
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user