Skip to content

Commit

Permalink
exchanges/futures: Implement open interest (thrasher-corp#1417)
Browse files Browse the repository at this point in the history
* adds open interest to exchanges

* ADDS TESTING YEAH

* New endpoints, BTSE, RPCS, cached

* slight design change, begin gateio

You will need to get cached for
each exchange that supports it

* gateio, huobi, rpc

* fix up kraken, cache retrieval

* okx, gateio

* finalising all implementations and tests

* definitely my final ever commit on this

* Well, well, well

* final v2

* quick fix of bug

* test coverage, assert notempty, test helper

Added a new testhelper for currency
management because its very annoying
in a parallel test setting which wastes
so much space otherwise

* minimises REST requests for Open Interest

* types.Number merge misses

* Minimises Kraken REST calls

* len change, value -> pointer receiver

* further fixup

* fixes gateio, batch calculates open interest

* single gateio, lint const fixes

* rejig and more thorough oi for huobi

* formatting expansion

* minor fix for handling expiring contracts

* rm unused Binance strings

* add bybit support, fix bybit issues

* oopsie doopsie, dont look at my whoopsie

* Fix issue, remove feature

* move an irrelevant function for the pr

* mini bybit upgrades

* fixes cli request bug
  • Loading branch information
gloriousCode authored Jan 12, 2024
1 parent 6140421 commit b71bf1f
Show file tree
Hide file tree
Showing 62 changed files with 20,424 additions and 7,859 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/engine"
Expand Down Expand Up @@ -190,7 +191,9 @@ func executeExchangeWrapperTests(ctx context.Context, t *testing.T, exch exchang
input.AssignableTo(orderModifyParam) ||
input.AssignableTo(orderCancelParam) ||
input.AssignableTo(orderCancelsParam) ||
input.AssignableTo(getOrdersRequestParam) {
input.AssignableTo(pairKeySliceParam) ||
input.AssignableTo(getOrdersRequestParam) ||
input.AssignableTo(pairKeySliceParam) {
// this allows wrapper functions that support assets types
// to be tested with all supported assets
assetLen = len(assetParams) - 1
Expand Down Expand Up @@ -290,6 +293,7 @@ var (
positionSummaryRequestParam = reflect.TypeOf((**futures.PositionSummaryRequest)(nil)).Elem()
positionsRequestParam = reflect.TypeOf((**futures.PositionsRequest)(nil)).Elem()
latestRateRequest = reflect.TypeOf((**fundingrate.LatestRateRequest)(nil)).Elem()
pairKeySliceParam = reflect.TypeOf((*[]key.PairAsset)(nil)).Elem()
)

// generateMethodArg determines the argument type and returns a pre-made
Expand All @@ -315,6 +319,12 @@ func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodAr
// OrderID
input = reflect.ValueOf("1337")
}
case argGenerator.MethodInputType.AssignableTo(pairKeySliceParam):
input = reflect.ValueOf(key.PairAsset{
Base: argGenerator.AssetParams.Pair.Base.Item,
Quote: argGenerator.AssetParams.Pair.Quote.Item,
Asset: argGenerator.AssetParams.Asset,
})
case argGenerator.MethodInputType.AssignableTo(credentialsParam):
input = reflect.ValueOf(&account.Credentials{
Key: "test",
Expand Down
99 changes: 99 additions & 0 deletions cmd/gctcli/futures.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,30 @@ var futuresCommands = &cli.Command{
},
},
},
{
Name: "getopeninterest",
Aliases: []string{"goi", "oi"},
Usage: "gets the open interest for provided exchange asset pair, if asset pair is not present, return all available if supported",
ArgsUsage: "<exchange> <asset> <pair>",
Action: getOpenInterest,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "exchange",
Aliases: []string{"e"},
Usage: "the exchange to retrieve open interest from",
},
&cli.StringFlag{
Name: "asset",
Aliases: []string{"a"},
Usage: "optional - the asset type of the currency pair, must be a futures type",
},
&cli.StringFlag{
Name: "pair",
Aliases: []string{"p"},
Usage: "optional - the currency pair",
},
},
},
},
}

Expand Down Expand Up @@ -1611,3 +1635,78 @@ func setMarginType(c *cli.Context) error {
jsonOutput(result)
return nil
}

func getOpenInterest(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var (
exchangeName, assetType, currencyPair string
err error
)
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}

if c.IsSet("asset") {
assetType = c.String("asset")
} else {
assetType = c.Args().Get(1)
}

if assetType != "" {
err = isFuturesAsset(assetType)
if err != nil {
return err
}
}

if c.IsSet("pair") {
currencyPair = c.String("pair")
} else {
currencyPair = c.Args().Get(2)
}
var pair currency.Pair
if currencyPair != "" {
if !validPair(currencyPair) {
return fmt.Errorf("%w currencypair:%v", errInvalidPair, currencyPair)
}
pair, err = currency.NewPairDelimiter(currencyPair, pairDelimiter)
if err != nil {
return err
}
}

data := make([]*gctrpc.OpenInterestDataRequest, 0, 1)
if !pair.IsEmpty() {
data = append(data, &gctrpc.OpenInterestDataRequest{
Asset: assetType,
Pair: &gctrpc.CurrencyPair{
Delimiter: pair.Delimiter,
Base: pair.Base.String(),
Quote: pair.Quote.String(),
},
})
}

conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)

client := gctrpc.NewGoCryptoTraderServiceClient(conn)
result, err := client.GetOpenInterest(c.Context,
&gctrpc.GetOpenInterestRequest{
Exchange: exchangeName,
Data: data,
})
if err != nil {
return err
}

jsonOutput(result)
return nil
}
25 changes: 25 additions & 0 deletions common/key/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,44 @@ type SubAccountCurrencyAsset struct {
Asset asset.Item
}

// Pair combines the base and quote into a pair
func (k *PairAsset) Pair() currency.Pair {
if k == nil || (k.Base == nil && k.Quote == nil) {
return currency.EMPTYPAIR
}
return currency.NewPair(k.Base.Currency(), k.Quote.Currency())
}

// Pair combines the base and quote into a pair
func (k *ExchangePairAsset) Pair() currency.Pair {
if k == nil || (k.Base == nil && k.Quote == nil) {
return currency.EMPTYPAIR
}
return currency.NewPair(k.Base.Currency(), k.Quote.Currency())
}

// MatchesExchangeAsset checks if the key matches the exchange and asset
func (k *ExchangePairAsset) MatchesExchangeAsset(exch string, item asset.Item) bool {
if k == nil {
return false
}
return strings.EqualFold(k.Exchange, exch) && k.Asset == item
}

// MatchesPairAsset checks if the key matches the pair and asset
func (k *ExchangePairAsset) MatchesPairAsset(pair currency.Pair, item asset.Item) bool {
if k == nil {
return false
}
return k.Base == pair.Base.Item &&
k.Quote == pair.Quote.Item &&
k.Asset == item
}

// MatchesExchange checks if the exchange matches
func (k *ExchangePairAsset) MatchesExchange(exch string) bool {
if k == nil {
return false
}
return strings.EqualFold(k.Exchange, exch)
}
39 changes: 39 additions & 0 deletions common/key/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package key
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
Expand Down Expand Up @@ -70,3 +71,41 @@ func TestMatchesExchange(t *testing.T) {
t.Error("expected false")
}
}

func TestExchangePairAsset_Pair(t *testing.T) {
t.Parallel()
cp := currency.NewPair(currency.BTC, currency.USD)
k := ExchangePairAsset{
Base: currency.BTC.Item,
Quote: currency.USD.Item,
Asset: asset.Spot,
}
assert.Equal(t, cp, k.Pair())

cp = currency.NewPair(currency.BTC, currency.EMPTYCODE)
k.Quote = currency.EMPTYCODE.Item
assert.Equal(t, cp, k.Pair())

cp = currency.EMPTYPAIR
var epa *ExchangePairAsset
assert.Equal(t, cp, epa.Pair())
}

func TestPairAsset_Pair(t *testing.T) {
t.Parallel()
cp := currency.NewPair(currency.BTC, currency.USD)
k := PairAsset{
Base: currency.BTC.Item,
Quote: currency.USD.Item,
Asset: asset.Spot,
}
assert.Equal(t, cp, k.Pair())

cp = currency.NewPair(currency.BTC, currency.EMPTYCODE)
k.Quote = currency.EMPTYCODE.Item
assert.Equal(t, cp, k.Pair())

cp = currency.EMPTYPAIR
var pa *PairAsset
assert.Equal(t, cp, pa.Pair())
}
4 changes: 4 additions & 0 deletions currency/code_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3066,6 +3066,10 @@ var (
SHIB1000 = NewCode("SHIB1000")
SWEAT = NewCode("SWEAT")
TOMI = NewCode("TOMI")
BONK = NewCode("BONK")
WIF = NewCode("WIF")
AIDOGE = NewCode("AIDOGE")
PEPE = NewCode("PEPE")

stables = Currencies{
USDT,
Expand Down
3 changes: 2 additions & 1 deletion engine/event_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
)

func TestSetupEventManager(t *testing.T) {
Expand Down Expand Up @@ -299,7 +300,7 @@ func TestCheckEventCondition(t *testing.T) {
}
m.m.Lock()
err = m.checkEventCondition(&m.events[0])
if err != nil && !strings.Contains(err.Error(), "no tickers associated") {
if err != nil && !errors.Is(err, ticker.ErrNoTickerFound) {
t.Error(err)
} else if err == nil {
t.Error("expected error")
Expand Down
51 changes: 51 additions & 0 deletions engine/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/common/file"
"github.com/thrasher-corp/gocryptotrader/common/file/archive"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/common/timeperiods"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
Expand Down Expand Up @@ -5948,3 +5949,53 @@ func (s *RPCServer) ChangePositionMargin(ctx context.Context, r *gctrpc.ChangePo
MarginSide: r.MarginSide,
}, nil
}

// GetOpenInterest fetches the open interest from the exchange
func (s *RPCServer) GetOpenInterest(ctx context.Context, r *gctrpc.GetOpenInterestRequest) (*gctrpc.GetOpenInterestResponse, error) {
if r == nil {
return nil, fmt.Errorf("%w GetOpenInterestRequest", common.ErrNilPointer)
}
exch, err := s.GetExchangeByName(r.Exchange)
if err != nil {
return nil, err
}
if !exch.IsEnabled() {
return nil, fmt.Errorf("%s %w", r.Exchange, errExchangeNotEnabled)
}
feat := exch.GetSupportedFeatures()
if !feat.FuturesCapabilities.OpenInterest.Supported {
return nil, common.ErrFunctionNotSupported
}
keys := make([]key.PairAsset, len(r.Data))
for i := range r.Data {
var a asset.Item
a, err = asset.New(r.Data[i].Asset)
if err != nil {
return nil, err
}
keys[i].Base = currency.NewCode(r.Data[i].Pair.Base).Item
keys[i].Quote = currency.NewCode(r.Data[i].Pair.Quote).Item
keys[i].Asset = a
}

openInterest, err := exch.GetOpenInterest(ctx, keys...)
if err != nil {
return nil, err
}

resp := make([]*gctrpc.OpenInterestDataResponse, len(openInterest))
for i := range openInterest {
resp[i] = &gctrpc.OpenInterestDataResponse{
Exchange: openInterest[i].Key.Exchange,
Pair: &gctrpc.CurrencyPair{
Base: openInterest[i].Key.Base.String(),
Quote: openInterest[i].Key.Quote.String(),
},
Asset: openInterest[i].Key.Asset.String(),
OpenInterest: openInterest[i].OpenInterest,
}
}
return &gctrpc.GetOpenInterestResponse{
Data: resp,
}, nil
}
Loading

0 comments on commit b71bf1f

Please sign in to comment.