From 612e25db66b8cd91b42dad680f5ea6b8c7c723d0 Mon Sep 17 00:00:00 2001 From: wqqqqqq Date: Wed, 17 Dec 2025 10:22:02 +0800 Subject: [PATCH] feat: init coinank openapi http request framework (#1240) --- provider/coinank/base_coin.go | 62 +++++++++++++ provider/coinank/base_coin_test.go | 34 +++++++ provider/coinank/coinank_enum/exchange.go | 25 ++++++ provider/coinank/coinank_enum/product_type.go | 6 ++ provider/coinank/coinank_enum/url.go | 5 ++ provider/coinank/coinank_http.go | 86 ++++++++++++++++++ provider/coinank/coinank_http_test.go | 3 + provider/coinank/instruments.go | 89 +++++++++++++++++++ provider/coinank/instruments_test.go | 34 +++++++ 9 files changed, 344 insertions(+) create mode 100644 provider/coinank/base_coin.go create mode 100644 provider/coinank/base_coin_test.go create mode 100644 provider/coinank/coinank_enum/exchange.go create mode 100644 provider/coinank/coinank_enum/product_type.go create mode 100644 provider/coinank/coinank_enum/url.go create mode 100644 provider/coinank/coinank_http.go create mode 100644 provider/coinank/coinank_http_test.go create mode 100644 provider/coinank/instruments.go create mode 100644 provider/coinank/instruments_test.go diff --git a/provider/coinank/base_coin.go b/provider/coinank/base_coin.go new file mode 100644 index 00000000..6da480cc --- /dev/null +++ b/provider/coinank/base_coin.go @@ -0,0 +1,62 @@ +package coinank + +import ( + "context" + "encoding/json" + "nofx/provider/coinank/coinank_enum" +) + +// ListCoin list all support coin from coinank, response is list of coin symbol +func (c *CoinankClient) ListCoin(ctx context.Context, productType coinank_enum.ProductType) (*[]string, error) { + paramsMap := make(map[string]string, 1) + paramsMap["productType"] = string(productType) + resp, err := c.Get(ctx, "/api/baseCoin/list", paramsMap) + if err != nil { + return nil, err + } + var result CoinankResponse[[]string] + err = json.Unmarshal([]byte(resp), &result) + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(resp), &result) + if err != nil { + return nil, err + } + if !result.Success { + return nil, HttpError + } + return &result.Data, nil +} + +// ListSymbols list all support symbols from coinank +func (c *CoinankClient) ListSymbols(ctx context.Context, exchange coinank_enum.Exchange, productType coinank_enum.ProductType) (*[]SymbolResp, error) { + paramsMap := make(map[string]string, 2) + paramsMap["exchange"] = string(exchange) + paramsMap["productType"] = string(productType) + resp, err := c.Get(ctx, "/api/baseCoin/symbols", paramsMap) + if err != nil { + return nil, err + } + var result CoinankResponse[[]SymbolResp] + err = json.Unmarshal([]byte(resp), &result) + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(resp), &result) + if err != nil { + return nil, err + } + if !result.Success { + return nil, HttpError + } + return &result.Data, nil +} + +type SymbolResp struct { + Symbol string `json:"symbol"` // symbol,such as:`BTCUSDT` + BaseCoin string `json:"baseCoin"` // baseCoin from symbol,such as `BTC` + ExchangeName string `json:"exchangeName"` // symbol source ,such as:`Binance` + ExpireAt int `json:"expireAt"` + UpdateAt int `json:"updateAt"` +} diff --git a/provider/coinank/base_coin_test.go b/provider/coinank/base_coin_test.go new file mode 100644 index 00000000..47c8a670 --- /dev/null +++ b/provider/coinank/base_coin_test.go @@ -0,0 +1,34 @@ +package coinank + +import ( + "context" + "encoding/json" + "nofx/provider/coinank/coinank_enum" + "testing" +) + +func TestListCoin(t *testing.T) { + client := NewCoinankClient(coinank_enum.MainUrl, TestApikey) + resp, err := client.ListCoin(context.TODO(), "SPOT") + if err != nil { + t.Error(err) + } + res, err := json.Marshal(resp) + if err != nil { + t.Error(err) + } + t.Logf("%s", res) +} + +func TestListSymbols(t *testing.T) { + client := NewCoinankClient(coinank_enum.MainUrl, TestApikey) + resp, err := client.ListSymbols(context.TODO(), "Binance", "SWAP") + if err != nil { + t.Error(err) + } + res, err := json.Marshal(resp) + if err != nil { + t.Error(err) + } + t.Logf("%s", res) +} diff --git a/provider/coinank/coinank_enum/exchange.go b/provider/coinank/coinank_enum/exchange.go new file mode 100644 index 00000000..854109e1 --- /dev/null +++ b/provider/coinank/coinank_enum/exchange.go @@ -0,0 +1,25 @@ +package coinank_enum + +type Exchange string + +const ( // all maybe support exchange + Binance Exchange = "Binance" + Huobi Exchange = "Huobi" + Okex Exchange = "Okex" + Bitmex Exchange = "Bitmex" + FTX Exchange = "FTX" + Bybit Exchange = "Bybit" + Gate Exchange = "Gate" + Bitget Exchange = "Bitget" + dYdX Exchange = "dYdX" + Deribit Exchange = "Deribit" + Kraken Exchange = "Kraken" + Bitfinex Exchange = "Bitfinex" + AAX Exchange = "AAX" + CME Exchange = "CME" + Upbit Exchange = "Upbit" + Coinbase Exchange = "Coinbase" + Hyperliquid Exchange = "Hyperliquid" + Bitunix Exchange = "Bitunix" + Aster Exchange = "Aster" +) diff --git a/provider/coinank/coinank_enum/product_type.go b/provider/coinank/coinank_enum/product_type.go new file mode 100644 index 00000000..72bfa357 --- /dev/null +++ b/provider/coinank/coinank_enum/product_type.go @@ -0,0 +1,6 @@ +package coinank_enum + +type ProductType string + +const SWAP ProductType = "SWAP" //Contract +const SPOT ProductType = "SPOT" //SPOT diff --git a/provider/coinank/coinank_enum/url.go b/provider/coinank/coinank_enum/url.go new file mode 100644 index 00000000..f3ff1323 --- /dev/null +++ b/provider/coinank/coinank_enum/url.go @@ -0,0 +1,5 @@ +package coinank_enum + +var MainUrl = "https://open-api.coinank.com" //coinank openapi main url + +var MainCnUrl = "https://open-api-cn.coinank.com" //coinank openapi url for chinese mainland diff --git a/provider/coinank/coinank_http.go b/provider/coinank/coinank_http.go new file mode 100644 index 00000000..ab5ae6d3 --- /dev/null +++ b/provider/coinank/coinank_http.go @@ -0,0 +1,86 @@ +package coinank + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" +) + +// CoinankClient coinank openapi url and apikey +type CoinankClient struct { + Url string + Apikey string +} + +// CoinankResponse coinank openapi common respones +type CoinankResponse[T any] struct { + Success bool `json:"success"` + Code string `json:"code"` + Data T `json:"data"` +} + +var HttpError error = errors.New("http client error") + +// NewCoinankClient new coinank http client for coinank openapi +func NewCoinankClient(url, apikey string) *CoinankClient { + return &CoinankClient{url, apikey} +} + +// Get coinank openapi get request +func (c *CoinankClient) Get(ctx context.Context, path string, paramsMap map[string]string) (string, error) { + data := url.Values{} + for key, value := range paramsMap { + data.Add(key, value) + } + fullURL := fmt.Sprintf("%s%s?%s", c.Url, path, data.Encode()) + request, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil) + if err != nil { + return "", err + } + request.Header.Add("apikey", c.Apikey) + resp, err := client.Do(request) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +// Post coinank openapi post request +func (c *CoinankClient) Post(ctx context.Context, path string, data any) (string, error) { + fullURL := fmt.Sprintf("%s%s", c.Url, path) + postData, err := json.Marshal(data) + if err != nil { + return "", err + } + request, err := http.NewRequestWithContext(ctx, "POST", fullURL, bytes.NewBuffer(postData)) + if err != nil { + return "", err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Add("apikey", c.Apikey) + resp, err := client.Do(request) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +var client = &http.Client{ + Timeout: 30 * time.Second, +} diff --git a/provider/coinank/coinank_http_test.go b/provider/coinank/coinank_http_test.go new file mode 100644 index 00000000..4b795308 --- /dev/null +++ b/provider/coinank/coinank_http_test.go @@ -0,0 +1,3 @@ +package coinank + +var TestApikey = "" //need fill the apikey before test diff --git a/provider/coinank/instruments.go b/provider/coinank/instruments.go new file mode 100644 index 00000000..335eb093 --- /dev/null +++ b/provider/coinank/instruments.go @@ -0,0 +1,89 @@ +package coinank + +import ( + "context" + "encoding/json" + "nofx/provider/coinank/coinank_enum" +) + +// GetLastPrice get symbol latest information, param example -> symbol:`BTCUSDT`,exchange:`Binance`,productType:`SWAP` +func (c *CoinankClient) GetLastPrice(ctx context.Context, + symbol string, exchange coinank_enum.Exchange, productType coinank_enum.ProductType) (*GetLastPriceResponse, error) { + paramsMap := make(map[string]string, 3) + paramsMap["symbol"] = symbol + paramsMap["exchange"] = string(exchange) + paramsMap["productType"] = string(productType) + resp, err := c.Get(ctx, "/api/instruments/getLastPrice", paramsMap) + if err != nil { + return nil, err + } + var result CoinankResponse[GetLastPriceResponse] + err = json.Unmarshal([]byte(resp), &result) + if err != nil { + return nil, err + } + if !result.Success { + return nil, HttpError + } + return &result.Data, nil +} + +// GetCoinMarketCap get market cap info for coin ,example -> baseCoin:`BTC` +func (c *CoinankClient) GetCoinMarketCap(ctx context.Context, + baseCoin string) (*GetCoinMarketResponse, error) { + paramsMap := make(map[string]string, 1) + paramsMap["baseCoin"] = baseCoin + resp, err := c.Get(ctx, "/api/instruments/getCoinMarketCap", paramsMap) + if err != nil { + return nil, err + } + var result CoinankResponse[GetCoinMarketResponse] + err = json.Unmarshal([]byte(resp), &result) + if err != nil { + return nil, err + } + if !result.Success { + return nil, HttpError + } + return &result.Data, nil +} + +type GetLastPriceResponse struct { + BaseCoin string `json:"baseCoin"` //symbol base_coin + QuoteCoin string `json:"quoteCoin"` //symbol quote_coin + Symbol string `json:"symbol"` //symbol name + ExchangeName string `json:"exchangeName"` //symbol from exchange + ContractType string `json:"contractType"` //`SWAP`:Perpetual Contracts,`FUTURES`:Delivery Contracts + LastPrice float64 `json:"lastPrice"` //Latest transaction price + Open24H float64 `json:"open24h"` //24-hour opening price + High24H float64 `json:"high24h"` //24-hour highest price + Low24H float64 `json:"low24h"` //24-hour lowest price + PriceChange24H float64 `json:"priceChange24h"` //24-hour price changes + VolCcy24H float64 `json:"volCcy24h"` //24-hour trading volume + Turnover24H float64 `json:"turnover24h"` //24-hour transaction volume + TradeTimes int `json:"tradeTimes"` //Number of transactions + OiUSD float64 `json:"oiUSD"` //Open interest(USD) + OiCcy float64 `json:"oiCcy"` //Open interest(ccy) + OiVol float64 `json:"oiVol"` //Open interest(vol) + FundingRate float64 `json:"fundingRate"` //Real-time funding rates + MarkPrice float64 `json:"markPrice"` //mark price + LiqLong24H float64 `json:"liqLong24h"` //24-hour margin call on long positions + LiqShort24H float64 `json:"liqShort24h"` //24-hour margin call on short positions + Liq24H float64 `json:"liq24h"` //24-hour margin call + OiChg24H float64 `json:"oiChg24h"` //24-hour position changes + BuyTurnover float64 `json:"buyTurnover"` //buy turnover + SellTurnover float64 `json:"sellTurnover"` //sell turnover + Basis float64 `json:"basis"` + BasisRate float64 `json:"basisRate"` + ExpireAt int64 `json:"expireAt"` //expire time + Ts int `json:"ts"` +} + +type GetCoinMarketResponse struct { + BaseCoin string `json:"baseCoin"` //coin symbol such as `BTC` + Price float64 `json:"price"` // now price + MarketCap float64 `json:"marketCap"` // now market cap + CirculatingSupply float64 `json:"circulatingSupply"` + TotalSupply float64 `json:"totalSupply"` + SupportContract bool `json:"supportContract"` +} diff --git a/provider/coinank/instruments_test.go b/provider/coinank/instruments_test.go new file mode 100644 index 00000000..c6855b14 --- /dev/null +++ b/provider/coinank/instruments_test.go @@ -0,0 +1,34 @@ +package coinank + +import ( + "context" + "encoding/json" + "nofx/provider/coinank/coinank_enum" + "testing" +) + +func TestGetLastPrice(t *testing.T) { + client := NewCoinankClient(coinank_enum.MainUrl, TestApikey) + resp, err := client.GetLastPrice(context.TODO(), "BTCUSDT", "Binance", "SWAP") + if err != nil { + t.Error(err) + } + res, err := json.Marshal(resp) + if err != nil { + t.Error(err) + } + t.Logf("%s", res) +} + +func TestGetCoinMarketCap(t *testing.T) { + client := NewCoinankClient(coinank_enum.MainUrl, TestApikey) + resp, err := client.GetCoinMarketCap(context.TODO(), "BTC") + if err != nil { + t.Error(err) + } + res, err := json.Marshal(resp) + if err != nil { + t.Error(err) + } + t.Logf("%s", res) +}