From 18f15b053478a96254acb0558120c8886bfe4648 Mon Sep 17 00:00:00 2001 From: "zhangkun9038@dingtalk.com" Date: Thu, 6 Mar 2025 00:31:55 +0800 Subject: [PATCH] up --- .gitignore | 1 + cmd/main.go | 49 ++++++++++ go.mod | 5 + go.sum | 5 + okx/candleApi.go | 28 ++++++ okx/instrmentsApi.go | 21 ++++ okx/publicApiService.go | 142 ++++++++++++++++++++++++++++ okx/tickerApi.go | 26 +++++ test/okxAli/get_candles_test.go | 58 ++++++++++++ test/okxAli/get_instruments_test.go | 44 +++++++++ test/okxAli/get_ticker_test.go | 45 +++++++++ 11 files changed, 424 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 okx/candleApi.go create mode 100644 okx/instrmentsApi.go create mode 100644 okx/publicApiService.go create mode 100644 okx/tickerApi.go create mode 100644 test/okxAli/get_candles_test.go create mode 100644 test/okxAli/get_instruments_test.go create mode 100644 test/okxAli/get_ticker_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..0112fc4 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "gitea.zjmud.xyz/phyer/tanya/okx" // 假设上述代码在 okx 包中 + "strconv" + "time" +) + +func main() { + service := okx.NewOkxPublicDataService() + + // 获取交易对信息 + instruments, err := service.GetInstruments(okx.InstrumentsRequest{ + InstType: "SPOT", + }) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("Instruments:", instruments) + + // 获取K线数据 + // // // 设置时间范围 + layout := "2006-01-02 15:04:05" + startTime, _ := time.Parse(layout, "2023-01-01 00:00:00") + endTime, _ := time.Parse(layout, "2023-12-31 23:59:59") + candles, err := service.GetCandles(okx.CandlesRequest{ + InstID: "BTC-USDT", + Bar: "1D", + Before: strconv.FormatInt(startTime.UnixMilli(), 10), + After: strconv.FormatInt(endTime.UnixMilli(), 10), + Limit: "50", + }) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("Candles:", candles) + // 获取单个 ticker 数据 + ticker, err := service.GetTicker(okx.TickerRequest{ + InstID: "BTC-USDT", + }) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Printf("Ticker: %+v\n", ticker) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7243748 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gitea.zjmud.xyz/phyer/tanya + +go 1.24.0 + +require github.com/google/go-querystring v1.1.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f99081b --- /dev/null +++ b/go.sum @@ -0,0 +1,5 @@ +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/okx/candleApi.go b/okx/candleApi.go new file mode 100644 index 0000000..5674ead --- /dev/null +++ b/okx/candleApi.go @@ -0,0 +1,28 @@ +// CandlesRequest 定义获取K线数据的请求参数 +package okx + +type CandlesRequest struct { + InstID string `url:"instId"` // 必填:交易对ID + Bar string `url:"bar,omitempty"` // 可选:K线周期,默认1m + Before string `url:"before,omitempty"` // 可选:请求此时间戳之前 + After string `url:"after,omitempty"` // 可选:请求此时间戳之后 + Limit string `url:"limit,omitempty"` // 可选:返回条数,默认100 +} + +// Candle 定义K线数据的返回结构 +type Candle struct { + Timestamp string `json:"timestamp"` + Open string `json:"open"` + High string `json:"high"` + Low string `json:"low"` + Close string `json:"close"` + Volume string `json:"volume"` + VolumeCcy string `json:"volumeCcy"` +} + +// ApiResponse 定义 OKX API 的通用返回结构 +type ApiResponse struct { + Code string `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` +} diff --git a/okx/instrmentsApi.go b/okx/instrmentsApi.go new file mode 100644 index 0000000..ccebc4e --- /dev/null +++ b/okx/instrmentsApi.go @@ -0,0 +1,21 @@ +package okx + +// InstrumentsRequest 定义获取交易对信息的请求参数 +type InstrumentsRequest struct { + InstType string `url:"instType"` // 必填:SPOT, SWAP, FUTURES, OPTION + Uly string `url:"uly,omitempty"` // 可选:标的指数 + InstID string `url:"instId,omitempty"` // 可选:具体交易对ID +} + +// Instrument 定义交易对信息的返回结构 +type Instrument struct { + InstType string `json:"instType"` + InstID string `json:"instId"` + Uly string `json:"uly,omitempty"` + BaseCcy string `json:"baseCcy"` + QuoteCcy string `json:"quoteCcy"` + TickSz string `json:"tickSz"` + LotSz string `json:"lotSz"` + State string `json:"state"` + // 可根据需要添加更多字段 +} diff --git a/okx/publicApiService.go b/okx/publicApiService.go new file mode 100644 index 0000000..adc7cb0 --- /dev/null +++ b/okx/publicApiService.go @@ -0,0 +1,142 @@ +package okx + +import ( + "encoding/json" + "fmt" + "github.com/google/go-querystring/query" // 用于将 struct 转为 URL 参数,需要安装:go get github.com/google/go-querystring + "net/http" + "net/url" +) + +type OkxPublicDataService struct { + BaseURL string +} + +// NewOkxPublicDataService 创建服务实例 +func NewOkxPublicDataService() *OkxPublicDataService { + return &OkxPublicDataService{ + BaseURL: "https://www.okx.com/api/v5", + } +} + +// 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 := http.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: %s", apiResp.Msg) + } + + return *apiResp.Data.(*[]Instrument), nil +} + +// GetCandles 获取K线数据 +func (s *OkxPublicDataService) GetCandles(params CandlesRequest) ([]Candle, error) { + u, err := url.Parse(s.BaseURL + "/market/candles") + 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 [][]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 +} diff --git a/okx/tickerApi.go b/okx/tickerApi.go new file mode 100644 index 0000000..262d6b7 --- /dev/null +++ b/okx/tickerApi.go @@ -0,0 +1,26 @@ +package okx + +// TickerRequest 定义获取单个 ticker 的请求参数 +type TickerRequest struct { + InstID string `url:"instId"` // 必填:交易对ID +} + +// Ticker 定义 ticker 返回数据结构 +type Ticker struct { + InstType string `json:"instType"` // 产品类型 + InstID string `json:"instId"` // 交易对ID + Last string `json:"last"` // 最新成交价 + LastSz string `json:"lastSz"` // 最新成交数量 + AskPx string `json:"askPx"` // 卖一价 + AskSz string `json:"askSz"` // 卖一量 + BidPx string `json:"bidPx"` // 买一价 + BidSz string `json:"bidSz"` // 买一量 + Open24h string `json:"open24h"` // 24小时开盘价 + High24h string `json:"high24h"` // 24小时最高价 + Low24h string `json:"low24h"` // 24小时最低价 + VolCcy24h string `json:"volCcy24h"` // 24小时成交量(按币种计) + Vol24h string `json:"vol24h"` // 24小时成交量(按张数计) + Ts string `json:"ts"` // 数据生成时间戳 + SodUtc0 string `json:"sodUtc0"` // UTC 0点开盘价 + SodUtc8 string `json:"sodUtc8"` // UTC 8点开盘价 +} diff --git a/test/okxAli/get_candles_test.go b/test/okxAli/get_candles_test.go new file mode 100644 index 0000000..58b895a --- /dev/null +++ b/test/okxAli/get_candles_test.go @@ -0,0 +1,58 @@ +package okx + +import ( + "fmt" + . "gitea.zjmud.xyz/phyer/tanya/okx" + "strconv" + "testing" + "time" +) + +func TestGetCandles(t *testing.T) { + service := NewOkxPublicDataService() + + params := CandlesRequest{ + InstID: "BTC-USDT", + Bar: "1H", + Limit: "10", + } + candles, err := service.GetCandles(params) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if len(candles) == 0 { + t.Fatal("Expected at least one candle, got none") + } + + // 检查返回数据的结构 + for _, candle := range candles { + if candle.Timestamp == "" { + t.Error("Expected non-empty Timestamp") + } + if candle.Open == "" || candle.High == "" || candle.Low == "" || candle.Close == "" { + t.Error("Expected non-empty OHLC values") + } + if candle.Volume == "" || candle.VolumeCcy == "" { + t.Error("Expected non-empty Volume and VolumeCcy") + } + } +} + +func TestGetCandles_InvalidInstID(t *testing.T) { + service := NewOkxPublicDataService() + + layout := "2006-01-02 15:04:05" + startTime, _ := time.Parse(layout, "2023-01-01 00:00:00") + endTime, _ := time.Parse(layout, "2023-12-31 23:59:59") + candles, _ := service.GetCandles(CandlesRequest{ + InstID: "BTC-USDT", + Bar: "1D", + Before: strconv.FormatInt(startTime.UnixMilli(), 10), + After: strconv.FormatInt(endTime.UnixMilli(), 10), + Limit: "50", + }) + + fmt.Println("Candles:", candles) + +} diff --git a/test/okxAli/get_instruments_test.go b/test/okxAli/get_instruments_test.go new file mode 100644 index 0000000..c26f94a --- /dev/null +++ b/test/okxAli/get_instruments_test.go @@ -0,0 +1,44 @@ +package okx + +import ( + . "gitea.zjmud.xyz/phyer/tanya/okx" + "testing" +) + +func TestGetInstruments(t *testing.T) { + service := NewOkxPublicDataService() // 使用默认的正式环境 URL + + params := InstrumentsRequest{InstType: "SPOT"} + instruments, err := service.GetInstruments(params) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if len(instruments) == 0 { + t.Fatal("Expected at least one instrument, got none") + } + + // 检查返回数据的结构 + for _, inst := range instruments { + if inst.InstType != "SPOT" { + t.Errorf("Expected InstType 'SPOT', got %s", inst.InstType) + } + if inst.InstID == "" { + t.Error("Expected non-empty InstID") + } + if inst.BaseCcy == "" || inst.QuoteCcy == "" { + t.Error("Expected non-empty BaseCcy and QuoteCcy") + } + } +} + +func TestGetInstruments_InvalidInstType(t *testing.T) { + service := NewOkxPublicDataService() + + params := InstrumentsRequest{InstType: "INVALID"} + _, err := service.GetInstruments(params) + if err == nil { + t.Fatal("Expected an error for invalid instType, got nil") + } +} + diff --git a/test/okxAli/get_ticker_test.go b/test/okxAli/get_ticker_test.go new file mode 100644 index 0000000..bd4ef8b --- /dev/null +++ b/test/okxAli/get_ticker_test.go @@ -0,0 +1,45 @@ +package okx + +import ( + "fmt" + . "gitea.zjmud.xyz/phyer/tanya/okx" + "testing" +) + +func TestGetTicker(t *testing.T) { + service := NewOkxPublicDataService() + + params := TickerRequest{InstID: "BTC-USDT"} + ticker, err := service.GetTicker(params) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // 检查返回数据的结构 + if ticker.InstID != "BTC-USDT" { + t.Errorf("Expected InstID 'BTC-USDT', got %s", ticker.InstID) + } + if ticker.Last == "" { + t.Error("Expected non-empty Last price") + } + if ticker.AskPx == "" || ticker.BidPx == "" { + t.Error("Expected non-empty AskPx and BidPx") + } + if ticker.Open24h == "" || ticker.High24h == "" || ticker.Low24h == "" { + t.Error("Expected non-empty 24h OHLC values") + } + if ticker.Vol24h == "" || ticker.VolCcy24h == "" { + t.Error("Expected non-empty 24h volume values") + } +} + +func TestGetTicker_InvalidInstID(t *testing.T) { + service := NewOkxPublicDataService() + + params := TickerRequest{InstID: "BTC-USDT"} + ticker, _ := service.GetTicker(params) + // if err == nil { + // t.Fatal("Expected an error for invalid instId, got nil") + // } + fmt.Println("ticker: ", ticker) +}