up
This commit is contained in:
commit
18f15b0534
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
vendor
|
49
cmd/main.go
Normal file
49
cmd/main.go
Normal file
@ -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)
|
||||||
|
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module gitea.zjmud.xyz/phyer/tanya
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require github.com/google/go-querystring v1.1.0
|
5
go.sum
Normal file
5
go.sum
Normal file
@ -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=
|
28
okx/candleApi.go
Normal file
28
okx/candleApi.go
Normal file
@ -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"`
|
||||||
|
}
|
21
okx/instrmentsApi.go
Normal file
21
okx/instrmentsApi.go
Normal file
@ -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"`
|
||||||
|
// 可根据需要添加更多字段
|
||||||
|
}
|
142
okx/publicApiService.go
Normal file
142
okx/publicApiService.go
Normal file
@ -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
|
||||||
|
}
|
26
okx/tickerApi.go
Normal file
26
okx/tickerApi.go
Normal file
@ -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点开盘价
|
||||||
|
}
|
58
test/okxAli/get_candles_test.go
Normal file
58
test/okxAli/get_candles_test.go
Normal file
@ -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)
|
||||||
|
|
||||||
|
}
|
44
test/okxAli/get_instruments_test.go
Normal file
44
test/okxAli/get_instruments_test.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
45
test/okxAli/get_ticker_test.go
Normal file
45
test/okxAli/get_ticker_test.go
Normal file
@ -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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user