mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
Merge pull request #82 from shazbert/binance
Added support for Binance exchange
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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":
|
||||
|
||||
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"}
|
||||
}
|
||||
130
exchanges/binance/binance_test.go
Normal file
130
exchanges/binance/binance_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
204
exchanges/binance/binance_types.go
Normal file
204
exchanges/binance/binance_types.go
Normal 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"`
|
||||
}
|
||||
95
exchanges/binance/binance_wrapper.go
Normal file
95
exchanges/binance/binance_wrapper.go
Normal 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")
|
||||
}
|
||||
21
testdata/configtest.json
vendored
21
testdata/configtest.json
vendored
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user