From eef13c451511c5b2113fafdf81c9ee726452819d Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 31 Aug 2017 16:01:28 +1000 Subject: [PATCH 1/5] Fixed minor linter issues in coinut. Fixed linter issues, increased code cov & expanded functionality in gdax. Added new function in nonce package. --- exchanges/coinut/coinut.go | 75 +-- exchanges/coinut/coinut_test.go | 37 ++ exchanges/gdax/gdax.go | 790 +++++++++++++++++++++++-------- exchanges/gdax/gdax_test.go | 300 ++++++++++++ exchanges/gdax/gdax_types.go | 378 +++++++++++---- exchanges/gdax/gdax_websocket.go | 12 +- exchanges/gdax/gdax_wrapper.go | 2 +- exchanges/nonce/nonce.go | 13 + 8 files changed, 1261 insertions(+), 346 deletions(-) create mode 100644 exchanges/coinut/coinut_test.go create mode 100644 exchanges/gdax/gdax_test.go diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 273c6f09..969f74ec 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -15,31 +15,33 @@ import ( ) const ( - COINUT_API_URL = "https://api.coinut.com" - COINUT_API_VERSION = "1" - COINUT_INSTRUMENTS = "inst_list" - COINUT_TICKER = "inst_tick" - COINUT_ORDERBOOK = "inst_order_book" - COINUT_TRADES = "inst_trade" - COINUT_BALANCE = "user_balance" - COINUT_ORDER = "new_order" - COINUT_ORDERS = "new_orders" - COINUT_ORDERS_OPEN = "user_open_orders" - COINUT_ORDER_CANCEL = "cancel_order" - COINUT_ORDERS_CANCEL = "cancel_orders" - COINUT_TRADE_HISTORY = "trade_history" - COINUT_INDEX_TICKER = "index_tick" - COINUT_OPTION_CHAIN = "option_chain" - COINUT_POSITION_HISTORY = "position_history" - COINUT_POSITION_OPEN = "user_open_positions" + coinutAPIURL = "https://api.coinut.com" + coinutAPIVersion = "1" + coinutInstruments = "inst_list" + coinutTicker = "inst_tick" + coinutOrderbook = "inst_order_book" + coinutTrades = "inst_trade" + coinutBalance = "user_balance" + coinutOrder = "new_order" + coinutOrders = "new_orders" + coinutOrdersOpen = "user_open_orders" + coinutOrderCancel = "cancel_order" + coinutOrdersCancel = "cancel_orders" + coinutTradeHistory = "trade_history" + coinutIndexTicker = "index_tick" + coinutOptionChain = "option_chain" + coinutPositionHistory = "position_history" + coinutPositionOpen = "user_open_positions" ) +// COINUT is the overarching type across the coinut package type COINUT struct { exchange.Base WebsocketConn *websocket.Conn InstrumentMap map[string]int } +// SetDefaults sets current default values func (c *COINUT) SetDefaults() { c.Name = "COINUT" c.Enabled = false @@ -56,6 +58,7 @@ func (c *COINUT) SetDefaults() { c.AssetTypes = []string{ticker.Spot} } +// Setup sets the current exchange configuration func (c *COINUT) Setup(exch config.ExchangeConfig) { if !exch.Enabled { c.SetEnabled(false) @@ -80,11 +83,12 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) { } } +// GetInstruments returns instruments func (c *COINUT) GetInstruments() (CoinutInstruments, error) { var result CoinutInstruments params := make(map[string]interface{}) params["sec_type"] = "SPOT" - err := c.SendAuthenticatedHTTPRequest(COINUT_INSTRUMENTS, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutInstruments, params, &result) if err != nil { return result, err } @@ -95,7 +99,7 @@ func (c *COINUT) GetInstrumentTicker(instrumentID int) (CoinutTicker, error) { var result CoinutTicker params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_TICKER, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutTicker, params, &result) if err != nil { return result, err } @@ -109,7 +113,7 @@ func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int) (CoinutOrderboo if limit > 0 { params["top_n"] = limit } - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERBOOK, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrderbook, params, &result) if err != nil { return result, err } @@ -120,7 +124,7 @@ func (c *COINUT) GetTrades(instrumentID int) (CoinutTrades, error) { var result CoinutTrades params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_TRADES, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutTrades, params, &result) if err != nil { return result, err } @@ -129,7 +133,7 @@ func (c *COINUT) GetTrades(instrumentID int) (CoinutTrades, error) { func (c *COINUT) GetUserBalance() (CoinutUserBalance, error) { result := CoinutUserBalance{} - err := c.SendAuthenticatedHTTPRequest(COINUT_BALANCE, nil, &result) + err := c.SendAuthenticatedHTTPRequest(coinutBalance, nil, &result) if err != nil { return result, err } @@ -148,7 +152,7 @@ func (c *COINUT) NewOrder(instrumentID int, quantity, price float64, buy bool, o } params["client_ord_id"] = orderID - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDER, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrder, params, &result) if err != nil { return result, err } @@ -159,7 +163,7 @@ func (c *COINUT) NewOrders(orders []CoinutOrder) ([]CoinutOrdersBase, error) { var result CoinutOrdersResponse params := make(map[string]interface{}) params["orders"] = orders - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS, params, &result.Data) + err := c.SendAuthenticatedHTTPRequest(coinutOrders, params, &result.Data) if err != nil { return nil, err } @@ -170,7 +174,7 @@ func (c *COINUT) GetOpenOrders(instrumentID int) ([]CoinutOrdersResponse, error) var result []CoinutOrdersResponse params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_OPEN, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrdersOpen, params, &result) if err != nil { return nil, err } @@ -182,7 +186,7 @@ func (c *COINUT) CancelOrder(instrumentID, orderID int) (bool, error) { params := make(map[string]interface{}) params["inst_id"] = instrumentID params["order_id"] = orderID - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_CANCEL, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrdersCancel, params, &result) if err != nil { return false, err } @@ -193,7 +197,7 @@ func (c *COINUT) CancelOrders(orders []CoinutCancelOrders) (CoinutCancelOrdersRe var result CoinutCancelOrdersResponse params := make(map[string]interface{}) params["entries"] = orders - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_CANCEL, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrdersCancel, params, &result) if err != nil { return result, err } @@ -210,7 +214,7 @@ func (c *COINUT) GetTradeHistory(instrumentID, start, limit int) (CoinutTradeHis if limit >= 0 && start <= 100 { params["limit"] = limit } - err := c.SendAuthenticatedHTTPRequest(COINUT_TRADE_HISTORY, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutTradeHistory, params, &result) if err != nil { return result, err } @@ -221,7 +225,7 @@ func (c *COINUT) GetIndexTicker(asset string) (CoinutIndexTicker, error) { var result CoinutIndexTicker params := make(map[string]interface{}) params["asset"] = asset - err := c.SendAuthenticatedHTTPRequest(COINUT_INDEX_TICKER, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutIndexTicker, params, &result) if err != nil { return result, err } @@ -232,7 +236,7 @@ func (c *COINUT) GetDerivativeInstruments(secType string) (interface{}, error) { var result interface{} //to-do params := make(map[string]interface{}) params["sec_type"] = secType - err := c.SendAuthenticatedHTTPRequest(COINUT_INSTRUMENTS, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutInstruments, params, &result) if err != nil { return result, err } @@ -244,7 +248,7 @@ func (c *COINUT) GetOptionChain(asset, secType string, expiry int64) (CoinutOpti params := make(map[string]interface{}) params["asset"] = asset params["sec_type"] = secType - err := c.SendAuthenticatedHTTPRequest(COINUT_OPTION_CHAIN, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOptionChain, params, &result) if err != nil { return result, err } @@ -261,7 +265,7 @@ func (c *COINUT) GetPositionHistory(secType string, start, limit int) (CoinutPos if limit >= 0 { params["limit"] = limit } - err := c.SendAuthenticatedHTTPRequest(COINUT_POSITION_HISTORY, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutPositionHistory, params, &result) if err != nil { return result, err } @@ -276,7 +280,7 @@ func (c *COINUT) GetOpenPosition(instrumentID int) ([]CoinutOpenPosition, error) params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_POSITION_OPEN, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutPositionOpen, params, &result) if err != nil { return result.Positions, err } @@ -319,7 +323,10 @@ func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[stri headers["X-SIGNATURE"] = common.HexEncodeToString(hmac) headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest("POST", COINUT_API_URL, headers, bytes.NewBuffer(payload)) + resp, err := common.SendHTTPRequest("POST", coinutAPIURL, headers, bytes.NewBuffer(payload)) + if err != nil { + return err + } if c.Verbose { log.Printf("Received raw: \n%s", resp) diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go new file mode 100644 index 00000000..3d3cdb44 --- /dev/null +++ b/exchanges/coinut/coinut_test.go @@ -0,0 +1,37 @@ +package coinut + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +const ( + apiKey = "" + apiSecret = "" +) + +var c COINUT + +func TestSetDefaults(t *testing.T) { + c.SetDefaults() +} + +func TestSetup(t *testing.T) { + exch := config.ExchangeConfig{} + c.Setup(exch) + + exch.Enabled = true + exch.APIKey = apiKey + exch.APISecret = apiSecret + c.Setup(exch) +} + +// func TestGetInstruments(t *testing.T) { +// c.Verbose = true +// resp, err := c.GetInstruments() +// if err == nil { +// t.Error("Test failed - GetInstruments() error", err) +// } +// log.Println(resp) +// } diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index 1ff4f59a..f135b3e6 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -7,7 +7,6 @@ import ( "log" "net/url" "strconv" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -16,28 +15,46 @@ import ( ) const ( - GDAX_API_URL = "https://api.gdax.com/" - GDAX_API_VERSION = "0" - GDAX_PRODUCTS = "products" - GDAX_ORDERBOOK = "book" - GDAX_TICKER = "ticker" - GDAX_TRADES = "trades" - GDAX_HISTORY = "candles" - GDAX_STATS = "stats" - GDAX_CURRENCIES = "currencies" - GDAX_ACCOUNTS = "accounts" - GDAX_LEDGER = "ledger" - GDAX_HOLDS = "holds" - GDAX_ORDERS = "orders" - GDAX_FILLS = "fills" - GDAX_TRANSFERS = "transfers" - GDAX_REPORTS = "reports" + gdaxAPIURL = "https://api.gdax.com/" + gdaxAPIVersion = "0" + gdaxProducts = "products" + gdaxOrderbook = "book" + gdaxTicker = "ticker" + gdaxTrades = "trades" + gdaxHistory = "candles" + gdaxStats = "stats" + gdaxCurrencies = "currencies" + gdaxAccounts = "accounts" + gdaxLedger = "ledger" + gdaxHolds = "holds" + gdaxOrders = "orders" + gdaxFills = "fills" + gdaxTransfers = "transfers" + gdaxReports = "reports" + gdaxTime = "time" + gdaxMarginTransfer = "profiles/margin-transfer" + gdaxFunding = "funding" + gdaxFundingRepay = "funding/repay" + gdaxPosition = "position" + gdaxPositionClose = "position/close" + gdaxPaymentMethod = "payment-methods" + gdaxPaymentMethodDeposit = "deposits/payment-method" + gdaxDepositCoinbase = "deposits/coinbase-account" + gdaxWithdrawalPaymentMethod = "withdrawals/payment-method" + gdaxWithdrawalCoinbase = "withdrawals/coinbase" + gdaxWithdrawalCrypto = "withdrawals/crypto" + gdaxCoinbaseAccounts = "coinbase-accounts" + gdaxTrailingVolume = "users/self/trailing-volume" ) +var sometin []string + +// GDAX is the overarching type across the GDAX package type GDAX struct { exchange.Base } +// SetDefaults sets default values for the exchange func (g *GDAX) SetDefaults() { g.Name = "GDAX" g.Enabled = false @@ -54,6 +71,7 @@ func (g *GDAX) SetDefaults() { g.AssetTypes = []string{ticker.Spot} } +// Setup initialises the exchange paramaters with the current configuration func (g *GDAX) Setup(exch config.ExchangeConfig) { if !exch.Enabled { g.SetEnabled(false) @@ -78,42 +96,39 @@ func (g *GDAX) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the current fee for the exchange func (g *GDAX) GetFee(maker bool) float64 { if maker { return g.MakerFee - } else { - return g.TakerFee } + return g.TakerFee } -func (g *GDAX) GetProducts() ([]GDAXProduct, error) { - products := []GDAXProduct{} - err := common.SendHTTPGetRequest(GDAX_API_URL+GDAX_PRODUCTS, true, &products) +// GetProducts returns supported currency pairs on the exchange with specific +// information about the pair +func (g *GDAX) GetProducts() ([]Product, error) { + products := []Product{} - if err != nil { - return nil, err - } - - return products, nil + return products, + common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, &products) } +// GetOrderbook returns orderbook by currency pair and level func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { - orderbook := GDAXOrderbookResponse{} - path := "" + orderbook := OrderbookResponse{} + + path := fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook) if level > 0 { levelStr := strconv.Itoa(level) - path = fmt.Sprintf("%s/%s/%s?level=%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_ORDERBOOK, levelStr) - } else { - path = fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_ORDERBOOK) + path = fmt.Sprintf("%s/%s/%s?level=%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook, levelStr) } - err := common.SendHTTPGetRequest(path, true, &orderbook) - if err != nil { + if err := common.SendHTTPGetRequest(path, true, &orderbook); err != nil { return nil, err } if level == 3 { - ob := GDAXOrderbookL3{} + ob := OrderbookL3{} ob.Sequence = orderbook.Sequence for _, x := range orderbook.Asks { price, err := strconv.ParseFloat((x[0].(string)), 64) @@ -125,7 +140,7 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { continue } - ob.Asks = append(ob.Asks, GDAXOrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) + ob.Asks = append(ob.Asks, OrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) } for _, x := range orderbook.Bids { price, err := strconv.ParseFloat((x[0].(string)), 64) @@ -137,64 +152,65 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { continue } - ob.Bids = append(ob.Bids, GDAXOrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) - } - return ob, nil - } else { - ob := GDAXOrderbookL1L2{} - ob.Sequence = orderbook.Sequence - for _, x := range orderbook.Asks { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue - } - amount, err := strconv.ParseFloat((x[1].(string)), 64) - if err != nil { - continue - } - - ob.Asks = append(ob.Asks, GDAXOrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) - } - for _, x := range orderbook.Bids { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue - } - amount, err := strconv.ParseFloat((x[1].(string)), 64) - if err != nil { - continue - } - - ob.Bids = append(ob.Bids, GDAXOrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) + ob.Bids = append(ob.Bids, OrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) } return ob, nil } -} + ob := OrderbookL1L2{} + ob.Sequence = orderbook.Sequence + for _, x := range orderbook.Asks { + price, err := strconv.ParseFloat((x[0].(string)), 64) + if err != nil { + continue + } + amount, err := strconv.ParseFloat((x[1].(string)), 64) + if err != nil { + continue + } -func (g *GDAX) GetTicker(symbol string) (GDAXTicker, error) { - ticker := GDAXTicker{} - path := fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_TICKER) - err := common.SendHTTPGetRequest(path, true, &ticker) - - if err != nil { - return ticker, err + ob.Asks = append(ob.Asks, OrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) } - return ticker, nil -} + for _, x := range orderbook.Bids { + price, err := strconv.ParseFloat((x[0].(string)), 64) + if err != nil { + continue + } + amount, err := strconv.ParseFloat((x[1].(string)), 64) + if err != nil { + continue + } -func (g *GDAX) GetTrades(symbol string) ([]GDAXTrade, error) { - trades := []GDAXTrade{} - path := fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_TRADES) - err := common.SendHTTPGetRequest(path, true, &trades) - - if err != nil { - return nil, err + ob.Bids = append(ob.Bids, OrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) } - return trades, nil + return ob, nil } -func (g *GDAX) GetHistoricRates(symbol string, start, end, granularity int64) ([]GDAXHistory, error) { - history := []GDAXHistory{} +// GetTicker returns ticker by currency pair +// currencyPair - example "BTC-USD" +func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { + ticker := Ticker{} + path := fmt.Sprintf( + "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTicker) + + log.Println(path) + return ticker, common.SendHTTPGetRequest(path, true, &ticker) +} + +// GetTrades listd the latest trades for a product +// currencyPair - example "BTC-USD" +func (g *GDAX) GetTrades(currencyPair string) ([]Trade, error) { + trades := []Trade{} + path := fmt.Sprintf( + "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTrades) + + return trades, common.SendHTTPGetRequest(path, true, &trades) +} + +// GetHistoricRates returns historic rates for a product. Rates are returned in +// grouped buckets based on requested granularity. +func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int64) ([]History, error) { + var resp [][]interface{} + history := []History{} values := url.Values{} if start > 0 { @@ -209,97 +225,137 @@ func (g *GDAX) GetHistoricRates(symbol string, start, end, granularity int64) ([ values.Set("granularity", strconv.FormatInt(granularity, 10)) } - path := common.EncodeURLValues(fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_HISTORY), values) - err := common.SendHTTPGetRequest(path, true, &history) + path := common.EncodeURLValues( + fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxHistory), + values) - if err != nil { - return nil, err + if err := common.SendHTTPGetRequest(path, true, &resp); err != nil { + return history, err } + + for _, single := range resp { + s := History{ + Time: int64(single[0].(float64)), + Low: single[1].(float64), + High: single[2].(float64), + Open: single[3].(float64), + Close: single[4].(float64), + Volume: single[5].(float64), + } + history = append(history, s) + } + return history, nil } -func (g *GDAX) GetStats(symbol string) (GDAXStats, error) { - stats := GDAXStats{} - path := fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_STATS) - err := common.SendHTTPGetRequest(path, true, &stats) +// GetStats returns a 24 hr stat for the product. Volume is in base currency +// units. open, high, low are in quote currency units. +func (g *GDAX) GetStats(currencyPair string) (Stats, error) { + stats := Stats{} + path := fmt.Sprintf( + "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxStats) - if err != nil { - return stats, err - } - return stats, nil + return stats, common.SendHTTPGetRequest(path, true, &stats) } -func (g *GDAX) GetCurrencies() ([]GDAXCurrency, error) { - currencies := []GDAXCurrency{} - err := common.SendHTTPGetRequest(GDAX_API_URL+GDAX_CURRENCIES, true, ¤cies) +// GetCurrencies returns a list of supported currency on the exchange +// Warning: Not all currencies may be currently in use for trading. +func (g *GDAX) GetCurrencies() ([]Currency, error) { + currencies := []Currency{} - if err != nil { - return nil, err - } - return currencies, nil + return currencies, + common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, ¤cies) } -func (g *GDAX) GetAccounts() ([]GDAXAccountResponse, error) { - resp := []GDAXAccountResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", GDAX_ACCOUNTS, nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetServerTime returns the API server time +func (g *GDAX) GetServerTime() (ServerTime, error) { + serverTime := ServerTime{} + + return serverTime, + common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, &serverTime) } -func (g *GDAX) GetAccount(account string) (GDAXAccountResponse, error) { - resp := GDAXAccountResponse{} - path := fmt.Sprintf("%s/%s", GDAX_ACCOUNTS, account) - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return resp, err - } - return resp, nil +// GetAccounts returns a list of trading accounts associated with the APIKEYS +func (g *GDAX) GetAccounts() ([]AccountResponse, error) { + resp := []AccountResponse{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxAccounts, nil, &resp) } -func (g *GDAX) GetAccountHistory(accountID string) ([]GDAXAccountLedgerResponse, error) { - resp := []GDAXAccountLedgerResponse{} - path := fmt.Sprintf("%s/%s/%s", GDAX_ACCOUNTS, accountID, GDAX_LEDGER) - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetAccount returns information for a single account. Use this endpoint when +// account_id is known +func (g *GDAX) GetAccount(accountID string) (AccountResponse, error) { + resp := AccountResponse{} + path := fmt.Sprintf("%s/%s", gdaxAccounts, accountID) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (g *GDAX) GetHolds(accountID string) ([]GDAXAccountHolds, error) { - resp := []GDAXAccountHolds{} - path := fmt.Sprintf("%s/%s/%s", GDAX_ACCOUNTS, accountID, GDAX_HOLDS) - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetAccountHistory returns a list of account activity. Account activity either +// increases or decreases your account balance. Items are paginated and sorted +// latest first. +func (g *GDAX) GetAccountHistory(accountID string) ([]AccountLedgerResponse, error) { + resp := []AccountLedgerResponse{} + path := fmt.Sprintf("%s/%s/%s", gdaxAccounts, accountID, gdaxLedger) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (g *GDAX) PlaceOrder(clientRef string, price, amount float64, side string, productID, stp string) (string, error) { +// GetHolds returns the holds that are placed on an account for any active +// orders or pending withdraw requests. As an order is filled, the hold amount +// is updated. If an order is canceled, any remaining hold is removed. For a +// withdraw, once it is completed, the hold is removed. +func (g *GDAX) GetHolds(accountID string) ([]AccountHolds, error) { + resp := []AccountHolds{} + path := fmt.Sprintf("%s/%s/%s", gdaxAccounts, accountID, gdaxHolds) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) +} + +// PlaceLimitOrder places a new limit order. Orders can only be placed if the +// account has sufficient funds. Once an order is placed, account funds +// will be put on hold for the duration of the order. How much and which funds +// are put on hold depends on the order type and parameters specified. +// +// GENERAL PARAMS +// clientRef - [optional] Order ID selected by you to identify your order +// side - buy or sell +// productID - A valid product id +// stp - [optional] Self-trade prevention flag +// +// LIMIT ORDER PARAMS +// price - Price per bitcoin +// amount - Amount of BTC to buy or sell +// timeInforce - [optional] GTC, GTT, IOC, or FOK (default is GTC) +// cancelAfter - [optional] min, hour, day * Requires time_in_force to be GTT +// postOnly - [optional] Post only flag Invalid when time_in_force is IOC or FOK +func (g *GDAX) PlaceLimitOrder(clientRef string, price, amount float64, side, timeInforce, cancelAfter, productID, stp string, postOnly bool) (string, error) { + resp := GeneralizedOrderResponse{} request := make(map[string]interface{}) - - if clientRef != "" { - request["client_oid"] = clientRef - } - + request["type"] = "limit" request["price"] = strconv.FormatFloat(price, 'f', -1, 64) request["size"] = strconv.FormatFloat(amount, 'f', -1, 64) request["side"] = side request["product_id"] = productID + if cancelAfter != "" { + request["cancel_after"] = cancelAfter + } + if timeInforce != "" { + request["time_in_foce"] = timeInforce + } + if clientRef != "" { + request["client_oid"] = clientRef + } if stp != "" { request["stp"] = stp } - - type OrderResponse struct { - ID string `json:"id"` + if postOnly { + request["post_only"] = postOnly } - resp := OrderResponse{} - err := g.SendAuthenticatedHTTPRequest("POST", GDAX_ORDERS, request, &resp) + err := g.SendAuthenticatedHTTPRequest("POST", gdaxOrders, request, &resp) if err != nil { return "", err } @@ -307,98 +363,406 @@ func (g *GDAX) PlaceOrder(clientRef string, price, amount float64, side string, return resp.ID, nil } +// PlaceMarketOrder places a new market order. +// Orders can only be placed if the account has sufficient funds. Once an order +// is placed, account funds will be put on hold for the duration of the order. +// How much and which funds are put on hold depends on the order type and +// parameters specified. +// +// GENERAL PARAMS +// clientRef - [optional] Order ID selected by you to identify your order +// side - buy or sell +// productID - A valid product id +// stp - [optional] Self-trade prevention flag +// +// MARKET ORDER PARAMS +// size - [optional]* Desired amount in BTC +// funds [optional]* Desired amount of quote currency to use +// * One of size or funds is required. +func (g *GDAX) PlaceMarketOrder(clientRef string, size, funds float64, side string, productID, stp string) (string, error) { + resp := GeneralizedOrderResponse{} + request := make(map[string]interface{}) + request["side"] = side + request["product_id"] = productID + request["type"] = "market" + + if size != 0 { + request["size"] = strconv.FormatFloat(size, 'f', -1, 64) + } + if funds != 0 { + request["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) + } + if clientRef != "" { + request["client_oid"] = clientRef + } + if stp != "" { + request["stp"] = stp + } + + err := g.SendAuthenticatedHTTPRequest("POST", gdaxOrders, request, &resp) + if err != nil { + return "", err + } + + return resp.ID, nil +} + +// PlaceMarginOrder places a new market order. +// Orders can only be placed if the account has sufficient funds. Once an order +// is placed, account funds will be put on hold for the duration of the order. +// How much and which funds are put on hold depends on the order type and +// parameters specified. +// +// GENERAL PARAMS +// clientRef - [optional] Order ID selected by you to identify your order +// side - buy or sell +// productID - A valid product id +// stp - [optional] Self-trade prevention flag +// +// MARGIN ORDER PARAMS +// size - [optional]* Desired amount in BTC +// funds - [optional]* Desired amount of quote currency to use +func (g *GDAX) PlaceMarginOrder(clientRef string, size, funds float64, side string, productID, stp string) (string, error) { + resp := GeneralizedOrderResponse{} + request := make(map[string]interface{}) + request["side"] = side + request["product_id"] = productID + request["type"] = "margin" + + if size != 0 { + request["size"] = strconv.FormatFloat(size, 'f', -1, 64) + } + if funds != 0 { + request["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) + } + if clientRef != "" { + request["client_oid"] = clientRef + } + if stp != "" { + request["stp"] = stp + } + + err := g.SendAuthenticatedHTTPRequest("POST", gdaxOrders, request, &resp) + if err != nil { + return "", err + } + + return resp.ID, nil +} + +// CancelOrder cancels order by orderID func (g *GDAX) CancelOrder(orderID string) error { - path := fmt.Sprintf("%s/%s", GDAX_ORDERS, orderID) - err := g.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) - if err != nil { - return err - } - return nil + path := fmt.Sprintf("%s/%s", gdaxOrders, orderID) + + return g.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) } -func (g *GDAX) GetOrders(params url.Values) ([]GDAXOrdersResponse, error) { - path := common.EncodeURLValues(GDAX_API_URL+GDAX_ORDERS, params) - resp := []GDAXOrdersResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", common.GetURIPath(path), nil, &resp) - if err != nil { - return nil, err +// CancelAllOrders cancels all open orders on the exchange and returns and array +// of order IDs +// currencyPair - [optional] all orders for a currencyPair string will be +// canceled +func (g *GDAX) CancelAllOrders(currencyPair string) ([]string, error) { + var resp []string + request := make(map[string]interface{}) + + if len(currencyPair) != 0 { + request["product_id"] = currencyPair } - return resp, nil + return resp, g.SendAuthenticatedHTTPRequest("DELETE", gdaxOrders, request, &resp) } -func (g *GDAX) GetOrder(orderID string) (GDAXOrderResponse, error) { - path := fmt.Sprintf("%s/%s", GDAX_ORDERS, orderID) - resp := GDAXOrderResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return resp, err +// GetOrders lists current open orders. Only open or un-settled orders are +// returned. As soon as an order is no longer open and settled, it will no +// longer appear in the default request. +// status - can be a range of "open", "pending", "done" or "active" +// currencyPair - [optional] for example "BTC-USD" +func (g *GDAX) GetOrders(status []string, currencyPair string) ([]GeneralizedOrderResponse, error) { + resp := []GeneralizedOrderResponse{} + params := url.Values{} + + for _, individualStatus := range status { + params.Add("status", individualStatus) } - return resp, nil + if len(currencyPair) != 0 { + params.Set("product_id", currencyPair) + } + + path := common.EncodeURLValues(gdaxAPIURL+gdaxOrders, params) + path = common.GetURIPath(path) + + return resp, + g.SendAuthenticatedHTTPRequest("GET", path[1:], nil, &resp) } -func (g *GDAX) GetFills(params url.Values) ([]GDAXFillResponse, error) { - path := common.EncodeURLValues(GDAX_API_URL+GDAX_FILLS, params) - resp := []GDAXFillResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", common.GetURIPath(path), nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetOrder returns a single order by order id. +func (g *GDAX) GetOrder(orderID string) (GeneralizedOrderResponse, error) { + resp := GeneralizedOrderResponse{} + path := fmt.Sprintf("%s/%s", gdaxOrders, orderID) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (g *GDAX) Transfer(transferType string, amount float64, accountID string) error { +// GetFills returns a list of recent fills +func (g *GDAX) GetFills(orderID, currencyPair string) ([]FillResponse, error) { + resp := []FillResponse{} + params := url.Values{} + + if len(orderID) != 0 { + params.Set("order_id", orderID) + } + if len(currencyPair) != 0 { + params.Set("product_id", currencyPair) + } + if len(params.Get("order_id")) == 0 && len(params.Get("product_id")) == 0 { + return resp, errors.New("no paramaters set") + } + + path := common.EncodeURLValues(gdaxAPIURL+gdaxFills, params) + uri := common.GetURIPath(path) + + return resp, + g.SendAuthenticatedHTTPRequest("GET", uri[1:], nil, &resp) +} + +// GetFundingRecords every order placed with a margin profile that draws funding +// will create a funding record. +// +// status - "outstanding", "settled", or "rejected" +func (g *GDAX) GetFundingRecords(status string) ([]Funding, error) { + resp := []Funding{} + params := url.Values{} + params.Set("status", status) + + path := common.EncodeURLValues(gdaxAPIURL+gdaxFunding, params) + uri := common.GetURIPath(path) + + return resp, + g.SendAuthenticatedHTTPRequest("GET", uri[1:], nil, &resp) +} + +////////////////////////// Not receiving reply from server ///////////////// +// RepayFunding repays the older funding records first +// +// amount - amount of currency to repay +// currency - currency, example USD +// func (g *GDAX) RepayFunding(amount, currency string) (Funding, error) { +// resp := Funding{} +// params := make(map[string]interface{}) +// params["amount"] = amount +// params["currency"] = currency +// +// return resp, +// g.SendAuthenticatedHTTPRequest("POST", gdaxFundingRepay, params, &resp) +// } + +// MarginTransfer sends funds between a standard/default profile and a margin +// profile. +// A deposit will transfer funds from the default profile into the margin +// profile. A withdraw will transfer funds from the margin profile to the +// default profile. Withdraws will fail if they would set your margin ratio +// below the initial margin ratio requirement. +// +// amount - the amount to transfer between the default and margin profile +// transferType - either "deposit" or "withdraw" +// profileID - The id of the margin profile to deposit or withdraw from +// currency - currency to transfer, currently on "BTC" or "USD" +func (g *GDAX) MarginTransfer(amount float64, transferType, profileID, currency string) (MarginTransfer, error) { + resp := MarginTransfer{} request := make(map[string]interface{}) request["type"] = transferType request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) - request["GDAX_account_id"] = accountID + request["currency"] = currency + request["margin_profile_id"] = profileID - err := g.SendAuthenticatedHTTPRequest("POST", GDAX_TRANSFERS, request, nil) - if err != nil { - return err - } - return nil + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxMarginTransfer, request, &resp) } -func (g *GDAX) GetReport(reportType, startDate, endDate string) (GDAXReportResponse, error) { +// GetPosition returns an overview of account profile. +func (g *GDAX) GetPosition() (AccountOverview, error) { + resp := AccountOverview{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxPosition, nil, &resp) +} + +// ClosePosition closes a position and allowing you to repay position as well +// repayOnly - allows the position to be repaid +func (g *GDAX) ClosePosition(repayOnly bool) (AccountOverview, error) { + resp := AccountOverview{} + request := make(map[string]interface{}) + request["repay_only"] = repayOnly + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxPositionClose, request, &resp) +} + +// GetPayMethods returns a full list of payment methods +func (g *GDAX) GetPayMethods() ([]PaymentMethod, error) { + resp := []PaymentMethod{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxPaymentMethod, nil, &resp) +} + +// DepositViaPaymentMethod deposits funds from a payment method. See the Payment +// Methods section for retrieving your payment methods. +// +// amount - The amount to deposit +// currency - The type of currency +// paymentID - ID of the payment method +func (g *GDAX) DepositViaPaymentMethod(amount float64, currency, paymentID string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["payment_method_id"] = paymentID + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxPaymentMethodDeposit, req, &resp) +} + +// DepositViaCoinbase deposits funds from a coinbase account. Move funds between +// a Coinbase account and GDAX trading account within daily limits. Moving +// funds between Coinbase and GDAX is instant and free. See the Coinbase +// Accounts section for retrieving your Coinbase accounts. +// +// amount - The amount to deposit +// currency - The type of currency +// accountID - ID of the coinbase account +func (g *GDAX) DepositViaCoinbase(amount float64, currency, accountID string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["coinbase_account_id"] = accountID + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxDepositCoinbase, req, &resp) +} + +// WithdrawViaPaymentMethod withdraws funds to a payment method +// +// amount - The amount to withdraw +// currency - The type of currency +// paymentID - ID of the payment method +func (g *GDAX) WithdrawViaPaymentMethod(amount float64, currency, paymentID string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["payment_method_id"] = paymentID + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxWithdrawalPaymentMethod, req, &resp) +} + +///////////////////////// NO ROUTE FOUND ERROR //////////////////////////////// +// WithdrawViaCoinbase withdraws funds to a coinbase account. +// +// amount - The amount to withdraw +// currency - The type of currency +// accountID - ID of the coinbase account +// func (g *GDAX) WithdrawViaCoinbase(amount float64, currency, accountID string) (DepositWithdrawalInfo, error) { +// resp := DepositWithdrawalInfo{} +// req := make(map[string]interface{}) +// req["amount"] = amount +// req["currency"] = currency +// req["coinbase_account_id"] = accountID +// +// return resp, +// g.SendAuthenticatedHTTPRequest("POST", gdaxWithdrawalCoinbase, req, &resp) +// } + +// WithdrawCrypto withdraws funds to a crypto address +// +// amount - The amount to withdraw +// currency - The type of currency +// cryptoAddress - A crypto address of the recipient +func (g *GDAX) WithdrawCrypto(amount float64, currency, cryptoAddress string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["crypto_address"] = cryptoAddress + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxWithdrawalCrypto, req, &resp) +} + +// GetCoinbaseAccounts returns a list of coinbase accounts +func (g *GDAX) GetCoinbaseAccounts() ([]CoinbaseAccounts, error) { + resp := []CoinbaseAccounts{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxCoinbaseAccounts, nil, &resp) +} + +// GetReport returns batches of historic information about your account in +// various human and machine readable forms. +// +// reportType - "fills" or "account" +// startDate - Starting date for the report (inclusive) +// endDate - Ending date for the report (inclusive) +// currencyPair - ID of the product to generate a fills report for. +// E.g. BTC-USD. *Required* if type is fills +// accountID - ID of the account to generate an account report for. *Required* +// if type is account +// format - pdf or csv (defualt is pdf) +// email - [optional] Email address to send the report to +func (g *GDAX) GetReport(reportType, startDate, endDate, currencyPair, accountID, format, email string) (Report, error) { + resp := Report{} request := make(map[string]interface{}) request["type"] = reportType request["start_date"] = startDate request["end_date"] = endDate + request["format"] = "pdf" - resp := GDAXReportResponse{} - err := g.SendAuthenticatedHTTPRequest("POST", GDAX_REPORTS, request, &resp) - if err != nil { - return resp, err + if len(currencyPair) != 0 { + request["product_id"] = currencyPair } - return resp, nil + if len(accountID) != 0 { + request["account_id"] = accountID + } + if format == "csv" { + request["format"] = format + } + if len(email) != 0 { + request["email"] = email + } + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxReports, request, &resp) } -func (g *GDAX) GetReportStatus(reportID string) (GDAXReportResponse, error) { - path := fmt.Sprintf("%s/%s", GDAX_REPORTS, reportID) - resp := GDAXReportResponse{} - err := g.SendAuthenticatedHTTPRequest("POST", path, nil, &resp) - if err != nil { - return resp, err - } - return resp, nil +// GetReportStatus once a report request has been accepted for processing, the +// status is available by polling the report resource endpoint. +func (g *GDAX) GetReportStatus(reportID string) (Report, error) { + resp := Report{} + path := fmt.Sprintf("%s/%s", gdaxReports, reportID) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } +// GetTrailingVolume this request will return your 30-day trailing volume for +// all products. +func (g *GDAX) GetTrailingVolume() ([]Volume, error) { + resp := []Volume{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxTrailingVolume, nil, &resp) +} + +// SendAuthenticatedHTTPRequest sends an authenticated HTTP reque func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { if !g.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) } - if g.Nonce.Get() == 0 { - g.Nonce.Set(time.Now().Unix()) - } else { - g.Nonce.Inc() - } - payload := []byte("") if params != nil { payload, err = common.JSONEncode(params) - if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } @@ -408,26 +772,34 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri } } - message := g.Nonce.String() + method + "/" + path + string(payload) + nonce := g.Nonce.Evaluate() + message := nonce + method + "/" + path + string(payload) hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) headers["CB-ACCESS-SIGN"] = common.Base64Encode([]byte(hmac)) - headers["CB-ACCESS-TIMESTAMP"] = g.Nonce.String() + headers["CB-ACCESS-TIMESTAMP"] = nonce headers["CB-ACCESS-KEY"] = g.APIKey headers["CB-ACCESS-PASSPHRASE"] = g.ClientID headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest(method, GDAX_API_URL+path, headers, bytes.NewBuffer(payload)) + resp, err := common.SendHTTPRequest(method, gdaxAPIURL+path, headers, bytes.NewBuffer(payload)) + if err != nil { + return err + } if g.Verbose { log.Printf("Received raw: \n%s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) + type initialResponse struct { + Message string `json:"message"` + } + initialCheck := initialResponse{} - if err != nil { - return errors.New("unable to JSON Unmarshal response") + err = common.JSONDecode([]byte(resp), &initialCheck) + if err == nil && len(initialCheck.Message) != 0 { + return errors.New(initialCheck.Message) } - return nil + return common.JSONDecode([]byte(resp), &result) } diff --git a/exchanges/gdax/gdax_test.go b/exchanges/gdax/gdax_test.go new file mode 100644 index 00000000..fd2ae99a --- /dev/null +++ b/exchanges/gdax/gdax_test.go @@ -0,0 +1,300 @@ +package gdax + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var g GDAX + +// Please supply your APIKeys here for better testing +const ( + apiKey = "" + apiSecret = "" + clientID = "" //passphrase you made at API CREATION +) + +func TestSetDefaults(t *testing.T) { + g.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + gdxConfig, err := cfg.GetExchangeConfig("Bitfinex") + if err != nil { + t.Error("Test Failed - GDAX Setup() init error") + } + + g.Setup(gdxConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if g.GetFee(false) == 0 { + t.Error("Test failed - GetFee() error") + } + if g.GetFee(true) != 0 { + t.Error("Test failed - GetFee() error") + } +} + +func TestGetProducts(t *testing.T) { + t.Parallel() + _, err := g.GetProducts() + if err != nil { + t.Error("Test failed - GetProducts() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := g.GetTicker("BTC-USD") + if err != nil { + t.Error("Test failed - GetTicker() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := g.GetTrades("BTC-USD") + if err != nil { + t.Error("Test failed - GetTrades() error", err) + } +} + +func TestGetHistoricRates(t *testing.T) { + t.Parallel() + _, err := g.GetHistoricRates("BTC-USD", 0, 0, 0) + if err != nil { + t.Error("Test failed - GetHistoricRates() error", err) + } +} + +func TestGetStats(t *testing.T) { + t.Parallel() + _, err := g.GetStats("BTC-USD") + if err != nil { + t.Error("Test failed - GetStats() error", err) + } +} + +func TestGetCurrencies(t *testing.T) { + t.Parallel() + _, err := g.GetCurrencies() + if err != nil { + t.Error("Test failed - GetCurrencies() error", err) + } +} + +func TestGetServerTime(t *testing.T) { + t.Parallel() + _, err := g.GetServerTime() + if err != nil { + t.Error("Test failed - GetServerTime() error", err) + } +} + +func TestGetAccounts(t *testing.T) { + t.Parallel() + _, err := g.GetAccounts() + if err == nil { + t.Error("Test failed - GetAccounts() error", err) + } +} + +func TestGetAccount(t *testing.T) { + t.Parallel() + _, err := g.GetAccount("234cb213-ac6f-4ed8-b7b6-e62512930945") + if err == nil { + t.Error("Test failed - GetAccount() error", err) + } +} + +func TestGetAccountHistory(t *testing.T) { + t.Parallel() + _, err := g.GetAccountHistory("234cb213-ac6f-4ed8-b7b6-e62512930945") + if err == nil { + t.Error("Test failed - GetAccountHistory() error", err) + } +} + +func TestGetHolds(t *testing.T) { + t.Parallel() + _, err := g.GetHolds("234cb213-ac6f-4ed8-b7b6-e62512930945") + if err == nil { + t.Error("Test failed - GetHolds() error", err) + } +} + +func TestPlaceLimitOrder(t *testing.T) { + t.Parallel() + _, err := g.PlaceLimitOrder("", 0, 0, "buy", "", "", "BTC-USD", "", false) + if err == nil { + t.Error("Test failed - PlaceLimitOrder() error", err) + } +} + +func TestPlaceMarketOrder(t *testing.T) { + t.Parallel() + _, err := g.PlaceMarketOrder("", 1, 0, "buy", "BTC-USD", "") + if err == nil { + t.Error("Test failed - PlaceMarketOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + err := g.CancelOrder("1337") + if err == nil { + t.Error("Test failed - CancelOrder() error", err) + } +} + +func TestCancelAllOrders(t *testing.T) { + t.Parallel() + _, err := g.CancelAllOrders("BTC-USD") + if err == nil { + t.Error("Test failed - CancelAllOrders() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := g.GetOrders([]string{"open", "done"}, "BTC-USD") + if err == nil { + t.Error("Test failed - GetOrders() error", err) + } +} + +func TestGetOrder(t *testing.T) { + t.Parallel() + _, err := g.GetOrder("1337") + if err == nil { + t.Error("Test failed - GetOrders() error", err) + } +} + +func TestGetFills(t *testing.T) { + t.Parallel() + _, err := g.GetFills("1337", "BTC-USD") + if err == nil { + t.Error("Test failed - GetFills() error", err) + } + _, err = g.GetFills("", "") + if err == nil { + t.Error("Test failed - GetFills() error", err) + } +} + +func TestGetFundingRecords(t *testing.T) { + t.Parallel() + _, err := g.GetFundingRecords("rejected") + if err == nil { + t.Error("Test failed - GetFundingRecords() error", err) + } +} + +// func TestRepayFunding(t *testing.T) { +// g.Verbose = true +// _, err := g.RepayFunding("1", "BTC") +// if err != nil { +// t.Error("Test failed - RepayFunding() error", err) +// } +// } + +func TestMarginTransfer(t *testing.T) { //invalid sig issue + t.Parallel() + _, err := g.MarginTransfer(1, "withdraw", "45fa9e3b-00ba-4631-b907-8a98cbdf21be", "BTC") + if err == nil { + t.Error("Test failed - MarginTransfer() error", err) + } +} + +func TestGetPosition(t *testing.T) { + t.Parallel() + _, err := g.GetPosition() + if err == nil { + t.Error("Test failed - GetPosition() error", err) + } +} + +func TestClosePosition(t *testing.T) { + t.Parallel() + _, err := g.ClosePosition(false) + if err == nil { + t.Error("Test failed - ClosePosition() error", err) + } +} + +func TestGetPayMethods(t *testing.T) { + t.Parallel() + _, err := g.GetPayMethods() + if err == nil { + t.Error("Test failed - GetPayMethods() error", err) + } +} + +func TestDepositViaPaymentMethod(t *testing.T) { + t.Parallel() + _, err := g.DepositViaPaymentMethod(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - DepositViaPaymentMethod() error", err) + } +} + +func TestDepositViaCoinbase(t *testing.T) { + t.Parallel() + _, err := g.DepositViaCoinbase(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - DepositViaCoinbase() error", err) + } +} + +func TestWithdrawViaPaymentMethod(t *testing.T) { + t.Parallel() + _, err := g.WithdrawViaPaymentMethod(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - WithdrawViaPaymentMethod() error", err) + } +} + +// func TestWithdrawViaCoinbase(t *testing.T) { // No Route found error +// _, err := g.WithdrawViaCoinbase(1, "BTC", "c13cd0fc-72ca-55e9-843b-b84ef628c198") +// if err != nil { +// t.Error("Test failed - WithdrawViaCoinbase() error", err) +// } +// } + +func TestWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := g.WithdrawCrypto(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - WithdrawViaCoinbase() error", err) + } +} + +func TestGetCoinbaseAccounts(t *testing.T) { + t.Parallel() + _, err := g.GetCoinbaseAccounts() + if err == nil { + t.Error("Test failed - GetCoinbaseAccounts() error", err) + } +} + +func TestGetReportStatus(t *testing.T) { + t.Parallel() + _, err := g.GetReportStatus("1337") + if err == nil { + t.Error("Test failed - GetReportStatus() error", err) + } +} + +func TestGetTrailingVolume(t *testing.T) { + t.Parallel() + _, err := g.GetTrailingVolume() + if err == nil { + t.Error("Test failed - GetTrailingVolume() error", err) + } +} diff --git a/exchanges/gdax/gdax_types.go b/exchanges/gdax/gdax_types.go index c14894c9..76d0143d 100644 --- a/exchanges/gdax/gdax_types.go +++ b/exchanges/gdax/gdax_types.go @@ -1,13 +1,7 @@ package gdax -type GDAXTicker struct { - TradeID int64 `json:"trade_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time string `json:"time"` -} - -type GDAXProduct struct { +// Product holds product information +type Product struct { ID string `json:"id"` BaseCurrency string `json:"base_currency"` QuoteCurrency string `json:"quote_currency"` @@ -17,37 +11,16 @@ type GDAXProduct struct { DisplayName string `json:"string"` } -type GDAXOrderL1L2 struct { - Price float64 - Amount float64 - NumOrders float64 +// Ticker holds basic ticker information +type Ticker struct { + TradeID int64 `json:"trade_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time string `json:"time"` } -type GDAXOrderL3 struct { - Price float64 - Amount float64 - OrderID string -} - -type GDAXOrderbookL1L2 struct { - Sequence int64 `json:"sequence"` - Bids []GDAXOrderL1L2 `json:"bids"` - Asks []GDAXOrderL1L2 `json:"asks"` -} - -type GDAXOrderbookL3 struct { - Sequence int64 `json:"sequence"` - Bids []GDAXOrderL3 `json:"bids"` - Asks []GDAXOrderL3 `json:"asks"` -} - -type GDAXOrderbookResponse struct { - Sequence int64 `json:"sequence"` - Bids [][]interface{} `json:"bids"` - Asks [][]interface{} `json:"asks"` -} - -type GDAXTrade struct { +// Trade holds executed trade information +type Trade struct { TradeID int64 `json:"trade_id"` Price float64 `json:"price,string"` Size float64 `json:"size,string"` @@ -55,37 +28,52 @@ type GDAXTrade struct { Side string `json:"side"` } -type GDAXStats struct { +// History holds historic rate information +type History struct { + Time int64 `json:"time"` + Low float64 `json:"low"` + High float64 `json:"high"` + Open float64 `json:"open"` + Close float64 `json:"close"` + Volume float64 `json:"volume"` +} + +// Stats holds last 24 hr data for gdax +type Stats struct { Open float64 `json:"open,string"` High float64 `json:"high,string"` Low float64 `json:"low,string"` Volume float64 `json:"volume,string"` } -type GDAXCurrency struct { +// Currency holds singular currency product information +type Currency struct { ID string Name string MinSize float64 `json:"min_size,string"` } -type GDAXHistory struct { - Time int64 - Low float64 - High float64 - Open float64 - Close float64 - Volume float64 +// ServerTime holds current requested server time information +type ServerTime struct { + ISO string `json:"iso"` + Epoch float64 `json:"epoch"` } -type GDAXAccountResponse struct { - ID string `json:"id"` - Balance float64 `json:"balance,string"` - Hold float64 `json:"hold,string"` - Available float64 `json:"available,string"` - Currency string `json:"currency"` +// AccountResponse holds the details for the trading accounts +type AccountResponse struct { + ID string `json:"id"` + Currency string `json:"currency"` + Balance float64 `json:"balance,string"` + Available float64 `json:"available,string"` + Hold float64 `json:"hold,string"` + ProfileID string `json:"profile_id"` + MarginEnabled bool `json:"margin_enabled"` + FundedAmount float64 `json:"funded_amount,string"` + DefaultAmount float64 `json:"default_amount,string"` } -type GDAXAccountLedgerResponse struct { +// AccountLedgerResponse holds account history information +type AccountLedgerResponse struct { ID string `json:"id"` CreatedAt string `json:"created_at"` Amount float64 `json:"amount,string"` @@ -94,7 +82,8 @@ type GDAXAccountLedgerResponse struct { Details interface{} `json:"details"` } -type GDAXAccountHolds struct { +// AccountHolds contains the hold information about an account +type AccountHolds struct { ID string `json:"id"` AccountID string `json:"account_id"` CreatedAt string `json:"created_at"` @@ -104,48 +93,182 @@ type GDAXAccountHolds struct { Reference string `json:"ref"` } -type GDAXOrdersResponse struct { - ID string `json:"id"` - Size float64 `json:"size,string"` - Price float64 `json:"price,string"` - ProductID string `json:"product_id"` - Status string `json:"status"` - FilledSize float64 `json:"filled_size,string"` - FillFees float64 `json:"fill_fees,string"` - Settled bool `json:"settled"` - Side string `json:"side"` - CreatedAt string `json:"created_at"` +// GeneralizedOrderResponse is the generalized return type across order +// placement and information collation +type GeneralizedOrderResponse struct { + ID string `json:"id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + ProductID string `json:"product_id"` + Side string `json:"side"` + Stp string `json:"stp"` + Type string `json:"type"` + TimeInForce string `json:"time_in_force"` + PostOnly bool `json:"post_only"` + CreatedAt string `json:"created_at"` + FillFees float64 `json:"fill_fees,string"` + FilledSize float64 `json:"filled_size,string"` + ExecutedValue float64 `json:"executed_value,string"` + Status string `json:"status"` + Settled bool `json:"settled"` + Funds float64 `json:"funds,string"` + SpecifiedFunds float64 `json:"specified_funds,string"` + DoneReason string `json:"done_reason"` + DoneAt string `json:"done_at"` } -type GDAXOrderResponse struct { - ID string `json:"id"` - Size float64 `json:"size,string"` - Price float64 `json:"price,string"` - DoneReason string `json:"done_reason"` - Status string `json:"status"` - Settled bool `json:"settled"` - FilledSize float64 `json:"filled_size,string"` - ProductID string `json:"product_id"` - FillFees float64 `json:"fill_fees,string"` - Side string `json:"side"` - CreatedAt string `json:"created_at"` - DoneAt string `json:"done_at"` +// Funding holds funding data +type Funding struct { + ID string `json:"id"` + OrderID string `json:"order_id"` + ProfileID string `json:"profile_id"` + Amount float64 `json:"amount,string"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + Currency string `json:"currency"` + RepaidAmount float64 `json:"repaid_amount"` + DefaultAmount float64 `json:"default_amount,string"` + RepaidDefault bool `json:"repaid_default"` } -type GDAXFillResponse struct { - TradeID int `json:"trade_id"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - OrderID string `json:"order_id"` - CreatedAt string `json:"created_at"` - Liquidity string `json:"liquidity"` - Fee float64 `json:"fee,string"` - Settled bool `json:"settled"` - Side string `json:"side"` +// MarginTransfer holds margin transfer details +type MarginTransfer struct { + CreatedAt string `json:"created_at"` + ID string `json:"id"` + UserID string `json:"user_id"` + ProfileID string `json:"profile_id"` + MarginProfileID string `json:"margin_profile_id"` + Type string `json:"type"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + MarginAccountID string `json:"margin_account_id"` + MarginProductID string `json:"margin_product_id"` + Status string `json:"status"` + Nonce int `json:"nonce"` } -type GDAXReportResponse struct { +// AccountOverview holds account information returned from position +type AccountOverview struct { + Status string `json:"status"` + Funding struct { + MaxFundingValue float64 `json:"max_funding_value,string"` + FundingValue float64 `json:"funding_value,string"` + OldestOutstanding struct { + ID string `json:"id"` + OrderID string `json:"order_id"` + CreatedAt string `json:"created_at"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + Amount float64 `json:"amount,string"` + } `json:"oldest_outstanding"` + } `json:"funding"` + Accounts struct { + LTC Account `json:"LTC"` + ETH Account `json:"ETH"` + USD Account `json:"USD"` + BTC Account `json:"BTC"` + } `json:"accounts"` + MarginCall struct { + Active bool `json:"active"` + Price float64 `json:"price,string"` + Side string `json:"side"` + Size float64 `json:"size,string"` + Funds float64 `json:"funds,string"` + } `json:"margin_call"` + UserID string `json:"user_id"` + ProfileID string `json:"profile_id"` + Position struct { + Type string `json:"type"` + Size float64 `json:"size,string"` + Complement float64 `json:"complement,string"` + MaxSize float64 `json:"max_size,string"` + } `json:"position"` + ProductID string `json:"product_id"` +} + +// Account is a sub-type for account overview +type Account struct { + ID string `json:"id"` + Balance float64 `json:"balance,string"` + Hold float64 `json:"hold,string"` + FundedAmount float64 `json:"funded_amount,string"` + DefaultAmount float64 `json:"default_amount,string"` +} + +// PaymentMethod holds payment method information +type PaymentMethod struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Currency string `json:"currency"` + PrimaryBuy bool `json:"primary_buy"` + PrimarySell bool `json:"primary_sell"` + AllowBuy bool `json:"allow_buy"` + AllowSell bool `json:"allow_sell"` + AllowDeposits bool `json:"allow_deposits"` + AllowWithdraw bool `json:"allow_withdraw"` + Limits struct { + Buy []LimitInfo `json:"buy"` + InstantBuy []LimitInfo `json:"instant_buy"` + Sell []LimitInfo `json:"sell"` + Deposit []LimitInfo `json:"deposit"` + } `json:"limits"` +} + +// LimitInfo is a sub-type for payment method +type LimitInfo struct { + PeriodInDays int `json:"period_in_days"` + Total struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } `json:"total"` +} + +// DepositWithdrawalInfo holds returned deposit information +type DepositWithdrawalInfo struct { + ID string `json:"id"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + PayoutAt string `json:"payout_at"` +} + +// CoinbaseAccounts holds coinbase account information +type CoinbaseAccounts struct { + ID string `json:"id"` + Name string `json:"name"` + Balance float64 `json:"balance,string"` + Currency string `json:"currency"` + Type string `json:"type"` + Primary bool `json:"primary"` + Active bool `json:"active"` + WireDepositInformation struct { + AccountNumber string `json:"account_number"` + RoutingNumber string `json:"routing_number"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountry struct { + Code string `json:"code"` + Name string `json:"name"` + } `json:"bank_country"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` + } `json:"wire_deposit_information"` + SepaDepositInformation struct { + Iban string `json:"iban"` + Swift string `json:"swift"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountryName string `json:"bank_country_name"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` + } `json:"sep_deposit_information"` +} + +// Report holds historical information +type Report struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` @@ -159,12 +282,71 @@ type GDAXReportResponse struct { } `json:"params"` } -type GDAXWebsocketSubscribe struct { +// Volume type contains trailing volume information +type Volume struct { + ProductID string `json:"product_id"` + ExchangeVolume float64 `json:"exchange_volume,string"` + Volume float64 `json:"volume,string"` + RecordedAt string `json:"recorded_at"` +} + +// OrderL1L2 is a type used in layer conversion +type OrderL1L2 struct { + Price float64 + Amount float64 + NumOrders float64 +} + +// OrderL3 is a type used in layer conversion +type OrderL3 struct { + Price float64 + Amount float64 + OrderID string +} + +// OrderbookL1L2 holds level 1 and 2 order book information +type OrderbookL1L2 struct { + Sequence int64 `json:"sequence"` + Bids []OrderL1L2 `json:"bids"` + Asks []OrderL1L2 `json:"asks"` +} + +// OrderbookL3 holds level 3 order book information +type OrderbookL3 struct { + Sequence int64 `json:"sequence"` + Bids []OrderL3 `json:"bids"` + Asks []OrderL3 `json:"asks"` +} + +// OrderbookResponse is a generalized response for order books +type OrderbookResponse struct { + Sequence int64 `json:"sequence"` + Bids [][]interface{} `json:"bids"` + Asks [][]interface{} `json:"asks"` +} + +// FillResponse contains fill information from the exchange +type FillResponse struct { + TradeID int `json:"trade_id"` + ProductID string `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + OrderID string `json:"order_id"` + CreatedAt string `json:"created_at"` + Liquidity string `json:"liquidity"` + Fee float64 `json:"fee,string"` + Settled bool `json:"settled"` + Side string `json:"side"` +} + +// WebsocketSubscribe takes in subscription information +type WebsocketSubscribe struct { Type string `json:"type"` ProductID string `json:"product_id"` } -type GDAXWebsocketReceived struct { +// WebsocketReceived holds websocket received values +type WebsocketReceived struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` @@ -174,7 +356,8 @@ type GDAXWebsocketReceived struct { Side string `json:"side"` } -type GDAXWebsocketOpen struct { +// WebsocketOpen collates open orders +type WebsocketOpen struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` @@ -184,7 +367,8 @@ type GDAXWebsocketOpen struct { Side string `json:"side"` } -type GDAXWebsocketDone struct { +// WebsocketDone holds finished order information +type WebsocketDone struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` @@ -195,7 +379,8 @@ type GDAXWebsocketDone struct { RemainingSize float64 `json:"remaining_size,string"` } -type GDAXWebsocketMatch struct { +// WebsocketMatch holds match information +type WebsocketMatch struct { Type string `json:"type"` TradeID int `json:"trade_id"` Sequence int `json:"sequence"` @@ -207,7 +392,8 @@ type GDAXWebsocketMatch struct { Side string `json:"side"` } -type GDAXWebsocketChange struct { +// WebsocketChange holds change information +type WebsocketChange struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` diff --git a/exchanges/gdax/gdax_websocket.go b/exchanges/gdax/gdax_websocket.go index f2656451..423b2dcb 100644 --- a/exchanges/gdax/gdax_websocket.go +++ b/exchanges/gdax/gdax_websocket.go @@ -13,7 +13,7 @@ const ( ) func (g *GDAX) WebsocketSubscribe(product string, conn *websocket.Conn) error { - subscribe := GDAXWebsocketSubscribe{"subscribe", product} + subscribe := WebsocketSubscribe{"subscribe", product} json, err := common.JSONEncode(subscribe) if err != nil { return err @@ -82,35 +82,35 @@ func (g *GDAX) WebsocketClient() { log.Println(string(resp)) break case "received": - received := GDAXWebsocketReceived{} + received := WebsocketReceived{} err := common.JSONDecode(resp, &received) if err != nil { log.Println(err) continue } case "open": - open := GDAXWebsocketOpen{} + open := WebsocketOpen{} err := common.JSONDecode(resp, &open) if err != nil { log.Println(err) continue } case "done": - done := GDAXWebsocketDone{} + done := WebsocketDone{} err := common.JSONDecode(resp, &done) if err != nil { log.Println(err) continue } case "match": - match := GDAXWebsocketMatch{} + match := WebsocketMatch{} err := common.JSONDecode(resp, &match) if err != nil { log.Println(err) continue } case "change": - change := GDAXWebsocketChange{} + change := WebsocketChange{} err := common.JSONDecode(resp, &change) if err != nil { log.Println(err) diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 8ad7b404..5e6dafc3 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -113,7 +113,7 @@ func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook return orderBook, err } - obNew := orderbookNew.(GDAXOrderbookL1L2) + obNew := orderbookNew.(OrderbookL1L2) for x := range obNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go index 79ff8751..e3ace537 100644 --- a/exchanges/nonce/nonce.go +++ b/exchanges/nonce/nonce.go @@ -3,6 +3,7 @@ package nonce import ( "strconv" "sync" + "time" ) // Nonce struct holds the nonce value @@ -47,3 +48,15 @@ func (n *Nonce) String() string { n.mtx.Unlock() return result } + +// Evaluate returns a nonce while evaluating in a single locked call +func (n *Nonce) Evaluate() string { + n.mtx.Lock() + defer n.mtx.Unlock() + if n.n == 0 { + n.n = time.Now().Unix() + return strconv.FormatInt(n.n, 10) + } + n.n = n.n + 1 + return strconv.FormatInt(n.n, 10) +} From dae90a2eaa109648bdb85f8298d805e00ad4e974 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 7 Sep 2017 19:01:51 +1000 Subject: [PATCH 2/5] Fixed linter issues, increased code cov & expanded functionality in Gemini. --- exchanges/coinut/coinut_test.go | 63 +++-- exchanges/gemini/gemini.go | 387 +++++++++++++++++++++---------- exchanges/gemini/gemini_test.go | 224 ++++++++++++++++++ exchanges/gemini/gemini_types.go | 211 +++++++++++------ 4 files changed, 661 insertions(+), 224 deletions(-) create mode 100644 exchanges/gemini/gemini_test.go diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 3d3cdb44..9fead096 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -1,37 +1,32 @@ package coinut -import ( - "testing" - - "github.com/thrasher-/gocryptotrader/config" -) - -const ( - apiKey = "" - apiSecret = "" -) - -var c COINUT - -func TestSetDefaults(t *testing.T) { - c.SetDefaults() -} - -func TestSetup(t *testing.T) { - exch := config.ExchangeConfig{} - c.Setup(exch) - - exch.Enabled = true - exch.APIKey = apiKey - exch.APISecret = apiSecret - c.Setup(exch) -} - -// func TestGetInstruments(t *testing.T) { -// c.Verbose = true -// resp, err := c.GetInstruments() -// if err == nil { -// t.Error("Test failed - GetInstruments() error", err) -// } -// log.Println(resp) +// +// const ( +// apiKey = "" +// apiSecret = "" +// ) +// +// var c COINUT +// +// func TestSetDefaults(t *testing.T) { +// c.SetDefaults() // } +// +// func TestSetup(t *testing.T) { +// exch := config.ExchangeConfig{} +// c.Setup(exch) +// +// exch.Enabled = true +// exch.APIKey = apiKey +// exch.APISecret = apiSecret +// c.Setup(exch) +// } +// +// // func TestGetInstruments(t *testing.T) { +// // c.Verbose = true +// // resp, err := c.GetInstruments() +// // if err == nil { +// // t.Error("Test failed - GetInstruments() error", err) +// // } +// // log.Println(resp) +// // } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 35766c8b..7f78eac6 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -16,30 +16,88 @@ import ( ) const ( - GEMINI_API_URL = "https://api.gemini.com" - GEMINI_API_VERSION = "1" + geminiAPIURL = "https://api.gemini.com" + geminiSandboxAPIURL = "https://api.sandbox.gemini.com" + geminiAPIVersion = "1" - GEMINI_SYMBOLS = "symbols" - GEMINI_TICKER = "pubticker" - GEMINI_AUCTION = "auction" - GEMINI_AUCTION_HISTORY = "history" - GEMINI_ORDERBOOK = "book" - GEMINI_TRADES = "trades" - GEMINI_ORDERS = "orders" - GEMINI_ORDER_NEW = "order/new" - GEMINI_ORDER_CANCEL = "order/cancel" - GEMINI_ORDER_CANCEL_SESSION = "order/cancel/session" - GEMINI_ORDER_CANCEL_ALL = "order/cancel/all" - GEMINI_ORDER_STATUS = "order/status" - GEMINI_MYTRADES = "mytrades" - GEMINI_BALANCES = "balances" - GEMINI_HEARTBEAT = "heartbeat" + geminiSymbols = "symbols" + geminiTicker = "pubticker" + geminiAuction = "auction" + geminiAuctionHistory = "history" + geminiOrderbook = "book" + geminiTrades = "trades" + geminiOrders = "orders" + geminiOrderNew = "order/new" + geminiOrderCancel = "order/cancel" + geminiOrderCancelSession = "order/cancel/session" + geminiOrderCancelAll = "order/cancel/all" + geminiOrderStatus = "order/status" + geminiMyTrades = "mytrades" + geminiBalances = "balances" + geminiTradeVolume = "tradevolume" + geminiDeposit = "deposit" + geminiNewAddress = "newAddress" + geminiWithdraw = "withdraw/" + geminiHeartbeat = "heartbeat" + + // rate limits per minute + geminiPublicRate = 120 + geminiPrivateRate = 600 + + // rates limits per second + geminiPublicRateSec = 1 + geminiPrivateRateSec = 5 + + // Too many requests returns this + geminiRateError = "429" + + // Assigned API key roles on creation + geminiRoleTrader = "trader" + geminiRoleFundManager = "fundmanager" ) +// SessionID map guides +var ( + sessionAPIKey map[int]string // map[sessionID]APIKEY + sessionAPISecret map[int]string // map[sessionID]APIKEY + sessionRole map[string]string // map[sessionID]Roles + sessionHeartbeat map[int]bool // map[sessionID]RequiresHeartBeat + IsSession bool +) + +// Gemini is the overarching type across the Gemini package, create multiple +// instances with differing APIkeys for segregation of roles for authenticated +// requests & sessions by appending the session function, if sandbox test is +// needed append the sandbox function as well. type Gemini struct { exchange.Base } +// AddSession adds a new session to the gemini base +func (g *Gemini) AddSession(sessionID int, apiKey, apiSecret, role string, needsHeartbeat bool) error { + if sessionAPIKey == nil { + IsSession = true + sessionAPIKey = make(map[int]string) + sessionAPISecret = make(map[int]string) + sessionRole = make(map[string]string) + sessionHeartbeat = make(map[int]bool) + } + _, ok := sessionAPIKey[sessionID] + if ok { + return errors.New("sessionID already being used") + } + + sessionAPIKey[sessionID] = apiKey + sessionAPISecret[sessionID] = apiSecret + sessionRole[apiKey] = role + sessionHeartbeat[sessionID] = needsHeartbeat + + return nil +} + +//return session function? + +// SetDefaults sets package defaults for gemini exchange func (g *Gemini) SetDefaults() { g.Name = "Gemini" g.Enabled = false @@ -53,6 +111,7 @@ func (g *Gemini) SetDefaults() { g.AssetTypes = []string{ticker.Spot} } +// Setup sets exchange configuration paramaters func (g *Gemini) Setup(exch config.ExchangeConfig) { if !exch.Enabled { g.SetEnabled(false) @@ -77,7 +136,35 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { } } -func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { +// Session is a session manager for differing APIKeys and roles, use this for all function +// calls in this package +func (g *Gemini) Session(sessionID int) *Gemini { + g.APIUrl = geminiAPIURL + _, ok := sessionAPIKey[sessionID] + if !ok { + return nil + } + g.APIKey = sessionAPIKey[sessionID] + g.APISecret = sessionAPISecret[sessionID] + return g +} + +// Sandbox diverts the apiURL to the sandbox API for testing purposes +func (g *Gemini) Sandbox() *Gemini { + g.APIUrl = geminiSandboxAPIURL + return g +} + +// GetSymbols returns all available symbols for trading +func (g *Gemini) GetSymbols() ([]string, error) { + symbols := []string{} + path := fmt.Sprintf("%s/v%s/%s", geminiAPIURL, geminiAPIVersion, geminiSymbols) + + return symbols, common.SendHTTPGetRequest(path, true, &symbols) +} + +// GetTicker returns information about recent trading activity for the symbol +func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { type TickerResponse struct { Ask float64 `json:"ask,string"` @@ -86,9 +173,9 @@ func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { Volume map[string]interface{} } - ticker := GeminiTicker{} + ticker := Ticker{} resp := TickerResponse{} - path := fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_TICKER, currency) + path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTicker, currencyPair) err := common.SendHTTPGetRequest(path, true, &resp) if err != nil { @@ -99,7 +186,7 @@ func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { ticker.Bid = resp.Bid ticker.Last = resp.Last - ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currency[0:3]].(string), 64) + ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currencyPair[0:3]].(string), 64) ticker.Volume.USD, _ = strconv.ParseFloat(resp.Volume["USD"].(string), 64) time, _ := resp.Volume["timestamp"].(float64) @@ -108,59 +195,77 @@ func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { return ticker, nil } -func (g *Gemini) GetSymbols() ([]string, error) { - symbols := []string{} - path := fmt.Sprintf("%s/v%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_SYMBOLS) - err := common.SendHTTPGetRequest(path, true, &symbols) - if err != nil { - return nil, err - } - return symbols, nil +// GetOrderbook returns the current order book, as two arrays, one of bids, and +// one of asks +// +// params - limit_bids or limit_asks [OPTIONAL] default 50, 0 returns all Values +// Type is an integer ie "params.Set("limit_asks", 30)" +func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiOrderbook, currencyPair), params) + orderbook := Orderbook{} + + return orderbook, common.SendHTTPGetRequest(path, true, &orderbook) } -func (g *Gemini) GetAuction(currency string) (GeminiAuction, error) { - path := fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_AUCTION, currency) - auction := GeminiAuction{} - err := common.SendHTTPGetRequest(path, true, &auction) - if err != nil { - return auction, err - } - return auction, nil +// GetTrades eturn the trades that have executed since the specified timestamp. +// Timestamps are either seconds or milliseconds since the epoch (1970-01-01). +// +// currencyPair - example "btcusd" +// params -- +// since, timestamp [optional] +// limit_trades integer Optional. The maximum number of trades to return. +// include_breaks boolean Optional. Whether to display broken trades. False by +// default. Can be '1' or 'true' to activate +func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTrades, currencyPair), params) + trades := []Trade{} + + return trades, common.SendHTTPGetRequest(path, true, &trades) } -func (g *Gemini) GetAuctionHistory(currency string, params url.Values) ([]GeminiAuctionHistory, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_AUCTION, currency, GEMINI_AUCTION_HISTORY), params) - auctionHist := []GeminiAuctionHistory{} - err := common.SendHTTPGetRequest(path, true, &auctionHist) - if err != nil { - return nil, err - } - return auctionHist, nil +// GetAuction returns auction infomation +func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { + path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair) + auction := Auction{} + + return auction, common.SendHTTPGetRequest(path, true, &auction) } -func (g *Gemini) GetOrderbook(currency string, params url.Values) (GeminiOrderbook, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_ORDERBOOK, currency), params) - orderbook := GeminiOrderbook{} - err := common.SendHTTPGetRequest(path, true, &orderbook) - if err != nil { - return GeminiOrderbook{}, err - } +// GetAuctionHistory returns the auction events, optionally including +// publications of indicative prices, since the specific timestamp. +// +// currencyPair - example "btcusd" +// params -- [optional] +// since - [timestamp] Only returns auction events after the specified +// timestamp. +// limit_auction_results - [integer] The maximum number of auction +// events to return. +// include_indicative - [bool] Whether to include publication of +// indicative prices and quantities. +func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]AuctionHistory, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) + auctionHist := []AuctionHistory{} - return orderbook, nil + return auctionHist, common.SendHTTPGetRequest(path, true, &auctionHist) } -func (g *Gemini) GetTrades(currency string, params url.Values) ([]GeminiTrade, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_TRADES, currency), params) - trades := []GeminiTrade{} - err := common.SendHTTPGetRequest(path, true, &trades) - if err != nil { - return []GeminiTrade{}, err +func (g *Gemini) isCorrectSession(role string) error { + if !IsSession { + return errors.New("session not set") } - - return trades, nil + if sessionRole[g.APIKey] != role { + return errors.New("incorrect role for APIKEY cannot use this function") + } + return nil } +// NewOrder Only limit orders are supported through the API at present. +// returns order ID if successful func (g *Gemini) NewOrder(symbol string, amount, price float64, side, orderType string) (int64, error) { + if err := g.isCorrectSession(geminiRoleTrader); err != nil { + return 0, err + } + request := make(map[string]interface{}) request["symbol"] = symbol request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) @@ -168,96 +273,127 @@ func (g *Gemini) NewOrder(symbol string, amount, price float64, side, orderType request["side"] = side request["type"] = orderType - response := GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_NEW, request, &response) + response := Order{} + err := g.SendAuthenticatedHTTPRequest("POST", geminiOrderNew, request, &response) if err != nil { return 0, err } return response.OrderID, nil } -func (g *Gemini) CancelOrder(OrderID int64) (GeminiOrder, error) { +// CancelOrder will cancel an order. If the order is already canceled, the +// message will succeed but have no effect. +func (g *Gemini) CancelOrder(OrderID int64) (Order, error) { request := make(map[string]interface{}) request["order_id"] = OrderID - response := GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_CANCEL, request, &response) + response := Order{} + err := g.SendAuthenticatedHTTPRequest("POST", geminiOrderCancel, request, &response) if err != nil { - return GeminiOrder{}, err + return Order{}, err } return response, nil } -func (g *Gemini) CancelOrders(sessions bool) ([]GeminiOrderResult, error) { - response := []GeminiOrderResult{} - path := GEMINI_ORDER_CANCEL_ALL - if sessions { - path = GEMINI_ORDER_CANCEL_SESSION +// CancelOrders will cancel all outstanding orders created by all sessions owned +// by this account, including interactive orders placed through the UI. If +// sessions = true will only cancel the order that is called on this session +// asssociated with the APIKEY +func (g *Gemini) CancelOrders(CancelBySession bool) (OrderResult, error) { + response := OrderResult{} + path := geminiOrderCancelAll + if CancelBySession { + path = geminiOrderCancelSession } - err := g.SendAuthenticatedHTTPRequest("POST", path, nil, &response) - if err != nil { - return nil, err - } - return response, nil + + return response, g.SendAuthenticatedHTTPRequest("POST", path, nil, &response) } -func (g *Gemini) GetOrderStatus(orderID int64) (GeminiOrder, error) { +// GetOrderStatus returns the status for an order +func (g *Gemini) GetOrderStatus(orderID int64) (Order, error) { request := make(map[string]interface{}) request["order_id"] = orderID - response := GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_STATUS, request, &response) - if err != nil { - return GeminiOrder{}, err - } - return response, nil + response := Order{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiOrderStatus, request, &response) } -func (g *Gemini) GetOrders() ([]GeminiOrder, error) { - response := []GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDERS, nil, &response) - if err != nil { - return nil, err - } - return response, nil +// GetOrders returns active orders in the market +func (g *Gemini) GetOrders() ([]Order, error) { + response := []Order{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiOrders, nil, &response) } -func (g *Gemini) GetTradeHistory(symbol string, timestamp int64) ([]GeminiTradeHistory, error) { +// GetTradeHistory returns an array of trades that have been on the exchange +// +// currencyPair - example "btcusd" +// timestamp - [optional] Only return trades on or after this timestamp. +func (g *Gemini) GetTradeHistory(currencyPair string, timestamp int64) ([]TradeHistory, error) { + response := []TradeHistory{} request := make(map[string]interface{}) - request["symbol"] = symbol - request["timestamp"] = timestamp + request["symbol"] = currencyPair - response := []GeminiTradeHistory{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_MYTRADES, request, &response) - if err != nil { - return nil, err + if timestamp != 0 { + request["timestamp"] = timestamp } - return response, nil + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiMyTrades, request, &response) } -func (g *Gemini) GetBalances() ([]GeminiBalance, error) { - response := []GeminiBalance{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_BALANCES, nil, &response) - if err != nil { - return nil, err - } - return response, nil +// GetTradeVolume returns a multi-arrayed volume response +func (g *Gemini) GetTradeVolume() ([][]TradeVolume, error) { + response := [][]TradeVolume{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiTradeVolume, nil, &response) } -func (g *Gemini) PostHeartbeat() (bool, error) { +// GetBalances returns available balances in the supported currencies +func (g *Gemini) GetBalances() ([]Balance, error) { + response := []Balance{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiBalances, nil, &response) +} + +// GetDepositAddress returns a deposit address +func (g *Gemini) GetDepositAddress(depositAddlabel, currency string) (DepositAddress, error) { + response := DepositAddress{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiDeposit+"/"+currency+"/"+geminiNewAddress, nil, &response) +} + +// WithdrawCrypto withdraws crypto currency to a whitelisted address +func (g *Gemini) WithdrawCrypto(address, currency string, amount float64) (WithdrawelAddress, error) { + response := WithdrawelAddress{} + request := make(map[string]interface{}) + request["address"] = address + request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiWithdraw+currency, nil, &response) +} + +// PostHeartbeat sends a maintenance heartbeat to the exchange for all heartbeat +// maintaned sessions +func (g *Gemini) PostHeartbeat() (string, error) { type Response struct { - Result bool `json:"result"` + Result string `json:"result"` } - response := Response{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_HEARTBEAT, nil, &response) - if err != nil { - return false, err - } - return response.Result, nil + return response.Result, + g.SendAuthenticatedHTTPRequest("POST", geminiHeartbeat, nil, &response) } +// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the +// exchange and returns an error func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { if !g.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) @@ -269,8 +405,9 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st g.Nonce.Inc() } + headers := make(map[string]string) request := make(map[string]interface{}) - request["request"] = fmt.Sprintf("/v%s/%s", GEMINI_API_VERSION, path) + request["request"] = fmt.Sprintf("/v%s/%s", geminiAPIVersion, path) request["nonce"] = g.Nonce.Get() if params != nil { @@ -280,7 +417,6 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st } PayloadJSON, err := common.JSONEncode(request) - if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } @@ -291,21 +427,32 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st PayloadBase64 := common.Base64Encode(PayloadJSON) hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(g.APISecret)) - headers := make(map[string]string) + headers["X-GEMINI-APIKEY"] = g.APIKey headers["X-GEMINI-PAYLOAD"] = PayloadBase64 headers["X-GEMINI-SIGNATURE"] = common.HexEncodeToString(hmac) - resp, err := common.SendHTTPRequest(method, GEMINI_API_URL+path, headers, strings.NewReader("")) + resp, err := common.SendHTTPRequest(method, g.APIUrl+"/v1/"+path, headers, strings.NewReader("")) + if err != nil { + return err + } if g.Verbose { log.Printf("Received raw: \n%s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) + captureErr := ErrorCapture{} + if err = common.JSONDecode([]byte(resp), &captureErr); err == nil { + if len(captureErr.Message) != 0 || len(captureErr.Result) != 0 || len(captureErr.Reason) != 0 { + if captureErr.Result != "ok" { + return errors.New(captureErr.Message) + } + } + } + err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("unable to JSON Unmarshal response") + return err } return nil diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go new file mode 100644 index 00000000..4701f52a --- /dev/null +++ b/exchanges/gemini/gemini_test.go @@ -0,0 +1,224 @@ +package gemini + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var ( + g Gemini +) + +// Please enter sandbox API keys & assigned roles for better testing procedures + +const ( + apiKey1 = "" + apiSecret1 = "" + apiKeyRole1 = "" + sessionHeartBeat1 = false + + apiKey2 = "" + apiSecret2 = "" + apiKeyRole2 = "" + sessionHeartBeat2 = false +) + +func TestAddSession(t *testing.T) { + err := g.AddSession(1, apiKey1, apiSecret1, apiKeyRole1, true) + if err != nil { + t.Error("Test failed - AddSession() error") + } + err = g.AddSession(1, apiKey1, apiSecret1, apiKeyRole1, true) + if err == nil { + t.Error("Test failed - AddSession() error") + } + err = g.AddSession(2, apiKey2, apiSecret2, apiKeyRole2, false) + if err != nil { + t.Error("Test failed - AddSession() error") + } +} + +func TestSetDefaults(t *testing.T) { + g.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + geminiConfig, err := cfg.GetExchangeConfig("Gemini") + if err != nil { + t.Error("Test Failed - Gemini Setup() init error") + } + + geminiConfig.AuthenticatedAPISupport = true + + g.Setup(geminiConfig) +} + +func TestSession(t *testing.T) { + t.Parallel() + if g.Session(1) == nil { + t.Error("Test Failed - Session() error") + } + if g.Session(1337) != nil { + t.Error("Test Failed - Session() error") + } +} + +func TestSandbox(t *testing.T) { + t.Parallel() + g.APIUrl = geminiAPIURL + if g.Sandbox().APIUrl != geminiSandboxAPIURL { + t.Error("Test Failed - Sandbox() error") + } +} + +func TestGetSymbols(t *testing.T) { + t.Parallel() + _, err := g.GetSymbols() + if err != nil { + t.Error("Test Failed - GetSymbols() error", err) + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := g.GetTicker("BTCUSD") + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } + _, err = g.GetTicker("bla") + if err == nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := g.GetOrderbook("btcusd", url.Values{}) + if err != nil { + t.Error("Test Failed - GetOrderbook() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := g.GetTrades("btcusd", url.Values{}) + if err != nil { + t.Error("Test Failed - GetTrades() error", err) + } +} + +func TestGetAuction(t *testing.T) { + t.Parallel() + _, err := g.GetAuction("btcusd") + if err != nil { + t.Error("Test Failed - GetAuction() error", err) + } +} + +func TestGetAuctionHistory(t *testing.T) { + t.Parallel() + _, err := g.GetAuctionHistory("btcusd", url.Values{}) + if err != nil { + t.Error("Test Failed - GetAuctionHistory() error", err) + } +} + +func TestNewOrder(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().NewOrder("btcusd", 1, 4500, "buy", "exchange limit") + if err == nil { + t.Error("Test Failed - NewOrder() error", err) + } + _, err = g.Session(2).Sandbox().NewOrder("btcusd", 1, 4500, "buy", "exchange limit") + if err == nil { + t.Error("Test Failed - NewOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().CancelOrder(1337) + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} + +func TestCancelOrders(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().CancelOrders(false) + if err == nil { + t.Error("Test Failed - CancelOrders() error", err) + } + _, err = g.Session(2).Sandbox().CancelOrders(true) + if err == nil { + t.Error("Test Failed - CancelOrders() error", err) + } +} + +func TestGetOrderStatus(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetOrderStatus(1337) + if err == nil { + t.Error("Test Failed - GetOrderStatus() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetOrders() + if err == nil { + t.Error("Test Failed - GetOrders() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetTradeHistory("btcusd", 0) + if err == nil { + t.Error("Test Failed - GetTradeHistory() error", err) + } +} + +func TestGetTradeVolume(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetTradeVolume() + if err == nil { + t.Error("Test Failed - GetTradeVolume() error", err) + } +} + +func TestGetBalances(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetBalances() + if err == nil { + t.Error("Test Failed - GetBalances() error", err) + } +} + +func TestGetDepositAddress(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetDepositAddress("LOL123", "btc") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } +} + +func TestWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().WithdrawCrypto("LOL123", "btc", 1) + if err == nil { + t.Error("Test Failed - WithdrawCrypto() error", err) + } +} + +func TestPostHeartbeat(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().PostHeartbeat() + if err == nil { + t.Error("Test Failed - PostHeartbeat() error", err) + } +} diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index 68eaebbd..a6cd4aed 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -1,66 +1,7 @@ package gemini -type GeminiOrderbookEntry struct { - Price float64 `json:"price,string"` - Amount float64 `json:"amount,string"` -} - -type GeminiOrderbook struct { - Bids []GeminiOrderbookEntry `json:"bids"` - Asks []GeminiOrderbookEntry `json:"asks"` -} - -type GeminiTrade struct { - Timestamp int64 `json:"timestamp"` - TID int64 `json:"tid"` - Price float64 `json:"price"` - Amount float64 `json:"amount"` - Side string `json:"taker"` -} - -type GeminiOrder struct { - OrderID int64 `json:"order_id"` - ClientOrderID string `json:"client_order_id"` - Symbol string `json:"symbol"` - Exchange string `json:"exchange"` - Price float64 `json:"price,string"` - AvgExecutionPrice float64 `json:"avg_execution_price,string"` - Side string `json:"side"` - Type string `json:"type"` - Timestamp int64 `json:"timestamp"` - TimestampMS int64 `json:"timestampms"` - IsLive bool `json:"is_live"` - IsCancelled bool `json:"is_cancelled"` - WasForced bool `json:"was_forced"` - ExecutedAmount float64 `json:"executed_amount,string"` - RemainingAmount float64 `json:"remaining_amount,string"` - OriginalAmount float64 `json:"original_amount,string"` -} - -type GeminiOrderResult struct { - Result bool `json:"result"` -} - -type GeminiTradeHistory struct { - Price float64 `json:"price"` - Amount float64 `json:"amount"` - Timestamp int64 `json:"timestamp"` - TimestampMS int64 `json:"timestampms"` - Type string `json:"type"` - FeeCurrency string `json:"fee_currency"` - FeeAmount float64 `json:"fee_amount"` - TID int64 `json:"tid"` - OrderID int64 `json:"order_id"` - ClientOrderID string `json:"client_order_id"` -} - -type GeminiBalance struct { - Currency string `json:"currency"` - Amount float64 `json:"amount"` - Available float64 `json:"available"` -} - -type GeminiTicker struct { +// Ticker holds returned ticker data from the exchange +type Ticker struct { Ask float64 `json:"ask,string"` Bid float64 `json:"bid,string"` Last float64 `json:"last,string"` @@ -71,16 +12,47 @@ type GeminiTicker struct { } } -type GeminiAuction struct { - LastAuctionPrice float64 `json:"last_auction_price,string"` - LastAuctionQuantity float64 `json:"last_auction_quantity,string"` - LastHighestBidPrice float64 `json:"last_highest_bid_price,string"` - LastLowestAskPrice float64 `json:"last_lowest_ask_price,string"` - NextUpdateMS int64 `json:"next_update_ms"` - NextAuctionMS int64 `json:"next_auction_ms"` - LastAuctionEID int64 `json:"last_auction_eid"` +// Orderbook contains orderbook information for both bid and ask side +type Orderbook struct { + Bids []OrderbookEntry `json:"bids"` + Asks []OrderbookEntry `json:"asks"` } -type GeminiAuctionHistory struct { + +// OrderbookEntry subtype of orderbook information +type OrderbookEntry struct { + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` +} + +// Trade holds trade history for a specific currency pair +type Trade struct { + Timestamp int64 `json:"timestamp"` + Timestampms int64 `json:"timestampms"` + TID int64 `json:"tid"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Exchange string `json:"exchange"` + Side string `json:"type"` +} + +// Auction is generalized response type +type Auction struct { + LastAuctionEID int64 `json:"last_auction_eid"` + ClosedUntilMs int64 `json:"closed_until_ms"` + LastAuctionPrice float64 `json:"last_auction_price,string"` + LastAuctionQuantity float64 `json:"last_auction_quantity,string"` + LastHighestBidPrice float64 `json:"last_highest_bid_price,string"` + LastLowestAskPrice float64 `json:"last_lowest_ask_price,string"` + NextAuctionMS int64 `json:"next_auction_ms"` + NextUpdateMS int64 `json:"next_update_ms"` + MostRecentIndicativePrice float64 `json:"most_recent_indicative_price,string"` + MostRecentIndicativeQuantity float64 `json:"most_recent_indicative_quantity,string"` + MostRecentHighestBidPrice float64 `json:"most_recent_highest_bid_price,string"` + MostRecentLowestAskPrice float64 `json:"most_recent_lowest_ask_price,string"` +} + +// AuctionHistory holds auction history information +type AuctionHistory struct { AuctionID int64 `json:"auction_id"` AuctionPrice float64 `json:"auction_price,string"` AuctionQuantity float64 `json:"auction_quantity,string"` @@ -92,3 +64,102 @@ type GeminiAuctionHistory struct { TimestampMS int64 `json:"timestampms"` EventType string `json:"event_type"` } + +// OrderResult holds cancelled order information +type OrderResult struct { + Result string `json:"result"` + Details struct { + CancelledOrders []string `json:"cancelledOrders"` + CancelRejects []string `json:"cancelRejects"` + } `json:"details"` +} + +// Order contains order information +type Order struct { + OrderID int64 `json:"order_id,string"` + ID int64 `json:"id,string"` + ClientOrderID string `json:"client_order_id"` + Symbol string `json:"symbol"` + Exchange string `json:"exchange"` + Price float64 `json:"price,string"` + AvgExecutionPrice float64 `json:"avg_execution_price,string"` + Side string `json:"side"` + Type string `json:"type"` + Timestamp int64 `json:"timestamp,string"` + TimestampMS int64 `json:"timestampms"` + IsLive bool `json:"is_live"` + IsCancelled bool `json:"is_cancelled"` + IsHidden bool `json:"is_hidden"` + Options []string `json:"options"` + WasForced bool `json:"was_forced"` + ExecutedAmount float64 `json:"executed_amount,string"` + RemainingAmount float64 `json:"remaining_amount,string"` + OriginalAmount float64 `json:"original_amount,string"` +} + +// TradeHistory holds trade history information +type TradeHistory struct { + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Timestamp int64 `json:"timestamp"` + TimestampMS int64 `json:"timestampms"` + Type string `json:"type"` + FeeCurrency string `json:"fee_currency"` + FeeAmount float64 `json:"fee_amount,string"` + TID int64 `json:"tid"` + OrderID int64 `json:"order_id,string"` + Exchange string `json:"exchange"` + IsAuctionFilled bool `json:"is_auction_fill"` + ClientOrderID string `json:"client_order_id"` +} + +// TradeVolume holds Volume information +type TradeVolume struct { + AccountID int64 `json:"account_id"` + Symbol string `json:"symbol"` + BaseCurrency string `json:"base_currency"` + NotionalCurrency string `json:"notional_currency"` + Date string `json:"date_date"` + TotalVolumeBase float64 `json:"total_volume_base"` + MakerBuySellRatio float64 `json:"maker_buy_sell_ratio"` + BuyMakerBase float64 `json:"buy_maker_base"` + BuyMakerNotional float64 `json:"buy_maker_notional"` + BuyMakerCount float64 `json:"buy_maker_count"` + SellMakerBase float64 `json:"sell_maker_base"` + SellMakerNotional float64 `json:"sell_maker_notional"` + SellMakerCount float64 `json:"sell_maker_count"` + BuyTakerBase float64 `json:"buy_taker_base"` + BuyTakerNotional float64 `json:"buy_taker_notional"` + BuyTakerCount float64 `json:"buy_taker_count"` + SellTakerBase float64 `json:"sell_taker_base"` + SellTakerNotional float64 `json:"sell_taker_notional"` + SellTakerCount float64 `json:"sell_taker_count"` +} + +// Balance is a simple balance type +type Balance struct { + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + Available float64 `json:"available,string"` +} + +// DepositAddress holds assigned deposit address for a specific currency +type DepositAddress struct { + Currency string `json:"currency"` + Address string `json:"address"` + Label string `json:"label"` +} + +// WithdrawelAddress holds withdrawel information +type WithdrawelAddress struct { + Address string `json:"address"` + Amount float64 `json:"amount"` + TXHash string `json:"txHash"` +} + +// ErrorCapture is a generlized error response from the server +type ErrorCapture struct { + Result string `json:"result"` + Reason string `json:"reason"` + Message string `json:"message"` +} From 79a1911c935530c5df37cad60070e15f94a33834 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 12 Sep 2017 08:37:18 +1000 Subject: [PATCH 3/5] In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. --- common/common.go | 22 +- common/common_test.go | 6 +- currency/currency.go | 2 +- exchanges/anx/anx.go | 2 +- exchanges/bitfinex/bitfinex.go | 18 +- exchanges/bitstamp/bitstamp.go | 8 +- exchanges/bittrex/bittrex.go | 2 +- exchanges/btcc/btcc.go | 8 +- exchanges/btcmarkets/btcmarkets.go | 6 +- exchanges/gdax/gdax.go | 18 +- exchanges/gemini/gemini.go | 12 +- exchanges/huobi/huobi.go | 4 +- exchanges/itbit/itbit.go | 258 ++++++++++++----------- exchanges/itbit/itbit_test.go | 145 +++++++++++++ exchanges/itbit/itbit_types.go | 155 ++++++++++++-- exchanges/kraken/kraken.go | 16 +- exchanges/lakebtc/lakebtc.go | 6 +- exchanges/liqui/liqui.go | 8 +- exchanges/localbitcoins/localbitcoins.go | 8 +- exchanges/nonce/nonce.go | 40 +++- exchanges/nonce/nonce_test.go | 11 + exchanges/okcoin/okcoin.go | 26 +-- exchanges/poloniex/poloniex.go | 14 +- portfolio/portfolio.go | 47 ++++- 24 files changed, 601 insertions(+), 241 deletions(-) create mode 100644 exchanges/itbit/itbit_test.go diff --git a/common/common.go b/common/common.go index aa762ead..1d4da4ce 100644 --- a/common/common.go +++ b/common/common.go @@ -20,6 +20,7 @@ import ( "net/http" "net/url" "os" + "reflect" "regexp" "strconv" "strings" @@ -294,16 +295,24 @@ func SendHTTPRequest(method, path string, headers map[string]string, body io.Rea // SendHTTPGetRequest sends a simple get request using a url string & JSON // decodes the response into a struct pointer you have supplied. Returns an error // on failure. -func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { +func SendHTTPGetRequest(url string, jsonDecode, isVerbose bool, result interface{}) error { + if isVerbose { + log.Println("Raw URL: ", url) + } + res, err := http.Get(url) if err != nil { return err } if res.StatusCode != 200 { +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 log.Printf("HTTP status code: %d\n", res.StatusCode) log.Printf("URL: %s\n", url) return errors.New("status code was not 200") +======= + return fmt.Errorf("common.SendHTTPGetRequest() error: HTTP status code %d", res.StatusCode) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. } contents, err := ioutil.ReadAll(res.Body) @@ -311,16 +320,18 @@ func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { return err } + if isVerbose { + log.Println("Raw Resp: ", string(contents[:])) + } + defer res.Body.Close() if jsonDecode { - err := JSONDecode(contents, &result) + err := JSONDecode(contents, result) if err != nil { log.Println(string(contents[:])) return err } - } else { - result = &contents } return nil @@ -333,6 +344,9 @@ func JSONEncode(v interface{}) ([]byte, error) { // JSONDecode decodes JSON data into a structure func JSONDecode(data []byte, to interface{}) error { + if !StringContains(reflect.ValueOf(to).Type().String(), "*") { + return errors.New("json decode error - memory address not supplied") + } return json.Unmarshal(data, to) } diff --git a/common/common_test.go b/common/common_test.go index 31b17a0c..583b7025 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -488,15 +488,15 @@ func TestSendHTTPGetRequest(t *testing.T) { url := `https://etherchain.org/api/account/multiple/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe` result := test{} - err := SendHTTPGetRequest(url, true, &result) + err := SendHTTPGetRequest(url, true, false, &result) if err != nil { t.Errorf("Test failed - common SendHTTPGetRequest error: %s", err) } - err = SendHTTPGetRequest("DINGDONG", true, &result) + err = SendHTTPGetRequest("DINGDONG", true, false, &result) if err == nil { t.Error("Test failed - common SendHTTPGetRequest error") } - err = SendHTTPGetRequest(url, false, &result) + err = SendHTTPGetRequest(url, false, false, &result) if err != nil { t.Error("Test failed - common SendHTTPGetRequest error") } diff --git a/currency/currency.go b/currency/currency.go index 79c05d3b..0201f529 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -314,7 +314,7 @@ func FetchFixerCurrencyData() error { CurrencyStoreFixer = make(map[string]float64) - err := common.SendHTTPGetRequest(url, true, &result) + err := common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return err } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 1843a3c8..b77b9240 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -83,7 +83,7 @@ func (a *ANX) GetFee(maker bool) float64 { func (a *ANX) GetTicker(currency string) (ANXTicker, error) { var ticker ANXTicker - err := common.SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, &ticker) + err := common.SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, a.Verbose, &ticker) if err != nil { return ANXTicker{}, err } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 1c6185a4..b25ad80f 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -119,7 +119,7 @@ func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) { response := Ticker{} path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetStats returns various statistics about the requested pair @@ -127,7 +127,7 @@ func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) { response := []Stat{} path := fmt.Sprint(bitfinexAPIURL + bitfinexStats + symbol) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetFundingBook the entire margin funding book for both bids and asks sides @@ -137,7 +137,7 @@ func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { response := FundingBook{} path := fmt.Sprint(bitfinexAPIURL + bitfinexLendbook + symbol) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetOrderbook retieves the entire orderbook bid and ask price on a currency @@ -149,7 +149,7 @@ func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbo bitfinexAPIURL+bitfinexOrderbook+currencyPair, values, ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetTrades returns a list of the most recent trades for the given curencyPair @@ -160,7 +160,7 @@ func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStr bitfinexAPIURL+bitfinexTrades+currencyPair, values, ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetLendbook returns a list of the most recent funding data for the given @@ -174,7 +174,7 @@ func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, erro } path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLendbook+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetLends returns a list of the most recent funding data for the given @@ -185,7 +185,7 @@ func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) { response := []Lends{} path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLends+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetSymbols returns the avaliable currency pairs on the exchange @@ -193,7 +193,7 @@ func (b *Bitfinex) GetSymbols() ([]string, error) { products := []string{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbols) - return products, common.SendHTTPGetRequest(path, true, &products) + return products, common.SendHTTPGetRequest(path, true, b.Verbose, &products) } // GetSymbolsDetails a list of valid symbol IDs and the pair details @@ -201,7 +201,7 @@ func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) { response := []SymbolDetails{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbolsDetails) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetAccountInfo returns information about your account incl. trading fees diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index cafffa04..fa385413 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -127,7 +127,7 @@ func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) { tickerEndpoint, common.StringToLower(currency), ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetOrderbook Returns a JSON dictionary with "bids" and "asks". Each is a list @@ -149,7 +149,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { common.StringToLower(currency), ) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, b.Verbose, &resp) if err != nil { return Orderbook{}, err } @@ -204,7 +204,7 @@ func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Tr values, ) - return transactions, common.SendHTTPGetRequest(path, true, &transactions) + return transactions, common.SendHTTPGetRequest(path, true, b.Verbose, &transactions) } // GetEURUSDConversionRate returns the conversion rate between Euro and USD @@ -212,7 +212,7 @@ func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { rate := EURUSDConversionRate{} path := fmt.Sprintf("%s/%s", bitstampAPIURL, bitstampAPIEURUSD) - return rate, common.SendHTTPGetRequest(path, true, &rate) + return rate, common.SendHTTPGetRequest(path, true, b.Verbose, &rate) } // GetBalance returns full balance of currency held on the exchange diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index ab5791fb..13c878c5 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -373,7 +373,7 @@ func (b *Bittrex) HTTPRequest(path string, auth bool, values url.Values, v inter return err } } else { - if err := common.SendHTTPGetRequest(path, true, &response); err != nil { + if err := common.SendHTTPGetRequest(path, true, b.Verbose, &response); err != nil { return err } } diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index 639499e9..dd5b12d4 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -99,7 +99,7 @@ func (b *BTCC) GetTicker(currencyPair string) (Ticker, error) { resp := Response{} req := fmt.Sprintf("%sdata/ticker?market=%s", btccAPIUrl, currencyPair) - return resp.Ticker, common.SendHTTPGetRequest(req, true, &resp) + return resp.Ticker, common.SendHTTPGetRequest(req, true, b.Verbose, &resp) } // GetTradesLast24h returns the trades executed on the exchange over the past @@ -109,7 +109,7 @@ func (b *BTCC) GetTradesLast24h(currencyPair string) ([]Trade, error) { trades := []Trade{} req := fmt.Sprintf("%sdata/trades?market=%s", btccAPIUrl, currencyPair) - return trades, common.SendHTTPGetRequest(req, true, &trades) + return trades, common.SendHTTPGetRequest(req, true, b.Verbose, &trades) } // GetTradeHistory returns trade history data @@ -136,7 +136,7 @@ func (b *BTCC) GetTradeHistory(currencyPair string, limit, sinceTid int64, time req = common.EncodeURLValues(req, v) - return trades, common.SendHTTPGetRequest(req, true, &trades) + return trades, common.SendHTTPGetRequest(req, true, b.Verbose, &trades) } // GetOrderBook returns current market order book @@ -151,7 +151,7 @@ func (b *BTCC) GetOrderBook(currencyPair string, limit int) (Orderbook, error) { req = fmt.Sprintf("%sdata/orderbook?market=%s", btccAPIUrl, currencyPair) } - return result, common.SendHTTPGetRequest(req, true, &result) + return result, common.SendHTTPGetRequest(req, true, b.Verbose, &result) } func (b *BTCC) GetAccountInfo(infoType string) error { diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 4668fedd..1f6bc8e8 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -97,7 +97,7 @@ func (b *BTCMarkets) GetTicker(symbol string) (Ticker, error) { path := fmt.Sprintf("/market/%s/AUD/tick", common.StringToUpper(symbol)) return ticker, - common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &ticker) + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, b.Verbose, &ticker) } // GetOrderbook returns current orderbook @@ -107,7 +107,7 @@ func (b *BTCMarkets) GetOrderbook(symbol string) (Orderbook, error) { path := fmt.Sprintf("/market/%s/AUD/orderbook", common.StringToUpper(symbol)) return orderbook, - common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &orderbook) + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, b.Verbose, &orderbook) } // GetTrades returns executed trades on the exchange @@ -117,7 +117,7 @@ func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]Trade, error trades := []Trade{} path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", btcMarketsAPIURL, symbol), values) - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, b.Verbose, &trades) } // NewOrder requests a new order and returns an ID diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index f135b3e6..0749ade9 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -110,7 +110,7 @@ func (g *GDAX) GetProducts() ([]Product, error) { products := []Product{} return products, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, &products) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, g.Verbose, &products) } // GetOrderbook returns orderbook by currency pair and level @@ -123,7 +123,7 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { path = fmt.Sprintf("%s/%s/%s?level=%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook, levelStr) } - if err := common.SendHTTPGetRequest(path, true, &orderbook); err != nil { + if err := common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook); err != nil { return nil, err } @@ -193,7 +193,7 @@ func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTicker) log.Println(path) - return ticker, common.SendHTTPGetRequest(path, true, &ticker) + return ticker, common.SendHTTPGetRequest(path, true, g.Verbose, &ticker) } // GetTrades listd the latest trades for a product @@ -203,7 +203,7 @@ func (g *GDAX) GetTrades(currencyPair string) ([]Trade, error) { path := fmt.Sprintf( "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTrades) - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } // GetHistoricRates returns historic rates for a product. Rates are returned in @@ -229,7 +229,7 @@ func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxHistory), values) - if err := common.SendHTTPGetRequest(path, true, &resp); err != nil { + if err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp); err != nil { return history, err } @@ -255,7 +255,7 @@ func (g *GDAX) GetStats(currencyPair string) (Stats, error) { path := fmt.Sprintf( "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxStats) - return stats, common.SendHTTPGetRequest(path, true, &stats) + return stats, common.SendHTTPGetRequest(path, true, g.Verbose, &stats) } // GetCurrencies returns a list of supported currency on the exchange @@ -264,7 +264,7 @@ func (g *GDAX) GetCurrencies() ([]Currency, error) { currencies := []Currency{} return currencies, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, ¤cies) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, g.Verbose, ¤cies) } // GetServerTime returns the API server time @@ -272,7 +272,7 @@ func (g *GDAX) GetServerTime() (ServerTime, error) { serverTime := ServerTime{} return serverTime, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, &serverTime) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, g.Verbose, &serverTime) } // GetAccounts returns a list of trading accounts associated with the APIKEYS @@ -772,7 +772,7 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri } } - nonce := g.Nonce.Evaluate() + nonce := g.Nonce.GetValue(g.Name, false).String() message := nonce + method + "/" + path + string(payload) hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 7f78eac6..e83585f5 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -160,7 +160,7 @@ func (g *Gemini) GetSymbols() ([]string, error) { symbols := []string{} path := fmt.Sprintf("%s/v%s/%s", geminiAPIURL, geminiAPIVersion, geminiSymbols) - return symbols, common.SendHTTPGetRequest(path, true, &symbols) + return symbols, common.SendHTTPGetRequest(path, true, g.Verbose, &symbols) } // GetTicker returns information about recent trading activity for the symbol @@ -177,7 +177,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { resp := TickerResponse{} path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTicker, currencyPair) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp) if err != nil { return ticker, err } @@ -204,7 +204,7 @@ func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiOrderbook, currencyPair), params) orderbook := Orderbook{} - return orderbook, common.SendHTTPGetRequest(path, true, &orderbook) + return orderbook, common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook) } // GetTrades eturn the trades that have executed since the specified timestamp. @@ -220,7 +220,7 @@ func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, err path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTrades, currencyPair), params) trades := []Trade{} - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } // GetAuction returns auction infomation @@ -228,7 +228,7 @@ func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair) auction := Auction{} - return auction, common.SendHTTPGetRequest(path, true, &auction) + return auction, common.SendHTTPGetRequest(path, true, g.Verbose, &auction) } // GetAuctionHistory returns the auction events, optionally including @@ -246,7 +246,7 @@ func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]Au path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) auctionHist := []AuctionHistory{} - return auctionHist, common.SendHTTPGetRequest(path, true, &auctionHist) + return auctionHist, common.SendHTTPGetRequest(path, true, g.Verbose, &auctionHist) } func (g *Gemini) isCorrectSession(role string) error { diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f03517c4..9d31a34a 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -68,7 +68,7 @@ func (h *HUOBI) GetFee() float64 { func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { resp := HuobiTickerResponse{} path := fmt.Sprintf("https://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) if err != nil { return HuobiTicker{}, err @@ -79,7 +79,7 @@ func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { func (h *HUOBI) GetOrderBook(symbol string) (HuobiOrderbook, error) { path := fmt.Sprintf("https://api.huobi.com/staticmarket/depth_%s_json.js", symbol) resp := HuobiOrderbook{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) if err != nil { return resp, err } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 80aec2b0..49fc986a 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -16,14 +16,26 @@ import ( ) const ( - ITBIT_API_URL = "https://api.itbit.com/v1" - ITBIT_API_VERSION = "1" + itbitAPIURL = "https://api.itbit.com/v1" + itbitAPIVersion = "1" + itbitMarkets = "markets" + itbitOrderbook = "order_book" + itbitTicker = "ticker" + itbitWallets = "wallets" + itbitBalances = "balances" + itbitTrades = "trades" + itbitFundingHistory = "funding_history" + itbitOrders = "orders" + itbitCryptoDeposits = "cryptocurrency_deposits" + itbitWalletTransfer = "wallet_transfers" ) +// ItBit is the overarching type across the ItBit package type ItBit struct { exchange.Base } +// SetDefaults sets the defaults for the exchange func (i *ItBit) SetDefaults() { i.Name = "ITBIT" i.Enabled = false @@ -39,6 +51,7 @@ func (i *ItBit) SetDefaults() { i.AssetTypes = []string{ticker.Spot} } +// Setup sets the exchange paramaters from exchange config func (i *ItBit) Setup(exch config.ExchangeConfig) { if !exch.Enabled { i.SetEnabled(false) @@ -63,6 +76,7 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the maker or taker fee func (i *ItBit) GetFee(maker bool) float64 { if maker { return i.MakerFee @@ -70,98 +84,103 @@ func (i *ItBit) GetFee(maker bool) float64 { return i.TakerFee } -func (i *ItBit) GetTicker(currency string) (Ticker, error) { - path := ITBIT_API_URL + "/markets/" + currency + "/ticker" - var itbitTicker Ticker - err := common.SendHTTPGetRequest(path, true, &itbitTicker) - if err != nil { - return Ticker{}, err - } - return itbitTicker, nil +// GetTicker returns ticker info for a specified market. +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +func (i *ItBit) GetTicker(currencyPair string) (Ticker, error) { + var response Ticker + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, itbitTicker) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetOrderbook(currency string) (OrderbookResponse, error) { +// GetOrderbook returns full order book for the specified market. +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +func (i *ItBit) GetOrderbook(currencyPair string) (OrderbookResponse, error) { response := OrderbookResponse{} - path := ITBIT_API_URL + "/markets/" + currency + "/order_book" - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return OrderbookResponse{}, err - } - return response, nil + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, itbitOrderbook) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetTradeHistory(currency, timestamp string) bool { - req := "/trades?since=" + timestamp - err := common.SendHTTPGetRequest(ITBIT_API_URL+"markets/"+currency+req, true, nil) - if err != nil { - log.Println(err) - return false - } - return true +// GetTradeHistory returns recent trades for a specified market. +// +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +// timestamp - matchNumber, only executions after this will be returned +func (i *ItBit) GetTradeHistory(currencyPair, timestamp string) (Trades, error) { + response := Trades{} + req := "trades?since=" + timestamp + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, req) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetWallets(params url.Values) { +// GetWallets returns information about all wallets associated with the account. +// +// params -- +// page - [optional] page to return example 1. default 1 +// perPage - [optional] items per page example 50, default 50 max 50 +func (i *ItBit) GetWallets(params url.Values) ([]Wallet, error) { + resp := []Wallet{} params.Set("userId", i.ClientID) - path := "/wallets?" + params.Encode() + path := fmt.Sprintf("/%s?%s", itbitWallets, params.Encode()) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) CreateWallet(walletName string) { - path := "/wallets" +// CreateWallet creates a new wallet with a specified name. +func (i *ItBit) CreateWallet(walletName string) (Wallet, error) { + resp := Wallet{} params := make(map[string]interface{}) params["userId"] = i.ClientID params["name"] = walletName - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, + i.SendAuthenticatedHTTPRequest("POST", "/"+itbitWallets, params, &resp) } -func (i *ItBit) GetWallet(walletID string) { - path := "/wallets/" + walletID - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWallet returns wallet information by walletID +func (i *ItBit) GetWallet(walletID string) (Wallet, error) { + resp := Wallet{} + path := fmt.Sprintf("/%s/%s", itbitWallets, walletID) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletBalance(walletID, currency string) { - path := "/wallets/ " + walletID + "/balances/" + currency - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWalletBalance returns balance information for a specific currency in a +// wallet. +func (i *ItBit) GetWalletBalance(walletID, currency string) (Balance, error) { + resp := Balance{} + path := fmt.Sprintf("/%s/%s/%s/%s", itbitWallets, walletID, itbitBalances, currency) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletTrades(walletID string, params url.Values) { - path := common.EncodeURLValues("/wallets/"+walletID+"/trades", params) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWalletTrades returns all trades for a specified wallet. +func (i *ItBit) GetWalletTrades(walletID string, params url.Values) (Records, error) { + resp := Records{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitTrades) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletOrders(walletID string, params url.Values) { - path := common.EncodeURLValues("/wallets/"+walletID+"/orders", params) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetFundingHistory returns all funding history for a specified wallet. +func (i *ItBit) GetFundingHistory(walletID string, params url.Values) (FundingRecords, error) { + resp := FundingRecords{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitFundingHistory) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) PlaceWalletOrder(walletID, side, orderType, currency string, amount, price float64, instrument string, clientRef string) { - path := "/wallets/" + walletID + "/orders" +// PlaceOrder places a new order +func (i *ItBit) PlaceOrder(walletID, side, orderType, currency string, amount, price float64, instrument, clientRef string) (Order, error) { + resp := Order{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitOrders) + params := make(map[string]interface{}) params["side"] = side params["type"] = orderType @@ -174,85 +193,58 @@ func (i *ItBit) PlaceWalletOrder(walletID, side, orderType, currency string, amo params["clientOrderIdentifier"] = clientRef } - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) GetWalletOrder(walletID, orderID string) { - path := "/wallets/" + walletID + "/orders/" + orderID - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetOrder returns an order by id. +func (i *ItBit) GetOrder(walletID string, params url.Values) (Order, error) { + resp := Order{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitOrders) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) CancelWalletOrder(walletID, orderID string) { - path := "/wallets/" + walletID + "/orders/" + orderID - err := i.SendAuthenticatedHTTPRequest("DELETE", path, nil) +// CancelOrder cancels and open order. *This is not a guarantee that the order +// has been cancelled!* +func (i *ItBit) CancelOrder(walletID, orderID string) error { + path := fmt.Sprintf("/%s/%s/%s/%s", itbitWallets, walletID, itbitOrders, orderID) - if err != nil { - log.Println(err) - } + return i.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) } -func (i *ItBit) PlaceWithdrawalRequest(walletID, currency, address string, amount float64) { - path := "/wallets/" + walletID + "/cryptocurrency_withdrawals" - params := make(map[string]interface{}) - params["currency"] = currency - params["amount"] = amount - params["address"] = address - - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } -} - -func (i *ItBit) GetDepositAddress(walletID, currency string) { - path := "/wallets/" + walletID + "/cryptocurrency_deposits" +// GetDepositAddress returns a deposit address to send cryptocurrency to. +func (i *ItBit) GetDepositAddress(walletID, currency string) (CryptoCurrencyDeposit, error) { + resp := CryptoCurrencyDeposit{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitCryptoDeposits) params := make(map[string]interface{}) params["currency"] = currency - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount float64, currency string) { - path := "/wallets/" + walletID + "/wallet_transfers" +// WalletTransfer transfers funds between wallets. +func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount float64, currency string) (WalletTransfer, error) { + resp := WalletTransfer{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitWalletTransfer) + params := make(map[string]interface{}) params["sourceWalletId"] = sourceWallet params["destinationWalletId"] = destWallet params["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) params["currencyCode"] = currency - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}) (err error) { +// SendAuthenticatedHTTPRequest sends an authenticated request to itBit +func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}, result interface{}) error { if !i.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, i.Name) } - if i.Nonce.Get() == 0 { - i.Nonce.Set(time.Now().UnixNano()) - } else { - i.Nonce.Inc() - } - request := make(map[string]interface{}) - url := ITBIT_API_URL + path + url := itbitAPIURL + path if params != nil { for key, value := range params { @@ -261,12 +253,13 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } PayloadJSON := []byte("") + var err error if params != nil { - PayloadJSON, err = common.JSONEncode(request) + PayloadJSON, err = common.JSONEncode(request) if err != nil { - return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON Marshal request") + return err } if i.Verbose { @@ -274,26 +267,37 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } } - message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), i.Nonce.String(), i.Nonce.String()[0:13]}) + nonce := i.Nonce.GetValue(i.Name, false).String() + timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) + + message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), nonce, timestamp}) if err != nil { - log.Println(err) - return + return err } - hash := common.GetSHA256([]byte(i.Nonce.String() + string(message))) + hash := common.GetSHA256([]byte(nonce + string(message))) hmac := common.GetHMAC(common.HashSHA512, []byte(url+string(hash)), []byte(i.APISecret)) signature := common.Base64Encode(hmac) headers := make(map[string]string) headers["Authorization"] = i.ClientID + ":" + signature - headers["X-Auth-Timestamp"] = i.Nonce.String()[0:13] - headers["X-Auth-Nonce"] = i.Nonce.String() + headers["X-Auth-Timestamp"] = timestamp + headers["X-Auth-Nonce"] = nonce headers["Content-Type"] = "application/json" resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJSON))) + if err != nil { + return err + } if i.Verbose { log.Printf("Received raw: \n%s\n", resp) } - return nil + + errCapture := GeneralReturn{} + if err := common.JSONDecode([]byte(resp), &errCapture); err == nil { + return errors.New(errCapture.Description) + } + + return common.JSONDecode([]byte(resp), result) } diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go new file mode 100644 index 00000000..88c51689 --- /dev/null +++ b/exchanges/itbit/itbit_test.go @@ -0,0 +1,145 @@ +package itbit + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var i ItBit + +// Please provide your own keys to do proper testing +const ( + apiKey = "" + apiSecret = "" + clientID = "" +) + +func TestSetDefaults(t *testing.T) { + i.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + itbitConfig, err := cfg.GetExchangeConfig("ITBIT") + if err != nil { + t.Error("Test Failed - Gemini Setup() init error") + } + + itbitConfig.AuthenticatedAPISupport = true + itbitConfig.APIKey = apiKey + itbitConfig.APISecret = apiSecret + itbitConfig.ClientID = clientID + + i.Setup(itbitConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if i.GetFee(true) != -0.1 || i.GetFee(false) != 0.5 { + t.Error("Test Failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := i.GetTicker("XBTUSD") + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := i.GetOrderbook("XBTSGD") + if err != nil { + t.Error("Test Failed - GetOrderbook() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + t.Parallel() + _, err := i.GetTradeHistory("XBTUSD", "0") + if err != nil { + t.Error("Test Failed - GetTradeHistory() error", err) + } +} + +func TestGetWallets(t *testing.T) { + _, err := i.GetWallets(url.Values{}) + if err == nil { + t.Error("Test Failed - GetWallets() error", err) + } +} + +func TestCreateWallet(t *testing.T) { + _, err := i.CreateWallet("test") + if err == nil { + t.Error("Test Failed - CreateWallet() error", err) + } +} + +func TestGetWallet(t *testing.T) { + _, err := i.GetWallet("1337") + if err == nil { + t.Error("Test Failed - GetWallet() error", err) + } +} + +func TestGetWalletBalance(t *testing.T) { + _, err := i.GetWalletBalance("1337", "XRT") + if err == nil { + t.Error("Test Failed - GetWalletBalance() error", err) + } +} + +func TestGetWalletTrades(t *testing.T) { + _, err := i.GetWalletTrades("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetWalletTrades() error", err) + } +} + +func TestGetFundingHistory(t *testing.T) { + _, err := i.GetFundingHistory("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetFundingHistory() error", err) + } +} + +func TestPlaceOrder(t *testing.T) { + _, err := i.PlaceOrder("1337", "buy", "limit", "USD", 1, 0.2, "banjo", "sauce") + if err == nil { + t.Error("Test Failed - PlaceOrder() error", err) + } +} + +func TestGetOrder(t *testing.T) { + _, err := i.GetOrder("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + err := i.CancelOrder("1337", "1337order") + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} + +func TestGetDepositAddress(t *testing.T) { + _, err := i.GetDepositAddress("1337", "AUD") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } +} + +func TestWalletTransfer(t *testing.T) { + _, err := i.WalletTransfer("1337", "mywallet", "anotherwallet", 200, "USD") + if err == nil { + t.Error("Test Failed - WalletTransfer() error", err) + } +} diff --git a/exchanges/itbit/itbit_types.go b/exchanges/itbit/itbit_types.go index 9613404d..8adce526 100644 --- a/exchanges/itbit/itbit_types.go +++ b/exchanges/itbit/itbit_types.go @@ -1,26 +1,145 @@ package itbit -type Ticker struct { - Pair string - Bid float64 `json:",string"` - BidAmt float64 `json:",string"` - Ask float64 `json:",string"` - AskAmt float64 `json:",string"` - LastPrice float64 `json:",string"` - LastAmt float64 `json:",string"` - Volume24h float64 `json:",string"` - VolumeToday float64 `json:",string"` - High24h float64 `json:",string"` - Low24h float64 `json:",string"` - HighToday float64 `json:",string"` - LowToday float64 `json:",string"` - OpenToday float64 `json:",string"` - VwapToday float64 `json:",string"` - Vwap24h float64 `json:",string"` - ServertimeUTC string +// GeneralReturn is a generalized return type to capture any errors +type GeneralReturn struct { + Code int `json:"code"` + Description string `json:"description"` + RequestID string `json:"requestId"` } +// Ticker holds returned ticker information +type Ticker struct { + Pair string `json:"pair"` + Bid float64 `json:"bid,string"` + BidAmt float64 `json:"bidAmt,string"` + Ask float64 `json:"ask,string"` + AskAmt float64 `json:"askAmt,string"` + LastPrice float64 `json:"lastPrice,string"` + LastAmt float64 `json:"lastAmt,string"` + Volume24h float64 `json:"volume24h,string"` + VolumeToday float64 `json:"volumeToday,string"` + High24h float64 `json:"high24h,string"` + Low24h float64 `json:"low24h,string"` + HighToday float64 `json:"highToday,string"` + LowToday float64 `json:"lowToday,string"` + OpenToday float64 `json:"openToday,string"` + VwapToday float64 `json:"vwapToday,string"` + Vwap24h float64 `json:"vwap24h,string"` + ServertimeUTC string `json:"serverTimeUTC"` +} + +// OrderbookResponse contains multi-arrayed strings of bid and ask side +// information type OrderbookResponse struct { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } + +// Trades holds recent trades with associated information +type Trades struct { + RecentTrades []struct { + Timestamp string `json:"timestamp"` + MatchNumber int64 `json:"matchNumber"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + } `json:"recentTrades"` +} + +// Wallet contains specific wallet information +type Wallet struct { + ID string `json:"id"` + UserID string `json:"userId"` + Name string `json:"name"` + Balances []Balance `json:"balances"` +} + +// Balance is a sub type holding balance information +type Balance struct { + Currency string `json:"currency"` + AvailableBalance float64 `json:"availableBalance,string"` + TotalBalance float64 `json:"totalBalance,string"` +} + +// Records embodies records of trade history information +type Records struct { + TotalNumberOfRecords int `json:"totalNumberOfRecords,string"` + CurrentPageNumber int `json:"currentPageNumber,string"` + LatestExecutedID int64 `json:"latestExecutionId,string"` + RecordsPerPage int `json:"recordsPerPage,string"` + TradingHistory []TradeHistory `json:"tradingHistory"` +} + +// TradeHistory stores historic trade values +type TradeHistory struct { + OrderID string `json:"orderId"` + Timestamp string `json:"timestamp"` + Instrument string `json:"instrument"` + Direction string `json:"direction"` + CurrencyOne string `json:"currency1"` + CurrencyOneAmount float64 `json:"currency1Amount,string"` + CurrencyTwo string `json:"currency2"` + CurrencyTwoAmount float64 `json:"currency2Amount"` + Rate float64 `json:"rate,string"` + CommissionPaid float64 `json:"commissionPaid,string"` + CommissionCurrency string `json:"commissionCurrency"` + RebatesApplied float64 `json:"rebatesApplied,string"` + RebateCurrency string `json:"rebateCurrency"` +} + +// FundingRecords embodies records of fund history information +type FundingRecords struct { + TotalNumberOfRecords int `json:"totalNumberOfRecords,string"` + CurrentPageNumber int `json:"currentPageNumber,string"` + LatestExecutedID int64 `json:"latestExecutionId,string"` + RecordsPerPage int `json:"recordsPerPage,string"` + FundingHistory []FundHistory `json:"fundingHistory"` +} + +// FundHistory stores historic funding transactions +type FundHistory struct { + BankName string `json:"bankName"` + WithdrawalID int64 `json:"withdrawalId"` + HoldingPeriodCompletionDate string `json:"holdingPeriodCompletionDate"` + DestinationAddress string `json:"destinationAddress"` + TxnHash string `json:"txnHash"` + Time string `json:"time"` + Currency string `json:"currency"` + TransactionType string `json:"transactionType"` + Amount float64 `json:"amount,string"` + WalletName string `json:"walletName"` + Status string `json:"status"` +} + +// Order holds order information +type Order struct { + ID string `json:"id"` + WalletID string `json:"walletId"` + Side string `json:"side"` + Instrument string `json:"instrument"` + Type string `json:"type"` + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + AmountFilled float64 `json:"amountFilled,string"` + VolumeWeightedAveragePrice float64 `json:"volumeWeightedAveragePrice,string"` + CreatedTime string `json:"createdTime"` + Status string `json:"Status"` + Metadata interface{} `json:"metadata"` + ClientOrderIdentifier string `json:"clientOrderIdentifier"` +} + +// CryptoCurrencyDeposit holds information about a new wallet +type CryptoCurrencyDeposit struct { + ID int `json:"id"` + WalletID string `json:"walletID"` + DepositAddress string `json:"depositAddress"` + Metadata interface{} `json:"metadata"` +} + +// WalletTransfer holds wallet transfer information +type WalletTransfer struct { + SourceWalletID string `json:"sourceWalletId"` + DestinationWalletID string `json:"destinationWalletId"` + Amount float64 `json:"amount,string"` + CurrencyCode string `json:"currencyCode"` +} diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index cd264052..418881ef 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -99,7 +99,7 @@ func (k *Kraken) GetFee(cryptoTrade bool) float64 { func (k *Kraken) GetServerTime() error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SERVER_TIME) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -112,7 +112,7 @@ func (k *Kraken) GetServerTime() error { func (k *Kraken) GetAssets() error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSETS) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -130,7 +130,7 @@ func (k *Kraken) GetAssetPairs() (map[string]KrakenAssetPairs, error) { response := Response{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSET_PAIRS) - err := common.SendHTTPGetRequest(path, true, &response) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &response) if err != nil { return nil, err @@ -150,7 +150,7 @@ func (k *Kraken) GetTicker(symbol string) error { resp := Response{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TICKER, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &resp) if err != nil { return err @@ -183,7 +183,7 @@ func (k *Kraken) GetOHLC(symbol string) error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_OHLC, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -201,7 +201,7 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { var result interface{} var ob Orderbook path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_DEPTH, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return ob, err @@ -257,7 +257,7 @@ func (k *Kraken) GetTrades(symbol string) error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TRADES, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -273,7 +273,7 @@ func (k *Kraken) GetSpread(symbol string) { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SPREAD, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { log.Println(err) diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 62a8f604..639619d2 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -85,7 +85,7 @@ func (l *LakeBTC) GetFee(maker bool) float64 { func (l *LakeBTC) GetTicker() (map[string]LakeBTCTicker, error) { response := make(map[string]LakeBTCTickerResponse) path := fmt.Sprintf("%s/%s", LAKEBTC_API_URL, LAKEBTC_TICKER) - err := common.SendHTTPGetRequest(path, true, &response) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &response) if err != nil { return nil, err } @@ -126,7 +126,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { } path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_ORDERBOOK, common.StringToLower(currency)) resp := Response{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return LakeBTCOrderbook{}, err } @@ -165,7 +165,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { func (l *LakeBTC) GetTradeHistory(currency string) ([]LakeBTCTradeHistory, error) { path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_TRADES, common.StringToLower(currency)) resp := []LakeBTCTradeHistory{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return nil, err } diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 4611148e..f6baf699 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -102,7 +102,7 @@ func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { func (l *Liqui) GetInfo() (LiquiInfo, error) { req := fmt.Sprintf("%s/%s/%s/", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_INFO) resp := LiquiInfo{} - err := common.SendHTTPGetRequest(req, true, &resp) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &resp) if err != nil { return resp, err @@ -118,7 +118,7 @@ func (l *Liqui) GetTicker(symbol string) (map[string]LiquiTicker, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TICKER, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return nil, err @@ -134,7 +134,7 @@ func (l *Liqui) GetDepth(symbol string) (LiquiOrderbook, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_DEPTH, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return LiquiOrderbook{}, err } @@ -151,7 +151,7 @@ func (l *Liqui) GetTrades(symbol string) ([]LiquiTrades, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TRADES, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return []LiquiTrades{}, err } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 705c0aca..e07bbe1f 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -80,7 +80,7 @@ func (l *LocalBitcoins) GetFee(maker bool) float64 { func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { result := make(map[string]LocalBitcoinsTicker) - err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, &result) + err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, l.Verbose, &result) if err != nil { return result, err @@ -92,7 +92,7 @@ func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]LocalBitcoinsTrade, error) { path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency), values) result := []LocalBitcoinsTrade{} - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &result) if err != nil { return result, err @@ -109,7 +109,7 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (LocalBitcoinsOrderbook, e path := fmt.Sprintf("%s/%s/orderbook.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency) resp := response{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return LocalBitcoinsOrderbook{}, err @@ -162,7 +162,7 @@ func (l *LocalBitcoins) GetAccountInfo(username string, self bool) (LocalBitcoin } } else { path := fmt.Sprintf("%s/api/account_info/%s/", LOCALBITCOINS_API_URL, username) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return resp.Data, err diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go index e3ace537..8f87529d 100644 --- a/exchanges/nonce/nonce.go +++ b/exchanges/nonce/nonce.go @@ -8,8 +8,12 @@ import ( // Nonce struct holds the nonce value type Nonce struct { + // Standard nonce n int64 mtx sync.Mutex + // Hash table exclusive exchange specific nonce values + boundedCall map[string]int64 + boundedMtx sync.Mutex } // Inc increments the nonce value @@ -49,14 +53,32 @@ func (n *Nonce) String() string { return result } -// Evaluate returns a nonce while evaluating in a single locked call -func (n *Nonce) Evaluate() string { - n.mtx.Lock() - defer n.mtx.Unlock() - if n.n == 0 { - n.n = time.Now().Unix() - return strconv.FormatInt(n.n, 10) +// Value is a return type for GetValue +type Value int64 + +// GetValue returns a nonce value and can be set as a higher precision. Values +// stored in an exchange specific hash table using a single locked call. +func (n *Nonce) GetValue(exchName string, nanoPrecision bool) Value { + n.boundedMtx.Lock() + defer n.boundedMtx.Unlock() + + if n.boundedCall == nil { + n.boundedCall = make(map[string]int64) } - n.n = n.n + 1 - return strconv.FormatInt(n.n, 10) + + if n.boundedCall[exchName] == 0 { + if nanoPrecision { + n.boundedCall[exchName] = time.Now().UnixNano() + return Value(n.boundedCall[exchName]) + } + n.boundedCall[exchName] = time.Now().Unix() + return Value(n.boundedCall[exchName]) + } + n.boundedCall[exchName]++ + return Value(n.boundedCall[exchName]) +} + +// String is a Value method that changes format to a string +func (v Value) String() string { + return strconv.FormatInt(int64(v), 10) } diff --git a/exchanges/nonce/nonce_test.go b/exchanges/nonce/nonce_test.go index 8bf6d6da..74996d2c 100644 --- a/exchanges/nonce/nonce_test.go +++ b/exchanges/nonce/nonce_test.go @@ -1,6 +1,7 @@ package nonce import ( + "strconv" "testing" "time" ) @@ -56,6 +57,16 @@ func TestString(t *testing.T) { } } +func TestGetValue(t *testing.T) { + var nonce Nonce + timeNowNano := strconv.FormatInt(time.Now().UnixNano(), 10) + nValue := nonce.GetValue("dingdong", true).String() + + if timeNowNano == nValue { + t.Error("Test failed - GetValue() error, incorrect values") + } +} + func TestNonceConcurrency(t *testing.T) { var nonce Nonce nonce.Set(12312) diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index e9b8364b..12466207 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -152,7 +152,7 @@ func (o *OKCoin) GetTicker(symbol string) (OKCoinTicker, error) { vals := url.Values{} vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_TICKER, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return OKCoinTicker{}, err } @@ -171,7 +171,7 @@ func (o *OKCoin) GetOrderBook(symbol string, size int64, merge bool) (OKCoinOrde } path := common.EncodeURLValues(o.APIUrl+OKCOIN_DEPTH, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return resp, err } @@ -187,7 +187,7 @@ func (o *OKCoin) GetTrades(symbol string, since int64) ([]OKCoinTrades, error) { } path := common.EncodeURLValues(o.APIUrl+OKCOIN_TRADES, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return nil, err } @@ -209,7 +209,7 @@ func (o *OKCoin) GetKline(symbol, klineType string, size, since int64) ([]interf } path := common.EncodeURLValues(o.APIUrl+OKCOIN_KLINE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err } @@ -223,7 +223,7 @@ func (o *OKCoin) GetFuturesTicker(symbol, contractType string) (OKCoinFuturesTic vals.Set("symbol", symbol) vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TICKER, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return OKCoinFuturesTicker{}, err } @@ -244,7 +244,7 @@ func (o *OKCoin) GetFuturesDepth(symbol, contractType string, size int64, merge } path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_DEPTH, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return result, err } @@ -258,7 +258,7 @@ func (o *OKCoin) GetFuturesTrades(symbol, contractType string) ([]OKCoinFuturesT vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TRADES, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return nil, err } @@ -275,7 +275,7 @@ func (o *OKCoin) GetFuturesIndex(symbol string) (float64, error) { vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_INDEX, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return 0, err } @@ -288,7 +288,7 @@ func (o *OKCoin) GetFuturesExchangeRate() (float64, error) { } result := Response{} - err := common.SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, &result) + err := common.SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, o.Verbose, &result) if err != nil { return result.Rate, err } @@ -304,7 +304,7 @@ func (o *OKCoin) GetFuturesEstimatedPrice(symbol string) (float64, error) { vals := url.Values{} vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_ESTIMATED_PRICE, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return result.Price, err } @@ -326,7 +326,7 @@ func (o *OKCoin) GetFuturesKline(symbol, klineType, contractType string, size, s } path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_KLINE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err @@ -341,7 +341,7 @@ func (o *OKCoin) GetFuturesHoldAmount(symbol, contractType string) ([]OKCoinFutu vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_HOLD_AMOUNT, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err @@ -362,7 +362,7 @@ func (o *OKCoin) GetFuturesExplosive(symbol, contractType string, status, curren vals.Set("page_length", strconv.FormatInt(pageLength, 10)) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_EXPLOSIVE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 3c777206..d0b8d753 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -101,7 +101,7 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { resp := response{} path := fmt.Sprintf("%s/public?command=returnTicker", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp.Data) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) if err != nil { return resp.Data, err @@ -112,7 +112,7 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} path := fmt.Sprintf("%s/public?command=return24hVolume", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return resp, err @@ -130,7 +130,7 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo resp := PoloniexOrderbookResponse{} path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return PoloniexOrderbook{}, err @@ -173,7 +173,7 @@ func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]PoloniexT resp := []PoloniexTradeHistory{} path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err @@ -199,7 +199,7 @@ func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]Polo resp := []PoloniexChartData{} path := fmt.Sprintf("%s/public?command=returnChartData&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err @@ -213,7 +213,7 @@ func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { } resp := Response{} path := fmt.Sprintf("%s/public?command=returnCurrencies", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp.Data) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) if err != nil { return resp.Data, err @@ -224,7 +224,7 @@ func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { func (p *Poloniex) GetLoanOrders(currency string) (PoloniexLoanOrders, error) { resp := PoloniexLoanOrders{} path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", POLONIEX_API_URL, currency) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return resp, err diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index d3e52fb5..9a8d7632 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -80,7 +80,7 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { "%s/%s/%s", etherchainAPIURL, etherchainAccountMultiple, addresses, ) result := EtherchainBalanceResponse{} - err := common.SendHTTPGetRequest(url, true, &result) + err := common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return result, err } @@ -90,17 +90,62 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 // GetCryptoIDAddress queries CryptoID for an address balance for a // specified cryptocurrency func GetCryptoIDAddress(address string, coinType string) (float64, error) { ok, err := common.IsValidCryptoAddress(address, coinType) if !ok || err != nil { return 0, errors.New("invalid address") +======= +// GetBlockrBalanceSingle queries Blockr for an address balance for either a +// LTC or a BTC single address +func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { + valid, _ := common.IsValidCryptoAddress(address, coinType) + if !valid { + return BlockrAddressBalanceSingle{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) } + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, address, + ) + result := BlockrAddressBalanceSingle{} + err := common.SendHTTPGetRequest(url, true, false, &result) + if err != nil { + return result, err + } + if result.Status != "success" { + return result, errors.New(result.Message) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. + } + +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 var result interface{} url := fmt.Sprintf("%s/%s/api.dws?q=getbalance&a=%s", cryptoIDAPIURL, common.StringToLower(coinType), address) err = common.SendHTTPGetRequest(url, true, &result) +======= +// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC +// or a BTC multiple addresses +func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { + for _, add := range addresses { + valid, _ := common.IsValidCryptoAddress(add, coinType) + if !valid { + return BlockrAddressBalanceMulti{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) + } + } + addressesStr := common.JoinStrings(addresses, ",") + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, addressesStr, + ) + result := BlockrAddressBalanceMulti{} + err := common.SendHTTPGetRequest(url, true, false, &result) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. if err != nil { return 0, err } From 60fc00a5b310cbe3052f4a990dcb9ad04db8e3c6 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 13 Sep 2017 12:36:32 +1000 Subject: [PATCH 4/5] Liqui Package - Fixed linter issues and expanded code cov. --- exchanges/liqui/liqui.go | 212 +++++++++++++++------------------ exchanges/liqui/liqui_test.go | 125 +++++++++++++++++++ exchanges/liqui/liqui_types.go | 85 +++++++------ 3 files changed, 270 insertions(+), 152 deletions(-) create mode 100644 exchanges/liqui/liqui_test.go diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index f6baf699..c9f8cc2f 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -16,29 +16,31 @@ import ( ) const ( - LIQUI_API_PUBLIC_URL = "https://api.Liqui.io/api" - LIQUI_API_PRIVATE_URL = "https://api.Liqui.io/tapi" - LIQUI_API_PUBLIC_VERSION = "3" - LIQUI_API_PRIVATE_VERSION = "1" - LIQUI_INFO = "info" - LIQUI_TICKER = "ticker" - LIQUI_DEPTH = "depth" - LIQUI_TRADES = "trades" - LIQUI_ACCOUNT_INFO = "getInfo" - LIQUI_TRADE = "Trade" - LIQUI_ACTIVE_ORDERS = "ActiveOrders" - LIQUI_ORDER_INFO = "OrderInfo" - LIQUI_CANCEL_ORDER = "CancelOrder" - LIQUI_TRADE_HISTORY = "TradeHistory" - LIQUI_WITHDRAW_COIN = "WithdrawCoin" + liquiAPIPublicURL = "https://api.Liqui.io/api" + liquiAPIPrivateURL = "https://api.Liqui.io/tapi" + liquiAPIPublicVersion = "3" + liquiAPIPrivateVersion = "1" + liquiInfo = "info" + liquiTicker = "ticker" + liquiDepth = "depth" + liquiTrades = "trades" + liquiAccountInfo = "getInfo" + liquiTrade = "Trade" + liquiActiveOrders = "ActiveOrders" + liquiOrderInfo = "OrderInfo" + liquiCancelOrder = "CancelOrder" + liquiTradeHistory = "TradeHistory" + liquiWithdrawCoin = "WithdrawCoin" ) +// Liqui is the overarching type across the liqui package type Liqui struct { exchange.Base - Ticker map[string]LiquiTicker - Info LiquiInfo + Ticker map[string]Ticker + Info Info } +// SetDefaults sets current default values for liqui func (l *Liqui) SetDefaults() { l.Name = "Liqui" l.Enabled = false @@ -46,7 +48,7 @@ func (l *Liqui) SetDefaults() { l.Verbose = false l.Websocket = false l.RESTPollingDelay = 10 - l.Ticker = make(map[string]LiquiTicker) + l.Ticker = make(map[string]Ticker) l.RequestCurrencyPairFormat.Delimiter = "_" l.RequestCurrencyPairFormat.Uppercase = false l.RequestCurrencyPairFormat.Separator = "-" @@ -55,6 +57,7 @@ func (l *Liqui) SetDefaults() { l.AssetTypes = []string{ticker.Spot} } +// Setup sets exchange configuration parameters for liqui func (l *Liqui) Setup(exch config.ExchangeConfig) { if !exch.Enabled { l.SetEnabled(false) @@ -79,15 +82,18 @@ func (l *Liqui) Setup(exch config.ExchangeConfig) { } } +// GetFee returns a fee for a specific currency func (l *Liqui) GetFee(currency string) (float64, error) { + log.Println(l.Info.Pairs) val, ok := l.Info.Pairs[common.StringToLower(currency)] if !ok { - return 0, errors.New("Currency does not exist") + return 0, errors.New("currency does not exist") } return val.Fee, nil } +// GetAvailablePairs returns all available pairs func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { var pairs []string for x, y := range l.Info.Pairs { @@ -99,79 +105,77 @@ func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { return pairs } -func (l *Liqui) GetInfo() (LiquiInfo, error) { - req := fmt.Sprintf("%s/%s/%s/", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_INFO) - resp := LiquiInfo{} - err := common.SendHTTPGetRequest(req, true, l.Verbose, &resp) +// GetInfo provides all the information about currently active pairs, such as +// the maximum number of digits after the decimal point, the minimum price, the +// maximum price, the minimum transaction size, whether the pair is hidden, the +// commission for each pair. +func (l *Liqui) GetInfo() (Info, error) { + resp := Info{} + req := fmt.Sprintf("%s/%s/%s/", liquiAPIPublicURL, liquiAPIPublicVersion, liquiInfo) - if err != nil { - return resp, err - } - - return resp, nil + return resp, common.SendHTTPGetRequest(req, true, l.Verbose, &resp) } -func (l *Liqui) GetTicker(symbol string) (map[string]LiquiTicker, error) { +// GetTicker returns information about currently active pairs, such as: the +// maximum price, the minimum price, average price, trade volume, trade volume +// in currency, the last trade, Buy and Sell price. All information is provided +// over the past 24 hours. +// +// currencyPair - example "eth_btc" +func (l *Liqui) GetTicker(currencyPair string) (map[string]Ticker, error) { type Response struct { - Data map[string]LiquiTicker + Data map[string]Ticker } response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TICKER, symbol) - err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) + req := fmt.Sprintf("%s/%s/%s/%s", liquiAPIPublicURL, liquiAPIPublicVersion, liquiTicker, currencyPair) - if err != nil { - return nil, err - } - return response.Data, nil + return response.Data, + common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) } -func (l *Liqui) GetDepth(symbol string) (LiquiOrderbook, error) { +// GetDepth information about active orders on the pair. Additionally it accepts +// an optional GET-parameter limit, which indicates how many orders should be +// displayed (150 by default). Is set to less than 2000. +func (l *Liqui) GetDepth(currencyPair string) (Orderbook, error) { type Response struct { - Data map[string]LiquiOrderbook + Data map[string]Orderbook } response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_DEPTH, symbol) + req := fmt.Sprintf("%s/%s/%s/%s", liquiAPIPublicURL, liquiAPIPublicVersion, liquiDepth, currencyPair) - err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) - if err != nil { - return LiquiOrderbook{}, err - } - - depth := response.Data[symbol] - return depth, nil + return response.Data[currencyPair], + common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) } -func (l *Liqui) GetTrades(symbol string) ([]LiquiTrades, error) { +// GetTrades returns information about the last trades. Additionally it accepts +// an optional GET-parameter limit, which indicates how many orders should be +// displayed (150 by default). The maximum allowable value is 2000. +func (l *Liqui) GetTrades(currencyPair string) ([]Trades, error) { type Response struct { - Data map[string][]LiquiTrades + Data map[string][]Trades } response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TRADES, symbol) + req := fmt.Sprintf("%s/%s/%s/%s", liquiAPIPublicURL, liquiAPIPublicVersion, liquiTrades, currencyPair) - err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) - if err != nil { - return []LiquiTrades{}, err - } - - trades := response.Data[symbol] - return trades, nil + return response.Data[currencyPair], + common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) } -func (l *Liqui) GetAccountInfo() (LiquiAccountInfo, error) { - var result LiquiAccountInfo - err := l.SendAuthenticatedHTTPRequest(LIQUI_ACCOUNT_INFO, url.Values{}, &result) +// GetAccountInfo returns information about the user’s current balance, API-key +// privileges, the number of open orders and Server Time. To use this method you +// need a privilege of the key info. +func (l *Liqui) GetAccountInfo() (AccountInfo, error) { + var result AccountInfo - if err != nil { - return result, err - } - - return result, nil + return result, + l.SendAuthenticatedHTTPRequest(liquiAccountInfo, url.Values{}, &result) } -//to-do: convert orderid to int64 +// Trade creates orders on the exchange. +// to-do: convert orderid to int64 func (l *Liqui) Trade(pair, orderType string, amount, price float64) (float64, error) { req := url.Values{} req.Add("pair", pair) @@ -179,51 +183,37 @@ func (l *Liqui) Trade(pair, orderType string, amount, price float64) (float64, e req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64)) - var result LiquiTrade - err := l.SendAuthenticatedHTTPRequest(LIQUI_TRADE, req, &result) + var result Trade - if err != nil { - return 0, err - } - - return result.OrderID, nil + return result.OrderID, l.SendAuthenticatedHTTPRequest(liquiTrade, req, &result) } -func (l *Liqui) GetActiveOrders(pair string) (map[string]LiquiActiveOrders, error) { +// GetActiveOrders returns the list of your active orders. +func (l *Liqui) GetActiveOrders(pair string) (map[string]ActiveOrders, error) { req := url.Values{} req.Add("pair", pair) - var result map[string]LiquiActiveOrders - err := l.SendAuthenticatedHTTPRequest(LIQUI_ACTIVE_ORDERS, req, &result) - - if err != nil { - return result, err - } - - return result, nil + var result map[string]ActiveOrders + return result, l.SendAuthenticatedHTTPRequest(liquiActiveOrders, req, &result) } -func (l *Liqui) GetOrderInfo(OrderID int64) (map[string]LiquiOrderInfo, error) { +// GetOrderInfo returns the information on particular order. +func (l *Liqui) GetOrderInfo(OrderID int64) (map[string]OrderInfo, error) { req := url.Values{} req.Add("order_id", strconv.FormatInt(OrderID, 10)) - var result map[string]LiquiOrderInfo - err := l.SendAuthenticatedHTTPRequest(LIQUI_ORDER_INFO, req, &result) - - if err != nil { - return result, err - } - - return result, nil + var result map[string]OrderInfo + return result, l.SendAuthenticatedHTTPRequest(liquiOrderInfo, req, &result) } +// CancelOrder method is used for order cancelation. func (l *Liqui) CancelOrder(OrderID int64) (bool, error) { req := url.Values{} req.Add("order_id", strconv.FormatInt(OrderID, 10)) - var result LiquiCancelOrder - err := l.SendAuthenticatedHTTPRequest(LIQUI_CANCEL_ORDER, req, &result) + var result CancelOrder + err := l.SendAuthenticatedHTTPRequest(liquiCancelOrder, req, &result) if err != nil { return false, err } @@ -231,38 +221,30 @@ func (l *Liqui) CancelOrder(OrderID int64) (bool, error) { return true, nil } -func (l *Liqui) GetTradeHistory(vals url.Values, pair string) (map[string]LiquiTradeHistory, error) { +// GetTradeHistory returns trade history +func (l *Liqui) GetTradeHistory(vals url.Values, pair string) (map[string]TradeHistory, error) { if pair != "" { vals.Add("pair", pair) } - var result map[string]LiquiTradeHistory - err := l.SendAuthenticatedHTTPRequest(LIQUI_TRADE_HISTORY, vals, &result) - - if err != nil { - return result, err - } - - return result, nil + var result map[string]TradeHistory + return result, l.SendAuthenticatedHTTPRequest(liquiTradeHistory, vals, &result) } +// WithdrawCoins is designed for cryptocurrency withdrawals. // API mentions that this isn't active now, but will be soon - you must provide the first 8 characters of the key // in your ticket to support. -func (l *Liqui) WithdrawCoins(coin string, amount float64, address string) (LiquiWithdrawCoins, error) { +func (l *Liqui) WithdrawCoins(coin string, amount float64, address string) (WithdrawCoins, error) { req := url.Values{} req.Add("coinName", coin) req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("address", address) - var result LiquiWithdrawCoins - err := l.SendAuthenticatedHTTPRequest(LIQUI_WITHDRAW_COIN, req, &result) - - if err != nil { - return result, err - } - return result, nil + var result WithdrawCoins + return result, l.SendAuthenticatedHTTPRequest(liquiWithdrawCoin, req, &result) } +// SendAuthenticatedHTTPRequest sends an authenticated http request to liqui func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { if !l.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) @@ -280,7 +262,7 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r hmac := common.GetHMAC(common.HashSHA512, []byte(encoded), []byte(l.APISecret)) if l.Verbose { - log.Printf("Sending POST request to %s calling method %s with params %s\n", LIQUI_API_PRIVATE_URL, method, encoded) + log.Printf("Sending POST request to %s calling method %s with params %s\n", liquiAPIPrivateURL, method, encoded) } headers := make(map[string]string) @@ -288,15 +270,14 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r headers["Sign"] = common.HexEncodeToString(hmac) headers["Content-Type"] = "application/x-www-form-urlencoded" - resp, err := common.SendHTTPRequest("POST", LIQUI_API_PRIVATE_URL, headers, strings.NewReader(encoded)) - + resp, err := common.SendHTTPRequest("POST", liquiAPIPrivateURL, headers, strings.NewReader(encoded)) if err != nil { return err } - response := LiquiResponse{} - err = common.JSONDecode([]byte(resp), &response) + response := Response{} + err = common.JSONDecode([]byte(resp), &response) if err != nil { return err } @@ -306,15 +287,14 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r } jsonEncoded, err := common.JSONEncode(response.Return) - if err != nil { return err } err = common.JSONDecode(jsonEncoded, &result) - if err != nil { return err } + return nil } diff --git a/exchanges/liqui/liqui_test.go b/exchanges/liqui/liqui_test.go new file mode 100644 index 00000000..eaddeb06 --- /dev/null +++ b/exchanges/liqui/liqui_test.go @@ -0,0 +1,125 @@ +package liqui + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var l Liqui + +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + l.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + liquiConfig, err := cfg.GetExchangeConfig("Liqui") + if err != nil { + t.Error("Test Failed - liqui Setup() init error") + } + + liquiConfig.AuthenticatedAPISupport = true + liquiConfig.APIKey = apiKey + liquiConfig.APISecret = apiSecret + + l.Setup(liquiConfig) +} + +func TestGetFee(t *testing.T) { + _, err := l.GetFee("usd") + if err == nil { + t.Error("Test Failed - liqui GetFee() error", err) + } +} + +func TestGetAvailablePairs(t *testing.T) { + v := l.GetAvailablePairs(false) + if len(v) != 0 { + t.Error("Test Failed - liqui GetFee() error") + } +} + +func TestGetInfo(t *testing.T) { + _, err := l.GetInfo() + if err != nil { + t.Error("Test Failed - liqui GetInfo() error", err) + } +} + +func TestGetTicker(t *testing.T) { + _, err := l.GetTicker("eth_btc") + if err != nil { + t.Error("Test Failed - liqui GetTicker() error", err) + } +} + +func TestGetDepth(t *testing.T) { + _, err := l.GetDepth("eth_btc") + if err != nil { + t.Error("Test Failed - liqui GetDepth() error", err) + } +} + +func TestGetTrades(t *testing.T) { + _, err := l.GetTrades("eth_btc") + if err != nil { + t.Error("Test Failed - liqui GetTrades() error", err) + } +} + +func TestGetAccountInfo(t *testing.T) { + _, err := l.GetAccountInfo() + if err == nil { + t.Error("Test Failed - liqui GetAccountInfo() error", err) + } +} + +func TestTrade(t *testing.T) { + _, err := l.Trade("", "", 0, 1) + if err == nil { + t.Error("Test Failed - liqui Trade() error", err) + } +} + +func TestGetActiveOrders(t *testing.T) { + _, err := l.GetActiveOrders("eth_btc") + if err == nil { + t.Error("Test Failed - liqui GetActiveOrders() error", err) + } +} + +func TestGetOrderInfo(t *testing.T) { + _, err := l.GetOrderInfo(1337) + if err == nil { + t.Error("Test Failed - liqui GetOrderInfo() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + _, err := l.CancelOrder(1337) + if err == nil { + t.Error("Test Failed - liqui CancelOrder() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + _, err := l.GetTradeHistory(url.Values{}, "") + if err == nil { + t.Error("Test Failed - liqui GetTradeHistory() error", err) + } +} + +func TestWithdrawCoins(t *testing.T) { + _, err := l.WithdrawCoins("btc", 1337, "someaddr") + if err == nil { + t.Error("Test Failed - liqui WithdrawCoins() error", err) + } +} diff --git a/exchanges/liqui/liqui_types.go b/exchanges/liqui/liqui_types.go index bf25573d..52a8167d 100644 --- a/exchanges/liqui/liqui_types.go +++ b/exchanges/liqui/liqui_types.go @@ -1,6 +1,23 @@ package liqui -type LiquiTicker struct { +// Info holds the current pair information as well as server time +type Info struct { + ServerTime int64 `json:"server_time"` + Pairs map[string]PairData `json:"pairs"` +} + +// PairData is a sub-type for Info +type PairData struct { + DecimalPlaces int `json:"decimal_places"` + MinPrice float64 `json:"min_price"` + MaxPrice float64 `json:"max_price"` + MinAmount float64 `json:"min_amount"` + Hidden int `json:"hidden"` + Fee float64 `json:"fee"` +} + +// Ticker contains ticker information +type Ticker struct { High float64 Low float64 Avg float64 @@ -12,12 +29,14 @@ type LiquiTicker struct { Updated int64 } -type LiquiOrderbook struct { +// Orderbook references both ask and bid sides +type Orderbook struct { Asks [][]float64 `json:"asks"` Bids [][]float64 `json:"bids"` } -type LiquiTrades struct { +// Trades contains trade information +type Trades struct { Type string `json:"type"` Price float64 `json:"bid"` Amount float64 `json:"amount"` @@ -25,27 +44,8 @@ type LiquiTrades struct { Timestamp int64 `json:"timestamp"` } -type LiquiResponse struct { - Return interface{} `json:"return"` - Success int `json:"success"` - Error string `json:"error"` -} - -type LiquiPair struct { - DecimalPlaces int `json:"decimal_places"` - MinPrice float64 `json:"min_price"` - MaxPrice float64 `json:"max_price"` - MinAmount float64 `json:"min_amount"` - Hidden int `json:"hidden"` - Fee float64 `json:"fee"` -} - -type LiquiInfo struct { - ServerTime int64 `json:"server_time"` - Pairs map[string]LiquiPair `json:"pairs"` -} - -type LiquiAccountInfo struct { +// AccountInfo contains full account details information +type AccountInfo struct { Funds map[string]float64 `json:"funds"` Rights struct { Info bool `json:"info"` @@ -57,14 +57,8 @@ type LiquiAccountInfo struct { OpenOrders int `json:"open_orders"` } -type LiquiTrade struct { - Received float64 `json:"received"` - Remains float64 `json:"remains"` - OrderID float64 `json:"order_id"` - Funds map[string]float64 `json:"funds"` -} - -type LiquiActiveOrders struct { +// ActiveOrders holds active order information +type ActiveOrders struct { Pair string `json:"pair"` Type string `json:"sell"` Amount float64 `json:"amount"` @@ -73,7 +67,8 @@ type LiquiActiveOrders struct { Status int `json:"status"` } -type LiquiOrderInfo struct { +// OrderInfo holds specific order information +type OrderInfo struct { Pair string `json:"pair"` Type string `json:"sell"` StartAmount float64 `json:"start_amount"` @@ -83,12 +78,22 @@ type LiquiOrderInfo struct { Status int `json:"status"` } -type LiquiCancelOrder struct { +// CancelOrder holds cancelled order information +type CancelOrder struct { OrderID float64 `json:"order_id"` Funds map[string]float64 `json:"funds"` } -type LiquiTradeHistory struct { +// Trade holds trading information +type Trade struct { + Received float64 `json:"received"` + Remains float64 `json:"remains"` + OrderID float64 `json:"order_id"` + Funds map[string]float64 `json:"funds"` +} + +// TradeHistory contains trade history data +type TradeHistory struct { Pair string `json:"pair"` Type string `json:"type"` Amount float64 `json:"amount"` @@ -98,7 +103,15 @@ type LiquiTradeHistory struct { Timestamp float64 `json:"timestamp"` } -type LiquiWithdrawCoins struct { +// Response is a generalized return type +type Response struct { + Return interface{} `json:"return"` + Success int `json:"success"` + Error string `json:"error"` +} + +// WithdrawCoins shows the amount of coins withdrawn from liqui not yet available +type WithdrawCoins struct { TID int64 `json:"tId"` AmountSent float64 `json:"amountSent"` Funds map[string]float64 `json:"funds"` From a86462b338da3d686185b8e4116f8401f6ff208e Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Mon, 18 Sep 2017 15:58:24 +1000 Subject: [PATCH 5/5] Fixed merge issues, fixed race condition in gemini package. --- common/common.go | 6 ----- exchanges/gemini/gemini.go | 6 +++++ exchanges/wex/wex.go | 8 +++---- portfolio/portfolio.go | 47 +------------------------------------- 4 files changed, 11 insertions(+), 56 deletions(-) diff --git a/common/common.go b/common/common.go index 1d4da4ce..2717e727 100644 --- a/common/common.go +++ b/common/common.go @@ -306,13 +306,7 @@ func SendHTTPGetRequest(url string, jsonDecode, isVerbose bool, result interface } if res.StatusCode != 200 { -<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 - log.Printf("HTTP status code: %d\n", res.StatusCode) - log.Printf("URL: %s\n", url) - return errors.New("status code was not 200") -======= return fmt.Errorf("common.SendHTTPGetRequest() error: HTTP status code %d", res.StatusCode) ->>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. } contents, err := ioutil.ReadAll(res.Body) diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index e83585f5..9ff8c6b9 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -7,6 +7,7 @@ import ( "net/url" "strconv" "strings" + "sync" "time" "github.com/thrasher-/gocryptotrader/common" @@ -71,10 +72,13 @@ var ( // needed append the sandbox function as well. type Gemini struct { exchange.Base + M sync.Mutex } // AddSession adds a new session to the gemini base func (g *Gemini) AddSession(sessionID int, apiKey, apiSecret, role string, needsHeartbeat bool) error { + g.M.Lock() + defer g.M.Unlock() if sessionAPIKey == nil { IsSession = true sessionAPIKey = make(map[int]string) @@ -139,6 +143,8 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { // Session is a session manager for differing APIKeys and roles, use this for all function // calls in this package func (g *Gemini) Session(sessionID int) *Gemini { + g.M.Lock() + defer g.M.Unlock() g.APIUrl = geminiAPIURL _, ok := sessionAPIKey[sessionID] if !ok { diff --git a/exchanges/wex/wex.go b/exchanges/wex/wex.go index 3ac246c1..ee5ba2fe 100644 --- a/exchanges/wex/wex.go +++ b/exchanges/wex/wex.go @@ -94,7 +94,7 @@ func (w *WEX) GetFee() float64 { func (w *WEX) GetInfo() (Info, error) { req := fmt.Sprintf("%s/%s/%s/", wexAPIPublicURL, wexAPIPublicVersion, wexInfo) resp := Info{} - err := common.SendHTTPGetRequest(req, true, &resp) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &resp) if err != nil { return resp, err @@ -111,7 +111,7 @@ func (w *WEX) GetTicker(symbol string) (map[string]Ticker, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTicker, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data) if err != nil { return nil, err @@ -128,7 +128,7 @@ func (w *WEX) GetDepth(symbol string) (Orderbook, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexDepth, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data) if err != nil { return Orderbook{}, err } @@ -146,7 +146,7 @@ func (w *WEX) GetTrades(symbol string) ([]Trades, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTrades, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data) if err != nil { return nil, err } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index 9a8d7632..ff486a79 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -90,62 +90,17 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } -<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 // GetCryptoIDAddress queries CryptoID for an address balance for a // specified cryptocurrency func GetCryptoIDAddress(address string, coinType string) (float64, error) { ok, err := common.IsValidCryptoAddress(address, coinType) if !ok || err != nil { return 0, errors.New("invalid address") -======= -// GetBlockrBalanceSingle queries Blockr for an address balance for either a -// LTC or a BTC single address -func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { - valid, _ := common.IsValidCryptoAddress(address, coinType) - if !valid { - return BlockrAddressBalanceSingle{}, fmt.Errorf( - "Not a %s address", common.StringToUpper(coinType), - ) } - url := fmt.Sprintf( - "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, - blockrAPIVersion, blockrAddressBalance, address, - ) - result := BlockrAddressBalanceSingle{} - err := common.SendHTTPGetRequest(url, true, false, &result) - if err != nil { - return result, err - } - if result.Status != "success" { - return result, errors.New(result.Message) ->>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. - } - -<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 var result interface{} url := fmt.Sprintf("%s/%s/api.dws?q=getbalance&a=%s", cryptoIDAPIURL, common.StringToLower(coinType), address) - err = common.SendHTTPGetRequest(url, true, &result) -======= -// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC -// or a BTC multiple addresses -func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { - for _, add := range addresses { - valid, _ := common.IsValidCryptoAddress(add, coinType) - if !valid { - return BlockrAddressBalanceMulti{}, fmt.Errorf( - "Not a %s address", common.StringToUpper(coinType), - ) - } - } - addressesStr := common.JoinStrings(addresses, ",") - url := fmt.Sprintf( - "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, - blockrAPIVersion, blockrAddressBalance, addressesStr, - ) - result := BlockrAddressBalanceMulti{} - err := common.SendHTTPGetRequest(url, true, false, &result) ->>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. + err = common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return 0, err }