mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-31 07:26:44 +00:00
Added support for Binance exchange
This commit is contained in:
497
exchanges/binance/binance.go
Normal file
497
exchanges/binance/binance.go
Normal file
@@ -0,0 +1,497 @@
|
||||
package binance
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// Binance is the overarching type across the Bithumb package
|
||||
type Binance struct {
|
||||
exchange.Base
|
||||
|
||||
// valid string list that a required by the exchange
|
||||
validLimits []string
|
||||
validIntervals []string
|
||||
}
|
||||
|
||||
const (
|
||||
apiURL = "https://api.binance.com"
|
||||
|
||||
// Public endpoints
|
||||
exchangeInfo = "/api/v1/exchangeInfo"
|
||||
orderBookDepth = "/api/v1/depth"
|
||||
recentTrades = "/api/v1/trades"
|
||||
historicalTrades = "/api/v1/historicalTrades"
|
||||
aggregatedTrades = "/api/v1/aggTrades"
|
||||
candleStick = "/api/v1/klines"
|
||||
priceChange = "/api/v1/ticker/24hr"
|
||||
symbolPrice = "/api/v3/ticker/price"
|
||||
bestPrice = "/api/v3/ticker/bookTicker"
|
||||
|
||||
// Authenticated endpoints
|
||||
|
||||
newOrderTest = "/api/v3/order/test"
|
||||
newOrder = "/api/v3/order"
|
||||
queryOrder = "/api/v3/order"
|
||||
)
|
||||
|
||||
// SetDefaults sets the basic defaults for Binance
|
||||
func (b *Binance) SetDefaults() {
|
||||
b.Name = "Binance"
|
||||
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}
|
||||
b.SetValues()
|
||||
}
|
||||
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
func (b *Binance) 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetExchangeValidCurrencyPairs returns the full pair list from the exchange
|
||||
// at the moment do not integrate with config currency pairs automatically
|
||||
func (b *Binance) GetExchangeValidCurrencyPairs() (string, error) {
|
||||
var validCurrencyPairs []string
|
||||
|
||||
info, err := b.GetExchangeInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, symbol := range info.Symbols {
|
||||
validCurrencyPairs = append(validCurrencyPairs, symbol.Symbol)
|
||||
}
|
||||
return common.JoinStrings(validCurrencyPairs, ","), nil
|
||||
}
|
||||
|
||||
// GetExchangeInfo returns exchange information. Check binance_types for more
|
||||
// information
|
||||
func (b *Binance) GetExchangeInfo() (ExchangeInfo, error) {
|
||||
var resp ExchangeInfo
|
||||
path := apiURL + exchangeInfo
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// GetOrderBook returns full orderbook information
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
// limit: returned limit amount
|
||||
func (b *Binance) GetOrderBook(symbol string, limit int64) (OrderBook, error) {
|
||||
orderbook, resp := OrderBook{}, OrderBookData{}
|
||||
|
||||
if err := b.CheckLimit(limit); err != nil {
|
||||
return orderbook, err
|
||||
}
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return orderbook, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, orderBookDepth, params.Encode())
|
||||
|
||||
if err := common.SendHTTPGetRequest(path, true, b.Verbose, &resp); err != nil {
|
||||
return orderbook, err
|
||||
}
|
||||
|
||||
for _, asks := range resp.Asks {
|
||||
var ASK struct {
|
||||
Price float64
|
||||
Quantity float64
|
||||
}
|
||||
for i, ask := range asks.([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
ASK.Price, _ = strconv.ParseFloat(ask.(string), 64)
|
||||
case 1:
|
||||
ASK.Quantity, _ = strconv.ParseFloat(ask.(string), 64)
|
||||
}
|
||||
orderbook.Asks = append(orderbook.Asks, ASK)
|
||||
}
|
||||
}
|
||||
|
||||
for _, bids := range resp.Bids {
|
||||
var BID struct {
|
||||
Price float64
|
||||
Quantity float64
|
||||
}
|
||||
for i, bid := range bids.([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
BID.Price, _ = strconv.ParseFloat(bid.(string), 64)
|
||||
case 1:
|
||||
BID.Quantity, _ = strconv.ParseFloat(bid.(string), 64)
|
||||
}
|
||||
orderbook.Bids = append(orderbook.Bids, BID)
|
||||
}
|
||||
}
|
||||
return orderbook, nil
|
||||
}
|
||||
|
||||
// GetRecentTrades returns recent trade activity
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
// limit: returned limit amount WARNING: MAX 500!
|
||||
func (b *Binance) GetRecentTrades(symbol string, limit int64) ([]RecentTrade, error) {
|
||||
resp := []RecentTrade{}
|
||||
|
||||
if err := b.CheckLimit(limit); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, recentTrades, params.Encode())
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// GetHistoricalTrades returns historical trade activity
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
// limit: returned limit amount WARNING: MAX 500! (NOT REQUIRED)
|
||||
// fromID:
|
||||
func (b *Binance) GetHistoricalTrades(symbol string, limit, fromID int64) ([]HistoricalTrade, error) {
|
||||
resp := []HistoricalTrade{}
|
||||
|
||||
if err := b.CheckLimit(limit); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
params.Set("fromid", strconv.FormatInt(fromID, 10))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, historicalTrades, params.Encode())
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// GetAggregatedTrades returns aggregated trade activity
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
// limit: returned limit amount WARNING: MAX 500!
|
||||
func (b *Binance) GetAggregatedTrades(symbol string, limit int64) ([]AggregatedTrade, error) {
|
||||
resp := []AggregatedTrade{}
|
||||
|
||||
if err := b.CheckLimit(limit); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, aggregatedTrades, params.Encode())
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// GetCandleStickData returns candle stick data
|
||||
//
|
||||
// symbol:
|
||||
// limit:
|
||||
// interval
|
||||
func (b *Binance) GetCandleStickData(symbol, interval string, limit int64) ([]CandleStick, error) {
|
||||
var resp interface{}
|
||||
var kline []CandleStick
|
||||
|
||||
if err := b.CheckLimit(limit); err != nil {
|
||||
return kline, err
|
||||
}
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return kline, err
|
||||
}
|
||||
if err := b.CheckIntervals(interval); err != nil {
|
||||
return kline, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
params.Set("limit", strconv.FormatInt(limit, 10))
|
||||
params.Set("interval", interval)
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, candleStick, params.Encode())
|
||||
|
||||
if err := common.SendHTTPGetRequest(path, true, b.Verbose, &resp); err != nil {
|
||||
return kline, err
|
||||
}
|
||||
|
||||
for _, responseData := range resp.([]interface{}) {
|
||||
var candle CandleStick
|
||||
for i, individualData := range responseData.([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
candle.OpenTime = individualData.(float64)
|
||||
case 1:
|
||||
candle.Open, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 2:
|
||||
candle.High, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 3:
|
||||
candle.Low, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 4:
|
||||
candle.Close, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 5:
|
||||
candle.Volume, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 6:
|
||||
candle.CloseTime = individualData.(float64)
|
||||
case 7:
|
||||
candle.QuoteAssetVolume, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 8:
|
||||
candle.TradeCount = individualData.(float64)
|
||||
case 9:
|
||||
candle.TakerBuyAssetVolume, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
case 10:
|
||||
candle.TakerBuyQuoteAssetVolume, _ = strconv.ParseFloat(individualData.(string), 64)
|
||||
}
|
||||
}
|
||||
kline = append(kline, candle)
|
||||
}
|
||||
return kline, nil
|
||||
}
|
||||
|
||||
// GetPriceChangeStats returns price change statistics for the last 24 hours
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
func (b *Binance) GetPriceChangeStats(symbol string) (PriceChangeStats, error) {
|
||||
resp := PriceChangeStats{}
|
||||
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, priceChange, params.Encode())
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// GetLatestSpotPrice returns latest spot price of symbol
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
func (b *Binance) GetLatestSpotPrice(symbol string) (SymbolPrice, error) {
|
||||
resp := SymbolPrice{}
|
||||
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, symbolPrice, params.Encode())
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// GetBestPrice returns the latest best price for symbol
|
||||
//
|
||||
// symbol: string of currency pair
|
||||
func (b *Binance) GetBestPrice(symbol string) (BestPrice, error) {
|
||||
resp := BestPrice{}
|
||||
|
||||
if err := b.CheckSymbol(symbol); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
|
||||
path := fmt.Sprintf("%s%s?%s", apiURL, bestPrice, params.Encode())
|
||||
|
||||
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
|
||||
}
|
||||
|
||||
// NewOrderTest sends a new order
|
||||
func (b *Binance) NewOrderTest() (interface{}, error) {
|
||||
var resp interface{}
|
||||
|
||||
path := fmt.Sprintf("%s%s", apiURL, newOrderTest)
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", "BTCUSDT")
|
||||
params.Set("side", "BUY")
|
||||
params.Set("type", "MARKET")
|
||||
params.Set("quantity", "0.1")
|
||||
|
||||
return resp, b.SendAuthHTTPRequest("POST", path, params, &resp)
|
||||
}
|
||||
|
||||
// NewOrder sends a new order to Binance
|
||||
func (b *Binance) NewOrder(o NewOrderRequest) (NewOrderResponse, error) {
|
||||
var resp NewOrderResponse
|
||||
|
||||
path := fmt.Sprintf("%s%s", apiURL, newOrderTest)
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", o.Symbol)
|
||||
params.Set("side", o.Side)
|
||||
params.Set("type", o.TradeType)
|
||||
params.Set("timeInForce", o.TimeInForce)
|
||||
params.Set("quantity", strconv.FormatFloat(o.Quantity, 'f', -1, 64))
|
||||
params.Set("price", strconv.FormatFloat(o.Price, 'f', -1, 64))
|
||||
params.Set("newClientOrderID", o.NewClientOrderID)
|
||||
params.Set("stopPrice", strconv.FormatFloat(o.StopPrice, 'f', -1, 64))
|
||||
params.Set("icebergQty", strconv.FormatFloat(o.IcebergQty, 'f', -1, 64))
|
||||
params.Set("newOrderRespType", o.NewOrderRespType)
|
||||
|
||||
if err := b.SendAuthHTTPRequest("POST", path, params, &resp); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if resp.Code != 0 {
|
||||
return resp, errors.New(resp.Msg)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// QueryOrder returns information on a past order
|
||||
func (b *Binance) QueryOrder(symbol, origClientOrderID string, orderID int64) (QueryOrderData, error) {
|
||||
var resp QueryOrderData
|
||||
|
||||
path := fmt.Sprintf("%s%s", apiURL, queryOrder)
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("symbol", common.StringToUpper(symbol))
|
||||
params.Set("origClientOrderId", origClientOrderID)
|
||||
params.Set("orderId", strconv.FormatInt(orderID, 10))
|
||||
|
||||
if err := b.SendAuthHTTPRequest("GET", path, params, &resp); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if resp.Code != 0 {
|
||||
return resp, errors.New(resp.Msg)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SendAuthHTTPRequest something
|
||||
func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, result interface{}) error {
|
||||
if !b.AuthenticatedAPISupport {
|
||||
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name)
|
||||
}
|
||||
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
params.Set("recvWindow", strconv.FormatInt(5000, 10))
|
||||
params.Set("timestamp", strconv.FormatInt(time.Now().Unix()*1000, 10))
|
||||
|
||||
signature := params.Encode()
|
||||
hmacSigned := common.GetHMAC(common.HashSHA256, []byte(signature), []byte(b.APISecret))
|
||||
hmacSignedStr := common.HexEncodeToString(hmacSigned)
|
||||
params.Set("signature", hmacSignedStr)
|
||||
|
||||
if b.Nonce.Get() == 0 {
|
||||
b.Nonce.Set(time.Now().UnixNano() / int64(time.Millisecond))
|
||||
} else {
|
||||
b.Nonce.Inc()
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["X-MBX-APIKEY"] = b.APIKey
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
if b.Verbose {
|
||||
log.Printf("sent path: \n%s\n", path)
|
||||
}
|
||||
|
||||
resp, err := common.SendHTTPRequest(method, path, headers, bytes.NewBufferString(params.Encode()))
|
||||
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
|
||||
}
|
||||
|
||||
// CheckLimit checks value against a variable list
|
||||
func (b *Binance) CheckLimit(limit int64) error {
|
||||
if !common.DataContains(b.validLimits, strconv.FormatInt(limit, 10)) {
|
||||
return errors.New("Incorrect limit values - valid values are 5, 10, 20, 50, 100, 500, 1000")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckSymbol checks value against a variable list
|
||||
func (b *Binance) CheckSymbol(symbol string) error {
|
||||
if !common.DataContains(b.AvailablePairs, symbol) {
|
||||
return errors.New("Incorrect symbol values - please check available pairs in configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckIntervals checks value against a variable list
|
||||
func (b *Binance) CheckIntervals(interval string) error {
|
||||
if !common.DataContains(b.validIntervals, interval) {
|
||||
return errors.New(`Incorrect interval values - valid values are "1m","3m","5m","15m","30m","1h","2h","4h","6h","8h","12h","1d","3d","1w","1M"`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetValues sets the default valid values
|
||||
func (b *Binance) SetValues() {
|
||||
b.validLimits = []string{"5", "10", "20", "50", "100", "500", "1000"}
|
||||
b.validIntervals = []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"}
|
||||
}
|
||||
Reference in New Issue
Block a user