Skip to content

Commit

Permalink
engine/exchanges: Add GetCurrencyTradeURL wrapper func/gRPC endpoint (#…
Browse files Browse the repository at this point in the history
…1521)

* beginning the concept

* expands testing and implementations

* test standardisation, expansion

* more

* finish remainder, add rpc func

* lint

* grammar? I barely know her!

* wow i forgot something wow wow wow wow

* rm SendPayload

* enFixio!

* improve binance long-dated support

* update test design, update wrapper funcs

* adds kraken futures, adds bybit support

* fixes SIMPLE bugs

* s is for sucks

* fixed option url x2

* fixed silly test
  • Loading branch information
gloriousCode authored May 3, 2024
1 parent 0676c78 commit f1ff951
Show file tree
Hide file tree
Showing 83 changed files with 3,911 additions and 2,254 deletions.
87 changes: 87 additions & 0 deletions cmd/gctcli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -4562,3 +4562,90 @@ func getMarginRatesHistory(c *cli.Context) error {
jsonOutput(result)
return nil
}

var getCurrencyTradeURLCommand = &cli.Command{
Name: "getcurrencytradeurl",
Usage: "returns the trading url of the instrument",
ArgsUsage: "<exchange> <asset> <pair>",
Action: getCurrencyTradeURL,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "exchange",
Aliases: []string{"e"},
Usage: "the exchange to retrieve margin rates from",
},
&cli.StringFlag{
Name: "asset",
Aliases: []string{"a"},
Usage: "the asset type of the currency pair",
},
&cli.StringFlag{
Name: "pair",
Aliases: []string{"p"},
Usage: "the currency pair",
},
},
}

func getCurrencyTradeURL(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}

var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}

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

if !validAsset(assetType) {
return errInvalidAsset
}

var cp string
if c.IsSet("pair") {
cp = c.String("pair")
} else {
cp = c.Args().Get(2)
}

if !validPair(cp) {
return errInvalidPair
}
p, err := currency.NewPairDelimiter(cp, pairDelimiter)
if err != nil {
return err
}

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

client := gctrpc.NewGoCryptoTraderServiceClient(conn)
result, err := client.GetCurrencyTradeURL(c.Context,
&gctrpc.GetCurrencyTradeURLRequest{
Exchange: exchangeName,
Asset: assetType,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
})
if err != nil {
return err
}

jsonOutput(result)
return nil
}
1 change: 1 addition & 0 deletions cmd/gctcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ func main() {
technicalAnalysisCommand,
getMarginRatesHistoryCommand,
orderbookCommand,
getCurrencyTradeURLCommand,
}

ctx, cancel := context.WithCancel(context.Background())
Expand Down
33 changes: 33 additions & 0 deletions engine/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6016,3 +6016,36 @@ func (s *RPCServer) GetOpenInterest(ctx context.Context, r *gctrpc.GetOpenIntere
Data: resp,
}, nil
}

// GetCurrencyTradeURL returns the URL for the trading pair
func (s *RPCServer) GetCurrencyTradeURL(ctx context.Context, r *gctrpc.GetCurrencyTradeURLRequest) (*gctrpc.GetCurrencyTradeURLResponse, error) {
if r == nil {
return nil, fmt.Errorf("%w GetCurrencyTradeURLRequest", 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)
}
ai, err := asset.New(r.Asset)
if err != nil {
return nil, err
}
if r.Pair == nil ||
(r.Pair.Base == "" && r.Pair.Quote == "") {
return nil, currency.ErrCurrencyPairEmpty
}
cp, err := exch.MatchSymbolWithAvailablePairs(r.Pair.Base+r.Pair.Quote, ai, false)
if err != nil {
return nil, err
}
url, err := exch.GetCurrencyTradeURL(ctx, ai, cp)
if err != nil {
return nil, err
}
return &gctrpc.GetCurrencyTradeURLResponse{
Url: url,
}, nil
}
56 changes: 56 additions & 0 deletions engine/rpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ func (f fExchange) GetHistoricCandlesExtended(_ context.Context, p currency.Pair
}, nil
}

func (f fExchange) GetCurrencyTradeURL(_ context.Context, _ asset.Item, _ currency.Pair) (string, error) {
return "https://google.com", nil
}

func (f fExchange) GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error) {
leet := decimal.NewFromInt(1337)
rates := []margin.Rate{
Expand Down Expand Up @@ -4290,3 +4294,55 @@ func TestRPCProxyAuthClient(t *testing.T) {
})
}
}

func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
em := NewExchangeManager()
exch, err := em.NewExchangeByName("binance")
require.NoError(t, err)

exch.SetDefaults()
b := exch.GetBase()
b.Name = fakeExchangeName
b.Enabled = true
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
err = b.CurrencyPairs.Store(asset.Spot, &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
Enabled: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)},
Available: []currency.Pair{currency.NewPair(currency.BTC, currency.USDT)},
RequestFormat: &currency.PairFormat{Uppercase: true},
ConfigFormat: &currency.PairFormat{Uppercase: true},
})
require.NoError(t, err)

fakeExchange := fExchange{
IBotExchange: exch,
}
err = em.Add(fakeExchange)
require.NoError(t, err)

s := RPCServer{Engine: &Engine{ExchangeManager: em}}
_, err = s.GetCurrencyTradeURL(context.Background(), nil)
assert.ErrorIs(t, err, common.ErrNilPointer)

req := &gctrpc.GetCurrencyTradeURLRequest{}
_, err = s.GetCurrencyTradeURL(context.Background(), req)
assert.ErrorIs(t, err, ErrExchangeNameIsEmpty)

req.Exchange = fakeExchangeName
_, err = s.GetCurrencyTradeURL(context.Background(), req)
assert.ErrorIs(t, err, asset.ErrNotSupported)

req.Asset = "spot"
_, err = s.GetCurrencyTradeURL(context.Background(), req)
assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty)

req.Pair = &gctrpc.CurrencyPair{
Delimiter: "-",
Base: "btc",
Quote: "usdt",
}
resp, err := s.GetCurrencyTradeURL(context.Background(), req)
assert.NoError(t, err)
assert.NotEmpty(t, resp.Url)
}
1 change: 1 addition & 0 deletions exchanges/binance/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
spotAPIURL = "https://sapi.binance.com"
cfuturesAPIURL = "https://dapi.binance.com"
ufuturesAPIURL = "https://fapi.binance.com"
tradeBaseURL = "https://www.binance.com/en/"

testnetSpotURL = "https://testnet.binance.vision/api"
testnetFutures = "https://testnet.binancefuture.com"
Expand Down
13 changes: 13 additions & 0 deletions exchanges/binance/binance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3489,3 +3489,16 @@ func TestGetOpenInterest(t *testing.T) {
})
assert.ErrorIs(t, err, asset.ErrNotSupported)
}

func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, b)
for _, a := range b.GetAssetTypes(false) {
pairs, err := b.CurrencyPairs.GetPairs(a, false)
require.NoError(t, err, "cannot get pairs for %s", a)
require.NotEmpty(t, pairs, "no pairs for %s", a)
resp, err := b.GetCurrencyTradeURL(context.Background(), a, pairs[0])
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
}
64 changes: 64 additions & 0 deletions exchanges/binance/binance_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3046,3 +3046,67 @@ func (b *Binance) 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 (b *Binance) GetCurrencyTradeURL(ctx context.Context, a asset.Item, cp currency.Pair) (string, error) {
_, err := b.CurrencyPairs.IsPairEnabled(cp, a)
if err != nil {
return "", err
}
symbol, err := b.FormatSymbol(cp, a)
if err != nil {
return "", err
}
switch a {
case asset.USDTMarginedFutures:
var ct string
if !cp.Quote.Equal(currency.USDT) && !cp.Quote.Equal(currency.BUSD) {
ei, err := b.UExchangeInfo(ctx)
if err != nil {
return "", err
}
for i := range ei.Symbols {
if ei.Symbols[i].Symbol != symbol {
continue
}
switch ei.Symbols[i].ContractType {
case "CURRENT_QUARTER":
ct = "_QUARTER"
case "NEXT_QUARTER":
ct = "_BI-QUARTER"
}
symbol = ei.Symbols[i].Pair
break
}
}
return tradeBaseURL + "futures/" + symbol + ct, nil
case asset.CoinMarginedFutures:
var ct string
if !cp.Quote.Equal(currency.USDT) && !cp.Quote.Equal(currency.BUSD) {
ei, err := b.FuturesExchangeInfo(ctx)
if err != nil {
return "", err
}
for i := range ei.Symbols {
if ei.Symbols[i].Symbol != symbol {
continue
}
switch ei.Symbols[i].ContractType {
case "CURRENT_QUARTER":
ct = "_QUARTER"
case "NEXT_QUARTER":
ct = "_BI-QUARTER"
}
symbol = ei.Symbols[i].Pair
break
}
}
return tradeBaseURL + "delivery/" + symbol + ct, nil
case asset.Spot:
return tradeBaseURL + "trade/" + symbol + "?type=spot", nil
case asset.Margin:
return tradeBaseURL + "trade/" + symbol + "?type=cross", nil
default:
return "", fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
}
1 change: 1 addition & 0 deletions exchanges/binanceus/binanceus.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Binanceus struct {
}

const (
tradeBaseURL = "https://www.binance.us/spot-trade/"
// General Data Endpoints
serverTime = "/api/v3/time"
systemStatus = "/sapi/v1/system/status"
Expand Down
16 changes: 16 additions & 0 deletions exchanges/binanceus/binanceus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
Expand All @@ -20,6 +22,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)

Expand Down Expand Up @@ -1807,3 +1810,16 @@ func TestGetUsersSpotAssetSnapshot(t *testing.T) {
t.Error("Binanceus GetUsersSpotAssetSnapshot() error", er)
}
}

func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, bi)
for _, a := range bi.GetAssetTypes(false) {
pairs, err := bi.CurrencyPairs.GetPairs(a, false)
require.NoError(t, err, "cannot get pairs for %s", a)
require.NotEmpty(t, pairs, "no pairs for %s", a)
resp, err := bi.GetCurrencyTradeURL(context.Background(), a, pairs[0])
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
}
13 changes: 13 additions & 0 deletions exchanges/binanceus/binanceus_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,3 +944,16 @@ func (bi *Binanceus) GetLatestFundingRates(context.Context, *fundingrate.LatestR
func (bi *Binanceus) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}

// GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair
func (bi *Binanceus) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currency.Pair) (string, error) {
_, err := bi.CurrencyPairs.IsPairEnabled(cp, a)
if err != nil {
return "", err
}
symbol, err := bi.FormatSymbol(cp, a)
if err != nil {
return "", err
}
return tradeBaseURL + symbol, nil
}
1 change: 1 addition & 0 deletions exchanges/bitfinex/bitfinex.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

const (
bitfinexAPIURLBase = "https://api.bitfinex.com"
tradeBaseURL = "https://trading.bitfinex.com"
// Version 1 API endpoints
bitfinexAPIVersion = "/v1/"
bitfinexStats = "stats/"
Expand Down
13 changes: 13 additions & 0 deletions exchanges/bitfinex/bitfinex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2009,3 +2009,16 @@ func setupWs(tb testing.TB) {

wsConnected = true
}

func TestGetCurrencyTradeURL(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, b)
for _, a := range b.GetAssetTypes(false) {
pairs, err := b.CurrencyPairs.GetPairs(a, false)
require.NoError(t, err, "cannot get pairs for %s", a)
require.NotEmpty(t, pairs, "no pairs for %s", a)
resp, err := b.GetCurrencyTradeURL(context.Background(), a, pairs[0])
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
}
Loading

0 comments on commit f1ff951

Please sign in to comment.