OKX: Fix intermittent GetFuturesContractDetails issue, add spread endpoints and various refactors (#1900)

* OKX: Fix intermittent GetFuturesContractDetails issue and various refactors

* refactor: Update LeadTraderRanksRequest fields for clarity and improve parameter checks

* refactor: Simplify live contract check in GetFuturesContractDetails

* OKX: Fix spread related issues and enhance tests

* OKX: Disable spread websocket support and adjust conditional logic in test

* refactor: Improve error handling in syncLeadTraderUniqueID and clean up variable usage

* refactor: Update LeadTraderRanksRequest State type to bool and adjust rate limit

* refactor: Rename State to HasVacancy in LeadTraderRanksRequest and update related logic
This commit is contained in:
Adrian Gallagher
2025-05-19 22:48:34 +10:00
committed by GitHub
parent c2bb050eac
commit a22870a89c
7 changed files with 683 additions and 516 deletions

View File

@@ -3845,48 +3845,41 @@ func (ok *Okx) GetHistoryLeadTraders(ctx context.Context, instrumentType, after,
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getMyLeadTradersEPL, http.MethodGet, common.EncodeURLValues("copytrading/lead-traders-history", params), nil, &resp, request.AuthenticatedRequest)
}
// GetLeadTradersRanks retrieve lead trader ranks.
// Instrument type: SWAP, the default value
// Sort type"overview": overview, the default value "pnl": profit and loss "aum": assets under management "win_ratio": win ratio "pnl_ratio": pnl ratio "current_copy_trader_pnl": current copy trader pnl
// Lead trader state: "0": All lead traders, the default, including vacancy and non-vacancy "1": lead traders who have vacancy
// Minimum lead days '1': 7 days '2': 30 days '3': 90 days '4': 180 days
func (ok *Okx) GetLeadTradersRanks(ctx context.Context, instrumentType, sortType, state,
minLeadDays, minAssets, maxAssets, minAssetUnderManagement, maxAssetUnderManagement,
dataVersion, page string, limit int64,
) ([]LeadTradersRank, error) {
// GetLeadTradersRanks retrieves lead trader ranks
func (ok *Okx) GetLeadTradersRanks(ctx context.Context, req *LeadTraderRanksRequest) ([]LeadTradersRank, error) {
params := url.Values{}
if instrumentType != "" {
params.Set("instType", instrumentType)
if req.InstrumentType != "" {
params.Set("instType", req.InstrumentType)
}
if sortType != "" {
params.Set("sortType", sortType)
if req.SortType != "" {
params.Set("sortType", req.SortType)
}
if state != "" {
params.Set("state", state)
if req.HasVacancy {
params.Set("state", "1")
}
if minLeadDays != "" {
params.Set("minLeadDays", minLeadDays)
if req.MinLeadDays != 0 {
params.Set("minLeadDays", strconv.FormatUint(req.MinLeadDays, 10))
}
if minAssets != "" {
params.Set("minAssets", minAssets)
if req.MinAssets > 0 {
params.Set("minAssets", strconv.FormatFloat(req.MinAssets, 'f', -1, 64))
}
if maxAssets != "" {
params.Set("maxAssets", maxAssets)
if req.MaxAssets > 0 {
params.Set("maxAssets", strconv.FormatFloat(req.MaxAssets, 'f', -1, 64))
}
if minAssetUnderManagement != "" {
params.Set("minAum", minAssetUnderManagement)
if req.MinAssetsUnderManagement > 0 {
params.Set("minAum", strconv.FormatFloat(req.MinAssetsUnderManagement, 'f', -1, 64))
}
if maxAssetUnderManagement != "" {
params.Set("maxAum", maxAssetUnderManagement)
if req.MaxAssetsUnderManagement > 0 {
params.Set("maxAum", strconv.FormatFloat(req.MaxAssetsUnderManagement, 'f', -1, 64))
}
if dataVersion != "" {
params.Set("dataVer", dataVersion)
if req.DataVersion != 0 {
params.Set("dataVer", strconv.FormatUint(req.DataVersion, 10))
}
if page != "" {
params.Set("page", page)
if req.Page != 0 {
params.Set("page", strconv.FormatUint(req.Page, 10))
}
if limit > 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
if req.Limit != 0 {
params.Set("limit", strconv.FormatUint(req.Limit, 10))
}
var resp []LeadTradersRank
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getLeadTraderRanksEPL, http.MethodGet, common.EncodeURLValues("copytrading/public-lead-traders", params), nil, &resp, request.UnauthenticatedRequest)
@@ -4844,6 +4837,52 @@ func (ok *Okx) GetPublicSpreadTrades(ctx context.Context, spreadID string) ([]Sp
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getSpreadPublicTradesEPL, http.MethodGet, common.EncodeURLValues("sprd/public-trades", params), nil, &resp, request.UnauthenticatedRequest)
}
// GetSpreadCandlesticks retrieves candlestick charts for a given spread instrument
func (ok *Okx) GetSpreadCandlesticks(ctx context.Context, spreadID string, interval kline.Interval, before, after time.Time, limit uint64) ([]SpreadCandlestick, error) {
if spreadID == "" {
return nil, fmt.Errorf("%w, spread ID is required", errMissingInstrumentID)
}
params := url.Values{}
params.Set("sprdId", spreadID)
if !before.IsZero() {
params.Set("before", strconv.FormatInt(before.UnixMilli(), 10))
}
if !after.IsZero() {
params.Set("after", strconv.FormatInt(after.UnixMilli(), 10))
}
if bar := IntervalFromString(interval, true); bar != "" {
params.Set("bar", bar)
}
if limit > 0 {
params.Set("limit", strconv.FormatUint(limit, 10))
}
var resp []SpreadCandlestick
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getSpreadCandlesticksEPL, http.MethodGet, common.EncodeURLValues("market/sprd-candles", params), nil, &resp, request.UnauthenticatedRequest)
}
// GetSpreadCandlesticksHistory retrieves candlestick chart history for a given spread instrument for a period of up to 3 months
func (ok *Okx) GetSpreadCandlesticksHistory(ctx context.Context, spreadID string, interval kline.Interval, before, after time.Time, limit uint64) ([]SpreadCandlestick, error) {
if spreadID == "" {
return nil, fmt.Errorf("%w, spread ID is required", errMissingInstrumentID)
}
params := url.Values{}
params.Set("sprdId", spreadID)
if !before.IsZero() {
params.Set("before", strconv.FormatInt(before.UnixMilli(), 10))
}
if !after.IsZero() {
params.Set("after", strconv.FormatInt(after.UnixMilli(), 10))
}
if bar := IntervalFromString(interval, true); bar != "" {
params.Set("bar", bar)
}
if limit > 0 {
params.Set("limit", strconv.FormatUint(limit, 10))
}
var resp []SpreadCandlestick
return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getSpreadCandlesticksHistoryEPL, http.MethodGet, common.EncodeURLValues("market/sprd-history-candles", params), nil, &resp, request.UnauthenticatedRequest)
}
// CancelAllSpreadOrdersAfterCountdown cancel all pending orders after the countdown timeout. Only applicable to spread trading
func (ok *Okx) CancelAllSpreadOrdersAfterCountdown(ctx context.Context, timeoutDuration int64) (*SpreadOrderCancellationResponse, error) {
if (timeoutDuration != 0) && (timeoutDuration < 10 || timeoutDuration > 120) {

File diff suppressed because it is too large Load Diff

View File

@@ -3092,14 +3092,18 @@ type SpreadOrderbook struct {
// SpreadTicker represents a ticker instance
type SpreadTicker struct {
SpreadID string `json:"sprdId"`
Last types.Number `json:"last"`
LastSize types.Number `json:"lastSz"`
AskPrice types.Number `json:"askPx"`
AskSize types.Number `json:"askSz"`
BidPrice types.Number `json:"bidPx"`
BidSize types.Number `json:"bidSz"`
Timestamp types.Time `json:"ts"`
SpreadID string `json:"sprdId"`
Last types.Number `json:"last"`
LastSize types.Number `json:"lastSz"`
AskPrice types.Number `json:"askPx"`
AskSize types.Number `json:"askSz"`
BidPrice types.Number `json:"bidPx"`
BidSize types.Number `json:"bidSz"`
Open24Hour types.Number `json:"open24h"`
High24Hour types.Number `json:"high24h"`
Low24Hour types.Number `json:"low24h"`
Volume24Hour types.Number `json:"vol24h"`
Timestamp types.Time `json:"ts"`
}
// SpreadPublicTradeItem represents publicly available trade order instance
@@ -3112,6 +3116,22 @@ type SpreadPublicTradeItem struct {
Timestamp types.Time `json:"ts"`
}
// SpreadCandlestick represents a candlestick instance
type SpreadCandlestick struct {
Timestamp types.Time
Open types.Number
High types.Number
Low types.Number
Close types.Number
Volume types.Number
Confirm types.Number
}
// UnmarshalJSON unmarshals the JSON data into a SpreadCandlestick struct
func (s *SpreadCandlestick) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &[7]any{&s.Timestamp, &s.Open, &s.High, &s.Low, &s.Close, &s.Volume, &s.Confirm})
}
// UnitConvertResponse unit convert response
type UnitConvertResponse struct {
InstrumentID string `json:"instId"`
@@ -4731,6 +4751,21 @@ type CopyTradingLeadTrader struct {
CopyState string `json:"copyState"`
}
// LeadTraderRanksRequest represents lead trader ranks request parameters
type LeadTraderRanksRequest struct {
InstrumentType string // Instrument type e.g 'SWAP'. The default value is 'SWAP'
SortType string // Overview, the default value. pnl: profit and loss, aum: assets under management, win_ratio: win ratio,pnl_ratio: pnl ratio, current_copy_trader_pnl: current copy trader pnl
HasVacancy bool // false: include all lead traders (default), with or without vacancies; true: include only those with vacancies
MinLeadDays uint64 // 1: 7 days. 2: 30 days. 3: 90 days. 4: 180 days
MinAssets float64 // Minimum assets in USDT
MaxAssets float64 // Maximum assets in USDT
MinAssetsUnderManagement float64 // Minimum assets under management in USDT
MaxAssetsUnderManagement float64 // Maximum assets under management in USDT
DataVersion uint64 // It is 14 numbers. e.g. 20231010182400 used for pagination. A new version will be generated every 10 minutes. Only last 5 versions are stored. The default is latest version
Page uint64 // Page number for pagination
Limit uint64 // Number of results per request. The maximum is 20; the default is 10
}
// LeadTradersRank represents lead traders rank info
type LeadTradersRank struct {
DataVer string `json:"dataVer"`

View File

@@ -63,6 +63,11 @@ func (ok *Okx) SetDefaults() {
log.Errorln(log.ExchangeSys, err)
}
// TODO: Disabled until spread/business websocket is implemented
if err := ok.DisableAssetWebsocketSupport(asset.Spread); err != nil {
log.Errorf(log.ExchangeSys, "%s error disabling %q asset websocket support: %s", ok.Name, asset.Spread.String(), err)
}
// Fill out the capabilities/features that the exchange supports
ok.Features = exchange.Features{
CurrencyTranslations: currency.NewTranslations(map[currency.Code]currency.Code{
@@ -360,47 +365,75 @@ func (ok *Okx) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) err
// UpdateTicker updates and returns the ticker for a currency pair
func (ok *Okx) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) {
var err error
p, err = ok.FormatExchangeCurrency(p, a)
if err != nil {
return nil, err
}
if !ok.SupportsAsset(a) {
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, a)
}
mdata, err := ok.GetTicker(ctx, p.String())
p, err := ok.FormatExchangeCurrency(p, a)
if err != nil {
return nil, err
}
var baseVolume, quoteVolume float64
switch a {
case asset.Spot, asset.Margin:
baseVolume = mdata.Vol24H.Float64()
quoteVolume = mdata.VolCcy24H.Float64()
case asset.PerpetualSwap, asset.Futures, asset.Options:
baseVolume = mdata.VolCcy24H.Float64()
quoteVolume = mdata.Vol24H.Float64()
default:
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
err = ticker.ProcessTicker(&ticker.Price{
Last: mdata.LastTradePrice.Float64(),
High: mdata.High24H.Float64(),
Low: mdata.Low24H.Float64(),
Bid: mdata.BestBidPrice.Float64(),
BidSize: mdata.BestBidSize.Float64(),
Ask: mdata.BestAskPrice.Float64(),
AskSize: mdata.BestAskSize.Float64(),
Volume: baseVolume,
QuoteVolume: quoteVolume,
Open: mdata.Open24H.Float64(),
Pair: p,
ExchangeName: ok.Name,
AssetType: a,
})
if err != nil {
return nil, err
if a == asset.Spread {
spreadTicker, err := ok.GetPublicSpreadTickers(ctx, p.String())
if err != nil {
return nil, err
}
if len(spreadTicker) == 0 {
return nil, fmt.Errorf("no ticker data for %s", p.String())
}
if err := ticker.ProcessTicker(&ticker.Price{
Last: spreadTicker[0].Last.Float64(),
High: spreadTicker[0].High24Hour.Float64(),
Low: spreadTicker[0].Low24Hour.Float64(),
Bid: spreadTicker[0].BidPrice.Float64(),
BidSize: spreadTicker[0].BidSize.Float64(),
Ask: spreadTicker[0].AskPrice.Float64(),
AskSize: spreadTicker[0].AskSize.Float64(),
Volume: spreadTicker[0].Volume24Hour.Float64(),
Open: spreadTicker[0].Open24Hour.Float64(),
LastUpdated: spreadTicker[0].Timestamp.Time(),
Pair: p,
AssetType: a,
ExchangeName: ok.Name,
}); err != nil {
return nil, err
}
} else {
mdata, err := ok.GetTicker(ctx, p.String())
if err != nil {
return nil, err
}
var baseVolume, quoteVolume float64
switch a {
case asset.Spot, asset.Margin:
baseVolume = mdata.Vol24H.Float64()
quoteVolume = mdata.VolCcy24H.Float64()
case asset.PerpetualSwap, asset.Futures, asset.Options:
baseVolume = mdata.VolCcy24H.Float64()
quoteVolume = mdata.Vol24H.Float64()
}
if err := ticker.ProcessTicker(&ticker.Price{
Last: mdata.LastTradePrice.Float64(),
High: mdata.High24H.Float64(),
Low: mdata.Low24H.Float64(),
Bid: mdata.BestBidPrice.Float64(),
BidSize: mdata.BestBidSize.Float64(),
Ask: mdata.BestAskPrice.Float64(),
AskSize: mdata.BestAskSize.Float64(),
Volume: baseVolume,
QuoteVolume: quoteVolume,
Open: mdata.Open24H.Float64(),
Pair: p,
ExchangeName: ok.Name,
AssetType: a,
}); err != nil {
return nil, err
}
}
return ticker.GetTicker(ok.Name, p, a)
}
@@ -779,6 +812,10 @@ func (ok *Okx) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a
// GetHistoricTrades retrieves historic trade data within the timeframe provided
func (ok *Okx) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
if !ok.SupportsAsset(assetType) || assetType == asset.Spread {
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, assetType)
}
if timestampStart.Before(time.Now().Add(-kline.ThreeMonth.Duration())) {
return nil, errOnlyThreeMonthsSupported
}
@@ -1518,9 +1555,6 @@ func (ok *Okx) GetOrderInfo(ctx context.Context, orderID string, pair currency.P
return nil, currency.ErrCurrencyPairsEmpty
}
instrumentID := pairFormat.Format(pair)
if !ok.SupportsAsset(assetType) {
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, assetType)
}
orderDetail, err := ok.GetOrderDetail(ctx, &OrderDetailRequestParam{
InstrumentID: instrumentID,
OrderID: orderID,
@@ -1955,39 +1989,66 @@ func (ok *Okx) ValidateAPICredentials(ctx context.Context, assetType asset.Item)
// GetHistoricCandles returns candles between a time period for a set time interval
func (ok *Okx) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
if !ok.SupportsAsset(a) {
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, a)
}
req, err := ok.GetKlineRequest(pair, a, interval, start, end, false)
if err != nil {
return nil, err
}
candles, err := ok.GetCandlesticksHistory(ctx,
req.RequestFormatted.Base.String()+
currency.DashDelimiter+
req.RequestFormatted.Quote.String(),
req.ExchangeInterval,
start.Add(-time.Nanosecond), // Start time not inclusive of candle.
end,
300)
if err != nil {
return nil, err
}
var timeSeries []kline.Candle
switch a {
case asset.Spread:
candles, err := ok.GetSpreadCandlesticksHistory(ctx, req.RequestFormatted.String(), req.ExchangeInterval, start.Add(-time.Nanosecond), end, 100)
if err != nil {
return nil, err
}
timeSeries = make([]kline.Candle, len(candles))
for x := range candles {
timeSeries[x] = kline.Candle{
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(),
}
}
default:
candles, err := ok.GetCandlesticksHistory(ctx,
req.RequestFormatted.String(),
req.ExchangeInterval,
start.Add(-time.Nanosecond), // Start time not inclusive of candle.
end,
100)
if err != nil {
return nil, err
}
timeSeries := make([]kline.Candle, len(candles))
for x := range candles {
timeSeries[x] = kline.Candle{
Time: candles[x].OpenTime.Time(),
Open: candles[x].OpenPrice.Float64(),
High: candles[x].HighestPrice.Float64(),
Low: candles[x].LowestPrice.Float64(),
Close: candles[x].ClosePrice.Float64(),
Volume: candles[x].Volume.Float64(),
timeSeries = make([]kline.Candle, len(candles))
for x := range candles {
timeSeries[x] = kline.Candle{
Time: candles[x].OpenTime.Time(),
Open: candles[x].OpenPrice.Float64(),
High: candles[x].HighestPrice.Float64(),
Low: candles[x].LowestPrice.Float64(),
Close: candles[x].ClosePrice.Float64(),
Volume: candles[x].Volume.Float64(),
}
}
}
return req.ProcessResponse(timeSeries)
}
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
func (ok *Okx) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
if !ok.SupportsAsset(a) {
return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, a)
}
req, err := ok.GetKlineExtendedRequest(pair, a, interval, start, end)
if err != nil {
return nil, err
@@ -2002,27 +2063,47 @@ func (ok *Okx) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pai
timeSeries := make([]kline.Candle, 0, req.Size())
for y := range req.RangeHolder.Ranges {
var candles []CandleStick
candles, err = ok.GetCandlesticksHistory(ctx,
req.RequestFormatted.Base.String()+
currency.DashDelimiter+
req.RequestFormatted.Quote.String(),
req.ExchangeInterval,
req.RangeHolder.Ranges[y].Start.Time.Add(-time.Nanosecond), // Start time not inclusive of candle.
req.RangeHolder.Ranges[y].End.Time,
300)
if err != nil {
return nil, err
}
for x := range candles {
timeSeries = append(timeSeries, kline.Candle{
Time: candles[x].OpenTime.Time(),
Open: candles[x].OpenPrice.Float64(),
High: candles[x].HighestPrice.Float64(),
Low: candles[x].LowestPrice.Float64(),
Close: candles[x].ClosePrice.Float64(),
Volume: candles[x].Volume.Float64(),
})
switch a {
case asset.Spread:
candles, err := ok.GetSpreadCandlesticksHistory(ctx,
req.RequestFormatted.String(),
req.ExchangeInterval,
req.RangeHolder.Ranges[y].Start.Time.Add(-time.Nanosecond), // Start time not inclusive of candle.
req.RangeHolder.Ranges[y].End.Time,
100)
if err != nil {
return nil, err
}
for x := range candles {
timeSeries = append(timeSeries, kline.Candle{
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(),
})
}
default:
candles, err := ok.GetCandlesticksHistory(ctx,
req.RequestFormatted.String(),
req.ExchangeInterval,
req.RangeHolder.Ranges[y].Start.Time.Add(-time.Nanosecond), // Start time not inclusive of candle.
req.RangeHolder.Ranges[y].End.Time,
100)
if err != nil {
return nil, err
}
for x := range candles {
timeSeries = append(timeSeries, kline.Candle{
Time: candles[x].OpenTime.Time(),
Open: candles[x].OpenPrice.Float64(),
High: candles[x].HighestPrice.Float64(),
Low: candles[x].LowestPrice.Float64(),
Close: candles[x].ClosePrice.Float64(),
Volume: candles[x].Volume.Float64(),
})
}
}
}
return req.ProcessResponse(timeSeries)
@@ -2731,16 +2812,31 @@ func (ok *Okx) GetFuturesContractDetails(ctx context.Context, item asset.Item) (
}
resp := make([]futures.Contract, len(result))
for i := range result {
var cp, underlying currency.Pair
underlying, err = currency.NewPairFromString(result[i].Underlying)
cp, err := currency.NewPairFromString(result[i].InstrumentID)
if err != nil {
return nil, err
}
cp, err = currency.NewPairFromString(result[i].InstrumentID)
if err != nil {
return nil, err
var (
underlying currency.Pair
settleCurr currency.Code
contractSettlementType futures.ContractSettlementType
)
if result[i].State == "live" {
underlying, err = currency.NewPairFromString(result[i].Underlying)
if err != nil {
return nil, err
}
settleCurr = currency.NewCode(result[i].SettlementCurrency)
contractSettlementType = futures.Linear
if result[i].SettlementCurrency == result[i].BaseCurrency {
contractSettlementType = futures.Inverse
}
}
settleCurr := currency.NewCode(result[i].SettlementCurrency)
var ct futures.ContractType
if item == asset.PerpetualSwap {
ct = futures.Perpetual
@@ -2752,25 +2848,25 @@ func (ok *Okx) GetFuturesContractDetails(ctx context.Context, item asset.Item) (
ct = futures.Quarterly
}
}
contractSettlementType := futures.Linear
if result[i].SettlementCurrency == result[i].BaseCurrency {
contractSettlementType = futures.Inverse
}
resp[i] = futures.Contract{
Exchange: ok.Name,
Name: cp,
Underlying: underlying,
Asset: item,
StartDate: result[i].ListTime.Time(),
EndDate: result[i].ExpTime.Time(),
IsActive: result[i].State == "live",
Status: result[i].State,
Type: ct,
SettlementType: contractSettlementType,
SettlementCurrencies: currency.Currencies{settleCurr},
MarginCurrency: settleCurr,
Multiplier: result[i].ContractValue.Float64(),
MaxLeverage: result[i].MaxLeverage.Float64(),
Exchange: ok.Name,
Name: cp,
Underlying: underlying,
Asset: item,
StartDate: result[i].ListTime.Time(),
EndDate: result[i].ExpTime.Time(),
IsActive: result[i].State == "live",
Status: result[i].State,
Type: ct,
SettlementType: contractSettlementType,
MarginCurrency: settleCurr,
Multiplier: result[i].ContractValue.Float64(),
MaxLeverage: result[i].MaxLeverage.Float64(),
}
if !settleCurr.IsEmpty() {
resp[i].SettlementCurrencies = currency.Currencies{settleCurr}
}
}
return resp, nil

View File

@@ -254,6 +254,8 @@ const (
getSpreadOrderbookEPL
getSpreadTickerEPL
getSpreadPublicTradesEPL
getSpreadCandlesticksEPL
getSpreadCandlesticksHistoryEPL
cancelAllSpreadOrdersAfterEPL
getActiveSpreadOrdersEPL
getSpreadOrders7DaysEPL
@@ -581,20 +583,22 @@ var rateLimits = func() request.RateLimitDefinitions {
getBlockTickersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getBlockTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
// Spread Orders rate limiters
placeSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
cancelSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
cancelAllSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 10, 1),
amendSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadOrderDetailsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getActiveSpreadOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 10, 1),
getSpreadOrders7DaysEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadOrderTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadOrderbookEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadTickerEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadPublicTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
cancelAllSpreadOrdersAfterEPL: request.NewRateLimitWithWeight(oneSecondInterval, 1, 1),
// Spread trading related rate limiters
placeSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
cancelSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
cancelAllSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 10, 1),
amendSpreadOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadOrderDetailsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getActiveSpreadOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 10, 1),
getSpreadOrders7DaysEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadOrderTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadOrderbookEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadTickerEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadPublicTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
getSpreadCandlesticksEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 40, 1),
getSpreadCandlesticksHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),
cancelAllSpreadOrdersAfterEPL: request.NewRateLimitWithWeight(oneSecondInterval, 1, 1),
// Public Data Endpoints
getInstrumentsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, 20, 1),

View File

@@ -10,6 +10,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
)
func TestWSPlaceOrder(t *testing.T) {
@@ -20,8 +21,10 @@ func TestWSPlaceOrder(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
out := &PlaceOrderRequestParam{
InstrumentID: btcusdt,
InstrumentID: mainPair.String(),
TradeMode: TradeModeIsolated, // depending on portfolio settings this can also be TradeModeCash
Side: "Buy",
OrderType: "post_only",
@@ -46,8 +49,10 @@ func TestWSPlaceMultipleOrders(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
out := PlaceOrderRequestParam{
InstrumentID: btcusdt,
InstrumentID: mainPair.String(),
TradeMode: TradeModeIsolated, // depending on portfolio settings this can also be TradeModeCash
Side: "Buy",
OrderType: "post_only",
@@ -70,12 +75,14 @@ func TestWSCancelOrder(t *testing.T) {
_, err = ok.WSCancelOrder(t.Context(), &CancelOrderRequestParam{})
require.ErrorIs(t, err, errMissingInstrumentID)
_, err = ok.WSCancelOrder(t.Context(), &CancelOrderRequestParam{InstrumentID: btcusdt})
_, err = ok.WSCancelOrder(t.Context(), &CancelOrderRequestParam{InstrumentID: mainPair.String()})
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
got, err := ok.WSCancelOrder(request.WithVerbose(t.Context()), &CancelOrderRequestParam{InstrumentID: btcusdt, OrderID: "2341161427393388544"})
testexch.SetupWs(t, ok)
got, err := ok.WSCancelOrder(request.WithVerbose(t.Context()), &CancelOrderRequestParam{InstrumentID: mainPair.String(), OrderID: "2341161427393388544"})
require.NoError(t, err)
require.NotEmpty(t, got)
}
@@ -89,12 +96,14 @@ func TestWSCancelMultipleOrders(t *testing.T) {
_, err = ok.WSCancelMultipleOrders(t.Context(), []CancelOrderRequestParam{{}})
require.ErrorIs(t, err, errMissingInstrumentID)
_, err = ok.WSCancelMultipleOrders(t.Context(), []CancelOrderRequestParam{{InstrumentID: btcusdt}})
_, err = ok.WSCancelMultipleOrders(t.Context(), []CancelOrderRequestParam{{InstrumentID: mainPair.String()}})
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
got, err := ok.WSCancelMultipleOrders(request.WithVerbose(t.Context()), []CancelOrderRequestParam{{InstrumentID: btcusdt, OrderID: "2341184920998715392"}})
testexch.SetupWs(t, ok)
got, err := ok.WSCancelMultipleOrders(request.WithVerbose(t.Context()), []CancelOrderRequestParam{{InstrumentID: mainPair.String(), OrderID: "2341184920998715392"}})
require.NoError(t, err)
require.NotEmpty(t, got)
}
@@ -109,7 +118,7 @@ func TestWSAmendOrder(t *testing.T) {
_, err = ok.WSAmendOrder(t.Context(), out)
require.ErrorIs(t, err, errMissingInstrumentID)
out.InstrumentID = btcusdt
out.InstrumentID = mainPair.String()
_, err = ok.WSAmendOrder(t.Context(), out)
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
@@ -119,6 +128,8 @@ func TestWSAmendOrder(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
out.NewPrice = 21000
got, err := ok.WSAmendOrder(request.WithVerbose(t.Context()), out)
require.NoError(t, err)
@@ -135,7 +146,7 @@ func TestWSAmendMultipleOrders(t *testing.T) {
_, err = ok.WSAmendMultipleOrders(t.Context(), []AmendOrderRequestParams{out})
require.ErrorIs(t, err, errMissingInstrumentID)
out.InstrumentID = btcusdt
out.InstrumentID = mainPair.String()
_, err = ok.WSAmendMultipleOrders(t.Context(), []AmendOrderRequestParams{out})
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
@@ -144,6 +155,7 @@ func TestWSAmendMultipleOrders(t *testing.T) {
require.ErrorIs(t, err, errInvalidNewSizeOrPriceInformation)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
out.NewPrice = 20000
got, err := ok.WSAmendMultipleOrders(request.WithVerbose(t.Context()), []AmendOrderRequestParams{out})
@@ -163,10 +175,11 @@ func TestWSMassCancelOrders(t *testing.T) {
require.ErrorIs(t, err, errInstrumentFamilyRequired)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
err = ok.WSMassCancelOrders(request.WithVerbose(t.Context()), []CancelMassReqParam{
{
InstrumentType: "OPTION",
InstrumentFamily: "BTC-USD",
InstrumentFamily: optionsPair.String(),
},
})
require.NoError(t, err)
@@ -178,8 +191,9 @@ func TestWSPlaceSpreadOrder(t *testing.T) {
require.ErrorIs(t, err, common.ErrNilPointer)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
result, err := ok.WSPlaceSpreadOrder(request.WithVerbose(t.Context()), &SpreadOrderParam{
SpreadID: "BTC-USDT_BTC-USDT-SWAP",
SpreadID: spreadPair.String(),
ClientOrderID: "b15",
Side: order.Buy.Lower(),
OrderType: "limit",
@@ -200,6 +214,7 @@ func TestWSAmendSpreadOrder(t *testing.T) {
require.ErrorIs(t, err, errSizeOrPriceIsRequired)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
result, err := ok.WSAmendSpreadOrder(request.WithVerbose(t.Context()), &AmendSpreadOrderParam{
OrderID: "2510789768709120",
NewSize: 2,
@@ -213,6 +228,7 @@ func TestWSCancelSpreadOrder(t *testing.T) {
_, err := ok.WSCancelSpreadOrder(t.Context(), "", "")
require.ErrorIs(t, err, order.ErrOrderIDNotSet)
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
testexch.SetupWs(t, ok)
result, err := ok.WSCancelSpreadOrder(request.WithVerbose(t.Context()), "1234", "")
require.NoError(t, err)
assert.NotNil(t, result)
@@ -221,7 +237,8 @@ func TestWSCancelSpreadOrder(t *testing.T) {
func TestWSCancelAllSpreadOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok, canManipulateRealOrders)
err := ok.WSCancelAllSpreadOrders(request.WithVerbose(t.Context()), "BTC-USDT_BTC-USDT-SWAP")
testexch.SetupWs(t, ok)
err := ok.WSCancelAllSpreadOrders(request.WithVerbose(t.Context()), spreadPair.String())
require.NoError(t, err)
}

View File

@@ -191,7 +191,7 @@ func SetupWs(tb testing.TB, e exchange.IBotExchange) {
w.GenerateSubs = func() (subscription.List, error) { return subscription.List{}, nil }
err = w.Connect()
require.NoError(tb, err, "WsConnect should not error")
require.NoError(tb, err, "Connect must not error")
setupWsOnce[e] = true
}