├── .gitignore ├── secret.yaml ├── internal ├── util_test.go ├── util.go ├── sig.go ├── sig_test.go ├── payload.go ├── payload_test.go └── req.go ├── pkg ├── bitopro │ ├── account_balance_test.go │ ├── order_cancel_test.go │ ├── order_history_test.go │ ├── order_get_test.go │ ├── trades_test.go │ ├── trading_pair_info_test.go │ ├── order_list_test.go │ ├── tickers_test.go │ ├── ticker_test.go │ ├── util.go │ ├── bitopro.go │ ├── tickers.go │ ├── account_balance.go │ ├── order_history.go │ ├── order_list.go │ ├── order_book_test.go │ ├── order_create_test.go │ ├── trades.go │ ├── ticker.go │ ├── order_cancel.go │ ├── order_book.go │ ├── trading_pair_info.go │ ├── order_get.go │ └── order_create.go └── ws │ ├── private_ws_test.go │ ├── private_ws.go │ ├── public_ws.go │ ├── ws.go │ ├── public_ws_test.go │ └── models.go ├── go.mod ├── LICENSE ├── go.sum └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | secret.yml 2 | .vscode/ 3 | .vscode/* -------------------------------------------------------------------------------- /secret.yaml: -------------------------------------------------------------------------------- 1 | key: "test_api_key_425" 2 | secret: "test_api_secret_425" 3 | endpoint: "https://staging-api.bitopro.com" 4 | -------------------------------------------------------------------------------- /internal/util_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "testing" 4 | 5 | func TestGetTimestamp(t *testing.T) { 6 | t.Log(1554454849723) 7 | t.Log(GetTimestamp()) 8 | } 9 | -------------------------------------------------------------------------------- /internal/util.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // GetTimestamp func 8 | func GetTimestamp() int64 { 9 | return time.Now().UnixNano() / 1e6 10 | } 11 | -------------------------------------------------------------------------------- /internal/sig.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha512" 6 | "encoding/hex" 7 | ) 8 | 9 | // GetSig func 10 | func getSig(secret, payload string) string { 11 | h := hmac.New(sha512.New384, []byte(secret)) 12 | h.Write([]byte(payload)) 13 | 14 | return hex.EncodeToString(h.Sum(nil)) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/bitopro/account_balance_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetAccountBalance(t *testing.T) { 9 | if json, err := json.MarshalIndent(getAuthClient().GetAccountBalance(), "", ""); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/bitopro/order_cancel_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_CancelOrder(t *testing.T) { 9 | if json, err := json.MarshalIndent(getAuthClient().CancelOrder("eth_twd", 2559364785), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /internal/sig_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_getSig(t *testing.T) { 8 | if getSig("bitopro", "eyJpZGVudGl0eSI6ImhjbWxpbmpAZ21haWwuY29tIiwibm9uY2UiOjE1NTQzODA5MDkxMzF9") != "01a85a9083db47c20da7196380598f3feacd3c76a9077aaf7ffaf08ce0091abf65b61778792607b010921adfe1c2941a" { 9 | t.Error("not match") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/bitopro/order_history_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | "testing" 7 | ) 8 | 9 | func TestAuthAPI_GetOrderHistory(t *testing.T) { 10 | if json, err := json.MarshalIndent(getAuthClient().GetOrderHistory("btc_usdt", url.Values{}), "", " "); err != nil { 11 | t.Error(err) 12 | } else { 13 | t.Logf("\n%s", string(json)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/bitopro/order_get_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetOrder(t *testing.T) { 9 | var resp []byte 10 | var err error 11 | getAuthResp, err := getAuthClient().GetOrder("usdt_twd", "384453381") 12 | if err != nil { 13 | t.Errorf("getAuthResp err %+v\n", err) 14 | } 15 | if resp, err = json.MarshalIndent(getAuthResp, "", " "); err != nil { 16 | t.Error(err) 17 | } else { 18 | t.Logf("resp=%+v\n", string(resp)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/bitopro/trades_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetTrades(t *testing.T) { 9 | if json, err := json.MarshalIndent(GetPubClient().GetTrades("eth_twd"), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | 15 | if json, err := json.MarshalIndent(getAuthClient().GetTrades("eth_twd"), "", " "); err != nil { 16 | t.Error(err) 17 | } else { 18 | t.Logf("\n%s", string(json)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/bitopro/trading_pair_info_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetTradingPairInfos(t *testing.T) { 9 | if json, err := json.MarshalIndent(GetPubClient().GetTradingPairInfos(), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | 15 | if json, err := json.MarshalIndent(getAuthClient().GetTradingPairInfos(), "", " "); err != nil { 16 | t.Error(err) 17 | } else { 18 | t.Logf("\n%s", string(json)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/bitopro/order_list_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetOrderList(t *testing.T) { 9 | if json, err := json.MarshalIndent(getAuthClient().GetOrderList("eth_twd", false, 1), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | 15 | if json, err := json.MarshalIndent(getAuthClient().GetOrderList("eth_twd", true, 1), "", " "); err != nil { 16 | t.Error(err) 17 | } else { 18 | t.Logf("\n%s", string(json)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/bitopro/tickers_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestPubAPI_GetTickers(t *testing.T) { 9 | if json, err := json.MarshalIndent(GetPubClient().GetTickers(), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | } 15 | 16 | func TestAuthAPI_GetTickers(t *testing.T) { 17 | if json, err := json.MarshalIndent(getAuthClient().GetTickers(), "", " "); err != nil { 18 | t.Error(err) 19 | } else { 20 | t.Logf("\n%s", string(json)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/bitopro/ticker_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetTicker(t *testing.T) { 9 | if json, err := json.MarshalIndent(getAuthClient().GetTicker("eth_twd"), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | } 15 | 16 | func TestPubAPI_GetTicker(t *testing.T) { 17 | if json, err := json.MarshalIndent(GetPubClient().GetTicker("eth_twd"), "", " "); err != nil { 18 | t.Error(err) 19 | } else { 20 | t.Logf("\n%s", string(json)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/payload.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | ) 7 | 8 | func getNonPostPayload(identity string, nonce int64) string { 9 | payload, _ := json.Marshal(map[string]interface{}{ 10 | "identity": identity, 11 | "nonce": nonce, 12 | }) 13 | 14 | return base64.StdEncoding.EncodeToString(payload) 15 | } 16 | 17 | func getPostPayload(body map[string]interface{}) (string, string, error) { 18 | payload, err := json.Marshal(body) 19 | if err != nil { 20 | return "", "", err 21 | } 22 | 23 | return string(payload), base64.StdEncoding.EncodeToString(payload), nil 24 | } 25 | -------------------------------------------------------------------------------- /pkg/bitopro/util.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/bitoex/bitopro-api-go/internal" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | var ( 11 | authAPI *AuthAPI 12 | once sync.Once 13 | ) 14 | 15 | func init() { 16 | viper.AddConfigPath(".") 17 | viper.AddConfigPath("../..") 18 | viper.SetConfigName("secret") 19 | viper.ReadInConfig() 20 | } 21 | 22 | func SetEndpoint(in string) { 23 | internal.ApiURL = in 24 | } 25 | 26 | func getAuthClient() *AuthAPI { 27 | once.Do(func() { 28 | authAPI = GetAuthClient(viper.GetString("identity"), viper.GetString("key"), viper.GetString("secret")) 29 | }) 30 | 31 | return authAPI 32 | } 33 | -------------------------------------------------------------------------------- /internal/payload_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_getNonPostPayload(t *testing.T) { 8 | if getNonPostPayload("support@bitoex.com", 1554380909131) != "eyJpZGVudGl0eSI6InN1cHBvcnRAYml0b2V4LmNvbSIsIm5vbmNlIjoxNTU0MzgwOTA5MTMxfQ==" { 9 | t.Error("not match") 10 | } 11 | } 12 | 13 | func Test_getPostPayload(t *testing.T) { 14 | if _, payload, _ := getPostPayload(map[string]interface{}{ 15 | "action": "buy", 16 | "type": "limit", 17 | "price": "1.123456789", 18 | "amount": "666", 19 | "timestamp": 1554380909131, 20 | }); payload != "eyJhY3Rpb24iOiJidXkiLCJhbW91bnQiOiI2NjYiLCJwcmljZSI6IjEuMTIzNDU2Nzg5IiwidGltZXN0YW1wIjoxNTU0MzgwOTA5MTMxLCJ0eXBlIjoibGltaXQifQ==" { 21 | t.Error("not match") 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bitoex/bitopro-api-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/gorilla/websocket v1.5.0 7 | github.com/parnurzeal/gorequest v0.2.15 8 | github.com/spf13/viper v1.3.2 9 | github.com/stretchr/testify v1.3.0 10 | ) 11 | 12 | require ( 13 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a // indirect 14 | github.com/kr/pretty v0.3.0 // indirect 15 | github.com/moul/http2curl v1.0.0 // indirect 16 | github.com/pelletier/go-toml v1.3.0 // indirect 17 | github.com/pkg/errors v0.8.1 // indirect 18 | github.com/smartystreets/goconvey v1.7.2 // indirect 19 | github.com/spf13/afero v1.2.2 // indirect 20 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 21 | golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b // indirect 22 | golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /pkg/bitopro/bitopro.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | // PubAPI struct 4 | type PubAPI struct { 5 | proxy string 6 | } 7 | 8 | // AuthAPI struct 9 | type AuthAPI struct { 10 | identity string 11 | Key string 12 | secret string 13 | proxy string 14 | } 15 | 16 | // GetPubClient func 17 | func GetPubClient() *PubAPI { 18 | return &PubAPI{} 19 | } 20 | 21 | func (a *PubAPI) SetProxy(in string) { 22 | a.proxy = in 23 | } 24 | 25 | func (a *AuthAPI) SetProxy(in string) { 26 | a.proxy = in 27 | } 28 | 29 | // GetAuthClient func 30 | func GetAuthClient(identity, key, secret string) *AuthAPI { 31 | return &AuthAPI{ 32 | identity: identity, 33 | Key: key, 34 | secret: secret, 35 | } 36 | } 37 | 38 | // StatusCode struct 39 | type StatusCode struct { 40 | Code interface{} `json:"code,omitempty"` 41 | Error string `json:"error,omitempty"` 42 | Offset int `json:"Offset,omitempty"` 43 | } 44 | -------------------------------------------------------------------------------- /pkg/bitopro/tickers.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/bitoex/bitopro-api-go/internal" 7 | ) 8 | 9 | // Tickers struct 10 | type Tickers struct { 11 | Data []TickerInfo `json:"data,omitempty"` 12 | StatusCode 13 | } 14 | 15 | func getTickers(proxy string) *Tickers { 16 | var data Tickers 17 | 18 | code, res, err := internal.ReqPublic("v3/tickers", proxy) 19 | if err != nil { 20 | data.Error = err.Error() 21 | } else { 22 | if err := json.Unmarshal([]byte(res), &data); err != nil { 23 | data.Error = res 24 | } 25 | } 26 | 27 | data.Code = code 28 | 29 | return &data 30 | } 31 | 32 | // GetTickers Ref. https://developer.bitopro.com/docs#operation/getTickers 33 | func (p *PubAPI) GetTickers() *Tickers { 34 | return getTickers(p.proxy) 35 | } 36 | 37 | // GetTickers Ref. https://developer.bitopro.com/docs#operation/getTickers 38 | func (a *AuthAPI) GetTickers() *Tickers { 39 | return getTickers(a.proxy) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/bitopro/account_balance.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | // Balance struct 11 | type Balance struct { 12 | Currency string `json:"currency"` 13 | Amount string `json:"amount"` 14 | Available string `json:"available"` 15 | Stake string `json:"stake"` 16 | Tradable bool `json:"tradable"` 17 | } 18 | 19 | // Account struct 20 | type Account struct { 21 | Data []Balance `json:"data,omitempty"` 22 | StatusCode 23 | } 24 | 25 | // GetAccountBalance Ref. https://developer.bitopro.com/docs#operation/getAccountBalance 26 | func (api *AuthAPI) GetAccountBalance() *Account { 27 | var data Account 28 | 29 | code, res, err := internal.ReqWithoutBody(api.identity, api.Key, api.secret, "GET", "v3/accounts/balance", api.proxy) 30 | if err != nil { 31 | data.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 32 | } 33 | 34 | if err = json.Unmarshal([]byte(res), &data); err != nil { 35 | data.Error = res 36 | } 37 | 38 | data.Code = code 39 | 40 | return &data 41 | } 42 | -------------------------------------------------------------------------------- /pkg/ws/private_ws_test.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestAccountBalanceWsConsumer(t *testing.T) { 10 | ctx := context.Background() 11 | ws := NewWs("", "", "", "") 12 | accountBalances, close := ws.RunAccountBalancesWsConsumer(ctx) 13 | if accountBalances == nil { 14 | t.Error("accountBalances is nil") 15 | } 16 | if close == nil { 17 | t.Error("close is nil") 18 | } 19 | accountBalance := <-accountBalances 20 | fmt.Printf("%+v", accountBalance) 21 | if accountBalance.Err != nil { 22 | t.Error("trade.Err is not nil") 23 | } 24 | } 25 | 26 | func TestOrderWsConsumer(t *testing.T) { 27 | ctx := context.Background() 28 | ws := NewWs("", "", "", "") 29 | accountBalances, close := ws.RunOrdersWsConsumer(ctx) 30 | if accountBalances == nil { 31 | t.Error("accountBalances is nil") 32 | } 33 | if close == nil { 34 | t.Error("close is nil") 35 | } 36 | accountBalance := <-accountBalances 37 | fmt.Printf("%+v", accountBalance) 38 | if accountBalance.Err != nil { 39 | t.Error("trade.Err is not nil") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/bitopro/order_history.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | 8 | "github.com/bitoex/bitopro-api-go/internal" 9 | ) 10 | 11 | // OrderHistory struct 12 | type OrderHistory struct { 13 | Data []OrderInfo `json:"data,omitempty"` 14 | StatusCode 15 | } 16 | 17 | // GetOrderHistory Ref. https://developer.bitopro.com/docs#operation/getOrderHistory 18 | func (api *AuthAPI) GetOrderHistory(pair string, queryParams url.Values) *OrderHistory { 19 | var data OrderHistory 20 | 21 | queryStr := queryParams.Encode() 22 | endpoint := fmt.Sprintf("%s%s", "v3/orders/all/", pair) 23 | if queryStr != "" { 24 | endpoint = fmt.Sprintf("%s%s?%s", "v3/orders/all/", pair, queryStr) 25 | } 26 | code, res, err := internal.ReqWithoutBody(api.identity, api.Key, api.secret, "GET", endpoint, api.proxy) 27 | 28 | if err != nil { 29 | data.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 30 | } 31 | 32 | if err := json.Unmarshal([]byte(res), &data); err != nil { 33 | data.Error = res 34 | } 35 | 36 | data.Code = code 37 | 38 | return &data 39 | } 40 | -------------------------------------------------------------------------------- /pkg/bitopro/order_list.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | // OrderList struct 11 | type OrderList struct { 12 | Data []OrderInfo `json:"data,omitempty"` 13 | Page int `json:"page"` 14 | TotalPages int `json:"totalPages"` 15 | StatusCode 16 | } 17 | 18 | // GetOrderList Ref. https://developer.bitopro.com/docs#operation/getOrders 19 | func (api *AuthAPI) GetOrderList(pair string, active bool, page uint) *OrderList { 20 | var data OrderList 21 | 22 | code, res, err := internal.ReqWithoutBody(api.identity, api.Key, api.secret, "GET", fmt.Sprintf("%s/%s?page=%d&active=%v", "v3/orders", pair, page, active), api.proxy) 23 | 24 | if err != nil { 25 | data.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 26 | return &data 27 | } 28 | data.Code = code 29 | if code < 400 && res != "" { 30 | if err := json.Unmarshal([]byte(res), &data); err != nil { 31 | data.Error = res 32 | } 33 | } else { 34 | data.Error = string(res) 35 | } 36 | return &data 37 | } 38 | -------------------------------------------------------------------------------- /pkg/bitopro/order_book_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_GetOrderBook(t *testing.T) { 9 | if json, err := json.MarshalIndent(getAuthClient().GetOrderBook("eth_twd"), "", " "); err != nil { 10 | t.Error(err) 11 | } else { 12 | t.Logf("\n%s", string(json)) 13 | } 14 | } 15 | 16 | func TestPubAPI_GetOrderBook(t *testing.T) { 17 | if json, err := json.MarshalIndent(GetPubClient().GetOrderBook("eth_twd"), "", " "); err != nil { 18 | t.Error(err) 19 | } else { 20 | t.Logf("\n%s", string(json)) 21 | } 22 | } 23 | 24 | func TestPubAPI_GetOrderBookWithLimit(t *testing.T) { 25 | if json, err := json.MarshalIndent(GetPubClient().GetOrderBookWithLimit("eth_twd", 1), "", " "); err != nil { 26 | t.Error(err) 27 | } else { 28 | t.Logf("\n%s", string(json)) 29 | } 30 | } 31 | 32 | func TestAuthAPI_GetOrderBookWithLimit(t *testing.T) { 33 | if json, err := json.MarshalIndent(getAuthClient().GetOrderBookWithLimit("eth_twd", 1), "", " "); err != nil { 34 | t.Error(err) 35 | } else { 36 | t.Logf("\n%s", string(json)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jamie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/bitopro/order_create_test.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestAuthAPI_CreateOrderLimitBuy(t *testing.T) { 9 | // satoshi amount 10 | if json, err := json.MarshalIndent(getAuthClient().CreateOrderLimitBuy(1, "eth_twd", "11", "1"), "", " "); err != nil { 11 | t.Error(err) 12 | } else { 13 | t.Logf("\n%s", string(json)) 14 | } 15 | } 16 | 17 | func TestAuthAPI_CreateOrderLimitSell(t *testing.T) { 18 | if json, err := json.MarshalIndent(getAuthClient().CreateOrderLimitSell(1, "eth_twd", "1", "1"), "", " "); err != nil { 19 | t.Error(err) 20 | } else { 21 | t.Logf("\n%s", string(json)) 22 | } 23 | } 24 | 25 | func TestAuthAPI_CreateOrderMarketBuy(t *testing.T) { 26 | if json, err := json.MarshalIndent(getAuthClient().CreateOrderMarketBuy(1, "eth_twd", "1000"), "", " "); err != nil { 27 | t.Errorf("err=%+v\n", err) 28 | } else { 29 | t.Logf("err=nil, res=%s", string(json)) 30 | } 31 | } 32 | 33 | func TestAuthAPI_CreateOrderMarketSell(t *testing.T) { 34 | if json, err := json.MarshalIndent(getAuthClient().CreateOrderMarketSell(1, "eth_twd", "1"), "", " "); err != nil { 35 | t.Error(err) 36 | } else { 37 | t.Logf("\n%s", string(json)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/bitopro/trades.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | // TradeInfo struct 11 | type TradeInfo struct { 12 | Amount string `json:"amount"` 13 | Price string `json:"price"` 14 | Timestamp int64 `json:"timestamp"` 15 | IsBuyer bool `json:"isBuyer"` 16 | } 17 | 18 | // Trade struct 19 | type Trade struct { 20 | Data []TradeInfo `json:"data,omitempty"` 21 | StatusCode 22 | } 23 | 24 | func getTrades(pair, proxy string) *Trade { 25 | var data Trade 26 | 27 | code, res, err := internal.ReqPublic(fmt.Sprintf("%s/%s", "v3/trades", pair), proxy) 28 | if err != nil { 29 | data.Error = err.Error() 30 | } else { 31 | if err := json.Unmarshal([]byte(res), &data); err != nil { 32 | data.Error = res 33 | } 34 | } 35 | 36 | data.Code = code 37 | 38 | return &data 39 | } 40 | 41 | // GetTrades Ref. https://developer.bitopro.com/docs#operation/getPairTrades 42 | func (p *PubAPI) GetTrades(pair string) *Trade { 43 | return getTrades(pair, p.proxy) 44 | } 45 | 46 | // GetTrades Ref. https://developer.bitopro.com/docs#operation/getPairTrades 47 | func (a *AuthAPI) GetTrades(pair string) *Trade { 48 | return getTrades(pair, a.proxy) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/ws/private_ws.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | func (ws Ws) RunAccountBalancesWsConsumer(ctx context.Context) (account chan AccountBalanceData, close chan struct{}) { 9 | path := "ws/v1/pub/auth/account-balance" 10 | account = make(chan AccountBalanceData, 1000) 11 | close = ws.runWsConnConsumer(ctx, path, true, func(msg []byte, err error) { 12 | if err != nil { 13 | account <- AccountBalanceData{Err: err} 14 | return 15 | } 16 | 17 | var accData AccountBalanceData 18 | err = json.Unmarshal(msg, &accData) 19 | if err != nil { 20 | account <- AccountBalanceData{Err: err} 21 | return 22 | } 23 | account <- accData 24 | }) 25 | return account, close 26 | } 27 | 28 | func (ws Ws) RunOrdersWsConsumer(ctx context.Context) (orders chan OrdersData, close chan struct{}) { 29 | path := "ws/v1/pub/auth/orders" 30 | orders = make(chan OrdersData, 1000) 31 | close = ws.runWsConnConsumer(ctx, path, true, func(msg []byte, err error) { 32 | if err != nil { 33 | orders <- OrdersData{Err: err} 34 | return 35 | } 36 | 37 | var order OrdersData 38 | err = json.Unmarshal(msg, &order) 39 | if err != nil { 40 | orders <- OrdersData{Err: err} 41 | return 42 | } 43 | orders <- order 44 | }) 45 | return orders, close 46 | } 47 | -------------------------------------------------------------------------------- /pkg/bitopro/ticker.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | // TickerInfo struct 11 | type TickerInfo struct { 12 | Pair string `json:"pair"` 13 | IsBuyer bool `json:"isBuyer"` 14 | High24hr string `json:"high24hr"` 15 | Low24hr string `json:"low24hr"` 16 | PriceChange24hr string `json:"priceChange24hr"` 17 | Volume24hr string `json:"volume24hr"` 18 | LastPrice string `json:"lastPrice"` 19 | } 20 | 21 | // Ticker struct 22 | type Ticker struct { 23 | Data TickerInfo `json:"data,omitempty"` 24 | StatusCode 25 | } 26 | 27 | func getTicker(pair, proxy string) *Ticker { 28 | var data Ticker 29 | 30 | code, res, err := internal.ReqPublic(fmt.Sprintf("%s/%s", "v3/tickers", pair), proxy) 31 | if err != nil { 32 | data.Error = err.Error() 33 | } 34 | 35 | if err := json.Unmarshal([]byte(res), &data); err != nil { 36 | data.Error = res 37 | } 38 | 39 | data.Code = code 40 | 41 | return &data 42 | } 43 | 44 | // GetTicker Ref. https://developer.bitopro.com/docs#operation/getTickers 45 | func (p *PubAPI) GetTicker(pair string) *Ticker { 46 | return getTicker(pair, p.proxy) 47 | } 48 | 49 | // GetTicker Ref. https://developer.bitopro.com/docs#operation/getTickers 50 | func (a *AuthAPI) GetTicker(pair string) *Ticker { 51 | return getTicker(pair, a.proxy) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/bitopro/order_cancel.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | // CancelOrder struct 11 | type CancelOrder struct { 12 | OrderID string `json:"orderId,omitempty"` 13 | Action string `json:"action,omitempty"` 14 | Timestamp int64 `json:"timestamp,omitempty"` 15 | Price string `json:"price,omitempty"` 16 | Amount string `json:"amount,omitempty"` 17 | StatusCode 18 | } 19 | 20 | // CancelOrder Ref. https://developer.bitopro.com/docs#operation/cancelOrder 21 | func (api *AuthAPI) CancelOrder(pair string, orderID int) *CancelOrder { 22 | var data CancelOrder 23 | 24 | code, res, err := internal.ReqWithoutBody(api.identity, api.Key, api.secret, "DELETE", fmt.Sprintf("%s/%s/%d", "v3/orders", pair, orderID), api.proxy) 25 | 26 | if err != nil { 27 | data.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 28 | } 29 | 30 | if err = json.Unmarshal([]byte(res), &data); err != nil { 31 | data.Error = res 32 | } 33 | 34 | data.Code = code 35 | 36 | return &data 37 | } 38 | 39 | type CancelAllResp struct { 40 | Data map[string][]string 41 | Error string 42 | Code int 43 | } 44 | 45 | func (api *AuthAPI) CancelAll(pair string) *CancelAllResp { 46 | var data CancelAllResp 47 | 48 | code, res, err := internal.ReqWithoutBody(api.identity, api.Key, api.secret, "DELETE", fmt.Sprintf("%s/%s", "v3/orders", pair), api.proxy) 49 | 50 | if err != nil { 51 | data.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 52 | } 53 | 54 | if err = json.Unmarshal([]byte(res), &data); err != nil { 55 | data.Error = res 56 | } 57 | 58 | data.Code = code 59 | 60 | return &data 61 | } 62 | -------------------------------------------------------------------------------- /pkg/bitopro/order_book.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | // OrderBookInfo struct 11 | type OrderBookInfo struct { 12 | Amount string `json:"amount"` 13 | Price string `json:"price"` 14 | Count int `json:"count"` 15 | Total string `json:"total"` 16 | } 17 | 18 | // OrderBook struct 19 | type OrderBook struct { 20 | Bids []OrderBookInfo `json:"bids"` 21 | Asks []OrderBookInfo `json:"asks"` 22 | StatusCode 23 | } 24 | 25 | func getOrderBook(pair string, limit int, proxy string) *OrderBook { 26 | var data OrderBook 27 | 28 | code, res, err := internal.ReqPublic(fmt.Sprintf("%s/%s?limit=%d", "v3/order-book", pair, limit), proxy) 29 | if err != nil { 30 | data.Error = err.Error() 31 | } else { 32 | if err := json.Unmarshal([]byte(res), &data); err != nil { 33 | data.Error = res 34 | } 35 | } 36 | 37 | data.Code = code 38 | 39 | return &data 40 | } 41 | 42 | // GetOrderBook Ref. https://developer.bitopro.com/docs#operation/getOrderBookByPair 43 | func (p *PubAPI) GetOrderBook(pair string) *OrderBook { 44 | return getOrderBook(pair, 5, p.proxy) 45 | } 46 | 47 | // GetOrderBook Ref. https://developer.bitopro.com/docs#operation/getOrderBookByPair 48 | func (a *AuthAPI) GetOrderBook(pair string) *OrderBook { 49 | return getOrderBook(pair, 5, a.proxy) 50 | } 51 | 52 | // GetOrderBookWithLimit Ref. https://developer.bitopro.com/docs#operation/getOrderBookByPair 53 | func (p *PubAPI) GetOrderBookWithLimit(pair string, limit int) *OrderBook { 54 | return getOrderBook(pair, limit, p.proxy) 55 | } 56 | 57 | // GetOrderBookWithLimit Ref. https://developer.bitopro.com/docs#operation/getOrderBookByPair 58 | func (a *AuthAPI) GetOrderBookWithLimit(pair string, limit int) *OrderBook { 59 | return getOrderBook(pair, limit, a.proxy) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/ws/public_ws.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strings" 7 | ) 8 | 9 | func (ws Ws) RunTickerWsConsumer(ctx context.Context, pairs []string) (tickers chan TickerData, close chan struct{}) { 10 | path := "ws/v1/pub/tickers?pairs=" + strings.Join(pairs, ",") 11 | tickers = make(chan TickerData, 1000) 12 | close = ws.runWsConnConsumer(ctx, path, false, func(msg []byte, err error) { 13 | if err != nil { 14 | tickers <- TickerData{Err: err} 15 | return 16 | } 17 | 18 | var ticker TickerData 19 | err = json.Unmarshal(msg, &ticker) 20 | if err != nil { 21 | tickers <- TickerData{Err: err} 22 | return 23 | } 24 | tickers <- ticker 25 | }) 26 | return tickers, close 27 | } 28 | 29 | func (ws Ws) RunOrderbookWsConsumer(ctx context.Context, pairLimits []string) (orderbooks chan OrderBookData, close chan struct{}) { 30 | path := "ws/v1/pub/order-books?pairs=" + strings.Join(pairLimits, ",") 31 | orderbooks = make(chan OrderBookData, 1000) 32 | close = ws.runWsConnConsumer(ctx, path, false, func(msg []byte, err error) { 33 | if err != nil { 34 | orderbooks <- OrderBookData{Err: err} 35 | return 36 | } 37 | 38 | var orderbook OrderBookData 39 | err = json.Unmarshal(msg, &orderbook) 40 | if err != nil { 41 | orderbooks <- OrderBookData{Err: err} 42 | return 43 | } 44 | orderbooks <- orderbook 45 | }) 46 | return orderbooks, close 47 | } 48 | 49 | func (ws Ws) RunTradesWsConsumer(ctx context.Context, pairs []string) (trades chan TradeData, close chan struct{}) { 50 | path := "ws/v1/pub/trades?pairs=" + strings.Join(pairs, ",") 51 | trades = make(chan TradeData, 1000) 52 | close = ws.runWsConnConsumer(ctx, path, false, func(msg []byte, err error) { 53 | if err != nil { 54 | trades <- TradeData{Err: err} 55 | return 56 | } 57 | 58 | var trade TradeData 59 | err = json.Unmarshal(msg, &trade) 60 | if err != nil { 61 | trades <- TradeData{Err: err} 62 | return 63 | } 64 | trades <- trade 65 | }) 66 | return trades, close 67 | } 68 | -------------------------------------------------------------------------------- /pkg/bitopro/trading_pair_info.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/bitoex/bitopro-api-go/internal" 7 | ) 8 | 9 | type TradingPairInfo struct { 10 | Pair string `json:"pair"` 11 | Base string `json:"base"` 12 | Quote string `json:"quote"` 13 | BasePrecision string `json:"basePrecision"` 14 | QuotePrecision string `json:"quotePrecision"` 15 | MinLimitBaseAmount string `json:"minLimitBaseAmount"` 16 | MaxLimitBaseAmount string `json:"maxLimitBaseAmount"` 17 | MinMarketBuyQuoteAmount string `json:"minMarketBuyQuoteAmount"` 18 | OrderOpenLimit string `json:"orderOpenLimit"` 19 | Maintain bool `json:"maintain"` 20 | OrderBookQuotePrecision string `json:"orderBookQuotePrecision"` 21 | OrderBookQuoteScaleLevel string `json:"orderBookQuoteScaleLevel"` 22 | AmountPrecision string `json:"amountPrecision"` 23 | } 24 | 25 | type TradingPairInfos struct { 26 | Data []TradingPairInfo `json:"data,omitempty"` 27 | StatusCode 28 | } 29 | 30 | func getTradingPairInfos(proxy string) *TradingPairInfos { 31 | var data TradingPairInfos 32 | 33 | code, res, err := internal.ReqPublic("v3/provisioning/trading-pairs", proxy) 34 | if err != nil { 35 | data.Error = err.Error() 36 | } else { 37 | if err := json.Unmarshal([]byte(res), &data); err != nil { 38 | data.Error = res 39 | } 40 | } 41 | 42 | data.Code = code 43 | 44 | return &data 45 | } 46 | 47 | 48 | // GetTradingPairInfos Ref. https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_trading_pair_info.md 49 | func (p *PubAPI) GetTradingPairInfos() *TradingPairInfos { 50 | return getTradingPairInfos(p.proxy) 51 | } 52 | 53 | // GetTradingPairInfos Ref. https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_trading_pair_info.md 54 | func (a *AuthAPI) GetTradingPairInfos() *TradingPairInfos { 55 | return getTradingPairInfos(a.proxy) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/bitopro/order_get.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/bitoex/bitopro-api-go/internal" 9 | ) 10 | 11 | // OrderInfo struct 12 | type OrderInfo struct { 13 | ID string `json:"id"` 14 | Pair string `json:"pair"` 15 | Price string `json:"price"` 16 | AvgExecutionPrice string `json:"avgExecutionPrice,omitempty"` 17 | Action string `json:"action,omitempty"` 18 | Type string `json:"type,omitempty"` 19 | Timestamp int64 `json:"timestamp,omitempty"` 20 | Status int `json:"status,omitempty"` 21 | CreatedTimestamp int64 `json:"createdTimestamp,omitempty"` 22 | UpdatedTimestamp int64 `json:"updatedTimestamp,omitempty"` 23 | OriginalAmount string `json:"originalAmount,omitempty"` 24 | RemainingAmount string `json:"remainingAmount,omitempty"` 25 | ExecutedAmount string `json:"executedAmount,omitempty"` 26 | Fee string `json:"fee,omitempty"` 27 | FeeSymbol string `json:"feeSymbol,omitempty"` 28 | BitoFee string `json:"bitoFee,omitempty"` 29 | Total string `json:"total,omitempty"` 30 | Seq string `json:"seq,omitempty"` 31 | TimeInForce string `json:"timeInForce,omitempty"` 32 | Error error 33 | StatusCode 34 | } 35 | 36 | // GetOrder Ref. https://developer.bitopro.com/docs#operation/getOrderStatus 37 | func (api *AuthAPI) GetOrder(pair string, orderID string) (*OrderInfo, error) { 38 | var data OrderInfo 39 | 40 | code, res, err := internal.ReqWithoutBody(api.identity, api.Key, api.secret, "GET", fmt.Sprintf("%s/%s/%s", "v3/orders", pair, orderID), api.proxy) 41 | 42 | if err != nil { 43 | data.Error = fmt.Errorf("req err:[%+v], res:[%+v]", err, res) 44 | } 45 | 46 | if err = json.Unmarshal([]byte(res), &data); err != nil { 47 | data.Error = err 48 | } 49 | 50 | data.Code = code 51 | if code >= 400 { 52 | data.Error = errors.New(string(res)) 53 | return &data, data.Error 54 | } 55 | if data.Error != nil { 56 | return &data, data.Error 57 | } 58 | 59 | return &data, nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/ws/ws.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/bitoex/bitopro-api-go/internal" 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | func NewPublicWs() *Ws { 13 | return NewWs("", "", "", "") 14 | } 15 | 16 | func NewPrivateWs(email, apiKey, apiSecret string) *Ws { 17 | return NewWs(email, apiKey, apiSecret, "") 18 | } 19 | 20 | // NewWs func 21 | func NewWs(email, apiKey, apiSecret, endpoint string) *Ws { 22 | if endpoint == "" { 23 | endpoint = "wss://stream.bitopro.com:9443" 24 | } 25 | return &Ws{ 26 | Endpoint: endpoint, 27 | ApiKey: apiKey, 28 | ApiSecret: apiSecret, 29 | Email: email, 30 | Dialer: websocket.DefaultDialer, 31 | } 32 | } 33 | 34 | type Ws struct { 35 | Endpoint string 36 | ApiKey string 37 | ApiSecret string 38 | Email string 39 | Dialer *websocket.Dialer 40 | } 41 | 42 | func (ws Ws) getConnection(ctx context.Context, path string, isAuth bool) (*websocket.Conn, error) { 43 | var ( 44 | header = http.Header{} 45 | err error 46 | ) 47 | if isAuth { 48 | header, err = internal.NewAuthHeader(ws.Email, ws.ApiKey, ws.ApiSecret, path, nil) 49 | if err != nil { 50 | return nil, fmt.Errorf("new auth header failed, err:%+v", err) 51 | } 52 | } 53 | if path == "" || path == "/" { 54 | return nil, fmt.Errorf("path cannot be empty") 55 | } 56 | 57 | if path[0] == '/' { 58 | path = path[1:] // remove leading slash from 59 | } 60 | path = fmt.Sprintf("%s/%s", ws.Endpoint, path) 61 | wsConn, resp, err := ws.Dialer.DialContext(ctx, path, header) 62 | if err != nil { 63 | return nil, fmt.Errorf("dial failed, err:%+v, resp:%+v", err, resp) 64 | } 65 | 66 | return wsConn, nil 67 | } 68 | 69 | func (ws Ws) runWsConnConsumer(ctx context.Context, path string, isAuth bool, callback func(msg []byte, err error)) (close chan struct{}) { 70 | close = make(chan struct{}) 71 | go func() { 72 | ws: 73 | wsConn, err := ws.getConnection(ctx, path, isAuth) 74 | if err != nil { 75 | callback(nil, err) 76 | return 77 | } 78 | for { 79 | select { 80 | case <-ctx.Done(): 81 | wsConn.Close() 82 | return 83 | case <-close: 84 | wsConn.Close() 85 | return 86 | default: 87 | _, msg, err := wsConn.ReadMessage() 88 | callback(msg, err) 89 | if err != nil { 90 | wsConn.Close() 91 | goto ws 92 | } 93 | } 94 | } 95 | }() 96 | return close 97 | } 98 | -------------------------------------------------------------------------------- /pkg/ws/public_ws_test.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | // unit test for Ws.RunTickerWsConsumer 12 | func TestTickerWsConsumer(t *testing.T) { 13 | ctx := context.Background() 14 | ws := NewPublicWs() 15 | tickers, close := ws.RunTickerWsConsumer(ctx, []string{"BTC_TWD"}) 16 | if tickers == nil { 17 | t.Error("tickers is nil") 18 | } 19 | if close == nil { 20 | t.Error("close is nil") 21 | } 22 | ticker := <-tickers 23 | fmt.Printf("%+v", ticker) 24 | if ticker.Err != nil { 25 | t.Error("ticker.Err is not nil") 26 | } 27 | assert.Equal(t, ticker.Event, "TICKER") 28 | assert.Equal(t, ticker.Pair, "BTC_TWD") 29 | assert.NotEqual(t, ticker.LastPrice, "") 30 | assert.NotEqual(t, ticker.PriceChange24hr, "") 31 | assert.NotEqual(t, ticker.Volume24hr, "") 32 | assert.NotEqual(t, ticker.High24hr, "") 33 | assert.NotEqual(t, ticker.Low24hr, "") 34 | assert.NotEqual(t, ticker.Timestamp, 0) 35 | assert.NotEqual(t, ticker.DateTime, "") 36 | } 37 | 38 | // unit test for Ws.RunOrderbookWsConsumer 39 | func TestOrderbookWsConsumer(t *testing.T) { 40 | ctx := context.Background() 41 | ws := NewPublicWs() 42 | orderbooks, close := ws.RunOrderbookWsConsumer(ctx, []string{"BTC_TWD:1"}) 43 | if orderbooks == nil { 44 | t.Error("orderbooks is nil") 45 | } 46 | if close == nil { 47 | t.Error("close is nil") 48 | } 49 | orderbook := <-orderbooks 50 | fmt.Printf("%+v", orderbook) 51 | if orderbook.Err != nil { 52 | t.Error("orderbook.Err is not nil") 53 | } 54 | assert.Equal(t, orderbook.Event, "ORDER_BOOK") 55 | assert.Equal(t, orderbook.Pair, "BTC_TWD") 56 | assert.Len(t, orderbook.Asks, 1) 57 | assert.Len(t, orderbook.Bids, 1) 58 | assert.NotEqual(t, orderbook.Timestamp, 0) 59 | assert.NotEqual(t, orderbook.DateTime, "") 60 | } 61 | 62 | // unit test for Ws.RunTradesWsConsumer 63 | func TestTradesWsConsumer(t *testing.T) { 64 | ctx := context.Background() 65 | ws := NewPublicWs() 66 | trades, close := ws.RunTradesWsConsumer(ctx, []string{"BTC_TWD"}) 67 | if trades == nil { 68 | t.Error("trades is nil") 69 | } 70 | if close == nil { 71 | t.Error("close is nil") 72 | } 73 | trade := <-trades 74 | fmt.Printf("%+v", trade) 75 | if trade.Err != nil { 76 | t.Error("trade.Err is not nil") 77 | } 78 | assert.Equal(t, trade.Event, "TRADE") 79 | assert.Equal(t, trade.Pair, "BTC_TWD") 80 | assert.NotEqual(t, trade.Timestamp, 0) 81 | assert.NotEqual(t, trade.DateTime, "") 82 | assert.NotZero(t, trade.Data) 83 | assert.NotEmpty(t, trade.Data[0].Amount) 84 | assert.NotEmpty(t, trade.Data[0].Price) 85 | assert.NotEmpty(t, trade.Data[0].Timestamp) 86 | } 87 | -------------------------------------------------------------------------------- /internal/req.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/parnurzeal/gorequest" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var ApiURL = "https://api.bitopro.com" 13 | 14 | func init() { 15 | viper.AddConfigPath(".") 16 | viper.AddConfigPath("../..") 17 | viper.SetConfigName("secret") 18 | viper.ReadInConfig() 19 | endpoint := viper.GetString("endpoint") 20 | if endpoint != "" { 21 | ApiURL = endpoint 22 | } 23 | } 24 | 25 | func SetEndpoint(in string) { 26 | ApiURL = in 27 | } 28 | 29 | func getReq(proxy string) *gorequest.SuperAgent { 30 | req := gorequest.New() 31 | if proxy != "" { 32 | req = req.Proxy(proxy) 33 | } 34 | return req 35 | } 36 | 37 | // ReqPublic func 38 | func ReqPublic(api, proxy string) (int, string, error) { 39 | req := getReq(proxy) 40 | req = req.Get(fmt.Sprintf("%s/%s", ApiURL, api)) 41 | req.Set("X-BITOPRO-API", "golang") 42 | 43 | res, body, errList := req.End() 44 | if len(errList) > 0 { 45 | return 0, body, getErrByErrList(errList) 46 | } 47 | return res.StatusCode, body, nil 48 | } 49 | 50 | // ReqWithoutBody func 51 | func ReqWithoutBody(identity, apiKey, apiSecret, method, endpoint, proxy string) (int, string, error) { 52 | payload := getNonPostPayload(identity, GetTimestamp()) 53 | sig := getSig(apiSecret, payload) 54 | url := fmt.Sprintf("%s/%s", ApiURL, endpoint) 55 | req := getReq(proxy) 56 | 57 | switch strings.ToUpper(method) { 58 | case "GET": 59 | req = req.Get(url) 60 | case "DELETE": 61 | req = req.Delete(url) 62 | default: 63 | return http.StatusMethodNotAllowed, fmt.Sprintf("Method Not Allowed: %s", method), fmt.Errorf("Method Not Allowed: %s", method) 64 | } 65 | 66 | req.Set("X-BITOPRO-APIKEY", apiKey) 67 | req.Set("X-BITOPRO-PAYLOAD", payload) 68 | req.Set("X-BITOPRO-SIGNATURE", sig) 69 | req.Set("X-BITOPRO-API", "golang") 70 | 71 | res, body, errList := req.End() 72 | if len(errList) > 0 { 73 | return 0, body, getErrByErrList(errList) 74 | } 75 | if len(errList) == 0 { 76 | return res.StatusCode, body, nil 77 | } 78 | return res.StatusCode, body, getErrByErrList(errList) 79 | } 80 | 81 | // ReqWithBody func 82 | func ReqWithBody(identity, apiKey, apiSecret, endpoint, proxy string, param map[string]interface{}) (int, string, error) { 83 | body, payload, err := getPostPayload(param) 84 | if err != nil { 85 | return 0, "", err 86 | } 87 | sig := getSig(apiSecret, payload) 88 | url := fmt.Sprintf("%s/%s", ApiURL, endpoint) 89 | req := getReq(proxy) 90 | 91 | req = req.Post(url) 92 | req.Set("X-BITOPRO-APIKEY", apiKey) 93 | req.Set("X-BITOPRO-PAYLOAD", payload) 94 | req.Set("X-BITOPRO-SIGNATURE", sig) 95 | req.Set("X-BITOPRO-API", "golang") 96 | req.Send(body) 97 | 98 | res, body, errList := req.End() 99 | if len(errList) > 0 { 100 | return 0, body, getErrByErrList(errList) 101 | } 102 | 103 | if len(errList) == 0 { 104 | return res.StatusCode, body, nil 105 | } 106 | 107 | return res.StatusCode, body, getErrByErrList(errList) 108 | } 109 | 110 | func getErrByErrList(errList []error) error { 111 | var errStr string 112 | for i, v := range errList { 113 | errStr = fmt.Sprintf("%d:%s; ", i, v.Error()) 114 | } 115 | return fmt.Errorf("errs: %s", errStr) 116 | } 117 | 118 | func NewAuthHeader(identity, apiKey, apiSecret string, method string, body map[string]interface{}) (http.Header, error) { 119 | var ( 120 | payload string 121 | err error 122 | ) 123 | if method == "POST" { 124 | _, payload, err = getPostPayload(body) 125 | if err != nil { 126 | return nil, err 127 | } 128 | } else { 129 | payload = getNonPostPayload(identity, GetTimestamp()) 130 | } 131 | sig := getSig(apiSecret, payload) 132 | 133 | header := http.Header{} 134 | header.Set("X-BITOPRO-APIKEY", apiKey) 135 | header.Set("X-BITOPRO-PAYLOAD", payload) 136 | header.Set("X-BITOPRO-SIGNATURE", sig) 137 | header.Set("X-BITOPRO-API", "golang") 138 | 139 | return header, nil 140 | } 141 | -------------------------------------------------------------------------------- /pkg/bitopro/order_create.go: -------------------------------------------------------------------------------- 1 | package bitopro 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/bitoex/bitopro-api-go/internal" 8 | ) 9 | 10 | type CreateOrder struct { 11 | OrderID string `json:"orderId,omitempty"` 12 | Timestamp int64 `json:"timestamp,omitempty"` 13 | Action string `json:"action,omitempty"` 14 | Amount string `json:"amount,omitempty"` 15 | IsBuy string `json:"isBuy,omitempty"` 16 | Price string `json:"price,omitempty"` 17 | TimeInForce string `json:"timeInForce,omitempty"` 18 | ClientID int `json:"clientId,omitempty"` 19 | Type string `json:"type,omitempty"` 20 | StatusCode 21 | } 22 | 23 | func createOrder(api *AuthAPI, pair string, body map[string]interface{}) *CreateOrder { 24 | var data CreateOrder 25 | 26 | code, res, err := internal.ReqWithBody(api.identity, api.Key, api.secret, fmt.Sprintf("%s/%s", "v3/orders", pair), api.proxy, body) 27 | 28 | if err != nil { 29 | data.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 30 | } 31 | 32 | if err = json.Unmarshal([]byte(res), &data); err != nil { 33 | data.Error = res 34 | } 35 | 36 | data.Code = code 37 | 38 | return &data 39 | } 40 | 41 | // CreateOrderLimitBuy Ref. https://developer.bitopro.com/docs#operation/createOrder 42 | func (api *AuthAPI) CreateOrderLimitBuy(clientID int, pair, price, amount string) *CreateOrder { 43 | return createOrder(api, pair, map[string]interface{}{ 44 | "type": "limit", 45 | "action": "buy", 46 | "price": price, 47 | "amount": amount, 48 | "clientId": clientID, 49 | "timestamp": internal.GetTimestamp(), 50 | }) 51 | } 52 | 53 | // CreateOrderLimitSell Ref. https://developer.bitopro.com/docs#operation/createOrder 54 | func (api *AuthAPI) CreateOrderLimitSell(clientID int, pair, price, amount string) *CreateOrder { 55 | return createOrder(api, pair, map[string]interface{}{ 56 | "type": "limit", 57 | "action": "sell", 58 | "price": price, 59 | "amount": amount, 60 | "clientId": clientID, 61 | "timestamp": internal.GetTimestamp(), 62 | }) 63 | } 64 | 65 | // CreateOrderMarketBuy Ref. https://developer.bitopro.com/docs#operation/createOrder 66 | func (api *AuthAPI) CreateOrderMarketBuy(clientID int, pair, amount string) *CreateOrder { 67 | return createOrder(api, pair, map[string]interface{}{ 68 | "type": "market", 69 | "action": "buy", 70 | "amount": amount, 71 | "clientId": clientID, 72 | "timestamp": internal.GetTimestamp(), 73 | }) 74 | } 75 | 76 | // CreateOrderMarketSell Ref. https://developer.bitopro.com/docs#operation/createOrder 77 | func (api *AuthAPI) CreateOrderMarketSell(clientID int, pair, amount string) *CreateOrder { 78 | return createOrder(api, pair, map[string]interface{}{ 79 | "type": "market", 80 | "action": "sell", 81 | "amount": amount, 82 | "clientId": clientID, 83 | "timestamp": internal.GetTimestamp(), 84 | }) 85 | } 86 | 87 | type OrderData struct { 88 | Pair string `json:"pair,omitempty"` 89 | Action string `json:"action,omitempty"` 90 | Type string `json:"type,omitempty"` 91 | Price string `json:"price,omitempty"` 92 | Amount string `json:"amount,omitempty"` 93 | Timestamp int64 `json:"timestamp,omitempty"` 94 | } 95 | 96 | type BatchCreateOrders struct { 97 | Data []struct { 98 | OrderID string `json:"orderId,omitempty"` 99 | Timestamp int64 `json:"timestamp,omitempty"` 100 | Action string `json:"action,omitempty"` 101 | Amount string `json:"amount,omitempty"` 102 | IsBuy string `json:"isBuy,omitempty"` 103 | Price string `json:"price,omitempty"` 104 | TimeInForce string `json:"timeInForce,omitempty"` 105 | ClientID int `json:"clientId,omitempty"` 106 | Type string `json:"type,omitempty"` 107 | } `json:"data"` 108 | StatusCode 109 | } 110 | 111 | // CreateOrderMarketSell Ref. https://developer.bitopro.com/docs#operation/createOrder 112 | func (api *AuthAPI) BatchCreateOrders(orders *OrderData) *BatchCreateOrders { 113 | resp := BatchCreateOrders{} 114 | data := map[string]interface{}{ 115 | "data": orders, 116 | } 117 | 118 | code, res, err := internal.ReqWithBody(api.identity, api.Key, api.secret, "/v3/orders/batch", api.proxy, data) 119 | if err != nil { 120 | resp.Error = fmt.Sprintf("req err:[%+v], res:[%+v]", err, res) 121 | } 122 | 123 | if code != 200 { 124 | resp.Error = res 125 | } else { 126 | if err = json.Unmarshal([]byte(res), &resp); err != nil { 127 | resp.Error = fmt.Sprintf("unmarshal failed, err:%+v", err) 128 | } 129 | } 130 | 131 | return &resp 132 | } 133 | -------------------------------------------------------------------------------- /pkg/ws/models.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | // OrderBookData struct 4 | // 5 | // { 6 | // "event": "ORDER_BOOK", 7 | // "pair": "BTC_TWD", 8 | // "bids": [ 9 | // { 10 | // "price": "1", 11 | // "amount": "1", 12 | // "count": 1, 13 | // "total": "1" 14 | // } 15 | // ... 16 | // ], 17 | // "asks": [ 18 | // { 19 | // "price": "0.9", 20 | // "amount": "1", 21 | // "count": 2, 22 | // "total": "2" 23 | // } 24 | // ... 25 | // ], 26 | // "timestamp": 1136185445000, 27 | // "datetime": "2006-01-02T15:04:05.700Z" 28 | // } 29 | type OrderBookData struct { 30 | Event string `json:"event"` 31 | Pair string `json:"pair"` 32 | Bids []struct { 33 | Price string `json:"price"` 34 | Amount string `json:"amount"` 35 | Count int `json:"count"` 36 | Total string `json:"total"` 37 | } `json:"bids"` 38 | Asks []struct { 39 | Price string `json:"price"` 40 | Amount string `json:"amount"` 41 | Count int `json:"count"` 42 | Total string `json:"total"` 43 | } `json:"asks"` 44 | Timestamp int64 `json:"timestamp"` 45 | DateTime string `json:"datetime"` 46 | Err error 47 | } 48 | 49 | // TickerData struct 50 | // 51 | // { 52 | // "event": "TICKER", 53 | // "pair": "BTC_TWD", 54 | // "lastPrice": "1", 55 | // "isBuyer": true, 56 | // "priceChange24hr": "1", 57 | // "volume24hr": "1", 58 | // "high24hr": "1", 59 | // "low24hr": "1", 60 | // "timestamp": 1136185445000, 61 | // "datetime": "2006-01-02T15:04:05.700Z" 62 | // } 63 | type TickerData struct { 64 | Event string `json:"event"` 65 | Pair string `json:"pair"` 66 | LastPrice string `json:"lastPrice"` 67 | IsBuyer bool `json:"isBuyer"` 68 | PriceChange24hr string `json:"priceChange24hr"` 69 | Volume24hr string `json:"volume24hr"` 70 | High24hr string `json:"high24hr"` 71 | Low24hr string `json:"low24hr"` 72 | Timestamp int64 `json:"timestamp"` 73 | DateTime string `json:"datetime"` 74 | Err error 75 | } 76 | 77 | // TradeData struct 78 | // 79 | // { 80 | // "event": "TRADE", 81 | // "pair": "BTC_TWD", 82 | // "timestamp": 1136185445000, 83 | // "datetime": "2006-01-02T15:04:05.700Z", 84 | // "data": [ 85 | // { 86 | // "timestamp": 1136185445, 87 | // "price": "1", 88 | // "amount": "1", 89 | // "isBuyer": false 90 | // }, 91 | // { 92 | // "timestamp": 1136185445, 93 | // "price": "1", 94 | // "amount": "1", 95 | // "isBuyer": true 96 | // } 97 | // ... 98 | // ] 99 | // } 100 | type TradeData struct { 101 | Event string `json:"event"` 102 | Pair string `json:"pair"` 103 | Timestamp int64 `json:"timestamp"` 104 | DateTime string `json:"datetime"` 105 | Data []struct { 106 | Timestamp int64 `json:"timestamp"` 107 | Price string `json:"price"` 108 | Amount string `json:"amount"` 109 | IsBuyer bool `json:"isBuyer"` 110 | } `json:"data"` 111 | Err error 112 | } 113 | 114 | // AccountBalanceData 115 | // 116 | // { 117 | // "event": "ACCOUNT_BALANCE", 118 | // "timestamp": 1639553303365, 119 | // "datetime": "2021-12-15T07:28:23.365Z", 120 | // 121 | // "data": { 122 | // "ADA": { 123 | // "currency": "ADA", 124 | // "amount": "999999999999.99999999", 125 | // "available": "999999999999.99999999", 126 | // "stake": "0", 127 | // "tradable": true 128 | // }, 129 | type AccountBalanceData struct { 130 | Event string `json:"event"` 131 | Timestamp int64 `json:"timestamp"` 132 | DateTime string `json:"datetime"` 133 | Data map[string]struct { 134 | Currency string `json:"currency"` 135 | Amount string `json:"amount"` 136 | Available string `json:"available"` 137 | Stake string `json:"stake"` 138 | Tradable bool `json:"tradable"` 139 | } `json:"data"` 140 | Err error 141 | } 142 | 143 | // OrdersData 144 | // 145 | // { 146 | // "event": "ACTIVE_ORDERS", 147 | // "timestamp": 1639552073346, 148 | // "datetime": "2021-12-15T07:07:53.346Z", 149 | // "data": { 150 | // "sol_usdt": [ 151 | // { 152 | // "id": "8917255503", 153 | // "pair": "sol_usdt", 154 | // "price": "107", 155 | // "avgExecutionPrice": "0", 156 | // "action": "SELL", 157 | // "type": "LIMIT", 158 | // "timestamp": 1639386803663, 159 | // "updatedTimestamp": 1639386803663, 160 | // "createdTimestamp": 1639386803663, 161 | // "status": 0, 162 | // "originalAmount": "0.02", 163 | // "remainingAmount": "0.02", 164 | // "executedAmount": "0", 165 | // "fee": "0", 166 | // "feeSymbol": "usdt", 167 | // "bitoFee": "0", 168 | // "total": "0", 169 | // "seq": "SOLUSDT3273528249", 170 | // "timeInForce": "GTC" 171 | // } 172 | // ], 173 | type OrdersData struct { 174 | Event string `json:"event"` 175 | Timestamp int64 `json:"timestamp"` 176 | DateTime string `json:"datetime"` 177 | Data map[string][]struct { 178 | ID string `json:"id"` 179 | Pair string `json:"pair"` 180 | Price string `json:"price"` 181 | AvgExecutionPrice string `json:"avgExecutionPrice"` 182 | Action string `json:"action"` 183 | Type string `json:"type"` 184 | Timestamp int64 `json:"timestamp"` 185 | UpdatedTimestamp int64 `json:"updatedTimestamp"` 186 | CreatedTimestamp int64 `json:"createdTimestamp"` 187 | Status int `json:"status"` 188 | OriginalAmount string `json:"originalAmount"` 189 | RemainingAmount string `json:"remainingAmount"` 190 | ExecutedAmount string `json:"executedAmount"` 191 | Fee string `json:"fee"` 192 | FeeSymbol string `json:"feeSymbol"` 193 | BitoFee string `json:"bitoFee"` 194 | Total string `json:"total"` 195 | Seq string `json:"seq"` 196 | TimeInForce string `json:"timeInForce"` 197 | } `json:"data"` 198 | Err error 199 | } 200 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 4 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 5 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 6 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a h1:A4wNiqeKqU56ZhtnzJCTyPZ1+cyu8jKtIchQ3TtxHgw= 12 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 13 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 14 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 15 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 16 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 17 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 18 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 19 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 20 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 21 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 22 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 23 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 24 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 25 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 26 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 27 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 28 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 29 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 30 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 31 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 32 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 33 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 34 | github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= 35 | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 36 | github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU= 37 | github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= 38 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 39 | github.com/pelletier/go-toml v1.3.0 h1:e5+lF2E4Y2WCIxBefVowBuB0iHrUH4HZ8q+6mGF7fJc= 40 | github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= 41 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 42 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 46 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 47 | github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 48 | github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 49 | github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 50 | github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 51 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 52 | github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= 53 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 54 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 55 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 56 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 57 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 58 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 59 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 60 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 61 | github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= 62 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 63 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 64 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 65 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 66 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 67 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 68 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 69 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 70 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 71 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 72 | golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b h1:/zjbcJPEGAyu6Is/VBOALsgdi4z9+kz/Vtdm6S+beD0= 73 | golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 74 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 75 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 76 | golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= 77 | golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 78 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 79 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 80 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 82 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 83 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 84 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 85 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 86 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BitoPro API Client for Go 2 | 3 | - [BitoPro API Client for Go](#BitoPro-API-Client-for-Go) 4 | - [Requirement](#Requirement) 5 | - [Installation](#Installation) 6 | - [Limitations](#Limitations) 7 | - [Rate Limit](#Rate-Limit) 8 | - [Precisions](#Precisions) 9 | - [Minimum order amount](#Minimum-order-amount) 10 | - [Getting Started](#Getting-Started) 11 | - [Public API](#Public-API) 12 | - [Authenticated API](#Authenticated-API) 13 | - [Basic](#Basic) 14 | - [From Viper](#From-Viper) 15 | - [Public REST Endpoints](#Public-REST-Endpoints) 16 | - [GetOrderBook](#GetOrderBook) 17 | - [GetTicker](#GetTicker) 18 | - [GetTickers](#GetTickers) 19 | - [GetTrades](#GetTrades) 20 | - [Authenticated REST Endpoints](#Authenticated-REST-Endpoints) 21 | - [GetAccountBalance](#GetAccountBalance) 22 | - [GetOrderHistory](#GetOrderHistory) 23 | - [GetOrderList](#GetOrderList) 24 | - [CreateOrderLimitBuy/CreateOrderLimitSell/CreateOrderMarketBuy/CreateOrderMarketSell](#CreateOrderLimitBuyCreateOrderLimitSellCreateOrderMarketBuyCreateOrderMarketSell) 25 | - [CancelOrder](#CancelOrder) 26 | - [GetOrder](#GetOrder) 27 | - [Websocket](#Websocket) 28 | - [Tickers Stream](#ticker-stream) 29 | - [Trades Stream](#trade-stream) 30 | - [OrderBook Stream](#orderbook-stream) 31 | - [AccountBalance Stream](#accountbalance-stream) 32 | - [UserOrder Stream](#userorders-stream) 33 | - [Contributing](#Contributing) 34 | - [License](#License) 35 | 36 | ## Requirement 37 | 38 | Requires minimum of Go 1.11. 39 | 40 | ## Installation 41 | 42 | ```sh 43 | $ go get github.com/bitoex/bitopro-api-go 44 | ``` 45 | 46 | ### Limitations 47 | 48 | #### Rate Limit 49 | 50 | There is rate limits applied to each API, please check [API documentation](https://developer.bitopro.com/docs) for more detail. 51 | 52 | #### Precisions 53 | 54 | Both price and amount are subject to decimal restrictions, please check [official settings](https://www.bitopro.com/fees) for more detail. 55 | 56 | #### Minimum order amount 57 | 58 | Checkout the [official settings](https://www.bitopro.com/fees) of minimum amount. 59 | 60 | ## Getting Started 61 | 62 | #### Public API 63 | 64 | Methods for public APIs are packaged in `PubAPI` struct which can be created through `GetPubClient`. 65 | 66 | ```go 67 | import "github.com/bitoex/bitopro-api-go/pkg/bitopro" 68 | 69 | pubClient := bitopro.GetPubClient() 70 | ``` 71 | 72 | #### Authenticated API 73 | 74 | Methods for authenticated APIs are packaged in `AuthAPI` struct which can be created in various ways through `GetAuthClient` depends on how you setup your authenticate information. To use the authenticated APIs, you need the following information, **API Key**, **API Secret**, **Identity (Account Email)**. You can create an API key 75 | [here](https://www.bitopro.com/api). 76 | 77 | ##### Basic 78 | 79 | ```go 80 | import "github.com/bitoex/bitopro-api-go/pkg/bitopro" 81 | 82 | authClient := bitopro.GetAuthClient("your identity (email)", "your key", "your secret") 83 | ``` 84 | 85 | ##### From Viper 86 | 87 | ```yaml 88 | # secret.yml 89 | identity: <> 90 | key: <> 91 | secret: <> 92 | ``` 93 | 94 | ```go 95 | import ( 96 | "github.com/spf13/viper" 97 | 98 | "github.com/bitoex/bitopro-api-go/pkg/bitopro" 99 | ) 100 | 101 | viper.AddConfigPath(".") 102 | viper.SetConfigName("secret") 103 | viper.ReadInConfig() 104 | 105 | authAPI := GetAuthClient(viper.GetString("identity"), viper.GetString("key"), viper.GetString("secret")) 106 | ``` 107 | 108 | ### Public REST Endpoints 109 | 110 | #### GetOrderBook 111 | 112 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/order_book_test.go) 113 | 114 | ```go 115 | pubClient.GetOrderBook("btc_twd") 116 | //or 117 | authClient.GetOrderBook("btc_twd") 118 | ``` 119 | 120 |
121 | JSON Response 122 | 123 | ```js 124 | { 125 | "bids": [ 126 | { 127 | "price": "180500", 128 | "amount": "0.12817687", 129 | "count": 1, 130 | "total": "0.12817687" 131 | }, 132 | { 133 | "price": "180010", 134 | "amount": "0.32292", 135 | "count": 2, 136 | "total": "0.45109687" 137 | }, 138 | { 139 | "price": "180000", 140 | "amount": "0.24236", 141 | "count": 3, 142 | "total": "0.69345687" 143 | } 144 | ], 145 | "asks": [ 146 | { 147 | "price": "180599", 148 | "amount": "0.00326056", 149 | "count": 1, 150 | "total": "0.00326056" 151 | }, 152 | { 153 | "price": "180600", 154 | "amount": "0.04202575", 155 | "count": 1, 156 | "total": "0.04528631" 157 | } 158 | ] 159 | } 160 | ``` 161 |
162 | 163 | #### GetTicker 164 | 165 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/ticker_test.go) 166 | 167 | ```go 168 | pubClient.GetTicker("btc_twd") 169 | //or 170 | authClient.GetTicker("btc_twd") 171 | ``` 172 | 173 |
174 | JSON Response 175 | 176 | ```js 177 | { 178 | "data": { 179 | "pair": "btc_twd", 180 | "lastPrice": "0.00010800", 181 | "isBuyer": false, 182 | "priceChange24hr": "0", 183 | "volume24hr": "0.00000000", 184 | "high24hr": "0.00010800", 185 | "low24hr": "0.00010800" 186 | } 187 | } 188 | ``` 189 |
190 | 191 | #### GetTickers 192 | 193 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/tickers_test.go) 194 | 195 | ```go 196 | pubClient.GetTickers() 197 | //or 198 | authClient.GetTickers() 199 | ``` 200 | 201 |
202 | JSON Response 203 | 204 | ```js 205 | { 206 | "data": [ 207 | { 208 | "pair": "xem_btc", 209 | "lastPrice": "0.00000098", 210 | "isBuyer": false, 211 | "priceChange24hr": "0", 212 | "volume24hr": "0.00000000", 213 | "high24hr": "0.00000098", 214 | "low24hr": "0.00000098" 215 | }, 216 | { 217 | "pair": "bch_eth", 218 | "lastPrice": "0.60010000", 219 | "isBuyer": false, 220 | "priceChange24hr": "0", 221 | "volume24hr": "0.00000000", 222 | "high24hr": "0.60010000", 223 | "low24hr": "0.60010000" 224 | }, 225 | { 226 | "pair": "eth_usdt", 227 | "lastPrice": "179.22000000", 228 | "isBuyer": true, 229 | "priceChange24hr": "10.85", 230 | "volume24hr": "925.14654180", 231 | "high24hr": "182.30000000", 232 | "low24hr": "159.94000000" 233 | } 234 | ] 235 | } 236 | ``` 237 |
238 | 239 | #### GetTrades 240 | 241 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/trades_test.go) 242 | 243 | ```go 244 | pubClient.GetTrades("btc_twd") 245 | //or 246 | authClient.GetTrades("btc_twd") 247 | ``` 248 | 249 |
250 | JSON Response 251 | 252 | ```js 253 | { 254 | "data": [ 255 | { 256 | "timestamp": 1557203407, 257 | "price": "180500.00000000", 258 | "amount": "0.07717687", 259 | "isBuyer": false 260 | }, 261 | { 262 | "timestamp": 1557203187, 263 | "price": "180500.00000000", 264 | "amount": "0.05100000", 265 | "isBuyer": false 266 | }, 267 | { 268 | "timestamp": 1557203053, 269 | "price": "180500.00000000", 270 | "amount": "0.01860000", 271 | "isBuyer": false 272 | }, 273 | { 274 | "timestamp": 1557202804, 275 | "price": "180500.00000000", 276 | "amount": "0.04781533", 277 | "isBuyer": false 278 | }, 279 | { 280 | "timestamp": 1557202804, 281 | "price": "180500.00000000", 282 | "amount": "0.06000000", 283 | "isBuyer": false 284 | } 285 | ] 286 | } 287 | ``` 288 | 289 |
290 | 291 | ### Authenticated REST Endpoints 292 | 293 | #### GetAccountBalance 294 | 295 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/account_balance_test.go) 296 | 297 | ```go 298 | authClient.GetAccountBalance() 299 | ``` 300 | 301 |
302 | JSON Response 303 | 304 | ```js 305 | { 306 | "data": [ 307 | { 308 | "amount": "10001", 309 | "available": "1.0", 310 | "currency": "bito", 311 | "stake": "10000" 312 | }, 313 | { 314 | "amount": "0.0", 315 | "available": "1.0", 316 | "currency": "btc", 317 | "stake": "0" 318 | }, 319 | { 320 | "amount": "3.0", 321 | "available": "0.01", 322 | "currency": "eth", 323 | "stake": "0" 324 | }, 325 | { 326 | "amount": "30000", 327 | "available": "2500", 328 | "currency": "twd", 329 | "stake": "0" 330 | } 331 | ] 332 | } 333 | ``` 334 | 335 |
336 | 337 | #### GetOrderHistory 338 | 339 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/order_history_test.go) 340 | 341 | ```go 342 | authClient.GetOrderHistory() 343 | ``` 344 | 345 |
346 | JSON Response 347 | 348 | ```js 349 | { 350 | "data": [ 351 | { 352 | "action": "buy", 353 | "avgExecutionPrice": "100000.00000000", 354 | "bitoFee": "0.00000000", 355 | "executedAmount": "1.00000000", 356 | "fee": "0.00100000", 357 | "feeSymbol": "BTC", 358 | "id": "123", 359 | "originalAmount": "1.00000000", 360 | "pair": "btc_twd", 361 | "price": "100000.00000000", 362 | "remainingAmount": "0.00000000", 363 | "status": 2, 364 | "timestamp": 1508753757000, 365 | "type": "limit" 366 | }, 367 | { 368 | "action": "buy", 369 | "avgExecutionPrice": "100000.00000000", 370 | "bitoFee": "0.00000000", 371 | "executedAmount": "1.00000000", 372 | "fee": "0.00200000", 373 | "feeSymbol": "BTC", 374 | "id": "456", 375 | "originalAmount": "1.00000000", 376 | "pair": "btc_twd", 377 | "price": "100000.00000000", 378 | "remainingAmount": "0.00000000", 379 | "status": 2, 380 | "timestamp": 1508753787000, 381 | "type": "limit" 382 | } 383 | ] 384 | } 385 | ``` 386 | 387 |
388 | 389 | #### GetOrderList 390 | 391 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/order_list_test.go) 392 | 393 | ```go 394 | authClient.GetOrderList("btc_twd", false, 1) 395 | ``` 396 | 397 |
398 | JSON Response 399 | 400 | ```js 401 | { 402 | "data": [ 403 | { 404 | "action": "buy", 405 | "avgExecutionPrice": "100000.00000000", 406 | "bitoFee": "0.00000000", 407 | "executedAmount": "1.00000000", 408 | "fee": "0.00100000", 409 | "feeSymbol": "BTC", 410 | "id": "123", 411 | "originalAmount": "1.00000000", 412 | "pair": "btc_twd", 413 | "price": "100000.00000000", 414 | "remainingAmount": "0.00000000", 415 | "status": 2, 416 | "timestamp": 1508753757000, 417 | "type": "limit" 418 | }, 419 | { 420 | "action": "buy", 421 | "avgExecutionPrice": "100000.00000000", 422 | "bitoFee": "0.00000000", 423 | "executedAmount": "1.00000000", 424 | "fee": "0.00200000", 425 | "feeSymbol": "BTC", 426 | "id": "456", 427 | "originalAmount": "1.00000000", 428 | "pair": "btc_twd", 429 | "price": "100000.00000000", 430 | "remainingAmount": "0.00000000", 431 | "status": 2, 432 | "timestamp": 1508753787000, 433 | "type": "limit" 434 | } 435 | ], 436 | "page": 1, 437 | "totalPages": 10 438 | } 439 | ``` 440 | 441 |
442 | 443 | #### CreateOrderLimitBuy/CreateOrderLimitSell/CreateOrderMarketBuy/CreateOrderMarketSell 444 | 445 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/order_create_test.go) 446 | 447 | ```go 448 | // create limit buy order 449 | authClient.CreateOrderLimitBuy("eth_twd", "0.1", "1") 450 | // create limit sell order 451 | authClient.CreateOrderLimitSell("eth_twd", "0.1", "1") 452 | // create market buy order 453 | authClient.CreateOrderMarketBuy("eth_twd", "1") 454 | // create market sell order 455 | authClient.CreateOrderMarketSell("eth_twd", "1") 456 | ``` 457 | 458 |
459 | JSON Response 460 | 461 | ```js 462 | { 463 | "action": "buy", 464 | "amount": "0.235", 465 | "orderId": "11233456", 466 | "price": "1.0", 467 | "timestamp": 1504262258000 468 | } 469 | ``` 470 | 471 |
472 | 473 | #### CancelOrder 474 | 475 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/order_cancel_test.go) 476 | 477 | ```go 478 | authClient.CancelOrder("eth_twd", 7517762903) 479 | ``` 480 | 481 |
482 | JSON Response 483 | 484 | ```js 485 | { 486 | "action": "buy", 487 | "amount": 2.3, 488 | "orderId": "7517762903", 489 | "price": 1.2, 490 | "timestamp": 1504262258000 491 | } 492 | ``` 493 | 494 |
495 | 496 | #### GetOrder 497 | 498 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/bitopro/order_get_test.go) 499 | 500 | ```go 501 | authClient.GetOrder("btc_twd", 2640904509) 502 | ``` 503 | 504 |
505 | JSON Response 506 | 507 | ```js 508 | { 509 | "action": "sell", 510 | "avgExecutionPrice": "112000.00000000", 511 | "bitoFee": "103.70370360", 512 | "executedAmount": "1.00000000", 513 | "fee": "0.00000000", 514 | "feeSymbol": "TWD", 515 | "id": "2640904509", 516 | "originalAmount": "1.00000000", 517 | "pair": "btc_twd", 518 | "price": "112000.00000000", 519 | "remainingAmount": "0.00000000", 520 | "status": 2, 521 | "timestamp": 1508753757000, 522 | "type": "limit" 523 | } 524 | ``` 525 | 526 |
527 | 528 | ### Websocket 529 | 530 | ```go 531 | publicWs := ws.NewPublicWs() 532 | privateWs := ws.NewPrivateWs("email", "api_key", "api_secret") 533 | ``` 534 | 535 | 536 | #### Ticker Stream 537 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/ws/public_ws_test.go#L12) 538 | 539 | ```go 540 | tickers, closeCh := publicWs.RunTickerWsConsumer(ctx, []string{"BTC_TWD"}) 541 | defer close(closeCh) 542 | 543 | for { 544 | ticker <- tickers 545 | if ticker.Err != nil { 546 | fmt.Printf("%+v\n", err) 547 | return 548 | } 549 | fmt.Printf("%+v\n", ticker) 550 | } 551 | 552 | ``` 553 | 554 | #### OrderBook Stream 555 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/ws/public_ws_test.go#L39) 556 | 557 | ```go 558 | orderBooks, closeCh := publicWs.RunOrderbookWsConsumer(ctx, []string{"BTC_TWD:30"}) 559 | defer close(closeCh) 560 | 561 | for { 562 | orderBook <- orderBooks 563 | if orderBook.Err != nil { 564 | fmt.Printf("%+v\n", err) 565 | return 566 | } 567 | fmt.Printf("%+v\n", orderBook) 568 | fmt.Printf("%+v\n", len(orderBook.Bids)) // => 30 569 | fmt.Printf("%+v\n", len(orderBook.Asks)) // => 30 570 | } 571 | 572 | ``` 573 | 574 | #### Trade Stream 575 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/ws/public_ws_test.go#L62) 576 | 577 | ```go 578 | trades, closeCh := publicWs.RunTradesWsConsumer(ctx, []string{"BTC_TWD"}) 579 | defer close(closeCh) 580 | 581 | for { 582 | trade <- trades 583 | if trade.Err != nil { 584 | fmt.Printf("%+v\n", err) 585 | return 586 | } 587 | fmt.Printf("%+v\n", trade) 588 | } 589 | ``` 590 | 591 | #### AccountBalance Stream 592 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/ws/private_ws_test.go#L9) 593 | 594 | ```go 595 | accBalances, closeCh := privateWs.RunAccountBalancesWsConsumer(ctx) 596 | defer close(closeCh) 597 | 598 | for { 599 | accBalance <- accBalances 600 | if accBalance.Err != nil { 601 | fmt.Printf("%+v\n", err) 602 | return 603 | } 604 | fmt.Printf("%+v\n", accBalance) 605 | } 606 | ``` 607 | 608 | #### UserOrders Stream 609 | [example](https://github.com/bitoex/bitopro-api-go/blob/master/pkg/ws/private_ws_test.go#L26) 610 | 611 | ```go 612 | ordersList, closeCh := privateWs.RunOrdersWsConsumer(ctx) 613 | defer close(closeCh) 614 | 615 | for { 616 | orders <- ordersList 617 | if orders.Err != nil { 618 | fmt.Printf("%+v\n", err) 619 | return 620 | } 621 | fmt.Printf("%+v\n", orders) 622 | } 623 | ``` 624 | 625 | ## Contributing 626 | 627 | Bug reports and pull requests are welcome on GitHub at [bitopro-api-go](https://github.com/bitoex/bitopro-api-go) and this project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 628 | 629 | 1. Fork it. 630 | 2. Create your feature branch (```git checkout -b my-new-feature```). 631 | 3. Commit your changes (```git commit -am 'Added some feature'```). 632 | 4. Push to the branch (```git push origin my-new-feature```). 633 | 5. Create new Pull Request. 634 | 635 | ## License 636 | 637 | The SDK is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). --------------------------------------------------------------------------------