Merge pull request #82 from shazbert/binance

Added support for Binance exchange
This commit is contained in:
Adrian Gallagher
2018-02-01 17:05:22 +11:00
committed by GitHub
9 changed files with 974 additions and 2 deletions

View File

@@ -20,6 +20,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|----------|------|-----------|-----|
| Alphapoint | Yes | Yes | NA |
| ANXPRO | Yes | No | NA |
| Bitfinex | Yes | No | NA |
| Bitfinex | Yes | Yes | NA |
| Bithumb | Yes | NA | NA |
| Bitstamp | Yes | Yes | NA |

View File

@@ -89,7 +89,7 @@ func TestGetEnabledExchanges(t *testing.T) {
}
exchanges := cfg.GetEnabledExchanges()
if len(exchanges) != 21 {
if len(exchanges) != 22 {
t.Error(
"Test failed. TestGetEnabledExchanges. Enabled exchanges value mismatch",
)
@@ -141,7 +141,7 @@ func TestGetDisabledExchanges(t *testing.T) {
}
func TestCountEnabledExchanges(t *testing.T) {
defaultEnabledExchanges := 21
defaultEnabledExchanges := 22
GetConfigEnabledExchanges := GetConfig()
err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile)
if err != nil {

View File

@@ -80,6 +80,27 @@
"Index": "BTC"
}
},
{
"Name": "Binance",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "ETHBTC,LTCBTC,BNBBTC,NEOBTC,123456,QTUMETH,EOSETH,SNTETH,BNTETH,BCCBTC,GASBTC,BNBETH,BTCUSDT,ETHUSDT,HSRBTC,OAXETH,DNTETH,MCOETH,ICNETH,MCOBTC,WTCBTC,WTCETH,LRCBTC,LRCETH,QTUMBTC,YOYOBTC,OMGBTC,OMGETH,ZRXBTC,ZRXETH,STRATBTC,STRATETH,SNGLSBTC,SNGLSETH,BQXBTC,BQXETH,KNCBTC,KNCETH,FUNBTC,FUNETH,SNMBTC,SNMETH,NEOETH,IOTABTC,IOTAETH,LINKBTC,LINKETH,XVGBTC,XVGETH,CTRBTC,CTRETH,SALTBTC,SALTETH,MDABTC,MDAETH,MTLBTC,MTLETH,SUBBTC,SUBETH,EOSBTC,SNTBTC,ETCETH,ETCBTC,MTHBTC,MTHETH,ENGBTC,ENGETH,DNTBTC,ZECBTC,ZECETH,BNTBTC,ASTBTC,ASTETH,DASHBTC,DASHETH,OAXBTC,ICNBTC,BTGBTC,BTGETH,EVXBTC,EVXETH,REQBTC,REQETH,VIBBTC,VIBETH,HSRETH,TRXBTC,TRXETH,POWRBTC,POWRETH,ARKBTC,ARKETH,YOYOETH,XRPBTC,XRPETH,MODBTC,MODETH,ENJBTC,ENJETH,STORJBTC,STORJETH,BNBUSDT,VENBNB,YOYOBNB,POWRBNB,VENBTC,VENETH,KMDBTC,KMDETH,NULSBNB,RCNBTC,RCNETH,RCNBNB,NULSBTC,NULSETH,RDNBTC,RDNETH,RDNBNB,XMRBTC,XMRETH,DLTBNB,WTCBNB,DLTBTC,DLTETH,AMBBTC,AMBETH,AMBBNB,BCCETH,BCCUSDT,BCCBNB,BATBTC,BATETH,BATBNB,BCPTBTC,BCPTETH,BCPTBNB,ARNBTC,ARNETH,GVTBTC,GVTETH,CDTBTC,CDTETH,GXSBTC,GXSETH,NEOUSDT,NEOBNB,POEBTC,POEETH,QSPBTC,QSPETH,QSPBNB,BTSBTC,BTSETH,BTSBNB,XZCBTC,XZCETH,XZCBNB,LSKBTC,LSKETH,LSKBNB,TNTBTC,TNTETH,FUELBTC,FUELETH,MANABTC,MANAETH,BCDBTC,BCDETH,DGDBTC,DGDETH,IOTABNB,ADXBTC,ADXETH,ADXBNB,ADABTC,ADAETH,PPTBTC,PPTETH,CMTBTC,CMTETH,CMTBNB,XLMBTC,XLMETH,XLMBNB,CNDBTC,CNDETH,CNDBNB,LENDBTC,LENDETH,WABIBTC,WABIETH,WABIBNB,LTCETH,LTCUSDT,LTCBNB,TNBBTC,TNBETH,WAVESBTC,WAVESETH,WAVESBNB,GTOBTC,GTOETH,GTOBNB,ICXBTC,ICXETH,ICXBNB,OSTBTC,OSTETH,OSTBNB,ELFBTC,ELFETH,AIONBTC,AIONETH,AIONBNB,NEBLBTC,NEBLETH,NEBLBNB,BRDBTC,BRDETH,BRDBNB,MCOBNB,EDOBTC,EDOETH,WINGSBTC,WINGSETH,NAVBTC,NAVETH,NAVBNB,LUNBTC,LUNETH,TRIGBTC,TRIGETH,TRIGBNB,APPCBTC,APPCETH,APPCBNB,VIBEBTC,VIBEETH,RLCBTC,RLCETH,RLCBNB,INSBTC,INSETH,PIVXBTC,PIVXETH,PIVXBNB,IOSTBTC,IOSTETH,CHATBTC,CHATETH",
"EnabledPairs": "BTCUSDT",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Bitfinex",
"Enabled": true,

View File

@@ -7,6 +7,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/anx"
"github.com/thrasher-/gocryptotrader/exchanges/binance"
"github.com/thrasher-/gocryptotrader/exchanges/bitfinex"
"github.com/thrasher-/gocryptotrader/exchanges/bithumb"
"github.com/thrasher-/gocryptotrader/exchanges/bitstamp"
@@ -128,6 +129,8 @@ func LoadExchange(name string) error {
switch nameLower {
case "anx":
exch = new(anx.ANX)
case "binance":
exch = new(binance.Binance)
case "bitfinex":
exch = new(bitfinex.Bitfinex)
case "bithumb":

View 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"}
}

View File

@@ -0,0 +1,130 @@
package binance
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
// Please supply your own keys here for due diligence testing
const (
testAPIKey = ""
testAPISecret = ""
)
var b Binance
func TestSetDefaults(t *testing.T) {
b.SetDefaults()
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
binanceConfig, err := cfg.GetExchangeConfig("Binance")
if err != nil {
t.Error("Test Failed - Binance Setup() init error")
}
binanceConfig.AuthenticatedAPISupport = true
binanceConfig.APIKey = testAPIKey
binanceConfig.APISecret = testAPISecret
b.Setup(binanceConfig)
}
func TestGetExchangeValidCurrencyPairs(t *testing.T) {
t.Parallel()
_, err := b.GetExchangeValidCurrencyPairs()
if err != nil {
t.Error("Test Failed - Binance GetExchangeValidCurrencyPairs() error", err)
}
}
func TestGetOrderBook(t *testing.T) {
t.Parallel()
_, err := b.GetOrderBook("BTCUSDT", 5)
if err != nil {
t.Error("Test Failed - Binance GetOrderBook() error", err)
}
}
func TestGetRecentTrades(t *testing.T) {
t.Parallel()
_, err := b.GetRecentTrades("BTCUSDT", 5)
if err != nil {
t.Error("Test Failed - Binance GetRecentTrades() error", err)
}
}
func TestGetHistoricalTrades(t *testing.T) {
t.Parallel()
_, err := b.GetHistoricalTrades("BTCUSDT", 5, 1337)
if err == nil {
t.Error("Test Failed - Binance GetHistoricalTrades() error", err)
}
}
func TestGetAggregatedTrades(t *testing.T) {
t.Parallel()
_, err := b.GetAggregatedTrades("BTCUSDT", 5)
if err != nil {
t.Error("Test Failed - Binance GetAggregatedTrades() error", err)
}
}
func TestGetCandleStickData(t *testing.T) {
t.Parallel()
_, err := b.GetCandleStickData("BTCUSDT", "1d", 5)
if err != nil {
t.Error("Test Failed - Binance GetCandleStickData() error", err)
}
}
func TestGetPriceChangeStats(t *testing.T) {
t.Parallel()
_, err := b.GetPriceChangeStats("BTCUSDT")
if err != nil {
t.Error("Test Failed - Binance GetPriceChangeStats() error", err)
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := b.GetLatestSpotPrice("BTCUSDT")
if err != nil {
t.Error("Test Failed - Binance GetLatestSpotPrice() error", err)
}
}
func TestGetBestPrice(t *testing.T) {
t.Parallel()
_, err := b.GetBestPrice("BTCUSDT")
if err != nil {
t.Error("Test Failed - Binance GetBestPrice() error", err)
}
}
func TestNewOrderTest(t *testing.T) {
t.Parallel()
_, err := b.NewOrderTest()
if err != nil {
t.Error("Test Failed - Binance NewOrderTest() error", err)
}
}
func TestNewOrder(t *testing.T) {
t.Parallel()
_, err := b.NewOrder(NewOrderRequest{})
if err == nil {
t.Error("Test Failed - Binance NewOrder() error", err)
}
}
func TestQueryOrder(t *testing.T) {
t.Parallel()
_, err := b.QueryOrder("", "", 1337)
if err == nil {
t.Error("Test Failed - Binance QueryOrder() error", err)
}
}

View File

@@ -0,0 +1,204 @@
package binance
// ExchangeInfo holds the full exchange information type
type ExchangeInfo struct {
Code int `json:"code"`
Msg string `json:"msg"`
Timezone string `json:"timezone"`
Servertime int64 `json:"serverTime"`
RateLimits []struct {
RateLimitType string `json:"rateLimitType"`
Interval string `json:"interval"`
Limit int `json:"limit"`
} `json:"rateLimits"`
ExchangeFilters interface{} `json:"exchangeFilters"`
Symbols []struct {
Symbol string `json:"symbol"`
Status string `json:"status"`
BaseAsset string `json:"baseAsset"`
BaseAssetPrecision int `json:"baseAssetPrecision"`
QuoteAsset string `json:"quoteAsset"`
QuotePrecision int `json:"quotePrecision"`
OrderTypes []string `json:"orderTypes"`
IcebergAllowed bool `json:"icebergAllowed"`
Filters []struct {
FilterType string `json:"filterType"`
MinPrice float64 `json:"minPrice,string"`
MaxPrice float64 `json:"maxPrice,string"`
TickSize float64 `json:"tickSize,string"`
MinQty float64 `json:"minQty,string"`
MaxQty float64 `json:"maxQty,string"`
StepSize float64 `json:"stepSize,string"`
MinNotional float64 `json:"minNotional,string"`
} `json:"filters"`
} `json:"symbols"`
}
// OrderBookData is resp data from orderbook endpoint
type OrderBookData struct {
Code int `json:"code"`
Msg string `json:"msg"`
LastUpdateID int64 `json:"lastUpdateId"`
Bids []interface{} `json:"bids"`
Asks []interface{} `json:"asks"`
}
// OrderBook actual structured data that can be used for orderbook
type OrderBook struct {
Code int
Msg string
Bids []struct {
Price float64
Quantity float64
}
Asks []struct {
Price float64
Quantity float64
}
}
// RecentTrade holds recent trade data
type RecentTrade struct {
Code int `json:"code"`
Msg string `json:"msg"`
ID int64 `json:"id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
Time int64 `json:"time"`
IsBuyerMaker bool `json:"isBuyerMaker"`
IsBestMatch bool `json:"isBestMatch"`
}
// HistoricalTrade holds recent trade data
type HistoricalTrade struct {
Code int `json:"code"`
Msg string `json:"msg"`
ID int64 `json:"id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
Time int64 `json:"time"`
IsBuyerMaker bool `json:"isBuyerMaker"`
IsBestMatch bool `json:"isBestMatch"`
}
// AggregatedTrade holds aggregated trade information
type AggregatedTrade struct {
ATradeID int64 `json:"a"`
Price float64 `json:"p,string"`
Quantity float64 `json:"q,string"`
FirstTradeID int64 `json:"f"`
LastTradeID int64 `json:"l"`
TimeStamp int64 `json:"T"`
Maker bool `json:"m"`
BestMatchPrice bool `json:"M"`
}
// CandleStick holds kline data
type CandleStick struct {
OpenTime float64
Open float64
High float64
Low float64
Close float64
Volume float64
CloseTime float64
QuoteAssetVolume float64
TradeCount float64
TakerBuyAssetVolume float64
TakerBuyQuoteAssetVolume float64
}
// PriceChangeStats contains statistics for the last 24 hours trade
type PriceChangeStats struct {
Symbol string `json:"symbol"`
PriceChange float64 `json:"priceChange,string"`
PriceChangePercent float64 `json:"priceChangePercent,string"`
WeightedAvgPrice float64 `json:"weightedAvgPrice,string"`
PrevClosePrice float64 `json:"prevClosePrice,string"`
LastPrice float64 `json:"lastPrice,string"`
LastQty float64 `json:"lastQty,string"`
BidPrice float64 `json:"bidPrice,string"`
AskPrice float64 `json:"askPrice,string"`
OpenPrice float64 `json:"openPrice,string"`
HighPrice float64 `json:"highPrice,string"`
LowPrice float64 `json:"lowPrice,string"`
Volume float64 `json:"volume,string"`
QuoteVolume float64 `json:"quoteVolume,string"`
OpenTime int64 `json:"openTime"`
CloseTime int64 `json:"closeTime"`
FirstID int64 `json:"fristId"`
LastID int64 `json:"lastId"`
Count int64 `json:"count"`
}
// SymbolPrice holds basic symbol price
type SymbolPrice struct {
Symbol string `json:"symbol"`
Price float64 `json:"price,string"`
}
// BestPrice holds best price data
type BestPrice struct {
Symbol string `json:"symbol"`
BidPrice float64 `json:"bidPrice,string"`
BidQty float64 `json:"bidQty,string"`
AskPrice float64 `json:"askPrice,string"`
AskQty float64 `json:"askQty,string"`
}
// NewOrderRequest request type
type NewOrderRequest struct {
Symbol string
Side string
TradeType string
TimeInForce string
Quantity float64
Price float64
NewClientOrderID string
StopPrice float64
IcebergQty float64
NewOrderRespType string
}
// NewOrderResponse is the return structured response from the exchange
type NewOrderResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Symbol string `json:"symbol"`
OrderID int64 `json:"orderId"`
ClientOrderID string `json:"clientOrderId"`
TransactionTime int64 `json:"transactTime"`
Price float64 `json:"price,string"`
OrigQty float64 `json:"origQty,string"`
ExecutedQty float64 `json:"executedQty,string"`
Status string `json:"status"`
TimeInForce string `json:"timeInForce"`
Type string `json:"type"`
Side string `json:"side"`
Fills []struct {
Price float64 `json:"price,string"`
Qty float64 `json:"qty,string"`
Commission float64 `json:"commission,string"`
CommissionAsset float64 `json:"commissionAsset,string"`
} `json:"fills"`
}
// QueryOrderData holds query order data
type QueryOrderData struct {
Code int `json:"code"`
Msg string `json:"msg"`
Symbol string `json:"symbol"`
OrderID int64 `json:"orderId"`
ClientOrderID string `json:"clientOrderId"`
Price float64 `json:"price,string"`
OrigQty float64 `json:"origQty,string"`
ExecutedQty float64 `json:"executedQty,string"`
Status string `json:"status"`
TimeInForce string `json:"timeInForce"`
Type string `json:"type"`
Side string `json:"side"`
StopPrice float64 `json:"stopPrice,string"`
IcebergQty float64 `json:"icebergQty,string"`
Time int64 `json:"time"`
IsWorking bool `json:"isWorking"`
}

View File

@@ -0,0 +1,95 @@
package binance
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 *Binance) Start() {
go b.Run()
}
// Run implements the OKEX wrapper
func (b *Binance) 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 *Binance) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := b.GetPriceChangeStats(p.Pair().String())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick.AskPrice
tickerPrice.Bid = tick.BidPrice
tickerPrice.High = tick.HighPrice
tickerPrice.Last = tick.LastPrice
tickerPrice.Low = tick.LowPrice
tickerPrice.Volume = tick.LastQty
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (b *Binance) 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 *Binance) 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 *Binance) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
currency := p.GetFirstCurrency().String()
orderbookNew, err := b.GetOrderBook(currency, 1000)
if err != nil {
return orderBook, err
}
for _, bids := range orderbookNew.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: bids.Quantity, Price: bids.Price})
}
for _, asks := range orderbookNew.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 *Binance) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
return response, errors.New("not implemented")
}

View File

@@ -80,6 +80,27 @@
"Index": "BTC"
}
},
{
"Name": "Binance",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "ETHBTC,LTCBTC,BNBBTC,NEOBTC,123456,QTUMETH,EOSETH,SNTETH,BNTETH,BCCBTC,GASBTC,BNBETH,BTCUSDT,ETHUSDT,HSRBTC,OAXETH,DNTETH,MCOETH,ICNETH,MCOBTC,WTCBTC,WTCETH,LRCBTC,LRCETH,QTUMBTC,YOYOBTC,OMGBTC,OMGETH,ZRXBTC,ZRXETH,STRATBTC,STRATETH,SNGLSBTC,SNGLSETH,BQXBTC,BQXETH,KNCBTC,KNCETH,FUNBTC,FUNETH,SNMBTC,SNMETH,NEOETH,IOTABTC,IOTAETH,LINKBTC,LINKETH,XVGBTC,XVGETH,CTRBTC,CTRETH,SALTBTC,SALTETH,MDABTC,MDAETH,MTLBTC,MTLETH,SUBBTC,SUBETH,EOSBTC,SNTBTC,ETCETH,ETCBTC,MTHBTC,MTHETH,ENGBTC,ENGETH,DNTBTC,ZECBTC,ZECETH,BNTBTC,ASTBTC,ASTETH,DASHBTC,DASHETH,OAXBTC,ICNBTC,BTGBTC,BTGETH,EVXBTC,EVXETH,REQBTC,REQETH,VIBBTC,VIBETH,HSRETH,TRXBTC,TRXETH,POWRBTC,POWRETH,ARKBTC,ARKETH,YOYOETH,XRPBTC,XRPETH,MODBTC,MODETH,ENJBTC,ENJETH,STORJBTC,STORJETH,BNBUSDT,VENBNB,YOYOBNB,POWRBNB,VENBTC,VENETH,KMDBTC,KMDETH,NULSBNB,RCNBTC,RCNETH,RCNBNB,NULSBTC,NULSETH,RDNBTC,RDNETH,RDNBNB,XMRBTC,XMRETH,DLTBNB,WTCBNB,DLTBTC,DLTETH,AMBBTC,AMBETH,AMBBNB,BCCETH,BCCUSDT,BCCBNB,BATBTC,BATETH,BATBNB,BCPTBTC,BCPTETH,BCPTBNB,ARNBTC,ARNETH,GVTBTC,GVTETH,CDTBTC,CDTETH,GXSBTC,GXSETH,NEOUSDT,NEOBNB,POEBTC,POEETH,QSPBTC,QSPETH,QSPBNB,BTSBTC,BTSETH,BTSBNB,XZCBTC,XZCETH,XZCBNB,LSKBTC,LSKETH,LSKBNB,TNTBTC,TNTETH,FUELBTC,FUELETH,MANABTC,MANAETH,BCDBTC,BCDETH,DGDBTC,DGDETH,IOTABNB,ADXBTC,ADXETH,ADXBNB,ADABTC,ADAETH,PPTBTC,PPTETH,CMTBTC,CMTETH,CMTBNB,XLMBTC,XLMETH,XLMBNB,CNDBTC,CNDETH,CNDBNB,LENDBTC,LENDETH,WABIBTC,WABIETH,WABIBNB,LTCETH,LTCUSDT,LTCBNB,TNBBTC,TNBETH,WAVESBTC,WAVESETH,WAVESBNB,GTOBTC,GTOETH,GTOBNB,ICXBTC,ICXETH,ICXBNB,OSTBTC,OSTETH,OSTBNB,ELFBTC,ELFETH,AIONBTC,AIONETH,AIONBNB,NEBLBTC,NEBLETH,NEBLBNB,BRDBTC,BRDETH,BRDBNB,MCOBNB,EDOBTC,EDOETH,WINGSBTC,WINGSETH,NAVBTC,NAVETH,NAVBNB,LUNBTC,LUNETH,TRIGBTC,TRIGETH,TRIGBNB,APPCBTC,APPCETH,APPCBNB,VIBEBTC,VIBEETH,RLCBTC,RLCETH,RLCBNB,INSBTC,INSETH,PIVXBTC,PIVXETH,PIVXBNB,IOSTBTC,IOSTETH,CHATBTC,CHATETH",
"EnabledPairs": "BTCUSDT",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Bitfinex",
"Enabled": true,