codebase: Cleanup various things (#1935)

* codebase: Rid base64/hex to string common funcs

* codebase: Rid local scope variable usage and other improvements

* codebase: Refactor currency pair usage across multiple exchanges

- Updated HitBTC tests to use the new currency pair format.
- Modified Kraken futures types to use currency.Pair instead of string for Symbol.
- Adjusted Kraken wrapper methods to handle currency pairs correctly.
- Refined OKX tests and types to utilize currency.Pair for instrument IDs.
- Enhanced Poloniex tests to consistently use predefined currency pairs.
- Streamlined order and orderbook tests to replace string pairs with currency.NewBTCUSD().
- Improved Yobit tests to utilize a standardized currency pair format.
- Updated validator wrapper to use currency pairs directly instead of string conversions.

* codebase: Use types.Number where possible

* refactor: update PayoutFee type to types.Number for consistency

* Refactor: Remove crypto functions to use standard library and other minor changes

- Removed custom crypto functions for SHA256, SHA512, and MD5 from the common/crypto package.
- Replaced usages of removed functions with standard library implementations in various files including:
  - cmd/websocket_client/main.go
  - engine/apiserver.go
  - exchanges/kraken/kraken.go
  - exchanges/lbank/lbank.go
  - exchanges/okx/okx_business_websocket.go
  - exchanges/kucoin/kucoin_websocket.go
  - gctscript/vm/vm.go
- Updated tests to reflect changes in the crypto functions.
- Renamed several functions for clarity, particularly in the context of order book updates across multiple exchanges.

* refactor: replace assert with require for consistency in test assertions

* refactor: Improve Binance futures candlestick test, standardise orderbook update function names and improve test parallelism

* refactor: Replace require.Len with require.Equal for better output in TestGetFuturesKlineData
This commit is contained in:
Adrian Gallagher
2025-06-12 14:12:36 +10:00
committed by GitHub
parent ce134a0a1d
commit d5ba674fc4
115 changed files with 1327 additions and 3112 deletions

View File

@@ -3,6 +3,7 @@ package bitstamp
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"net/http"
@@ -20,6 +21,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/types"
)
const (
@@ -170,9 +172,9 @@ func (b *Bitstamp) GetTicker(ctx context.Context, currency string, hourly bool)
// the amount.
func (b *Bitstamp) GetOrderbook(ctx context.Context, currency string) (*Orderbook, error) {
type response struct {
Timestamp int64 `json:"timestamp,string"`
Bids [][2]string `json:"bids"`
Asks [][2]string `json:"asks"`
Timestamp types.Time `json:"timestamp"`
Bids [][2]types.Number `json:"bids"`
Asks [][2]types.Number `json:"asks"`
}
path := "/v" + bitstampAPIVersion + "/" + bitstampAPIOrderbook + "/" + strings.ToLower(currency) + "/"
@@ -182,37 +184,23 @@ func (b *Bitstamp) GetOrderbook(ctx context.Context, currency string) (*Orderboo
return nil, err
}
orderbook := Orderbook{
Timestamp: resp.Timestamp,
ob := &Orderbook{
Timestamp: resp.Timestamp.Time(),
Bids: make([]OrderbookBase, len(resp.Bids)),
Asks: make([]OrderbookBase, len(resp.Asks)),
}
for x := range resp.Bids {
price, err := strconv.ParseFloat(resp.Bids[x][0], 64)
if err != nil {
return nil, err
}
amount, err := strconv.ParseFloat(resp.Bids[x][1], 64)
if err != nil {
return nil, err
}
orderbook.Bids[x] = OrderbookBase{price, amount}
ob.Bids[x].Price = resp.Bids[x][0].Float64()
ob.Bids[x].Amount = resp.Bids[x][1].Float64()
}
for x := range resp.Asks {
price, err := strconv.ParseFloat(resp.Asks[x][0], 64)
if err != nil {
return nil, err
}
amount, err := strconv.ParseFloat(resp.Asks[x][1], 64)
if err != nil {
return nil, err
}
orderbook.Asks[x] = OrderbookBase{price, amount}
ob.Asks[x].Price = resp.Asks[x][0].Float64()
ob.Asks[x].Amount = resp.Asks[x][1].Float64()
}
return &orderbook, nil
return ob, nil
}
// GetTradingPairs returns a list of trading pairs which Bitstamp
@@ -244,7 +232,7 @@ func (b *Bitstamp) GetEURUSDConversionRate(ctx context.Context) (EURUSDConversio
// GetBalance returns full balance of currency held on the exchange
func (b *Bitstamp) GetBalance(ctx context.Context) (Balances, error) {
var balance map[string]string
var balance map[string]types.Number
err := b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPIBalance, true, nil, &balance)
if err != nil {
return nil, err
@@ -259,15 +247,11 @@ func (b *Bitstamp) GetBalance(ctx context.Context) (Balances, error) {
balances := make(map[string]Balance)
for _, curr := range currs {
avail, _ := strconv.ParseFloat(balance[curr+"_available"], 64)
bal, _ := strconv.ParseFloat(balance[curr+"_balance"], 64)
reserved, _ := strconv.ParseFloat(balance[curr+"_reserved"], 64)
withdrawalFee, _ := strconv.ParseFloat(balance[curr+"_withdrawal_fee"], 64)
currBalance := Balance{
Available: avail,
Balance: bal,
Reserved: reserved,
WithdrawalFee: withdrawalFee,
Available: balance[curr+"_available"].Float64(),
Balance: balance[curr+"_balance"].Float64(),
Reserved: balance[curr+"_reserved"].Float64(),
WithdrawalFee: balance[curr+"_withdrawal_fee"].Float64(),
}
balances[strings.ToUpper(curr)] = currBalance
}
@@ -277,19 +261,19 @@ func (b *Bitstamp) GetBalance(ctx context.Context) (Balances, error) {
// GetUserTransactions returns an array of transactions
func (b *Bitstamp) GetUserTransactions(ctx context.Context, currencyPair string) ([]UserTransactions, error) {
type Response struct {
Date string `json:"datetime"`
TransactionID int64 `json:"id"`
Type int `json:"type,string"`
USD any `json:"usd"`
EUR any `json:"eur"`
XRP any `json:"xrp"`
BTC any `json:"btc"`
BTCUSD any `json:"btc_usd"`
Fee float64 `json:"fee,string"`
OrderID int64 `json:"order_id"`
Date string `json:"datetime"`
TransactionID int64 `json:"id"`
Type int64 `json:"type,string"`
USD types.Number `json:"usd"`
EUR types.Number `json:"eur"`
XRP types.Number `json:"xrp"`
BTC types.Number `json:"btc"`
BTCUSD types.Number `json:"btc_usd"`
Fee float64 `json:"fee,string"`
OrderID int64 `json:"order_id"`
}
var response []Response
var response []Response
if currencyPair == "" {
if err := b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPIUserTransactions,
true,
@@ -306,32 +290,20 @@ func (b *Bitstamp) GetUserTransactions(ctx context.Context, currencyPair string)
}
}
processNumber := func(i any) float64 {
switch t := i.(type) {
case float64:
return t
case string:
amt, _ := strconv.ParseFloat(t, 64)
return amt
default:
return 0
}
}
transactions := make([]UserTransactions, len(response))
for x := range response {
tx := UserTransactions{}
tx.Date = response[x].Date
tx.TransactionID = response[x].TransactionID
tx.Type = response[x].Type
tx.EUR = processNumber(response[x].EUR)
tx.XRP = processNumber(response[x].XRP)
tx.USD = processNumber(response[x].USD)
tx.BTC = processNumber(response[x].BTC)
tx.BTCUSD = processNumber(response[x].BTCUSD)
tx.Fee = response[x].Fee
tx.OrderID = response[x].OrderID
transactions[x] = tx
transactions[x] = UserTransactions{
Date: response[x].Date,
TransactionID: response[x].TransactionID,
Type: response[x].Type,
EUR: response[x].EUR.Float64(),
XRP: response[x].XRP.Float64(),
USD: response[x].USD.Float64(),
BTC: response[x].BTC.Float64(),
BTCUSD: response[x].BTCUSD.Float64(),
Fee: response[x].Fee,
OrderID: response[x].OrderID,
}
}
return transactions, nil
@@ -600,15 +572,12 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange
values.Set("key", creds.Key)
values.Set("nonce", n)
var hmac []byte
hmac, err = crypto.GetHMAC(crypto.HashSHA256,
[]byte(n+creds.ClientID+creds.Key),
[]byte(creds.Secret))
hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(n+creds.ClientID+creds.Key), []byte(creds.Secret))
if err != nil {
return nil, err
}
values.Set("signature", strings.ToUpper(crypto.HexEncodeToString(hmac)))
values.Set("signature", strings.ToUpper(hex.EncodeToString(hmac)))
var fullPath string
if v2 {

View File

@@ -391,7 +391,7 @@ func TestGetOrderStatus(t *testing.T) {
assert.Equal(t, "2022-01-31 14:43:15", o.DateTime, "DateTime should match")
assert.Equal(t, "1458532827766784", o.ID, "OrderID should match")
assert.Equal(t, 200.00, o.AmountRemaining, "AmountRemaining should match")
assert.Equal(t, 0, o.Type, "Type should match")
assert.Equal(t, int64(0), o.Type, "Type should match")
assert.Equal(t, "0.50000000", o.ClientOrderID, "ClientOrderID should match")
assert.Equal(t, "BTC/USD", o.Market, "Market should match")
for _, tr := range o.Transactions {
@@ -399,7 +399,7 @@ func TestGetOrderStatus(t *testing.T) {
assert.Equal(t, 50.00, tr.Price, "Price should match")
assert.Equal(t, 101.00, tr.FromCurrency, "FromCurrency should match")
assert.Equal(t, 1.0, tr.ToCurrency, "ToCurrency should match")
assert.Equal(t, 0, o.Type, "Type should match")
assert.Equal(t, int64(0), o.Type, "Type should match")
}
}
}
@@ -863,11 +863,10 @@ func TestWsRequestReconnect(t *testing.T) {
require.NoError(t, err, "WsRequestReconnect must not error")
}
func TestBitstamp_OHLC(t *testing.T) {
start := time.Unix(1546300800, 0)
end := time.Unix(1577836799, 0)
o, err := b.OHLC(t.Context(), "btcusd", start, end, "60", "10")
require.NoError(t, err, "TestBitstamp_OHLC must not error")
func TestOHLC(t *testing.T) {
t.Parallel()
o, err := b.OHLC(t.Context(), "btcusd", time.Unix(1546300800, 0), time.Unix(1577836799, 0), "60", "10")
require.NoError(t, err, "OHLC must not error")
assert.Equal(t, "BTC/USD", o.Data.Pair, "Pair should be correct")
for _, req := range o.Data.OHLCV {
assert.Positive(t, req.Low, "Low should be positive")
@@ -878,10 +877,9 @@ func TestBitstamp_OHLC(t *testing.T) {
}
}
func TestBitstamp_GetHistoricCandles(t *testing.T) {
start := time.Unix(1546300800, 0)
end := time.Unix(1577836799, 0)
c, err := b.GetHistoricCandles(t.Context(), btcusdPair, asset.Spot, kline.OneDay, start, end)
func TestGetHistoricCandles(t *testing.T) {
t.Parallel()
c, err := b.GetHistoricCandles(t.Context(), btcusdPair, asset.Spot, kline.OneDay, time.Unix(1546300800, 0), time.Unix(1577836799, 0))
require.NoError(t, err, "GetHistoricCandles must not error")
assert.Equal(t, btcusdPair, c.Pair, "Pair should be correct")
assert.NotEmpty(t, c, "Candles should not be empty")
@@ -895,11 +893,9 @@ func TestBitstamp_GetHistoricCandles(t *testing.T) {
}
}
func TestBitstamp_GetHistoricCandlesExtended(t *testing.T) {
start := time.Unix(1546300800, 0)
end := time.Unix(1577836799, 0)
c, err := b.GetHistoricCandlesExtended(t.Context(), btcusdPair, asset.Spot, kline.OneDay, start, end)
func TestGetHistoricCandlesExtended(t *testing.T) {
t.Parallel()
c, err := b.GetHistoricCandlesExtended(t.Context(), btcusdPair, asset.Spot, kline.OneDay, time.Unix(1546300800, 0), time.Unix(1577836799, 0))
require.NoError(t, err, "GetHistoricCandlesExtended must not error")
assert.Equal(t, btcusdPair, c.Pair, "Pair should be correct")
assert.NotEmpty(t, c, "Candles should not be empty")

View File

@@ -1,6 +1,8 @@
package bitstamp
import (
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/types"
)
@@ -43,7 +45,7 @@ type OrderbookBase struct {
// Orderbook holds orderbook information
type Orderbook struct {
Timestamp int64 `json:"timestamp,string"`
Timestamp time.Time
Bids []OrderbookBase
Asks []OrderbookBase
}
@@ -101,7 +103,7 @@ type Balances map[string]Balance
type UserTransactions struct {
Date string `json:"datetime"`
TransactionID int64 `json:"id"`
Type int `json:"type,string"`
Type int64 `json:"type,string"`
USD float64 `json:"usd"`
EUR float64 `json:"eur"`
BTC float64 `json:"btc"`
@@ -128,7 +130,7 @@ type Order struct {
// OrderStatus holds order status information
type OrderStatus struct {
AmountRemaining float64 `json:"amount_remaining,string"`
Type int `json:"type"`
Type int64 `json:"type"`
ID string `json:"id"`
DateTime string `json:"datetime"`
Status string `json:"status"`
@@ -251,10 +253,10 @@ type websocketOrderBookResponse struct {
}
type websocketOrderBook struct {
Asks [][2]string `json:"asks"`
Bids [][2]string `json:"bids"`
Timestamp int64 `json:"timestamp,string"`
Microtimestamp int64 `json:"microtimestamp,string"`
Asks [][2]types.Number `json:"asks"`
Bids [][2]types.Number `json:"bids"`
Timestamp types.Time `json:"timestamp"`
Microtimestamp types.Time `json:"microtimestamp"`
}
// OHLCResponse holds returned candle data
@@ -262,12 +264,12 @@ type OHLCResponse struct {
Data struct {
Pair string `json:"pair"`
OHLCV []struct {
Timestamp int64 `json:"timestamp,string"`
Open float64 `json:"open,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Close float64 `json:"close,string"`
Volume float64 `json:"volume,string"`
Timestamp types.Time `json:"timestamp"`
Open float64 `json:"open,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Close float64 `json:"close,string"`
Volume float64 `json:"volume,string"`
} `json:"ohlc"`
} `json:"data"`
}

View File

@@ -291,47 +291,28 @@ func (b *Bitstamp) handleWSOrderbook(msg []byte) error {
return err
}
wsOrderBookResp := websocketOrderBookResponse{}
var wsOrderBookResp websocketOrderBookResponse
if err := json.Unmarshal(msg, &wsOrderBookResp); err != nil {
return err
}
update := &wsOrderBookResp.Data
if len(update.Asks) == 0 && len(update.Bids) == 0 {
return errors.New("no orderbook data")
}
obUpdate := &orderbook.Base{
Bids: make(orderbook.Tranches, len(update.Bids)),
Asks: make(orderbook.Tranches, len(update.Asks)),
Bids: make(orderbook.Tranches, len(wsOrderBookResp.Data.Bids)),
Asks: make(orderbook.Tranches, len(wsOrderBookResp.Data.Asks)),
Pair: p,
LastUpdated: time.UnixMicro(update.Microtimestamp),
LastUpdated: wsOrderBookResp.Data.Microtimestamp.Time(),
Asset: asset.Spot,
Exchange: b.Name,
VerifyOrderbook: b.CanVerifyOrderbook,
}
for i := range update.Asks {
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
if err != nil {
return err
}
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
if err != nil {
return err
}
obUpdate.Asks[i] = orderbook.Tranche{Price: target, Amount: amount}
for i := range wsOrderBookResp.Data.Asks {
obUpdate.Asks[i].Price = wsOrderBookResp.Data.Asks[i][0].Float64()
obUpdate.Asks[i].Amount = wsOrderBookResp.Data.Asks[i][1].Float64()
}
for i := range update.Bids {
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
if err != nil {
return err
}
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
if err != nil {
return err
}
obUpdate.Bids[i] = orderbook.Tranche{Price: target, Amount: amount}
for i := range wsOrderBookResp.Data.Bids {
obUpdate.Bids[i].Price = wsOrderBookResp.Data.Bids[i][0].Float64()
obUpdate.Bids[i].Amount = wsOrderBookResp.Data.Bids[i][1].Float64()
}
filterOrderbookZeroBidPrice(obUpdate)
return b.Websocket.Orderbook.LoadSnapshot(obUpdate)
@@ -360,7 +341,7 @@ func (b *Bitstamp) seedOrderBook(ctx context.Context) error {
VerifyOrderbook: b.CanVerifyOrderbook,
Bids: make(orderbook.Tranches, len(orderbookSeed.Bids)),
Asks: make(orderbook.Tranches, len(orderbookSeed.Asks)),
LastUpdated: time.Unix(orderbookSeed.Timestamp, 0),
LastUpdated: orderbookSeed.Timestamp,
}
for i := range orderbookSeed.Asks {

View File

@@ -819,7 +819,7 @@ func (b *Bitstamp) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
timeSeries := make([]kline.Candle, 0, len(candles.Data.OHLCV))
for x := range candles.Data.OHLCV {
timestamp := time.Unix(candles.Data.OHLCV[x].Timestamp, 0)
timestamp := candles.Data.OHLCV[x].Timestamp.Time()
if timestamp.Before(req.Start) || timestamp.After(req.End) {
continue
}
@@ -857,13 +857,13 @@ func (b *Bitstamp) GetHistoricCandlesExtended(ctx context.Context, pair currency
}
for i := range candles.Data.OHLCV {
timestamp := time.Unix(candles.Data.OHLCV[i].Timestamp, 0)
timestamp := candles.Data.OHLCV[i].Timestamp.Time()
if timestamp.Before(req.RangeHolder.Ranges[x].Start.Time) ||
timestamp.After(req.RangeHolder.Ranges[x].End.Time) {
continue
}
timeSeries = append(timeSeries, kline.Candle{
Time: time.Unix(candles.Data.OHLCV[i].Timestamp, 0),
Time: timestamp,
Open: candles.Data.OHLCV[i].Open,
High: candles.Data.OHLCV[i].High,
Low: candles.Data.OHLCV[i].Low,