Merge pull request #1490 from c9s/edwin/okx/query-mkts

REFACTOR: [okx] query markets
This commit is contained in:
bailantaotao 2024-01-09 13:23:20 +08:00 committed by GitHub
commit 2beec1d6ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 251 additions and 86 deletions

View File

@ -3,7 +3,6 @@ package okex
import (
"context"
"fmt"
"math"
"strconv"
"time"
@ -24,6 +23,8 @@ import (
var (
marketDataLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 5)
orderRateLimiter = rate.NewLimiter(rate.Every(300*time.Millisecond), 5)
queryMarketLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10)
)
const ID = "okex"
@ -68,10 +69,11 @@ func (e *Exchange) Name() types.ExchangeName {
}
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
instruments, err := e.client.NewGetInstrumentsRequest().
InstrumentType(okexapi.InstrumentTypeSpot).
Do(ctx)
if err := queryMarketLimiter.Wait(ctx); err != nil {
return nil, fmt.Errorf("markets rate limiter wait error: %w", err)
}
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
if err != nil {
return nil, err
}
@ -87,8 +89,8 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
BaseCurrency: instrument.BaseCurrency,
// convert tick size OKEx to precision
PricePrecision: int(-math.Log10(instrument.TickSize.Float64())),
VolumePrecision: int(-math.Log10(instrument.LotSize.Float64())),
PricePrecision: instrument.TickSize.NumFractionalDigits(),
VolumePrecision: instrument.LotSize.NumFractionalDigits(),
// TickSize: OKEx's price tick, for BTC-USDT it's "0.1"
TickSize: instrument.TickSize,

View File

@ -30,11 +30,9 @@ func getTestClientOrSkip(t *testing.T) *RestClient {
func TestClient_GetInstrumentsRequest(t *testing.T) {
client := NewClient()
ctx := context.Background()
req := client.NewGetInstrumentsRequest()
req := client.NewGetInstrumentsInfoRequest()
instruments, err := req.
InstrumentType(InstrumentTypeSpot).
Do(ctx)
instruments, err := req.Do(ctx)
assert.NoError(t, err)
assert.NotEmpty(t, instruments)
t.Logf("instruments: %+v", instruments)

View File

@ -0,0 +1,47 @@
package okexapi
import (
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/requestgen"
)
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data
type InstrumentInfo struct {
InstrumentType string `json:"instType"`
InstrumentID string `json:"instId"`
BaseCurrency string `json:"baseCcy"`
QuoteCurrency string `json:"quoteCcy"`
SettleCurrency string `json:"settleCcy"`
ContractValue string `json:"ctVal"`
ContractMultiplier string `json:"ctMult"`
ContractValueCurrency string `json:"ctValCcy"`
ListTime types.MillisecondTimestamp `json:"listTime"`
ExpiryTime types.MillisecondTimestamp `json:"expTime"`
TickSize fixedpoint.Value `json:"tickSz"`
LotSize fixedpoint.Value `json:"lotSz"`
// MinSize = min order size
MinSize fixedpoint.Value `json:"minSz"`
// instrument status
State string `json:"state"`
}
//go:generate GetRequest -url "/api/v5/public/instruments" -type GetInstrumentsInfoRequest -responseDataType []InstrumentInfo
type GetInstrumentsInfoRequest struct {
client requestgen.APIClient
instType InstrumentType `param:"instType,query" validValues:"SPOT"`
instId *string `param:"instId,query"`
}
func (c *RestClient) NewGetInstrumentsInfoRequest() *GetInstrumentsInfoRequest {
return &GetInstrumentsInfoRequest{
client: c,
instType: InstrumentTypeSpot,
}
}

View File

@ -0,0 +1,194 @@
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v5/public/instruments -type GetInstrumentsInfoRequest -responseDataType []InstrumentInfo"; DO NOT EDIT.
package okexapi
import (
"context"
"encoding/json"
"fmt"
"net/url"
"reflect"
"regexp"
)
func (g *GetInstrumentsInfoRequest) InstType(instType InstrumentType) *GetInstrumentsInfoRequest {
g.instType = instType
return g
}
func (g *GetInstrumentsInfoRequest) InstId(instId string) *GetInstrumentsInfoRequest {
g.instId = &instId
return g
}
// GetQueryParameters builds and checks the query parameters and returns url.Values
func (g *GetInstrumentsInfoRequest) GetQueryParameters() (url.Values, error) {
var params = map[string]interface{}{}
// check instType field -> json key instType
instType := g.instType
// TEMPLATE check-valid-values
switch instType {
case "SPOT":
params["instType"] = instType
default:
return nil, fmt.Errorf("instType value %v is invalid", instType)
}
// END TEMPLATE check-valid-values
// assign parameter of instType
params["instType"] = instType
// check instId field -> json key instId
if g.instId != nil {
instId := *g.instId
// assign parameter of instId
params["instId"] = instId
} else {
}
query := url.Values{}
for _k, _v := range params {
query.Add(_k, fmt.Sprintf("%v", _v))
}
return query, nil
}
// GetParameters builds and checks the parameters and return the result in a map object
func (g *GetInstrumentsInfoRequest) GetParameters() (map[string]interface{}, error) {
var params = map[string]interface{}{}
return params, nil
}
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
func (g *GetInstrumentsInfoRequest) GetParametersQuery() (url.Values, error) {
query := url.Values{}
params, err := g.GetParameters()
if err != nil {
return query, err
}
for _k, _v := range params {
if g.isVarSlice(_v) {
g.iterateSlice(_v, func(it interface{}) {
query.Add(_k+"[]", fmt.Sprintf("%v", it))
})
} else {
query.Add(_k, fmt.Sprintf("%v", _v))
}
}
return query, nil
}
// GetParametersJSON converts the parameters from GetParameters into the JSON format
func (g *GetInstrumentsInfoRequest) GetParametersJSON() ([]byte, error) {
params, err := g.GetParameters()
if err != nil {
return nil, err
}
return json.Marshal(params)
}
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
func (g *GetInstrumentsInfoRequest) GetSlugParameters() (map[string]interface{}, error) {
var params = map[string]interface{}{}
return params, nil
}
func (g *GetInstrumentsInfoRequest) applySlugsToUrl(url string, slugs map[string]string) string {
for _k, _v := range slugs {
needleRE := regexp.MustCompile(":" + _k + "\\b")
url = needleRE.ReplaceAllString(url, _v)
}
return url
}
func (g *GetInstrumentsInfoRequest) iterateSlice(slice interface{}, _f func(it interface{})) {
sliceValue := reflect.ValueOf(slice)
for _i := 0; _i < sliceValue.Len(); _i++ {
it := sliceValue.Index(_i).Interface()
_f(it)
}
}
func (g *GetInstrumentsInfoRequest) isVarSlice(_v interface{}) bool {
rt := reflect.TypeOf(_v)
switch rt.Kind() {
case reflect.Slice:
return true
}
return false
}
func (g *GetInstrumentsInfoRequest) GetSlugsMap() (map[string]string, error) {
slugs := map[string]string{}
params, err := g.GetSlugParameters()
if err != nil {
return slugs, nil
}
for _k, _v := range params {
slugs[_k] = fmt.Sprintf("%v", _v)
}
return slugs, nil
}
// GetPath returns the request path of the API
func (g *GetInstrumentsInfoRequest) GetPath() string {
return "/api/v5/public/instruments"
}
// Do generates the request object and send the request object to the API endpoint
func (g *GetInstrumentsInfoRequest) Do(ctx context.Context) ([]InstrumentInfo, error) {
// no body params
var params interface{}
query, err := g.GetQueryParameters()
if err != nil {
return nil, err
}
var apiURL string
apiURL = g.GetPath()
req, err := g.client.NewRequest(ctx, "GET", apiURL, query, params)
if err != nil {
return nil, err
}
response, err := g.client.SendRequest(req)
if err != nil {
return nil, err
}
var apiResponse APIResponse
if err := response.DecodeJSON(&apiResponse); err != nil {
return nil, err
}
type responseValidator interface {
Validate() error
}
validator, ok := interface{}(apiResponse).(responseValidator)
if ok {
if err := validator.Validate(); err != nil {
return nil, err
}
}
var data []InstrumentInfo
if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
return nil, err
}
return data, nil
}

View File

@ -10,12 +10,6 @@ import (
"github.com/pkg/errors"
)
func (s *RestClient) NewGetInstrumentsRequest() *GetInstrumentsRequest {
return &GetInstrumentsRequest{
client: s,
}
}
func (s *RestClient) NewGetFundingRate() *GetFundingRateRequest {
return &GetFundingRateRequest{
client: s,
@ -71,73 +65,3 @@ func (r *GetFundingRateRequest) Do(ctx context.Context) (*FundingRate, error) {
return &data[0], nil
}
type Instrument struct {
InstrumentType string `json:"instType"`
InstrumentID string `json:"instId"`
BaseCurrency string `json:"baseCcy"`
QuoteCurrency string `json:"quoteCcy"`
SettleCurrency string `json:"settleCcy"`
ContractValue string `json:"ctVal"`
ContractMultiplier string `json:"ctMult"`
ContractValueCurrency string `json:"ctValCcy"`
ListTime types.MillisecondTimestamp `json:"listTime"`
ExpiryTime types.MillisecondTimestamp `json:"expTime"`
TickSize fixedpoint.Value `json:"tickSz"`
LotSize fixedpoint.Value `json:"lotSz"`
// MinSize = min order size
MinSize fixedpoint.Value `json:"minSz"`
// instrument status
State string `json:"state"`
}
type GetInstrumentsRequest struct {
client *RestClient
instType InstrumentType
instId *string
}
func (r *GetInstrumentsRequest) InstrumentType(instType InstrumentType) *GetInstrumentsRequest {
r.instType = instType
return r
}
func (r *GetInstrumentsRequest) InstrumentID(instId string) *GetInstrumentsRequest {
r.instId = &instId
return r
}
func (r *GetInstrumentsRequest) Do(ctx context.Context) ([]Instrument, error) {
// SPOT, SWAP, FUTURES, OPTION
var params = url.Values{}
params.Add("instType", string(r.instType))
if r.instId != nil {
params.Add("instId", *r.instId)
}
req, err := r.client.NewRequest(ctx, "GET", "/api/v5/public/instruments", params, nil)
if err != nil {
return nil, err
}
response, err := r.client.SendRequest(req)
if err != nil {
return nil, err
}
var apiResponse APIResponse
if err := response.DecodeJSON(&apiResponse); err != nil {
return nil, err
}
var data []Instrument
if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
return nil, err
}
return data, nil
}