Introduce request package and integrate with exchanges

This commit is contained in:
Ryan O'Hara-Reid
2018-03-27 14:05:15 +11:00
committed by Adrian Gallagher
parent 52dfddbb18
commit 7fc9d20fd7
52 changed files with 1990 additions and 1544 deletions

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strconv"
"strings"
@@ -11,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
@@ -39,13 +41,9 @@ const (
geminiWithdraw = "withdraw/"
geminiHeartbeat = "heartbeat"
// rate limits per minute
geminiPublicRate = 120
geminiPrivateRate = 600
// rates limits per second
geminiPublicRateSec = 1
geminiPrivateRateSec = 5
// gemini limit rates
geminiAuthRate = 100
geminiUnauthRate = 500
// Too many requests returns this
geminiRateError = "429"
@@ -57,7 +55,8 @@ const (
var (
// Session manager
Session map[int]*Gemini
Session map[int]*Gemini
gHandler *request.Handler
)
// Gemini is the overarching type across the Gemini package, create multiple
@@ -69,6 +68,7 @@ type Gemini struct {
exchange.Base
Role string
RequiresHeartBeat bool
*request.Handler
}
// AddSession adds a new session to the gemini base
@@ -76,6 +76,9 @@ func AddSession(g *Gemini, sessionID int, apiKey, apiSecret, role string, needsH
if Session == nil {
Session = make(map[int]*Gemini)
}
if gHandler == nil {
gHandler = new(request.Handler)
}
_, ok := Session[sessionID]
if ok {
@@ -110,6 +113,14 @@ func (g *Gemini) SetDefaults() {
g.ConfigCurrencyPairFormat.Uppercase = true
g.AssetTypes = []string{ticker.Spot}
g.SupportsAutoPairUpdating = true
if gHandler != nil {
g.Handler = gHandler
} else {
g.Handler = new(request.Handler)
}
if g.Handler.Client == nil {
g.SetRequestHandler(g.Name, geminiAuthRate, geminiUnauthRate, new(http.Client))
}
}
// Setup sets exchange configuration parameters
@@ -151,28 +162,33 @@ func (g *Gemini) GetSymbols() ([]string, error) {
symbols := []string{}
path := fmt.Sprintf("%s/v%s/%s", g.APIUrl, geminiAPIVersion, geminiSymbols)
return symbols, common.SendHTTPGetRequest(path, true, g.Verbose, &symbols)
return symbols, g.SendHTTPRequest(path, &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"`
Bid float64 `json:"bid,string"`
Last float64 `json:"last,string"`
Volume map[string]interface{}
Ask float64 `json:"ask,string"`
Bid float64 `json:"bid,string"`
Last float64 `json:"last,string"`
Volume map[string]interface{}
Message string `json:"message"`
}
ticker := Ticker{}
resp := TickerResponse{}
path := fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiTicker, currencyPair)
err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp)
err := g.SendHTTPRequest(path, &resp)
if err != nil {
return ticker, err
}
if resp.Message != "" {
return ticker, errors.New(resp.Message)
}
ticker.Ask = resp.Ask
ticker.Bid = resp.Bid
ticker.Last = resp.Last
@@ -201,7 +217,7 @@ func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook
path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiOrderbook, currencyPair), params)
orderbook := Orderbook{}
return orderbook, common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook)
return orderbook, g.SendHTTPRequest(path, &orderbook)
}
// GetTrades eturn the trades that have executed since the specified timestamp.
@@ -217,7 +233,7 @@ func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, err
path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiTrades, currencyPair), params)
trades := []Trade{}
return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades)
return trades, g.SendHTTPRequest(path, &trades)
}
// GetAuction returns auction information
@@ -225,7 +241,7 @@ func (g *Gemini) GetAuction(currencyPair string) (Auction, error) {
path := fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiAuction, currencyPair)
auction := Auction{}
return auction, common.SendHTTPGetRequest(path, true, g.Verbose, &auction)
return auction, g.SendHTTPRequest(path, &auction)
}
// GetAuctionHistory returns the auction events, optionally including
@@ -243,7 +259,7 @@ func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]Au
path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params)
auctionHist := []AuctionHistory{}
return auctionHist, common.SendHTTPGetRequest(path, true, g.Verbose, &auctionHist)
return auctionHist, g.SendHTTPRequest(path, &auctionHist)
}
func (g *Gemini) isCorrectSession(role string) error {
@@ -286,6 +302,10 @@ func (g *Gemini) CancelOrder(OrderID int64) (Order, error) {
if err != nil {
return Order{}, err
}
if response.Message != "" {
return response, errors.New(response.Message)
}
return response, nil
}
@@ -300,7 +320,14 @@ func (g *Gemini) CancelOrders(CancelBySession bool) (OrderResult, error) {
path = geminiOrderCancelSession
}
return response, g.SendAuthenticatedHTTPRequest("POST", path, nil, &response)
err := g.SendAuthenticatedHTTPRequest("POST", path, nil, &response)
if err != nil {
return response, err
}
if response.Message != "" {
return response, errors.New(response.Message)
}
return response, nil
}
// GetOrderStatus returns the status for an order
@@ -310,16 +337,32 @@ func (g *Gemini) GetOrderStatus(orderID int64) (Order, error) {
response := Order{}
return response,
g.SendAuthenticatedHTTPRequest("POST", geminiOrderStatus, request, &response)
err := g.SendAuthenticatedHTTPRequest("POST", geminiOrderStatus, request, &response)
if err != nil {
return response, err
}
if response.Message != "" {
return response, errors.New(response.Message)
}
return response, nil
}
// GetOrders returns active orders in the market
func (g *Gemini) GetOrders() ([]Order, error) {
response := []Order{}
var response struct {
orders []Order
Message string `json:"message"`
}
return response,
g.SendAuthenticatedHTTPRequest("POST", geminiOrders, nil, &response)
err := g.SendAuthenticatedHTTPRequest("POST", geminiOrders, nil, &response)
if err != nil {
return response.orders, err
}
if response.Message != "" {
return response.orders, errors.New(response.Message)
}
return response.orders, nil
}
// GetTradeHistory returns an array of trades that have been on the exchange
@@ -359,8 +402,14 @@ func (g *Gemini) GetBalances() ([]Balance, error) {
func (g *Gemini) GetDepositAddress(depositAddlabel, currency string) (DepositAddress, error) {
response := DepositAddress{}
return response,
g.SendAuthenticatedHTTPRequest("POST", geminiDeposit+"/"+currency+"/"+geminiNewAddress, nil, &response)
err := g.SendAuthenticatedHTTPRequest("POST", geminiDeposit+"/"+currency+"/"+geminiNewAddress, nil, &response)
if err != nil {
return response, err
}
if response.Message != "" {
return response, errors.New(response.Message)
}
return response, nil
}
// WithdrawCrypto withdraws crypto currency to a whitelisted address
@@ -370,20 +419,38 @@ func (g *Gemini) WithdrawCrypto(address, currency string, amount float64) (Withd
request["address"] = address
request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
return response,
g.SendAuthenticatedHTTPRequest("POST", geminiWithdraw+currency, nil, &response)
err := g.SendAuthenticatedHTTPRequest("POST", geminiWithdraw+currency, nil, &response)
if err != nil {
return response, err
}
if response.Message != "" {
return response, errors.New(response.Message)
}
return response, nil
}
// PostHeartbeat sends a maintenance heartbeat to the exchange for all heartbeat
// maintaned sessions
func (g *Gemini) PostHeartbeat() (string, error) {
type Response struct {
Result string `json:"result"`
Result string `json:"result"`
Message string `json:"message"`
}
response := Response{}
return response.Result,
g.SendAuthenticatedHTTPRequest("POST", geminiHeartbeat, nil, &response)
err := g.SendAuthenticatedHTTPRequest("POST", geminiHeartbeat, nil, &response)
if err != nil {
return response.Result, err
}
if response.Message != "" {
return response.Result, errors.New(response.Message)
}
return response.Result, nil
}
// SendHTTPRequest sends an unauthenticated request
func (g *Gemini) SendHTTPRequest(path string, result interface{}) error {
return g.SendPayload("GET", path, nil, nil, result, false, g.Verbose)
}
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the
@@ -420,23 +487,5 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st
headers["X-GEMINI-PAYLOAD"] = PayloadBase64
headers["X-GEMINI-SIGNATURE"] = common.HexEncodeToString(hmac)
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)
}
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)
}
}
}
return common.JSONDecode([]byte(resp), &result)
return g.SendPayload(method, g.APIUrl+"/v1/"+path, headers, strings.NewReader(""), result, true, g.Verbose)
}

View File

@@ -7,10 +7,6 @@ import (
"github.com/thrasher-/gocryptotrader/config"
)
var (
g Gemini
)
// Please enter sandbox API keys & assigned roles for better testing procedures
const (
@@ -26,15 +22,17 @@ const (
)
func TestAddSession(t *testing.T) {
err := AddSession(&g, 1, apiKey1, apiSecret1, apiKeyRole1, true, false)
var g1 Gemini
err := AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, false)
if err != nil {
t.Error("Test failed - AddSession() error")
}
err = AddSession(&g, 1, apiKey1, apiSecret1, apiKeyRole1, true, false)
err = AddSession(&g1, 1, apiKey1, apiSecret1, apiKeyRole1, true, false)
if err == nil {
t.Error("Test failed - AddSession() error")
}
err = AddSession(&g, 2, apiKey2, apiSecret2, apiKeyRole2, false, true)
var g2 Gemini
err = AddSession(&g2, 2, apiKey2, apiSecret2, apiKeyRole2, false, true)
if err != nil {
t.Error("Test failed - AddSession() error")
}
@@ -46,6 +44,7 @@ func TestSetDefaults(t *testing.T) {
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
geminiConfig, err := cfg.GetExchangeConfig("Gemini")

View File

@@ -74,6 +74,7 @@ type OrderResult struct {
CancelledOrders []string `json:"cancelledOrders"`
CancelRejects []string `json:"cancelRejects"`
} `json:"details"`
Message string `json:"message"`
}
// Order contains order information
@@ -97,6 +98,7 @@ type Order struct {
ExecutedAmount float64 `json:"executed_amount,string"`
RemainingAmount float64 `json:"remaining_amount,string"`
OriginalAmount float64 `json:"original_amount,string"`
Message string `json:"message"`
}
// TradeHistory holds trade history information
@@ -150,6 +152,7 @@ type DepositAddress struct {
Currency string `json:"currency"`
Address string `json:"address"`
Label string `json:"label"`
Message string `json:"message"`
}
// WithdrawalAddress holds withdrawal information
@@ -157,6 +160,7 @@ type WithdrawalAddress struct {
Address string `json:"address"`
Amount float64 `json:"amount"`
TXHash string `json:"txHash"`
Message string `json:"message"`
}
// ErrorCapture is a generlized error response from the server