Files
gocryptotrader/geminihttp.go

494 lines
14 KiB
Go

package main
import (
"errors"
"fmt"
"log"
"net/url"
"strconv"
"strings"
"time"
)
const (
GEMINI_API_URL = "https://api.gemini.com"
GEMINI_API_VERSION = "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"
)
type Gemini struct {
Name string
Enabled bool
Verbose bool
Websocket bool
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APIKey, APISecret string
BaseCurrencies []string
AvailablePairs []string
EnabledPairs []string
}
type GeminiOrderbookEntry struct {
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,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"`
}
func (g *Gemini) SetDefaults() {
g.Name = "Gemini"
g.Enabled = false
g.Verbose = false
g.Websocket = false
g.RESTPollingDelay = 10
}
func (g *Gemini) GetName() string {
return g.Name
}
func (g *Gemini) SetEnabled(enabled bool) {
g.Enabled = enabled
}
func (g *Gemini) IsEnabled() bool {
return g.Enabled
}
func (g *Gemini) Setup(exch Exchanges) {
if !exch.Enabled {
g.SetEnabled(false)
} else {
g.Enabled = true
g.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
g.SetAPIKeys(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 *Gemini) GetEnabledCurrencies() []string {
return k.EnabledPairs
}
func (g *Gemini) Start() {
go g.Run()
}
func (g *Gemini) SetAPIKeys(apiKey, apiSecret string) {
g.APIKey = apiKey
g.APISecret = apiSecret
}
func (g *Gemini) Run() {
if g.Verbose {
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)
}
exchangeProducts, err := g.GetSymbols()
if err != nil {
log.Printf("%s Failed to get available symbols.\n", g.GetName())
} else {
exchangeProducts = SplitStrings(StringToUpper(JoinStrings(exchangeProducts, ",")), ",")
diff := StringSliceDifference(g.AvailablePairs, exchangeProducts)
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(exchangeProducts, ",")
UpdateExchangeConfig(exch)
}
}
}
for g.Enabled {
for _, x := range g.EnabledPairs {
currency := x
go func() {
ticker, err := g.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("Gemini %s Last %f Bid %f Ask %f Volume %f\n", currency, ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume)
AddExchangeInfo(g.GetName(), currency[0:3], currency[3:], ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * g.RESTPollingDelay)
}
}
type GeminiTicker struct {
Ask float64 `json:"ask,string"`
Bid float64 `json:"bid,string"`
Last float64 `json:"last,string"`
Volume struct {
Currency float64
USD float64
Timestamp int64
}
}
func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) {
type TickerResponse struct {
Ask float64 `json:"ask,string"`
Bid float64 `json:"bid,string"`
Last float64 `json:"last,string"`
Volume map[string]interface{}
}
ticker := GeminiTicker{}
resp := TickerResponse{}
path := fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_TICKER, currency)
err := SendHTTPGetRequest(path, true, &resp)
if err != nil {
return ticker, err
}
ticker.Ask = resp.Ask
ticker.Bid = resp.Bid
ticker.Last = resp.Last
ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currency[0:3]].(string), 64)
ticker.Volume.USD, _ = strconv.ParseFloat(resp.Volume["USD"].(string), 64)
time, _ := resp.Volume["timestamp"].(float64)
ticker.Volume.Timestamp = int64(time)
return ticker, nil
}
func (g *Gemini) GetTickerPrice(currency string) (TickerPrice, error) {
tickerNew, err := GetTicker(g.GetName(), currency[0:3], currency[3:])
if err == nil {
return tickerNew, nil
}
var tickerPrice TickerPrice
ticker, err := g.GetTicker(currency)
if err != nil {
return tickerPrice, err
}
tickerPrice.Ask = ticker.Ask
tickerPrice.Bid = ticker.Bid
tickerPrice.FirstCurrency = currency[0:3]
tickerPrice.SecondCurrency = currency[3:]
tickerPrice.Last = ticker.Last
tickerPrice.Volume = ticker.Volume.USD
ProcessTicker(g.GetName(), tickerPrice.FirstCurrency, tickerPrice.SecondCurrency, tickerPrice)
return tickerPrice, 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 := SendHTTPGetRequest(path, true, &symbols)
if err != nil {
return nil, err
}
return symbols, nil
}
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"`
}
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 := SendHTTPGetRequest(path, true, &auction)
if err != nil {
return auction, err
}
return auction, nil
}
type GeminiAuctionHistory struct {
AuctionID int64 `json:"auction_id"`
AuctionPrice float64 `json:"auction_price,string"`
AuctionQuantity float64 `json:"auction_quantity,string"`
EID int64 `json:"eid"`
HighestBidPrice float64 `json:"highest_bid_price,string"`
LowestAskPrice float64 `json:"lowest_ask_price,string"`
AuctionResult string `json:"auction_result"`
Timestamp int64 `json:"timestamp"`
TimestampMS int64 `json:"timestampms"`
EventType string `json:"event_type"`
}
func (g *Gemini) GetAuctionHistory(currency string, params url.Values) ([]GeminiAuctionHistory, error) {
path := 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 := SendHTTPGetRequest(path, true, &auctionHist)
if err != nil {
return nil, err
}
return auctionHist, nil
}
func (g *Gemini) GetOrderbook(currency string, params url.Values) (GeminiOrderbook, error) {
path := EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_ORDERBOOK, currency), params)
orderbook := GeminiOrderbook{}
err := SendHTTPGetRequest(path, true, &orderbook)
if err != nil {
return GeminiOrderbook{}, err
}
return orderbook, nil
}
func (g *Gemini) GetTrades(currency string, params url.Values) ([]GeminiTrade, error) {
path := EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_TRADES, currency), params)
trades := []GeminiTrade{}
err := SendHTTPGetRequest(path, true, &trades)
if err != nil {
return []GeminiTrade{}, err
}
return trades, nil
}
func (g *Gemini) NewOrder(symbol string, amount, price float64, side, orderType string) (int64, error) {
request := make(map[string]interface{})
request["symbol"] = symbol
request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
request["price"] = strconv.FormatFloat(price, 'f', -1, 64)
request["side"] = side
request["type"] = orderType
response := GeminiOrder{}
err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_NEW, request, &response)
if err != nil {
return 0, err
}
return response.OrderID, nil
}
func (g *Gemini) CancelOrder(OrderID int64) (GeminiOrder, error) {
request := make(map[string]interface{})
request["order_id"] = OrderID
response := GeminiOrder{}
err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_CANCEL, request, &response)
if err != nil {
return GeminiOrder{}, 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
}
err := g.SendAuthenticatedHTTPRequest("POST", path, nil, &response)
if err != nil {
return nil, err
}
return response, nil
}
func (g *Gemini) GetOrderStatus(orderID int64) (GeminiOrder, 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
}
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
}
func (g *Gemini) GetTradeHistory(symbol string, timestamp int64) ([]GeminiTradeHistory, error) {
request := make(map[string]interface{})
request["symbol"] = symbol
request["timestamp"] = timestamp
response := []GeminiTradeHistory{}
err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_MYTRADES, request, &response)
if err != nil {
return nil, err
}
return response, nil
}
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
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Gemini exchange
func (e *Gemini) GetExchangeAccountInfo() (ExchangeAccountInfo, error) {
var response ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetBalances()
if err != nil {
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency ExchangeAccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Amount
exchangeCurrency.Hold = accountBalance[i].Available
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil
}
func (g *Gemini) PostHeartbeat() (bool, error) {
type Response struct {
Result bool `json:"result"`
}
response := Response{}
err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_HEARTBEAT, nil, &response)
if err != nil {
return false, err
}
return response.Result, nil
}
func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) {
request := make(map[string]interface{})
request["request"] = fmt.Sprintf("/v%s/%s", GEMINI_API_VERSION, path)
request["nonce"] = time.Now().UnixNano()
if params != nil {
for key, value := range params {
request[key] = value
}
}
PayloadJson, err := JSONEncode(request)
if err != nil {
return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request")
}
if g.Verbose {
log.Printf("Request JSON: %s\n", PayloadJson)
}
PayloadBase64 := Base64Encode(PayloadJson)
hmac := GetHMAC(HASH_SHA512_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"] = HexEncodeToString(hmac)
resp, err := SendHTTPRequest(method, BITFINEX_API_URL+path, headers, strings.NewReader(""))
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
}