diff --git a/README.md b/README.md index 459a9e376e1..30bb4f3158b 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Alphapoint | Yes | Yes | NA | | ANXPRO | Yes | No | NA | | Bitfinex | Yes | Yes | NA | +| Bithumb | Yes | NA | NA | | Bitstamp | Yes | Yes | NA | | Bittrex | Yes | No | NA | | BTCC | Yes | Yes | No | @@ -103,7 +104,7 @@ copy %GOPATH%\src\github.com\thrasher-\gocryptotrader\config_example.json %GOPAT -If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: ***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB*** diff --git a/config/config_test.go b/config/config_test.go index 7637c469a02..dbd596dca74 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -89,7 +89,7 @@ func TestGetEnabledExchanges(t *testing.T) { } exchanges := cfg.GetEnabledExchanges() - if len(exchanges) != 20 { + if len(exchanges) != 21 { t.Error( "Test failed. TestGetEnabledExchanges. Enabled exchanges value mismatch", ) @@ -141,7 +141,7 @@ func TestGetDisabledExchanges(t *testing.T) { } func TestCountEnabledExchanges(t *testing.T) { - defaultEnabledExchanges := 20 + defaultEnabledExchanges := 21 GetConfigEnabledExchanges := GetConfig() err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile) if err != nil { diff --git a/config_example.json b/config_example.json index 97c2921518d..94ddf390990 100644 --- a/config_example.json +++ b/config_example.json @@ -101,6 +101,28 @@ "Uppercase": true } }, + { + "Name": "Bithumb", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "UseSandbox": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "ClientID": "ClientID", + "AvailablePairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "EnabledPairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "BaseCurrencies": "KRW", + "AssetTypes": "SPOT", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } + }, { "Name": "Bitstamp", "Enabled": true, @@ -501,4 +523,4 @@ } } ] -} \ No newline at end of file +} diff --git a/exchange.go b/exchange.go index fcb7239b644..392cfcedffa 100644 --- a/exchange.go +++ b/exchange.go @@ -8,6 +8,7 @@ import ( exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/anx" "github.com/thrasher-/gocryptotrader/exchanges/bitfinex" + "github.com/thrasher-/gocryptotrader/exchanges/bithumb" "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" "github.com/thrasher-/gocryptotrader/exchanges/bittrex" "github.com/thrasher-/gocryptotrader/exchanges/btcc" @@ -129,6 +130,8 @@ func LoadExchange(name string) error { exch = new(anx.ANX) case "bitfinex": exch = new(bitfinex.Bitfinex) + case "bithumb": + exch = new(bithumb.Bithumb) case "bitstamp": exch = new(bitstamp.Bitstamp) case "bittrex": diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go new file mode 100644 index 00000000000..0ec5b94591d --- /dev/null +++ b/exchanges/bithumb/bithumb.go @@ -0,0 +1,464 @@ +package bithumb + +import ( + "bytes" + "errors" + "fmt" + "log" + "net/url" + "strconv" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +const ( + apiURL = "https://api.bithumb.com" + + noError = "0000" + + // Public API + requestsPerSecondPublicAPI = 20 + + publicTicker = "/public/ticker/" + publicOrderBook = "/public/orderbook/" + publicRecentTransaction = "/public/recent_transactions/" + + // Private API + requestsPerSecondPrivateAPI = 10 + + privateAccInfo = "/info/account" + privateAccBalance = "/info/balance" + privateWalletAdd = "/info/wallet_address" + privateTicker = "/info/ticker" + privateOrders = "/info/orders" + privateUserTrans = "/info/user_transactions" + privatePlaceTrade = "/trade/place" + privateOrderDetail = "/info/order_detail" + privateCancelTrade = "/trade/cancel" + privateBTCWithdraw = "/trade/btc_withdrawal" + privateKRWDeposit = "/trade/krw_deposit" + privateKRWWithdraw = "/trade/krw_withdrawal" + privateMarketBuy = "/trade/market_buy" + privateMarketSell = "/trade/market_sell" +) + +// Bithumb is the overarching type across the Bithumb package +type Bithumb struct { + exchange.Base +} + +// SetDefaults sets the basic defaults for Bithumb +func (b *Bithumb) SetDefaults() { + b.Name = "Bithumb" + b.Enabled = false + b.Verbose = false + b.Websocket = false + b.RESTPollingDelay = 10 + b.RequestCurrencyPairFormat.Delimiter = "" + b.RequestCurrencyPairFormat.Uppercase = true + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} +} + +// Setup takes in the supplied exchange configuration details and sets params +func (b *Bithumb) Setup(exch config.ExchangeConfig) { + if !exch.Enabled { + b.SetEnabled(false) + } else { + b.Enabled = true + b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) + b.RESTPollingDelay = exch.RESTPollingDelay + b.Verbose = exch.Verbose + b.Websocket = exch.Websocket + b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") + b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") + b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } + } +} + +// GetTicker returns ticker information +// +// symbol e.g. "btc" +func (b *Bithumb) GetTicker(symbol string) (Ticker, error) { + response := Ticker{} + path := fmt.Sprintf("%s%s%s", apiURL, publicTicker, common.StringToUpper(symbol)) + + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) +} + +// GetOrderBook returns current orderbook +// +// symbol e.g. "btc" +func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { + response := Orderbook{} + path := fmt.Sprintf("%s%s%s", apiURL, publicOrderBook, common.StringToUpper(symbol)) + + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) +} + +// GetRecentTransactions returns recent transactions +// +// symbol e.g. "btc" +func (b *Bithumb) GetRecentTransactions(symbol string) (RecentTransactions, error) { + response := RecentTransactions{} + path := fmt.Sprintf("%s%s%s", apiURL, publicRecentTransaction, common.StringToUpper(symbol)) + + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) +} + +// GetAccountInfo returns account information +func (b *Bithumb) GetAccountInfo() (Account, error) { + response := Account{} + + err := b.SendAuthenticatedHTTPRequest(privateAccInfo, nil, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// GetAccountBalance returns customer wallet information +func (b *Bithumb) GetAccountBalance() (Balance, error) { + response := Balance{} + + err := b.SendAuthenticatedHTTPRequest(privateAccBalance, nil, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// GetWalletAddress returns customer wallet address +// +// currency e.g. btc, ltc or "", will default to btc without currency specified +func (b *Bithumb) GetWalletAddress(currency string) (WalletAddressRes, error) { + response := WalletAddressRes{} + params := url.Values{} + params.Set("currency", common.StringToUpper(currency)) + + err := b.SendAuthenticatedHTTPRequest(privateWalletAdd, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// GetLastTransaction returns customer last transaction +func (b *Bithumb) GetLastTransaction() (LastTransactionTicker, error) { + response := LastTransactionTicker{} + + err := b.SendAuthenticatedHTTPRequest(privateTicker, nil, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// GetOrders returns order list +// +// orderID: order number registered for purchase/sales +// transactionType: transaction type(bid : purchase, ask : sell) +// count: Value : 1 ~1000 (default : 100) +// after: YYYY-MM-DD hh:mm:ss's UNIX Timestamp +// (2014-11-28 16:40:01 = 1417160401000) +func (b *Bithumb) GetOrders(orderID, transactionType, count, after, currency string) (Orders, error) { + response := Orders{} + + params := url.Values{} + params.Set("order_id", orderID) + params.Set("type", transactionType) + params.Set("count", count) + params.Set("after", after) + params.Set("currency", common.StringToUpper(currency)) + + err := b.SendAuthenticatedHTTPRequest(privateOrders, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// GetUserTransactions returns customer transactions +func (b *Bithumb) GetUserTransactions() (UserTransactions, error) { + response := UserTransactions{} + + err := b.SendAuthenticatedHTTPRequest(privateUserTrans, nil, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// PlaceTrade executes a trade order +// +// orderCurrency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS +// (default value: BTC) +// transactionType: Transaction type(bid : purchase, ask : sales) +// units: Order quantity +// price: Transaction amount per currency +func (b *Bithumb) PlaceTrade(orderCurrency, transactionType string, units float64, price int64) (OrderPlace, error) { + response := OrderPlace{} + + params := url.Values{} + params.Set("order_currency", common.StringToUpper(orderCurrency)) + params.Set("Payment_currency", "KRW") + params.Set("type", common.StringToUpper(transactionType)) + params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) + params.Set("price", strconv.FormatInt(price, 10)) + + err := b.SendAuthenticatedHTTPRequest(privatePlaceTrade, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// GetOrderDetails returns specific order details +// +// orderID: Order number registered for purchase/sales +// transactionType: Transaction type(bid : purchase, ask : sales) +// currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS +// (default value: BTC) +func (b *Bithumb) GetOrderDetails(orderID, transactionType, currency string) (OrderDetails, error) { + response := OrderDetails{} + + params := url.Values{} + params.Set("order_id", common.StringToUpper(orderID)) + params.Set("type", common.StringToUpper(transactionType)) + params.Set("currency", common.StringToUpper(currency)) + + err := b.SendAuthenticatedHTTPRequest(privateOrderDetail, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// CancelTrade cancels a customer purchase/sales transaction +// transactionType: Transaction type(bid : purchase, ask : sales) +// orderID: Order number registered for purchase/sales +// currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS +// (default value: BTC) +func (b *Bithumb) CancelTrade(transactionType, orderID, currency string) (ActionStatus, error) { + response := ActionStatus{} + + params := url.Values{} + params.Set("order_id", common.StringToUpper(orderID)) + params.Set("type", common.StringToUpper(transactionType)) + params.Set("currency", common.StringToUpper(currency)) + + err := b.SendAuthenticatedHTTPRequest(privateCancelTrade, nil, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// WithdrawCrypto withdraws a customer currency to an address +// +// address: Currency withdrawing address +// destination: Currency withdrawal Destination Tag (when withdraw XRP) OR +// Currency withdrawal Payment Id (when withdraw XMR) +// currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM +// (default value: BTC) +// units: Quantity to withdraw currency +func (b *Bithumb) WithdrawCrypto(address, destination, currency string, units float64) (ActionStatus, error) { + response := ActionStatus{} + + params := url.Values{} + params.Set("address", address) + params.Set("destination", destination) + params.Set("currency", common.StringToUpper(currency)) + params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) + + err := b.SendAuthenticatedHTTPRequest(privateBTCWithdraw, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// RequestKRWDepositDetails returns Bithumb banking details for deposit +// information +func (b *Bithumb) RequestKRWDepositDetails() (KRWDeposit, error) { + response := KRWDeposit{} + + err := b.SendAuthenticatedHTTPRequest(privateKRWDeposit, nil, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// RequestKRWWithdraw allows a customer KRW withdrawal request +// +// bank: Bankcode with bank name e.g. (bankcode)_(bankname) +// account: Withdrawing bank account number +// price: Withdrawing amount +func (b *Bithumb) RequestKRWWithdraw(bank, account string, price int64) (ActionStatus, error) { + response := ActionStatus{} + + params := url.Values{} + params.Set("bank", bank) + params.Set("account", account) + params.Set("price", strconv.FormatInt(price, 10)) + + err := b.SendAuthenticatedHTTPRequest(privateKRWWithdraw, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// MarketBuyOrder initiates a buy order through available order books +// +// currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS +// (default value: BTC) +// units: Order quantity +func (b *Bithumb) MarketBuyOrder(currency string, units float64) (MarketBuy, error) { + response := MarketBuy{} + + params := url.Values{} + params.Set("currency", common.StringToUpper(currency)) + params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) + + err := b.SendAuthenticatedHTTPRequest(privateMarketBuy, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// MarketSellOrder initiates a sell order through available order books +// +// currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS +// (default value: BTC) +// units: Order quantity +func (b *Bithumb) MarketSellOrder(currency string, units float64) (MarketSell, error) { + response := MarketSell{} + + params := url.Values{} + params.Set("currency", common.StringToUpper(currency)) + params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) + + err := b.SendAuthenticatedHTTPRequest(privateMarketSell, params, &response) + if err != nil { + return response, err + } + + if response.Status != noError { + return response, errors.New(response.Message) + } + return response, nil +} + +// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to bithumb +func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, result interface{}) error { + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if params == nil { + params = url.Values{} + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().UnixNano() / int64(time.Millisecond)) + } else { + b.Nonce.Inc() + } + + params.Set("endpoint", path) + payload := params.Encode() + hmacPayload := path + string(0) + payload + string(0) + b.Nonce.String() + hmac := common.GetHMAC(common.HashSHA512, []byte(hmacPayload), []byte(b.APISecret)) + hmacStr := common.HexEncodeToString(hmac) + + headers := make(map[string]string) + headers["Api-Key"] = b.APIKey + headers["Api-Sign"] = common.Base64Encode([]byte(hmacStr)) + headers["Api-Nonce"] = b.Nonce.String() + headers["Content-Type"] = "application/x-www-form-urlencoded" + + resp, err := common.SendHTTPRequest( + "POST", apiURL+path, headers, bytes.NewBufferString(payload), + ) + if err != nil { + return err + } + + if b.Verbose { + log.Printf("Received raw: \n%s\n", resp) + } + + if err = common.JSONDecode([]byte(resp), &result); err != nil { + return errors.New("sendAuthenticatedHTTPRequest: Unable to JSON Unmarshal response." + err.Error()) + } + return nil +} diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go new file mode 100644 index 00000000000..d10435eea52 --- /dev/null +++ b/exchanges/bithumb/bithumb_test.go @@ -0,0 +1,202 @@ +package bithumb + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +// Please supply your own keys here for due diligence testing +const ( + testAPIKey = "" + testAPISecret = "" +) + +var b Bithumb + +func TestSetDefaults(t *testing.T) { + b.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + bitConfig, err := cfg.GetExchangeConfig("Bithumb") + if err != nil { + t.Error("Test Failed - Bithumb Setup() init error") + } + + bitConfig.AuthenticatedAPISupport = true + bitConfig.APIKey = testAPIKey + bitConfig.APISecret = testAPISecret + + b.Setup(bitConfig) +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := b.GetTicker("btc") + if err != nil { + t.Error("test failed - Bithumb GetTicker() error", err) + } +} + +func TestGetOrderBook(t *testing.T) { + t.Parallel() + _, err := b.GetOrderBook("btc") + if err != nil { + t.Error("test failed - Bithumb GetOrderBook() error", err) + } +} + +func TestGetRecentTransactions(t *testing.T) { + t.Parallel() + _, err := b.GetRecentTransactions("btc") + if err != nil { + t.Error("test failed - Bithumb GetRecentTransactions() error", err) + } +} + +func TestGetAccountInfo(t *testing.T) { + t.Parallel() + _, err := b.GetAccountInfo() + if err == nil { + t.Error("test failed - Bithumb GetAccountInfo() error", err) + } +} + +func TestGetAccountBalance(t *testing.T) { + t.Parallel() + _, err := b.GetAccountBalance() + if err == nil { + t.Error("test failed - Bithumb GetAccountBalance() error", err) + } +} + +func TestGetWalletAddress(t *testing.T) { + t.Parallel() + _, err := b.GetWalletAddress("") + if err == nil { + t.Error("test failed - Bithumb GetWalletAddress() error", err) + } +} + +func TestGetLastTransaction(t *testing.T) { + t.Parallel() + _, err := b.GetLastTransaction() + if err == nil { + t.Error("test failed - Bithumb GetLastTransaction() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := b.GetOrders("1337", "bid", "100", "", "BTC") + if err == nil { + t.Error("test failed - Bithumb GetOrders() error", err) + } +} + +func TestGetUserTransactions(t *testing.T) { + t.Parallel() + _, err := b.GetUserTransactions() + if err == nil { + t.Error("test failed - Bithumb GetUserTransactions() error", err) + } +} + +func TestPlaceTrade(t *testing.T) { + t.Parallel() + _, err := b.PlaceTrade("btc", "bid", 0, 0) + if err == nil { + t.Error("test failed - Bithumb PlaceTrade() error", err) + } +} + +func TestGetOrderDetails(t *testing.T) { + t.Parallel() + _, err := b.GetOrderDetails("1337", "bid", "btc") + if err == nil { + t.Error("test failed - Bithumb GetOrderDetails() error", err) + } +} + +func TestCancelTrade(t *testing.T) { + t.Parallel() + _, err := b.CancelTrade("", "", "") + if err == nil { + t.Error("test failed - Bithumb CancelTrade() error", err) + } +} + +func TestWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := b.WithdrawCrypto("LQxiDhKU7idKiWQhx4ALKYkBx8xKEQVxJR", "", "ltc", 0) + if err == nil { + t.Error("test failed - Bithumb WithdrawCrypto() error", err) + } +} + +func TestRequestKRWDepositDetails(t *testing.T) { + t.Parallel() + _, err := b.RequestKRWDepositDetails() + if err == nil { + t.Error("test failed - Bithumb RequestKRWDepositDetails() error", err) + } +} + +func TestRequestKRWWithdraw(t *testing.T) { + t.Parallel() + _, err := b.RequestKRWWithdraw("102_bank", "1337", 1000) + if err == nil { + t.Error("test failed - Bithumb RequestKRWWithdraw() error", err) + } +} + +func TestMarketBuyOrder(t *testing.T) { + t.Parallel() + _, err := b.MarketBuyOrder("btc", 0) + if err == nil { + t.Error("test failed - Bithumb MarketBuyOrder() error", err) + } +} + +func TestMarketSellOrder(t *testing.T) { + t.Parallel() + _, err := b.MarketSellOrder("btc", 0) + if err == nil { + t.Error("test failed - Bithumb MarketSellOrder() error", err) + } +} + +func TestRun(t *testing.T) { + t.Parallel() + b.Run() +} + +func TestUpdateTicker(t *testing.T) { + t.Parallel() + pair := b.GetEnabledCurrencies()[0] + _, err := b.UpdateTicker(pair, b.AssetTypes[0]) + if err != nil { + t.Error("test failed - Bithumb UpdateTicker() error", err) + } +} + +func TestGetTickerPrice(t *testing.T) { + t.Parallel() + pair := b.GetEnabledCurrencies()[0] + _, err := b.GetTickerPrice(pair, b.AssetTypes[0]) + if err != nil { + t.Error("test failed - Bithumb GetTickerPrice() error", err) + } +} + +func TestGetOrderbookEx(t *testing.T) { + t.Parallel() + pair := b.GetEnabledCurrencies()[0] + _, err := b.GetOrderbookEx(pair, b.AssetTypes[0]) + if err != nil { + t.Error("test failed - Bithumb GetOrderbookEx() error", err) + } +} diff --git a/exchanges/bithumb/bithumb_types.go b/exchanges/bithumb/bithumb_types.go new file mode 100644 index 00000000000..29a30388988 --- /dev/null +++ b/exchanges/bithumb/bithumb_types.go @@ -0,0 +1,217 @@ +package bithumb + +// Ticker holds the standard ticker information +type Ticker struct { + Status string `json:"status"` + Data struct { + OpeningPrice float64 `json:"opening_price,string"` + ClosingPrice float64 `json:"closing_price,string"` + MinPrice float64 `json:"min_price,string"` + MaxPrice float64 `json:"max_price,string"` + AveragePrice float64 `json:"average_price,string"` + UnitsTraded float64 `json:"units_traded,string"` + Volume1Day float64 `json:"volume_1day,string"` + Volume7Day float64 `json:"volume_7day,string"` + BuyPrice float64 `json:"buy_price,string"` + SellPrice float64 `json:"sell_price,string"` + Date int64 `json:"date,string"` + } `json:"data"` + Message string `json:"message"` +} + +// Orderbook holds full range of order book information +type Orderbook struct { + Status string `json:"status"` + Data struct { + Timestamp int64 `json:"timestamp,string"` + OrderCurrency string `json:"order_currency"` + PaymentCurrency string `json:"payment_currency"` + Bids []struct { + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + } `json:"bids"` + Asks []struct { + Quantity float64 `json:"quantity,string"` + Price float64 `json:"price,string"` + } `json:"asks"` + } `json:"data"` + Message string `json:"message"` +} + +// RecentTransactions holds history of completed transaction data +type RecentTransactions struct { + Status string `json:"status"` + Data []struct { + TransactionDate string `json:"transaction_date"` + Type string `json:"type"` + UnitsTraded float64 `json:"units_traded,string"` + Price float64 `json:"price,string"` + Total float64 `json:"total,string"` + } `json:"data"` + Message string `json:"message"` +} + +// Account holds account details +type Account struct { + Status string `json:"status"` + Data struct { + Created int64 `json:"created,string"` + AccountID string `json:"account_id"` + TradeFee float64 `json:"trade_fee,string"` + Balance float64 `json:"balance,string"` + } `json:"data"` + Message string `json:"message"` +} + +// Balance holds balance details +type Balance struct { + Status string `json:"status"` + Data struct { + TotalBTC float64 `json:"total_btc,string"` + TotalKRW float64 `json:"total_krw"` + InUseBTC float64 `json:"in_use_btc,string"` + InUseKRW float64 `json:"in_use_krw"` + AvailableBTC float64 `json:"available_btc,string"` + AvailableKRW float64 `json:"available_krw"` + MisuKRW float64 `json:"misu_krw"` + MisuBTC float64 `json:"misu_btc,string"` + XcoinLast float64 `json:"xcoin_last,string"` + } `json:"data"` + Message string `json:"message"` +} + +// WalletAddressRes contains wallet address information +type WalletAddressRes struct { + Status string `json:"status"` + Data struct { + WalletAddress string `json:"wallet_address"` + Currency string `json:"currency"` + } `json:"data"` + Message string `json:"message"` +} + +// LastTransactionTicker holds customer last transaction information +type LastTransactionTicker struct { + Status string `json:"status"` + Data struct { + OpeningPrice float64 `json:"opening_price,string"` + ClosingPrice float64 `json:"closing_price,string"` + MinPrice float64 `json:"min_price,string"` + MaxPrice float64 `json:"max_price,string"` + AveragePrice float64 `json:"average_price,string"` + UnitsTraded float64 `json:"units_traded,string"` + Volume1Day float64 `json:"volume_1day,string"` + Volume7Day float64 `json:"volume_7day,string"` + BuyPrice int64 `json:"buy_price,string"` + SellPrice int64 `json:"sell_price,string"` + Date int64 `json:"date,string"` + } `json:"data"` + Message string `json:"message"` +} + +// Orders contains information about your current orders +type Orders struct { + Status string `json:"status"` + Data []struct { + OrderID string `json:"order_id"` + OrderCurrency string `json:"order_currency"` + OrderDate int64 `json:"order_date"` + PaymentCurrency string `json:"payment_currency"` + Type string `json:"type"` + Status string `json:"status"` + Units float64 `json:"units,string"` + UnitsRemaining float64 `json:"units_remaining,string"` + Price float64 `json:"price,string"` + Fee float64 `json:"fee,string"` + Total float64 `json:"total,string"` + DateCompleted int64 `json:"date_completed"` + } `json:"data"` + Message string `json:"message"` +} + +// UserTransactions holds users full transaction list +type UserTransactions struct { + Status string `json:"status"` + Data []struct { + Search string `json:"search"` + TransferDate int64 `json:"transfer_date"` + Units string `json:"units"` + Price float64 `json:"price,string"` + BTC1KRW float64 `json:"btc1krw,string"` + Fee string `json:"fee"` + BTCRemain float64 `json:"btc_remain,string"` + KRWRemain float64 `json:"krw_remain,string"` + } `json:"data"` + Message string `json:"message"` +} + +// OrderPlace contains order information +type OrderPlace struct { + Status string `json:"status"` + Data []struct { + ContID string `json:"cont_id"` + Units float64 `json:"units,string"` + Price float64 `json:"price,string"` + Total float64 `json:"total,string"` + Fee float64 `json:"fee,string"` + } `json:"data"` + Message string `json:"message"` +} + +// OrderDetails contains specific order information +type OrderDetails struct { + Status string `json:"status"` + Data []struct { + TransactionDate int64 `json:"transaction_date,string"` + Type string `json:"type"` + OrderCurrency string `json:"order_currency"` + PaymentCurrency string `json:"payment_currency"` + UnitsTraded float64 `json:"units_traded,string"` + Price float64 `json:"price,string"` + Total float64 `json:"total,string"` + } `json:"data"` + Message string `json:"message"` +} + +// ActionStatus holds the return status +type ActionStatus struct { + Status string `json:"status"` + Message string `json:"message"` +} + +// KRWDeposit resp type for a KRW deposit +type KRWDeposit struct { + Status string `json:"status"` + Account string `json:"account"` + Bank string `json:"bank"` + BankUser string `json:"BankUser"` + Message string `json:"message"` +} + +// MarketBuy holds market buy order information +type MarketBuy struct { + Status string `json:"status"` + OrderID string `json:"order_id"` + Data []struct { + ContID string `json:"cont_id"` + Units float64 `json:"units,string"` + Price float64 `json:"price,string"` + Total float64 `json:"total,string"` + Fee float64 `json:"fee,string"` + } `json:"data"` + Message string `json:"message"` +} + +// MarketSell holds market buy order information +type MarketSell struct { + Status string `json:"status"` + OrderID string `json:"order_id"` + Data []struct { + ContID string `json:"cont_id"` + Units float64 `json:"units,string"` + Price float64 `json:"price,string"` + Total float64 `json:"total,string"` + Fee float64 `json:"fee,string"` + } `json:"data"` + Message string `json:"message"` +} diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go new file mode 100644 index 00000000000..31572dc1fb3 --- /dev/null +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -0,0 +1,94 @@ +package bithumb + +import ( + "errors" + "log" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// Start starts the OKEX go routine +func (b *Bithumb) Start() { + go b.Run() +} + +// Run implements the OKEX wrapper +func (b *Bithumb) Run() { + if b.Verbose { + log.Printf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket), b.WebsocketURL) + log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) + log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + } +} + +// UpdateTicker updates and returns the ticker for a currency pair +func (b *Bithumb) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price + item := p.GetFirstCurrency().String() + tick, err := b.GetTicker(item) + if err != nil { + return tickerPrice, err + } + + tickerPrice.Pair = p + tickerPrice.Ask = tick.Data.SellPrice + tickerPrice.Bid = tick.Data.BuyPrice + tickerPrice.Low = tick.Data.MinPrice + tickerPrice.Last = tick.Data.ClosingPrice + tickerPrice.Volume = tick.Data.Volume1Day + tickerPrice.High = tick.Data.MaxPrice + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + + return ticker.GetTicker(b.Name, p, assetType) +} + +// GetTickerPrice returns the ticker for a currency pair +func (b *Bithumb) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) + if err != nil { + return b.UpdateTicker(p, assetType) + } + return tickerNew, nil +} + +// GetOrderbookEx returns orderbook base on the currency pair +func (b *Bithumb) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), currency, assetType) + if err != nil { + return b.UpdateOrderbook(currency, assetType) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *Bithumb) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base + currency := p.GetFirstCurrency().String() + + orderbookNew, err := b.GetOrderBook(currency) + if err != nil { + return orderBook, err + } + + for _, bids := range orderbookNew.Data.Bids { + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: bids.Quantity, Price: bids.Price}) + } + + for _, asks := range orderbookNew.Data.Asks { + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: asks.Quantity, Price: asks.Price}) + } + + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) +} + +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// Bithumb exchange +func (b *Bithumb) GetExchangeAccountInfo() (exchange.AccountInfo, error) { + var response exchange.AccountInfo + return response, errors.New("not implemented") +} diff --git a/testdata/configtest.json b/testdata/configtest.json index 835d7ef8379..0ad634943ad 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -101,6 +101,28 @@ "Uppercase": true } }, + { + "Name": "Bithumb", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "UseSandbox": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "ClientID": "ClientID", + "AvailablePairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "EnabledPairs": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "BaseCurrencies": "KRW", + "AssetTypes": "SPOT", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } + }, { "Name": "Bitstamp", "Enabled": true,