tanya/okx/publicApiService.go

262 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package okx
import (
"bytes"
"encoding/json"
"fmt"
"github.com/google/go-querystring/query" // 用于将 struct 转为 URL 参数需要安装go get github.com/google/go-querystring
"io"
"net/http"
"net/url"
"strconv"
"time"
)
// getPeriodDuration 根据时间周期字符串返回对应的 duration
func getPeriodDuration(period string) (time.Duration, error) {
switch period {
case "1m":
return time.Minute, nil
case "3m":
return 3 * time.Minute, nil
case "5m":
return 5 * time.Minute, nil
case "15m":
return 15 * time.Minute, nil
case "30m":
return 30 * time.Minute, nil
case "1H":
return time.Hour, nil
case "2H":
return 2 * time.Hour, nil
case "4H":
return 4 * time.Hour, nil
case "6H":
return 6 * time.Hour, nil
case "12H":
return 12 * time.Hour, nil
case "1D":
return 24 * time.Hour, nil
case "2D":
return 2 * 24 * time.Hour, nil
case "3D":
return 3 * 24 * time.Hour, nil
case "5D":
return 5 * 24 * time.Hour, nil
case "1W":
return 7 * 24 * time.Hour, nil
default:
return 0, fmt.Errorf("unsupported bar period: %s", period)
}
}
type OkxPublicDataService struct {
BaseURL string
client *http.Client
}
// NewOkxPublicDataService 创建服务实例
func NewOkxPublicDataService() *OkxPublicDataService {
return &OkxPublicDataService{
BaseURL: "https://aws.okx.com/api/v5",
client: &http.Client{
Timeout: 10 * time.Second, // 设置10秒超时
},
}
}
// GetInstruments 获取交易对信息
func (s *OkxPublicDataService) GetInstruments(params InstrumentsRequest) ([]Instrument, error) {
u, err := url.Parse(s.BaseURL + "/public/instruments")
if err != nil {
return nil, err
}
// 将 struct 转为查询参数
q, err := query.Values(params)
if err != nil {
return nil, err
}
u.RawQuery = q.Encode()
resp, err := s.client.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var apiResp ApiResponse
apiResp.Data = new([]Instrument) // 预分配 Data 字段为 Instrument 切片
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
return nil, err
}
if apiResp.Code != "0" {
return nil, fmt.Errorf("API error: code=%s, msg=%s, request_url=%s", apiResp.Code, apiResp.Msg, u.String())
}
// 进行类型断言并检查长度
switch data := apiResp.Data.(type) {
case []Instrument:
if len(data) == 0 {
return nil, fmt.Errorf("no data returned from API")
}
case [][]string:
if len(data) == 0 {
return nil, fmt.Errorf("no data returned from API")
}
case []*Ticker:
if len(data) == 0 {
return nil, fmt.Errorf("no data returned from API")
}
default:
return nil, fmt.Errorf("unexpected data type from API: %T", apiResp.Data)
}
return *apiResp.Data.(*[]Instrument), nil
}
// GetCandles 获取K线数据
func (s *OkxPublicDataService) GetCandles(params CandlesRequest) ([]*Candle, error) {
// 根据时间范围选择不同的API端点
endpoint := "/market/candles"
// 计算时间范围
now := time.Now()
var startTime time.Time
var err error
// 优先使用 After 参数,如果都提供了
if params.After != "" {
// 将毫秒时间戳转换为 time.Time
timestamp, err := strconv.ParseInt(params.After, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid After timestamp: %v", err)
}
startTime = time.Unix(0, timestamp*int64(time.Millisecond))
} else if params.Before != "" {
// 将毫秒时间戳转换为 time.Time
timestamp, err := strconv.ParseInt(params.Before, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid Before timestamp: %v", err)
}
startTime = time.Unix(0, timestamp*int64(time.Millisecond))
} else {
return nil, fmt.Errorf("either After or Before parameter is required")
}
// 根据时间维度计算20个周期
unitDuration, err := getPeriodDuration(params.Bar)
if err != nil {
return nil, err
}
periodDuration := 20 * unitDuration
// 如果数据超过20个周期使用历史数据接口
if now.Sub(startTime) > periodDuration {
endpoint = "/market/history-candles"
}
u, err := url.Parse(s.BaseURL + endpoint)
if err != nil {
return nil, err
}
// 将 struct 转为查询参数
q, err := query.Values(params)
if err != nil {
return nil, err
}
u.RawQuery = q.Encode()
// 添加调试日志
fmt.Printf("Making request to: %s\n", u.String())
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
// 记录响应状态
fmt.Printf("Response Status: %s\n", resp.Status)
// 读取并记录响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
fmt.Printf("Response Body: %s\n", string(body))
// 重新设置resp.Body以便后续解码
resp.Body = io.NopCloser(bytes.NewBuffer(body))
var apiResp struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data [][]string `json:"data"` // K线数据是二维数组
}
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
return nil, err
}
if apiResp.Code != "0" {
return nil, fmt.Errorf("API error: %s", apiResp.Msg)
}
// 将二维数组转为 Candle 切片
candles := make([]*Candle, len(apiResp.Data))
for i, item := range apiResp.Data {
candles[i] = &Candle{
Timestamp: item[0],
Open: item[1],
High: item[2],
Low: item[3],
Close: item[4],
Volume: item[5],
VolumeCcy: item[6],
}
}
return candles, nil
} // GetTicker 获取单个交易对的 ticker 数据
func (s *OkxPublicDataService) GetTicker(params TickerRequest) (*Ticker, error) {
u, err := url.Parse(s.BaseURL + "/market/ticker")
if err != nil {
return nil, err
}
// 将 struct 转为查询参数
q, err := query.Values(params)
if err != nil {
return nil, err
}
u.RawQuery = q.Encode()
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var apiResp struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data []*Ticker `json:"data"` // 返回的是指针数组
}
if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
return nil, err
}
if apiResp.Code != "0" {
return nil, fmt.Errorf("API error: %s", apiResp.Msg)
}
if len(apiResp.Data) == 0 {
return nil, fmt.Errorf("no ticker data returned")
}
// 返回第一个 ticker通常只返回一个
return apiResp.Data[0], nil
}