exchanges/qa: Add exchange wrapper testing suite (#1159)

* initial concept of a nice validation tester for exchanges

* adds some datahandler design

* expand testing

* more tests and fixes

* minor end of day fix for bithumb

* fixes implementation issues

* more test coverage and improvements, but not sure if i should continue

* fix more wrapper implementations

* adds error type, more fixes

* changes signature, fixes implementations

* fixes more wrapper implementations

* one more bit

* more cleanup

* WOW things work?

* lintle 1/1337

* mini bump

* fixes all linting

* neaten

* GetOrderInfo+ asset pair fixes+improvements

* adds new websocket test

* expand ws testing

* fix bug, expand tests, improve implementation

* code coverage of a lot of new codes

* fixes everything

* reverts accidental changes

* minor fixes from reviewing code

* removes Bitfinex cancelBatchOrder implementation

* fixes dumb baby typo for babies

* mini nit fixes

* so many nits to address

* addresses all the nits

* Titlecase

* switcheroo

* removes websocket testing for now

* fix appveyor, minor test fix

* fixes typo, re-kindles killed kode

* skip binance wrapper tests when running CI

* expired context, huobi okx fixes

* kodespull

* fix ordering

* time fix because why not

* fix exmo, others

* hopefully this fixes all of my life's problems

* last thing today

* huobi, more like hypotrophy

* golangci-lint, more like mypooroldknee-splint

* fix huobi times by removing them

* should fix okx currency issues

* blocks the application

* adds last little contingency for pairs

* addresses most nits and new problems

* lovely fixed before seeing why okx sucks

* fixes issues with okx websocket

* the classic receieieivaier

* lintle

* adds test and fixes existing tests

* expands error handling messages during setup

* fixes dumb okx bugs introduced

* quick fix for lint and exmo

* fixes nixes

* fix exmo deposit issue

* lint

* fixes issue with extra asset runs missing

* fix surprise race

* all the lint and merge fixes

* fixes surprise bugs in OKx

* fixes issues with times and chains

* fixing all the merge stuff

* merge fix

* rm logs and a panic potential

* lovely lint lament

* an easy demonstration of scenario, but not of initial purpose

* put it in the bin

* Revert "put it in the bin"

This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd.

* re-add after immediate error popup

* fix mini poor test design

* okx okay

* merge fixes

* fixes issues discovered in lovely test

* I FORGOT TO COMMIT THIS

* nit fixaroonaboo

* forgoetten test fix

* revert old okx asset intrument work

* fixes

* revert problems I didnt understand. update bybit

* fix merge bugs

* test cleanup

* further improvements

* reshuffle and lint

* rm redundant CI_TEST by rm the CI_TEST field that is redundant

* path fix

* move to its own section, dont run on 32 bit + appveyor

* lint

* fix lbank

* address nits

* let it rip

* fix failing test time range

* niteroo boogaloo

* mod tidy, use common.SimpleTimeFormat
This commit is contained in:
Scott
2023-07-03 11:09:43 +10:00
committed by GitHub
parent ef605a3c19
commit fcc5ad4551
210 changed files with 38548 additions and 6519 deletions

View File

@@ -41,12 +41,12 @@ const (
bitfinexOrderCancelAll = "order/cancel/all"
bitfinexOrderCancelReplace = "order/cancel/replace"
bitfinexOrderStatus = "order/status"
bitfinexInactiveOrders = "orders/hist"
bitfinexInactiveOrders = "hist"
bitfinexOrders = "orders"
bitfinexPositions = "positions"
bitfinexClaimPosition = "position/claim"
bitfinexHistory = "history"
bitfinexHistoryMovements = "history/movements"
bitfinexHistoryMovements = "movements"
bitfinexTradeHistory = "mytrades"
bitfinexOfferNew = "offer/new"
bitfinexOfferCancel = "offer/cancel"
@@ -67,18 +67,20 @@ const (
bitfinexV2AccountInfo = "auth/r/info/user"
bitfinexV2MarginInfo = "auth/r/info/margin/"
bitfinexV2FundingInfo = "auth/r/info/funding/%s"
bitfinexV2Auth = "auth/"
bitfinexDerivativeData = "status/deriv?"
bitfinexPlatformStatus = "platform/status"
bitfinexTickerBatch = "tickers"
bitfinexTicker = "ticker/"
bitfinexTrades = "trades/"
bitfinexOrderbook = "book/"
bitfinexStatistics = "stats1/"
bitfinexHistoryShort = "hist"
bitfinexCandles = "candles/trade"
bitfinexKeyPermissions = "key_info"
bitfinexMarginInfo = "margin_infos"
bitfinexDepositMethod = "conf/pub:map:tx:method"
bitfinexDepositAddress = "auth/w/deposit/address"
bitfinexOrderUpdate = "auth/w/order/update"
bitfinexMarginPairs = "conf/pub:list:pair:margin"
bitfinexSpotPairs = "conf/pub:list:pair:exchange"
@@ -584,58 +586,58 @@ func (b *Bitfinex) GetTickerBatch(ctx context.Context) (map[string]Ticker, error
for x := range response {
symbol, ok := response[x][0].(string)
if !ok {
return nil, errors.New("unable to type assert symbol")
return nil, common.GetTypeAssertError("string", response[x][0], "symbol")
}
var t Ticker
if len(response[x]) > 11 {
if t.FlashReturnRate, ok = response[x][1].(float64); !ok {
return nil, errors.New("unable to type assert flashReturnRate")
return nil, common.GetTypeAssertError("float64", response[x][1], "FlashReturnRate")
}
if t.Bid, ok = response[x][2].(float64); !ok {
return nil, errors.New("unable to type assert bid")
return nil, common.GetTypeAssertError("float64", response[x][2], "bid")
}
var bidPeriod float64
bidPeriod, ok = response[x][3].(float64)
if !ok {
return nil, errors.New("unable to type assert bidPeriod")
return nil, common.GetTypeAssertError("float64", response[x][3], "bidPeriod")
}
t.BidPeriod = int64(bidPeriod)
if t.BidSize, ok = response[x][4].(float64); !ok {
return nil, errors.New("unable to type assert bidSize")
return nil, common.GetTypeAssertError("float64", response[x][4], "bidSize")
}
if t.Ask, ok = response[x][5].(float64); !ok {
return nil, errors.New("unable to type assert ask")
return nil, common.GetTypeAssertError("float64", response[x][5], "ask")
}
var askPeriod float64
askPeriod, ok = response[x][6].(float64)
if !ok {
return nil, errors.New("unable to type assert askPeriod")
return nil, common.GetTypeAssertError("float64", response[x][6], "askPeriod")
}
t.AskPeriod = int64(askPeriod)
if t.AskSize, ok = response[x][7].(float64); !ok {
return nil, errors.New("unable to type assert askSize")
return nil, common.GetTypeAssertError("float64", response[x][7], "askSize")
}
if t.DailyChange, ok = response[x][8].(float64); !ok {
return nil, errors.New("unable to type assert dailyChange")
return nil, common.GetTypeAssertError("float64", response[x][8], "dailyChange")
}
if t.DailyChangePerc, ok = response[x][9].(float64); !ok {
return nil, errors.New("unable to type assert dailyChangePerc")
return nil, common.GetTypeAssertError("float64", response[x][9], "dailyChangePerc")
}
if t.Last, ok = response[x][10].(float64); !ok {
return nil, errors.New("unable to type assert last")
return nil, common.GetTypeAssertError("float64", response[x][10], "last")
}
if t.Volume, ok = response[x][11].(float64); !ok {
return nil, errors.New("unable to type assert volume")
return nil, common.GetTypeAssertError("float64", response[x][11], "volume")
}
if t.High, ok = response[x][12].(float64); !ok {
return nil, errors.New("unable to type assert high")
return nil, common.GetTypeAssertError("float64", response[x][12], "high")
}
if t.Low, ok = response[x][13].(float64); !ok {
return nil, errors.New("unable to type assert low")
return nil, common.GetTypeAssertError("float64", response[x][13], "low")
}
if t.FFRAmountAvailable, ok = response[x][16].(float64); !ok {
return nil, errors.New("unable to type assert FFRAmountAvailable")
return nil, common.GetTypeAssertError("float64", response[x][16], "FFRAmountAvailable")
}
tickers[symbol] = t
@@ -643,34 +645,34 @@ func (b *Bitfinex) GetTickerBatch(ctx context.Context) (map[string]Ticker, error
}
if t.Bid, ok = response[x][1].(float64); !ok {
return nil, errors.New("unable to type assert bid")
return nil, common.GetTypeAssertError("float64", response[x][1], "bid")
}
if t.BidSize, ok = response[x][2].(float64); !ok {
return nil, errors.New("unable to type assert bid size")
return nil, common.GetTypeAssertError("float64", response[x][2], "bid size")
}
if t.Ask, ok = response[x][3].(float64); !ok {
return nil, errors.New("unable to type assert ask")
return nil, common.GetTypeAssertError("float64", response[x][3], "ask")
}
if t.AskSize, ok = response[x][4].(float64); !ok {
return nil, errors.New("unable to type assert ask size")
return nil, common.GetTypeAssertError("float64", response[x][4], "ask size")
}
if t.DailyChange, ok = response[x][5].(float64); !ok {
return nil, errors.New("unable to type assert daily change")
return nil, common.GetTypeAssertError("float64", response[x][5], "daily change")
}
if t.DailyChangePerc, ok = response[x][6].(float64); !ok {
return nil, errors.New("unable to type assert daily change perc")
return nil, common.GetTypeAssertError("float64", response[x][6], "daily change percent")
}
if t.Last, ok = response[x][7].(float64); !ok {
return nil, errors.New("unable to type assert last")
return nil, common.GetTypeAssertError("float64", response[x][7], "last")
}
if t.Volume, ok = response[x][8].(float64); !ok {
return nil, errors.New("unable to type assert volume")
return nil, common.GetTypeAssertError("float64", response[x][8], "volume")
}
if t.High, ok = response[x][9].(float64); !ok {
return nil, errors.New("unable to type assert high")
return nil, common.GetTypeAssertError("float64", response[x][9], "high")
}
if t.Low, ok = response[x][10].(float64); !ok {
return nil, errors.New("unable to type assert low")
return nil, common.GetTypeAssertError("float64", response[x][10], "low")
}
tickers[symbol] = t
}
@@ -1520,6 +1522,32 @@ func (b *Bitfinex) NewOrder(ctx context.Context, currencyPair, orderType string,
orderV1)
}
// OrderUpdate will send an update signal for an existing order
// and attempt to modify it
func (b *Bitfinex) OrderUpdate(ctx context.Context, orderID, groupID, clientOrderID string, amount, price, leverage float64) (*Order, error) {
req := make(map[string]interface{})
if orderID != "" {
req["id"] = orderID
}
if groupID != "" {
req["gid"] = groupID
}
if clientOrderID != "" {
req["cid"] = clientOrderID
}
req["price"] = strconv.FormatFloat(price, 'f', -1, 64)
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
if leverage > 1 {
req["lev"] = strconv.FormatFloat(leverage, 'f', -1, 64)
}
response := Order{}
return &response, b.SendAuthenticatedHTTPRequestV2(ctx, exchange.RestSpot, http.MethodPost,
bitfinexOrderUpdate,
req,
&response,
orderV1)
}
// NewOrderMulti allows several new orders at once
func (b *Bitfinex) NewOrderMulti(ctx context.Context, orders []PlaceOrder) (OrderMultiResponse, error) {
response := OrderMultiResponse{}
@@ -1551,7 +1579,6 @@ func (b *Bitfinex) CancelMultipleOrders(ctx context.Context, orderIDs []int64) (
response := GenericResponse{}
req := make(map[string]interface{})
req["order_ids"] = orderIDs
return response.Result, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost,
bitfinexOrderCancelMulti,
req,
@@ -1559,6 +1586,152 @@ func (b *Bitfinex) CancelMultipleOrders(ctx context.Context, orderIDs []int64) (
orderMulti)
}
// CancelMultipleOrdersV2 cancels multiple orders
func (b *Bitfinex) CancelMultipleOrdersV2(ctx context.Context, orderID, clientOrderID, groupOrderID int64, clientOrderIDDate time.Time, allOrders bool) ([]CancelMultiOrderResponse, error) {
var response []interface{}
req := make(map[string]interface{})
if orderID > 0 {
req["id"] = orderID
}
if clientOrderID > 0 {
req["cid"] = clientOrderID
}
if !clientOrderIDDate.IsZero() {
req["cid_date"] = clientOrderIDDate.Format("2006-01-02")
}
if groupOrderID > 0 {
req["gid"] = groupOrderID
}
if allOrders {
req["all"] = 1
}
err := b.SendAuthenticatedHTTPRequestV2(ctx, exchange.RestSpot, http.MethodPost,
bitfinexOrderCancelMulti,
req,
&response,
orderMulti)
if err != nil {
return nil, err
}
var cancelledOrders []CancelMultiOrderResponse
for x := range response {
cancelledOrdersSlice, ok := response[x].([]interface{})
if !ok {
continue
}
for y := range cancelledOrdersSlice {
cancelledOrderFields, ok := cancelledOrdersSlice[y].([]interface{})
if !ok {
continue
}
var cancelledOrder CancelMultiOrderResponse
for z := range cancelledOrderFields {
switch z {
case 0:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "ID")
}
cancelledOrder.OrderID = strconv.FormatFloat(f, 'f', -1, 64)
case 1:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "GID")
}
cancelledOrder.GroupOrderID = strconv.FormatFloat(f, 'f', -1, 64)
case 2:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "CID")
}
cancelledOrder.ClientOrderID = strconv.FormatFloat(f, 'f', -1, 64)
case 3:
f, ok := cancelledOrderFields[z].(string)
if !ok {
return nil, common.GetTypeAssertError("string", cancelledOrderFields[z], "SYMBOL")
}
cancelledOrder.Symbol = f
case 4:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "MTS_CREATE")
}
cancelledOrder.CreatedTime = time.UnixMilli(int64(f))
case 5:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "MTS_UPDATE")
}
cancelledOrder.UpdatedTime = time.UnixMilli(int64(f))
case 6:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "AMOUNT")
}
cancelledOrder.Amount = f
case 7:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "AMOUNT_ORIG")
}
cancelledOrder.OriginalAmount = f
case 8:
f, ok := cancelledOrderFields[z].(string)
if !ok {
return nil, common.GetTypeAssertError("string", cancelledOrderFields[z], "TYPE")
}
cancelledOrder.OrderType = f
case 9:
f, ok := cancelledOrderFields[z].(string)
if !ok {
return nil, common.GetTypeAssertError("string", cancelledOrderFields[z], "TYPE_PREV")
}
cancelledOrder.OriginalOrderType = f
case 12:
f, ok := cancelledOrderFields[z].(string)
if !ok {
return nil, common.GetTypeAssertError("string", cancelledOrderFields[z], "FLAGS")
}
cancelledOrder.OrderFlags = f
case 13:
f, ok := cancelledOrderFields[z].(string)
if !ok {
return nil, common.GetTypeAssertError("string", cancelledOrderFields[z], "ORDER_STATUS")
}
cancelledOrder.OrderStatus = f
case 16:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "PRICE")
}
cancelledOrder.Price = f
case 17:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "PRICE_AVG")
}
cancelledOrder.AveragePrice = f
case 18:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "PRICE_TRAILING")
}
cancelledOrder.TrailingPrice = f
case 19:
f, ok := cancelledOrderFields[z].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", cancelledOrderFields[z], "PRICE_AUX_LIMIT")
}
cancelledOrder.AuxLimitPrice = f
}
}
cancelledOrders[y] = cancelledOrder
}
}
return cancelledOrders, nil
}
// CancelAllExistingOrders cancels all active and open orders
func (b *Bitfinex) CancelAllExistingOrders(ctx context.Context) (string, error) {
response := GenericResponse{}
@@ -1609,24 +1782,33 @@ func (b *Bitfinex) GetOrderStatus(ctx context.Context, orderID int64) (Order, er
}
// GetInactiveOrders returns order status information
func (b *Bitfinex) GetInactiveOrders(ctx context.Context) ([]Order, error) {
func (b *Bitfinex) GetInactiveOrders(ctx context.Context, symbol string, ids ...int64) ([]Order, error) {
var response []Order
req := make(map[string]interface{})
req["limit"] = "100"
return response, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost,
bitfinexInactiveOrders,
req["limit"] = 2500
if len(ids) > 0 {
req["ids"] = ids
}
return response, b.SendAuthenticatedHTTPRequestV2(
ctx,
exchange.RestSpot,
http.MethodPost,
bitfinexV2Auth+"r/"+bitfinexOrders+"/"+symbol+"/"+bitfinexInactiveOrders,
req,
&response,
orderMulti)
}
// GetOpenOrders returns all active orders and statuses
func (b *Bitfinex) GetOpenOrders(ctx context.Context) ([]Order, error) {
func (b *Bitfinex) GetOpenOrders(ctx context.Context, ids ...int64) ([]Order, error) {
var response []Order
req := make(map[string]interface{})
if len(ids) > 0 {
req["ids"] = ids
}
return response, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost,
bitfinexOrders,
nil,
req,
&response,
orderMulti)
}
@@ -1683,7 +1865,7 @@ func (b *Bitfinex) GetBalanceHistory(ctx context.Context, symbol string, timeSin
// GetMovementHistory returns an array of past deposits and withdrawals
func (b *Bitfinex) GetMovementHistory(ctx context.Context, symbol, method string, timeSince, timeUntil time.Time, limit int) ([]MovementHistory, error) {
var response []MovementHistory
var response [][]interface{}
req := make(map[string]interface{})
req["currency"] = symbol
@@ -1700,11 +1882,80 @@ func (b *Bitfinex) GetMovementHistory(ctx context.Context, symbol, method string
req["limit"] = limit
}
return response, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost,
bitfinexHistoryMovements,
err := b.SendAuthenticatedHTTPRequestV2(ctx, exchange.RestSpot, http.MethodPost,
"auth/r/"+bitfinexHistoryMovements+"/"+symbol+"/"+bitfinexHistoryShort,
req,
&response,
orderMulti)
if err != nil {
return nil, err
}
var resp []MovementHistory //nolint:prealloc // its an array in an array
var ok bool
for i := range response {
var move MovementHistory
for j := range response[i] {
if response[i][j] == nil {
continue
}
switch j {
case 0:
var id float64
id, ok = response[i][j].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", response[i][j], "ID")
}
move.ID = int64(id)
case 1:
move.Currency, ok = response[i][j].(string)
if !ok {
return nil, common.GetTypeAssertError("string", response[i][j], "CURRENCY")
}
case 5:
move.TimestampCreated, ok = response[i][j].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", response[i][j], "MTS_STARTED")
}
case 6:
move.Timestamp, ok = response[i][j].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", response[i][j], "MTS_UPDATED")
}
case 9:
move.Status, ok = response[i][j].(string)
if !ok {
return nil, common.GetTypeAssertError("string", response[i][j], "STATUS")
}
case 12:
move.Amount, ok = response[i][j].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", response[i][j], "AMOUNT")
}
case 13:
move.Fee, ok = response[i][j].(float64)
if !ok {
return nil, common.GetTypeAssertError("float64", response[i][j], "FEE")
}
case 16:
move.Address, ok = response[i][j].(string)
if !ok {
return nil, common.GetTypeAssertError("string", response[i][j], "DESTINATION_ADDRESS")
}
case 20:
move.TxID, ok = response[i][j].(string)
if !ok {
return nil, common.GetTypeAssertError("string", response[i][j], "TRANSACTION_ID")
}
case 21:
move.Description, ok = response[i][j].(string)
if !ok {
return nil, common.GetTypeAssertError("string", response[i][j], "WITHDRAW_TRANSACTION_NOTE")
}
}
}
resp = append(resp, move)
}
return resp, nil
}
// GetTradeHistory returns past executed trades
@@ -1861,7 +2112,7 @@ func (b *Bitfinex) SendHTTPRequest(ctx context.Context, ep exchange.URL, path st
return b.SendPayload(ctx, e, func() (*request.Item, error) {
return item, nil
})
}, request.UnauthenticatedRequest)
}
// SendAuthenticatedHTTPRequest sends an authenticated http request and json
@@ -1910,12 +2161,11 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange
Path: fullPath,
Headers: headers,
Result: result,
AuthRequest: true,
NonceEnabled: true,
Verbose: b.Verbose,
HTTPDebugging: b.HTTPDebugging,
HTTPRecording: b.HTTPRecording}, nil
})
}, request.AuthenticatedRequest)
}
// SendAuthenticatedHTTPRequestV2 sends an authenticated http request and json
@@ -1964,13 +2214,12 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequestV2(ctx context.Context, ep exchan
Headers: headers,
Body: body,
Result: result,
AuthRequest: true,
NonceEnabled: true,
Verbose: b.Verbose,
HTTPDebugging: b.HTTPDebugging,
HTTPRecording: b.HTTPRecording,
}, nil
})
}, request.AuthenticatedRequest)
}
// GetFee returns an estimate of fee based on type of transaction

View File

@@ -277,7 +277,9 @@ func TestGetLends(t *testing.T) {
func TestGetCandles(t *testing.T) {
t.Parallel()
_, err := b.GetCandles(context.Background(), "fUSD", "1m", 0, 0, 10, true)
e := time.Now().Add(-time.Hour * 2).Truncate(time.Hour)
s := e.Add(-time.Hour * 4)
_, err := b.GetCandles(context.Background(), "fUST", "1D", s.UnixMilli(), e.UnixMilli(), 10000, true)
if err != nil {
t.Fatal(err)
}
@@ -343,8 +345,8 @@ func TestGetAccountSummary(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetAccountSummary(context.Background())
if err == nil {
t.Error("GetAccountSummary() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -352,13 +354,13 @@ func TestNewDeposit(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.NewDeposit(context.Background(), "blabla", "testwallet", 0)
if err == nil {
t.Error("NewDeposit() Expected error")
if err != nil {
t.Error(err)
}
_, err = b.NewDeposit(context.Background(), "bitcoin", "testwallet", 0)
if err == nil {
t.Error("NewDeposit() Expected error")
if err != nil {
t.Error(err)
}
_, err = b.NewDeposit(context.Background(), "ripple", "", 0)
@@ -407,8 +409,8 @@ func TestWalletTransfer(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.WalletTransfer(context.Background(), 0.01, "btc", "bla", "bla")
if err == nil {
t.Error("error cannot be nil")
if err != nil {
t.Error(err)
}
}
@@ -422,7 +424,7 @@ func TestNewOrder(t *testing.T) {
2,
false,
true)
if err == nil {
if err != nil {
t.Error(err)
}
}
@@ -491,8 +493,8 @@ func TestNewOrderMulti(t *testing.T) {
}
_, err := b.NewOrderMulti(context.Background(), newOrder)
if err == nil {
t.Error("NewOrderMulti() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -500,8 +502,8 @@ func TestCancelOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.CancelExistingOrder(context.Background(), 1337)
if err == nil {
t.Error("CancelExistingOrder() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -509,8 +511,8 @@ func TestCancelMultipleOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.CancelMultipleOrders(context.Background(), []int64{1337, 1336})
if err == nil {
t.Error("CancelMultipleOrders() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -518,8 +520,8 @@ func TestCancelAllOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.CancelAllExistingOrders(context.Background())
if err == nil {
t.Error("CancelAllExistingOrders() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -528,8 +530,8 @@ func TestReplaceOrder(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.ReplaceOrder(context.Background(), 1337, "BTCUSD",
1, 1, true, order.Limit.Lower(), false)
if err == nil {
t.Error("ReplaceOrder() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -537,8 +539,8 @@ func TestGetOrderStatus(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetOrderStatus(context.Background(), 1337)
if err == nil {
t.Error("GetOrderStatus() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -546,8 +548,13 @@ func TestGetOpenOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetOpenOrders(context.Background())
if err == nil {
t.Error("GetOpenOrders() Expectederror")
if err != nil {
t.Error(err)
}
_, err = b.GetOpenOrders(context.Background(), 1, 2, 3, 4)
if err != nil {
t.Error(err)
}
}
@@ -555,8 +562,8 @@ func TestGetActivePositions(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetActivePositions(context.Background())
if err == nil {
t.Error("GetActivePositions() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -564,8 +571,8 @@ func TestClaimPosition(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.ClaimPosition(context.Background(), 1337)
if err == nil {
t.Error("ClaimPosition() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -574,8 +581,8 @@ func TestGetBalanceHistory(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetBalanceHistory(context.Background(),
"USD", time.Time{}, time.Time{}, 1, "deposit")
if err == nil {
t.Error("GetBalanceHistory() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -583,8 +590,8 @@ func TestGetMovementHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetMovementHistory(context.Background(), "USD", "bitcoin", time.Time{}, time.Time{}, 1)
if err == nil {
t.Error("GetMovementHistory() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -593,8 +600,8 @@ func TestGetTradeHistory(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetTradeHistory(context.Background(),
"BTCUSD", time.Time{}, time.Time{}, 1, 0)
if err == nil {
t.Error("GetTradeHistory() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -602,8 +609,8 @@ func TestNewOffer(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.NewOffer(context.Background(), "BTC", 1, 1, 1, "loan")
if err == nil {
t.Error("NewOffer() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -611,8 +618,17 @@ func TestCancelOffer(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.CancelOffer(context.Background(), 1337)
if err == nil {
t.Error("CancelOffer() Expected error")
if err != nil {
t.Error(err)
}
}
func TestGetWithdrawalsHistory(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Spot)
if err != nil {
t.Error(err)
}
}
@@ -620,8 +636,8 @@ func TestGetOfferStatus(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetOfferStatus(context.Background(), 1337)
if err == nil {
t.Error("NewOffer() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -629,8 +645,8 @@ func TestGetActiveCredits(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetActiveCredits(context.Background())
if err == nil {
t.Error("GetActiveCredits() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -638,8 +654,8 @@ func TestGetActiveOffers(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetActiveOffers(context.Background())
if err == nil {
t.Error("GetActiveOffers() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -647,8 +663,8 @@ func TestGetActiveMarginFunding(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetActiveMarginFunding(context.Background())
if err == nil {
t.Error("GetActiveMarginFunding() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -656,8 +672,8 @@ func TestGetUnusedMarginFunds(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetUnusedMarginFunds(context.Background())
if err == nil {
t.Error("GetUnusedMarginFunds() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -665,8 +681,8 @@ func TestGetMarginTotalTakenFunds(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetMarginTotalTakenFunds(context.Background())
if err == nil {
t.Error("GetMarginTotalTakenFunds() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -674,8 +690,8 @@ func TestCloseMarginFunding(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.CloseMarginFunding(context.Background(), 1337)
if err == nil {
t.Error("CloseMarginFunding() Expected error")
if err != nil {
t.Error(err)
}
}
@@ -781,7 +797,8 @@ func TestFormatWithdrawPermissions(t *testing.T) {
func TestGetActiveOrders(t *testing.T) {
t.Parallel()
var getOrdersRequest = order.GetOrdersRequest{
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
var getOrdersRequest = order.MultiOrderRequest{
Type: order.AnyType,
AssetType: asset.Spot,
Side: order.AnySide,
@@ -797,17 +814,15 @@ func TestGetActiveOrders(t *testing.T) {
func TestGetOrderHistory(t *testing.T) {
t.Parallel()
var getOrdersRequest = order.GetOrdersRequest{
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
var getOrdersRequest = order.MultiOrderRequest{
Type: order.AnyType,
AssetType: asset.Spot,
Side: order.AnySide,
}
_, err := b.GetOrderHistory(context.Background(), &getOrdersRequest)
if sharedtestvalues.AreAPICredentialsSet(b) && err != nil {
t.Errorf("Could not get order history: %s", err)
} else if !sharedtestvalues.AreAPICredentialsSet(b) && err == nil {
t.Error("Expecting an error when no keys are set")
if err != nil {
t.Error(err)
}
}
@@ -896,10 +911,15 @@ func TestCancelAllExchangeOrdera(t *testing.T) {
func TestModifyOrder(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
_, err := b.ModifyOrder(context.Background(),
&order.Modify{AssetType: asset.Spot})
if err == nil {
t.Error("ModifyOrder() Expected error")
_, err := b.ModifyOrder(
context.Background(),
&order.Modify{
OrderID: "1337",
AssetType: asset.Spot,
Pair: currency.NewPair(currency.BTC, currency.USD),
})
if err != nil {
t.Error(err)
}
}
@@ -1538,7 +1558,10 @@ func Test_FormatExchangeKlineInterval(t *testing.T) {
for x := range testCases {
test := testCases[x]
t.Run(test.name, func(t *testing.T) {
ret := b.FormatExchangeKlineInterval(test.interval)
ret, err := b.FormatExchangeKlineInterval(test.interval)
if err != nil {
t.Error(err)
}
if ret != test.output {
t.Fatalf("unexpected result return expected: %v received: %v", test.output, ret)
}
@@ -1749,3 +1772,35 @@ func TestGetSiteListConfigData(t *testing.T) {
t.Fatal("expected pairs")
}
}
func TestOrderUpdate(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
_, err := b.OrderUpdate(context.Background(), "1234", "", "", 1, 1, 1)
if err != nil {
t.Error(err)
}
}
func TestGetInactiveOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
_, err := b.GetInactiveOrders(context.Background(), "tBTCUSD")
if err != nil {
t.Error(err)
}
_, err = b.GetInactiveOrders(context.Background(), "tBTCUSD", 1, 2, 3, 4)
if err != nil {
t.Error(err)
}
}
func TestCancelMultipleOrdersV2(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders)
_, err := b.CancelMultipleOrdersV2(context.Background(), 1337, 0, 0, time.Time{}, false)
if err != nil {
t.Error(err)
}
}

View File

@@ -418,7 +418,7 @@ type BalanceHistory struct {
// MovementHistory holds deposit and withdrawal history data
type MovementHistory struct {
ID int64 `json:"id"`
TxID int64 `json:"txid"`
TxID string `json:"txid"`
Currency string `json:"currency"`
Method string `json:"method"`
Type string `json:"withdrawal"`
@@ -426,8 +426,8 @@ type MovementHistory struct {
Description string `json:"description"`
Address string `json:"address"`
Status string `json:"status"`
Timestamp string `json:"timestamp"`
TimestampCreated string `json:"timestamp_created"`
Timestamp float64 `json:"timestamp"`
TimestampCreated float64 `json:"timestamp_created"`
Fee float64 `json:"fee"`
}
@@ -837,3 +837,23 @@ type WsCancelOfferRequest struct {
type WsCancelAllOrdersRequest struct {
All int64 `json:"all"`
}
// CancelMultiOrderResponse holds v2 cancelled order data
type CancelMultiOrderResponse struct {
OrderID string
ClientOrderID string
GroupOrderID string
Symbol string
CreatedTime time.Time
UpdatedTime time.Time
Amount float64
OriginalAmount float64
OrderType string
OriginalOrderType string
OrderFlags string
OrderStatus string
Price float64
AveragePrice float64
TrailingPrice float64
AuxLimitPrice float64
}

View File

@@ -319,7 +319,7 @@ func (b *Bitfinex) UpdateTradablePairs(ctx context.Context, forceUpdate bool) er
return err
}
}
return nil
return b.EnsureOnePairEnabled()
}
// UpdateTickers updates the ticker for all currency pairs of a given asset type
@@ -372,9 +372,9 @@ func (b *Bitfinex) FetchTicker(ctx context.Context, p currency.Pair, a asset.Ite
if err != nil {
return nil, err
}
b.appendOptionalDelimiter(&fPair)
tick, err := ticker.GetTicker(b.Name, fPair, asset.Spot)
DFPair := fPair
b.appendOptionalDelimiter(&DFPair)
tick, err := ticker.GetTicker(b.Name, DFPair, a)
if err != nil {
return b.UpdateTicker(ctx, fPair, a)
}
@@ -387,9 +387,9 @@ func (b *Bitfinex) FetchOrderbook(ctx context.Context, p currency.Pair, assetTyp
if err != nil {
return nil, err
}
b.appendOptionalDelimiter(&fPair)
ob, err := orderbook.Get(b.Name, fPair, assetType)
DFPair := fPair
b.appendOptionalDelimiter(&DFPair)
ob, err := orderbook.Get(b.Name, DFPair, assetType)
if err != nil {
return b.UpdateOrderbook(ctx, fPair, assetType)
}
@@ -398,6 +398,12 @@ func (b *Bitfinex) FetchOrderbook(ctx context.Context, p currency.Pair, assetTyp
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *Bitfinex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
if p.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
if err := b.CurrencyPairs.IsAssetEnabled(assetType); err != nil {
return nil, err
}
o := &orderbook.Base{
Exchange: b.Name,
Pair: p,
@@ -411,7 +417,7 @@ func (b *Bitfinex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy
return o, err
}
if assetType != asset.Spot && assetType != asset.Margin && assetType != asset.MarginFunding {
return o, fmt.Errorf("assetType not supported: %v", assetType)
return o, fmt.Errorf("%w %v", asset.ErrNotSupported, assetType)
}
b.appendOptionalDelimiter(&fPair)
var prefix = "t"
@@ -527,15 +533,34 @@ func (b *Bitfinex) FetchAccountInfo(ctx context.Context, assetType asset.Item) (
return acc, nil
}
// GetFundingHistory returns funding history, deposits and
// GetAccountFundingHistory returns funding history, deposits and
// withdrawals
func (b *Bitfinex) GetFundingHistory(_ context.Context) ([]exchange.FundHistory, error) {
func (b *Bitfinex) GetAccountFundingHistory(_ context.Context) ([]exchange.FundingHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bitfinex) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
func (b *Bitfinex) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) {
history, err := b.GetMovementHistory(ctx, c.String(), "", time.Date(2012, 0, 0, 0, 0, 0, 0, time.Local), time.Now(), 0)
if err != nil {
return nil, err
}
resp := make([]exchange.WithdrawalHistory, len(history))
for i := range history {
resp[i] = exchange.WithdrawalHistory{
Status: history[i].Status,
TransferID: strconv.FormatInt(history[i].ID, 10),
Description: history[i].Description,
Timestamp: time.UnixMilli(int64(history[i].Timestamp)),
Currency: history[i].Currency,
Amount: history[i].Amount,
Fee: history[i].Fee,
TransferType: history[i].Type,
CryptoToAddress: history[i].Address,
CryptoTxID: history[i].TxID,
}
}
return resp, nil
}
// GetRecentTrades returns the most recent trades for a currency and asset
@@ -544,20 +569,20 @@ func (b *Bitfinex) GetRecentTrades(ctx context.Context, p currency.Pair, assetTy
}
// GetHistoricTrades returns historic trade data within the timeframe provided
func (b *Bitfinex) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
if assetType == asset.MarginFunding {
return nil, fmt.Errorf("asset type '%v' not supported", assetType)
func (b *Bitfinex) GetHistoricTrades(ctx context.Context, p currency.Pair, a asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
if a == asset.MarginFunding {
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
if err := common.StartEndTimeCheck(timestampStart, timestampEnd); err != nil {
return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v %w", timestampStart, timestampEnd, err)
}
var err error
p, err = b.FormatExchangeCurrency(p, assetType)
p, err = b.FormatExchangeCurrency(p, a)
if err != nil {
return nil, err
}
var currString string
currString, err = b.fixCasing(p, assetType)
currString, err = b.fixCasing(p, a)
if err != nil {
return nil, err
}
@@ -582,7 +607,7 @@ allTrades:
TID: tID,
Exchange: b.Name,
CurrencyPair: p,
AssetType: assetType,
AssetType: a,
Price: tradeData[i].Price,
Amount: tradeData[i].Amount,
Timestamp: time.UnixMilli(tradeData[i].Timestamp),
@@ -616,7 +641,7 @@ func (b *Bitfinex) SubmitOrder(ctx context.Context, o *order.Submit) (*order.Sub
return nil, err
}
fpair, err := b.FormatExchangeCurrency(o.Pair, o.AssetType)
fPair, err := b.FormatExchangeCurrency(o.Pair, o.AssetType)
if err != nil {
return nil, err
}
@@ -627,7 +652,7 @@ func (b *Bitfinex) SubmitOrder(ctx context.Context, o *order.Submit) (*order.Sub
orderID, err = b.WsNewOrder(&WsNewOrderRequest{
CustomID: b.Websocket.AuthConn.GenerateMessageID(false),
Type: o.Type.String(),
Symbol: fpair.String(),
Symbol: fPair.String(),
Amount: o.Amount,
Price: o.Price,
})
@@ -636,13 +661,13 @@ func (b *Bitfinex) SubmitOrder(ctx context.Context, o *order.Submit) (*order.Sub
}
} else {
var response Order
b.appendOptionalDelimiter(&fpair)
b.appendOptionalDelimiter(&fPair)
orderType := o.Type.Lower()
if o.AssetType == asset.Spot {
orderType = "exchange " + orderType
}
response, err = b.NewOrder(ctx,
fpair.String(),
fPair.String(),
orderType,
o.Amount,
o.Price,
@@ -667,29 +692,33 @@ func (b *Bitfinex) SubmitOrder(ctx context.Context, o *order.Submit) (*order.Sub
// ModifyOrder will allow of changing orderbook placement and limit to
// market conversion
func (b *Bitfinex) ModifyOrder(_ context.Context, action *order.Modify) (*order.ModifyResponse, error) {
if !b.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
return nil, common.ErrNotYetImplemented
}
func (b *Bitfinex) ModifyOrder(ctx context.Context, action *order.Modify) (*order.ModifyResponse, error) {
if err := action.Validate(); err != nil {
return nil, err
}
orderIDInt, err := strconv.ParseInt(action.OrderID, 10, 64)
if err != nil {
return &order.ModifyResponse{OrderID: action.OrderID}, err
if b.Websocket.IsEnabled() && b.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
orderIDInt, err := strconv.ParseInt(action.OrderID, 10, 64)
if err != nil {
return &order.ModifyResponse{OrderID: action.OrderID}, err
}
wsRequest := WsUpdateOrderRequest{
OrderID: orderIDInt,
Price: action.Price,
Amount: action.Amount,
}
if action.Side == order.Sell && action.Amount > 0 {
wsRequest.Amount *= -1
}
err = b.WsModifyOrder(&wsRequest)
if err != nil {
return nil, err
}
return action.DeriveModifyResponse()
}
wsRequest := WsUpdateOrderRequest{
OrderID: orderIDInt,
Price: action.Price,
Amount: action.Amount,
}
if action.Side == order.Sell && action.Amount > 0 {
wsRequest.Amount *= -1
}
err = b.WsModifyOrder(&wsRequest)
_, err := b.OrderUpdate(ctx, action.OrderID, "", action.ClientOrderID, action.Amount, action.Price, -1)
if err != nil {
return nil, err
}
@@ -715,8 +744,11 @@ func (b *Bitfinex) CancelOrder(ctx context.Context, o *order.Cancel) error {
}
// CancelBatchOrders cancels an orders by their corresponding ID numbers
func (b *Bitfinex) CancelBatchOrders(_ context.Context, _ []order.Cancel) (order.CancelBatchResponse, error) {
return order.CancelBatchResponse{}, common.ErrNotYetImplemented
func (b *Bitfinex) CancelBatchOrders(_ context.Context, _ []order.Cancel) (*order.CancelBatchResponse, error) {
// While bitfinex supports cancelling multiple orders, it is
// done in a way that is not helpful for GCT, and it would be better instead
// to use CancelAllOrders or CancelOrder
return nil, common.ErrFunctionNotSupported
}
// CancelAllOrders cancels all orders associated with a currency pair
@@ -730,10 +762,115 @@ func (b *Bitfinex) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.
return order.CancelAllResponse{}, err
}
func (b *Bitfinex) parseOrderToOrderDetail(o *Order) (*order.Detail, error) {
side, err := order.StringToOrderSide(o.Side)
if err != nil {
return nil, err
}
var timestamp float64
timestamp, err = strconv.ParseFloat(o.Timestamp, 64)
if err != nil {
log.Warnf(log.ExchangeSys,
"%s Unable to convert timestamp '%s', leaving blank",
b.Name, o.Timestamp)
}
var pair currency.Pair
pair, err = currency.NewPairFromString(o.Symbol)
if err != nil {
return nil, err
}
orderDetail := &order.Detail{
Amount: o.OriginalAmount,
Date: time.Unix(int64(timestamp), 0),
Exchange: b.Name,
OrderID: strconv.FormatInt(o.ID, 10),
Side: side,
Price: o.Price,
RemainingAmount: o.RemainingAmount,
Pair: pair,
ExecutedAmount: o.ExecutedAmount,
}
switch {
case o.IsLive:
orderDetail.Status = order.Active
case o.IsCancelled:
orderDetail.Status = order.Cancelled
case o.IsHidden:
orderDetail.Status = order.Hidden
default:
orderDetail.Status = order.UnknownStatus
}
// API docs discrepancy. Example contains prefixed "exchange "
// Return type suggests “market” / “limit” / “stop” / “trailing-stop”
orderType := strings.Replace(o.Type, "exchange ", "", 1)
if orderType == "trailing-stop" {
orderDetail.Type = order.TrailingStop
} else {
orderDetail.Type, err = order.StringToOrderType(orderType)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", b.Name, err)
}
}
return orderDetail, nil
}
// GetOrderInfo returns order information based on order ID
func (b *Bitfinex) GetOrderInfo(_ context.Context, _ string, _ currency.Pair, _ asset.Item) (order.Detail, error) {
var orderDetail order.Detail
return orderDetail, common.ErrNotYetImplemented
func (b *Bitfinex) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetType asset.Item) (*order.Detail, error) {
if pair.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
if err := b.CurrencyPairs.IsAssetEnabled(assetType); err != nil {
return nil, err
}
id, err := strconv.ParseInt(orderID, 10, 64)
if err != nil {
return nil, err
}
b.appendOptionalDelimiter(&pair)
var cf string
cf, err = b.fixCasing(pair, assetType)
if err != nil {
return nil, err
}
resp, err := b.GetInactiveOrders(ctx, cf, id)
if err != nil {
return nil, err
}
for i := range resp {
if resp[i].OrderID != id {
continue
}
var o *order.Detail
o, err = b.parseOrderToOrderDetail(&resp[i])
if err != nil {
return nil, err
}
return o, nil
}
resp, err = b.GetOpenOrders(ctx, id)
if err != nil {
return nil, err
}
for i := range resp {
if resp[i].OrderID != id {
continue
}
var o *order.Detail
o, err = b.parseOrderToOrderDetail(&resp[i])
if err != nil {
return nil, err
}
return o, nil
}
return nil, fmt.Errorf("%w %v", order.ErrOrderNotFound, orderID)
}
// GetDepositAddress returns a deposit address for a specified currency
@@ -753,7 +890,7 @@ func (b *Bitfinex) GetDepositAddress(ctx context.Context, c currency.Code, accou
methods := acceptableMethods.lookup(c)
if len(methods) == 0 {
return nil, errors.New("unsupported currency")
return nil, currency.ErrCurrencyNotSupported
}
method := methods[0]
if len(methods) > 1 && chain != "" {
@@ -873,7 +1010,7 @@ func (b *Bitfinex) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBui
}
// GetActiveOrders retrieves any orders that are active/open
func (b *Bitfinex) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) (order.FilteredOrders, error) {
func (b *Bitfinex) GetActiveOrders(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) {
err := req.Validate()
if err != nil {
return nil, err
@@ -886,141 +1023,49 @@ func (b *Bitfinex) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequ
orders := make([]order.Detail, len(resp))
for i := range resp {
var side order.Side
side, err = order.StringToOrderSide(resp[i].Side)
var orderDetail *order.Detail
orderDetail, err = b.parseOrderToOrderDetail(&resp[i])
if err != nil {
return nil, err
}
var timestamp float64
timestamp, err = strconv.ParseFloat(resp[i].Timestamp, 64)
if err != nil {
log.Warnf(log.ExchangeSys,
"Unable to convert timestamp '%s', leaving blank",
resp[i].Timestamp)
}
var pair currency.Pair
pair, err = currency.NewPairFromString(resp[i].Symbol)
if err != nil {
return nil, err
}
orderDetail := order.Detail{
Amount: resp[i].OriginalAmount,
Date: time.Unix(int64(timestamp), 0),
Exchange: b.Name,
OrderID: strconv.FormatInt(resp[i].ID, 10),
Side: side,
Price: resp[i].Price,
RemainingAmount: resp[i].RemainingAmount,
Pair: pair,
ExecutedAmount: resp[i].ExecutedAmount,
}
switch {
case resp[i].IsLive:
orderDetail.Status = order.Active
case resp[i].IsCancelled:
orderDetail.Status = order.Cancelled
case resp[i].IsHidden:
orderDetail.Status = order.Hidden
default:
orderDetail.Status = order.UnknownStatus
}
// API docs discrepancy. Example contains prefixed "exchange "
// Return type suggests “market” / “limit” / “stop” / “trailing-stop”
orderType := strings.Replace(resp[i].Type, "exchange ", "", 1)
if orderType == "trailing-stop" {
orderDetail.Type = order.TrailingStop
} else {
orderDetail.Type, err = order.StringToOrderType(orderType)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", b.Name, err)
}
}
orders[i] = orderDetail
orders[i] = *orderDetail
}
return req.Filter(b.Name, orders), nil
}
// GetOrderHistory retrieves account order information
// Can Limit response to specific order status
func (b *Bitfinex) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) (order.FilteredOrders, error) {
func (b *Bitfinex) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) {
err := req.Validate()
if err != nil {
return nil, err
}
resp, err := b.GetInactiveOrders(ctx)
if err != nil {
return nil, err
}
orders := make([]order.Detail, len(resp))
for i := range resp {
var side order.Side
side, err = order.StringToOrderSide(resp[i].Side)
if err != nil {
return nil, err
}
var timestamp int64
timestamp, err = strconv.ParseInt(resp[i].Timestamp, 10, 64)
if err != nil {
log.Warnf(log.ExchangeSys, "Unable to convert timestamp '%v', leaving blank", resp[i].Timestamp)
}
orderDate := time.Unix(timestamp, 0)
var pair currency.Pair
pair, err = currency.NewPairFromString(resp[i].Symbol)
if err != nil {
return nil, err
}
orderDetail := order.Detail{
Amount: resp[i].OriginalAmount,
Date: orderDate,
Exchange: b.Name,
OrderID: strconv.FormatInt(resp[i].ID, 10),
Side: side,
Price: resp[i].Price,
AverageExecutedPrice: resp[i].AverageExecutionPrice,
RemainingAmount: resp[i].RemainingAmount,
ExecutedAmount: resp[i].ExecutedAmount,
Pair: pair,
}
orderDetail.InferCostsAndTimes()
switch {
case resp[i].IsLive:
orderDetail.Status = order.Active
case resp[i].IsCancelled:
orderDetail.Status = order.Cancelled
case resp[i].IsHidden:
orderDetail.Status = order.Hidden
default:
orderDetail.Status = order.UnknownStatus
}
// API docs discrepancy. Example contains prefixed "exchange "
// Return type suggests “market” / “limit” / “stop” / “trailing-stop”
orderType := strings.Replace(resp[i].Type, "exchange ", "", 1)
if orderType == "trailing-stop" {
orderDetail.Type = order.TrailingStop
} else {
orderDetail.Type, err = order.StringToOrderType(orderType)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", b.Name, err)
}
}
orders[i] = orderDetail
}
var orders []order.Detail
for i := range req.Pairs {
b.appendOptionalDelimiter(&req.Pairs[i])
var cf string
cf, err = b.fixCasing(req.Pairs[i], req.AssetType)
if err != nil {
return nil, err
}
var resp []Order
resp, err = b.GetInactiveOrders(ctx, cf)
if err != nil {
return nil, err
}
for j := range resp {
var orderDetail *order.Detail
orderDetail, err = b.parseOrderToOrderDetail(&resp[j])
if err != nil {
return nil, err
}
orders = append(orders, *orderDetail)
}
}
return req.Filter(b.Name, orders), nil
}
@@ -1045,16 +1090,34 @@ func (b *Bitfinex) ValidateAPICredentials(ctx context.Context, assetType asset.I
}
// FormatExchangeKlineInterval returns Interval to exchange formatted string
func (b *Bitfinex) FormatExchangeKlineInterval(in kline.Interval) string {
func (b *Bitfinex) FormatExchangeKlineInterval(in kline.Interval) (string, error) {
switch in {
case kline.OneMin:
return "1m", nil
case kline.FiveMin:
return "5m", nil
case kline.FifteenMin:
return "15m", nil
case kline.ThirtyMin:
return "30m", nil
case kline.OneHour:
return "1h", nil
case kline.ThreeHour:
return "3h", nil
case kline.SixHour:
return "6h", nil
case kline.TwelveHour:
return "12h", nil
case kline.OneDay:
return "1D"
return "1D", nil
case kline.OneWeek:
return "7D"
return "7D", nil
case kline.OneWeek * 2:
return "14D"
return "14D", nil
case kline.OneMonth:
return "1M", nil
default:
return in.Short()
return "", fmt.Errorf("%w %v", kline.ErrInvalidInterval, in)
}
}
@@ -1069,14 +1132,11 @@ func (b *Bitfinex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
if err != nil {
return nil, err
}
candles, err := b.GetCandles(ctx,
cf,
b.FormatExchangeKlineInterval(req.ExchangeInterval),
req.Start.UnixMilli(),
req.End.UnixMilli(),
uint32(req.RequestLimit),
true)
fInterval, err := b.FormatExchangeKlineInterval(req.ExchangeInterval)
if err != nil {
return nil, err
}
candles, err := b.GetCandles(ctx, cf, fInterval, req.Start.UnixMilli(), req.End.UnixMilli(), uint32(req.RequestLimit), true)
if err != nil {
return nil, err
}
@@ -1106,17 +1166,14 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency
if err != nil {
return nil, err
}
fInterval, err := b.FormatExchangeKlineInterval(req.ExchangeInterval)
if err != nil {
return nil, err
}
timeSeries := make([]kline.Candle, 0, req.Size())
for x := range req.RangeHolder.Ranges {
var candles []Candle
candles, err = b.GetCandles(ctx,
cf,
b.FormatExchangeKlineInterval(req.ExchangeInterval),
req.RangeHolder.Ranges[x].Start.Ticks*1000,
req.RangeHolder.Ranges[x].End.Ticks*1000,
uint32(req.RequestLimit),
true)
candles, err = b.GetCandles(ctx, cf, fInterval, req.RangeHolder.Ranges[x].Start.Time.UnixMilli(), req.RangeHolder.Ranges[x].End.Time.UnixMilli(), uint32(req.RequestLimit), true)
if err != nil {
return nil, err
}
@@ -1148,7 +1205,7 @@ func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
checkString[1] = 'F'
}
fmt, err := b.FormatExchangeCurrency(in, a)
cFmt, err := b.FormatExchangeCurrency(in, a)
if err != nil {
return "", err
}
@@ -1156,15 +1213,15 @@ func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
y := in.Base.String()
if (y[0] != checkString[0] && y[0] != checkString[1]) ||
(y[0] == checkString[1] && y[1] == checkString[1]) || in.Base == currency.TNB {
if fmt.Quote.IsEmpty() {
return string(checkString[0]) + fmt.Base.Upper().String(), nil
if cFmt.Quote.IsEmpty() {
return string(checkString[0]) + cFmt.Base.Upper().String(), nil
}
return string(checkString[0]) + fmt.Upper().String(), nil
return string(checkString[0]) + cFmt.Upper().String(), nil
}
runes := []rune(fmt.Upper().String())
if fmt.Quote.IsEmpty() {
runes = []rune(fmt.Base.Upper().String())
runes := []rune(cFmt.Upper().String())
if cFmt.Quote.IsEmpty() {
runes = []rune(cFmt.Base.Upper().String())
}
runes[0] = unicode.ToLower(runes[0])
return string(runes), nil
@@ -1188,3 +1245,8 @@ func (b *Bitfinex) GetAvailableTransferChains(ctx context.Context, cryptocurrenc
}
return availChains, nil
}
// GetServerTime returns the current exchange server time.
func (b *Bitfinex) GetServerTime(_ context.Context, _ asset.Item) (time.Time, error) {
return time.Time{}, common.ErrFunctionNotSupported
}