bitget: minimize api client code

This commit is contained in:
c9s 2023-05-17 14:26:25 +08:00
parent f942f7afd8
commit e23f4b5114
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
2 changed files with 50 additions and 129 deletions

View File

@ -1,16 +1,15 @@
//go:build exchangetest
// +build exchangetest
package cmd package cmd
import ( import (
"context" "context"
"fmt"
"syscall"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/exchange"
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
@ -18,90 +17,47 @@ import (
var exchangeTestCmd = &cobra.Command{ var exchangeTestCmd = &cobra.Command{
Use: "exchange-test", Use: "exchange-test",
Short: "test the exchange", Short: "test the exchange",
PreRunE: cobraInitRequired([]string{
"session",
"symbol",
"interval",
}),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background() ctx := context.Background()
environ := bbgo.NewEnvironment() exchangeNameStr, err := cmd.Flags().GetString("exchange")
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
return err
}
sessionName, err := cmd.Flags().GetString("session")
if err != nil { if err != nil {
return err return err
} }
session, ok := environ.Session(sessionName) exchangeName, err := types.ValidExchangeName(exchangeNameStr)
if !ok {
return fmt.Errorf("session %s not found", sessionName)
}
symbol, err := cmd.Flags().GetString("symbol")
if err != nil {
return fmt.Errorf("can not get the symbol from flags: %w", err)
}
if symbol == "" {
return fmt.Errorf("--symbol option is required")
}
interval, err := cmd.Flags().GetString("interval")
if err != nil { if err != nil {
return err return err
} }
now := time.Now() exMinimal, err := exchange.NewWithEnvVarPrefix(exchangeName, "")
kLines, err := session.Exchange.QueryKLines(ctx, symbol, types.Interval(interval), types.KLineQueryOptions{
Limit: 50,
EndTime: &now,
})
if err != nil { if err != nil {
return err return err
} }
log.Infof("kLines from RESTful API")
for _, k := range kLines { log.Infof("types.ExchangeMinimal: ✅")
log.Info(k.String())
if service, ok := exMinimal.(types.ExchangeAccountService); ok {
log.Infof("types.ExchangeAccountService: ✅ (%T)", service)
} }
s := session.Exchange.NewStream() if service, ok := exMinimal.(types.ExchangeMarketDataService); ok {
s.SetPublicOnly() log.Infof("types.ExchangeMarketDataService: ✅ (%T)", service)
s.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: types.Interval(interval)})
s.OnKLineClosed(func(kline types.KLine) {
log.Infof("kline closed: %s", kline.String())
})
s.OnKLine(func(kline types.KLine) {
log.Infof("kline: %s", kline.String())
})
log.Infof("connecting...")
if err := s.Connect(ctx); err != nil {
return err
} }
log.Infof("connected") if ex, ok := exMinimal.(types.Exchange); ok {
defer func() { log.Infof("types.Exchange: ✅ (%T)", ex)
log.Infof("closing connection...") }
if err := s.Close(); err != nil {
log.WithError(err).Errorf("connection close error")
}
}()
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM) _ = ctx
// cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
return nil return nil
}, },
} }
func init() { func init() {
// since the public data does not require trading authentication, we use --exchange option here. exchangeTestCmd.Flags().String("exchange", "", "session name")
exchangeTestCmd.Flags().String("session", "", "session name") exchangeTestCmd.MarkFlagRequired("exchange")
exchangeTestCmd.Flags().String("symbol", "", "the trading pair. e.g, BTCUSDT, LTCUSDT...")
exchangeTestCmd.Flags().String("interval", "1m", "interval of the kline (candle), .e.g, 1m, 3m, 15m")
RootCmd.AddCommand(exchangeTestCmd) RootCmd.AddCommand(exchangeTestCmd)
} }

View File

@ -12,9 +12,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/c9s/requestgen"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/c9s/bbgo/pkg/util"
) )
const defaultHTTPTimeout = time.Second * 15 const defaultHTTPTimeout = time.Second * 15
@ -23,8 +22,7 @@ const PublicWebSocketURL = "wss://ws.bitget.com/spot/v1/stream"
const PrivateWebSocketURL = "wss://ws.bitget.com/spot/v1/stream" const PrivateWebSocketURL = "wss://ws.bitget.com/spot/v1/stream"
type RestClient struct { type RestClient struct {
BaseURL *url.URL requestgen.BaseAPIClient
client *http.Client
Key, Secret, Passphrase string Key, Secret, Passphrase string
} }
@ -36,56 +34,21 @@ func NewClient() *RestClient {
} }
return &RestClient{ return &RestClient{
BaseURL: u, BaseAPIClient: requestgen.BaseAPIClient{
client: &http.Client{ BaseURL: u,
Timeout: defaultHTTPTimeout, HttpClient: &http.Client{
Timeout: defaultHTTPTimeout,
},
}, },
} }
} }
func (c *RestClient) Auth(key, secret, passphrase string) { func (c *RestClient) Auth(key, secret, passphrase string) {
c.Key = key c.Key = key
// pragma: allowlist nextline secret
c.Secret = secret c.Secret = secret
c.Passphrase = passphrase c.Passphrase = passphrase
} }
// NewRequest create new API request. Relative url can be provided in refURL.
func (c *RestClient) newRequest(ctx context.Context, method, refURL string, params url.Values, body []byte) (*http.Request, error) {
rel, err := url.Parse(refURL)
if err != nil {
return nil, err
}
if params != nil {
rel.RawQuery = params.Encode()
}
pathURL := c.BaseURL.ResolveReference(rel)
return http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body))
}
// sendRequest sends the request to the API server and handle the response
func (c *RestClient) sendRequest(req *http.Request) (*util.Response, error) {
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
// newResponse reads the response body and return a new Response object
response, err := util.NewResponse(resp)
if err != nil {
return response, err
}
// Check error, if there is an error, return the ErrorResponse struct type
if response.IsError() {
return response, errors.New(string(response.Body))
}
return response, nil
}
// newAuthenticatedRequest creates new http request for authenticated routes. // newAuthenticatedRequest creates new http request for authenticated routes.
func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL string, params url.Values, payload interface{}) (*http.Request, error) { func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL string, params url.Values, payload interface{}) (*http.Request, error) {
if len(c.Key) == 0 { if len(c.Key) == 0 {
@ -115,26 +78,13 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL
t := time.Now().In(time.UTC) t := time.Now().In(time.UTC)
timestamp := t.Format("2006-01-02T15:04:05.999Z07:00") timestamp := t.Format("2006-01-02T15:04:05.999Z07:00")
var body []byte body, err := castPayload(payload)
if err != nil {
if payload != nil { return nil, err
switch v := payload.(type) {
case string:
body = []byte(v)
case []byte:
body = v
default:
body, err = json.Marshal(v)
if err != nil {
return nil, err
}
}
} }
signKey := timestamp + strings.ToUpper(method) + path + string(body) signKey := timestamp + strings.ToUpper(method) + path + string(body)
signature := Sign(signKey, c.Secret) signature := sign(signKey, c.Secret)
req, err := http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body)) req, err := http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body))
if err != nil { if err != nil {
@ -150,7 +100,7 @@ func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL
return req, nil return req, nil
} }
func Sign(payload string, secret string) string { func sign(payload string, secret string) string {
var sig = hmac.New(sha256.New, []byte(secret)) var sig = hmac.New(sha256.New, []byte(secret))
_, err := sig.Write([]byte(payload)) _, err := sig.Write([]byte(payload))
if err != nil { if err != nil {
@ -158,5 +108,20 @@ func Sign(payload string, secret string) string {
} }
return base64.StdEncoding.EncodeToString(sig.Sum(nil)) return base64.StdEncoding.EncodeToString(sig.Sum(nil))
// return hex.EncodeToString(sig.Sum(nil)) }
func castPayload(payload interface{}) ([]byte, error) {
if payload == nil {
return nil, nil
}
switch v := payload.(type) {
case string:
return []byte(v), nil
case []byte:
return v, nil
}
return json.Marshal(payload)
} }