mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
implement okex balances endpoint
This commit is contained in:
parent
fe269fd93d
commit
e678289577
82
examples/okex-book/main.go
Normal file
82
examples/okex-book/main.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user