mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Deribit: bug fixes, test fixes and implement GetCurrencyTradeURL (#1558)
* initial * fixes WS instrument parsing, adds new funcs * lint, err restore, funding rate fixes * tightens function * fix breaking test, reimplement option string * fixes klines * enhance regex * WHOOPS * verbose whoops * exchange interval for basic too * Adds err processing, err channel, fix the others * utilises concurrent error grabber * minor shrinkage, its cold
This commit is contained in:
@@ -28,11 +28,25 @@ type Deribit struct {
|
||||
exchange.Base
|
||||
}
|
||||
|
||||
var (
|
||||
// optionRegex compiles optionDecimalRegex at startup and is used to help set
|
||||
// option currency lower-case d eg MATIC-USDC-3JUN24-0d64-P
|
||||
optionRegex *regexp.Regexp
|
||||
)
|
||||
|
||||
const (
|
||||
deribitAPIVersion = "/api/v2"
|
||||
tradeBaseURL = "https://www.deribit.com/"
|
||||
tradeSpot = "spot/"
|
||||
tradeFutures = "futures/"
|
||||
tradeOptions = "options/"
|
||||
tradeFuturesCombo = "futures-spreads/"
|
||||
tradeOptionsCombo = "combos/"
|
||||
|
||||
perpString = "PERPETUAL"
|
||||
optionDecimalRegex = `\d+(D)\d+`
|
||||
|
||||
// Public endpoints
|
||||
|
||||
// Market Data
|
||||
getBookByCurrency = "public/get_book_summary_by_currency"
|
||||
getBookByInstrument = "public/get_book_summary_by_instrument"
|
||||
@@ -2682,10 +2696,51 @@ func (d *Deribit) StringToAssetKind(assetType string) (asset.Item, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func guessAssetTypeFromInstrument(currencyPair currency.Pair) (asset.Item, error) {
|
||||
// getAssetPairByInstrument is able to determine the asset type and currency pair
|
||||
// based on the received instrument ID
|
||||
func (d *Deribit) getAssetPairByInstrument(instrument string) (currency.Pair, asset.Item, error) {
|
||||
if instrument == "" {
|
||||
return currency.EMPTYPAIR, asset.Empty, errInvalidInstrumentName
|
||||
}
|
||||
|
||||
var item asset.Item
|
||||
// Find the first occurrence of the delimiter and split the instrument string accordingly
|
||||
parts := strings.Split(instrument, currency.DashDelimiter)
|
||||
switch {
|
||||
case len(parts) == 1:
|
||||
if i := strings.IndexAny(instrument, currency.UnderscoreDelimiter); i == -1 {
|
||||
return currency.EMPTYPAIR, asset.Empty, fmt.Errorf("%w %s", errUnsupportedInstrumentFormat, instrument)
|
||||
}
|
||||
item = asset.Spot
|
||||
case len(parts) == 2:
|
||||
item = asset.Futures
|
||||
case parts[len(parts)-1] == "C" || parts[len(parts)-1] == "P":
|
||||
item = asset.Options
|
||||
case len(parts) >= 3:
|
||||
// Check for options or other types
|
||||
switch parts[1] {
|
||||
case "USDC", "USDT":
|
||||
item = asset.Futures
|
||||
case "FS":
|
||||
item = asset.FutureCombo
|
||||
default:
|
||||
item = asset.OptionCombo
|
||||
}
|
||||
default:
|
||||
return currency.EMPTYPAIR, asset.Empty, fmt.Errorf("%w %s", errUnsupportedInstrumentFormat, instrument)
|
||||
}
|
||||
cp, err := currency.NewPairFromString(instrument)
|
||||
if err != nil {
|
||||
return currency.EMPTYPAIR, asset.Empty, err
|
||||
}
|
||||
|
||||
return cp, item, nil
|
||||
}
|
||||
|
||||
func getAssetFromPair(currencyPair currency.Pair) (asset.Item, error) {
|
||||
currencyPairString := currencyPair.String()
|
||||
vals := strings.Split(currencyPairString, currency.DashDelimiter)
|
||||
if strings.HasSuffix(currencyPairString, "PERPETUAL") || len(vals) == 2 {
|
||||
if strings.HasSuffix(currencyPairString, perpString) || len(vals) == 2 {
|
||||
return asset.Futures, nil
|
||||
} else if len(vals) == 1 {
|
||||
if vals = strings.Split(vals[0], currency.UnderscoreDelimiter); len(vals) == 2 {
|
||||
@@ -2721,7 +2776,7 @@ func guessAssetTypeFromInstrument(currencyPair currency.Pair) (asset.Item, error
|
||||
}
|
||||
|
||||
func calculateTradingFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
assetType, err := guessAssetTypeFromInstrument(feeBuilder.Pair)
|
||||
assetType, err := getAssetFromPair(feeBuilder.Pair)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -2735,7 +2790,7 @@ func calculateTradingFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0005, nil
|
||||
case strings.HasPrefix(feeBuilder.Pair.String(), currencyBTC),
|
||||
strings.HasPrefix(feeBuilder.Pair.String(), currencyETH):
|
||||
if strings.HasSuffix(feeBuilder.Pair.String(), "PERPETUAL") {
|
||||
if strings.HasSuffix(feeBuilder.Pair.String(), perpString) {
|
||||
if feeBuilder.IsMaker {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -2786,10 +2841,16 @@ func (d *Deribit) formatFuturesTradablePair(pair currency.Pair) string {
|
||||
// it has both uppercase or lowercase characters, which we can not achieve with the Upper=true or Upper=false
|
||||
func (d *Deribit) optionPairToString(pair currency.Pair) string {
|
||||
subCodes := strings.Split(pair.Quote.String(), currency.DashDelimiter)
|
||||
if len(subCodes) == 3 {
|
||||
if match, err := regexp.MatchString(`^[a-zA-Z0-9_]*$`, subCodes[1]); match && err == nil {
|
||||
subCodes[1] = strings.ToLower(subCodes[1])
|
||||
initialDelimiter := currency.DashDelimiter
|
||||
if subCodes[0] == "USDC" {
|
||||
initialDelimiter = currency.UnderscoreDelimiter
|
||||
}
|
||||
for i := range subCodes {
|
||||
if match := optionRegex.MatchString(subCodes[i]); match {
|
||||
subCodes[i] = strings.ToLower(subCodes[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
return pair.Base.String() + currency.DashDelimiter + strings.Join(subCodes, currency.DashDelimiter)
|
||||
|
||||
return pair.Base.String() + initialDelimiter + strings.Join(subCodes, currency.DashDelimiter)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package deribit
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -47,7 +46,7 @@ var (
|
||||
d = &Deribit{}
|
||||
optionsTradablePair, optionComboTradablePair, futureComboTradablePair currency.Pair
|
||||
spotTradablePair = currency.NewPairWithDelimiter(currencyBTC, "USDC", "_")
|
||||
futuresTradablePair = currency.NewPairWithDelimiter(currencyBTC, "PERPETUAL", "-")
|
||||
futuresTradablePair = currency.NewPairWithDelimiter(currencyBTC, perpString, "-")
|
||||
assetTypeToPairsMap map[asset.Item]currency.Pair
|
||||
)
|
||||
|
||||
@@ -90,6 +89,39 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func instantiateTradablePairs() {
|
||||
if err := d.UpdateTradablePairs(context.Background(), true); err != nil {
|
||||
log.Fatalf("Failed to update tradable pairs. Error: %v", err)
|
||||
}
|
||||
|
||||
handleError := func(err error, msg string) {
|
||||
if err != nil {
|
||||
log.Fatalf("%s. Error: %v", msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
updateTradablePair := func(assetType asset.Item, tradablePair *currency.Pair) {
|
||||
if d.CurrencyPairs.IsAssetEnabled(assetType) == nil {
|
||||
pairs, err := d.GetEnabledPairs(assetType)
|
||||
handleError(err, fmt.Sprintf("Failed to get enabled pairs for asset type %v", assetType))
|
||||
|
||||
if len(pairs) == 0 {
|
||||
handleError(currency.ErrCurrencyPairsEmpty, fmt.Sprintf("No enabled pairs for asset type %v", assetType))
|
||||
}
|
||||
|
||||
if assetType == asset.Options {
|
||||
*tradablePair, err = d.FormatExchangeCurrency(pairs[0], assetType)
|
||||
handleError(err, "Failed to format exchange currency for options pair")
|
||||
} else {
|
||||
*tradablePair = pairs[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTradablePair(asset.Options, &optionsTradablePair)
|
||||
updateTradablePair(asset.OptionCombo, &optionComboTradablePair)
|
||||
updateTradablePair(asset.FutureCombo, &futureComboTradablePair)
|
||||
}
|
||||
|
||||
func TestUpdateTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := d.UpdateTicker(context.Background(), currency.Pair{}, asset.Margin)
|
||||
@@ -97,8 +129,8 @@ func TestUpdateTicker(t *testing.T) {
|
||||
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.UpdateTicker(context.Background(), cp, assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +138,7 @@ func TestUpdateOrderbook(t *testing.T) {
|
||||
t.Parallel()
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.UpdateOrderbook(context.Background(), cp, assetType)
|
||||
require.NoErrorf(t, err, "%w for asset type: %v", err, assetType)
|
||||
require.NoErrorf(t, err, "asset type: %v", assetType)
|
||||
require.NotNil(t, result)
|
||||
}
|
||||
}
|
||||
@@ -115,11 +147,9 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := d.GetHistoricTrades(context.Background(), futureComboTradablePair, asset.FutureCombo, time.Now().Add(-time.Minute*10), time.Now())
|
||||
require.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
var result []trade.Data
|
||||
for assetType, cp := range map[asset.Item]currency.Pair{asset.Spot: spotTradablePair, asset.Futures: futuresTradablePair} {
|
||||
result, err = d.GetHistoricTrades(context.Background(), cp, assetType, time.Now().Add(-time.Minute*10), time.Now())
|
||||
require.NoErrorf(t, err, "%w asset type: %v", err, assetType)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for asset type: %s", err, assetType.String())
|
||||
_, err = d.GetHistoricTrades(context.Background(), cp, assetType, time.Now().Add(-time.Minute*10), time.Now())
|
||||
require.NoErrorf(t, err, "asset type: %v", assetType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +157,8 @@ func TestFetchRecentTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.GetRecentTrades(context.Background(), cp, assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +221,8 @@ func TestSubmitOrder(t *testing.T) {
|
||||
var info *InstrumentData
|
||||
for assetType, cp := range assetToPairStringMap {
|
||||
info, err = d.GetInstrument(context.Background(), d.formatPairString(assetType, cp))
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
|
||||
result, err = d.SubmitOrder(
|
||||
context.Background(),
|
||||
@@ -206,8 +236,8 @@ func TestSubmitOrder(t *testing.T) {
|
||||
Pair: cp,
|
||||
},
|
||||
)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +258,7 @@ func TestGetMarkPriceHistory(t *testing.T) {
|
||||
} {
|
||||
result, err = d.GetMarkPriceHistory(context.Background(), ps, time.Now().Add(-5*time.Minute), time.Now())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps)
|
||||
require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps)
|
||||
require.NotNilf(t, result, "expected result not to be nil for pair %s", ps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +276,7 @@ func TestWSRetrieveMarkPriceHistory(t *testing.T) {
|
||||
} {
|
||||
result, err = d.WSRetrieveMarkPriceHistory(ps, time.Now().Add(-4*time.Hour), time.Now())
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, ps)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for pair: %v", ps)
|
||||
require.NotNilf(t, result, "expected value not to be nil for pair: %v", ps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +320,7 @@ func TestGetBookSummaryByInstrument(t *testing.T) {
|
||||
} {
|
||||
result, err = d.GetBookSummaryByInstrument(context.Background(), ps)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps)
|
||||
require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps)
|
||||
require.NotNilf(t, result, "expected result not to be nil for pair %s", ps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +338,7 @@ func TestWSRetrieveBookSummaryByInstrument(t *testing.T) {
|
||||
} {
|
||||
result, err = d.WSRetrieveBookSummaryByInstrument(ps)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps)
|
||||
require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps)
|
||||
require.NotNilf(t, result, "expected result not to be nil for pair %s", ps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,8 +533,8 @@ func TestGetInstrumentData(t *testing.T) {
|
||||
var result *InstrumentData
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.GetInstrument(context.Background(), d.formatPairString(assetType, cp))
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,8 +546,8 @@ func TestWSRetrieveInstrumentData(t *testing.T) {
|
||||
var result *InstrumentData
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.WSRetrieveInstrumentData(d.formatPairString(assetType, cp))
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,7 +668,7 @@ func TestGetLastTradesByInstrument(t *testing.T) {
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.GetLastTradesByInstrument(context.Background(), d.formatPairString(assetType, cp), "30500", "31500", "desc", 0, true)
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency asset %v pair %v", nil, err, assetType, cp)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for asset %v pair: %v", assetType, cp)
|
||||
require.NotNilf(t, result, "expected value not to be nil for asset %v pair: %v", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +680,7 @@ func TestWSRetrieveLastTradesByInstrument(t *testing.T) {
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.WSRetrieveLastTradesByInstrument(d.formatPairString(assetType, cp), "30500", "31500", "desc", 0, true)
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency asset %v pair %v", nil, err, assetType, cp)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for asset %v pair: %v", assetType, cp)
|
||||
require.NotNilf(t, result, "expected value not to be nil for asset %v pair: %v", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +692,7 @@ func TestGetLastTradesByInstrumentAndTime(t *testing.T) {
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.GetLastTradesByInstrumentAndTime(context.Background(), d.formatPairString(assetType, cp), "", 0, time.Now().Add(-8*time.Hour), time.Now())
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
|
||||
require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +704,7 @@ func TestWSRetrieveLastTradesByInstrumentAndTime(t *testing.T) {
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.WSRetrieveLastTradesByInstrumentAndTime(d.formatPairString(assetType, cp), "", 0, true, time.Now().Add(-8*time.Hour), time.Now())
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
|
||||
require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,7 +717,7 @@ func TestGetOrderbookData(t *testing.T) {
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.GetOrderbook(context.Background(), d.formatPairString(assetType, cp), 0)
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
|
||||
require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,7 +733,7 @@ func TestWSRetrieveOrderbookData(t *testing.T) {
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.WSRetrieveOrderbookData(d.formatPairString(assetType, cp), 0)
|
||||
require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
|
||||
require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
|
||||
require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3249,8 +3279,8 @@ func TestFetchTicker(t *testing.T) {
|
||||
var err error
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.FetchTicker(context.Background(), cp, assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3260,8 +3290,8 @@ func TestFetchOrderbook(t *testing.T) {
|
||||
var err error
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.FetchOrderbook(context.Background(), cp, assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s", assetType.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s", assetType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3279,8 +3309,8 @@ func TestFetchAccountInfo(t *testing.T) {
|
||||
assetTypes := d.GetAssetTypes(true)
|
||||
for _, assetType := range assetTypes {
|
||||
result, err := d.FetchAccountInfo(context.Background(), assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s", assetType.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s", assetType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3306,8 +3336,8 @@ func TestGetRecentTrades(t *testing.T) {
|
||||
var err error
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err = d.GetRecentTrades(context.Background(), cp, assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3337,8 +3367,8 @@ func TestCancelAllOrders(t *testing.T) {
|
||||
orderCancellation.AssetType = assetType
|
||||
orderCancellation.Pair = cp
|
||||
result, err = d.CancelAllOrders(context.Background(), orderCancellation)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3347,8 +3377,8 @@ func TestGetOrderInfo(t *testing.T) {
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
|
||||
for assetType, cp := range assetTypeToPairsMap {
|
||||
result, err := d.GetOrderInfo(context.Background(), "1234", cp, assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3389,8 +3419,8 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
getOrdersRequest.Pairs = []currency.Pair{cp}
|
||||
getOrdersRequest.AssetType = assetType
|
||||
result, err := d.GetActiveOrders(context.Background(), &getOrdersRequest)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3402,24 +3432,24 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
Type: order.AnyType, AssetType: assetType,
|
||||
Side: order.AnySide, Pairs: []currency.Pair{cp},
|
||||
})
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp)
|
||||
require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuessAssetTypeFromInstrument(t *testing.T) {
|
||||
func TestGetAssetFromPair(t *testing.T) {
|
||||
var assetTypeNew asset.Item
|
||||
for _, assetType := range []asset.Item{asset.Spot, asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} {
|
||||
availablePairs, err := d.GetEnabledPairs(assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String())
|
||||
require.NotNilf(t, availablePairs, "Expected result not to be nil for asset type %s", assetType.String())
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType)
|
||||
require.NotNilf(t, availablePairs, "expected result not to be nil for asset type %s", assetType)
|
||||
|
||||
format, err := d.GetPairFormat(assetType, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
for id, cp := range availablePairs {
|
||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
||||
assetTypeNew, err = guessAssetTypeFromInstrument(cp.Format(format))
|
||||
assetTypeNew, err = getAssetFromPair(cp.Format(format))
|
||||
require.Equalf(t, assetType, assetTypeNew, "expected %s, but found %s for pair string %s", assetType.String(), assetTypeNew.String(), cp.Format(format))
|
||||
})
|
||||
}
|
||||
@@ -3427,10 +3457,38 @@ func TestGuessAssetTypeFromInstrument(t *testing.T) {
|
||||
|
||||
cp, err := currency.NewPairFromString("some_thing_else")
|
||||
require.NoError(t, err)
|
||||
_, err = guessAssetTypeFromInstrument(cp)
|
||||
_, err = getAssetFromPair(cp)
|
||||
assert.ErrorIs(t, err, errUnsupportedInstrumentFormat)
|
||||
}
|
||||
|
||||
func TestGetAssetPairByInstrument(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, assetType := range []asset.Item{asset.Spot, asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} {
|
||||
availablePairs, err := d.GetAvailablePairs(assetType)
|
||||
require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType)
|
||||
require.NotNilf(t, availablePairs, "expected result not to be nil for asset type %s", assetType)
|
||||
for _, cp := range availablePairs {
|
||||
t.Run(fmt.Sprintf("%s %s", assetType, cp), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
extractedPair, extractedAsset, err := d.getAssetPairByInstrument(cp.String())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, cp.String(), extractedPair.String())
|
||||
assert.Equal(t, assetType.String(), extractedAsset.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
t.Run("empty asset, empty pair", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, _, err := d.getAssetPairByInstrument("")
|
||||
assert.ErrorIs(t, err, errInvalidInstrumentName)
|
||||
})
|
||||
t.Run("thisIsAFakeCurrency", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, _, err := d.getAssetPairByInstrument("thisIsAFakeCurrency")
|
||||
assert.ErrorIs(t, err, errUnsupportedInstrumentFormat)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
|
||||
var feeBuilder = &exchange.FeeBuilder{
|
||||
Amount: 1,
|
||||
@@ -3445,9 +3503,9 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
if !sharedtestvalues.AreAPICredentialsSet(d) {
|
||||
assert.Equalf(t, exchange.OfflineTradeFee, feeBuilder.FeeType, "Expected %f, received %f", exchange.OfflineTradeFee, feeBuilder.FeeType)
|
||||
assert.Equalf(t, exchange.OfflineTradeFee, feeBuilder.FeeType, "expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType)
|
||||
} else {
|
||||
assert.Equalf(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType, "Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType)
|
||||
assert.Equalf(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType, "expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3594,24 +3652,47 @@ var websocketPushData = map[string]string{
|
||||
|
||||
func TestProcessPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
for x := range websocketPushData {
|
||||
err := d.wsHandleData([]byte(websocketPushData[x]))
|
||||
require.NoErrorf(t, err, "%s: Received unexpected error for", x)
|
||||
for k, v := range websocketPushData {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := d.wsHandleData([]byte(v))
|
||||
require.NoErrorf(t, err, "%s: Received unexpected error for", k)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatFuturesTradablePair(t *testing.T) {
|
||||
t.Parallel()
|
||||
futuresInstrumentsOutputList := map[currency.Pair]string{
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode("PERPETUAL")}: "BTC-PERPETUAL",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode(perpString)}: "BTC-PERPETUAL",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.AVAX, Quote: currency.NewCode("USDC-PERPETUAL")}: "AVAX_USDC-PERPETUAL",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.ETH, Quote: currency.NewCode("30DEC22")}: "ETH-30DEC22",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.SOL, Quote: currency.NewCode("30DEC22")}: "SOL-30DEC22",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.NewCode("BTCDVOL"), Quote: currency.NewCode("USDC-28JUN23")}: "BTCDVOL_USDC-28JUN23",
|
||||
}
|
||||
for pair, instrumentID := range futuresInstrumentsOutputList {
|
||||
instrument := d.formatFuturesTradablePair(pair)
|
||||
require.Equal(t, instrumentID, instrument)
|
||||
t.Run(instrumentID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
instrument := d.formatFuturesTradablePair(pair)
|
||||
require.Equal(t, instrumentID, instrument)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionPairToString(t *testing.T) {
|
||||
t.Parallel()
|
||||
optionsList := map[currency.Pair]string{
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode("30MAY24-61000-C")}: "BTC-30MAY24-61000-C",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.ETH, Quote: currency.NewCode("1JUN24-3200-P")}: "ETH-1JUN24-3200-P",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.SOL, Quote: currency.NewCode("USDC-31MAY24-162-P")}: "SOL_USDC-31MAY24-162-P",
|
||||
{Delimiter: currency.DashDelimiter, Base: currency.MATIC, Quote: currency.NewCode("USDC-6APR24-0d98-P")}: "MATIC_USDC-6APR24-0d98-P",
|
||||
}
|
||||
for pair, instrumentID := range optionsList {
|
||||
t.Run(instrumentID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
instrument := d.optionPairToString(pair)
|
||||
require.Equal(t, instrumentID, instrument)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3625,39 +3706,6 @@ func TestWSRetrieveCombos(t *testing.T) {
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func instantiateTradablePairs() {
|
||||
if err := d.UpdateTradablePairs(context.Background(), true); err != nil {
|
||||
log.Fatalf("Failed to update tradable pairs. Error: %v", err)
|
||||
}
|
||||
|
||||
handleError := func(err error, msg string) {
|
||||
if err != nil {
|
||||
log.Fatalf("%s. Error: %v", msg, err)
|
||||
}
|
||||
}
|
||||
|
||||
updateTradablePair := func(assetType asset.Item, tradablePair *currency.Pair) {
|
||||
if d.CurrencyPairs.IsAssetEnabled(assetType) == nil {
|
||||
pairs, err := d.GetEnabledPairs(assetType)
|
||||
handleError(err, fmt.Sprintf("Failed to get enabled pairs for asset type %v", assetType))
|
||||
|
||||
if len(pairs) == 0 {
|
||||
handleError(currency.ErrCurrencyPairsEmpty, fmt.Sprintf("No enabled pairs for asset type %v", assetType))
|
||||
}
|
||||
|
||||
if assetType == asset.Options {
|
||||
*tradablePair, err = d.FormatExchangeCurrency(pairs[0], assetType)
|
||||
handleError(err, "Failed to format exchange currency for options pair")
|
||||
} else {
|
||||
*tradablePair = pairs[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTradablePair(asset.Options, &optionsTradablePair)
|
||||
updateTradablePair(asset.OptionCombo, &optionComboTradablePair)
|
||||
updateTradablePair(asset.FutureCombo, &futureComboTradablePair)
|
||||
}
|
||||
|
||||
func TestGetLatestFundingRates(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := d.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
|
||||
@@ -3799,6 +3847,9 @@ func TestGetFuturesContractDetails(t *testing.T) {
|
||||
result, err := d.GetFuturesContractDetails(context.Background(), asset.Futures)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
|
||||
_, err = d.GetFuturesContractDetails(context.Background(), asset.FutureCombo)
|
||||
require.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
func TestGetFuturesPositionSummary(t *testing.T) {
|
||||
@@ -3817,7 +3868,7 @@ func TestGetFuturesPositionSummary(t *testing.T) {
|
||||
sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
|
||||
req := &futures.PositionSummaryRequest{
|
||||
Asset: asset.Futures,
|
||||
Pair: currency.NewPair(currency.BTC, currency.NewCode("PERPETUAL")),
|
||||
Pair: currency.NewPair(currency.BTC, currency.NewCode(perpString)),
|
||||
}
|
||||
result, err := d.GetFuturesPositionSummary(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
@@ -3834,23 +3885,32 @@ func TestGetOpenInterest(t *testing.T) {
|
||||
require.ErrorIs(t, err, asset.ErrNotSupported)
|
||||
|
||||
_, err = d.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: currency.BTC.Item,
|
||||
Base: optionsTradablePair.Base.Item,
|
||||
Quote: optionsTradablePair.Quote.Item,
|
||||
Asset: asset.Options,
|
||||
})
|
||||
require.True(t, err == nil || errors.Is(err, currency.ErrCurrencyNotFound))
|
||||
require.NoError(t, err)
|
||||
|
||||
var result []futures.OpenInterest
|
||||
assetTypeToPairs := getAssetToPairMap(asset.Futures & asset.FutureCombo)
|
||||
for assetType, cp := range assetTypeToPairs {
|
||||
result, err = d.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: cp.Base.Item,
|
||||
Quote: cp.Quote.Item,
|
||||
Asset: assetType,
|
||||
})
|
||||
require.NoErrorf(t, err, "expected nil, got %s for asset type %s pair %s", assetType.String(), cp.String())
|
||||
require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
|
||||
}
|
||||
_, err = d.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: currency.BTC.Item,
|
||||
Quote: currency.NewCode(perpString).Item,
|
||||
Asset: asset.Futures,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = d.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: currency.NewCode("XRP").Item,
|
||||
Quote: currency.NewCode("USDC-PERPETUAL").Item,
|
||||
Asset: asset.Futures,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = d.GetOpenInterest(context.Background(), key.PairAsset{
|
||||
Base: futureComboTradablePair.Base.Item,
|
||||
Quote: futureComboTradablePair.Quote.Item,
|
||||
Asset: asset.FutureCombo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIsPerpetualFutureCurrency(t *testing.T) {
|
||||
@@ -3861,26 +3921,27 @@ func TestIsPerpetualFutureCurrency(t *testing.T) {
|
||||
Response bool
|
||||
}{
|
||||
asset.Spot: {
|
||||
{Pair: currency.EMPTYPAIR, Error: futures.ErrNotPerpetualFuture},
|
||||
{Pair: currency.EMPTYPAIR, Error: currency.ErrCurrencyPairEmpty, Response: false},
|
||||
{Pair: spotTradablePair, Error: nil, Response: false},
|
||||
},
|
||||
asset.Futures: {
|
||||
{Pair: currency.EMPTYPAIR, Error: currency.ErrCurrencyPairEmpty},
|
||||
{Pair: currency.NewPair(currency.BTC, currency.NewCode("PERPETUAL")), Response: true},
|
||||
{Pair: currency.NewPair(currency.NewCode("ETH"), currency.NewCode("FS-30DEC22_PERP")), Response: true},
|
||||
{Pair: currency.NewPair(currency.BTC, currency.NewCode(perpString)), Response: true},
|
||||
},
|
||||
asset.FutureCombo: {
|
||||
{Pair: currency.NewPair(currency.NewCode("SOL"), currency.NewCode("FS-30DEC22_28OCT22"))},
|
||||
{Pair: currency.NewPair(currency.NewCode("BTC"), currency.NewCode("FS-27SEP24_PERP")), Response: false},
|
||||
},
|
||||
asset.OptionCombo: {
|
||||
{Pair: currency.NewPair(currency.NewCode(currencyBTC), currency.NewCode("STRG-21OCT22")), Error: futures.ErrNotPerpetualFuture},
|
||||
{Pair: currency.EMPTYPAIR, Error: futures.ErrNotPerpetualFuture},
|
||||
{Pair: currency.NewPair(currency.NewCode(currencyBTC), currency.NewCode("STRG-21OCT22")), Error: nil, Response: false},
|
||||
},
|
||||
}
|
||||
for assetType, instances := range assetPairToErrorMap {
|
||||
for i := range instances {
|
||||
is, err := d.IsPerpetualFutureCurrency(assetType, instances[i].Pair)
|
||||
require.ErrorIsf(t, err, instances[i].Error, "expected %v, got %v for asset: %s pair: %s", instances[i].Error, err, assetType.String(), instances[i].Pair.String())
|
||||
require.Equalf(t, is, instances[i].Response, "expected %v, got %v for asset: %s pair: %s", instances[i].Response, is, assetType.String(), instances[i].Pair.String())
|
||||
t.Run(fmt.Sprintf("Asset: %s Pair: %s", assetType.String(), instances[i].Pair.String()), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
is, err := d.IsPerpetualFutureCurrency(assetType, instances[i].Pair)
|
||||
require.ErrorIsf(t, err, instances[i].Error, "expected %v, got %v for asset: %s pair: %s", instances[i].Error, err, assetType.String(), instances[i].Pair.String())
|
||||
require.Equalf(t, is, instances[i].Response, "expected %v, got %v for asset: %s pair: %s", instances[i].Response, is, assetType.String(), instances[i].Pair.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3952,24 +4013,14 @@ func TestGetResolutionFromInterval(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func getAssetToPairMap(items asset.Item) map[asset.Item]currency.Pair {
|
||||
newMap := make(map[asset.Item]currency.Pair)
|
||||
for a := range assetTypeToPairsMap {
|
||||
if a&items == a {
|
||||
newMap[a] = assetTypeToPairsMap[a]
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
func TestGetValidatedCurrencyCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
pairs := map[currency.Pair]string{
|
||||
currency.NewPairWithDelimiter(currencySOL, "21OCT22-20-C", "-"): currencySOL,
|
||||
currency.NewPairWithDelimiter(currencyBTC, "PERPETUAL", "-"): currencyBTC,
|
||||
currency.NewPairWithDelimiter(currencyETH, "PERPETUAL", "-"): currencyETH,
|
||||
currency.NewPairWithDelimiter(currencySOL, "PERPETUAL", "-"): currencySOL,
|
||||
currency.NewPairWithDelimiter("AVAX_USDC", "PERPETUAL", "-"): currencyUSDC,
|
||||
currency.NewPairWithDelimiter(currencyBTC, perpString, "-"): currencyBTC,
|
||||
currency.NewPairWithDelimiter(currencyETH, perpString, "-"): currencyETH,
|
||||
currency.NewPairWithDelimiter(currencySOL, perpString, "-"): currencySOL,
|
||||
currency.NewPairWithDelimiter("AVAX_USDC", perpString, "-"): currencyUSDC,
|
||||
currency.NewPairWithDelimiter(currencyBTC, "USDC", "_"): currencyBTC,
|
||||
currency.NewPairWithDelimiter(currencyETH, "USDC", "_"): currencyETH,
|
||||
currency.NewPairWithDelimiter("DOT", "USDC-PERPETUAL", "_"): currencyUSDC,
|
||||
@@ -3981,3 +4032,30 @@ func TestGetValidatedCurrencyCode(t *testing.T) {
|
||||
require.Equal(t, pairs[x], result, "expected: %s actual : %s for currency pair: %v", x, result, pairs[x])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrencyTradeURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := d.GetCurrencyTradeURL(context.Background(), asset.Spot, currency.EMPTYPAIR)
|
||||
require.ErrorIs(t, err, currency.ErrCurrencyPairEmpty)
|
||||
|
||||
for _, a := range d.GetAssetTypes(false) {
|
||||
var pairs currency.Pairs
|
||||
pairs, err = d.CurrencyPairs.GetPairs(a, false)
|
||||
require.NoError(t, err, "cannot get pairs for %s", a)
|
||||
require.NotEmpty(t, pairs, "no pairs for %s", a)
|
||||
var resp string
|
||||
resp, err = d.GetCurrencyTradeURL(context.Background(), a, pairs[0])
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
// specific test to ensure perps work
|
||||
cp := currency.NewPair(currency.BTC, currency.NewCode("USDC-PERPETUAL"))
|
||||
resp, err := d.GetCurrencyTradeURL(context.Background(), asset.Futures, cp)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
// specific test to ensure options with dates work
|
||||
cp = currency.NewPair(currency.BTC, currency.NewCode("14JUN24-62000-C"))
|
||||
resp, err = d.GetCurrencyTradeURL(context.Background(), asset.Options, cp)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp)
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func (d *Deribit) wsHandleData(respRaw []byte) error {
|
||||
accessLog := &wsAccessLog{}
|
||||
return d.processData(respRaw, accessLog)
|
||||
case "changes":
|
||||
return d.processChanges(respRaw, channels)
|
||||
return d.processUserOrderChanges(respRaw, channels)
|
||||
case "lock":
|
||||
userLock := &WsUserLock{}
|
||||
return d.processData(respRaw, userLock)
|
||||
@@ -265,7 +265,7 @@ func (d *Deribit) wsHandleData(respRaw []byte) error {
|
||||
}
|
||||
return d.processData(respRaw, data)
|
||||
case "orders":
|
||||
return d.processOrders(respRaw, channels)
|
||||
return d.processUserOrders(respRaw, channels)
|
||||
case "portfolio":
|
||||
portfolio := &wsUserPortfolio{}
|
||||
return d.processData(respRaw, portfolio)
|
||||
@@ -294,33 +294,23 @@ func (d *Deribit) wsHandleData(respRaw []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Deribit) processOrders(respRaw []byte, channels []string) error {
|
||||
var currencyPair currency.Pair
|
||||
var err error
|
||||
var a asset.Item
|
||||
switch len(channels) {
|
||||
case 4:
|
||||
currencyPair, err = currency.NewPairFromString(channels[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 5:
|
||||
a, err = d.StringToAssetKind(channels[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
func (d *Deribit) processUserOrders(respRaw []byte, channels []string) error {
|
||||
if len(channels) != 4 && len(channels) != 5 {
|
||||
return fmt.Errorf("%w, expected format 'user.orders.{instrument_name}.raw, user.orders.{instrument_name}.{interval}, user.orders.{kind}.{currency}.raw, or user.orders.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
|
||||
}
|
||||
var response WsResponse
|
||||
orderData := []WsOrder{}
|
||||
response.Params.Data = orderData
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderDetails := make([]order.Detail, len(orderData))
|
||||
for x := range orderData {
|
||||
cp, a, err := d.getAssetPairByInstrument(orderData[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oType, err := order.StringToOrderType(orderData[x].OrderType)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -333,16 +323,6 @@ func (d *Deribit) processOrders(respRaw []byte, channels []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a != asset.Empty {
|
||||
currencyPair, err = currency.NewPairFromString(orderData[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
a, err = guessAssetTypeFromInstrument(currencyPair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orderDetails[x] = order.Detail{
|
||||
Price: orderData[x].Price,
|
||||
Amount: orderData[x].Amount,
|
||||
@@ -356,14 +336,17 @@ func (d *Deribit) processOrders(respRaw []byte, channels []string) error {
|
||||
AssetType: a,
|
||||
Date: orderData[x].CreationTimestamp.Time(),
|
||||
LastUpdated: orderData[x].LastUpdateTimestamp.Time(),
|
||||
Pair: currencyPair,
|
||||
Pair: cp,
|
||||
}
|
||||
}
|
||||
d.Websocket.DataHandler <- orderDetails
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
|
||||
func (d *Deribit) processUserOrderChanges(respRaw []byte, channels []string) error {
|
||||
if len(channels) < 4 || len(channels) > 5 {
|
||||
return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
|
||||
}
|
||||
var response WsResponse
|
||||
changeData := &wsChanges{}
|
||||
response.Params.Data = changeData
|
||||
@@ -371,43 +354,22 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var currencyPair currency.Pair
|
||||
var a asset.Item
|
||||
switch len(channels) {
|
||||
case 4:
|
||||
currencyPair, err = currency.NewPairFromString(channels[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 5:
|
||||
a, err = d.StringToAssetKind(channels[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
|
||||
}
|
||||
tradeDatas := make([]trade.Data, len(changeData.Trades))
|
||||
td := make([]trade.Data, len(changeData.Trades))
|
||||
for x := range changeData.Trades {
|
||||
var side order.Side
|
||||
side, err = order.StringToOrderSide(changeData.Trades[x].Direction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if currencyPair.IsEmpty() {
|
||||
currencyPair, err = currency.NewPairFromString(changeData.Trades[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var cp currency.Pair
|
||||
var a asset.Item
|
||||
cp, a, err = d.getAssetPairByInstrument(changeData.Trades[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a == asset.Empty {
|
||||
a, err = guessAssetTypeFromInstrument(currencyPair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tradeDatas[x] = trade.Data{
|
||||
CurrencyPair: currencyPair,
|
||||
|
||||
td[x] = trade.Data{
|
||||
CurrencyPair: cp,
|
||||
Exchange: d.Name,
|
||||
Timestamp: changeData.Trades[x].Timestamp.Time(),
|
||||
Price: changeData.Trades[x].Price,
|
||||
@@ -417,7 +379,7 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
|
||||
AssetType: a,
|
||||
}
|
||||
}
|
||||
err = trade.AddTradesToBuffer(d.Name, tradeDatas...)
|
||||
err = trade.AddTradesToBuffer(d.Name, td...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -435,16 +397,9 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a != asset.Empty {
|
||||
currencyPair, err = currency.NewPairFromString(changeData.Orders[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
a, err = guessAssetTypeFromInstrument(currencyPair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cp, a, err := d.getAssetPairByInstrument(changeData.Orders[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orders[x] = order.Detail{
|
||||
Price: changeData.Orders[x].Price,
|
||||
@@ -459,7 +414,7 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
|
||||
AssetType: a,
|
||||
Date: changeData.Orders[x].CreationTimestamp.Time(),
|
||||
LastUpdated: changeData.Orders[x].LastUpdateTimestamp.Time(),
|
||||
Pair: currencyPair,
|
||||
Pair: cp,
|
||||
}
|
||||
}
|
||||
d.Websocket.DataHandler <- orders
|
||||
@@ -468,7 +423,7 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
|
||||
}
|
||||
|
||||
func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error {
|
||||
cp, err := currency.NewPairFromString(channels[1])
|
||||
cp, a, err := d.getAssetPairByInstrument(channels[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -479,10 +434,6 @@ func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a, err := guessAssetTypeFromInstrument(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: d.Name,
|
||||
Pair: cp,
|
||||
@@ -497,55 +448,33 @@ func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error {
|
||||
}
|
||||
|
||||
func (d *Deribit) processTrades(respRaw []byte, channels []string) error {
|
||||
var err error
|
||||
var currencyPair currency.Pair
|
||||
var a asset.Item
|
||||
switch {
|
||||
case (len(channels) == 3 && channels[0] == "trades") || (len(channels) == 4 && channels[0] == "user"):
|
||||
currencyPair, err = currency.NewPairFromString(channels[len(channels)-2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case (len(channels) == 4 && channels[0] == "trades") || (len(channels) == 5 && channels[0] == "user"):
|
||||
a, err = d.StringToAssetKind(channels[len(channels)-3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if len(channels) < 3 || len(channels) > 5 {
|
||||
return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
|
||||
}
|
||||
var response WsResponse
|
||||
tradeList := []wsTrade{}
|
||||
var tradeList []wsTrade
|
||||
response.Params.Data = &tradeList
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
err := json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tradeList) == 0 {
|
||||
return fmt.Errorf("%v, empty list of trades found", common.ErrNoResponse)
|
||||
}
|
||||
if a == asset.Empty && currencyPair.IsEmpty() {
|
||||
currencyPair, err = currency.NewPairFromString(tradeList[0].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a, err = guessAssetTypeFromInstrument(currencyPair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tradeDatas := make([]trade.Data, len(tradeList))
|
||||
for x := range tradeDatas {
|
||||
var cp currency.Pair
|
||||
var a asset.Item
|
||||
cp, a, err = d.getAssetPairByInstrument(tradeList[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
side, err := order.StringToOrderSide(tradeList[x].Direction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currencyPair, err = currency.NewPairFromString(tradeList[x].InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tradeDatas[x] = trade.Data{
|
||||
CurrencyPair: currencyPair,
|
||||
CurrencyPair: cp,
|
||||
Exchange: d.Name,
|
||||
Timestamp: tradeList[x].Timestamp.Time(),
|
||||
Price: tradeList[x].Price,
|
||||
@@ -562,7 +491,7 @@ func (d *Deribit) processIncrementalTicker(respRaw []byte, channels []string) er
|
||||
if len(channels) != 2 {
|
||||
return fmt.Errorf("%w, expected format 'incremental_ticker.{instrument_name}', but found %s", errMalformedData, strings.Join(channels, "."))
|
||||
}
|
||||
cp, err := currency.NewPairFromString(channels[1])
|
||||
cp, a, err := d.getAssetPairByInstrument(channels[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -573,14 +502,10 @@ func (d *Deribit) processIncrementalTicker(respRaw []byte, channels []string) er
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assetType, err := guessAssetTypeFromInstrument(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Websocket.DataHandler <- &ticker.Price{
|
||||
ExchangeName: d.Name,
|
||||
Pair: cp,
|
||||
AssetType: assetType,
|
||||
AssetType: a,
|
||||
LastUpdated: incrementalTicker.Timestamp.Time(),
|
||||
BidSize: incrementalTicker.BestBidAmount,
|
||||
AskSize: incrementalTicker.BestAskAmount,
|
||||
@@ -602,11 +527,10 @@ func (d *Deribit) processInstrumentTicker(respRaw []byte, channels []string) err
|
||||
}
|
||||
|
||||
func (d *Deribit) processTicker(respRaw []byte, channels []string) error {
|
||||
cp, err := currency.NewPairFromString(channels[1])
|
||||
cp, a, err := d.getAssetPairByInstrument(channels[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var a asset.Item
|
||||
var response WsResponse
|
||||
tickerPriceResponse := &wsTicker{}
|
||||
response.Params.Data = tickerPriceResponse
|
||||
@@ -614,10 +538,6 @@ func (d *Deribit) processTicker(respRaw []byte, channels []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a, err = guessAssetTypeFromInstrument(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tickerPrice := &ticker.Price{
|
||||
ExchangeName: d.Name,
|
||||
Pair: cp,
|
||||
@@ -658,22 +578,17 @@ func (d *Deribit) processCandleChart(respRaw []byte, channels []string) error {
|
||||
if len(channels) != 4 {
|
||||
return fmt.Errorf("%w, expected format 'chart.trades.{instrument_name}.{resolution}', but found %s", errMalformedData, strings.Join(channels, "."))
|
||||
}
|
||||
cp, err := currency.NewPairFromString(channels[2])
|
||||
cp, a, err := d.getAssetPairByInstrument(channels[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var response WsResponse
|
||||
var a asset.Item
|
||||
candleData := &wsCandlestickData{}
|
||||
response.Params.Data = candleData
|
||||
err = json.Unmarshal(respRaw, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a, err = guessAssetTypeFromInstrument(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Websocket.DataHandler <- stream.KlineData{
|
||||
Timestamp: time.UnixMilli(candleData.Tick),
|
||||
Pair: cp,
|
||||
@@ -696,9 +611,8 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var assetType asset.Item
|
||||
if len(channels) == 3 {
|
||||
cp, err := currency.NewPairFromString(orderbookData.InstrumentName)
|
||||
cp, a, err := d.getAssetPairByInstrument(orderbookData.InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -743,10 +657,6 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error {
|
||||
if len(asks) == 0 && len(bids) == 0 {
|
||||
return nil
|
||||
}
|
||||
assetType, err = guessAssetTypeFromInstrument(cp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if orderbookData.Type == "snapshot" {
|
||||
return d.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
||||
Exchange: d.Name,
|
||||
@@ -755,7 +665,7 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error {
|
||||
Pair: cp,
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Asset: assetType,
|
||||
Asset: a,
|
||||
LastUpdateID: orderbookData.ChangeID,
|
||||
})
|
||||
} else if orderbookData.Type == "change" {
|
||||
@@ -763,17 +673,13 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error {
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: cp,
|
||||
Asset: assetType,
|
||||
Asset: a,
|
||||
UpdateID: orderbookData.ChangeID,
|
||||
UpdateTime: orderbookData.Timestamp.Time(),
|
||||
})
|
||||
}
|
||||
} else if len(channels) == 5 {
|
||||
cp, err := currency.NewPairFromString(orderbookData.InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assetType, err = guessAssetTypeFromInstrument(cp)
|
||||
cp, a, err := d.getAssetPairByInstrument(orderbookData.InstrumentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -824,7 +730,7 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error {
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
Pair: cp,
|
||||
Asset: assetType,
|
||||
Asset: a,
|
||||
Exchange: d.Name,
|
||||
LastUpdateID: orderbookData.ChangeID,
|
||||
LastUpdated: orderbookData.Timestamp.Time(),
|
||||
@@ -864,8 +770,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) {
|
||||
case chartTradesChannel:
|
||||
for _, a := range assets {
|
||||
for z := range assetPairs[a] {
|
||||
if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" ||
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
|
||||
if ((assetPairs[a][z].Quote.Upper().String() == perpString ||
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) &&
|
||||
a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
|
||||
continue
|
||||
}
|
||||
@@ -885,8 +791,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) {
|
||||
rawUserOrdersChannel:
|
||||
for _, a := range assets {
|
||||
for z := range assetPairs[a] {
|
||||
if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" ||
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
|
||||
if ((assetPairs[a][z].Quote.Upper().String() == perpString ||
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) &&
|
||||
a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
|
||||
continue
|
||||
}
|
||||
@@ -900,8 +806,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) {
|
||||
case orderbookChannel:
|
||||
for _, a := range assets {
|
||||
for z := range assetPairs[a] {
|
||||
if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" ||
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
|
||||
if ((assetPairs[a][z].Quote.Upper().String() == perpString ||
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) &&
|
||||
a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
|
||||
continue
|
||||
}
|
||||
@@ -936,8 +842,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) {
|
||||
tradesChannel:
|
||||
for _, a := range assets {
|
||||
for z := range assetPairs[a] {
|
||||
if ((assetPairs[a][z].Quote.Upper().String() != "PERPETUAL" &&
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
|
||||
if ((assetPairs[a][z].Quote.Upper().String() != perpString &&
|
||||
!strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) &&
|
||||
a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
|
||||
continue
|
||||
}
|
||||
@@ -955,7 +861,7 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) {
|
||||
userTradesChannelByInstrument:
|
||||
for _, a := range assets {
|
||||
for z := range assetPairs[a] {
|
||||
if subscriptionChannels[x] == perpetualChannel && !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL") {
|
||||
if subscriptionChannels[x] == perpetualChannel && !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString) {
|
||||
continue
|
||||
}
|
||||
subscriptions = append(subscriptions,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -62,16 +63,14 @@ func (d *Deribit) SetDefaults() {
|
||||
d.API.CredentialsValidator.RequiresKey = true
|
||||
d.API.CredentialsValidator.RequiresSecret = true
|
||||
|
||||
requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
|
||||
configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
|
||||
err := d.StoreAssetPairFormat(asset.Spot, currency.PairStore{
|
||||
RequestFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter},
|
||||
ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}})
|
||||
dashFormat := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
|
||||
underscoreFormat := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}
|
||||
err := d.StoreAssetPairFormat(asset.Spot, currency.PairStore{RequestFormat: underscoreFormat, ConfigFormat: underscoreFormat})
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
for _, assetType := range []asset.Item{asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} {
|
||||
if err = d.StoreAssetPairFormat(assetType, currency.PairStore{RequestFormat: requestFmt, ConfigFormat: configFmt}); err != nil {
|
||||
if err = d.StoreAssetPairFormat(assetType, currency.PairStore{RequestFormat: dashFormat, ConfigFormat: dashFormat}); err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
}
|
||||
@@ -208,6 +207,10 @@ func (d *Deribit) Setup(exch *config.Exchange) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// setup option decimal regex at startup to make constant checks more efficient
|
||||
optionRegex = regexp.MustCompile(optionDecimalRegex)
|
||||
|
||||
return d.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
URL: d.Websocket.GetWebsocketURL(),
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
@@ -238,16 +241,9 @@ func (d *Deribit) FetchTradablePairs(ctx context.Context, assetType asset.Item)
|
||||
continue
|
||||
}
|
||||
var cp currency.Pair
|
||||
if assetType == asset.Options {
|
||||
cp, err = currency.NewPairDelimiter(instrumentsData[y].InstrumentName, currency.DashDelimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cp, err = currency.NewPairFromString(instrumentsData[y].InstrumentName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cp, err = currency.NewPairFromString(instrumentsData[y].InstrumentName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp = resp.Add(cp)
|
||||
}
|
||||
@@ -259,17 +255,19 @@ func (d *Deribit) FetchTradablePairs(ctx context.Context, assetType asset.Item)
|
||||
// them in the exchanges config
|
||||
func (d *Deribit) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error {
|
||||
assets := d.GetAssetTypes(false)
|
||||
errs := common.CollectErrors(len(assets))
|
||||
for x := range assets {
|
||||
pairs, err := d.FetchTradablePairs(ctx, assets[x])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.UpdatePairs(pairs, assets[x], false, forceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(x int) {
|
||||
defer errs.Wg.Done()
|
||||
pairs, err := d.FetchTradablePairs(ctx, assets[x])
|
||||
if err != nil {
|
||||
errs.C <- err
|
||||
return
|
||||
}
|
||||
errs.C <- d.UpdatePairs(pairs, assets[x], false, forceUpdate)
|
||||
}(x)
|
||||
}
|
||||
return nil
|
||||
return errs.Collect()
|
||||
}
|
||||
|
||||
// UpdateTickers updates the ticker for all currency pairs of a given asset type
|
||||
@@ -1091,7 +1089,7 @@ func (d *Deribit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
intervalString, err := d.GetResolutionFromInterval(interval)
|
||||
intervalString, err := d.GetResolutionFromInterval(req.ExchangeInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1149,7 +1147,7 @@ func (d *Deribit) GetHistoricCandlesExtended(ctx context.Context, pair currency.
|
||||
switch a {
|
||||
case asset.Futures, asset.Spot:
|
||||
for x := range req.RangeHolder.Ranges {
|
||||
intervalString, err := d.GetResolutionFromInterval(interval)
|
||||
intervalString, err := d.GetResolutionFromInterval(req.ExchangeInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1266,59 +1264,6 @@ func (d *Deribit) GetFuturesContractDetails(ctx context.Context, item asset.Item
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetLatestFundingRates returns the latest funding rates data
|
||||
func (d *Deribit) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer)
|
||||
}
|
||||
if !d.SupportsAsset(r.Asset) {
|
||||
return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported)
|
||||
}
|
||||
isPerpetual, err := d.IsPerpetualFutureCurrency(r.Asset, r.Pair)
|
||||
if !isPerpetual || err != nil {
|
||||
return nil, futures.ErrNotPerpetualFuture
|
||||
}
|
||||
available, err := d.GetAvailablePairs(r.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available.Contains(r.Pair, true) && r.Pair.Quote.String() != "PERPETUAL" && !strings.HasSuffix(r.Pair.String(), "PERP") {
|
||||
return nil, fmt.Errorf("%w pair: %v", futures.ErrNotPerpetualFuture, r.Pair)
|
||||
}
|
||||
r.Pair, err = d.FormatExchangeCurrency(r.Pair, r.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fri []FundingRateHistory
|
||||
fri, err = d.GetFundingRateHistory(ctx, r.Pair.String(), time.Now().Add(-time.Hour*16), time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := make([]fundingrate.LatestRateResponse, 1)
|
||||
latestTime := fri[0].Timestamp.Time()
|
||||
for i := range fri {
|
||||
if fri[i].Timestamp.Time().Before(latestTime) {
|
||||
continue
|
||||
}
|
||||
resp[0] = fundingrate.LatestRateResponse{
|
||||
TimeChecked: time.Now(),
|
||||
Exchange: d.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: r.Pair,
|
||||
LatestRate: fundingrate.Rate{
|
||||
Time: fri[i].Timestamp.Time(),
|
||||
Rate: decimal.NewFromFloat(fri[i].Interest8H),
|
||||
},
|
||||
}
|
||||
latestTime = fri[i].Timestamp.Time()
|
||||
}
|
||||
if len(resp) == 0 {
|
||||
return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdateOrderExecutionLimits sets exchange execution order limits for an asset type
|
||||
func (d *Deribit) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error {
|
||||
if !d.SupportsAsset(a) {
|
||||
@@ -1449,26 +1394,23 @@ func (d *Deribit) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]fu
|
||||
}
|
||||
}
|
||||
result := make([]futures.OpenInterest, 0, len(k))
|
||||
var err error
|
||||
var pair currency.Pair
|
||||
for i := range k {
|
||||
pair, err = d.FormatExchangeCurrency(k[i].Pair(), k[i].Asset)
|
||||
pFmt, err := d.CurrencyPairs.GetFormat(k[i].Asset, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cp := k[i].Pair().Format(pFmt)
|
||||
p := d.formatPairString(k[i].Asset, cp)
|
||||
var oi []BookSummaryData
|
||||
if d.Websocket.IsConnected() {
|
||||
oi, err = d.WSRetrieveBookBySummary(pair.Base, d.GetAssetKind(k[i].Asset))
|
||||
oi, err = d.WSRetrieveBookSummaryByInstrument(p)
|
||||
} else {
|
||||
oi, err = d.GetBookSummaryByCurrency(ctx, pair.Base, d.GetAssetKind(k[i].Asset))
|
||||
oi, err = d.GetBookSummaryByInstrument(ctx, p)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for a := range oi {
|
||||
if oi[a].InstrumentName != pair.String() {
|
||||
continue
|
||||
}
|
||||
result = append(result, futures.OpenInterest{
|
||||
Key: key.ExchangePairAsset{
|
||||
Exchange: d.Name,
|
||||
@@ -1487,28 +1429,105 @@ func (d *Deribit) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]fu
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair
|
||||
func (d *Deribit) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currency.Pair) (string, error) {
|
||||
if cp.IsEmpty() {
|
||||
return "", currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
switch a {
|
||||
case asset.Futures:
|
||||
isPerp, err := d.IsPerpetualFutureCurrency(a, cp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if isPerp {
|
||||
return tradeBaseURL + tradeFutures + cp.Base.Upper().String() + currency.UnderscoreDelimiter + cp.Quote.Upper().String(), nil
|
||||
}
|
||||
return tradeBaseURL + tradeFutures + cp.Upper().String(), nil
|
||||
case asset.Spot:
|
||||
cp.Delimiter = currency.UnderscoreDelimiter
|
||||
return tradeBaseURL + tradeSpot + cp.Upper().String(), nil
|
||||
case asset.Options:
|
||||
baseString := cp.Base.Upper().String()
|
||||
quoteString := cp.Quote.Upper().String()
|
||||
quoteSplit := strings.Split(quoteString, currency.DashDelimiter)
|
||||
if len(quoteSplit) > 1 &&
|
||||
(quoteSplit[len(quoteSplit)-1] == "C" || quoteSplit[len(quoteSplit)-1] == "P") {
|
||||
return tradeBaseURL + tradeOptions + baseString + "/" + baseString + currency.DashDelimiter + quoteSplit[0], nil
|
||||
}
|
||||
return tradeBaseURL + tradeOptions + baseString, nil
|
||||
case asset.FutureCombo:
|
||||
return tradeBaseURL + tradeFuturesCombo + cp.Upper().String(), nil
|
||||
case asset.OptionCombo:
|
||||
return tradeBaseURL + tradeOptionsCombo + cp.Base.Upper().String(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("%w %v", asset.ErrNotSupported, a)
|
||||
}
|
||||
}
|
||||
|
||||
// IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future
|
||||
// differs by exchange
|
||||
func (d *Deribit) IsPerpetualFutureCurrency(assetType asset.Item, pair currency.Pair) (bool, error) {
|
||||
if !assetType.IsFutures() {
|
||||
return false, futures.ErrNotPerpetualFuture
|
||||
} else if strings.EqualFold(pair.Quote.String(), "PERPETUAL") || strings.HasSuffix(pair.String(), "PERP") {
|
||||
return true, nil
|
||||
if pair.IsEmpty() {
|
||||
return false, currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
pair, err := d.FormatExchangeCurrency(pair, assetType)
|
||||
if assetType != asset.Futures {
|
||||
// deribit considers future combo, even if ending in "PERP" to not be a perpetual
|
||||
return false, nil
|
||||
}
|
||||
pqs := strings.Split(pair.Quote.Upper().String(), currency.DashDelimiter)
|
||||
return pqs[len(pqs)-1] == perpString, nil
|
||||
}
|
||||
|
||||
// GetLatestFundingRates returns the latest funding rates data
|
||||
func (d *Deribit) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer)
|
||||
}
|
||||
if !d.SupportsAsset(r.Asset) {
|
||||
return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported)
|
||||
}
|
||||
isPerpetual, err := d.IsPerpetualFutureCurrency(r.Asset, r.Pair)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
var instrumentInfo *InstrumentData
|
||||
if d.Websocket.IsConnected() {
|
||||
instrumentInfo, err = d.WSRetrieveInstrumentData(pair.String())
|
||||
} else {
|
||||
instrumentInfo, err = d.GetInstrument(context.Background(), pair.String())
|
||||
if !isPerpetual {
|
||||
return nil, fmt.Errorf("%w '%s'", futures.ErrNotPerpetualFuture, r.Pair)
|
||||
}
|
||||
pFmt, err := d.CurrencyPairs.GetFormat(r.Asset, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
return strings.EqualFold(instrumentInfo.SettlementPeriod, "perpetual"), nil
|
||||
cp := r.Pair.Format(pFmt)
|
||||
p := d.formatPairString(r.Asset, cp)
|
||||
var fri []FundingRateHistory
|
||||
fri, err = d.GetFundingRateHistory(ctx, p, time.Now().Add(-time.Hour*16), time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := make([]fundingrate.LatestRateResponse, 1)
|
||||
latestTime := fri[0].Timestamp.Time()
|
||||
for i := range fri {
|
||||
if fri[i].Timestamp.Time().Before(latestTime) {
|
||||
continue
|
||||
}
|
||||
resp[0] = fundingrate.LatestRateResponse{
|
||||
TimeChecked: time.Now(),
|
||||
Exchange: d.Name,
|
||||
Asset: r.Asset,
|
||||
Pair: r.Pair,
|
||||
LatestRate: fundingrate.Rate{
|
||||
Time: fri[i].Timestamp.Time(),
|
||||
Rate: decimal.NewFromFloat(fri[i].Interest8H),
|
||||
},
|
||||
}
|
||||
latestTime = fri[i].Timestamp.Time()
|
||||
}
|
||||
if len(resp) == 0 {
|
||||
return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetHistoricalFundingRates returns historical funding rates for a future
|
||||
@@ -1532,10 +1551,12 @@ func (d *Deribit) GetHistoricalFundingRates(ctx context.Context, r *fundingrate.
|
||||
if r.IncludePayments {
|
||||
return nil, fmt.Errorf("include payments %w", common.ErrNotYetImplemented)
|
||||
}
|
||||
fPair, err := d.FormatExchangeCurrency(r.Pair, r.Asset)
|
||||
pFmt, err := d.CurrencyPairs.GetFormat(r.Asset, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cp := r.Pair.Format(pFmt)
|
||||
p := d.formatPairString(r.Asset, cp)
|
||||
ed := r.EndDate
|
||||
|
||||
var fundingRates []fundingrate.Rate
|
||||
@@ -1546,9 +1567,9 @@ func (d *Deribit) GetHistoricalFundingRates(ctx context.Context, r *fundingrate.
|
||||
}
|
||||
var records []FundingRateHistory
|
||||
if d.Websocket.IsConnected() {
|
||||
records, err = d.WSRetrieveFundingRateHistory(fPair.String(), r.StartDate, ed)
|
||||
records, err = d.WSRetrieveFundingRateHistory(p, r.StartDate, ed)
|
||||
} else {
|
||||
records, err = d.GetFundingRateHistory(ctx, fPair.String(), r.StartDate, ed)
|
||||
records, err = d.GetFundingRateHistory(ctx, p, r.StartDate, ed)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -198,8 +198,9 @@ func (k *Item) addPadding(start, exclusiveEnd time.Time, purgeOnPartial bool) er
|
||||
padded[x].Time = start
|
||||
case !k.Candles[target].Time.Equal(start):
|
||||
if k.Candles[target].Time.Before(start) {
|
||||
return fmt.Errorf("%w when it should be %s truncated at a %s interval",
|
||||
return fmt.Errorf("%w '%s' should be '%s' at '%s' interval",
|
||||
errCandleOpenTimeIsNotUTCAligned,
|
||||
k.Candles[target].Time,
|
||||
start.Add(k.Interval.Duration()),
|
||||
k.Interval)
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ func (b *Base) Verify() error {
|
||||
// level books. In the event that there is a massive liquidity change where
|
||||
// a book dries up, this will still update so we do not traverse potential
|
||||
// incorrect old data.
|
||||
if len(b.Asks) == 0 || len(b.Bids) == 0 {
|
||||
if (len(b.Asks) == 0 || len(b.Bids) == 0) && !b.Asset.IsOptions() {
|
||||
log.Warnf(log.OrderBook,
|
||||
bookLengthIssue,
|
||||
b.Exchange,
|
||||
|
||||
Reference in New Issue
Block a user