Files
gocryptotrader/exchanges/bithumb/bithumb.go
Adrian Gallagher 5e5ca8a887 Linter fixes (#246)
Linter fixes
2019-02-05 16:26:04 +11:00

685 lines
19 KiB
Go

package bithumb
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"reflect"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/symbol"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
apiURL = "https://api.bithumb.com"
noError = "0000"
// Public API
requestsPerSecondPublicAPI = 20
publicTicker = "/public/ticker/"
publicOrderBook = "/public/orderbook/"
publicTransactionHistory = "/public/transaction_history/"
// 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"
bithumbAuthRate = 10
bithumbUnauthRate = 20
)
// 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.RESTPollingDelay = 10
b.APIWithdrawPermissions = exchange.AutoWithdrawCrypto |
exchange.AutoWithdrawFiat
b.RequestCurrencyPairFormat.Delimiter = ""
b.RequestCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Index = "KRW"
b.AssetTypes = []string{ticker.Spot}
b.SupportsAutoPairUpdating = true
b.SupportsRESTTickerBatching = true
b.Requester = request.New(b.Name,
request.NewRateLimit(time.Second, bithumbAuthRate),
request.NewRateLimit(time.Second, bithumbUnauthRate),
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = apiURL
b.APIUrl = b.APIUrlDefault
b.WebsocketInit()
}
// 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.SetHTTPClientTimeout(exch.HTTPTimeout)
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
b.RESTPollingDelay = exch.RESTPollingDelay
b.Verbose = exch.Verbose
b.Websocket.SetEnabled(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)
}
err = b.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
err = b.SetAPIURL(exch)
if err != nil {
log.Fatal(err)
}
err = b.SetClientProxyAddress(exch.ProxyAddress)
if err != nil {
log.Fatal(err)
}
}
}
// GetTradablePairs returns a list of tradable currencies
func (b *Bithumb) GetTradablePairs() ([]string, error) {
result, err := b.GetAllTickers()
if err != nil {
return nil, err
}
var currencies []string
for x := range result {
currencies = append(currencies, x)
}
return currencies, nil
}
// GetTicker returns ticker information
//
// symbol e.g. "btc"
func (b *Bithumb) GetTicker(symbol string) (Ticker, error) {
response := Ticker{}
path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTicker, common.StringToUpper(symbol))
err := b.SendHTTPRequest(path, &response)
if err != nil {
return response, err
}
if response.Status != noError {
return response, errors.New(response.Message)
}
return response, nil
}
// GetAllTickers returns all ticker information
func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) {
type Response struct {
ActionStatus
Data map[string]interface{}
}
response := Response{}
path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTicker, "all")
err := b.SendHTTPRequest(path, &response)
if err != nil {
return nil, err
}
if response.Status != noError {
return nil, errors.New(response.Message)
}
result := make(map[string]Ticker)
for k, v := range response.Data {
if k == "date" {
continue
}
if reflect.TypeOf(v).String() != "map[string]interface {}" {
continue
}
data := v.(map[string]interface{})
var t Ticker
t.AveragePrice, _ = strconv.ParseFloat(data["average_price"].(string), 64)
t.BuyPrice, _ = strconv.ParseFloat(data["buy_price"].(string), 64)
t.ClosingPrice, _ = strconv.ParseFloat(data["closing_price"].(string), 64)
t.MaxPrice, _ = strconv.ParseFloat(data["max_price"].(string), 64)
t.MinPrice, _ = strconv.ParseFloat(data["min_price"].(string), 64)
t.OpeningPrice, _ = strconv.ParseFloat(data["opening_price"].(string), 64)
t.SellPrice, _ = strconv.ParseFloat(data["sell_price"].(string), 64)
t.UnitsTraded, _ = strconv.ParseFloat(data["units_traded"].(string), 64)
t.Volume1Day, _ = strconv.ParseFloat(data["volume_1day"].(string), 64)
t.Volume7Day, _ = strconv.ParseFloat(data["volume_7day"].(string), 64)
result[k] = t
}
return result, nil
}
// GetOrderBook returns current orderbook
//
// symbol e.g. "btc"
func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) {
response := Orderbook{}
path := fmt.Sprintf("%s%s%s", b.APIUrl, publicOrderBook, common.StringToUpper(symbol))
err := b.SendHTTPRequest(path, &response)
if err != nil {
return response, err
}
if response.Status != noError {
return response, errors.New(response.Message)
}
return response, nil
}
// GetTransactionHistory returns recent transactions
//
// symbol e.g. "btc"
func (b *Bithumb) GetTransactionHistory(symbol string) (TransactionHistory, error) {
response := TransactionHistory{}
path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTransactionHistory, common.StringToUpper(symbol))
err := b.SendHTTPRequest(path, &response)
if err != nil {
return response, err
}
if response.Status != noError {
return response, errors.New(response.Message)
}
return response, nil
}
// GetAccountInformation returns account information by singular currency
func (b *Bithumb) GetAccountInformation(currency string) (Account, error) {
response := Account{}
val := url.Values{}
if currency != "" {
val.Set("currency", currency)
}
return response,
b.SendAuthenticatedHTTPRequest(privateAccInfo, val, &response)
}
// GetAccountBalance returns customer wallet information
func (b *Bithumb) GetAccountBalance(c string) (FullBalance, error) {
var response Balance
var fullBalance = FullBalance{
make(map[string]float64),
make(map[string]float64),
make(map[string]float64),
make(map[string]float64),
make(map[string]float64),
}
vals := url.Values{}
if c != "" {
vals.Set("currency", c)
}
err := b.SendAuthenticatedHTTPRequest(privateAccBalance, vals, &response)
if err != nil {
return fullBalance, err
}
// Added due to increasing of the usuable currencies on exchange, usually
// without notificatation, so we dont need to update structs later on
for tag, datum := range response.Data {
splitTag := common.SplitStrings(tag, "_")
c := splitTag[len(splitTag)-1]
var val float64
if reflect.TypeOf(datum).String() != "float64" {
val, err = strconv.ParseFloat(datum.(string), 64)
if err != nil {
return fullBalance, err
}
} else {
val = datum.(float64)
}
switch splitTag[0] {
case "available":
fullBalance.Available[c] = val
case "in":
fullBalance.InUse[c] = val
case "total":
fullBalance.Total[c] = val
case "misu":
fullBalance.Misu[c] = val
case "xcoin":
fullBalance.Xcoin[c] = val
default:
return fullBalance, fmt.Errorf("GetAccountBalance error tag name %s unhandled",
splitTag)
}
}
return fullBalance, 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.Data.WalletAddress == "" {
return response,
fmt.Errorf("deposit address needs to be created via the Bithumb website before retreival for currency %s",
currency)
}
return response, nil
}
// GetLastTransaction returns customer last transaction
func (b *Bithumb) GetLastTransaction() (LastTransactionTicker, error) {
response := LastTransactionTicker{}
return response,
b.SendAuthenticatedHTTPRequest(privateTicker, nil, &response)
}
// 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{}
if len(orderID) > 0 {
params.Set("order_id", orderID)
}
if len(transactionType) > 0 {
params.Set("type", transactionType)
}
if len(count) > 0 {
params.Set("count", count)
}
if len(after) > 0 {
params.Set("after", after)
}
if len(currency) > 0 {
params.Set("currency", common.StringToUpper(currency))
}
return response,
b.SendAuthenticatedHTTPRequest(privateOrders, params, &response)
}
// GetUserTransactions returns customer transactions
func (b *Bithumb) GetUserTransactions() (UserTransactions, error) {
response := UserTransactions{}
return response,
b.SendAuthenticatedHTTPRequest(privateUserTrans, nil, &response)
}
// 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))
return response,
b.SendAuthenticatedHTTPRequest(privatePlaceTrade, params, &response)
}
// ModifyTrade modifies an order already on the exchange books
func (b *Bithumb) ModifyTrade(orderID, 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))
params.Set("order_id", orderID)
return response,
b.SendAuthenticatedHTTPRequest(privatePlaceTrade, params, &response)
}
// 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", transactionType)
params.Set("currency", common.StringToUpper(currency))
return response,
b.SendAuthenticatedHTTPRequest(privateOrderDetail, params, &response)
}
// 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))
return response,
b.SendAuthenticatedHTTPRequest(privateCancelTrade, nil, &response)
}
// 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)
if len(destination) > 0 {
params.Set("destination", destination)
}
params.Set("currency", common.StringToUpper(currency))
params.Set("units", strconv.FormatFloat(units, 'f', -1, 64))
return response,
b.SendAuthenticatedHTTPRequest(privateBTCWithdraw, params, &response)
}
// RequestKRWDepositDetails returns Bithumb banking details for deposit
// information
func (b *Bithumb) RequestKRWDepositDetails() (KRWDeposit, error) {
response := KRWDeposit{}
return response,
b.SendAuthenticatedHTTPRequest(privateKRWDeposit, nil, &response)
}
// 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))
return response,
b.SendAuthenticatedHTTPRequest(privateKRWWithdraw, params, &response)
}
// 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))
return response,
b.SendAuthenticatedHTTPRequest(privateMarketBuy, params, &response)
}
// 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))
return response,
b.SendAuthenticatedHTTPRequest(privateMarketSell, params, &response)
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (b *Bithumb) SendHTTPRequest(path string, result interface{}) error {
return b.SendPayload(http.MethodGet, path, nil, nil, result, false, b.Verbose)
}
// 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"
var intermediary json.RawMessage
errCapture := struct {
Status string `json:"status"`
Message string `json:"message"`
}{}
err := b.SendPayload(http.MethodPost,
b.APIUrl+path,
headers,
bytes.NewBufferString(payload),
&intermediary,
true,
b.Verbose)
if err != nil {
return err
}
err = common.JSONDecode(intermediary, &errCapture)
if err == nil {
if errCapture.Status != "" && errCapture.Status != "0000" {
return fmt.Errorf("SendAuthenticatedHTTPRequest error Code:%s Message:%s",
errCapture.Status,
errCode[errCapture.Status])
}
}
return common.JSONDecode(intermediary, result)
}
// GetFee returns an estimate of fee based on type of transaction
func (b *Bithumb) GetFee(feeBuilder exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
case exchange.CyptocurrencyDepositFee:
fee = getDepositFee(feeBuilder.FirstCurrency, feeBuilder.Amount)
case exchange.CryptocurrencyWithdrawalFee:
fee = getWithdrawalFee(feeBuilder.FirstCurrency)
case exchange.InternationalBankWithdrawalFee:
fee = getWithdrawalFee(feeBuilder.CurrencyItem)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
// calculateTradingFee returns fee when performing a trade
func calculateTradingFee(purchasePrice float64, amount float64) float64 {
fee := 0.0015
return fee * amount * purchasePrice
}
// getDepositFee returns fee on a currency when depositing small amounts to bithumb
func getDepositFee(currency string, amount float64) float64 {
var fee float64
switch currency {
case symbol.BTC:
if amount <= 0.005 {
fee = 0.001
}
case symbol.LTC:
if amount <= 0.3 {
fee = 0.01
}
case symbol.DASH:
if amount <= 0.04 {
fee = 0.01
}
case symbol.BCH:
if amount <= 0.03 {
fee = 0.001
}
case symbol.ZEC:
if amount <= 0.02 {
fee = 0.001
}
case symbol.BTG:
if amount <= 0.15 {
fee = 0.001
}
}
return fee
}
// getWithdrawalFee returns fee on a currency when withdrawing out of bithumb
func getWithdrawalFee(currency string) float64 {
return WithdrawalFees[currency]
}
var errCode = map[string]string{
"5100": "Bad Request",
"5200": "Not Member",
"5300": "Invalid Apikey",
"5302": "Method Not Allowed",
"5400": "Database Fail",
"5500": "Invalid Parameter",
"5600": "CUSTOM NOTICE (상황별 에러 메시지 출력) usually means transaction not allowed",
"5900": "Unknown Error",
}