Use batch requesting where possible, add new functions for various exchanges, allow auto updating currencies for certain exchanges, update tests and configs

This commit is contained in:
Adrian Gallagher
2018-02-12 14:57:44 +11:00
parent 0cd20804e9
commit 4069595f7c
13 changed files with 427 additions and 109 deletions

View File

@@ -54,7 +54,7 @@ func (b *Binance) SetDefaults() {
b.RESTPollingDelay = 10
b.RequestCurrencyPairFormat.Delimiter = ""
b.RequestCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Delimiter = "-"
b.ConfigCurrencyPairFormat.Uppercase = true
b.AssetTypes = []string{ticker.Spot}
b.SetValues()
@@ -87,18 +87,20 @@ func (b *Binance) Setup(exch config.ExchangeConfig) {
// 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) {
func (b *Binance) GetExchangeValidCurrencyPairs() ([]string, error) {
var validCurrencyPairs []string
info, err := b.GetExchangeInfo()
if err != nil {
return "", err
return nil, err
}
for _, symbol := range info.Symbols {
validCurrencyPairs = append(validCurrencyPairs, symbol.Symbol)
if symbol.Status == "TRADING" {
validCurrencyPairs = append(validCurrencyPairs, symbol.BaseAsset+"-"+symbol.QuoteAsset)
}
}
return common.JoinStrings(validCurrencyPairs, ","), nil
return validCurrencyPairs, nil
}
// GetExchangeInfo returns exchange information. Check binance_types for more
@@ -320,6 +322,13 @@ func (b *Binance) GetPriceChangeStats(symbol string) (PriceChangeStats, error) {
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
}
// GetTickers returns the ticker data for the last 24 hrs
func (b *Binance) GetTickers() ([]PriceChangeStats, error) {
var resp []PriceChangeStats
path := fmt.Sprintf("%s/%s", apiURL, priceChange)
return resp, common.SendHTTPGetRequest(path, true, b.Verbose, &resp)
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
@@ -470,10 +479,13 @@ func (b *Binance) CheckLimit(limit int64) error {
// CheckSymbol checks value against a variable list
func (b *Binance) CheckSymbol(symbol string) error {
if !common.StringDataCompare(b.AvailablePairs, symbol) {
return errors.New("Incorrect symbol values - please check available pairs in configuration")
enPairs := b.GetEnabledCurrencies()
for x := range enPairs {
if exchange.FormatExchangeCurrency(b.Name, enPairs[x]).String() == symbol {
return nil
}
}
return nil
return errors.New("Incorrect symbol values - please check available pairs in configuration")
}
// CheckIntervals checks value against a variable list

View File

@@ -89,6 +89,14 @@ func TestGetPriceChangeStats(t *testing.T) {
}
}
func TestGetTickers(t *testing.T) {
t.Parallel()
_, err := b.GetTickers()
if err != nil {
t.Error("Test Failed - Binance TestGetTickers error", err)
}
}
func TestGetLatestSpotPrice(t *testing.T) {
t.Parallel()
_, err := b.GetLatestSpotPrice("BTCUSDT")

View File

@@ -23,27 +23,56 @@ func (b *Binance) Run() {
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)
}
symbols, err := b.GetExchangeValidCurrencyPairs()
if err != nil {
log.Printf("%s Failed to get exchange info.\n", b.GetName())
} else {
forceUpgrade := false
if !common.StringDataContains(b.EnabledPairs, "-") || !common.StringDataContains(b.AvailablePairs, "-") {
forceUpgrade = true
}
if forceUpgrade {
enabledPairs := []string{"BTC-USDT"}
log.Println("WARNING: Available pairs for Binance reset due to config upgrade, please enable the ones you would like again")
err = b.UpdateEnabledCurrencies(enabledPairs, true)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
}
}
err = b.UpdateAvailableCurrencies(symbols, forceUpgrade)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
}
}
}
// 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())
tick, err := b.GetTickers()
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.Volume
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
for _, x := range b.GetEnabledCurrencies() {
curr := exchange.FormatExchangeCurrency(b.Name, x)
for y := range tick {
if tick[y].Symbol == curr.String() {
tickerPrice.Pair = x
tickerPrice.Ask = tick[y].AskPrice
tickerPrice.Bid = tick[y].BidPrice
tickerPrice.High = tick[y].HighPrice
tickerPrice.Last = tick[y].LastPrice
tickerPrice.Low = tick[y].LowPrice
tickerPrice.Volume = tick[y].Volume
ticker.ProcessTicker(b.Name, x, tickerPrice, assetType)
}
}
}
return ticker.GetTicker(b.Name, p, assetType)
}
@@ -68,7 +97,7 @@ func (b *Binance) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (
// 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
orderbookNew, err := b.GetOrderBook(p.Pair().String(), 1000)
orderbookNew, err := b.GetOrderBook(exchange.FormatExchangeCurrency(b.Name, p).String(), 1000)
if err != nil {
return orderBook, err
}

View File

@@ -18,10 +18,15 @@ import (
const (
bitfinexAPIURL = "https://api.bitfinex.com/v1/"
bitfinexAPIURLBase = "https://api.bitfinex.com"
bitfinexAPIVersion = "1"
bitfinexAPIVersion2 = "2"
bitfinexTickerV2 = "ticker"
bitfinexTickersV2 = "tickers"
bitfinexTicker = "pubticker/"
bitfinexStats = "stats/"
bitfinexLendbook = "lendbook/"
bitfinexOrderbookV2 = "book"
bitfinexOrderbook = "book/"
bitfinexTrades = "trades/"
bitfinexKeyPermissions = "key_info"
@@ -58,10 +63,17 @@ const (
bitfinexTransfer = "transfer"
bitfinexWithdrawal = "withdraw"
bitfinexActiveCredits = "credits"
bitfinexPlatformStatus = "platform/status"
// bitfinexMaxRequests if exceeded IP address blocked 10-60 sec, JSON response
// {"error": "ERR_RATE_LIMIT"}
bitfinexMaxRequests = 90
// Bitfinex platform status values
// When the platform is marked in maintenance mode bots should stop trading
// activity. Cancelling orders will be still possible.
bitfinexMaintenanceMode = 0
bitfinexOperativeMode = 1
)
// Bitfinex is the overarching type across the bitfinex package
@@ -114,6 +126,24 @@ func (b *Bitfinex) Setup(exch config.ExchangeConfig) {
}
}
// GetPlatformStatus returns the Bifinex platform status
func (b *Bitfinex) GetPlatformStatus() (int, error) {
var response []interface{}
path := fmt.Sprintf("%s/v%s/%s", bitfinexAPIURLBase, bitfinexAPIVersion2,
bitfinexPlatformStatus)
err := common.SendHTTPGetRequest(path, true, b.Verbose, &response)
if err != nil {
return 0, err
}
if (len(response)) != 1 {
return 0, errors.New("unexpected platform status value")
}
return int(response[0].(float64)), nil
}
// GetTicker returns ticker information
func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) {
response := Ticker{}
@@ -122,6 +152,96 @@ func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) {
return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
// GetTickerV2 returns ticker information
func (b *Bitfinex) GetTickerV2(symbol string) (Tickerv2, error) {
var response []interface{}
var ticker Tickerv2
path := fmt.Sprintf("%s/v%s/%s/%s", bitfinexAPIURLBase, bitfinexAPIVersion2, bitfinexTickerV2, symbol)
err := common.SendHTTPGetRequest(path, true, b.Verbose, &response)
if err != nil {
return ticker, err
}
if len(response) > 10 {
ticker.FlashReturnRate = response[0].(float64)
ticker.Bid = response[1].(float64)
ticker.BidSize = response[2].(float64)
ticker.BidPeriod = int64(response[3].(float64))
ticker.Ask = response[4].(float64)
ticker.AskSize = response[5].(float64)
ticker.AskPeriod = int64(response[6].(float64))
ticker.DailyChange = response[7].(float64)
ticker.DailyChangePerc = response[8].(float64)
ticker.Last = response[9].(float64)
ticker.Volume = response[10].(float64)
ticker.High = response[11].(float64)
ticker.Low = response[12].(float64)
} else {
ticker.Bid = response[0].(float64)
ticker.BidSize = response[1].(float64)
ticker.Ask = response[2].(float64)
ticker.AskSize = response[3].(float64)
ticker.DailyChange = response[4].(float64)
ticker.DailyChangePerc = response[5].(float64)
ticker.Last = response[6].(float64)
ticker.Volume = response[7].(float64)
ticker.High = response[8].(float64)
ticker.Low = response[9].(float64)
}
return ticker, nil
}
// GetTickersV2 returns ticker information for multiple symbols
func (b *Bitfinex) GetTickersV2(symbols string) ([]Tickersv2, error) {
var response [][]interface{}
var tickers []Tickersv2
v := url.Values{}
v.Set("symbols", symbols)
path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s", bitfinexAPIURLBase, bitfinexAPIVersion2, bitfinexTickersV2), v)
err := common.SendHTTPGetRequest(path, true, b.Verbose, &response)
if err != nil {
return nil, err
}
for x := range response {
var tick Tickersv2
data := response[x]
if len(data) > 11 {
tick.Symbol = data[0].(string)
tick.FlashReturnRate = data[1].(float64)
tick.Bid = data[2].(float64)
tick.BidSize = data[3].(float64)
tick.BidPeriod = int64(data[4].(float64))
tick.Ask = data[5].(float64)
tick.AskSize = data[6].(float64)
tick.AskPeriod = int64(data[7].(float64))
tick.DailyChange = data[8].(float64)
tick.DailyChangePerc = data[9].(float64)
tick.Last = data[10].(float64)
tick.Volume = data[11].(float64)
tick.High = data[12].(float64)
tick.Low = data[13].(float64)
} else {
tick.Symbol = data[0].(string)
tick.Bid = data[1].(float64)
tick.BidSize = data[2].(float64)
tick.Ask = data[3].(float64)
tick.AskSize = data[4].(float64)
tick.DailyChange = data[5].(float64)
tick.DailyChangePerc = data[6].(float64)
tick.Last = data[7].(float64)
tick.Volume = data[8].(float64)
tick.High = data[9].(float64)
tick.Low = data[10].(float64)
}
tickers = append(tickers, tick)
}
return tickers, nil
}
// GetStats returns various statistics about the requested pair
func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) {
response := []Stat{}
@@ -154,6 +274,54 @@ func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbo
return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
// GetOrderbookV2 retieves the orderbook bid and ask price points for a currency
// pair - By default the response will return 25 bid and 25 ask price points.
// symbol - Example "tBTCUSD"
// precision - P0,P1,P2,P3,R0
// Values can contain limit amounts for both the asks and bids - Example
// "len" = 1000
func (b *Bitfinex) GetOrderbookV2(symbol, precision string, values url.Values) (OrderbookV2, error) {
var response [][]interface{}
var book OrderbookV2
path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", bitfinexAPIURLBase,
bitfinexAPIVersion2, bitfinexOrderbookV2, symbol, precision), values)
err := common.SendHTTPGetRequest(path, true, b.Verbose, &response)
if err != nil {
return book, err
}
for x := range response {
data := response[x]
bookItem := BookV2{}
if len(data) > 3 {
bookItem.Rate = data[0].(float64)
bookItem.Price = data[1].(float64)
bookItem.Count = int64(data[2].(float64))
bookItem.Amount = data[3].(float64)
} else {
bookItem.Price = data[0].(float64)
bookItem.Count = int64(data[1].(float64))
bookItem.Amount = data[2].(float64)
}
if symbol[0] == 't' {
if bookItem.Amount > 0 {
book.Bids = append(book.Bids, bookItem)
} else {
book.Asks = append(book.Asks, bookItem)
}
} else {
if bookItem.Amount > 0 {
book.Asks = append(book.Asks, bookItem)
} else {
book.Bids = append(book.Bids, bookItem)
}
}
}
return book, nil
}
// GetTrades returns a list of the most recent trades for the given curencyPair
// By default the response will return 100 trades
// CurrencyPair - Example "BTCUSD"

View File

@@ -49,6 +49,19 @@ func TestSetup(t *testing.T) {
}
}
func TestGetPlatformStatus(t *testing.T) {
t.Parallel()
result, err := b.GetPlatformStatus()
if err != nil {
t.Errorf("TestGetPlatformStatus error: %s", err)
}
if result != bitfinexOperativeMode && result != bitfinexMaintenanceMode {
t.Errorf("TestGetPlatformStatus unexpected response code")
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := b.GetTicker("BTCUSD", url.Values{})
@@ -62,6 +75,27 @@ func TestGetTicker(t *testing.T) {
}
}
func TestGetTickerV2(t *testing.T) {
t.Parallel()
_, err := b.GetTickerV2("tBTCUSD")
if err != nil {
t.Errorf("GetTickerV2 error: %s", err)
}
_, err = b.GetTickerV2("fUSD")
if err != nil {
t.Errorf("GetTickerV2 error: %s", err)
}
}
func TestGetTickersV2(t *testing.T) {
t.Parallel()
_, err := b.GetTickersV2("tBTCUSD,fUSD")
if err != nil {
t.Errorf("GetTickersV2 error: %s", err)
}
}
func TestGetStats(t *testing.T) {
t.Parallel()
_, err := b.GetStats("BTCUSD")
@@ -105,6 +139,20 @@ func TestGetOrderbook(t *testing.T) {
}
}
func TestGetOrderbookV2(t *testing.T) {
t.Parallel()
_, err := b.GetOrderbookV2("tBTCUSD", "P0", url.Values{})
if err != nil {
t.Errorf("GetOrderbookV2 error: %s", err)
}
_, err = b.GetOrderbookV2("fUSD", "P0", url.Values{})
if err != nil {
t.Errorf("GetOrderbookV2 error: %s", err)
}
}
func TestGetTrades(t *testing.T) {
t.Parallel()

View File

@@ -12,6 +12,29 @@ type Ticker struct {
Timestamp string `json:"timestamp"`
}
// Tickerv2 holds the version 2 ticker information
type Tickerv2 struct {
FlashReturnRate float64
Bid float64
BidPeriod int64
BidSize float64
Ask float64
AskPeriod int64
AskSize float64
DailyChange float64
DailyChangePerc float64
Last float64
Volume float64
High float64
Low float64
}
// Tickersv2 holds the version 2 tickers information
type Tickersv2 struct {
Symbol string
Tickerv2
}
// Stat holds individual statistics from exchange
type Stat struct {
Period int64 `json:"period"`
@@ -30,6 +53,21 @@ type Orderbook struct {
Asks []Book
}
// BookV2 holds the orderbook item
type BookV2 struct {
Price float64
Rate float64
Period float64
Count int64
Amount float64
}
// OrderbookV2 holds orderbook information from bid and ask sides
type OrderbookV2 struct {
Bids []BookV2
Asks []BookV2
}
// TradeStructure holds executed trade information
type TradeStructure struct {
Timestamp int64 `json:"timestamp"`

View File

@@ -42,19 +42,30 @@ func (b *Bitfinex) Run() {
// UpdateTicker updates and returns the ticker for a currency pair
func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tickerNew, err := b.GetTicker(p.Pair().String(), nil)
enabledPairs := b.GetEnabledCurrencies()
var pairs []string
for x := range enabledPairs {
pairs = append(pairs, "t"+enabledPairs[x].Pair().String())
}
tickerNew, err := b.GetTickersV2(common.JoinStrings(pairs, ","))
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tickerNew.Ask
tickerPrice.Bid = tickerNew.Bid
tickerPrice.Low = tickerNew.Low
tickerPrice.Last = tickerNew.Last
tickerPrice.Volume = tickerNew.Volume
tickerPrice.High = tickerNew.High
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
for x := range tickerNew {
newP := pair.NewCurrencyPair(tickerNew[x].Symbol[1:4], tickerNew[x].Symbol[4:])
var tick ticker.Price
tick.Pair = newP
tick.Ask = tickerNew[x].Ask
tick.Bid = tickerNew[x].Bid
tick.Low = tickerNew[x].Low
tick.Last = tickerNew[x].Last
tick.Volume = tickerNew[x].Volume
tick.High = tickerNew[x].High
ticker.ProcessTicker(b.Name, tick.Pair, tick, assetType)
}
return ticker.GetTicker(b.Name, p, assetType)
}

View File

@@ -19,6 +19,7 @@ func TestRun(t *testing.T) {
func TestGetTickerPrice(t *testing.T) {
getTickerPrice := Bitfinex{}
getTickerPrice.EnabledPairs = []string{"BTCUSD", "LTCUSD"}
_, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD"),
ticker.Spot)
if err != nil {

View File

@@ -77,16 +77,26 @@ func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
// UpdateTicker updates and returns the ticker for a currency pair
func (b *Bittrex) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := b.GetMarketSummary(exchange.FormatExchangeCurrency(b.GetName(), p).String())
tick, err := b.GetMarketSummaries()
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick[0].Ask
tickerPrice.Bid = tick[0].Bid
tickerPrice.Last = tick[0].Last
tickerPrice.Volume = tick[0].Volume
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
for _, x := range b.GetEnabledCurrencies() {
curr := exchange.FormatExchangeCurrency(b.Name, x)
for y := range tick {
if tick[y].MarketName == curr.String() {
tickerPrice.Pair = x
tickerPrice.High = tick[y].High
tickerPrice.Low = tick[y].Low
tickerPrice.Ask = tick[y].Ask
tickerPrice.Bid = tick[y].Bid
tickerPrice.Last = tick[y].Last
tickerPrice.Volume = tick[y].Volume
ticker.ProcessTicker(b.GetName(), x, tickerPrice, assetType)
}
}
}
return ticker.GetTicker(b.Name, p, assetType)
}

View File

@@ -57,7 +57,7 @@ func TestGetFee(t *testing.T) {
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := bm.GetTicker("BTC")
_, err := bm.GetTicker("BTC", "AUD")
if err != nil {
t.Error("Test failed - GetTicker() error", err)
}
@@ -65,7 +65,7 @@ func TestGetTicker(t *testing.T) {
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := bm.GetOrderbook("BTC")
_, err := bm.GetOrderbook("BTC", "AUD")
if err != nil {
t.Error("Test failed - GetOrderbook() error", err)
}
@@ -73,14 +73,14 @@ func TestGetOrderbook(t *testing.T) {
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := bm.GetTrades("BTC", nil)
_, err := bm.GetTrades("BTC", "AUD", nil)
if err != nil {
t.Error("Test failed - GetTrades() error", err)
}
val := url.Values{}
val.Set("since", "0")
_, err = bm.GetTrades("BTC", val)
_, err = bm.GetTrades("BTC", "AUD", val)
if err != nil {
t.Error("Test failed - GetTrades() error", err)
}