func (b *Bitfinex) GetOfferStatus(OfferID int64) (Offer, error) {
	response := Offer{}
	request := make(map[string]interface{})
	request["offer_id"] = OfferID
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderStatus, request, &response)
}
GetOfferStatus checks offer status whether it has been cancelled, execute or
is still active
package bitfinex
import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
	"github.com/gorilla/websocket"
	"github.com/mattkanwisher/cryptofiend/common"
	"github.com/mattkanwisher/cryptofiend/config"
	"github.com/mattkanwisher/cryptofiend/currency/pair"
	"github.com/mattkanwisher/cryptofiend/exchanges"
	"github.com/mattkanwisher/cryptofiend/exchanges/orderbook"
	"github.com/mattkanwisher/cryptofiend/exchanges/ticker"
)
const (
	bitfinexAPIURL                   = "https://api.bitfinex.com/v1/"
	bitfinexAPIVersion1        uint8 = 1
	bitfinexTicker                   = "pubticker/"
	bitfinexStats                    = "stats/"
	bitfinexLendbook                 = "lendbook/"
	bitfinexOrderbook                = "book/"
	bitfinexTrades                   = "trades/"
	bitfinexKeyPermissions           = "key_info"
	bitfinexLends                    = "lends/"
	bitfinexSymbols                  = "symbols/"
	bitfinexSymbolsDetails           = "symbols_details/"
	bitfinexAccountInfo              = "account_infos"
	bitfinexAccountFees              = "account_fees"
	bitfinexAccountSummary           = "summary"
	bitfinexDeposit                  = "deposit/new"
	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"
	bitfinexOrders                   = "orders"
	bitfinexPositions                = "positions"
	bitfinexClaimPosition            = "position/claim"
	bitfinexHistory                  = "history"
	bitfinexHistoryMovements         = "history/movements"
	bitfinexTradeHistory             = "mytrades"
	bitfinexOfferNew                 = "offer/new"
	bitfinexOfferCancel              = "offer/cancel"
	bitfinexOfferStatus              = "offer/status"
	bitfinexOffers                   = "offers"
	bitfinexMarginActiveFunds        = "taken_funds"
	bitfinexMarginTotalFunds         = "total_taken_funds"
	bitfinexMarginUnusedFunds        = "unused_taken_funds"
	bitfinexMarginClose              = "funding/close"
	bitfinexBalances                 = "balances"
	bitfinexMarginInfo               = "margin_infos"
	bitfinexTransfer                 = "transfer"
	bitfinexWithdrawal               = "withdraw"
	bitfinexActiveCredits            = "credits"
	bitfinexAPI2URL                    = "https://api.bitfinex.com/v2/"
	bitfinexAPIVersion2          uint8 = 2
	bitfinexCalcAvailableBalance       = "auth/calc/order/avail"
	bitfinexMaxRequests = 90
)
const (
	InvalidAPIKeyErrCode = 10100
	MaintenanceErrCode   = 20060  
)
var errRateLimit = errors.New("ERR_RATE_LIMIT")
type Bitfinex struct {
	exchange.Base
	WebsocketConn         *websocket.Conn
	WebsocketSubdChannels map[int]WebsocketChanInfo
	currencyPairs map[pair.CurrencyItem]*exchange.CurrencyPairInfo
	symbolDetails map[pair.CurrencyItem]*SymbolDetails
	rateLimits map[string]int64
	ipBanStartTime int64
	lastBalances     []Balance
	lastActiveOrders []Order
}
func (b *Bitfinex) SetDefaults() {
	b.Name = "Bitfinex"
	b.Enabled = false
	b.Verbose = false
	b.Websocket = false
	b.RESTPollingDelay = 10
	b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo)
	b.RequestCurrencyPairFormat.Delimiter = ""
	b.RequestCurrencyPairFormat.Uppercase = true
	b.ConfigCurrencyPairFormat.Delimiter = ""
	b.ConfigCurrencyPairFormat.Uppercase = true
	b.AssetTypes = []string{ticker.Spot}
	b.Orderbooks = orderbook.Init()
	b.rateLimits = map[string]int64{}
	b.lastBalances = []Balance{}
	b.lastActiveOrders = []Order{}
}
func (b *Bitfinex) Setup(exch config.ExchangeConfig) {
	if !exch.Enabled {
		b.SetEnabled(false)
	} else {
		b.Enabled = true
		b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
		b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
		b.RESTPollingDelay = exch.RESTPollingDelay
		b.Verbose = exch.Verbose
		b.Websocket = exch.Websocket
		b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
		b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
		b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
		err := b.SetCurrencyPairFormat()
		if err != nil {
			log.Fatal(err)
		}
		err = b.SetAssetTypes()
		if err != nil {
			log.Fatal(err)
		}
	}
}
func (b *Bitfinex) CurrencyPairToSymbol(p pair.CurrencyPair) string {
	return p.
		Display(b.RequestCurrencyPairFormat.Delimiter, b.RequestCurrencyPairFormat.Uppercase).
		String()
}
func (b *Bitfinex) SymbolToCurrencyPair(symbol string) (pair.CurrencyPair, error) {
	if len(symbol) != 6 {
		return pair.CurrencyPair{}, fmt.Errorf("symbol %s is longer than expected", symbol)
	}
	p := pair.NewCurrencyPair(symbol[0:3], symbol[3:])
	return p.FormatPair(b.RequestCurrencyPairFormat.Delimiter, b.RequestCurrencyPairFormat.Uppercase), nil
}
type currencyLimits struct {
	exchangeName string
	data map[pair.CurrencyItem]*SymbolDetails
}
func newCurrencyLimits(exchangeName string, data map[pair.CurrencyItem]*SymbolDetails) *currencyLimits {
	return &currencyLimits{exchangeName, data}
}
func (cl *currencyLimits) GetPriceDecimalPlaces(p pair.CurrencyPair) int32 {
	k := p.Display("/", false)
	if v, exists := cl.data[k]; exists {
		return int32(v.PricePrecision)
	}
	return 0
}
func (cl *currencyLimits) GetAmountDecimalPlaces(p pair.CurrencyPair) int32 {
	return 8
}
func (cl *currencyLimits) GetMinAmount(p pair.CurrencyPair) float64 {
	k := p.Display("/", false)
	if v, exists := cl.data[k]; exists {
		return v.MinimumOrderSize
	}
	return 0
}
func (cl *currencyLimits) GetMinTotal(p pair.CurrencyPair) float64 {
	return 0
}
func (b *Bitfinex) GetLimits() exchange.ILimits {
	return newCurrencyLimits(b.Name, b.symbolDetails)
}
func (b *Bitfinex) GetCurrencyPairs() map[pair.CurrencyItem]*exchange.CurrencyPairInfo {
	return b.currencyPairs
}
func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) {
	response := Ticker{}
	path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, values)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) {
	response := []Stat{}
	path := fmt.Sprint(bitfinexAPIURL + bitfinexStats + symbol)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) {
	response := FundingBook{}
	path := fmt.Sprint(bitfinexAPIURL + bitfinexLendbook + symbol)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbook, error) {
	response := Orderbook{}
	path := common.EncodeURLValues(
		bitfinexAPIURL+bitfinexOrderbook+currencyPair,
		values,
	)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStructure, error) {
	response := []TradeStructure{}
	path := common.EncodeURLValues(
		bitfinexAPIURL+bitfinexTrades+currencyPair,
		values,
	)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, error) {
	response := Lendbook{}
	if len(symbol) == 6 {
		symbol = symbol[:3]
	}
	path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLendbook+symbol, values)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) {
	response := []Lends{}
	path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLends+symbol, values)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetSymbols() ([]string, error) {
	products := []string{}
	path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbols)
	return products, common.SendHTTPGetRequest(path, true, b.Verbose, &products)
}
func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) {
	response := []SymbolDetails{}
	path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbolsDetails)
	return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response)
}
func (b *Bitfinex) GetAccountInfo() ([]AccountInfo, error) {
	response := []AccountInfo{}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexAccountInfo, nil, &response)
}
func (b *Bitfinex) GetAccountFees() (AccountFees, error) {
	response := AccountFees{}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexAccountFees, nil, &response)
}
func (b *Bitfinex) GetAccountSummary() (AccountSummary, error) {
	response := AccountSummary{}
	return response,
		b.SendAuthenticatedHTTPRequest(
			"POST", bitfinexAccountSummary, nil, &response,
		)
}
func (b *Bitfinex) NewDeposit(method, walletName string, renew int) (DepositResponse, error) {
	response := DepositResponse{}
	request := make(map[string]interface{})
	request["method"] = method
	request["wallet_name"] = walletName
	request["renew"] = renew
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexDeposit, request, &response)
}
func (b *Bitfinex) GetKeyPermissions() (KeyPermissions, error) {
	response := KeyPermissions{}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexKeyPermissions, nil, &response)
}
func (b *Bitfinex) GetMarginInfo() ([]MarginInfo, error) {
	response := []MarginInfo{}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexMarginInfo, nil, &response)
}
func (b *Bitfinex) GetAccountBalance() ([]Balance, error) {
	response := []Balance{}
	err := b.SendRateLimitedHTTPRequest(12, "POST", bitfinexAPIVersion1, bitfinexBalances, nil, &response, b.lastBalances)
	if err != nil {
		return response, err
	}
	b.lastBalances = response
	return response, nil
}
func (b *Bitfinex) CalcAvailableBalance(
	symbol string, side exchange.OrderSide, rate float64, orderType exchange.OrderType) (float64, error) {
	params := make(map[string]interface{})
	if side == exchange.OrderSideBuy {
		params["dir"] = 1
	} else {
		params["dir"] = -1
	}
	params["symbol"] = "t" + symbol
	params["rate"] = rate
	switch orderType {
	case exchange.OrderTypeExchangeLimit:
		params["type"] = "EXCHANGE"
	case exchange.OrderTypeMarginLimit:
		params["type"] = "MARGIN"
	default:
		return 0.0, errors.New("invalid order type")
	}
	var availableAmt []float64
	defVal := []float64{0.0}
	err := b.SendRateLimitedHTTPRequest(10, "POST", bitfinexAPIVersion2, bitfinexCalcAvailableBalance,
		params, &availableAmt, defVal)
	if err != nil {
		return 0.0, err
	}
	return availableAmt[0], nil
}
func (b *Bitfinex) WalletTransfer(amount float64, currency, walletFrom, walletTo string) ([]WalletTransfer, error) {
	response := []WalletTransfer{}
	request := make(map[string]interface{})
	request["amount"] = amount
	request["currency"] = currency
	request["walletfrom"] = walletFrom
	request["walletTo"] = walletTo
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexTransfer, request, &response)
}
func (b *Bitfinex) Withdrawal(withdrawType, wallet, address string, amount float64) ([]Withdrawal, error) {
	response := []Withdrawal{}
	request := make(map[string]interface{})
	request["withdrawal_type"] = withdrawType
	request["walletselected"] = wallet
	request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
	request["address"] = address
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexWithdrawal, request, &response)
}
func (b *Bitfinex) newOrder(symbol string, amount float64, price float64, side string,
	orderType OrderType, hidden bool) (Order, error) {
	response := Order{}
	request := make(map[string]interface{})
	request["symbol"] = symbol
	request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
	request["price"] = strconv.FormatFloat(price, 'f', -1, 64)
	request["exchange"] = "bitfinex"
	request["type"] = string(orderType)
	request["is_hidden"] = hidden
	request["side"] = side  
	err := b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderNew, request, &response)
	if httpErr, ok := err.(*common.HTTPRequestError); ok {
		msg := strings.ToLower(err.Error())
		if (httpErr.StatusCode == 400) && strings.HasPrefix(msg, "invalid order: not enough") {
			return response, exchange.ErrInsufficentFundsForOrder()
		}
	}
	return response, err
}
func (b *Bitfinex) NewOrder(currencyPair pair.CurrencyPair, amount, price float64,
	side exchange.OrderSide, orderType exchange.OrderType) (string, error) {
	symbol := b.CurrencyPairToSymbol(currencyPair)
	var bitfinexOrderType OrderType
	switch orderType {
	case exchange.OrderTypeMarginLimit:
		bitfinexOrderType = OrderTypeMarginLimit
	case exchange.OrderTypeExchangeLimit:
		bitfinexOrderType = OrderTypeExchangeLimit
	default:
		return "", fmt.Errorf("'%s' order type not currently supported for this exchange", string(orderType))
	}
	order, err := b.newOrder(symbol, amount, price, string(side), bitfinexOrderType, false)
	if err != nil {
		return "", err
	}
	orderID := strconv.FormatInt(order.ID, 10)
	return orderID, nil
}
func (b *Bitfinex) NewOrderMulti(orders []PlaceOrder) (OrderMultiResponse, error) {
	response := OrderMultiResponse{}
	request := make(map[string]interface{})
	request["orders"] = orders
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderNewMulti, request, &response)
}
func (b *Bitfinex) CancelOrder(orderStr string, currencyPair pair.CurrencyPair) error {
	var orderID int64
	var err error
	if orderID, err = strconv.ParseInt(orderStr, 10, 64); err != nil {
		return err
	}
	_, err = b.cancelOrder(orderID)
	return err
}
func (b *Bitfinex) cancelOrder(OrderID int64) (Order, error) {
	response := Order{}
	request := make(map[string]interface{})
	request["order_id"] = OrderID
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderCancel, request, &response)
}
func (b *Bitfinex) CancelMultipleOrders(OrderIDs []int64) (string, error) {
	response := GenericResponse{}
	request := make(map[string]interface{})
	request["order_ids"] = OrderIDs
	return response.Result,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderCancelMulti, request, nil)
}
func (b *Bitfinex) CancelAllOrders() (string, error) {
	response := GenericResponse{}
	return response.Result,
		b.SendAuthenticatedHTTPRequest("GET", bitfinexOrderCancelAll, nil, nil)
}
func (b *Bitfinex) ReplaceOrder(OrderID int64, Symbol string, Amount float64, Price float64, Buy bool, Type string, Hidden bool) (Order, error) {
	response := Order{}
	request := make(map[string]interface{})
	request["order_id"] = OrderID
	request["symbol"] = Symbol
	request["amount"] = strconv.FormatFloat(Amount, 'f', -1, 64)
	request["price"] = strconv.FormatFloat(Price, 'f', -1, 64)
	request["exchange"] = "bitfinex"
	request["type"] = Type
	request["is_hidden"] = Hidden
	if Buy {
		request["side"] = "buy"
	} else {
		request["side"] = "sell"
	}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderCancelReplace, request, &response)
}
func (b *Bitfinex) GetOrderStatus(OrderID int64) (Order, error) {
	orderStatus := Order{}
	request := make(map[string]interface{})
	request["order_id"] = OrderID
	return orderStatus,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderStatus, request, &orderStatus)
}
func (b *Bitfinex) GetOrder(orderID string, currencyPair pair.CurrencyPair) (*exchange.Order, error) {
	id, err := strconv.ParseInt(orderID, 10, 64)
	if err != nil {
		return nil, err
	}
	order, err := b.GetOrderStatus(id)
	if err != nil {
		return nil, err
	}
	return b.convertOrderToExchangeOrder(&order), nil
}
func (b *Bitfinex) convertOrderToExchangeOrder(order *Order) *exchange.Order {
	retOrder := &exchange.Order{}
	retOrder.OrderID = strconv.FormatInt(order.ID, 10)
	if order.IsCancelled {
		retOrder.Status = exchange.OrderStatusAborted
	} else if order.IsLive {
		retOrder.Status = exchange.OrderStatusActive
	} else {
		retOrder.Status = exchange.OrderStatusFilled
	}
	retOrder.Amount = order.OriginalAmount
	retOrder.FilledAmount = order.ExecutedAmount
	retOrder.RemainingAmount = order.RemainingAmount
	if retOrder.Status == exchange.OrderStatusActive {
		retOrder.Rate = order.Price
	} else if order.AverageExecutionPrice == 0 {
		retOrder.Rate = order.Price
	} else {
		retOrder.Rate = order.AverageExecutionPrice
	}
	var createdAt int64
	timeParts := strings.Split(order.Timestamp, ".")
	if len(timeParts) > 0 {
		createdAt, _ = strconv.ParseInt(timeParts[0], 10, 64)
	}
	retOrder.CreatedAt = createdAt
	retOrder.CurrencyPair, _ = b.SymbolToCurrencyPair(order.Symbol)
	retOrder.Side = exchange.OrderSide(order.Side)
	if order.Type == OrderTypeExchangeLimit {
		retOrder.Type = exchange.OrderTypeExchangeLimit
	} else if order.Type == OrderTypeMarginLimit {
		retOrder.Type = exchange.OrderTypeMarginLimit
	}
	return retOrder
}
func (b *Bitfinex) GetActiveOrders() ([]Order, error) {
	response := []Order{}
	err := b.SendRateLimitedHTTPRequest(10, http.MethodPost, bitfinexAPIVersion1, bitfinexOrders,
		nil, &response, b.lastActiveOrders)
	if err != nil {
		return response, err
	}
	b.lastActiveOrders = response
	return response, nil
}
func (b *Bitfinex) GetOrders(pairs []pair.CurrencyPair) ([]*exchange.Order, error) {
	var retErr error
	orders, err := b.GetActiveOrders()
	if err == exchange.WarningHTTPRequestRateLimited() {
		retErr = err
	} else if err != nil {
		return nil, err
	}
	ret := make([]*exchange.Order, 0, len(orders))
	for _, order := range orders {
		ret = append(ret, b.convertOrderToExchangeOrder(&order))
	}
	return ret, retErr
}
func (b *Bitfinex) GetActivePositions() ([]Position, error) {
	response := []Position{}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexPositions, nil, &response)
}
func (b *Bitfinex) ClaimPosition(PositionID int) (Position, error) {
	response := Position{}
	request := make(map[string]interface{})
	request["position_id"] = PositionID
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexClaimPosition, nil, nil)
}
func (b *Bitfinex) GetBalanceHistory(symbol string, timeSince, timeUntil time.Time, limit int, wallet string) ([]BalanceHistory, error) {
	response := []BalanceHistory{}
	request := make(map[string]interface{})
	request["currency"] = symbol
	if !timeSince.IsZero() {
		request["since"] = timeSince
	}
	if !timeUntil.IsZero() {
		request["until"] = timeUntil
	}
	if limit > 0 {
		request["limit"] = limit
	}
	if len(wallet) > 0 {
		request["wallet"] = wallet
	}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexHistory, request, &response)
}
func (b *Bitfinex) GetMovementHistory(symbol, method string, timeSince, timeUntil time.Time, limit int) ([]MovementHistory, error) {
	response := []MovementHistory{}
	request := make(map[string]interface{})
	request["currency"] = symbol
	if len(method) > 0 {
		request["method"] = method
	}
	if !timeSince.IsZero() {
		request["since"] = timeSince
	}
	if !timeUntil.IsZero() {
		request["until"] = timeUntil
	}
	if limit > 0 {
		request["limit"] = limit
	}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexHistoryMovements, request, &response)
}
func (b *Bitfinex) GetTradeHistory(currencyPair string, timestamp, until time.Time, limit, reverse int) ([]TradeHistory, error) {
	response := []TradeHistory{}
	request := make(map[string]interface{})
	request["currency"] = currencyPair
	request["timestamp"] = timestamp
	if !until.IsZero() {
		request["until"] = until
	}
	if limit > 0 {
		request["limit"] = limit
	}
	if reverse > 0 {
		request["reverse"] = reverse
	}
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexTradeHistory, request, &response)
}
func (b *Bitfinex) NewOffer(symbol string, amount, rate float64, period int64, direction string) (Offer, error) {
	response := Offer{}
	request := make(map[string]interface{})
	request["currency"] = symbol
	request["amount"] = amount
	request["rate"] = rate
	request["period"] = period
	request["direction"] = direction
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOfferNew, request, &response)
}
func (b *Bitfinex) CancelOffer(OfferID int64) (Offer, error) {
	response := Offer{}
	request := make(map[string]interface{})
	request["offer_id"] = OfferID
	return response,
		b.SendAuthenticatedHTTPRequest("POST", bitfinexOfferCancel, request, &response)
}