From f27472269ce9153370a7cb2cad8a342f0ca84d0b Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 31 Aug 2017 16:01:28 +1000 Subject: [PATCH] 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) +}