okex: add market ticker api support

This commit is contained in:
c9s 2021-05-23 13:44:08 +08:00
parent e678289577
commit 8842208441
5 changed files with 292 additions and 8 deletions

View File

@ -46,16 +46,53 @@ var rootCmd = &cobra.Command{
client := okexapi.NewClient() client := okexapi.NewClient()
client.Auth(key, secret, passphrase) client.Auth(key, secret, passphrase)
log.Infof("balances:") log.Infof("ACCOUNT BALANCES:")
balanceSummaryList, err := client.Balances() balanceSummaries, err := client.AccountBalances()
if err != nil { if err != nil {
return err return err
} }
for _, balanceSummary := range balanceSummaryList { for _, balanceSummary := range balanceSummaries {
log.Infof("%+v", balanceSummary) log.Infof("%+v", balanceSummary)
} }
log.Infof("ASSET BALANCES:")
assetBalances, err := client.AssetBalances()
if err != nil {
return err
}
for _, balance := range assetBalances {
log.Infof("%T%+v", balance, balance)
}
log.Infof("ASSET CURRENCIES:")
currencies, err := client.AssetCurrencies()
if err != nil {
return err
}
for _, currency := range currencies {
log.Infof("%T%+v", currency, currency)
}
log.Infof("MARKET TICKERS:")
tickers, err := client.MarketTickers("SPOT")
if err != nil {
return err
}
for _, ticker := range tickers {
log.Infof("%T%+v", ticker, ticker)
}
ticker, err := client.MarketTicker("ETH-USDT")
if err != nil {
return err
}
log.Infof("TICKER:")
log.Infof("%T%+v", ticker, ticker)
_ = ctx _ = ctx
// cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM) // cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
return nil return nil

View File

@ -186,7 +186,7 @@ outboundAccountInfo
"W": true, // Can withdraw? "W": true, // Can withdraw?
"D": true, // Can deposit? "D": true, // Can deposit?
"u": 1499405658848, // Time of last account update "u": 1499405658848, // Time of last account update
"B": [ // Balances array "B": [ // AccountBalances array
{ {
"a": "LTC", // Asset "a": "LTC", // Asset
"f": "17366.18538083", // Free amount "f": "17366.18538083", // Free amount

View File

@ -5,14 +5,16 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util" "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 defaultHTTPTimeout = time.Second * 15
@ -94,7 +96,6 @@ func (c *RestClient) newAuthenticatedRequest(method, refURL string, params url.V
// set location to UTC so that it outputs "2020-12-08T09:08:57.715Z" // set location to UTC so that it outputs "2020-12-08T09:08:57.715Z"
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")
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)
@ -135,7 +136,7 @@ type BalanceSummary struct {
type BalanceSummaryList []BalanceSummary type BalanceSummaryList []BalanceSummary
func (c *RestClient) Balances() (BalanceSummaryList, error) { func (c *RestClient) AccountBalances() (BalanceSummaryList, error) {
req, err := c.newAuthenticatedRequest("GET", "/api/v5/account/balance", nil) req, err := c.newAuthenticatedRequest("GET", "/api/v5/account/balance", nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -158,6 +159,158 @@ func (c *RestClient) Balances() (BalanceSummaryList, error) {
return balanceResponse.Data, nil return balanceResponse.Data, nil
} }
type AssetBalance struct {
Currency string `json:"ccy"`
Balance string `json:"bal"`
Frozen string `json:"frozenBal,omitempty"`
Available string `json:"availBal,omitempty"`
}
type AssetBalanceList []AssetBalance
func (c *RestClient) AssetBalances() (AssetBalanceList, error) {
req, err := c.newAuthenticatedRequest("GET", "/api/v5/asset/balances", 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 AssetBalanceList `json:"data"`
}
if err := response.DecodeJSON(&balanceResponse); err != nil {
return nil, err
}
return balanceResponse.Data, nil
}
type AssetCurrency struct {
Currency string `json:"ccy"`
Name string `json:"name"`
Chain string `json:"chain"`
CanDeposit bool `json:"canDep"`
CanWithdraw bool `json:"canWd"`
CanInternal bool `json:"canInternal"`
MinWithdrawalFee string `json:"minFee"`
MaxWithdrawalFee string `json:"maxFee"`
MinWithdrawalThreshold string `json:"minWd"`
}
func (c *RestClient) AssetCurrencies() ([]AssetCurrency, error) {
req, err := c.newAuthenticatedRequest("GET", "/api/v5/asset/currencies", nil)
if err != nil {
return nil, err
}
response, err := c.sendRequest(req)
if err != nil {
return nil, err
}
var currencyResponse struct {
Code string `json:"code"`
Message string `json:"msg"`
Data []AssetCurrency `json:"data"`
}
if err := response.DecodeJSON(&currencyResponse); err != nil {
return nil, err
}
return currencyResponse.Data, nil
}
type MarketTicker struct {
InstrumentType string `json:"instType"`
InstrumentID string `json:"instId"`
// last traded price
Last fixedpoint.Value `json:"last"`
// last traded size
LastSize fixedpoint.Value `json:"lastSz"`
AskPrice fixedpoint.Value `json:"askPx"`
AskSize fixedpoint.Value `json:"askSz"`
BidPrice fixedpoint.Value `json:"bidPx"`
BidSize fixedpoint.Value `json:"bidSz"`
Open24H fixedpoint.Value `json:"open24h"`
High24H fixedpoint.Value `json:"high24H"`
Low24H fixedpoint.Value `json:"low24H"`
Volume24H fixedpoint.Value `json:"vol24h"`
VolumeCurrency24H fixedpoint.Value `json:"volCcy24h"`
// Millisecond timestamp
Timestamp types.MillisecondTimestamp `json:"ts"`
}
func (c *RestClient) MarketTicker(instId string) (*MarketTicker, error) {
// SPOT, SWAP, FUTURES, OPTION
var params = url.Values{}
params.Add("instId", instId)
req, err := c.newAuthenticatedRequest("GET", "/api/v5/market/ticker", params)
if err != nil {
return nil, err
}
response, err := c.sendRequest(req)
if err != nil {
return nil, err
}
var tickerResponse struct {
Code string `json:"code"`
Message string `json:"msg"`
Data []MarketTicker `json:"data"`
}
if err := response.DecodeJSON(&tickerResponse); err != nil {
return nil, err
}
if len(tickerResponse.Data) == 0 {
return nil, fmt.Errorf("ticker of %s not found", instId)
}
return &tickerResponse.Data[0], nil
}
func (c *RestClient) MarketTickers(instType string) ([]MarketTicker, error) {
// SPOT, SWAP, FUTURES, OPTION
var params = url.Values{}
params.Add("instType", instType)
req, err := c.newAuthenticatedRequest("GET", "/api/v5/market/tickers", params)
if err != nil {
return nil, err
}
response, err := c.sendRequest(req)
if err != nil {
return nil, err
}
var tickerResponse struct {
Code string `json:"code"`
Message string `json:"msg"`
Data []MarketTicker `json:"data"`
}
if err := response.DecodeJSON(&tickerResponse); err != nil {
return nil, err
}
return tickerResponse.Data, nil
}
// sendRequest sends the request to the API server and handle the response // sendRequest sends the request to the API server and handle the response
func (c *RestClient) sendRequest(req *http.Request) (*util.Response, error) { func (c *RestClient) sendRequest(req *http.Request) (*util.Response, error) {
resp, err := c.client.Do(req) resp, err := c.client.Do(req)

View File

@ -2,15 +2,71 @@ package types
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json"
"fmt" "fmt"
"strconv"
"time" "time"
) )
type MillisecondTimestamp time.Time
func (t *MillisecondTimestamp) UnmarshalJSON(data []byte) error {
var v interface{}
var err = json.Unmarshal(data, &v)
if err != nil {
return err
}
switch vt := v.(type) {
case string:
i, err := strconv.ParseInt(vt, 10, 64)
if err == nil {
*t = MillisecondTimestamp(time.Unix(0, i*int64(time.Millisecond)))
return nil
}
f, err := strconv.ParseFloat(vt, 64)
if err == nil {
*t = MillisecondTimestamp(time.Unix(0, int64(f*float64(time.Millisecond))))
return nil
}
tt, err := time.Parse(time.RFC3339Nano, vt)
if err == nil {
*t = MillisecondTimestamp(tt)
return nil
}
return err
case int64:
*t = MillisecondTimestamp(time.Unix(0, vt*int64(time.Millisecond)))
return nil
case int:
*t = MillisecondTimestamp(time.Unix(0, int64(vt)*int64(time.Millisecond)))
return nil
case float64:
*t = MillisecondTimestamp(time.Unix(0, int64(vt)*int64(time.Millisecond)))
return nil
default:
return fmt.Errorf("can not parse %T %+v as millisecond timestamp", vt, vt)
}
// fallback to RFC3339
return (*time.Time)(t).UnmarshalJSON(data)
}
type Time time.Time type Time time.Time
var layout = "2006-01-02 15:04:05.999Z07:00" var layout = "2006-01-02 15:04:05.999Z07:00"
func (t *Time) UnmarshalJSON(data []byte) error { func (t *Time) UnmarshalJSON(data []byte) error {
// fallback to RFC3339
return (*time.Time)(t).UnmarshalJSON(data) return (*time.Time)(t).UnmarshalJSON(data)
} }
@ -26,7 +82,7 @@ func (t Time) Time() time.Time {
return time.Time(t) return time.Time(t)
} }
// driver.Valuer interface // Value implements the driver.Valuer interface
// see http://jmoiron.net/blog/built-in-interfaces/ // see http://jmoiron.net/blog/built-in-interfaces/
func (t Time) Value() (driver.Value, error) { func (t Time) Value() (driver.Value, error) {
return time.Time(t), nil return time.Time(t), nil

38
pkg/types/time_test.go Normal file
View File

@ -0,0 +1,38 @@
package types
import (
"testing"
"time"
)
func TestMillisecondTimestamp_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
t MillisecondTimestamp
args []byte
wantErr bool
}{
{
name: "millisecond in string",
args: []byte("\"1620289117764\""),
t: MillisecondTimestamp(time.Unix(0, 1620289117764*int64(time.Millisecond))),
},
{
name: "millisecond in number",
args: []byte("1620289117764"),
t: MillisecondTimestamp(time.Unix(0, 1620289117764*int64(time.Millisecond))),
},
{
name: "millisecond in decimal",
args: []byte("1620289117.764"),
t: MillisecondTimestamp(time.Unix(0, 1620289117764*int64(time.Millisecond))),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.t.UnmarshalJSON(tt.args); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}