mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-03 15:10:49 +00:00
exchanges: Refactor time handling and other minor improvements (#1948)
* exchanges: Refactor time handling and other minor improvements - Updated Kraken wrapper to utilise new time handling methods. - Simplified Kucoin types by removing unnecessary structures and using direct JSON unmarshalling. - Improved websocket handling in Kucoin to directly parse candlestick data. - Modified Lbank types to use the new time representation. - Adjusted Poloniex wrapper and types to utilise the new time handling. - Updated Yobit types and wrapper to reflect changes in time representation. - Introduced DateTime type for better handling of specific time formats. - Added tests for DateTime unmarshalling to ensure correctness. - Rid UTC().Unix and UTC().UnixMilli as it's not needed - Correct Huobi timestamp usage for some endpoints. - Rid RFC3339 time parsing since Go does that automatically. * exchanges: Refactor JSON unmarshalling for various types and improve test coverage * linter: Update error message in TestGetKlines * refactor: Simplify JSON unmarshalling in MovementHistory and improve test assertions in GetKlines * refactor: Improve JSON unmarshalling for channel name and clarify comment in wsProcessOpenOrders * refactor: Update time handling in Huobi types to use types.Time for createdAt fields and relax GetLiquidationOrders test * refactor: Move wsTicker, wsSpread, wsTrades, and wsCandle types to kraken_types.go for better organistion * refactor: Add validation for underlying parameter in GetExpirationTime and update tests
This commit is contained in:
@@ -750,18 +750,16 @@ func tickerFromFundingResp(symbol string, respAny []any) (*Ticker, error) {
|
||||
// timestampStart is a millisecond timestamp
|
||||
// timestampEnd is a millisecond timestamp
|
||||
// reOrderResp reorders the returned data.
|
||||
func (b *Bitfinex) GetTrades(ctx context.Context, currencyPair string, limit, timestampStart, timestampEnd int64, reOrderResp bool) ([]Trade, error) {
|
||||
func (b *Bitfinex) GetTrades(ctx context.Context, currencyPair string, limit uint64, start, end time.Time, reOrderResp bool) ([]Trade, error) {
|
||||
v := url.Values{}
|
||||
if limit > 0 {
|
||||
v.Set("limit", strconv.FormatInt(limit, 10))
|
||||
v.Set("limit", strconv.FormatUint(limit, 10))
|
||||
}
|
||||
|
||||
if timestampStart > 0 {
|
||||
v.Set("start", strconv.FormatInt(timestampStart, 10))
|
||||
if !start.IsZero() {
|
||||
v.Set("start", strconv.FormatInt(start.UnixMilli(), 10))
|
||||
}
|
||||
|
||||
if timestampEnd > 0 {
|
||||
v.Set("end", strconv.FormatInt(timestampEnd, 10))
|
||||
if !end.IsZero() {
|
||||
v.Set("end", strconv.FormatInt(end.UnixMilli(), 10))
|
||||
}
|
||||
sortVal := "0"
|
||||
if reOrderResp {
|
||||
@@ -769,72 +767,9 @@ func (b *Bitfinex) GetTrades(ctx context.Context, currencyPair string, limit, ti
|
||||
}
|
||||
v.Set("sort", sortVal)
|
||||
|
||||
path := bitfinexAPIVersion2 + bitfinexTrades + currencyPair + "/hist" + "?" + v.Encode()
|
||||
|
||||
var resp [][]any
|
||||
err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp, tradeRateLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
history := make([]Trade, len(resp))
|
||||
for i := range resp {
|
||||
amount, ok := resp[i][2].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert amount")
|
||||
}
|
||||
side := order.Buy.String()
|
||||
if amount < 0 {
|
||||
side = order.Sell.String()
|
||||
amount *= -1
|
||||
}
|
||||
|
||||
tid, ok := resp[i][0].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert trade ID")
|
||||
}
|
||||
timestamp, ok := resp[i][1].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert timestamp")
|
||||
}
|
||||
|
||||
if len(resp[i]) > 4 {
|
||||
var rate float64
|
||||
rate, ok = resp[i][3].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert rate")
|
||||
}
|
||||
var period float64
|
||||
period, ok = resp[i][4].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert period")
|
||||
}
|
||||
|
||||
history[i] = Trade{
|
||||
TID: int64(tid),
|
||||
Timestamp: int64(timestamp),
|
||||
Amount: amount,
|
||||
Rate: rate,
|
||||
Period: int64(period),
|
||||
Type: side,
|
||||
}
|
||||
continue
|
||||
}
|
||||
price, ok := resp[i][3].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert price")
|
||||
}
|
||||
|
||||
history[i] = Trade{
|
||||
TID: int64(tid),
|
||||
Timestamp: int64(timestamp),
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Type: side,
|
||||
}
|
||||
}
|
||||
|
||||
return history, nil
|
||||
path := common.EncodeURLValues(bitfinexAPIVersion2+bitfinexTrades+currencyPair+"/hist", v)
|
||||
var resp []Trade
|
||||
return resp, b.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp, tradeRateLimit)
|
||||
}
|
||||
|
||||
// GetOrderbook retrieves the orderbook bid and ask price points for a currency
|
||||
@@ -997,108 +932,39 @@ func (b *Bitfinex) GetLends(ctx context.Context, symbol string, values url.Value
|
||||
// GetCandles returns candle chart data
|
||||
// timeFrame values: '1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '1W', '14D', '1M'
|
||||
// section values: last or hist
|
||||
func (b *Bitfinex) GetCandles(ctx context.Context, symbol, timeFrame string, start, end int64, limit uint64, historic bool) ([]Candle, error) {
|
||||
func (b *Bitfinex) GetCandles(ctx context.Context, symbol, timeFrame string, start, end time.Time, limit uint64, historic bool) ([]Candle, error) {
|
||||
var fundingPeriod string
|
||||
if symbol[0] == 'f' {
|
||||
fundingPeriod = ":p30"
|
||||
}
|
||||
|
||||
path := bitfinexAPIVersion2 +
|
||||
bitfinexCandles +
|
||||
":" +
|
||||
timeFrame +
|
||||
":" +
|
||||
symbol +
|
||||
fundingPeriod
|
||||
path := bitfinexAPIVersion2 + bitfinexCandles + ":" + timeFrame + ":" + symbol + fundingPeriod
|
||||
|
||||
if historic {
|
||||
v := url.Values{}
|
||||
if start > 0 {
|
||||
v.Set("start", strconv.FormatInt(start, 10))
|
||||
if !start.IsZero() {
|
||||
v.Set("start", strconv.FormatInt(start.UnixMilli(), 10))
|
||||
}
|
||||
|
||||
if end > 0 {
|
||||
v.Set("end", strconv.FormatInt(end, 10))
|
||||
if !end.IsZero() {
|
||||
v.Set("end", strconv.FormatInt(end.UnixMilli(), 10))
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
v.Set("limit", strconv.FormatUint(limit, 10))
|
||||
}
|
||||
|
||||
path += "/hist"
|
||||
if len(v) > 0 {
|
||||
path += "?" + v.Encode()
|
||||
}
|
||||
|
||||
var response [][]any
|
||||
err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &response, candle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
candles := make([]Candle, len(response))
|
||||
for i := range response {
|
||||
var c Candle
|
||||
timestamp, ok := response[i][0].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert timestamp")
|
||||
}
|
||||
c.Timestamp = time.UnixMilli(int64(timestamp))
|
||||
if c.Open, ok = response[i][1].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert open")
|
||||
}
|
||||
if c.Close, ok = response[i][2].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert close")
|
||||
}
|
||||
if c.High, ok = response[i][3].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert high")
|
||||
}
|
||||
if c.Low, ok = response[i][4].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert low")
|
||||
}
|
||||
if c.Volume, ok = response[i][5].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert volume")
|
||||
}
|
||||
candles[i] = c
|
||||
}
|
||||
|
||||
return candles, nil
|
||||
var response []Candle
|
||||
return response, b.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(path+"/hist", v), &response, candle)
|
||||
}
|
||||
|
||||
path += "/last"
|
||||
|
||||
var response []any
|
||||
err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &response, candle)
|
||||
var c Candle
|
||||
err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &c, candle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(response) == 0 {
|
||||
return nil, errors.New("no data returned")
|
||||
}
|
||||
|
||||
var c Candle
|
||||
timestamp, ok := response[0].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert timestamp")
|
||||
}
|
||||
c.Timestamp = time.UnixMilli(int64(timestamp))
|
||||
if c.Open, ok = response[1].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert open")
|
||||
}
|
||||
if c.Close, ok = response[2].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert close")
|
||||
}
|
||||
if c.High, ok = response[3].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert high")
|
||||
}
|
||||
if c.Low, ok = response[4].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert low")
|
||||
}
|
||||
if c.Volume, ok = response[5].(float64); !ok {
|
||||
return nil, errors.New("unable to type assert volume")
|
||||
}
|
||||
|
||||
return []Candle{c}, nil
|
||||
}
|
||||
|
||||
@@ -1831,7 +1697,6 @@ 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 [][]any
|
||||
req := make(map[string]any)
|
||||
req["currency"] = symbol
|
||||
|
||||
@@ -1839,89 +1704,18 @@ func (b *Bitfinex) GetMovementHistory(ctx context.Context, symbol, method string
|
||||
req["method"] = method
|
||||
}
|
||||
if !timeSince.IsZero() {
|
||||
req["since"] = timeSince
|
||||
req["since"] = timeSince.UnixMilli()
|
||||
}
|
||||
if !timeUntil.IsZero() {
|
||||
req["until"] = timeUntil
|
||||
req["until"] = timeUntil.UnixMilli()
|
||||
}
|
||||
if limit > 0 {
|
||||
req["limit"] = limit
|
||||
}
|
||||
|
||||
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], "Movements.Id")
|
||||
}
|
||||
move.ID = int64(id)
|
||||
case 1:
|
||||
move.Currency, ok = response[i][j].(string)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("string", response[i][j], "Movements.Currency")
|
||||
}
|
||||
case 5:
|
||||
move.TimestampCreated, ok = response[i][j].(float64)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("float64", response[i][j], "Movements.MovementStartedAt")
|
||||
}
|
||||
case 6:
|
||||
move.Timestamp, ok = response[i][j].(float64)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("float64", response[i][j], "Movements.MovementLastUpdated")
|
||||
}
|
||||
case 9:
|
||||
move.Status, ok = response[i][j].(string)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("string", response[i][j], "Movements.CurrentStatus")
|
||||
}
|
||||
case 12:
|
||||
move.Amount, ok = response[i][j].(float64)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("float64", response[i][j], "Movements.AmountOfFundsMoved")
|
||||
}
|
||||
case 13:
|
||||
move.Fee, ok = response[i][j].(float64)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("float64", response[i][j], "Movements.FeesApplied")
|
||||
}
|
||||
case 16:
|
||||
move.Address, ok = response[i][j].(string)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("string", response[i][j], "Movements.DestinationAddress")
|
||||
}
|
||||
case 20:
|
||||
move.TxID, ok = response[i][j].(string)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("string", response[i][j], "Movements.TransactionId")
|
||||
}
|
||||
case 21:
|
||||
move.Description, ok = response[i][j].(string)
|
||||
if !ok {
|
||||
return nil, common.GetTypeAssertError("string", response[i][j], "Movements.WithdrawTransactionNote")
|
||||
}
|
||||
}
|
||||
}
|
||||
resp = append(resp, move)
|
||||
}
|
||||
return resp, nil
|
||||
var resp []MovementHistory
|
||||
path := bitfinexV2Auth + "r/" + bitfinexHistoryMovements + "/" + symbol + "/" + bitfinexHistoryShort
|
||||
return resp, b.SendAuthenticatedHTTPRequestV2(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp, orderMulti)
|
||||
}
|
||||
|
||||
// GetTradeHistory returns past executed trades
|
||||
@@ -2232,18 +2026,12 @@ func getOfflineTradeFee(price, amount float64) float64 {
|
||||
}
|
||||
|
||||
// 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
|
||||
func (b *Bitfinex) GetCryptocurrencyWithdrawalFee(c currency.Code, accountFees AccountFees) (float64, error) {
|
||||
fee, ok := accountFees.Withdraw[c.String()]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("withdrawal fee for %s not found", c.String())
|
||||
}
|
||||
|
||||
return fee, nil
|
||||
return fee.Float64(), nil
|
||||
}
|
||||
|
||||
func getInternationalBankDepositFee(amount float64) float64 {
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
|
||||
testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions"
|
||||
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
|
||||
"github.com/thrasher-corp/gocryptotrader/types"
|
||||
)
|
||||
|
||||
// Please supply API keys here or in config/testdata.json to test authenticated endpoints
|
||||
@@ -308,10 +309,9 @@ func checkFundingTick(tb testing.TB, tick *Ticker) {
|
||||
func TestGetTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := b.GetTrades(t.Context(), "tBTCUSD", 5, 0, 0, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
r, err := b.GetTrades(t.Context(), "tBTCUSD", 5, time.Time{}, time.Time{}, false)
|
||||
require.NoError(t, err, "GetTrades must not error")
|
||||
assert.NotEmpty(t, r, "GetTrades should return some trades")
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
@@ -368,12 +368,9 @@ func TestGetLends(t *testing.T) {
|
||||
|
||||
func TestGetCandles(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := time.Now().Add(-time.Hour * 2).Truncate(time.Hour)
|
||||
s := e.Add(-time.Hour * 4)
|
||||
_, err := b.GetCandles(t.Context(), "fUST", "1D", s.UnixMilli(), e.UnixMilli(), 10000, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := b.GetCandles(t.Context(), "fUST", "1D", time.Now().AddDate(0, 0, -1), time.Now(), 10000, true)
|
||||
require.NoError(t, err, "GetCandles must not error")
|
||||
assert.NotEmpty(t, c, "GetCandles should return some candles")
|
||||
}
|
||||
|
||||
func TestGetLeaderboard(t *testing.T) {
|
||||
@@ -670,14 +667,45 @@ func TestGetMovementHistory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTradeHistory(t *testing.T) {
|
||||
func TestMovementHistoryUnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, b)
|
||||
_, err := b.GetTradeHistory(t.Context(),
|
||||
"BTCUSD", time.Time{}, time.Time{}, 1, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
deposit := []byte(`[13105603,"ETH","ETHEREUM",null,null,1569348774000,1569348774000,null,null,"COMPLETED",null,null,0.26300954,-0.00135,null,null,"DESTINATION_ADDRESS",null,null,null,"TRANSACTION_ID",null]`)
|
||||
var result MovementHistory
|
||||
require.NoError(t, json.Unmarshal(deposit, &result))
|
||||
stringPtr := func(s string) *string {
|
||||
return &s
|
||||
}
|
||||
exp := MovementHistory{
|
||||
ID: 13105603,
|
||||
Currency: "ETH",
|
||||
CurrencyName: "ETHEREUM",
|
||||
MTSStarted: types.Time(time.Unix(1569348774, 0)),
|
||||
MTSUpdated: types.Time(time.Unix(1569348774, 0)),
|
||||
Status: "COMPLETED",
|
||||
Amount: 0.26300954,
|
||||
Fees: -0.00135,
|
||||
DestinationAddress: "DESTINATION_ADDRESS",
|
||||
TransactionID: stringPtr("TRANSACTION_ID"),
|
||||
TransactionType: "deposit",
|
||||
}
|
||||
assert.Equal(t, exp, result, "MovementHistory should unmarshal correctly")
|
||||
withdrawal := []byte(`[13293039,"ETH","ETHEREUM",null,null,1574175052000,1574181326000,null,null,"CANCELED",null,null,-0.24,-0.00135,null,null,"DESTINATION_ADDRESS",null,null,null,"TRANSACTION_ID","Purchase of 100 pizzas"]`)
|
||||
require.NoError(t, json.Unmarshal(withdrawal, &result))
|
||||
exp = MovementHistory{
|
||||
ID: 13293039,
|
||||
Currency: "ETH",
|
||||
CurrencyName: "ETHEREUM",
|
||||
MTSStarted: types.Time(time.Unix(1574175052, 0)),
|
||||
MTSUpdated: types.Time(time.Unix(1574181326, 0)),
|
||||
Status: "CANCELED",
|
||||
Amount: -0.24,
|
||||
Fees: -0.00135,
|
||||
DestinationAddress: "DESTINATION_ADDRESS",
|
||||
TransactionID: stringPtr("TRANSACTION_ID"),
|
||||
TransactionNote: stringPtr("Purchase of 100 pizzas"),
|
||||
TransactionType: "withdrawal",
|
||||
}
|
||||
assert.Equal(t, exp, result, "MovementHistory should unmarshal correctly")
|
||||
}
|
||||
|
||||
func TestNewOffer(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package bitfinex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -197,17 +198,31 @@ type Orderbook struct {
|
||||
|
||||
// Trade holds resp information
|
||||
type Trade struct {
|
||||
Timestamp int64
|
||||
TID int64
|
||||
Price float64
|
||||
Timestamp types.Time
|
||||
Amount float64
|
||||
Exchange string
|
||||
Price float64
|
||||
Rate float64
|
||||
Period int64
|
||||
Type string
|
||||
Period int64 // Funding offer period in days
|
||||
Side order.Side
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals JSON data into a Trade struct
|
||||
func (t *Trade) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &[5]any{&t.TID, &t.Timestamp, &t.Amount, &t.Rate, &t.Period}); err != nil {
|
||||
return err
|
||||
}
|
||||
if t.Period == 0 {
|
||||
t.Price, t.Rate = t.Rate, 0
|
||||
}
|
||||
t.Side = order.Buy
|
||||
if t.Amount < 0 {
|
||||
t.Amount = math.Abs(t.Amount)
|
||||
t.Side = order.Sell
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lendbook holds most recent funding data for a relevant currency
|
||||
type Lendbook struct {
|
||||
Bids []Book `json:"bids"`
|
||||
@@ -216,19 +231,19 @@ type Lendbook struct {
|
||||
|
||||
// FundingBookItem is a generalised sub-type to hold book information
|
||||
type FundingBookItem struct {
|
||||
Rate float64 `json:"rate,string"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Period int `json:"period"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
FlashReturnRate string `json:"frr"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Period int `json:"period"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
FlashReturnRate string `json:"frr"`
|
||||
}
|
||||
|
||||
// Lends holds the lent information by currency
|
||||
type Lends struct {
|
||||
Rate float64 `json:"rate,string"`
|
||||
AmountLent float64 `json:"amount_lent,string"`
|
||||
AmountUsed float64 `json:"amount_used,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
AmountLent float64 `json:"amount_lent,string"`
|
||||
AmountUsed float64 `json:"amount_used,string"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// AccountInfoFull adds the error message to Account info
|
||||
@@ -254,7 +269,7 @@ type AccountInfoFees struct {
|
||||
|
||||
// AccountFees stores withdrawal account fee data from Bitfinex
|
||||
type AccountFees struct {
|
||||
Withdraw map[string]any `json:"withdraw"`
|
||||
Withdraw map[string]types.Number `json:"withdraw"`
|
||||
}
|
||||
|
||||
// AccountSummary holds account summary data
|
||||
@@ -395,80 +410,120 @@ type GenericResponse struct {
|
||||
|
||||
// Position holds position information
|
||||
type Position struct {
|
||||
ID int64 `json:"id"`
|
||||
Symbol string `json:"string"`
|
||||
Status string `json:"active"`
|
||||
Base float64 `json:"base,string"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Swap float64 `json:"swap,string"`
|
||||
PL float64 `json:"pl,string"`
|
||||
ID int64 `json:"id"`
|
||||
Symbol string `json:"string"`
|
||||
Status string `json:"active"`
|
||||
Base float64 `json:"base,string"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
Swap float64 `json:"swap,string"`
|
||||
PL float64 `json:"pl,string"`
|
||||
}
|
||||
|
||||
// BalanceHistory holds balance history information
|
||||
type BalanceHistory struct {
|
||||
Currency string `json:"currency"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Balance float64 `json:"balance,string"`
|
||||
Description string `json:"description"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Currency string `json:"currency"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Balance float64 `json:"balance,string"`
|
||||
Description string `json:"description"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// MovementHistory holds deposit and withdrawal history data
|
||||
type MovementHistory struct {
|
||||
ID int64 `json:"id"`
|
||||
TxID string `json:"txid"`
|
||||
Currency string `json:"currency"`
|
||||
Method string `json:"method"`
|
||||
Type string `json:"withdrawal"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Description string `json:"description"`
|
||||
Address string `json:"address"`
|
||||
Status string `json:"status"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
TimestampCreated float64 `json:"timestamp_created"`
|
||||
Fee float64 `json:"fee"`
|
||||
ID int64
|
||||
Currency string
|
||||
CurrencyName string // AKA Method
|
||||
TXID string
|
||||
MTSStarted types.Time
|
||||
MTSUpdated types.Time
|
||||
Status string
|
||||
Amount types.Number // Positive for deposits, negative for withdrawals
|
||||
Fees types.Number
|
||||
DestinationAddress string
|
||||
PaymentID *string
|
||||
TransactionID *string
|
||||
TransactionNote *string
|
||||
TransactionType string // "deposit" or "withdrawal"
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals JSON data into a MovementHistory struct
|
||||
func (m *MovementHistory) UnmarshalJSON(data []byte) error {
|
||||
var unusedField any
|
||||
if err := json.Unmarshal(data, &[22]any{
|
||||
&m.ID,
|
||||
&m.Currency,
|
||||
&m.CurrencyName,
|
||||
&unusedField,
|
||||
&unusedField,
|
||||
&m.MTSStarted,
|
||||
&m.MTSUpdated,
|
||||
&unusedField,
|
||||
&unusedField,
|
||||
&m.Status,
|
||||
&unusedField,
|
||||
&unusedField,
|
||||
&m.Amount,
|
||||
&m.Fees,
|
||||
&unusedField,
|
||||
&unusedField,
|
||||
&m.DestinationAddress,
|
||||
&m.PaymentID,
|
||||
&unusedField,
|
||||
&unusedField,
|
||||
&m.TransactionID,
|
||||
&m.TransactionNote,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.Amount < 0 {
|
||||
m.TransactionType = "withdrawal"
|
||||
} else {
|
||||
m.TransactionType = "deposit"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TradeHistory holds trade history data
|
||||
type TradeHistory struct {
|
||||
Price float64 `json:"price,string"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Exchange string `json:"exchange"`
|
||||
Type string `json:"type"`
|
||||
FeeCurrency string `json:"fee_currency"`
|
||||
FeeAmount float64 `json:"fee_amount,string"`
|
||||
TID int64 `json:"tid"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
Exchange string `json:"exchange"`
|
||||
Type string `json:"type"`
|
||||
FeeCurrency string `json:"fee_currency"`
|
||||
FeeAmount float64 `json:"fee_amount,string"`
|
||||
TID int64 `json:"tid"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
|
||||
// Offer holds offer information
|
||||
type Offer struct {
|
||||
ID int64 `json:"id"`
|
||||
Currency string `json:"currency"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
Period int64 `json:"period"`
|
||||
Direction string `json:"direction"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
ID int64 `json:"id"`
|
||||
Currency string `json:"currency"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
Period int64 `json:"period"`
|
||||
Direction string `json:"direction"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
IsLive bool `json:"is_live"`
|
||||
IsCancelled bool `json:"is_cancelled"`
|
||||
OriginalAmount float64 `json:"original_amount,string"`
|
||||
RemainingAmount float64 `json:"remaining_amount,string"`
|
||||
ExecutedAmount float64 `json:"executed_amount,string"`
|
||||
}
|
||||
|
||||
// MarginFunds holds active funding information used in a margin position
|
||||
type MarginFunds struct {
|
||||
ID int64 `json:"id"`
|
||||
PositionID int64 `json:"position_id"`
|
||||
Currency string `json:"currency"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
Period int `json:"period"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
AutoClose bool `json:"auto_close"`
|
||||
ID int64 `json:"id"`
|
||||
PositionID int64 `json:"position_id"`
|
||||
Currency string `json:"currency"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
Period int `json:"period"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Timestamp types.Time `json:"timestamp"`
|
||||
AutoClose bool `json:"auto_close"`
|
||||
}
|
||||
|
||||
// MarginTotalTakenFunds holds position funding including sum of active backing
|
||||
@@ -493,28 +548,19 @@ type WebsocketBook struct {
|
||||
Period int64
|
||||
}
|
||||
|
||||
// wsTrade holds trade information
|
||||
type wsTrade struct {
|
||||
ID int64
|
||||
Timestamp types.Time
|
||||
Amount float64
|
||||
Price float64
|
||||
Period int64 // Funding offer period in days
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals json bytes into a wsTrade
|
||||
func (t *wsTrade) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, &[5]any{&t.ID, &t.Timestamp, &t.Amount, &t.Price, &t.Period})
|
||||
}
|
||||
|
||||
// Candle holds OHLC data
|
||||
// Candle holds OHLCV data
|
||||
type Candle struct {
|
||||
Timestamp time.Time
|
||||
Open float64
|
||||
Close float64
|
||||
High float64
|
||||
Low float64
|
||||
Volume float64
|
||||
Timestamp types.Time
|
||||
Open types.Number
|
||||
Close types.Number
|
||||
High types.Number
|
||||
Low types.Number
|
||||
Volume types.Number
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals JSON data into a Candle struct
|
||||
func (c *Candle) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, &[6]any{&c.Timestamp, &c.Open, &c.Close, &c.High, &c.Low, &c.Volume})
|
||||
}
|
||||
|
||||
// Leaderboard keys
|
||||
@@ -578,7 +624,7 @@ type WebsocketOrder struct {
|
||||
Status string
|
||||
Price float64
|
||||
PriceAvg float64
|
||||
Timestamp int64
|
||||
Timestamp types.Time
|
||||
Notify int
|
||||
}
|
||||
|
||||
@@ -586,7 +632,7 @@ type WebsocketOrder struct {
|
||||
type WebsocketTradeExecuted struct {
|
||||
TradeID int64
|
||||
Pair string
|
||||
Timestamp int64
|
||||
Timestamp types.Time
|
||||
OrderID int64
|
||||
AmountExecuted float64
|
||||
PriceExecuted float64
|
||||
@@ -596,7 +642,7 @@ type WebsocketTradeExecuted struct {
|
||||
type WebsocketTradeData struct {
|
||||
TradeID int64
|
||||
Pair string
|
||||
Timestamp int64
|
||||
Timestamp types.Time
|
||||
OrderID int64
|
||||
AmountExecuted float64
|
||||
PriceExecuted float64
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package bitfinex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"math"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/buger/jsonparser"
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
||||
@@ -33,6 +32,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -628,7 +628,7 @@ func (b *Bitfinex) handleWSChannelUpdate(s *subscription.Subscription, respRaw [
|
||||
case subscription.OrderbookChannel:
|
||||
return b.handleWSBookUpdate(s, d)
|
||||
case subscription.CandlesChannel:
|
||||
return b.handleWSCandleUpdate(s, d)
|
||||
return b.handleWSAllCandleUpdates(s, respRaw)
|
||||
case subscription.TickerChannel:
|
||||
return b.handleWSTickerUpdate(s, d)
|
||||
case subscription.AllTradesChannel:
|
||||
@@ -777,83 +777,48 @@ func (b *Bitfinex) handleWSBookUpdate(c *subscription.Subscription, d []any) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSCandleUpdate(c *subscription.Subscription, d []any) error {
|
||||
func (b *Bitfinex) handleWSAllCandleUpdates(c *subscription.Subscription, respRaw []byte) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("%w: Subscription param", common.ErrNilPointer)
|
||||
}
|
||||
if len(c.Pairs) != 1 {
|
||||
return subscription.ErrNotSinglePair
|
||||
}
|
||||
candleBundle, ok := d[1].([]any)
|
||||
if !ok || len(candleBundle) == 0 {
|
||||
return nil
|
||||
v, valueType, _, err := jsonparser.Get(respRaw, "[1]")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w `candlesUpdate[1]`: %w", common.ErrParsingWSField, err)
|
||||
}
|
||||
if valueType != jsonparser.Array {
|
||||
return fmt.Errorf("%w `candlesUpdate[1]`: %w %q", common.ErrParsingWSField, jsonparser.UnknownValueTypeError, valueType)
|
||||
}
|
||||
var wsCandles []Candle
|
||||
if bytes.HasPrefix(v, []byte("[[")) {
|
||||
if err := json.Unmarshal(v, &wsCandles); err != nil {
|
||||
return fmt.Errorf("error unmarshalling candle snapshot: %w", err)
|
||||
}
|
||||
} else {
|
||||
var wsCandle Candle
|
||||
if err := json.Unmarshal(v, &wsCandle); err != nil {
|
||||
return fmt.Errorf("error unmarshalling candle update: %w", err)
|
||||
}
|
||||
wsCandles = []Candle{wsCandle}
|
||||
}
|
||||
|
||||
switch candleData := candleBundle[0].(type) {
|
||||
case []any:
|
||||
for i := range candleBundle {
|
||||
var element []any
|
||||
element, ok = candleBundle[i].([]any)
|
||||
if !ok {
|
||||
return errors.New("candle type assertion for element data")
|
||||
}
|
||||
if len(element) < 6 {
|
||||
return errors.New("invalid candleBundle length")
|
||||
}
|
||||
var err error
|
||||
var klineData websocket.KlineData
|
||||
if klineData.Timestamp, err = convert.TimeFromUnixTimestampFloat(element[0]); err != nil {
|
||||
return fmt.Errorf("unable to convert candle timestamp: %w", err)
|
||||
}
|
||||
if klineData.OpenPrice, ok = element[1].(float64); !ok {
|
||||
return errors.New("unable to type assert candle open price")
|
||||
}
|
||||
if klineData.ClosePrice, ok = element[2].(float64); !ok {
|
||||
return errors.New("unable to type assert candle close price")
|
||||
}
|
||||
if klineData.HighPrice, ok = element[3].(float64); !ok {
|
||||
return errors.New("unable to type assert candle high price")
|
||||
}
|
||||
if klineData.LowPrice, ok = element[4].(float64); !ok {
|
||||
return errors.New("unable to type assert candle low price")
|
||||
}
|
||||
if klineData.Volume, ok = element[5].(float64); !ok {
|
||||
return errors.New("unable to type assert candle volume")
|
||||
}
|
||||
klineData.Exchange = b.Name
|
||||
klineData.AssetType = c.Asset
|
||||
klineData.Pair = c.Pairs[0]
|
||||
b.Websocket.DataHandler <- klineData
|
||||
klines := make([]websocket.KlineData, len(wsCandles))
|
||||
for i := range wsCandles {
|
||||
klines[i] = websocket.KlineData{
|
||||
Exchange: b.Name,
|
||||
AssetType: c.Asset,
|
||||
Pair: c.Pairs[0],
|
||||
Timestamp: wsCandles[i].Timestamp.Time(),
|
||||
OpenPrice: wsCandles[i].Open.Float64(),
|
||||
ClosePrice: wsCandles[i].Close.Float64(),
|
||||
HighPrice: wsCandles[i].High.Float64(),
|
||||
LowPrice: wsCandles[i].Low.Float64(),
|
||||
Volume: wsCandles[i].Volume.Float64(),
|
||||
}
|
||||
case float64:
|
||||
if len(candleBundle) < 6 {
|
||||
return errors.New("invalid candleBundle length")
|
||||
}
|
||||
var err error
|
||||
var klineData websocket.KlineData
|
||||
if klineData.Timestamp, err = convert.TimeFromUnixTimestampFloat(candleData); err != nil {
|
||||
return fmt.Errorf("unable to convert candle timestamp: %w", err)
|
||||
}
|
||||
if klineData.OpenPrice, ok = candleBundle[1].(float64); !ok {
|
||||
return errors.New("unable to type assert candle open price")
|
||||
}
|
||||
if klineData.ClosePrice, ok = candleBundle[2].(float64); !ok {
|
||||
return errors.New("unable to type assert candle close price")
|
||||
}
|
||||
if klineData.HighPrice, ok = candleBundle[3].(float64); !ok {
|
||||
return errors.New("unable to type assert candle high price")
|
||||
}
|
||||
if klineData.LowPrice, ok = candleBundle[4].(float64); !ok {
|
||||
return errors.New("unable to type assert candle low price")
|
||||
}
|
||||
if klineData.Volume, ok = candleBundle[5].(float64); !ok {
|
||||
return errors.New("unable to type assert candle volume")
|
||||
}
|
||||
klineData.Exchange = b.Name
|
||||
klineData.AssetType = c.Asset
|
||||
klineData.Pair = c.Pairs[0]
|
||||
b.Websocket.DataHandler <- klineData
|
||||
}
|
||||
b.Websocket.DataHandler <- klines
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -951,14 +916,14 @@ func (b *Bitfinex) handleWSAllTrades(s *subscription.Subscription, respRaw []byt
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w `tradesUpdate[1]`: %w", common.ErrParsingWSField, err)
|
||||
}
|
||||
var wsTrades []*wsTrade
|
||||
var wsTrades []*Trade
|
||||
switch valueType {
|
||||
case jsonparser.String:
|
||||
t, err := b.handleWSPublicTradeUpdate(respRaw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w `tradesUpdate[2]`: %w", common.ErrParsingWSField, err)
|
||||
}
|
||||
wsTrades = []*wsTrade{t}
|
||||
wsTrades = []*Trade{t}
|
||||
case jsonparser.Array:
|
||||
if wsTrades, err = b.handleWSPublicTradesSnapshot(v); err != nil {
|
||||
return fmt.Errorf("%w `tradesSnapshot`: %w", common.ErrParsingWSField, err)
|
||||
@@ -972,18 +937,15 @@ func (b *Bitfinex) handleWSAllTrades(s *subscription.Subscription, respRaw []byt
|
||||
Exchange: b.Name,
|
||||
AssetType: s.Asset,
|
||||
CurrencyPair: s.Pairs[0],
|
||||
TID: strconv.FormatInt(w.ID, 10),
|
||||
TID: strconv.FormatInt(w.TID, 10),
|
||||
Timestamp: w.Timestamp.Time().UTC(),
|
||||
Side: order.Buy,
|
||||
Side: w.Side,
|
||||
Amount: w.Amount,
|
||||
Price: w.Price,
|
||||
}
|
||||
if w.Period != 0 {
|
||||
t.AssetType = asset.MarginFunding
|
||||
}
|
||||
if t.Amount < 0 {
|
||||
t.Side = order.Sell
|
||||
t.Amount = math.Abs(t.Amount)
|
||||
t.Price = w.Rate
|
||||
}
|
||||
if feedEnabled {
|
||||
b.Websocket.DataHandler <- t
|
||||
@@ -995,17 +957,17 @@ func (b *Bitfinex) handleWSAllTrades(s *subscription.Subscription, respRaw []byt
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSPublicTradesSnapshot(v []byte) ([]*wsTrade, error) {
|
||||
var trades []*wsTrade
|
||||
func (b *Bitfinex) handleWSPublicTradesSnapshot(v []byte) ([]*Trade, error) {
|
||||
var trades []*Trade
|
||||
return trades, json.Unmarshal(v, &trades)
|
||||
}
|
||||
|
||||
func (b *Bitfinex) handleWSPublicTradeUpdate(respRaw []byte) (*wsTrade, error) {
|
||||
func (b *Bitfinex) handleWSPublicTradeUpdate(respRaw []byte) (*Trade, error) {
|
||||
v, _, _, err := jsonparser.Get(respRaw, "[2]")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &wsTrade{}
|
||||
t := &Trade{}
|
||||
return t, json.Unmarshal(v, t)
|
||||
}
|
||||
|
||||
@@ -1199,7 +1161,7 @@ func (b *Bitfinex) handleWSMyTradeUpdate(d []any, eventType string) error {
|
||||
if timestamp, ok = tradeData[2].(float64); !ok {
|
||||
return errors.New("unable to type assert trade timestamp")
|
||||
}
|
||||
tData.Timestamp = int64(timestamp)
|
||||
tData.Timestamp = types.Time(time.UnixMilli(int64(timestamp)))
|
||||
var orderID float64
|
||||
if orderID, ok = tradeData[3].(float64); !ok {
|
||||
return errors.New("unable to type assert trade order ID")
|
||||
|
||||
@@ -468,14 +468,14 @@ func (b *Bitfinex) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _
|
||||
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)),
|
||||
Description: *history[i].TransactionID,
|
||||
Timestamp: history[i].MTSStarted.Time(),
|
||||
Currency: history[i].Currency,
|
||||
Amount: history[i].Amount,
|
||||
Fee: history[i].Fee,
|
||||
TransferType: history[i].Type,
|
||||
CryptoToAddress: history[i].Address,
|
||||
CryptoTxID: history[i].TxID,
|
||||
Amount: history[i].Amount.Float64(),
|
||||
Fee: history[i].Fees.Float64(),
|
||||
TransferType: history[i].TransactionType,
|
||||
CryptoToAddress: history[i].DestinationAddress,
|
||||
CryptoTxID: history[i].TXID,
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
@@ -494,41 +494,38 @@ func (b *Bitfinex) GetHistoricTrades(ctx context.Context, p currency.Pair, a ass
|
||||
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, a)
|
||||
p, err := b.FormatExchangeCurrency(p, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var currString string
|
||||
currString, err = b.fixCasing(p, a)
|
||||
|
||||
currString, err := b.fixCasing(p, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []trade.Data
|
||||
ts := timestampEnd
|
||||
limit := 10000
|
||||
const limit = 10000
|
||||
allTrades:
|
||||
for {
|
||||
var tradeData []Trade
|
||||
tradeData, err = b.GetTrades(ctx,
|
||||
currString, int64(limit), 0, ts.Unix()*1000, false)
|
||||
tradeData, err := b.GetTrades(ctx, currString, limit, time.Time{}, ts, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range tradeData {
|
||||
tradeTS := time.UnixMilli(tradeData[i].Timestamp)
|
||||
tradeTS := tradeData[i].Timestamp.Time()
|
||||
if tradeTS.Before(timestampStart) && !timestampStart.IsZero() {
|
||||
break allTrades
|
||||
}
|
||||
tID := strconv.FormatInt(tradeData[i].TID, 10)
|
||||
resp = append(resp, trade.Data{
|
||||
TID: tID,
|
||||
TID: strconv.FormatInt(tradeData[i].TID, 10),
|
||||
Exchange: b.Name,
|
||||
CurrencyPair: p,
|
||||
AssetType: a,
|
||||
Price: tradeData[i].Price,
|
||||
Amount: tradeData[i].Amount,
|
||||
Timestamp: time.UnixMilli(tradeData[i].Timestamp),
|
||||
Timestamp: tradeData[i].Timestamp.Time(),
|
||||
})
|
||||
if i == len(tradeData)-1 {
|
||||
if ts.Equal(tradeTS) {
|
||||
@@ -543,8 +540,7 @@ allTrades:
|
||||
}
|
||||
}
|
||||
|
||||
err = b.AddTradesToBuffer(resp...)
|
||||
if err != nil {
|
||||
if err := b.AddTradesToBuffer(resp...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1052,7 +1048,7 @@ func (b *Bitfinex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candles, err := b.GetCandles(ctx, cf, fInterval, req.Start.UnixMilli(), req.End.UnixMilli(), req.RequestLimit, true)
|
||||
candles, err := b.GetCandles(ctx, cf, fInterval, req.Start, req.End, req.RequestLimit, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1060,12 +1056,12 @@ func (b *Bitfinex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
|
||||
timeSeries := make([]kline.Candle, len(candles))
|
||||
for x := range candles {
|
||||
timeSeries[x] = kline.Candle{
|
||||
Time: candles[x].Timestamp,
|
||||
Open: candles[x].Open,
|
||||
High: candles[x].High,
|
||||
Low: candles[x].Low,
|
||||
Close: candles[x].Close,
|
||||
Volume: candles[x].Volume,
|
||||
Time: candles[x].Timestamp.Time(),
|
||||
Open: candles[x].Open.Float64(),
|
||||
High: candles[x].High.Float64(),
|
||||
Low: candles[x].Low.Float64(),
|
||||
Close: candles[x].Close.Float64(),
|
||||
Volume: candles[x].Volume.Float64(),
|
||||
}
|
||||
}
|
||||
return req.ProcessResponse(timeSeries)
|
||||
@@ -1089,19 +1085,19 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency
|
||||
timeSeries := make([]kline.Candle, 0, req.Size())
|
||||
for x := range req.RangeHolder.Ranges {
|
||||
var candles []Candle
|
||||
candles, err = b.GetCandles(ctx, cf, fInterval, req.RangeHolder.Ranges[x].Start.Time.UnixMilli(), req.RangeHolder.Ranges[x].End.Time.UnixMilli(), req.RequestLimit, true)
|
||||
candles, err = b.GetCandles(ctx, cf, fInterval, req.RangeHolder.Ranges[x].Start.Time, req.RangeHolder.Ranges[x].End.Time, req.RequestLimit, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range candles {
|
||||
timeSeries = append(timeSeries, kline.Candle{
|
||||
Time: candles[i].Timestamp,
|
||||
Open: candles[i].Open,
|
||||
High: candles[i].High,
|
||||
Low: candles[i].Low,
|
||||
Close: candles[i].Close,
|
||||
Volume: candles[i].Volume,
|
||||
Time: candles[i].Timestamp.Time(),
|
||||
Open: candles[i].Open.Float64(),
|
||||
High: candles[i].High.Float64(),
|
||||
Low: candles[i].Low.Float64(),
|
||||
Close: candles[i].Close.Float64(),
|
||||
Volume: candles[i].Volume.Float64(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user