implement okex balances endpoint

This commit is contained in:
c9s 2021-05-23 12:11:27 +08:00
parent fe269fd93d
commit e678289577
4 changed files with 162 additions and 6 deletions

View File

@ -0,0 +1,82 @@
package main
import (
"context"
"os"
"strings"
"github.com/c9s/bbgo/pkg/exchange/okex/okexapi"
"github.com/joho/godotenv"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
rootCmd.PersistentFlags().String("okex-api-key", "", "okex api key")
rootCmd.PersistentFlags().String("okex-api-secret", "", "okex api secret")
rootCmd.PersistentFlags().String("okex-api-passphrase", "", "okex api secret")
rootCmd.PersistentFlags().String("symbol", "BNBUSDT", "symbol")
}
var rootCmd = &cobra.Command{
Use: "okex-book",
Short: "okex book",
// SilenceUsage is an option to silence usage when an error occurs.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
symbol := viper.GetString("symbol")
if len(symbol) == 0 {
return errors.New("empty symbol")
}
key, secret, passphrase := viper.GetString("okex-api-key"),
viper.GetString("okex-api-secret"),
viper.GetString("okex-api-passphrase")
if len(key) == 0 || len(secret) == 0 {
return errors.New("empty key, secret or passphrase")
}
client := okexapi.NewClient()
client.Auth(key, secret, passphrase)
log.Infof("balances:")
balanceSummaryList, err := client.Balances()
if err != nil {
return err
}
for _, balanceSummary := range balanceSummaryList {
log.Infof("%+v", balanceSummary)
}
_ = ctx
// cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
return nil
},
}
func main() {
if _, err := os.Stat(".env.local"); err == nil {
if err := godotenv.Load(".env.local"); err != nil {
log.Fatal(err)
}
}
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
log.WithError(err).Error("bind pflags error")
}
if err := rootCmd.ExecuteContext(context.Background()); err != nil {
log.WithError(err).Error("cmd error")
}
}

View File

@ -293,7 +293,7 @@ func (c *RestClient) sendRequest(req *http.Request) (*util.Response, error) {
// Check error, if there is an error, return the ErrorResponse struct type // Check error, if there is an error, return the ErrorResponse struct type
if response.IsError() { if response.IsError() {
errorResponse, err := toErrorResponse(response) errorResponse, err := ToErrorResponse(response)
if err != nil { if err != nil {
return response, err return response, err
} }
@ -377,8 +377,8 @@ func (r *ErrorResponse) Error() string {
) )
} }
// toErrorResponse tries to convert/parse the server response to the standard Error interface object // ToErrorResponse tries to convert/parse the server response to the standard Error interface object
func toErrorResponse(response *util.Response) (errorResponse *ErrorResponse, err error) { func ToErrorResponse(response *util.Response) (errorResponse *ErrorResponse, err error) {
errorResponse = &ErrorResponse{Response: response} errorResponse = &ErrorResponse{Response: response}
contentType := response.Header.Get("content-type") contentType := response.Header.Get("content-type")

View File

@ -10,9 +10,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/c9s/bbgo/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus"
) )
const defaultHTTPTimeout = time.Second * 15
const RestBaseURL = "https://www.okex.com/" const RestBaseURL = "https://www.okex.com/"
const PublicWebSocketURL = "wss://ws.okex.com:8443/ws/v5/public" const PublicWebSocketURL = "wss://ws.okex.com:8443/ws/v5/public"
const PrivateWebSocketURL = "wss://ws.okex.com:8443/ws/v5/private" const PrivateWebSocketURL = "wss://ws.okex.com:8443/ws/v5/private"
@ -20,6 +23,8 @@ const PrivateWebSocketURL = "wss://ws.okex.com:8443/ws/v5/private"
type RestClient struct { type RestClient struct {
BaseURL *url.URL BaseURL *url.URL
client *http.Client
Key, Secret, Passphrase string Key, Secret, Passphrase string
} }
@ -31,6 +36,9 @@ func NewClient() *RestClient {
return &RestClient{ return &RestClient{
BaseURL: u, BaseURL: u,
client: &http.Client{
Timeout: defaultHTTPTimeout,
},
} }
} }
@ -83,9 +91,11 @@ func (c *RestClient) newAuthenticatedRequest(method, refURL string, params url.V
pathURL := c.BaseURL.ResolveReference(rel) pathURL := c.BaseURL.ResolveReference(rel)
path := pathURL.Path path := pathURL.Path
// 2020-12-08T09:08:57.715Z // set location to UTC so that it outputs "2020-12-08T09:08:57.715Z"
t := time.Now() 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")
log.Info(timestamp)
payload := timestamp + strings.ToUpper(method) + path payload := timestamp + strings.ToUpper(method) + path
sign := signPayload(payload, c.Secret) sign := signPayload(payload, c.Secret)
@ -105,6 +115,70 @@ func (c *RestClient) newAuthenticatedRequest(method, refURL string, params url.V
return req, nil return req, nil
} }
type BalanceDetail struct {
Currency string `json:"ccy"`
Available string `json:"availEq"`
CashBalance string `json:"cashBal"`
OrderFrozen string `json:"ordFrozen"`
Frozen string `json:"frozenBal"`
Equity string `json:"eq"`
EquityInUSD string `json:"eqUsd"`
UpdateTime string `json:"uTime"`
UnrealizedProfitAndLoss string `json:"upl"`
}
type BalanceSummary struct {
TotalEquityInUSD string `json:"totalEq"`
UpdateTime string `json:"uTime"`
Details []BalanceDetail `json:"details"`
}
type BalanceSummaryList []BalanceSummary
func (c *RestClient) Balances() (BalanceSummaryList, error) {
req, err := c.newAuthenticatedRequest("GET", "/api/v5/account/balance", nil)
if err != nil {
return nil, err
}
response, err := c.sendRequest(req)
if err != nil {
return nil, err
}
var balanceResponse struct {
Code string `json:"code"`
Message string `json:"msg"`
Data []BalanceSummary `json:"data"`
}
if err := response.DecodeJSON(&balanceResponse); err != nil {
return nil, err
}
return balanceResponse.Data, nil
}
// 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
}
func signPayload(payload string, secret string) string { func signPayload(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))

View File

@ -15,7 +15,7 @@ type Response struct {
Body []byte Body []byte
} }
// newResponse is a wrapper of the http.Response instance, it reads the response body and close the file. // NewResponse is a wrapper of the http.Response instance, it reads the response body and close the file.
func NewResponse(r *http.Response) (response *Response, err error) { func NewResponse(r *http.Response) (response *Response, err error) {
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {