package okx import ( "context" "errors" "fmt" "net/http" "net/url" "sig-pub/pkg/types" "strconv" "strings" "time" "github.com/bytedance/sonic" "github.com/go-resty/resty/v2" "github.com/govalues/decimal" ) const ( HttpBaseUrl = "https://www.okx.com" ) type OkxFetcher struct { client *resty.Client httpProxy string } func NewOkxFetcher(httpProxy string) (f *OkxFetcher) { client := resty.New() client.SetTimeout(30 * time.Second) client.SetTransport(&http.Transport{ MaxIdleConns: 100, MaxConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, }) if httpProxy != "" { client.SetProxy(httpProxy) } f = &OkxFetcher{ client: client, httpProxy: httpProxy, } return } func (okx *OkxFetcher) ExhcangeType() types.Exchange { return types.ExchangeOKX } // FetchHistoryKlines 获取交易产品历史K线数据 // https://my.okx.com/docs-v5/zh/#order-book-trading-market-data-get-candlesticks-history // 周期区间 after > before, (after, before) func (f *OkxFetcher) FetchHistoryKlines(ctx context.Context, okxInstId string, interval types.Interval, after, before int64) (klines []*types.Kline, err error) { if okxInstId == "" { err = errors.New("instid is empty") return } if after <= 0 && before <= 0 { err = errors.New("time range zero") return } if err = fetchHistoryKlineLimiter.Wait(ctx); err != nil { return } var params []string params = append(params, fmt.Sprintf("instId=%s", url.QueryEscape(okxInstId))) params = append(params, "limit=100") // 最大为100 if v, ok := subscribeCandles[interval]; ok { params = append(params, "bar="+v) } if after > 0 { params = append(params, fmt.Sprintf("after=%d", after)) } if before > 0 { params = append(params, fmt.Sprintf("before=%d", before)) } url := fmt.Sprintf("%s/api/v5/market/history-candles?%s", HttpBaseUrl, strings.Join(params, "&")) resp, err := f.client.R().Get(url) if err != nil { return } // http status: 429 Too Many Requests status := resp.StatusCode() if status != 200 { err = fmt.Errorf("request history klines status error: %s, %s", url, resp.Status()) return } r := new(RespHistoryKline) if err = sonic.Unmarshal(resp.Body(), r); err != nil { return } if r.Code != "0" { err = fmt.Errorf("code: %s, msg: %s", r.Code, r.Msg) return } for _, data := range r.Data { ts, e := strconv.ParseInt(data[0], 10, 64) if e != nil { err = e return } kline := &types.Kline{ Interval: interval, Ts: ts, Confirm: data[8] == "1", } var kinds = []*decimal.Decimal{&kline.Open, &kline.High, &kline.Low, &kline.Close, &kline.Vol, nil, &kline.VolQuote} for i := 1; i <= 7; i++ { if kinds[i-1] == nil { continue } *kinds[i-1], err = decimal.Parse(data[i]) if err != nil { return } } klines = append(klines, kline) } return }