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"` +}