exchanges: Use singular futures settlement currency (#2092)

* Change settlement to singular currency

* whoops.go

* bitmex fix

* minor updates

* 64 divided by 2

* whoops2.go

* ROBOT ROCK

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* ROCK ROCK ROCK ROCK ROBOT

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* shazNit

* currencies unmarshal and code use

* Update currency/currencies.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/btse/btse_wrapper.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* reuse comment for better clarity

* collapses entire thing

* shazLint

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
This commit is contained in:
Scott
2025-11-10 13:21:54 +11:00
committed by GitHub
parent 9441f33f42
commit 61d720b72f
19 changed files with 399 additions and 471 deletions

View File

@@ -15,7 +15,6 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
@@ -642,10 +641,7 @@ func TestWsUnexpectedData(t *testing.T) {
func TestGetFuturesContractDetails(t *testing.T) {
t.Parallel()
_, err := e.GetFuturesContractDetails(t.Context(), asset.Spot)
assert.ErrorIs(t, err, futures.ErrNotFuturesAsset, "GetFuturesContractDetails should error correctly on Spot")
_, err = e.GetFuturesContractDetails(t.Context(), asset.USDTMarginedFutures)
_, err := e.GetFuturesContractDetails(t.Context(), asset.Margin)
assert.ErrorIs(t, err, asset.ErrNotSupported, "GetFuturesContractDetails should error correctly on Margin")
_, err = e.GetFuturesContractDetails(t.Context(), asset.Futures)
@@ -771,18 +767,18 @@ func TestMarketPair(t *testing.T) {
for _, tt := range []struct {
symbol string
base string
base currency.Code
futures bool
expectedErr error
expectedSymbol string
}{
{symbol: "RUNEPFC", base: currency.RUNE.String(), futures: true, expectedSymbol: "RUNEPFC"},
{symbol: "TRUMPPFC", base: "TRUMPSOL", futures: true, expectedSymbol: "TRUMPPFC"},
{symbol: "BTCUSD", base: "NAUGHTYBASE", futures: true, expectedErr: errInvalidPairSymbol},
{symbol: "NAUGHTYSYMBOL", base: currency.BTC.String(), expectedErr: errInvalidPairSymbol},
{symbol: "BTC-USD", base: currency.BTC.String(), expectedSymbol: "BTCUSD"},
{symbol: "RUNEPFC", base: currency.RUNE, futures: true, expectedSymbol: "RUNEPFC"},
{symbol: "TRUMPPFC", base: currency.NewCode("TRUMPSOL"), futures: true, expectedSymbol: "TRUMPPFC"},
{symbol: "BTCUSD", base: currency.NewCode("NAUGHTYBASE"), futures: true, expectedErr: errInvalidPairSymbol},
{symbol: "NAUGHTYSYMBOL", base: currency.BTC, expectedErr: errInvalidPairSymbol},
{symbol: "BTC-USD", base: currency.BTC, expectedSymbol: "BTCUSD"},
} {
mp := MarketPair{Symbol: tt.symbol, Base: tt.base, Quote: "USD", Futures: tt.futures}
mp := MarketPair{Symbol: tt.symbol, Base: tt.base, Quote: currency.USD, Futures: tt.futures}
p, err := mp.Pair()
assert.ErrorIs(t, err, tt.expectedErr, "Pair should not error")
assert.Equal(t, tt.expectedSymbol, p.String(), "Pair should return the expected symbol")

View File

@@ -28,41 +28,41 @@ type MarketSummary []*MarketPair
// MarketPair is a single pair in Market Summary
type MarketPair struct {
Symbol string `json:"symbol"`
Last float64 `json:"last"`
LowestAsk float64 `json:"lowestAsk"`
HighestBid float64 `json:"highestBid"`
PercentageChange float64 `json:"percentageChange"`
Volume float64 `json:"volume"`
High24Hr float64 `json:"high24Hr"`
Low24Hr float64 `json:"low24Hr"`
Base string `json:"base"`
Quote string `json:"quote"`
Active bool `json:"active"`
Size float64 `json:"size"`
MinValidPrice float64 `json:"minValidPrice"`
MinPriceIncrement float64 `json:"minPriceIncrement"`
MinOrderSize float64 `json:"minOrderSize"`
MaxOrderSize float64 `json:"maxOrderSize"`
MinSizeIncrement float64 `json:"minSizeIncrement"`
OpenInterest float64 `json:"openInterest"`
OpenInterestUSD float64 `json:"openInterestUSD"`
ContractStart int64 `json:"contractStart"`
ContractEnd int64 `json:"contractEnd"`
TimeBasedContract bool `json:"timeBasedContract"`
OpenTime types.Time `json:"openTime"`
CloseTime types.Time `json:"closeTime"`
StartMatching int64 `json:"startMatching"`
InactiveTime types.Time `json:"inactiveTime"`
FundingRate float64 `json:"fundingRate"`
ContractSize float64 `json:"contractSize"`
MaxPosition int64 `json:"maxPosition"`
MinRiskLimit int `json:"minRiskLimit"`
MaxRiskLimit int `json:"maxRiskLimit"`
AvailableSettlement []string `json:"availableSettlement"`
Futures bool `json:"futures"`
IsMarketOpenToSpot bool `json:"isMarketOpenToSpot"`
IsMarketOpenToOTC bool `json:"isMarketOpenToOtc"`
Symbol string `json:"symbol"`
Last float64 `json:"last"`
LowestAsk float64 `json:"lowestAsk"`
HighestBid float64 `json:"highestBid"`
PercentageChange float64 `json:"percentageChange"`
Volume float64 `json:"volume"`
High24Hr float64 `json:"high24Hr"`
Low24Hr float64 `json:"low24Hr"`
Base currency.Code `json:"base"`
Quote currency.Code `json:"quote"`
Active bool `json:"active"`
Size float64 `json:"size"`
MinValidPrice float64 `json:"minValidPrice"`
MinPriceIncrement float64 `json:"minPriceIncrement"`
MinOrderSize float64 `json:"minOrderSize"`
MaxOrderSize float64 `json:"maxOrderSize"`
MinSizeIncrement float64 `json:"minSizeIncrement"`
OpenInterest float64 `json:"openInterest"`
OpenInterestUSD float64 `json:"openInterestUSD"`
ContractStart int64 `json:"contractStart"`
ContractEnd int64 `json:"contractEnd"`
TimeBasedContract bool `json:"timeBasedContract"`
OpenTime types.Time `json:"openTime"`
CloseTime types.Time `json:"closeTime"`
StartMatching int64 `json:"startMatching"`
InactiveTime types.Time `json:"inactiveTime"`
FundingRate float64 `json:"fundingRate"`
ContractSize float64 `json:"contractSize"`
MaxPosition int64 `json:"maxPosition"`
MinRiskLimit int `json:"minRiskLimit"`
MaxRiskLimit int `json:"maxRiskLimit"`
AvailableSettlement currency.Currencies `json:"availableSettlement"`
Futures bool `json:"futures"`
IsMarketOpenToSpot bool `json:"isMarketOpenToSpot"`
IsMarketOpenToOTC bool `json:"isMarketOpenToOtc"`
}
// OHLCV holds Open, High Low, Close, Volume data for set symbol

View File

@@ -996,27 +996,28 @@ func (m *MarketPair) StripExponent() (string, error) {
// Pair returns the currency Pair for a MarketPair
func (m *MarketPair) Pair() (currency.Pair, error) {
baseCurr := m.Base
var quoteCurr string
var quoteStr string
if m.Futures {
if baseCurr == "TRUMPSOL" { // Only base currency which is different to the rest
baseCurr = "TRUMP"
quoteCurr = strings.TrimPrefix(m.Symbol, baseCurr)
if baseCurr.String() == "TRUMPSOL" { // Only base currency which is different to the rest
baseCurr = currency.TRUMP
quoteStr = strings.TrimPrefix(m.Symbol, baseCurr.String())
} else {
s := strings.Split(m.Symbol, m.Base) // e.g. RUNEPFC for RUNE-USD futures pair
// Quote field is the settlement currency, create the quote currency from the symbol
s := strings.Split(m.Symbol, m.Base.String())
if len(s) <= 1 {
return currency.EMPTYPAIR, errInvalidPairSymbol
}
quoteCurr = s[1]
quoteStr = s[1]
}
} else {
s := strings.Split(m.Symbol, currency.DashDelimiter)
if len(s) != 2 {
return currency.EMPTYPAIR, errInvalidPairSymbol
}
baseCurr = s[0]
quoteCurr = s[1]
baseCurr = currency.NewCode(s[0])
quoteStr = s[1]
}
return currency.NewPairFromStrings(baseCurr, quoteCurr)
return currency.NewPair(baseCurr, currency.NewCode(quoteStr)), nil
}
// GetMarketSummary returns filtered market pair details; Specifically:
@@ -1055,9 +1056,6 @@ func (e *Exchange) GetMarketSummary(ctx context.Context, symbol string, spot boo
// GetFuturesContractDetails returns details about futures contracts
func (e *Exchange) GetFuturesContractDetails(ctx context.Context, item asset.Item) ([]futures.Contract, error) {
if !item.IsFutures() {
return nil, futures.ErrNotFuturesAsset
}
if item != asset.Futures {
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, item)
}
@@ -1065,67 +1063,46 @@ func (e *Exchange) GetFuturesContractDetails(ctx context.Context, item asset.Ite
if err != nil {
return nil, err
}
resp := make([]futures.Contract, 0, len(marketSummary))
resp := make([]futures.Contract, len(marketSummary))
for i := range marketSummary {
var cp currency.Pair
cp, err = currency.NewPairFromStrings(marketSummary[i].Base, marketSummary[i].Symbol[len(marketSummary[i].Base):])
if err != nil {
return nil, err
}
settlementCurrencies := make(currency.Currencies, len(marketSummary[i].AvailableSettlement))
var startTime, endTime time.Time
var ct futures.ContractType
if !marketSummary[i].OpenTime.Time().IsZero() {
startTime = marketSummary[i].OpenTime.Time()
}
if !marketSummary[i].CloseTime.Time().IsZero() {
endTime = marketSummary[i].CloseTime.Time()
}
// Quote field is the settlement currency, create the quote currency from the symbol
quote := currency.NewCode(marketSummary[i].Symbol[len(marketSummary[i].Base.String()):])
cp := currency.NewPair(marketSummary[i].Base, quote)
startTime := marketSummary[i].OpenTime.Time()
endTime := marketSummary[i].CloseTime.Time()
ct := futures.Perpetual
if marketSummary[i].TimeBasedContract {
if endTime.Sub(startTime) > kline.OneMonth.Duration() {
ct = futures.Quarterly
} else {
ct = futures.Monthly
}
} else {
ct = futures.Perpetual
}
var contractSettlementType futures.ContractSettlementType
for j := range marketSummary[i].AvailableSettlement {
settlementCurrencies[j] = currency.NewCode(marketSummary[i].AvailableSettlement[j])
if contractSettlementType == futures.LinearOrInverse {
continue
}
containsUSD := strings.Contains(marketSummary[i].AvailableSettlement[j], "USD")
if !containsUSD {
contractSettlementType = futures.LinearOrInverse
continue
}
if containsUSD {
contractSettlementType = futures.Linear
}
}
c := futures.Contract{
Exchange: e.Name,
Name: cp,
Underlying: currency.NewPair(currency.NewCode(marketSummary[i].Base), currency.NewCode(marketSummary[i].Quote)),
Asset: item,
SettlementCurrencies: settlementCurrencies,
StartDate: startTime,
EndDate: endTime,
SettlementType: contractSettlementType,
IsActive: marketSummary[i].Active,
Type: ct,
contractSettlementType := futures.LinearOrInverse
if marketSummary[i].AvailableSettlement.Contains(currency.USD) {
contractSettlementType = futures.Linear
}
var rate fundingrate.Rate
if marketSummary[i].FundingRate > 0 {
c.LatestRate = fundingrate.Rate{
rate = fundingrate.Rate{
Rate: decimal.NewFromFloat(marketSummary[i].FundingRate),
Time: time.Now().Truncate(time.Hour),
}
}
resp = append(resp, c)
resp[i] = futures.Contract{
Exchange: e.Name,
Name: cp,
Underlying: currency.NewPair(marketSummary[i].Base, marketSummary[i].Quote),
Asset: item,
SettlementCurrency: currency.USDT,
AdditionalSettlementCurrencies: marketSummary[i].AvailableSettlement,
StartDate: startTime,
EndDate: endTime,
SettlementType: contractSettlementType,
IsActive: marketSummary[i].Active,
Type: ct,
LatestRate: rate,
}
}
return resp, nil
}