Add Exchange: HitBTC

This commit is contained in:
Bret Palsson
2018-01-06 00:45:56 -08:00
parent fca7e5e59a
commit 71333b29c7
11 changed files with 1149 additions and 3 deletions

View File

@@ -10,4 +10,5 @@ Manuel Kreutz - 140am
libsora.so - if1live
Tong - tongxiaofeng
Jamie Cheng - starit
Jake - snipesjr
Jake - snipesjr
Bret Palsson - bretep

View File

@@ -30,6 +30,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| COINUT | Yes | No | NA |
| GDAX(Coinbase) | Yes | Yes | No|
| Gemini | Yes | NA | NA |
| HitBTC | Yes | Yes | NA |
| Huobi.Pro | Yes | No |No |
| ItBit | Yes | NA | NA |
| Kraken | Yes | NA | NA |

View File

@@ -89,7 +89,7 @@ func TestGetEnabledExchanges(t *testing.T) {
}
exchanges := cfg.GetEnabledExchanges()
if len(exchanges) != 23 {
if len(exchanges) != 24 {
t.Error(
"Test failed. TestGetEnabledExchanges. Enabled exchanges value mismatch",
)
@@ -141,7 +141,7 @@ func TestGetDisabledExchanges(t *testing.T) {
}
func TestCountEnabledExchanges(t *testing.T) {
defaultEnabledExchanges := 23
defaultEnabledExchanges := 24
GetConfigEnabledExchanges := GetConfig()
err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile)
if err != nil {

View File

@@ -298,6 +298,27 @@
"Uppercase": true
}
},
{
"Name": "HitBTC",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "BTCUSD,ETHBTC,ETHUSD",
"EnabledPairs": "BTCUSD",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Huobi",
"Enabled": true,

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/coinut"
"github.com/thrasher-/gocryptotrader/exchanges/gdax"
"github.com/thrasher-/gocryptotrader/exchanges/gemini"
"github.com/thrasher-/gocryptotrader/exchanges/hitbtc"
"github.com/thrasher-/gocryptotrader/exchanges/huobi"
"github.com/thrasher-/gocryptotrader/exchanges/itbit"
"github.com/thrasher-/gocryptotrader/exchanges/kraken"
@@ -150,6 +151,8 @@ func LoadExchange(name string) error {
exch = new(gdax.GDAX)
case "gemini":
exch = new(gemini.Gemini)
case "hitbtc":
exch = new(hitbtc.HitBTC)
case "huobi":
exch = new(huobi.HUOBI)
case "itbit":

507
exchanges/hitbtc/hitbtc.go Normal file
View File

@@ -0,0 +1,507 @@
package hitbtc
import (
"bytes"
"errors"
"fmt"
"log"
"net/url"
"strconv"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
// API
APIURL = "https://api.hitbtc.com"
// Public
APIv2Trades = "api/2/public/trades"
APIv2Currency = "api/2/public/currency"
APIv2Symbol = "api/2/public/symbol"
APIv2Ticker = "api/2/public/ticker"
APIv2OrderBook = "api/2/public/orderbook"
APIv2Candles = "api/2/public/candles"
// Authenticated
APIv2Balance = "api/2/trading/balance"
APIv2CryptoAddress = "api/2/account/crypto/address"
APIv2CryptoWithdraw = "api/2/account/crypto/withdraw"
APIv2TradeHistory = "api/2/history/trades"
APIv2FeeInfo = "api/2/trading/fee"
Orders = "order"
OrderBuy = "buy"
OrderSell = "sell"
OrderCancel = "cancelOrder"
OrderMove = "moveOrder"
TradableBalances = "returnTradableBalances"
TransferBalance = "transferBalance"
)
// HitBTC is the overarching type across the hitbtc package
type HitBTC struct {
exchange.Base
}
// SetDefaults sets default settings for hitbtc
func (p *HitBTC) SetDefaults() {
p.Name = "HitBTC"
p.Enabled = false
p.Fee = 0
p.Verbose = false
p.Websocket = false
p.RESTPollingDelay = 10
p.RequestCurrencyPairFormat.Delimiter = ""
p.RequestCurrencyPairFormat.Uppercase = true
p.ConfigCurrencyPairFormat.Delimiter = ""
p.ConfigCurrencyPairFormat.Uppercase = true
p.AssetTypes = []string{ticker.Spot}
}
// Setup sets user exchange configuration settings
func (p *HitBTC) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
p.SetEnabled(false)
} else {
p.Enabled = true
p.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
p.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
p.RESTPollingDelay = exch.RESTPollingDelay // Max 60000ms
p.Verbose = exch.Verbose
p.Websocket = exch.Websocket
p.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
availiableSymbols, err := p.GetSymbols("")
if err != nil {
log.Println(err)
p.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
} else {
p.AvailablePairs = availiableSymbols
}
p.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err = p.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = p.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
// GetFee returns the fee for hitbtc
func (p *HitBTC) GetFee() float64 {
return p.Fee
}
// Public Market Data
// https://api.hitbtc.com/?python#market-data
// GetCurrencies
// Return the actual list of available currencies, tokens, ICO etc.
func (p *HitBTC) GetCurrencies(currency string) (map[string]Currencies, error) {
type Response struct {
Data []Currencies
}
resp := Response{}
path := fmt.Sprintf("%s/%s/%s", APIURL, APIv2Currency, currency)
err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data)
ret := make(map[string]Currencies)
for _, id := range resp.Data {
ret[id.Id] = id
}
return ret, err
}
// GetSymbols
// Return the actual list of currency symbols (currency pairs) traded on HitBTC exchange.
// The first listed currency of a symbol is called the base currency, and the second currency
// is called the quote currency. The currency pair indicates how much of the quote currency
// is needed to purchase one unit of the base currency.
func (p *HitBTC) GetSymbols(symbol string) ([]string, error) {
resp := []Symbol{}
path := fmt.Sprintf("%s/%s/%s", APIURL, APIv2Symbol, symbol)
err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp)
ret := make([]string, 0, len(resp))
for _, x := range resp {
ret = append(ret, x.Id)
}
return ret, err
}
// GetTicker
// Return ticker information
func (p *HitBTC) GetTicker(symbol string) map[string]Ticker {
resp1 := []Ticker{}
resp2 := Ticker{}
ret := make(map[string]Ticker)
path := fmt.Sprintf("%s/%s/%s", APIURL, APIv2Ticker, symbol)
if symbol == "" {
common.SendHTTPGetRequest(path, true, p.Verbose, &resp1)
for _, item := range resp1 {
if item.Symbol != "" {
ret[item.Symbol] = item
}
}
} else {
common.SendHTTPGetRequest(path, true, p.Verbose, &resp2)
ret[resp2.Symbol] = resp2
}
return ret
}
// GetTrades returns trades from hitbtc
func (p *HitBTC) GetTrades(currencyPair, from, till, limit, offset, by, sort string) ([]TradeHistory, error) {
// start Number or Datetime
// end Number or Datetime
// limit Number
// offset Number
// by Filtration definition. Accepted values: id, timestamp. Default timestamp
// sort Default DESC
vals := url.Values{}
if from != "" {
vals.Set("from", from)
}
if till != "" {
vals.Set("till", till)
}
if limit != "" {
vals.Set("limit", limit)
}
if offset != "" {
vals.Set("offset", offset)
}
if by != "" {
vals.Set("by", by)
}
if sort != "" {
vals.Set("sort", sort)
}
resp := []TradeHistory{}
path := fmt.Sprintf("%s/%s/%s?%s", APIURL, APIv2Trades, currencyPair, vals.Encode())
return resp, common.SendHTTPGetRequest(path, true, p.Verbose, &resp)
}
// GetOrderbook
// An order book is an electronic list of buy and sell orders for a specific
// symbol, organized by price level.
func (p *HitBTC) GetOrderbook(currencyPair string, limit int) (Orderbook, error) {
// limit Limit of orderbook levels, default 100. Set 0 to view full orderbook levels
vals := url.Values{}
if limit != 0 {
vals.Set("limit", strconv.Itoa(limit))
}
resp := OrderbookResponse{}
path := fmt.Sprintf("%s/%s/%s?%s", APIURL, APIv2OrderBook, currencyPair, vals.Encode())
err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp)
if err != nil {
return Orderbook{}, err
}
ob := Orderbook{}
for _, x := range resp.Asks {
ob.Asks = append(ob.Asks, x)
}
for _, x := range resp.Bids {
ob.Bids = append(ob.Bids, x)
}
return ob, nil
}
// GetCandles
// A candles used for OHLC a specific symbol.
// Note: Result contain candles only with non zero volume.
func (p *HitBTC) GetCandles(currencyPair, limit, period string) ([]ChartData, error) {
// limit Limit of candles, default 100.
// period One of: M1 (one minute), M3, M5, M15, M30, H1, H4, D1, D7, 1M (one month). Default is M30 (30 minutes).
vals := url.Values{}
if limit != "" {
vals.Set("limit", limit)
}
if period != "" {
vals.Set("period", period)
}
resp := []ChartData{}
path := fmt.Sprintf("%s/%s/%s?%s", APIURL, APIv2Candles, currencyPair, vals.Encode())
err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
// Authenticated Market Data
// https://api.hitbtc.com/?python#market-data
// GetBalances
func (p *HitBTC) GetBalances() (map[string]Balance, error) {
result := []Balance{}
err := p.SendAuthenticatedHTTPRequest("GET", APIv2Balance, url.Values{}, &result)
ret := make(map[string]Balance)
if err != nil {
return ret, err
}
for _, item := range result {
ret[item.Currency] = item
}
return ret, nil
}
func (p *HitBTC) GetDepositAddresses(currency string) (DepositCryptoAddresses, error) {
resp := DepositCryptoAddresses{}
err := p.SendAuthenticatedHTTPRequest("GET", APIv2CryptoAddress+"/"+currency, url.Values{}, &resp)
return resp, err
}
func (p *HitBTC) GenerateNewAddress(currency string) (DepositCryptoAddresses, error) {
resp := DepositCryptoAddresses{}
err := p.SendAuthenticatedHTTPRequest("POST", APIv2CryptoAddress+"/"+currency, url.Values{}, &resp)
return resp, err
}
// Get Active orders
func (p *HitBTC) GetActiveOrders(currency string) ([]Order, error) {
resp := []Order{}
err := p.SendAuthenticatedHTTPRequest("GET", Orders+"?symbol="+currency, url.Values{}, &resp)
return resp, err
}
func (p *HitBTC) GetAuthenticatedTradeHistory(currency, start, end string) (interface{}, error) {
values := url.Values{}
if start != "" {
values.Set("start", start)
}
if end != "" {
values.Set("end", end)
}
if currency != "" && currency != "all" {
values.Set("currencyPair", currency)
result := AuthenticatedTradeHistoryResponse{}
err := p.SendAuthenticatedHTTPRequest("POST", APIv2TradeHistory, values, &result.Data)
if err != nil {
return result, err
}
return result, nil
} else {
values.Set("currencyPair", "all")
result := AuthenticatedTradeHistoryAll{}
err := p.SendAuthenticatedHTTPRequest("POST", APIv2TradeHistory, values, &result.Data)
if err != nil {
return result, err
}
return result, nil
}
}
func (p *HitBTC) PlaceOrder(currency string, rate, amount float64, immediate, fillOrKill, buy bool) (OrderResponse, error) {
result := OrderResponse{}
values := url.Values{}
var orderType string
if buy {
orderType = OrderBuy
} else {
orderType = OrderSell
}
values.Set("currencyPair", currency)
values.Set("rate", strconv.FormatFloat(rate, 'f', -1, 64))
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
if immediate {
values.Set("immediateOrCancel", "1")
}
if fillOrKill {
values.Set("fillOrKill", "1")
}
err := p.SendAuthenticatedHTTPRequest("POST", orderType, values, &result)
if err != nil {
return result, err
}
return result, nil
}
func (p *HitBTC) CancelOrder(orderID int64) (bool, error) {
result := GenericResponse{}
values := url.Values{}
values.Set("orderNumber", strconv.FormatInt(orderID, 10))
err := p.SendAuthenticatedHTTPRequest("POST", OrderCancel, values, &result)
if err != nil {
return false, err
}
if result.Success != 1 {
return false, errors.New(result.Error)
}
return true, nil
}
func (p *HitBTC) MoveOrder(orderID int64, rate, amount float64) (MoveOrderResponse, error) {
result := MoveOrderResponse{}
values := url.Values{}
values.Set("orderNumber", strconv.FormatInt(orderID, 10))
values.Set("rate", strconv.FormatFloat(rate, 'f', -1, 64))
if amount != 0 {
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
}
err := p.SendAuthenticatedHTTPRequest("POST", OrderMove, values, &result)
if err != nil {
return result, err
}
if result.Success != 1 {
return result, errors.New(result.Error)
}
return result, nil
}
func (p *HitBTC) Withdraw(currency, address string, amount float64) (bool, error) {
result := Withdraw{}
values := url.Values{}
values.Set("currency", currency)
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
values.Set("address", address)
err := p.SendAuthenticatedHTTPRequest("POST", APIv2CryptoWithdraw, values, &result)
if err != nil {
return false, err
}
if result.Error != "" {
return false, errors.New(result.Error)
}
return true, nil
}
func (p *HitBTC) GetFeeInfo(currencyPair string) (Fee, error) {
result := Fee{}
err := p.SendAuthenticatedHTTPRequest("GET", APIv2FeeInfo+"/"+currencyPair, url.Values{}, &result)
return result, err
}
func (p *HitBTC) GetTradableBalances() (map[string]map[string]float64, error) {
type Response struct {
Data map[string]map[string]interface{}
}
result := Response{}
err := p.SendAuthenticatedHTTPRequest("POST", TradableBalances, url.Values{}, &result.Data)
if err != nil {
return nil, err
}
balances := make(map[string]map[string]float64)
for x, y := range result.Data {
balances[x] = make(map[string]float64)
for z, w := range y {
balances[x][z], _ = strconv.ParseFloat(w.(string), 64)
}
}
return balances, nil
}
func (p *HitBTC) TransferBalance(currency, from, to string, amount float64) (bool, error) {
values := url.Values{}
result := GenericResponse{}
values.Set("currency", currency)
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
values.Set("fromAccount", from)
values.Set("toAccount", to)
err := p.SendAuthenticatedHTTPRequest("POST", TransferBalance, values, &result)
if err != nil {
return false, err
}
if result.Error != "" && result.Success != 1 {
return false, errors.New(result.Error)
}
return true, nil
}
func (p *HitBTC) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error {
if !p.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, p.Name)
}
headers := make(map[string]string)
headers["Authorization"] = "Basic " + common.Base64Encode([]byte(p.APIKey+":"+p.APISecret))
path := fmt.Sprintf("%s/%s", APIURL, endpoint)
resp, err := common.SendHTTPRequest(method, path, headers, bytes.NewBufferString(values.Encode()))
if err != nil {
return err
}
err = common.JSONDecode([]byte(resp), &result)
if err != nil {
return errors.New("Unable to JSON Unmarshal response.")
}
return nil
}

View File

@@ -0,0 +1,69 @@
package hitbtc
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
var p HitBTC
// Please supply your own APIKEYS here for due diligence testing
const (
apiKey = ""
apiSecret = ""
)
func TestSetDefaults(t *testing.T) {
p.SetDefaults()
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
hitbtcConfig, err := cfg.GetExchangeConfig("HitBTC")
if err != nil {
t.Error("Test Failed - HitBTC Setup() init error")
}
hitbtcConfig.AuthenticatedAPISupport = true
hitbtcConfig.APIKey = apiKey
hitbtcConfig.APISecret = apiSecret
p.Setup(hitbtcConfig)
}
func TestGetFee(t *testing.T) {
if p.GetFee() != 0 {
t.Error("Test faild - HitBTC GetFee() error")
}
}
func TestGetOrderbook(t *testing.T) {
_, err := p.GetOrderbook("BTCUSD", 50)
if err != nil {
t.Error("Test faild - HitBTC GetOrderbook() error", err)
}
}
func TestGetTrades(t *testing.T) {
_, err := p.GetTrades("BTCUSD", "", "", "", "", "", "")
if err != nil {
t.Error("Test faild - HitBTC GetTradeHistory() error", err)
}
}
func TestGetChartCandles(t *testing.T) {
_, err := p.GetCandles("BTCUSD", "", "")
if err != nil {
t.Error("Test faild - HitBTC GetChartData() error", err)
}
}
func TestGetCurrencies(t *testing.T) {
_, err := p.GetCurrencies("")
if err != nil {
t.Error("Test faild - HitBTC GetCurrencies() error", err)
}
}

View File

@@ -0,0 +1,233 @@
package hitbtc
import "time"
type Ticker struct {
Last float64 `json:"last,string"` // Last trade price
Ask float64 `json:"ask,string"` // Best ask price
Bid float64 `json:"bid,string"` // Best bid price
Timestamp time.Time `json:"timestamp,string"` // Last update or refresh ticker timestamp
Volume float64 `json:"volume,string"` // Total trading amount within 24 hours in base currency
VolumeQuote float64 `json:"volumeQuote,string"` // Total trading amount within 24 hours in quote currency
Symbol string `json:"symbol"`
High float64 `json:"high,string"` // Highest trade price within 24 hours
Low float64 `json:"low,string"` // Lowest trade price within 24 hours
Open float64 `json:"open,string"` // Last trade price 24 hours ago
}
type Symbol struct {
Id string `json:"id"` // Symbol identifier. In the future, the description will simply use the symbol
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
QuantityIncrement float64 `json:"quantityIncrement,string"`
TickSize float64 `json:"tickSize,string"`
TakeLiquidityRate float64 `json:"takeLiquidityRate,string"` // Default fee rate
ProvideLiquidityRate float64 `json:"provideLiquidityRate,string"` // Default fee rate for market making trades
FeeCurrency string `json:"feeCurrency"` // Default fee rate for market making trades
}
type OrderbookResponse struct {
Asks []OrderbookItem `json:"ask"` // Ask side array of levels
Bids []OrderbookItem `json:"bid"` // Bid side array of levels
}
type OrderbookItem struct {
Price float64 `json:"price,string"` // Price level
Amount float64 `json:"size,string"` // Total volume of orders with the specified price
}
type Orderbook struct {
Asks []OrderbookItem `json:"asks"`
Bids []OrderbookItem `json:"bids"`
}
type TradeHistory struct {
Id int64 `json:"id"` // Trade id
Timestamp string `json:"timestamp"` // Trade timestamp
Side string `json:"side"` // Trade side sell or buy
Price float64 `json:"price,string"` // Trade price
Quantity float64 `json:"quantity,string"` // Trade quantity
}
type ChartData struct {
Timestamp time.Time `json:"timestamp,string"`
Max float64 `json:"max,string"` // Max price
Min float64 `json:"min,string"` // Min price
Open float64 `json:"open,string"` // Open price
Close float64 `json:"close,string"` // Close price
Volume float64 `json:"volume,string"` // Volume in base currency
VolumeQuote float64 `json:"volumeQuote,string"` // Volume in quote currency
}
type Currencies struct {
Id string `json:"id"` // Currency identifier.
FullName string `json:"fullName"` // Currency full name
Crypto bool `json:"crypto,boolean"` // Is currency belongs to blockchain (false for ICO and fiat, like EUR)
PayinEnabled bool `json:"payinEnabled"` // Is allowed for deposit (false for ICO)
PayinPaymentId bool `json:"payinPaymentId"` // Is required to provide additional information other than the address for deposit
PayinConfirmations int64 `json:"payinConfirmations"` // Blocks confirmations count for deposit
PayoutEnabled bool `json:"payoutEnabled"` // Is allowed for withdraw (false for ICO)
PayoutIsPaymentId bool `json:"payoutIsPaymentId"` // Is allowed to provide additional information for withdraw
TransferEnabled bool `json:"transferEnabled"` // Is allowed to transfer between trading and account (may be disabled on maintain)
}
type LoanOrder struct {
Rate float64 `json:"rate,string"`
Amount float64 `json:"amount,string"`
RangeMin int `json:"rangeMin"`
RangeMax int `json:"rangeMax"`
}
type LoanOrders struct {
Offers []LoanOrder `json:"offers"`
Demands []LoanOrder `json:"demands"`
}
type Balance struct {
Currency string `json:"currency"`
Available float64 `json:"available,string"` // Amount available for trading or transfer to main account
Reserved float64 `json:"reserved,string"` // Amount reserved for active orders or incomplete transfers to main account
}
type DepositCryptoAddresses struct {
Address string `json:"address"` // Address for deposit
PaymentId string `json:"paymentId"` // Optional additional parameter. Required for deposit if persist
}
type Order struct {
Id int64 `json:"id,string"` // Unique identifier for Order as assigned by exchange
ClientOrderId string `json:"clientOrderId"` // Unique identifier for Order as assigned by trader. Uniqueness must be
// guaranteed within a single trading day, including all active orders.
Symbol string `json:"symbol"` // Trading symbol
Side string `json:"side"` // sell buy
Status string `json:"status"` // new, suspended, partiallyFilled, filled, canceled, expired
Type string `json:"type"` // Enum: limit, market, stopLimit, stopMarket
TimeInForce string `json:"timeInForce"` // Time in force is a special instruction used when placing a trade to
// indicate how long an order will remain active before it is executed or expires
// GTC - Good till cancel. GTC order won't close until it is filled.
// IOC - An immediate or cancel order is an order to buy or sell that must be executed immediately, and any portion
// of the order that cannot be immediately filled is cancelled.
// FOK - Fill or kill is a type of time-in-force designation used in securities trading that instructs a brokerage
// to execute a transaction immediately and completely or not at all.
// Day - keeps the order active until the end of the trading day in UTC.
// GTD - Good till date specified in expireTime.
Quantity float64 `json:"quantity,string"` // Order quantity
Price float64 `json:"price,string"` // Order price
CumQuantity float64 `json:"cumQuantity,string"` // Cumulative executed quantity
CreatedAt time.Time `json:"createdAt,string"`
UpdatedAt time.Time `json:"updatedAt,string"`
StopPrice float64 `json:"stopPrice,string"`
ExpireTime time.Time `json:"expireTime,string"`
}
type OpenOrdersResponseAll struct {
Data map[string][]Order
}
type OpenOrdersResponse struct {
Data []Order
}
type AuthentictedTradeHistory struct {
GlobalTradeID int64 `json:"globalTradeID"`
TradeID int64 `json:"tradeID,string"`
Date string `json:"date"`
Rate float64 `json:"rate,string"`
Amount float64 `json:"amount,string"`
Total float64 `json:"total,string"`
Fee float64 `json:"fee,string"`
OrderNumber int64 `json:"orderNumber,string"`
Type string `json:"type"`
Category string `json:"category"`
}
type AuthenticatedTradeHistoryAll struct {
Data map[string][]AuthentictedTradeHistory
}
type AuthenticatedTradeHistoryResponse struct {
Data []AuthentictedTradeHistory
}
type ResultingTrades struct {
Amount float64 `json:"amount,string"`
Date string `json:"date"`
Rate float64 `json:"rate,string"`
Total float64 `json:"total,string"`
TradeID int64 `json:"tradeID,string"`
Type string `json:"type"`
}
type OrderResponse struct {
OrderNumber int64 `json:"orderNumber,string"`
Trades []ResultingTrades `json:"resultingTrades"`
}
type GenericResponse struct {
Success int `json:"success"`
Error string `json:"error"`
}
type MoveOrderResponse struct {
Success int `json:"success"`
Error string `json:"error"`
OrderNumber int64 `json:"orderNumber,string"`
Trades map[string][]ResultingTrades `json:"resultingTrades"`
}
type Withdraw struct {
Response string `json:"response"`
Error string `json:"error"`
}
type Fee struct {
TakeLiquidityRate float64 `json:"takeLiquidityRate,string"` // Taker
ProvideLiquidityRate float64 `json:"provideLiquidityRate,string"` // Maker
}
type Margin struct {
TotalValue float64 `json:"totalValue,string"`
ProfitLoss float64 `json:"pl,string"`
LendingFees float64 `json:"lendingFees,string"`
NetValue float64 `json:"netValue,string"`
BorrowedValue float64 `json:"totalBorrowedValue,string"`
CurrentMargin float64 `json:"currentMargin,string"`
}
type MarginPosition struct {
Amount float64 `json:"amount,string"`
Total float64 `json:"total,string"`
BasePrice float64 `json:"basePrice,string"`
LiquidiationPrice float64 `json:"liquidiationPrice"`
ProfitLoss float64 `json:"pl,string"`
LendingFees float64 `json:"lendingFees,string"`
Type string `json:"type"`
}
type LoanOffer struct {
ID int64 `json:"id"`
Rate float64 `json:"rate,string"`
Amount float64 `json:"amount,string"`
Duration int `json:"duration"`
AutoRenew bool `json:"autoRenew,int"`
Date string `json:"date"`
}
type ActiveLoans struct {
Provided []LoanOffer `json:"provided"`
Used []LoanOffer `json:"used"`
}
type LendingHistory struct {
ID int64 `json:"id"`
Currency string `json:"currency"`
Rate float64 `json:"rate,string"`
Amount float64 `json:"amount,string"`
Duration float64 `json:"duration,string"`
Interest float64 `json:"interest,string"`
Fee float64 `json:"fee,string"`
Earned float64 `json:"earned,string"`
Open string `json:"open"`
Close string `json:"close"`
}

View File

@@ -0,0 +1,182 @@
package hitbtc
import (
"log"
"strconv"
"github.com/beatgammit/turnpike"
)
const (
HITBTC_WEBSOCKET_ADDRESS = "wss://api.hitbtc.com"
HITBTC_WEBSOCKET_REALM = "realm1"
HITBTC_WEBSOCKET_TICKER = "ticker"
HITBTC_WEBSOCKET_TROLLBOX = "trollbox"
)
type HitBTCWebsocketTicker struct {
CurrencyPair string
Last float64
LowestAsk float64
HighestBid float64
PercentChange float64
BaseVolume float64
QuoteVolume float64
IsFrozen bool
High float64
Low float64
}
func HitBTCOnTicker(args []interface{}, kwargs map[string]interface{}) {
ticker := HitBTCWebsocketTicker{}
ticker.CurrencyPair = args[0].(string)
ticker.Last, _ = strconv.ParseFloat(args[1].(string), 64)
ticker.LowestAsk, _ = strconv.ParseFloat(args[2].(string), 64)
ticker.HighestBid, _ = strconv.ParseFloat(args[3].(string), 64)
ticker.PercentChange, _ = strconv.ParseFloat(args[4].(string), 64)
ticker.BaseVolume, _ = strconv.ParseFloat(args[5].(string), 64)
ticker.QuoteVolume, _ = strconv.ParseFloat(args[6].(string), 64)
if args[7].(float64) != 0 {
ticker.IsFrozen = true
} else {
ticker.IsFrozen = false
}
ticker.High, _ = strconv.ParseFloat(args[8].(string), 64)
ticker.Low, _ = strconv.ParseFloat(args[9].(string), 64)
}
type HitBTCWebsocketTrollboxMessage struct {
MessageNumber float64
Username string
Message string
Reputation float64
}
func HitBTCOnTrollbox(args []interface{}, kwargs map[string]interface{}) {
message := HitBTCWebsocketTrollboxMessage{}
message.MessageNumber, _ = args[1].(float64)
message.Username = args[2].(string)
message.Message = args[3].(string)
if len(args) == 5 {
message.Reputation = args[4].(float64)
}
}
func HitBTCOnDepthOrTrade(args []interface{}, kwargs map[string]interface{}) {
for x := range args {
data := args[x].(map[string]interface{})
msgData := data["data"].(map[string]interface{})
msgType := data["type"].(string)
switch msgType {
case "orderBookModify":
{
type HitBTCWebsocketOrderbookModify struct {
Type string
Rate float64
Amount float64
}
orderModify := HitBTCWebsocketOrderbookModify{}
orderModify.Type = msgData["type"].(string)
rateStr := msgData["rate"].(string)
orderModify.Rate, _ = strconv.ParseFloat(rateStr, 64)
amountStr := msgData["amount"].(string)
orderModify.Amount, _ = strconv.ParseFloat(amountStr, 64)
}
case "orderBookRemove":
{
type HitBTCWebsocketOrderbookRemove struct {
Type string
Rate float64
}
orderRemoval := HitBTCWebsocketOrderbookRemove{}
orderRemoval.Type = msgData["type"].(string)
rateStr := msgData["rate"].(string)
orderRemoval.Rate, _ = strconv.ParseFloat(rateStr, 64)
}
case "newTrade":
{
type HitBTCWebsocketNewTrade struct {
Type string
TradeID int64
Rate float64
Amount float64
Date string
Total float64
}
trade := HitBTCWebsocketNewTrade{}
trade.Type = msgData["type"].(string)
tradeIDstr := msgData["tradeID"].(string)
trade.TradeID, _ = strconv.ParseInt(tradeIDstr, 10, 64)
rateStr := msgData["rate"].(string)
trade.Rate, _ = strconv.ParseFloat(rateStr, 64)
amountStr := msgData["amount"].(string)
trade.Amount, _ = strconv.ParseFloat(amountStr, 64)
totalStr := msgData["total"].(string)
trade.Rate, _ = strconv.ParseFloat(totalStr, 64)
trade.Date = msgData["date"].(string)
}
}
}
}
func (p *HitBTC) WebsocketClient() {
for p.Enabled && p.Websocket {
c, err := turnpike.NewWebsocketClient(turnpike.JSON, HITBTC_WEBSOCKET_ADDRESS, nil)
if err != nil {
log.Printf("%s Unable to connect to Websocket. Error: %s\n", p.GetName(), err)
continue
}
if p.Verbose {
log.Printf("%s Connected to Websocket.\n", p.GetName())
}
_, err = c.JoinRealm(HITBTC_WEBSOCKET_REALM, nil)
if err != nil {
log.Printf("%s Unable to join realm. Error: %s\n", p.GetName(), err)
continue
}
if p.Verbose {
log.Printf("%s Joined Websocket realm.\n", p.GetName())
}
c.ReceiveDone = make(chan bool)
if err := c.Subscribe(HITBTC_WEBSOCKET_TICKER, HitBTCOnTicker); err != nil {
log.Printf("%s Error subscribing to ticker channel: %s\n", p.GetName(), err)
}
if err := c.Subscribe(HITBTC_WEBSOCKET_TROLLBOX, HitBTCOnTrollbox); err != nil {
log.Printf("%s Error subscribing to trollbox channel: %s\n", p.GetName(), err)
}
for x := range p.EnabledPairs {
currency := p.EnabledPairs[x]
if err := c.Subscribe(currency, HitBTCOnDepthOrTrade); err != nil {
log.Printf("%s Error subscribing to %s channel: %s\n", p.GetName(), currency, err)
}
}
if p.Verbose {
log.Printf("%s Subscribed to websocket channels.\n", p.GetName())
}
<-c.ReceiveDone
log.Printf("%s Websocket client disconnected.\n", p.GetName())
}
}

View File

@@ -0,0 +1,108 @@
package hitbtc
import (
"log"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the HitBTC go routine
func (p *HitBTC) Start() {
go p.Run()
}
// Run implements the HitBTC wrapper
func (p *HitBTC) Run() {
if p.Verbose {
log.Printf("%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket), HITBTC_WEBSOCKET_ADDRESS)
log.Printf("%s polling delay: %ds.\n", p.GetName(), p.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", p.GetName(), len(p.EnabledPairs), p.EnabledPairs)
}
if p.Websocket {
go p.WebsocketClient()
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (p *HitBTC) UpdateTicker(currencyPair pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick := p.GetTicker("")
for _, x := range p.GetEnabledCurrencies() {
var tp ticker.Price
curr := exchange.FormatExchangeCurrency(p.GetName(), x).String()
tp.Pair = x
tp.Ask = tick[curr].Ask
tp.Bid = tick[curr].Bid
tp.High = tick[curr].High
tp.Last = tick[curr].Last
tp.Low = tick[curr].Low
tp.Volume = tick[curr].Volume
ticker.ProcessTicker(p.GetName(), x, tp, assetType)
}
return ticker.GetTicker(p.Name, currencyPair, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (p *HitBTC) GetTickerPrice(currencyPair pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair, assetType)
if err != nil {
return p.UpdateTicker(currencyPair, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (p *HitBTC) GetOrderbookEx(currencyPair pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair, assetType)
if err == nil {
return p.UpdateOrderbook(currencyPair, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (p *HitBTC) UpdateOrderbook(currencyPair pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := p.GetOrderbook(exchange.FormatExchangeCurrency(p.GetName(), currencyPair).String(), 1000)
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
data := orderbookNew.Bids[x]
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
for x := range orderbookNew.Asks {
data := orderbookNew.Asks[x]
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
orderbook.ProcessOrderbook(p.GetName(), currencyPair, orderBook, assetType)
return orderbook.GetOrderbook(p.Name, currencyPair, assetType)
}
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// HitBTC exchange
func (p *HitBTC) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = p.GetName()
accountBalance, err := p.GetBalances()
if err != nil {
return response, err
}
for _, item := range accountBalance {
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = item.Currency
exchangeCurrency.TotalValue = item.Available
exchangeCurrency.Hold = item.Reserved
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil
}

View File

@@ -321,6 +321,27 @@
"Uppercase": true
}
},
{
"Name": "HitBTC",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "BTCUSD,ETHBTC,ETHUSD",
"EnabledPairs": "BTCUSD",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Huobi",
"Enabled": true,