Files
gocryptotrader/exchanges/bitfinex/bitfinex.go
Adam 504c2fad6d Feature: Implement funding rates, futures and coin margin (exchange API coverage) (#530)
* ALMOST THERE

* more api wips

* more api thingz

* testing n more api wipz

* more apiz

* more wips

* what is goin on

* more wips

* whip n testing

* testing

* testing

no keys

* remove log

* kraken is broken

ugh

* still broken

* fixing auth funcs + usdtm api docs

* wip

* api stuffs

* whip

* more wips

* whip

* more wip

* api wip n testing

* wip

* wip

* unsaved

* wip n testing

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* whip

* wrapper authenticated functions

* adding asset type and fixing dependencies

* wip

* binance auth wrapper start

* wrapper functionality

* wip

* wip

* wip

* wrapper cancel functions

* order submission for wrappers

* wip

* more error fixing and nits

* websocket beginning n error fix

* wip

* WOW

* glorious n shazzy nits

* useless nits

* wip

* fixing things

* merge stuffs

* crapveyor

* crapveyor rebuild

* probably broke more things than he fixed

* rm lns n other thangs

* hope

* please

* stop it

* done

* ofcourse

* rm vb

* fix lbank

* appveyor please

* float lev

* DONT ASK RYAN FOR HELP EVER

* wip

* wip

* endpoint upgrades continued

* path upgrade

* NeeeNeeeNeeeNeeeNING

* fix stuffs

* fixing time issue

* fixing broken funcs

* glorious nits

* shaz changes

* fixing errors for fundmon

* more error fixing for fundmon

* test running past 30s

* basic changes

* THX AGAIN SHAZBERT

* path system upgrade

* config upgrade

* unsaved stuffs

* broken wip config upgrade

* path system upgrade contd.

* path system upgrade contd

* path upgrade ready for review

* testing verbose removed

* linter stuffs

* appveyor stuffs

* appveyor stuff

* fixed?

* bugfix

* wip

* broken stuff

* fix test

* wierd hack fix

* appveyor pls stop

* error found

* more useless nits

* bitmex err

* broken wip

* broken wip path upgrade change to uint32

* changed url lookups to uint

* WOW

* ready4review

* config fixed HOPEFULLY

* config fix and glorious changes

* efficient way of getting orders and open orders

* binance wrapper logic fixing

* testing, adding tests and fixing lot of errrrrs

* merge master

* appveyor stuffs

* appveyor stuffs

* fmt

* test

* octalLiteral issue fix?

* octalLiteral fix?

* rm vb

* prnt ln to restart

* adding testz

* test fixzzz

* READY FOR REVIEW

* Actually ready now

* FORMATTING

* addressing shazzy n glorious nits

* crapveyor

* rm vb

* small change

* fixing err

* shazbert nits

* review changes

* requested changes

* more requested changes

* noo

* last nit fixes

* restart appveyor

* improving test cov

* Update .golangci.yml

* shazbert changes

* moving pair formatting

* format pair update wip

* path upgrade complete

* error fix

* appveyor linters

* more linters

* remove testexch

* more formatting changes

* changes

* shazbert changes

* checking older requested changes to ensure completion

* wip

* fixing broken code

* error fix

* all fixed

* additional changes

* more changes

* remove commented code

* ftx margin api

* appveyor fixes

* more appveyor issues + test addition

* more appveyor issues + test addition

* remove unnecessary

* testing

* testing, fixing okex api, error fix

* git merge fix

* go sum

* glorious changes and error fix

* rm vb

* more glorious changes and go mod tidy

* fixed now

* okex testing upgrade

* old config migration and batch fetching fix

* added test

* glorious requested changes WIP

* tested and fixed

* go fmted

* go fmt and test fix

* additional funcs and tests for fundingRates

* OKEX tested and fixed

* appveyor fixes

* ineff assign

* 1 glorious change

* error fix

* typo

* shazbert changes

* glorious code changes and path fixing huobi WIP

* adding assetType to accountinfo functions

* fixing panic

* panic fix and updating account info wrappers WIP

* updateaccountinfo updated

* testing WIP binance USDT n Coin Margined and Kraken Futures

* auth functions tested and fixed

* added test

* config reverted

* shazbert and glorious changes

* shazbert and glorious changes

* latest changes and portfolio update

* go fmt change:

* remove commented codes

* improved error checking

* index out of range fix

* rm ln

* critical nit

* glorious latest changes

* appveyor changes

* shazbert change

* easier readability

* latest glorious changes

* shadow dec

* assetstore updated

* last change

* another last change

* merge changes

* go mod tidy

* thrasher requested changes wip

* improving struct layouts

* appveyor go fmt

* remove unnecessary code

* shazbert changes

* small change

* oopsie

* tidy

* configtest reverted

* error fix

* oopsie

* for what

* test patch fix

* insecurities

* fixing tests

* fix config
2021-02-12 16:19:18 +11:00

1673 lines
49 KiB
Go

package bitfinex
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/log"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
const (
bitfinexAPIURLBase = "https://api.bitfinex.com"
// Version 1 API endpoints
bitfinexAPIVersion = "/v1/"
bitfinexStats = "stats/"
bitfinexAccountInfo = "account_infos"
bitfinexAccountFees = "account_fees"
bitfinexAccountSummary = "summary"
bitfinexDeposit = "deposit/new"
bitfinexBalances = "balances"
bitfinexTransfer = "transfer"
bitfinexWithdrawal = "withdraw"
bitfinexOrderNew = "order/new"
bitfinexOrderNewMulti = "order/new/multi"
bitfinexOrderCancel = "order/cancel"
bitfinexOrderCancelMulti = "order/cancel/multi"
bitfinexOrderCancelAll = "order/cancel/all"
bitfinexOrderCancelReplace = "order/cancel/replace"
bitfinexOrderStatus = "order/status"
bitfinexInactiveOrders = "orders/hist"
bitfinexOrders = "orders"
bitfinexPositions = "positions"
bitfinexClaimPosition = "position/claim"
bitfinexHistory = "history"
bitfinexHistoryMovements = "history/movements"
bitfinexTradeHistory = "mytrades"
bitfinexOfferNew = "offer/new"
bitfinexOfferCancel = "offer/cancel"
bitfinexActiveCredits = "credits"
bitfinexOffers = "offers"
bitfinexMarginActiveFunds = "taken_funds"
bitfinexMarginUnusedFunds = "unused_taken_funds"
bitfinexMarginTotalFunds = "total_taken_funds"
bitfinexMarginClose = "funding/close"
bitfinexLendbook = "lendbook/"
bitfinexLends = "lends/"
bitfinexLeaderboard = "rankings"
// Version 2 API endpoints
bitfinexAPIVersion2 = "/v2/"
bitfinexV2MarginFunding = "calc/trade/avg?"
bitfinexV2Balances = "auth/r/wallets"
bitfinexV2AccountInfo = "auth/r/info/user"
bitfinexV2FundingInfo = "auth/r/info/funding/%s"
bitfinexDerivativeData = "status/deriv?"
bitfinexPlatformStatus = "platform/status"
bitfinexTickerBatch = "tickers"
bitfinexTicker = "ticker/"
bitfinexTrades = "trades/"
bitfinexOrderbook = "book/"
bitfinexStatistics = "stats1/"
bitfinexCandles = "candles/trade"
bitfinexKeyPermissions = "key_info"
bitfinexMarginInfo = "margin_infos"
bitfinexDepositMethod = "conf/pub:map:currency:label"
bitfinexMarginPairs = "conf/pub:list:pair:margin"
// Bitfinex platform status values
// When the platform is marked in maintenance mode bots should stop trading
// activity. Cancelling orders will be possible.
bitfinexMaintenanceMode = 0
bitfinexOperativeMode = 1
bitfinexChecksumFlag = 131072
bitfinexWsSequenceFlag = 65536
)
// Bitfinex is the overarching type across the bitfinex package
type Bitfinex struct {
exchange.Base
WebsocketSubdChannels map[int]WebsocketChanInfo
}
// GetPlatformStatus returns the Bifinex platform status
func (b *Bitfinex) GetPlatformStatus() (int, error) {
var response []int
err := b.SendHTTPRequest(exchange.RestSpot,
bitfinexAPIVersion2+
bitfinexPlatformStatus,
&response,
platformStatus)
if err != nil {
return -1, err
}
switch response[0] {
case bitfinexOperativeMode:
return bitfinexOperativeMode, nil
case bitfinexMaintenanceMode:
return bitfinexMaintenanceMode, nil
}
return -1, fmt.Errorf("unexpected platform status value %d", response[0])
}
// GetV2MarginFunding gets borrowing rates for margin trading
func (b *Bitfinex) GetV2MarginFunding(symbol, amount string, period int32) (MarginV2FundingData, error) {
var resp []interface{}
var response MarginV2FundingData
params := make(map[string]interface{})
params["symbol"] = symbol
params["period"] = period
params["amount"] = amount
err := b.SendAuthenticatedHTTPRequestV2(exchange.RestSpot, http.MethodPost,
bitfinexV2MarginFunding,
params,
&resp,
getAccountFees)
if err != nil {
return response, err
}
if len(resp) != 2 {
return response, errors.New("invalid data received")
}
avgRate, ok := resp[0].(float64)
if !ok {
return response, errors.New("failed type assertion for rate")
}
avgAmount, ok := resp[1].(float64)
if !ok {
return response, errors.New("failed type assertion for amount")
}
response.Symbol = symbol
response.RateAverage = avgRate
response.AmountAverage = avgAmount
return response, nil
}
// GetV2FundingInfo gets funding info for margin pairs
func (b *Bitfinex) GetV2FundingInfo(key string) (MarginFundingDataV2, error) {
var resp []interface{}
var response MarginFundingDataV2
err := b.SendAuthenticatedHTTPRequestV2(exchange.RestSpot, http.MethodPost,
fmt.Sprintf(bitfinexV2FundingInfo, key),
nil,
&resp,
getAccountFees)
if err != nil {
return response, err
}
if len(resp) != 3 {
return response, errors.New("invalid data received")
}
sym, ok := resp[0].(string)
if !ok {
return response, errors.New("failed type assertion for sym")
}
symbol, ok := resp[1].(string)
if !ok {
return response, errors.New("failed type assertion for symbol")
}
fundingData, ok := resp[2].([]interface{})
if !ok {
return response, errors.New("failed type assertion for fundingData")
}
response.Sym = sym
response.Symbol = symbol
if len(fundingData) < 4 {
return response, errors.New("invalid length of fundingData")
}
for x := 0; x < 3; x++ {
_, ok := fundingData[x].(float64)
if !ok {
return response, fmt.Errorf("type conversion failed for x = %d", x)
}
}
response.Data.YieldLoan = fundingData[0].(float64)
response.Data.YieldLend = fundingData[1].(float64)
response.Data.DurationLoan = fundingData[2].(float64)
response.Data.DurationLend = fundingData[3].(float64)
return response, nil
}
// GetAccountInfoV2 gets V2 account data
func (b *Bitfinex) GetAccountInfoV2() (AccountV2Data, error) {
var resp AccountV2Data
var data []interface{}
err := b.SendAuthenticatedHTTPRequestV2(exchange.RestSpot, http.MethodPost,
bitfinexV2AccountInfo,
nil,
&data,
getAccountFees)
if err != nil {
return resp, err
}
if len(data) < 8 {
return resp, errors.New("invalid length of data")
}
var ok bool
var tempString string
var tempFloat float64
if tempFloat, ok = data[0].(float64); !ok {
return resp, errors.New("type assertion failed for id, check for api updates")
}
resp.ID = int64(tempFloat)
if tempString, ok = data[1].(string); !ok {
return resp, errors.New("type assertion failed for email, check for api updates")
}
resp.Email = tempString
if tempString, ok = data[2].(string); !ok {
return resp, errors.New("type assertion failed for username, check for api updates")
}
resp.Username = tempString
if tempFloat, ok = data[3].(float64); !ok {
return resp, errors.New("type assertion failed for accountcreate, check for api updates")
}
resp.MTSAccountCreate = int64(tempFloat)
if tempFloat, ok = data[4].(float64); !ok {
return resp, errors.New("type assertion failed for verified, check for api updates")
}
resp.Verified = int64(tempFloat)
if tempString, ok = data[7].(string); !ok {
return resp, errors.New("type assertion failed for timezone, check for api updates")
}
resp.Timezone = tempString
return resp, nil
}
// GetV2Balances gets v2 balances
func (b *Bitfinex) GetV2Balances() ([]WalletDataV2, error) {
var resp []WalletDataV2
var data [][4]interface{}
err := b.SendAuthenticatedHTTPRequestV2(exchange.RestSpot, http.MethodPost,
bitfinexV2Balances,
nil,
&data,
getAccountFees)
if err != nil {
return resp, err
}
for x := range data {
wType, ok := data[x][0].(string)
if !ok {
return resp, errors.New("type assertion failed for walletType, check for api updates")
}
curr, ok := data[x][1].(string)
if !ok {
return resp, errors.New("type assertion failed for currency, check for api updates")
}
bal, ok := data[x][2].(float64)
if !ok {
return resp, errors.New("type assertion failed for balance, check for api updates")
}
unsettledInterest, ok := data[x][3].(float64)
if !ok {
return resp, errors.New("type assertion failed for unsettledInterest, check for api updates")
}
resp = append(resp, WalletDataV2{
WalletType: wType,
Currency: curr,
Balance: bal,
UnsettledInterest: unsettledInterest,
})
}
return resp, nil
}
// GetMarginPairs gets pairs that allow margin trading
func (b *Bitfinex) GetMarginPairs() ([]string, error) {
var resp [][]string
path := bitfinexAPIVersion2 + bitfinexMarginPairs
err := b.SendHTTPRequest(exchange.RestSpot, path, &resp, status)
if err != nil {
return nil, err
}
if len(resp) != 1 {
return nil, errors.New("invalid response")
}
return resp[0], nil
}
// GetDerivativeData gets data for the queried derivative
func (b *Bitfinex) GetDerivativeData(keys, startTime, endTime string, sort, limit int64) (DerivativeDataResponse, error) {
var result [][19]interface{}
var response DerivativeDataResponse
params := url.Values{}
params.Set("keys", keys)
if startTime != "" {
params.Set("start", startTime)
}
if endTime != "" {
params.Set("end", endTime)
}
if sort != 0 {
params.Set("sort", strconv.FormatInt(sort, 10))
}
if limit != 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
path := bitfinexAPIVersion2 + bitfinexDerivativeData +
params.Encode()
err := b.SendHTTPRequest(exchange.RestSpot, path, &result, status)
if err != nil {
return response, err
}
if len(result) < 1 {
return response, errors.New("invalid response, array length too small, check api docs for updates")
}
if len(result[0]) < 19 {
return response, errors.New("invalid response, array length too small, check api docs for updates")
}
var floatData float64
var stringData string
var ok bool
if stringData, ok = result[0][0].(string); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.Key = stringData
if floatData, ok = result[0][1].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.MTS = floatData
if floatData, ok = result[0][3].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.DerivPrice = floatData
if floatData, ok = result[0][4].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.SpotPrice = floatData
if floatData, ok = result[0][6].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.InsuranceFundBalance = floatData
if floatData, ok = result[0][8].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.NextFundingEventTS = floatData
if floatData, ok = result[0][9].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.NextFundingAccured = floatData
if floatData, ok = result[0][10].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.NextFundingStep = floatData
if floatData, ok = result[0][12].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.CurrentFunding = floatData
if floatData, ok = result[0][15].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.MarkPrice = floatData
if floatData, ok = result[0][18].(float64); !ok {
return response, errors.New("type assertion failed, check for api updates")
}
response.OpenInterest = floatData
return response, nil
}
// GetTickerBatch returns all supported ticker information
func (b *Bitfinex) GetTickerBatch() (map[string]Ticker, error) {
var response [][]interface{}
path := bitfinexAPIVersion2 + bitfinexTickerBatch +
"?symbols=ALL"
err := b.SendHTTPRequest(exchange.RestSpot, path, &response, tickerBatch)
if err != nil {
return nil, err
}
var tickers = make(map[string]Ticker)
for x := range response {
if len(response[x]) > 11 {
tickers[response[x][0].(string)] = Ticker{
FlashReturnRate: response[x][1].(float64),
Bid: response[x][2].(float64),
BidPeriod: int64(response[x][3].(float64)),
BidSize: response[x][4].(float64),
Ask: response[x][5].(float64),
AskPeriod: int64(response[x][6].(float64)),
AskSize: response[x][7].(float64),
DailyChange: response[x][8].(float64),
DailyChangePerc: response[x][9].(float64),
Last: response[x][10].(float64),
Volume: response[x][11].(float64),
High: response[x][12].(float64),
Low: response[x][13].(float64),
FFRAmountAvailable: response[x][16].(float64),
}
continue
}
tickers[response[x][0].(string)] = Ticker{
Bid: response[x][1].(float64),
BidSize: response[x][2].(float64),
Ask: response[x][3].(float64),
AskSize: response[x][4].(float64),
DailyChange: response[x][5].(float64),
DailyChangePerc: response[x][6].(float64),
Last: response[x][7].(float64),
Volume: response[x][8].(float64),
High: response[x][9].(float64),
Low: response[x][10].(float64),
}
}
return tickers, nil
}
// GetTicker returns ticker information for one symbol
func (b *Bitfinex) GetTicker(symbol string) (Ticker, error) {
var response []interface{}
path := bitfinexAPIVersion2 + bitfinexTicker + symbol
err := b.SendHTTPRequest(exchange.RestSpot, path, &response, tickerFunction)
if err != nil {
return Ticker{}, err
}
if len(response) > 10 {
return Ticker{
FlashReturnRate: response[0].(float64),
Bid: response[1].(float64),
BidPeriod: int64(response[2].(float64)),
BidSize: response[3].(float64),
Ask: response[4].(float64),
AskPeriod: int64(response[5].(float64)),
AskSize: response[6].(float64),
DailyChange: response[7].(float64),
DailyChangePerc: response[8].(float64),
Last: response[9].(float64),
Volume: response[10].(float64),
High: response[11].(float64),
Low: response[12].(float64),
FFRAmountAvailable: response[15].(float64),
}, nil
}
return Ticker{
Bid: response[0].(float64),
BidSize: response[1].(float64),
Ask: response[2].(float64),
AskSize: response[3].(float64),
DailyChange: response[4].(float64),
DailyChangePerc: response[5].(float64),
Last: response[6].(float64),
Volume: response[7].(float64),
High: response[8].(float64),
Low: response[9].(float64),
}, nil
}
// GetTrades gets historic trades that occurred on the exchange
//
// currencyPair e.g. "tBTCUSD"
// timestampStart is a millisecond timestamp
// timestampEnd is a millisecond timestamp
// reOrderResp reorders the returned data.
func (b *Bitfinex) GetTrades(currencyPair string, limit, timestampStart, timestampEnd int64, reOrderResp bool) ([]Trade, error) {
v := url.Values{}
if limit > 0 {
v.Set("limit", strconv.FormatInt(limit, 10))
}
if timestampStart > 0 {
v.Set("start", strconv.FormatInt(timestampStart, 10))
}
if timestampEnd > 0 {
v.Set("end", strconv.FormatInt(timestampEnd, 10))
}
sortVal := "0"
if reOrderResp {
sortVal = "1"
}
v.Set("sort", sortVal)
path := bitfinexAPIVersion2 + bitfinexTrades + currencyPair + "/hist" + "?" + v.Encode()
var resp [][]interface{}
err := b.SendHTTPRequest(exchange.RestSpot, path, &resp, tradeRateLimit)
if err != nil {
return nil, err
}
var history []Trade
for i := range resp {
amount := resp[i][2].(float64)
side := order.Buy.String()
if amount < 0 {
side = order.Sell.String()
amount *= -1
}
if len(resp[i]) > 4 {
history = append(history, Trade{
TID: int64(resp[i][0].(float64)),
Timestamp: int64(resp[i][1].(float64)),
Amount: amount,
Rate: resp[i][3].(float64),
Period: int64(resp[i][4].(float64)),
Type: side,
})
continue
}
history = append(history, Trade{
TID: int64(resp[i][0].(float64)),
Timestamp: int64(resp[i][1].(float64)),
Amount: amount,
Price: resp[i][3].(float64),
Type: side,
})
}
return history, nil
}
// GetOrderbook retieves the orderbook bid and ask price points for a currency
// pair - By default the response will return 25 bid and 25 ask price points.
// symbol - Example "tBTCUSD"
// precision - P0,P1,P2,P3,R0
// Values can contain limit amounts for both the asks and bids - Example
// "len" = 100
func (b *Bitfinex) GetOrderbook(symbol, precision string, limit int64) (Orderbook, error) {
var u = url.Values{}
if limit > 0 {
u.Set("len", strconv.FormatInt(limit, 10))
}
path := bitfinexAPIVersion2 + bitfinexOrderbook + symbol + "/" + precision + "?" + u.Encode()
var response [][]interface{}
err := b.SendHTTPRequest(exchange.RestSpot, path, &response, orderbookFunction)
if err != nil {
return Orderbook{}, err
}
var o Orderbook
if precision == "R0" {
// Raw book changes the return
for x := range response {
var b Book
if len(response[x]) > 3 {
// Funding currency
b.Amount = response[x][3].(float64)
b.Rate = response[x][2].(float64)
b.Period = response[x][1].(float64)
b.OrderID = int64(response[x][0].(float64))
if b.Amount > 0 {
o.Asks = append(o.Asks, b)
} else {
b.Amount *= -1
o.Bids = append(o.Bids, b)
}
} else {
// Trading currency
b.Amount = response[x][2].(float64)
b.Price = response[x][1].(float64)
b.OrderID = int64(response[x][0].(float64))
if b.Amount > 0 {
o.Bids = append(o.Bids, b)
} else {
b.Amount *= -1
o.Asks = append(o.Asks, b)
}
}
}
} else {
for x := range response {
var b Book
if len(response[x]) > 3 {
// Funding currency
b.Amount = response[x][3].(float64)
b.Count = int64(response[x][2].(float64))
b.Period = response[x][1].(float64)
b.Rate = response[x][0].(float64)
if b.Amount > 0 {
o.Asks = append(o.Asks, b)
} else {
b.Amount *= -1
o.Bids = append(o.Bids, b)
}
} else {
// Trading currency
b.Amount = response[x][2].(float64)
b.Count = int64(response[x][1].(float64))
b.Price = response[x][0].(float64)
if b.Amount > 0 {
o.Bids = append(o.Bids, b)
} else {
b.Amount *= -1
o.Asks = append(o.Asks, b)
}
}
}
}
return o, nil
}
// GetStats returns various statistics about the requested pair
func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) {
var response []Stat
path := bitfinexAPIVersion + bitfinexStats + symbol
return response, b.SendHTTPRequest(exchange.RestSpot, path, &response, statsV1)
}
// GetFundingBook the entire margin funding book for both bids and asks sides
// per currency string
// symbol - example "USD"
// WARNING: Orderbook now has this support, will be deprecated once a full
// conversion to full V2 API update is done.
func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) {
response := FundingBook{}
path := bitfinexAPIVersion + bitfinexLendbook + symbol
if err := b.SendHTTPRequest(exchange.RestSpot, path, &response, fundingbook); err != nil {
return response, err
}
return response, nil
}
// GetLends returns a list of the most recent funding data for the given
// currency: total amount provided and Flash Return Rate (in % by 365 days)
// over time
// Symbol - example "USD"
func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) {
var response []Lends
path := common.EncodeURLValues(bitfinexAPIVersion+
bitfinexLends+
symbol,
values)
return response, b.SendHTTPRequest(exchange.RestSpot, path, &response, lends)
}
// GetCandles returns candle chart data
// timeFrame values: '1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D',
// '7D', '14D', '1M'
// section values: last or hist
func (b *Bitfinex) GetCandles(symbol, timeFrame string, start, end int64, limit uint32, historic bool) ([]Candle, error) {
var fundingPeriod string
if symbol[0] == 'f' {
fundingPeriod = ":p30"
}
var path = bitfinexAPIVersion2 +
bitfinexCandles +
":" +
timeFrame +
":" +
symbol +
fundingPeriod
if historic {
v := url.Values{}
if start > 0 {
v.Set("start", strconv.FormatInt(start, 10))
}
if end > 0 {
v.Set("end", strconv.FormatInt(end, 10))
}
if limit > 0 {
v.Set("limit", strconv.FormatInt(int64(limit), 10))
}
path += "/hist"
if len(v) > 0 {
path += "?" + v.Encode()
}
var response [][]interface{}
err := b.SendHTTPRequest(exchange.RestSpot, path, &response, candle)
if err != nil {
return nil, err
}
var c []Candle
for i := range response {
c = append(c, Candle{
Timestamp: time.Unix(int64(response[i][0].(float64)/1000), 0),
Open: response[i][1].(float64),
Close: response[i][2].(float64),
High: response[i][3].(float64),
Low: response[i][4].(float64),
Volume: response[i][5].(float64),
})
}
return c, nil
}
path += "/last"
var response []interface{}
err := b.SendHTTPRequest(exchange.RestSpot, path, &response, candle)
if err != nil {
return nil, err
}
if len(response) == 0 {
return nil, errors.New("no data returned")
}
return []Candle{{
Timestamp: time.Unix(int64(response[0].(float64))/1000, 0),
Open: response[1].(float64),
Close: response[2].(float64),
High: response[3].(float64),
Low: response[4].(float64),
Volume: response[5].(float64),
}}, nil
}
// GetConfigurations fetchs currency and symbol site configuration data.
func (b *Bitfinex) GetConfigurations() error {
return common.ErrNotYetImplemented
}
// GetStatus returns different types of platform information - currently
// supports derivatives pair status only.
func (b *Bitfinex) GetStatus() error {
return common.ErrNotYetImplemented
}
// GetLiquidationFeed returns liquidations. By default it will retrieve the most
// recent liquidations, but time-specific data can be retrieved using
// timestamps.
func (b *Bitfinex) GetLiquidationFeed() error {
return common.ErrNotYetImplemented
}
// GetLeaderboard returns leaderboard standings for unrealized profit (period
// delta), unrealized profit (inception), volume, and realized profit.
// Allowed key values: "plu_diff" for unrealized profit (period delta), "plu"
// for unrealized profit (inception); "vol" for volume; "plr" for realized
// profit
// Allowed time frames are 3h, 1w and 1M
// Allowed symbols are trading pairs (e.g. tBTCUSD, tETHUSD and tGLOBAL:USD)
func (b *Bitfinex) GetLeaderboard(key, timeframe, symbol string, sort, limit int, start, end string) ([]LeaderboardEntry, error) {
validLeaderboardKey := func(input string) bool {
switch input {
case LeaderboardUnrealisedProfitPeriodDelta,
LeaderboardUnrealisedProfitInception,
LeaderboardVolume,
LeaderbookRealisedProfit:
return true
default:
return false
}
}
if !validLeaderboardKey(key) {
return nil, errors.New("invalid leaderboard key")
}
path := fmt.Sprintf("%s/%s:%s:%s/hist", bitfinexAPIVersion2+bitfinexLeaderboard,
key,
timeframe,
symbol)
vals := url.Values{}
if sort != 0 {
vals.Set("sort", strconv.Itoa(sort))
}
if limit != 0 {
vals.Set("limit", strconv.Itoa(limit))
}
if start != "" {
vals.Set("start", start)
}
if end != "" {
vals.Set("end", end)
}
path = common.EncodeURLValues(path, vals)
var resp []interface{}
if err := b.SendHTTPRequest(exchange.RestSpot, path, &resp, leaderBoardReqRate); err != nil {
return nil, err
}
parseTwitterHandle := func(i interface{}) string {
r, ok := i.(string)
if !ok {
return ""
}
return r
}
var result []LeaderboardEntry
for x := range resp {
r := resp[x].([]interface{})
result = append(result, LeaderboardEntry{
Timestamp: time.Unix(0, int64(r[0].(float64))*int64(time.Millisecond)),
Username: r[2].(string),
Ranking: int(r[3].(float64)),
Value: r[6].(float64),
TwitterHandle: parseTwitterHandle(r[9]),
})
}
return result, nil
}
// GetMarketAveragePrice calculates the average execution price for Trading or
// rate for Margin funding
func (b *Bitfinex) GetMarketAveragePrice() error {
return common.ErrNotYetImplemented
}
// GetForeignExchangeRate calculates the exchange rate between two currencies
func (b *Bitfinex) GetForeignExchangeRate() error {
return common.ErrNotYetImplemented
}
// GetAccountFees returns information about your account trading fees
func (b *Bitfinex) GetAccountFees() ([]AccountInfo, error) {
var responses []AccountInfo
return responses, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexAccountInfo,
nil,
&responses,
getAccountFees)
}
// GetWithdrawalFees - Gets all fee rates for withdrawals
func (b *Bitfinex) GetWithdrawalFees() (AccountFees, error) {
response := AccountFees{}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexAccountFees,
nil,
&response,
getWithdrawalFees)
}
// GetAccountSummary returns a 30-day summary of your trading volume and return
// on margin funding
func (b *Bitfinex) GetAccountSummary() (AccountSummary, error) {
response := AccountSummary{}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexAccountSummary,
nil,
&response,
getAccountSummary)
}
// NewDeposit returns a new deposit address
// Method - Example methods accepted: “bitcoin”, “litecoin”, “ethereum”,
// “tethers", "ethereumc", "zcash", "monero", "iota", "bcash"
// WalletName - accepted: “trading”, “exchange”, “deposit”
// renew - Default is 0. If set to 1, will return a new unused deposit address
func (b *Bitfinex) NewDeposit(method, walletName string, renew int) (DepositResponse, error) {
if !common.StringDataCompare(AcceptedWalletNames, walletName) {
return DepositResponse{},
fmt.Errorf("walletname: [%s] is not allowed, supported: %s",
walletName,
AcceptedWalletNames)
}
response := DepositResponse{}
req := make(map[string]interface{})
req["method"] = method
req["wallet_name"] = walletName
req["renew"] = renew
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexDeposit,
req,
&response,
newDepositAddress)
}
// GetKeyPermissions checks the permissions of the key being used to generate
// this request.
func (b *Bitfinex) GetKeyPermissions() (KeyPermissions, error) {
response := KeyPermissions{}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexKeyPermissions,
nil,
&response,
getAccountFees)
}
// GetMarginInfo shows your trading wallet information for margin trading
func (b *Bitfinex) GetMarginInfo() ([]MarginInfo, error) {
var response []MarginInfo
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexMarginInfo,
nil,
&response,
getMarginInfo)
}
// GetAccountBalance returns full wallet balance information
func (b *Bitfinex) GetAccountBalance() ([]Balance, error) {
var response []Balance
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexBalances,
nil,
&response,
getAccountBalance)
}
// WalletTransfer move available balances between your wallets
// Amount - Amount to move
// Currency - example "BTC"
// WalletFrom - example "exchange"
// WalletTo - example "deposit"
func (b *Bitfinex) WalletTransfer(amount float64, currency, walletFrom, walletTo string) (WalletTransfer, error) {
var response []WalletTransfer
req := make(map[string]interface{})
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
req["currency"] = currency
req["walletfrom"] = walletFrom
req["walletto"] = walletTo
err := b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexTransfer,
req,
&response,
walletTransfer)
if err != nil {
return WalletTransfer{}, err
}
if response[0].Status == "error" {
return WalletTransfer{}, errors.New(response[0].Message)
}
return response[0], nil
}
// WithdrawCryptocurrency requests a withdrawal from one of your wallets.
// For FIAT, use WithdrawFIAT
func (b *Bitfinex) WithdrawCryptocurrency(wallet, address, paymentID string, amount float64, c currency.Code) (Withdrawal, error) {
var response []Withdrawal
req := make(map[string]interface{})
req["withdraw_type"] = b.ConvertSymbolToWithdrawalType(c)
req["walletselected"] = wallet
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
req["address"] = address
if paymentID != "" {
req["payment_id"] = paymentID
}
err := b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexWithdrawal,
req,
&response,
withdrawV1)
if err != nil {
return Withdrawal{}, err
}
if response[0].Status == "error" {
return Withdrawal{}, errors.New(response[0].Message)
}
return response[0], nil
}
// WithdrawFIAT Sends an authenticated request to withdraw FIAT currency
func (b *Bitfinex) WithdrawFIAT(withdrawalType, walletType string, withdrawRequest *withdraw.Request) (Withdrawal, error) {
var response []Withdrawal
req := make(map[string]interface{})
req["withdraw_type"] = withdrawalType
req["walletselected"] = walletType
req["amount"] = strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64)
req["account_name"] = withdrawRequest.Fiat.Bank.AccountName
req["account_number"] = withdrawRequest.Fiat.Bank.AccountNumber
req["bank_name"] = withdrawRequest.Fiat.Bank.BankName
req["bank_address"] = withdrawRequest.Fiat.Bank.BankAddress
req["bank_city"] = withdrawRequest.Fiat.Bank.BankPostalCity
req["bank_country"] = withdrawRequest.Fiat.Bank.BankCountry
req["expressWire"] = withdrawRequest.Fiat.IsExpressWire
req["swift"] = withdrawRequest.Fiat.Bank.SWIFTCode
req["detail_payment"] = withdrawRequest.Description
req["currency"] = withdrawRequest.Currency
req["account_address"] = withdrawRequest.Fiat.Bank.BankAddress
if withdrawRequest.Fiat.RequiresIntermediaryBank {
req["intermediary_bank_name"] = withdrawRequest.Fiat.IntermediaryBankName
req["intermediary_bank_address"] = withdrawRequest.Fiat.IntermediaryBankAddress
req["intermediary_bank_city"] = withdrawRequest.Fiat.IntermediaryBankCity
req["intermediary_bank_country"] = withdrawRequest.Fiat.IntermediaryBankCountry
req["intermediary_bank_account"] = strconv.FormatFloat(withdrawRequest.Fiat.IntermediaryBankAccountNumber, 'f', -1, 64)
req["intermediary_bank_swift"] = withdrawRequest.Fiat.IntermediarySwiftCode
}
err := b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexWithdrawal,
req,
&response,
withdrawV1)
if err != nil {
return Withdrawal{}, err
}
if response[0].Status == "error" {
return Withdrawal{}, errors.New(response[0].Message)
}
return response[0], nil
}
// NewOrder submits a new order and returns a order information
// Major Upgrade needed on this function to include all query params
func (b *Bitfinex) NewOrder(currencyPair, orderType string, amount, price float64, buy, hidden bool) (Order, error) {
if !common.StringDataCompare(AcceptedOrderType, orderType) {
return Order{}, fmt.Errorf("order type %s not accepted", orderType)
}
response := Order{}
req := make(map[string]interface{})
req["symbol"] = currencyPair
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
req["price"] = strconv.FormatFloat(price, 'f', -1, 64)
req["type"] = orderType
req["is_hidden"] = hidden
req["side"] = order.Sell.Lower()
if buy {
req["side"] = order.Buy.Lower()
}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderNew,
req,
&response,
orderV1)
}
// NewOrderMulti allows several new orders at once
func (b *Bitfinex) NewOrderMulti(orders []PlaceOrder) (OrderMultiResponse, error) {
response := OrderMultiResponse{}
req := make(map[string]interface{})
req["orders"] = orders
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderNewMulti,
req,
&response,
orderMulti)
}
// CancelExistingOrder cancels a single order by OrderID
func (b *Bitfinex) CancelExistingOrder(orderID int64) (Order, error) {
response := Order{}
req := make(map[string]interface{})
req["order_id"] = orderID
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderCancel,
req,
&response,
orderMulti)
}
// CancelMultipleOrders cancels multiple orders
func (b *Bitfinex) CancelMultipleOrders(orderIDs []int64) (string, error) {
response := GenericResponse{}
req := make(map[string]interface{})
req["order_ids"] = orderIDs
return response.Result, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderCancelMulti,
req,
nil,
orderMulti)
}
// CancelAllExistingOrders cancels all active and open orders
func (b *Bitfinex) CancelAllExistingOrders() (string, error) {
response := GenericResponse{}
return response.Result, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderCancelAll,
nil,
nil,
orderMulti)
}
// ReplaceOrder replaces an older order with a new order
func (b *Bitfinex) ReplaceOrder(orderID int64, symbol string, amount, price float64, buy bool, orderType string, hidden bool) (Order, error) {
response := Order{}
req := make(map[string]interface{})
req["order_id"] = orderID
req["symbol"] = symbol
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
req["price"] = strconv.FormatFloat(price, 'f', -1, 64)
req["exchange"] = "bitfinex"
req["type"] = orderType
req["is_hidden"] = hidden
if buy {
req["side"] = order.Buy.Lower()
} else {
req["side"] = order.Sell.Lower()
}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderCancelReplace,
req,
&response,
orderMulti)
}
// GetOrderStatus returns order status information
func (b *Bitfinex) GetOrderStatus(orderID int64) (Order, error) {
orderStatus := Order{}
req := make(map[string]interface{})
req["order_id"] = orderID
return orderStatus, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderStatus,
req,
&orderStatus,
orderMulti)
}
// GetInactiveOrders returns order status information
func (b *Bitfinex) GetInactiveOrders() ([]Order, error) {
var response []Order
req := make(map[string]interface{})
req["limit"] = "100"
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexInactiveOrders,
req,
&response,
orderMulti)
}
// GetOpenOrders returns all active orders and statuses
func (b *Bitfinex) GetOpenOrders() ([]Order, error) {
var response []Order
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrders,
nil,
&response,
orderMulti)
}
// GetActivePositions returns an array of active positions
func (b *Bitfinex) GetActivePositions() ([]Position, error) {
var response []Position
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexPositions,
nil,
&response,
orderMulti)
}
// ClaimPosition allows positions to be claimed
func (b *Bitfinex) ClaimPosition(positionID int) (Position, error) {
response := Position{}
req := make(map[string]interface{})
req["position_id"] = positionID
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexClaimPosition,
nil,
nil,
orderMulti)
}
// GetBalanceHistory returns balance history for the account
func (b *Bitfinex) GetBalanceHistory(symbol string, timeSince, timeUntil time.Time, limit int, wallet string) ([]BalanceHistory, error) {
var response []BalanceHistory
req := make(map[string]interface{})
req["currency"] = symbol
if !timeSince.IsZero() {
req["since"] = timeSince
}
if !timeUntil.IsZero() {
req["until"] = timeUntil
}
if limit > 0 {
req["limit"] = limit
}
if len(wallet) > 0 {
req["wallet"] = wallet
}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexHistory,
req,
&response,
orderMulti)
}
// GetMovementHistory returns an array of past deposits and withdrawals
func (b *Bitfinex) GetMovementHistory(symbol, method string, timeSince, timeUntil time.Time, limit int) ([]MovementHistory, error) {
var response []MovementHistory
req := make(map[string]interface{})
req["currency"] = symbol
if len(method) > 0 {
req["method"] = method
}
if !timeSince.IsZero() {
req["since"] = timeSince
}
if !timeUntil.IsZero() {
req["until"] = timeUntil
}
if limit > 0 {
req["limit"] = limit
}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexHistoryMovements,
req,
&response,
orderMulti)
}
// GetTradeHistory returns past executed trades
func (b *Bitfinex) GetTradeHistory(currencyPair string, timestamp, until time.Time, limit, reverse int) ([]TradeHistory, error) {
var response []TradeHistory
req := make(map[string]interface{})
req["currency"] = currencyPair
req["timestamp"] = timestamp
if !until.IsZero() {
req["until"] = until
}
if limit > 0 {
req["limit"] = limit
}
if reverse > 0 {
req["reverse"] = reverse
}
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexTradeHistory,
req,
&response,
orderMulti)
}
// NewOffer submits a new offer
func (b *Bitfinex) NewOffer(symbol string, amount, rate float64, period int64, direction string) (Offer, error) {
response := Offer{}
req := make(map[string]interface{})
req["currency"] = symbol
req["amount"] = amount
req["rate"] = rate
req["period"] = period
req["direction"] = direction
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOfferNew,
req,
&response,
orderMulti)
}
// CancelOffer cancels offer by offerID
func (b *Bitfinex) CancelOffer(offerID int64) (Offer, error) {
response := Offer{}
req := make(map[string]interface{})
req["offer_id"] = offerID
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOfferCancel,
req,
&response,
orderMulti)
}
// GetOfferStatus checks offer status whether it has been cancelled, execute or
// is still active
func (b *Bitfinex) GetOfferStatus(offerID int64) (Offer, error) {
response := Offer{}
req := make(map[string]interface{})
req["offer_id"] = offerID
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOrderStatus,
req,
&response,
orderMulti)
}
// GetActiveCredits returns all available credits
func (b *Bitfinex) GetActiveCredits() ([]Offer, error) {
var response []Offer
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexActiveCredits,
nil,
&response,
orderMulti)
}
// GetActiveOffers returns all current active offers
func (b *Bitfinex) GetActiveOffers() ([]Offer, error) {
var response []Offer
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexOffers,
nil,
&response,
orderMulti)
}
// GetActiveMarginFunding returns an array of active margin funds
func (b *Bitfinex) GetActiveMarginFunding() ([]MarginFunds, error) {
var response []MarginFunds
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexMarginActiveFunds,
nil,
&response,
orderMulti)
}
// GetUnusedMarginFunds returns an array of funding borrowed but not currently
// used
func (b *Bitfinex) GetUnusedMarginFunds() ([]MarginFunds, error) {
var response []MarginFunds
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexMarginUnusedFunds,
nil,
&response,
orderMulti)
}
// GetMarginTotalTakenFunds returns an array of active funding used in a
// position
func (b *Bitfinex) GetMarginTotalTakenFunds() ([]MarginTotalTakenFunds, error) {
var response []MarginTotalTakenFunds
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexMarginTotalFunds,
nil,
&response,
orderMulti)
}
// CloseMarginFunding closes an unused or used taken fund
func (b *Bitfinex) CloseMarginFunding(swapID int64) (Offer, error) {
response := Offer{}
req := make(map[string]interface{})
req["swap_id"] = swapID
return response, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost,
bitfinexMarginClose,
req,
&response,
closeFunding)
}
// SendHTTPRequest sends an unauthenticated request
func (b *Bitfinex) SendHTTPRequest(ep exchange.URL, path string, result interface{}, e request.EndpointLimit) error {
endpoint, err := b.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
return b.SendPayload(context.Background(), &request.Item{
Method: http.MethodGet,
Path: endpoint + path,
Result: result,
Verbose: b.Verbose,
HTTPDebugging: b.HTTPDebugging,
HTTPRecording: b.HTTPRecording,
Endpoint: e})
}
// SendAuthenticatedHTTPRequest sends an autheticated http request and json
// unmarshals result to a supplied variable
func (b *Bitfinex) SendAuthenticatedHTTPRequest(ep exchange.URL, method, path string, params map[string]interface{}, result interface{}, endpoint request.EndpointLimit) error {
if !b.AllowAuthenticatedRequest() {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet,
b.Name)
}
ePoint, err := b.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
n := b.Requester.GetNonce(true)
req := make(map[string]interface{})
req["request"] = bitfinexAPIVersion + path
req["nonce"] = n.String()
for key, value := range params {
req[key] = value
}
PayloadJSON, err := json.Marshal(req)
if err != nil {
return errors.New("sendAuthenticatedAPIRequest: unable to JSON request")
}
if b.Verbose {
log.Debugf(log.ExchangeSys, "Request JSON: %s\n", PayloadJSON)
}
PayloadBase64 := crypto.Base64Encode(PayloadJSON)
hmac := crypto.GetHMAC(crypto.HashSHA512_384, []byte(PayloadBase64),
[]byte(b.API.Credentials.Secret))
headers := make(map[string]string)
headers["X-BFX-APIKEY"] = b.API.Credentials.Key
headers["X-BFX-PAYLOAD"] = PayloadBase64
headers["X-BFX-SIGNATURE"] = crypto.HexEncodeToString(hmac)
return b.SendPayload(context.Background(), &request.Item{
Method: method,
Path: ePoint + bitfinexAPIVersion + path,
Headers: headers,
Result: result,
AuthRequest: true,
NonceEnabled: true,
Verbose: b.Verbose,
HTTPDebugging: b.HTTPDebugging,
HTTPRecording: b.HTTPRecording,
Endpoint: endpoint})
}
// SendAuthenticatedHTTPRequestV2 sends an autheticated http request and json
// unmarshals result to a supplied variable
func (b *Bitfinex) SendAuthenticatedHTTPRequestV2(ep exchange.URL, method, path string, params map[string]interface{}, result interface{}, endpoint request.EndpointLimit) error {
if !b.AllowAuthenticatedRequest() {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet,
b.Name)
}
ePoint, err := b.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
var body io.Reader
var payload []byte
if len(params) != 0 {
payload, err = json.Marshal(params)
if err != nil {
return err
}
body = bytes.NewBuffer(payload)
}
// This is done in a weird way because bitfinex doesn't accept unixnano
n := strconv.FormatInt(int64(b.Requester.GetNonce(false))*1e9, 10)
headers := make(map[string]string)
headers["Content-Type"] = "application/json"
headers["Accept"] = "application/json"
headers["bfx-apikey"] = b.API.Credentials.Key
headers["bfx-nonce"] = n
strPath := "/api" + bitfinexAPIVersion2 + path + string(payload)
signStr := strPath + n
hmac := crypto.GetHMAC(
crypto.HashSHA512_384,
[]byte(signStr),
[]byte(b.API.Credentials.Secret),
)
headers["bfx-signature"] = crypto.HexEncodeToString(hmac)
return b.SendPayload(context.Background(), &request.Item{
Method: method,
Path: ePoint + bitfinexAPIVersion2 + path,
Headers: headers,
Body: body,
Result: result,
AuthRequest: true,
NonceEnabled: true,
Verbose: b.Verbose,
HTTPDebugging: b.HTTPDebugging,
HTTPRecording: b.HTTPRecording,
Endpoint: endpoint,
})
}
// GetFee returns an estimate of fee based on type of transaction
func (b *Bitfinex) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
accountInfos, err := b.GetAccountFees()
if err != nil {
return 0, err
}
fee, err = b.CalculateTradingFee(accountInfos,
feeBuilder.PurchasePrice,
feeBuilder.Amount,
feeBuilder.Pair.Base,
feeBuilder.IsMaker)
if err != nil {
return 0, err
}
case exchange.CyptocurrencyDepositFee:
//TODO: fee is charged when < $1000USD is transferred, need to infer value in some way
fee = 0
case exchange.CryptocurrencyWithdrawalFee:
acc, err := b.GetWithdrawalFees()
if err != nil {
return 0, err
}
fee, err = b.GetCryptocurrencyWithdrawalFee(feeBuilder.Pair.Base, acc)
if err != nil {
return 0, err
}
case exchange.InternationalBankDepositFee:
fee = getInternationalBankDepositFee(feeBuilder.Amount)
case exchange.InternationalBankWithdrawalFee:
fee = getInternationalBankWithdrawalFee(feeBuilder.Amount)
case exchange.OfflineTradeFee:
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
// getOfflineTradeFee calculates the worst case-scenario trading fee
// does not require an API request, requires manual updating
func getOfflineTradeFee(price, amount float64) float64 {
return 0.001 * price * amount
}
// GetCryptocurrencyWithdrawalFee returns an estimate of fee based on type of transaction
func (b *Bitfinex) GetCryptocurrencyWithdrawalFee(c currency.Code, accountFees AccountFees) (fee float64, err error) {
switch result := accountFees.Withdraw[c.String()].(type) {
case string:
fee, err = strconv.ParseFloat(result, 64)
if err != nil {
return 0, err
}
case float64:
fee = result
}
return fee, nil
}
func getInternationalBankDepositFee(amount float64) float64 {
return 0.001 * amount
}
func getInternationalBankWithdrawalFee(amount float64) float64 {
return 0.001 * amount
}
// CalculateTradingFee returns an estimate of fee based on type of whether is maker or taker fee
func (b *Bitfinex) CalculateTradingFee(i []AccountInfo, purchasePrice, amount float64, c currency.Code, isMaker bool) (fee float64, err error) {
for x := range i {
for y := range i[x].Fees {
if c.String() == i[x].Fees[y].Pairs {
if isMaker {
fee = i[x].Fees[y].MakerFees
} else {
fee = i[x].Fees[y].TakerFees
}
break
}
}
if fee > 0 {
break
}
}
return (fee / 100) * purchasePrice * amount, err
}
// ConvertSymbolToWithdrawalType You need to have specific withdrawal types to withdraw from Bitfinex
func (b *Bitfinex) ConvertSymbolToWithdrawalType(c currency.Code) string {
switch c {
case currency.BTC:
return "bitcoin"
case currency.LTC:
return "litecoin"
case currency.ETH:
return "ethereum"
case currency.ETC:
return "ethereumc"
case currency.USDT:
return "tetheruso"
case currency.ZEC:
return "zcash"
case currency.XMR:
return "monero"
case currency.DSH:
return "dash"
case currency.XRP:
return "ripple"
case currency.SAN:
return "santiment"
case currency.OMG:
return "omisego"
case currency.BCH:
return "bcash"
case currency.ETP:
return "metaverse"
case currency.AVT:
return "aventus"
case currency.EDO:
return "eidoo"
case currency.BTG:
return "bgold"
case currency.DATA:
return "datacoin"
case currency.GNT:
return "golem"
case currency.SNT:
return "status"
default:
return c.Lower().String()
}
}
// ConvertSymbolToDepositMethod returns a converted currency deposit method
func (b *Bitfinex) ConvertSymbolToDepositMethod(c currency.Code) (string, error) {
if err := b.PopulateAcceptableMethods(); err != nil {
return "", err
}
method, ok := AcceptableMethods[c.String()]
if !ok {
return "", fmt.Errorf("currency %s not supported in method list",
c)
}
return strings.ToLower(method), nil
}
// PopulateAcceptableMethods retrieves all accepted currency strings and
// populates a map to check
func (b *Bitfinex) PopulateAcceptableMethods() error {
if len(AcceptableMethods) == 0 {
var response [][][2]string
err := b.SendHTTPRequest(exchange.RestSpot,
bitfinexAPIVersion2+bitfinexDepositMethod,
&response,
configs)
if err != nil {
return err
}
if len(response) == 0 {
return errors.New("response contains no data cannot populate acceptable method map")
}
for i := range response[0] {
if len(response[0][i]) != 2 {
return errors.New("response contains no data cannot populate acceptable method map")
}
AcceptableMethods[response[0][i][0]] = response[0][i][1]
}
}
return nil
}