mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-16 23:16:48 +00:00
New endpoint to retrieve values for all enabled currency data for all enabled exchanges and return it as JSON object for front end
472 lines
13 KiB
Go
472 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
BTCMARKETS_API_URL = "https://api.btcmarkets.net"
|
|
BTCMARKETS_API_VERSION = "0"
|
|
BTCMARKETS_ACCOUNT_BALANCE = "/account/balance"
|
|
BTCMARKETS_ORDER_CREATE = "/order/create"
|
|
BTCMARKETS_ORDER_CANCEL = "/order/cancel"
|
|
BTCMARKETS_ORDER_HISTORY = "/order/history"
|
|
BTCMARKETS_ORDER_OPEN = "/order/open"
|
|
BTCMARKETS_ORDER_TRADE_HISTORY = "/order/trade/history"
|
|
BTCMARKETS_ORDER_DETAIL = "/order/detail"
|
|
)
|
|
|
|
type BTCMarkets struct {
|
|
Name string
|
|
Enabled bool
|
|
Verbose bool
|
|
Websocket bool
|
|
RESTPollingDelay time.Duration
|
|
Fee float64
|
|
Ticker map[string]BTCMarketsTicker
|
|
AuthenticatedAPISupport bool
|
|
APIKey, APISecret string
|
|
BaseCurrencies []string
|
|
AvailablePairs []string
|
|
EnabledPairs []string
|
|
}
|
|
|
|
type BTCMarketsTicker struct {
|
|
BestBID float64
|
|
BestAsk float64
|
|
LastPrice float64
|
|
Currency string
|
|
Instrument string
|
|
Timestamp int64
|
|
}
|
|
|
|
type BTCMarketsTrade struct {
|
|
TradeID int64 `json:"tid"`
|
|
Amount float64 `json:"amount"`
|
|
Price float64 `json:"price"`
|
|
Date int64 `json:"date"`
|
|
}
|
|
|
|
type BTCMarketsOrderbook struct {
|
|
Currency string `json:"currency"`
|
|
Instrument string `json:"instrument"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
Asks [][]float64 `json:"asks"`
|
|
Bids [][]float64 `json:"bids"`
|
|
}
|
|
|
|
type BTCMarketsTradeResponse struct {
|
|
ID int64 `json:"id"`
|
|
CreationTime float64 `json:"creationTime"`
|
|
Description string `json:"description"`
|
|
Price float64 `json:"price"`
|
|
Volume float64 `json:"volume"`
|
|
Fee float64 `json:"fee"`
|
|
}
|
|
|
|
type BTCMarketsOrder struct {
|
|
ID int64 `json:"id"`
|
|
Currency string `json:"currency"`
|
|
Instrument string `json:"instrument"`
|
|
OrderSide string `json:"orderSide"`
|
|
OrderType string `json:"ordertype"`
|
|
CreationTime float64 `json:"creationTime"`
|
|
Status string `json:"status"`
|
|
ErrorMessage string `json:"errorMessage"`
|
|
Price float64 `json:"price"`
|
|
Volume float64 `json:"volume"`
|
|
OpenVolume float64 `json:"openVolume"`
|
|
ClientRequestId string `json:"clientRequestId"`
|
|
Trades []BTCMarketsTradeResponse `json:"trades"`
|
|
}
|
|
|
|
func (b *BTCMarkets) SetDefaults() {
|
|
b.Name = "BTC Markets"
|
|
b.Enabled = false
|
|
b.Fee = 0.85
|
|
b.Verbose = false
|
|
b.Websocket = false
|
|
b.RESTPollingDelay = 10
|
|
b.Ticker = make(map[string]BTCMarketsTicker)
|
|
}
|
|
|
|
func (b *BTCMarkets) GetName() string {
|
|
return b.Name
|
|
}
|
|
|
|
func (b *BTCMarkets) SetEnabled(enabled bool) {
|
|
b.Enabled = enabled
|
|
}
|
|
|
|
func (b *BTCMarkets) IsEnabled() bool {
|
|
return b.Enabled
|
|
}
|
|
|
|
func (b *BTCMarkets) Setup(exch Exchanges) {
|
|
if !exch.Enabled {
|
|
b.SetEnabled(false)
|
|
} else {
|
|
b.Enabled = false
|
|
b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
|
|
b.SetAPIKeys(exch.APIKey, exch.APISecret)
|
|
b.RESTPollingDelay = exch.RESTPollingDelay
|
|
b.Verbose = exch.Verbose
|
|
b.Websocket = exch.Websocket
|
|
b.BaseCurrencies = SplitStrings(exch.BaseCurrencies, ",")
|
|
b.AvailablePairs = SplitStrings(exch.AvailablePairs, ",")
|
|
b.EnabledPairs = SplitStrings(exch.EnabledPairs, ",")
|
|
|
|
}
|
|
}
|
|
|
|
func (k *BTCMarkets) GetEnabledCurrencies() []string {
|
|
return k.EnabledPairs
|
|
}
|
|
|
|
func (b *BTCMarkets) Start() {
|
|
go b.Run()
|
|
}
|
|
|
|
func (b *BTCMarkets) SetAPIKeys(apiKey, apiSecret string) {
|
|
if !b.AuthenticatedAPISupport {
|
|
return
|
|
}
|
|
|
|
b.APIKey = apiKey
|
|
result, err := Base64Decode(apiSecret)
|
|
|
|
if err != nil {
|
|
log.Printf("%s unable to decode secret key.\n", b.GetName())
|
|
b.Enabled = false
|
|
return
|
|
}
|
|
|
|
b.APISecret = string(result)
|
|
}
|
|
|
|
func (b *BTCMarkets) GetFee() float64 {
|
|
return b.Fee
|
|
}
|
|
|
|
func (b *BTCMarkets) Run() {
|
|
if b.Verbose {
|
|
log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
|
|
log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
|
|
}
|
|
|
|
for b.Enabled {
|
|
for _, x := range b.EnabledPairs {
|
|
currency := x
|
|
go func() {
|
|
ticker, err := b.GetTicker(currency)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
b.Ticker[currency] = ticker
|
|
BTCMarketsLastUSD, _ := ConvertCurrency(ticker.LastPrice, "AUD", "USD")
|
|
BTCMarketsBestBidUSD, _ := ConvertCurrency(ticker.BestBID, "AUD", "USD")
|
|
BTCMarketsBestAskUSD, _ := ConvertCurrency(ticker.BestAsk, "AUD", "USD")
|
|
log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", currency, BTCMarketsLastUSD, ticker.LastPrice, BTCMarketsBestBidUSD, ticker.BestBID, BTCMarketsBestAskUSD, ticker.BestAsk)
|
|
AddExchangeInfo(b.GetName(), currency[0:3], currency[3:], ticker.LastPrice, 0)
|
|
AddExchangeInfo(b.GetName(), currency[0:3], "USD", BTCMarketsLastUSD, 0)
|
|
}()
|
|
}
|
|
time.Sleep(time.Second * b.RESTPollingDelay)
|
|
}
|
|
}
|
|
|
|
func (b *BTCMarkets) GetTicker(symbol string) (BTCMarketsTicker, error) {
|
|
ticker := BTCMarketsTicker{}
|
|
path := fmt.Sprintf("/market/%s/AUD/tick", symbol)
|
|
err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &ticker)
|
|
if err != nil {
|
|
return BTCMarketsTicker{}, err
|
|
}
|
|
return ticker, nil
|
|
}
|
|
|
|
func (b *BTCMarkets) GetTickerPrice(currency string) TickerPrice {
|
|
var tickerPrice TickerPrice
|
|
ticker, err := b.GetTicker(currency)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return tickerPrice
|
|
}
|
|
tickerPrice.Ask = ticker.BestAsk
|
|
tickerPrice.Bid = ticker.BestBID
|
|
tickerPrice.CryptoCurrency = currency
|
|
tickerPrice.Last = ticker.LastPrice
|
|
|
|
return tickerPrice
|
|
}
|
|
|
|
func (b *BTCMarkets) GetOrderbook(symbol string) (BTCMarketsOrderbook, error) {
|
|
orderbook := BTCMarketsOrderbook{}
|
|
path := fmt.Sprintf("/market/%s/AUD/orderbook", symbol)
|
|
err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &orderbook)
|
|
if err != nil {
|
|
return BTCMarketsOrderbook{}, err
|
|
}
|
|
return orderbook, nil
|
|
}
|
|
|
|
func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]BTCMarketsTrade, error) {
|
|
trades := []BTCMarketsTrade{}
|
|
path := EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", BTCMARKETS_API_URL, symbol), values)
|
|
err := SendHTTPGetRequest(path, true, &trades)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return trades, nil
|
|
}
|
|
|
|
func (b *BTCMarkets) Order(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) {
|
|
type Order struct {
|
|
Currency string `json:"currency"`
|
|
Instrument string `json:"instrument"`
|
|
Price int64 `json:"price"`
|
|
Volume int64 `json:"volume"`
|
|
OrderSide string `json:"orderSide"`
|
|
OrderType string `json:"ordertype"`
|
|
ClientRequestId string `json:"clientRequestId"`
|
|
}
|
|
order := Order{}
|
|
order.Currency = currency
|
|
order.Instrument = instrument
|
|
order.Price = price * SATOSHIS_PER_BTC
|
|
order.Volume = amount * SATOSHIS_PER_BTC
|
|
order.OrderSide = orderSide
|
|
order.OrderType = orderType
|
|
order.ClientRequestId = clientReq
|
|
|
|
type Response struct {
|
|
Success bool `json:"success"`
|
|
ErrorCode int `json:"errorCode"`
|
|
ErrorMessage string `json:"errorMessage"`
|
|
ID int `json:"id"`
|
|
ClientRequestID string `json:"clientRequestId"`
|
|
}
|
|
var resp Response
|
|
|
|
err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CREATE, order, &resp)
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if !resp.Success {
|
|
return 0, fmt.Errorf("%s Unable to place order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
|
|
}
|
|
return resp.ID, nil
|
|
}
|
|
|
|
func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) {
|
|
type CancelOrder struct {
|
|
OrderIDs []int64 `json:"orderIds"`
|
|
}
|
|
orders := CancelOrder{}
|
|
orders.OrderIDs = append(orders.OrderIDs, orderID...)
|
|
|
|
type Response struct {
|
|
Success bool `json:"success"`
|
|
ErrorCode int `json:"errorCode"`
|
|
ErrorMessage string `json:"errorMessage"`
|
|
Responses []struct {
|
|
Success bool `json:"success"`
|
|
ErrorCode int `json:"errorCode"`
|
|
ErrorMessage string `json:"errorMessage"`
|
|
ID int64 `json:"id"`
|
|
}
|
|
ClientRequestID string `json:"clientRequestId"`
|
|
}
|
|
var resp Response
|
|
|
|
err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CANCEL, orders, &resp)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !resp.Success {
|
|
return false, fmt.Errorf("%s Unable to cancel order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
|
|
}
|
|
|
|
ordersToBeCancelled := len(orderID)
|
|
ordersCancelled := 0
|
|
for _, y := range resp.Responses {
|
|
if y.Success {
|
|
ordersCancelled++
|
|
log.Printf("%s Cancelled order %d.\n", b.GetName(), y.ID)
|
|
} else {
|
|
log.Printf("%s Unable to cancel order %d. Error message: %s\n", b.GetName(), y.ID, y.ErrorMessage)
|
|
}
|
|
}
|
|
|
|
if ordersCancelled == ordersToBeCancelled {
|
|
return true, nil
|
|
} else {
|
|
return false, fmt.Errorf("%s Unable to cancel order(s).", b.GetName())
|
|
}
|
|
}
|
|
|
|
func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]BTCMarketsOrder, error) {
|
|
request := make(map[string]interface{})
|
|
request["currency"] = currency
|
|
request["instrument"] = instrument
|
|
request["limit"] = limit
|
|
request["since"] = since
|
|
|
|
path := BTCMARKETS_ORDER_OPEN
|
|
if historic {
|
|
path = BTCMARKETS_ORDER_HISTORY
|
|
}
|
|
|
|
type response struct {
|
|
Success bool `json:"success"`
|
|
ErrorCode int `json:"errorCode"`
|
|
ErrorMessage string `json:"errorMessage"`
|
|
Orders []BTCMarketsOrder `json:"orders"`
|
|
}
|
|
|
|
resp := response{}
|
|
err := b.SendAuthenticatedRequest("POST", path, request, &resp)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !resp.Success {
|
|
return nil, errors.New(resp.ErrorMessage)
|
|
}
|
|
|
|
for i := range resp.Orders {
|
|
resp.Orders[i].Price = resp.Orders[i].Price / SATOSHIS_PER_BTC
|
|
resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / SATOSHIS_PER_BTC
|
|
resp.Orders[i].Volume = resp.Orders[i].Volume / SATOSHIS_PER_BTC
|
|
|
|
for x := range resp.Orders[i].Trades {
|
|
resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / SATOSHIS_PER_BTC
|
|
resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / SATOSHIS_PER_BTC
|
|
resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / SATOSHIS_PER_BTC
|
|
}
|
|
}
|
|
return resp.Orders, nil
|
|
}
|
|
|
|
func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]BTCMarketsOrder, error) {
|
|
type OrderDetail struct {
|
|
OrderIDs []int64 `json:"orderIds"`
|
|
}
|
|
orders := OrderDetail{}
|
|
orders.OrderIDs = append(orders.OrderIDs, orderID...)
|
|
|
|
type response struct {
|
|
Success bool `json:"success"`
|
|
ErrorCode int `json:"errorCode"`
|
|
ErrorMessage string `json:"errorMessage"`
|
|
Orders []BTCMarketsOrder `json:"orders"`
|
|
}
|
|
|
|
resp := response{}
|
|
err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_DETAIL, orders, &resp)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !resp.Success {
|
|
return nil, errors.New(resp.ErrorMessage)
|
|
}
|
|
|
|
for i := range resp.Orders {
|
|
resp.Orders[i].Price = resp.Orders[i].Price / SATOSHIS_PER_BTC
|
|
resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / SATOSHIS_PER_BTC
|
|
resp.Orders[i].Volume = resp.Orders[i].Volume / SATOSHIS_PER_BTC
|
|
|
|
for x := range resp.Orders[i].Trades {
|
|
resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / SATOSHIS_PER_BTC
|
|
resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / SATOSHIS_PER_BTC
|
|
resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / SATOSHIS_PER_BTC
|
|
}
|
|
}
|
|
return resp.Orders, nil
|
|
}
|
|
|
|
type BTCMarketsAccountBalance struct {
|
|
Balance float64 `json:"balance"`
|
|
PendingFunds float64 `json:"pendingFunds"`
|
|
Currency string `json:"currency"`
|
|
}
|
|
|
|
func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) {
|
|
balance := []BTCMarketsAccountBalance{}
|
|
err := b.SendAuthenticatedRequest("GET", BTCMARKETS_ACCOUNT_BALANCE, nil, &balance)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range balance {
|
|
if balance[i].Currency == "LTC" || balance[i].Currency == "BTC" {
|
|
balance[i].Balance = balance[i].Balance / SATOSHIS_PER_BTC
|
|
balance[i].PendingFunds = balance[i].PendingFunds / SATOSHIS_PER_BTC
|
|
}
|
|
}
|
|
return balance, nil
|
|
}
|
|
|
|
func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interface{}, result interface{}) (err error) {
|
|
nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
|
|
request := ""
|
|
payload := []byte("")
|
|
|
|
if data != nil {
|
|
payload, err = JSONEncode(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
request = path + "\n" + nonce + "\n" + string(payload)
|
|
} else {
|
|
request = path + "\n" + nonce + "\n"
|
|
}
|
|
|
|
hmac := GetHMAC(HASH_SHA512, []byte(request), []byte(b.APISecret))
|
|
|
|
if b.Verbose {
|
|
log.Printf("Sending %s request to URL %s with params %s\n", reqType, BTCMARKETS_API_URL+path, request)
|
|
}
|
|
|
|
headers := make(map[string]string)
|
|
headers["Accept"] = "application/json"
|
|
headers["Accept-Charset"] = "UTF-8"
|
|
headers["Content-Type"] = "application/json"
|
|
headers["apikey"] = b.APIKey
|
|
headers["timestamp"] = nonce
|
|
headers["signature"] = Base64Encode(hmac)
|
|
|
|
resp, err := SendHTTPRequest(reqType, BTCMARKETS_API_URL+path, headers, bytes.NewBuffer(payload))
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if b.Verbose {
|
|
log.Printf("Recieved raw: %s\n", resp)
|
|
}
|
|
|
|
err = JSONDecode([]byte(resp), &result)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|