package main import ( "bytes" "errors" "fmt" "log" "net/url" "strconv" "time" ) const ( GDAX_API_URL = "https://api.gdax.com/" GDAX_API_VERISON = "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" ) type GDAX struct { Name string Enabled bool Verbose bool Websocket bool RESTPollingDelay time.Duration AuthenticatedAPISupport bool Password, APIKey, APISecret string TakerFee, MakerFee float64 BaseCurrencies []string AvailablePairs []string EnabledPairs []string } 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 { ID string `json:"id"` BaseCurrency string `json:"base_currency"` QuoteCurrency string `json:"quote_currency"` BaseMinSize float64 `json:"base_min_size,string"` BaseMaxSize int64 `json:"base_max_size,string"` QuoteIncrement float64 `json:"quote_increment,string"` DisplayName string `json:"string"` } type GDAXOrderL1L2 struct { Price float64 Amount float64 NumOrders float64 } type GDAXOrderL3 struct { Price float64 Amount float64 OrderID string } type GDAXOrderbookL1L2 struct { Sequence int64 `json:"sequence"` Bids [][]GDAXOrderL1L2 `json:"asks"` Asks [][]GDAXOrderL1L2 `json:"asks"` } type GDAXOrderbookL3 struct { Sequence int64 `json:"sequence"` Bids [][]GDAXOrderL3 `json:"asks"` Asks [][]GDAXOrderL3 `json:"asks"` } type GDAXOrderbookResponse struct { Sequence int64 `json:"sequence"` Bids [][]interface{} `json:"bids"` Asks [][]interface{} `json:"asks"` } type GDAXTrade struct { TradeID int64 `json:"trade_id"` Price float64 `json:"price,string"` Size float64 `json:"size,string"` Time string `json:"time"` Side string `json:"side"` } type GDAXStats struct { Open float64 `json:"open,string"` High float64 `json:"high,string"` Low float64 `json:"low,string"` Volume float64 `json:"volume,string"` } type GDAXCurrency 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 } func (g *GDAX) SetDefaults() { g.Name = "GDAX" g.Enabled = false g.Verbose = false g.TakerFee = 0.25 g.MakerFee = 0 g.Verbose = false g.Websocket = false g.RESTPollingDelay = 10 } func (g *GDAX) GetName() string { return g.Name } func (g *GDAX) SetEnabled(enabled bool) { g.Enabled = enabled } func (g *GDAX) IsEnabled() bool { return g.Enabled } func (g *GDAX) Setup(exch Exchanges) { if !exch.Enabled { g.SetEnabled(false) } else { g.Enabled = true g.AuthenticatedAPISupport = exch.AuthenticatedAPISupport g.SetAPIKeys(exch.ClientID, exch.APIKey, exch.APISecret) g.RESTPollingDelay = exch.RESTPollingDelay g.Verbose = exch.Verbose g.Websocket = exch.Websocket g.BaseCurrencies = SplitStrings(exch.BaseCurrencies, ",") g.AvailablePairs = SplitStrings(exch.AvailablePairs, ",") g.EnabledPairs = SplitStrings(exch.EnabledPairs, ",") } } func (k *GDAX) GetEnabledCurrencies() []string { return k.EnabledPairs } func (g *GDAX) Start() { go g.Run() } func (g *GDAX) GetFee(maker bool) float64 { if maker { return g.MakerFee } else { return g.TakerFee } } func (g *GDAX) Run() { if g.Verbose { log.Printf("%s Websocket: %s. (url: %s).\n", g.GetName(), IsEnabled(g.Websocket), GDAX_WEBSOCKET_URL) log.Printf("%s polling delay: %ds.\n", g.GetName(), g.RESTPollingDelay) log.Printf("%s %d currencies enabled: %s.\n", g.GetName(), len(g.EnabledPairs), g.EnabledPairs) } if g.Websocket { go g.WebsocketClient() } exchangeProducts, err := g.GetProducts() if err != nil { log.Printf("%s Failed to get available products.\n", g.GetName()) } else { currencies := []string{} for _, x := range exchangeProducts { if x.ID != "BTC" && x.ID != "USD" && x.ID != "GBP" { currencies = append(currencies, x.ID[0:3]+x.ID[4:]) } } diff := StringSliceDifference(g.AvailablePairs, currencies) if len(diff) > 0 { exch, err := GetExchangeConfig(g.Name) if err != nil { log.Println(err) } else { log.Printf("%s Updating available pairs. Difference: %s.\n", g.Name, diff) exch.AvailablePairs = JoinStrings(currencies, ",") UpdateExchangeConfig(exch) } } } for g.Enabled { for _, x := range g.EnabledPairs { currency := x[0:3] + "-" + x[3:] go func() { stats, err := g.GetStats(currency) if err != nil { log.Println(err) return } ticker, err := g.GetTicker(currency) if err != nil { log.Println(err) return } log.Printf("GDAX %s: Last %f High %f Low %f Volume %f\n", currency, ticker.Price, stats.High, stats.Low, stats.Volume) AddExchangeInfo(g.GetName(), currency[0:3], currency[4:], ticker.Price, stats.Volume) }() } time.Sleep(time.Second * g.RESTPollingDelay) } } func (g *GDAX) SetAPIKeys(password, apiKey, apiSecret string) { if !g.AuthenticatedAPISupport { return } g.Password = password g.APIKey = apiKey result, err := Base64Decode(apiSecret) if err != nil { log.Printf("%s unable to decode secret key.", g.GetName()) g.Enabled = false return } g.APISecret = string(result) } func (g *GDAX) GetProducts() ([]GDAXProduct, error) { products := []GDAXProduct{} err := SendHTTPGetRequest(GDAX_API_URL+GDAX_PRODUCTS, true, &products) if err != nil { return nil, err } return products, nil } func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { orderbook := GDAXOrderbookResponse{} path := "" 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) } err := SendHTTPGetRequest(path, true, &orderbook) if err != nil { return nil, err } if level == 3 { ob := GDAXOrderbookL3{} 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 } order := make([]GDAXOrderL3, 1) order[0].Price = price order[0].Amount = amount order[0].OrderID = x[2].(string) ob.Asks = append(ob.Asks, order) } 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 } order := make([]GDAXOrderL3, 1) order[0].Price = price order[0].Amount = amount order[0].OrderID = x[2].(string) ob.Bids = append(ob.Bids, order) } 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 } order := make([]GDAXOrderL1L2, 1) order[0].Price = price order[0].Amount = amount order[0].NumOrders = x[2].(float64) ob.Asks = append(ob.Asks, order) } 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 } order := make([]GDAXOrderL1L2, 1) order[0].Price = price order[0].Amount = amount order[0].NumOrders = x[2].(float64) ob.Bids = append(ob.Bids, order) } return ob, nil } } 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 := SendHTTPGetRequest(path, true, &ticker) if err != nil { return ticker, err } return ticker, nil } func (g *GDAX) GetTickerPrice(currency string) TickerPrice { var tickerPrice TickerPrice ticker, err := g.GetTicker(currency) if err != nil { log.Println(err) return TickerPrice{} } tickerPrice.Ask = ticker.Price tickerPrice.CryptoCurrency = currency tickerPrice.Volume = ticker.Size return tickerPrice } 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 := SendHTTPGetRequest(path, true, &trades) if err != nil { return nil, err } return trades, nil } func (g *GDAX) GetHistoricRates(symbol string, start, end, granularity int64) ([]GDAXHistory, error) { history := []GDAXHistory{} values := url.Values{} if start > 0 { values.Set("start", strconv.FormatInt(start, 10)) } if end > 0 { values.Set("end", strconv.FormatInt(end, 10)) } if granularity > 0 { values.Set("granularity", strconv.FormatInt(granularity, 10)) } path := EncodeURLValues(fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_HISTORY), values) err := SendHTTPGetRequest(path, true, &history) if err != nil { return nil, err } 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 := SendHTTPGetRequest(path, true, &stats) if err != nil { return stats, err } return stats, nil } func (g *GDAX) GetCurrencies() ([]GDAXCurrency, error) { currencies := []GDAXCurrency{} err := SendHTTPGetRequest(GDAX_API_URL+GDAX_CURRENCIES, true, ¤cies) if err != nil { return nil, err } return currencies, nil } 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"` } func (g *GDAX) GetAccounts() ([]GDAXAccountResponse, error) { resp := []GDAXAccountResponse{} err := g.SendAuthenticatedHTTPRequest("GET", GDAX_API_URL+GDAX_ACCOUNTS, nil, &resp) if err != nil { return nil, err } return resp, nil } func (g *GDAX) GetAccount(account string) (GDAXAccountResponse, error) { resp := GDAXAccountResponse{} path := fmt.Sprintf("%s/%s", GDAX_ACCOUNTS, account) err := g.SendAuthenticatedHTTPRequest("GET", GDAX_API_URL+path, nil, &resp) if err != nil { return resp, err } return resp, nil } type GDAXAccountLedgerResponse struct { ID string `json:"id"` CreatedAt string `json:"created_at"` Amount float64 `json:"amount,string"` Balance float64 `json:"balance,string"` Type string `json:"type"` details interface{} `json:"details"` } 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", GDAX_API_URL+path, nil, &resp) if err != nil { return nil, err } return resp, nil } type GDAXAccountHolds struct { ID string `json:"id"` AccountID string `json:"account_id"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Amount float64 `json:"amount,string"` Type string `json:"type"` Reference string `json:"ref"` } 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", GDAX_API_URL+path, nil, &resp) if err != nil { return nil, err } return resp, nil } func (g *GDAX) PlaceOrder(clientRef string, price, amount float64, side string, productID, stp string) (string, error) { request := make(map[string]interface{}) if clientRef != "" { request["client_oid"] = clientRef } request["price"] = strconv.FormatFloat(price, 'f', -1, 64) request["size"] = strconv.FormatFloat(amount, 'f', -1, 64) request["side"] = side request["product_id"] = productID if stp != "" { request["stp"] = stp } type OrderResponse struct { ID string `json:"id"` } resp := OrderResponse{} err := g.SendAuthenticatedHTTPRequest("POST", GDAX_API_URL+GDAX_ORDERS, request, &resp) if err != nil { return "", err } return resp.ID, nil } func (g *GDAX) CancelOrder(orderID string) error { path := fmt.Sprintf("%s/%s", GDAX_ORDERS, orderID) err := g.SendAuthenticatedHTTPRequest("DELETE", GDAX_API_URL+path, nil, nil) if err != nil { return err } return nil } 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"` } func (g *GDAX) GetOrders(params url.Values) ([]GDAXOrdersResponse, error) { path := EncodeURLValues(GDAX_API_URL+GDAX_ORDERS, params) resp := []GDAXOrdersResponse{} err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) if err != nil { return nil, err } return resp, nil } 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"` } func (g *GDAX) GetOrder(orderID string) (GDAXOrderResponse, error) { path := fmt.Sprintf("%s/%s", GDAX_ORDERS, orderID) resp := GDAXOrderResponse{} err := g.SendAuthenticatedHTTPRequest("GET", GDAX_API_URL+path, nil, &resp) if err != nil { return resp, err } return resp, nil } 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"` } func (g *GDAX) GetFills(params url.Values) ([]GDAXFillResponse, error) { path := EncodeURLValues(GDAX_API_URL+GDAX_FILLS, params) resp := []GDAXFillResponse{} err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) if err != nil { return nil, err } return resp, nil } func (g *GDAX) Transfer(transferType string, amount float64, accountID string) error { request := make(map[string]interface{}) request["type"] = transferType request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) request["GDAX_account_id"] = accountID err := g.SendAuthenticatedHTTPRequest("POST", GDAX_API_URL+GDAX_TRANSFERS, request, nil) if err != nil { return err } return nil } type GDAXReportResponse struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` CreatedAt string `json:"created_at"` CompletedAt string `json:"completed_at"` ExpiresAt string `json:"expires_at"` FileURL string `json:"file_url"` Params struct { StartDate string `json:"start_date"` EndDate string `json:"end_date"` } `json:params"` } func (g *GDAX) GetReport(reportType, startDate, endDate string) (GDAXReportResponse, error) { request := make(map[string]interface{}) request["type"] = reportType request["start_date"] = startDate request["end_date"] = endDate resp := GDAXReportResponse{} err := g.SendAuthenticatedHTTPRequest("POST", GDAX_API_URL+GDAX_REPORTS, request, &resp) if err != nil { return resp, err } return resp, nil } func (g *GDAX) GetReportStatus(reportID string) (GDAXReportResponse, error) { path := fmt.Sprintf("%s/%s", GDAX_REPORTS, reportID) resp := GDAXReportResponse{} err := g.SendAuthenticatedHTTPRequest("POST", GDAX_API_URL+path, nil, &resp) if err != nil { return resp, err } return resp, nil } func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] payload := []byte("") if params != nil { payload, err = JSONEncode(params) if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } if g.Verbose { log.Printf("Request JSON: %s\n", payload) } } message := timestamp + method + path + string(payload) hmac := GetHMAC(HASH_SHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) headers["CB-ACCESS-SIGN"] = Base64Encode([]byte(hmac)) headers["CB-ACCESS-TIMESTAMP"] = timestamp headers["CB-ACCESS-KEY"] = g.APIKey headers["CB-ACCESS-PASSPHRASE"] = g.Password headers["Content-Type"] = "application/json" resp, err := SendHTTPRequest(method, GDAX_API_URL+path, headers, bytes.NewBuffer(payload)) if g.Verbose { log.Printf("Recieved raw: \n%s\n", resp) } err = JSONDecode([]byte(resp), &result) if err != nil { return errors.New("Unable to JSON Unmarshal response.") } return nil }