package btcc import ( "errors" "fmt" "log" "net/url" "strconv" "strings" "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( btccAPIUrl = "https://spotusd-data.btcc.com" btccAPIAuthenticatedMethod = "api_trade_v1.php" btccAPIVersion = "2.0.1.3" btccOrderBuy = "buyOrder2" btccOrderSell = "sellOrder2" btccOrderCancel = "cancelOrder" btccIcebergBuy = "buyIcebergOrder" btccIcebergSell = "sellIcebergOrder" btccIcebergOrder = "getIcebergOrder" btccIcebergOrders = "getIcebergOrders" btccIcebergCancel = "cancelIcebergOrder" btccAccountInfo = "getAccountInfo" btccDeposits = "getDeposits" btccMarketdepth = "getMarketDepth2" btccOrder = "getOrder" btccOrders = "getOrders" btccTransactions = "getTransactions" btccWithdrawal = "getWithdrawal" btccWithdrawals = "getWithdrawals" btccWithdrawalRequest = "requestWithdrawal" btccStoporderBuy = "buyStopOrder" btccStoporderSell = "sellStopOrder" btccStoporderCancel = "cancelStopOrder" btccStoporder = "getStopOrder" btccStoporders = "getStopOrders" btccAuthRate = 0 btccUnauthRate = 0 ) // BTCC is the main overaching type across the BTCC package type BTCC struct { exchange.Base } // SetDefaults sets default values for the exchange func (b *BTCC) SetDefaults() { b.Name = "BTCC" b.Enabled = false b.Fee = 0 b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 b.RequestCurrencyPairFormat.Delimiter = "" b.RequestCurrencyPairFormat.Uppercase = false b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true b.AssetTypes = []string{ticker.Spot} b.SupportsAutoPairUpdating = true b.SupportsRESTTickerBatching = false b.Requester = request.New(b.Name, request.NewRateLimit(time.Second, btccAuthRate), request.NewRateLimit(time.Second, btccUnauthRate), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) b.APIUrlDefault = btccAPIUrl b.APIUrl = b.APIUrlDefault } // Setup is run on startup to setup exchange with config values func (b *BTCC) 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 = 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) } } } // GetFee returns the fees associated with transactions func (b *BTCC) GetFee() float64 { return b.Fee } // GetTicker returns ticker information // currencyPair - Example "btccny", "ltccny" or "ltcbtc" func (b *BTCC) GetTicker(currencyPair string) (Ticker, error) { resp := Response{} path := fmt.Sprintf("%s/data/pro/ticker?symbol=%s", b.APIUrl, currencyPair) return resp.Ticker, b.SendHTTPRequest(path, &resp) } // GetTradeHistory returns trade history data // currencyPair - Example "btccny", "ltccny" or "ltcbtc" // limit - limits the returned trades example "10" // sinceTid - returns trade records starting from id supplied example "5000" // time - returns trade records starting from unix time 1406794449 func (b *BTCC) GetTradeHistory(currencyPair string, limit, sinceTid int64, time time.Time) ([]Trade, error) { trades := []Trade{} path := fmt.Sprintf("%s/data/pro/historydata?symbol=%s", b.APIUrl, currencyPair) v := url.Values{} if limit > 0 { v.Set("limit", strconv.FormatInt(limit, 10)) } if sinceTid > 0 { v.Set("since", strconv.FormatInt(sinceTid, 10)) } if !time.IsZero() { v.Set("sincetype", strconv.FormatInt(time.Unix(), 10)) } path = common.EncodeURLValues(path, v) return trades, b.SendHTTPRequest(path, &trades) } // GetOrderBook returns current symbol order book // currencyPair - Example "btccny", "ltccny" or "ltcbtc" // limit - limits the returned trades example "10" if 0 will return full // orderbook func (b *BTCC) GetOrderBook(currencyPair string, limit int) (Orderbook, error) { result := Orderbook{} path := fmt.Sprintf("%s/data/pro/orderbook?symbol=%s&limit=%d", b.APIUrl, currencyPair, limit) if limit == 0 { path = fmt.Sprintf("%s/data/pro/orderbook?symbol=%s", b.APIUrl, currencyPair) } return result, b.SendHTTPRequest(path, &result) } // GetAccountInfo returns account information func (b *BTCC) GetAccountInfo(infoType string) error { params := make([]interface{}, 0) if len(infoType) > 0 { params = append(params, infoType) } return b.SendAuthenticatedHTTPRequest(btccAccountInfo, params) } // PlaceOrder places a new order func (b *BTCC) PlaceOrder(buyOrder bool, price, amount float64, symbol string) { params := make([]interface{}, 0) params = append(params, strconv.FormatFloat(price, 'f', -1, 64)) params = append(params, strconv.FormatFloat(amount, 'f', -1, 64)) if len(symbol) > 0 { params = append(params, symbol) } req := btccOrderBuy if !buyOrder { req = btccOrderSell } err := b.SendAuthenticatedHTTPRequest(req, params) if err != nil { log.Println(err) } } // CancelOrder cancels an order func (b *BTCC) CancelOrder(orderID int64, symbol string) { params := make([]interface{}, 0) params = append(params, orderID) if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccOrderCancel, params) if err != nil { log.Println(err) } } // GetDeposits returns deposit information func (b *BTCC) GetDeposits(currency string, pending bool) { params := make([]interface{}, 0) params = append(params, currency) if pending { params = append(params, pending) } err := b.SendAuthenticatedHTTPRequest(btccDeposits, params) if err != nil { log.Println(err) } } // GetMarketDepth returns market depth at limit func (b *BTCC) GetMarketDepth(symbol string, limit int64) { params := make([]interface{}, 0) if limit > 0 { params = append(params, limit) } if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccMarketdepth, params) if err != nil { log.Println(err) } } // GetOrder returns information about a specific order func (b *BTCC) GetOrder(orderID int64, symbol string, detailed bool) { params := make([]interface{}, 0) params = append(params, orderID) if len(symbol) > 0 { params = append(params, symbol) } if detailed { params = append(params, detailed) } err := b.SendAuthenticatedHTTPRequest(btccOrder, params) if err != nil { log.Println(err) } } // GetOrders returns information of a range of orders func (b *BTCC) GetOrders(openonly bool, symbol string, limit, offset, since int64, detailed bool) { params := make([]interface{}, 0) if openonly { params = append(params, openonly) } if len(symbol) > 0 { params = append(params, symbol) } if limit > 0 { params = append(params, limit) } if offset > 0 { params = append(params, offset) } if since > 0 { params = append(params, since) } if detailed { params = append(params, detailed) } err := b.SendAuthenticatedHTTPRequest(btccOrders, params) if err != nil { log.Println(err) } } // GetTransactions returns transaction lists func (b *BTCC) GetTransactions(transType string, limit, offset, since int64, sinceType string) { params := make([]interface{}, 0) if len(transType) > 0 { params = append(params, transType) } if limit > 0 { params = append(params, limit) } if offset > 0 { params = append(params, offset) } if since > 0 { params = append(params, since) } if len(sinceType) > 0 { params = append(params, sinceType) } err := b.SendAuthenticatedHTTPRequest(btccTransactions, params) if err != nil { log.Println(err) } } // GetWithdrawal returns information about a withdrawal process func (b *BTCC) GetWithdrawal(withdrawalID int64, currency string) { params := make([]interface{}, 0) params = append(params, withdrawalID) if len(currency) > 0 { params = append(params, currency) } err := b.SendAuthenticatedHTTPRequest(btccWithdrawal, params) if err != nil { log.Println(err) } } // GetWithdrawals gets information about all withdrawals func (b *BTCC) GetWithdrawals(currency string, pending bool) { params := make([]interface{}, 0) params = append(params, currency) if pending { params = append(params, pending) } err := b.SendAuthenticatedHTTPRequest(btccWithdrawals, params) if err != nil { log.Println(err) } } // RequestWithdrawal requests a new withdrawal func (b *BTCC) RequestWithdrawal(currency string, amount float64) { params := make([]interface{}, 0) params = append(params, currency) params = append(params, amount) err := b.SendAuthenticatedHTTPRequest(btccWithdrawalRequest, params) if err != nil { log.Println(err) } } // IcebergOrder intiates a large order but at intervals to preserve orderbook // integrity func (b *BTCC) IcebergOrder(buyOrder bool, price, amount, discAmount, variance float64, symbol string) { params := make([]interface{}, 0) params = append(params, strconv.FormatFloat(price, 'f', -1, 64)) params = append(params, strconv.FormatFloat(amount, 'f', -1, 64)) params = append(params, strconv.FormatFloat(discAmount, 'f', -1, 64)) params = append(params, strconv.FormatFloat(variance, 'f', -1, 64)) if len(symbol) > 0 { params = append(params, symbol) } req := btccIcebergBuy if !buyOrder { req = btccIcebergSell } err := b.SendAuthenticatedHTTPRequest(req, params) if err != nil { log.Println(err) } } // GetIcebergOrder returns information on your iceberg order func (b *BTCC) GetIcebergOrder(orderID int64, symbol string) { params := make([]interface{}, 0) params = append(params, orderID) if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccIcebergOrder, params) if err != nil { log.Println(err) } } // GetIcebergOrders returns information on all iceberg orders func (b *BTCC) GetIcebergOrders(limit, offset int64, symbol string) { params := make([]interface{}, 0) if limit > 0 { params = append(params, limit) } if offset > 0 { params = append(params, offset) } if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccIcebergOrders, params) if err != nil { log.Println(err) } } // CancelIcebergOrder cancels iceberg order func (b *BTCC) CancelIcebergOrder(orderID int64, symbol string) { params := make([]interface{}, 0) params = append(params, orderID) if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccIcebergCancel, params) if err != nil { log.Println(err) } } // PlaceStopOrder inserts a stop loss order func (b *BTCC) PlaceStopOrder(buyOder bool, stopPrice, price, amount, trailingAmt, trailingPct float64, symbol string) { params := make([]interface{}, 0) if stopPrice > 0 { params = append(params, stopPrice) } params = append(params, strconv.FormatFloat(price, 'f', -1, 64)) params = append(params, strconv.FormatFloat(amount, 'f', -1, 64)) if trailingAmt > 0 { params = append(params, strconv.FormatFloat(trailingAmt, 'f', -1, 64)) } if trailingPct > 0 { params = append(params, strconv.FormatFloat(trailingPct, 'f', -1, 64)) } if len(symbol) > 0 { params = append(params, symbol) } req := btccStoporderBuy if !buyOder { req = btccStoporderSell } err := b.SendAuthenticatedHTTPRequest(req, params) if err != nil { log.Println(err) } } // GetStopOrder returns a stop order func (b *BTCC) GetStopOrder(orderID int64, symbol string) { params := make([]interface{}, 0) params = append(params, orderID) if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccStoporder, params) if err != nil { log.Println(err) } } // GetStopOrders returns all stop orders func (b *BTCC) GetStopOrders(status, orderType string, stopPrice float64, limit, offset int64, symbol string) { params := make([]interface{}, 0) if len(status) > 0 { params = append(params, status) } if len(orderType) > 0 { params = append(params, orderType) } if stopPrice > 0 { params = append(params, stopPrice) } if limit > 0 { params = append(params, limit) } if offset > 0 { params = append(params, limit) } if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccStoporders, params) if err != nil { log.Println(err) } } // CancelStopOrder cancels a stop order func (b *BTCC) CancelStopOrder(orderID int64, symbol string) { params := make([]interface{}, 0) params = append(params, orderID) if len(symbol) > 0 { params = append(params, symbol) } err := b.SendAuthenticatedHTTPRequest(btccStoporderCancel, params) if err != nil { log.Println(err) } } // SendHTTPRequest sends an unauthenticated HTTP request func (b *BTCC) SendHTTPRequest(path string, result interface{}) error { return b.SendPayload("GET", path, nil, nil, result, false, b.Verbose) } // SendAuthenticatedHTTPRequest sends a valid authenticated HTTP request func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{}) (err error) { if !b.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } if b.Nonce.Get() == 0 { b.Nonce.Set(time.Now().UnixNano()) } else { b.Nonce.Inc() } encoded := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=%d&method=%s¶ms=", b.Nonce.String()[0:16], b.APIKey, 1, method) if len(params) == 0 { params = make([]interface{}, 0) } else { items := make([]string, 0) for _, x := range params { xType := fmt.Sprintf("%T", x) switch xType { case "int64", "int": { items = append(items, fmt.Sprintf("%d", x)) } case "string": { items = append(items, fmt.Sprintf("%s", x)) } case "float64": { items = append(items, fmt.Sprintf("%f", x)) } case "bool": { if x == true { items = append(items, "1") } else { items = append(items, "") } } default: { items = append(items, fmt.Sprintf("%v", x)) } } } encoded += common.JoinStrings(items, ",") } if b.Verbose { log.Println(encoded) } hmac := common.GetHMAC(common.HashSHA1, []byte(encoded), []byte(b.APISecret)) postData := make(map[string]interface{}) postData["method"] = method postData["params"] = params postData["id"] = 1 apiURL := fmt.Sprintf("%s/%s", b.APIUrl, btccAPIAuthenticatedMethod) data, err := common.JSONEncode(postData) if err != nil { return errors.New("Unable to JSON Marshal POST data") } if b.Verbose { log.Printf("Sending POST request to %s calling method %s with params %s\n", apiURL, method, data) } headers := make(map[string]string) headers["Content-type"] = "application/json-rpc" headers["Authorization"] = "Basic " + common.Base64Encode([]byte(b.APIKey+":"+common.HexEncodeToString(hmac))) headers["Json-Rpc-Tonce"] = b.Nonce.String() return b.SendPayload("POST", apiURL, headers, strings.NewReader(string(data)), nil, true, b.Verbose) }