feat: implement coinank openapi rank interface (#1250)

- implement VisualScreener Rank
- implement More Instrument Agg Rank
- rank interface can help user to choose a coin
This commit is contained in:
wqqqqqq
2025-12-20 01:12:50 +08:00
committed by GitHub
parent e2d702c662
commit 7baa07738a
5 changed files with 460 additions and 1 deletions
@@ -0,0 +1,85 @@
package coinank_enum
type InstrumentAggSortBy string
const (
OpenInterest InstrumentAggSortBy = "openInterest"
Price InstrumentAggSortBy = "price"
PriceChangeM5 InstrumentAggSortBy = "priceChangeM5"
PriceChangeM15 InstrumentAggSortBy = "priceChangeM15"
PriceChangeM30 InstrumentAggSortBy = "priceChangeM30"
PriceChangeH4 InstrumentAggSortBy = "priceChangeH4"
PriceChangeH24 InstrumentAggSortBy = "priceChangeH24"
PriceChangeH1 InstrumentAggSortBy = "priceChangeH1"
PriceChangeH6 InstrumentAggSortBy = "priceChangeH6"
PriceChangeH12 InstrumentAggSortBy = "priceChangeH12"
OpenInterestChM15 InstrumentAggSortBy = "openInterestChM15"
OpenInterestChM5 InstrumentAggSortBy = "openInterestChM5"
OpenInterestChM30 InstrumentAggSortBy = "openInterestChM30"
OpenInterestCh1 InstrumentAggSortBy = "openInterestCh1"
OpenInterestCh4 InstrumentAggSortBy = "openInterestCh4"
OpenInterestCh24 InstrumentAggSortBy = "openInterestCh24"
OpenInterestCh2D InstrumentAggSortBy = "openInterestCh2D"
OpenInterestCh3D InstrumentAggSortBy = "openInterestCh3D"
OpenInterestCh7D InstrumentAggSortBy = "openInterestCh7D"
LiquidationH1 InstrumentAggSortBy = "liquidationH1"
LiquidationH1Long InstrumentAggSortBy = "liquidationH1Long"
LiquidationH1Short InstrumentAggSortBy = "liquidationH1Short"
LiquidationH4Long InstrumentAggSortBy = "liquidationH4Long"
LiquidationH4Short InstrumentAggSortBy = "liquidationH4Short"
LiquidationH24Short InstrumentAggSortBy = "liquidationH24Short"
LiquidationH24Long InstrumentAggSortBy = "liquidationH24Long"
LiquidationH4 InstrumentAggSortBy = "liquidationH4"
FundingRate InstrumentAggSortBy = "fundingRate"
LiquidationH12 InstrumentAggSortBy = "liquidationH12"
LiquidationH12Long InstrumentAggSortBy = "liquidationH12Long"
LiquidationH12Short InstrumentAggSortBy = "liquidationH12Short"
LiquidationH24 InstrumentAggSortBy = "liquidationH24"
Turnover24h InstrumentAggSortBy = "turnover24h"
TurnoverChg24h InstrumentAggSortBy = "turnoverChg24h"
LongShortAccount InstrumentAggSortBy = "longShortAccount"
LsPersonChg5m InstrumentAggSortBy = "lsPersonChg5m"
LsPersonChg15m InstrumentAggSortBy = "lsPersonChg15m"
LsPersonChg30m InstrumentAggSortBy = "lsPersonChg30m"
LsPersonChg1h InstrumentAggSortBy = "lsPersonChg1h"
LsPersonChg4h InstrumentAggSortBy = "lsPersonChg4h"
LongShortPerson InstrumentAggSortBy = "longShortPerson"
LongShortPosition InstrumentAggSortBy = "longShortPosition"
LongShortRatio InstrumentAggSortBy = "longShortRatio"
MarketCap InstrumentAggSortBy = "marketCap"
MarketCapChange24H InstrumentAggSortBy = "marketCapChange24H"
MarketCapChangeValue24H InstrumentAggSortBy = "marketCapChangeValue24H"
TotalSupply InstrumentAggSortBy = "totalSupply"
MaxSupply InstrumentAggSortBy = "maxSupply"
CirculatingSupply InstrumentAggSortBy = "circulatingSupply"
Buy5m InstrumentAggSortBy = "buy5m"
Sell5m InstrumentAggSortBy = "sell5m"
Buy15m InstrumentAggSortBy = "buy15m"
Sell15m InstrumentAggSortBy = "sell15m"
Buy30m InstrumentAggSortBy = "buy30m"
Sell30m InstrumentAggSortBy = "sell30m"
Buy1h InstrumentAggSortBy = "buy1h"
Sell1h InstrumentAggSortBy = "sell1h"
Buy2h InstrumentAggSortBy = "buy2h"
Sell2h InstrumentAggSortBy = "sell2h"
Buy4h InstrumentAggSortBy = "buy4h"
Sell4h InstrumentAggSortBy = "sell4h"
Buy6h InstrumentAggSortBy = "buy6h"
Sell6h InstrumentAggSortBy = "sell6h"
Buy8h InstrumentAggSortBy = "buy8h"
Sell8h InstrumentAggSortBy = "sell8h"
Buy12h InstrumentAggSortBy = "buy12h"
Sell12h InstrumentAggSortBy = "sell12h"
Buy24h InstrumentAggSortBy = "buy24h"
Sell24h InstrumentAggSortBy = "sell24h"
TurnoverChg15m InstrumentAggSortBy = "turnoverChg15m"
TurnoverChg30m InstrumentAggSortBy = "turnoverChg30m"
TurnoverChg1h InstrumentAggSortBy = "turnoverChg1h"
TurnoverChg4h InstrumentAggSortBy = "turnoverChg4h"
VolatilityM5 InstrumentAggSortBy = "volatilityM5"
VolatilityM15 InstrumentAggSortBy = "volatilityM15"
VolatilityH1 InstrumentAggSortBy = "volatilityH1"
OiVsMar InstrumentAggSortBy = "oiVsMar"
OiVsVol InstrumentAggSortBy = "oiVsVol"
VolVsMar InstrumentAggSortBy = "volVsMar"
)
@@ -0,0 +1,8 @@
package coinank_enum
type SortType string
const (
Asc SortType = "asc"
Desc SortType = "desc"
)
+11 -1
View File
@@ -18,13 +18,23 @@ type CoinankClient struct {
Apikey string
}
// CoinankResponse coinank openapi common respones
// CoinankResponse coinank openapi common response
type CoinankResponse[T any] struct {
Success bool `json:"success"`
Code string `json:"code"`
Data T `json:"data"`
}
// PageData coinank openapi pageData in response
type PageData[T any] struct {
List []T `json:"list"`
Pagination struct {
Current int `json:"current"`
Total int `json:"total"`
PageSize int `json:"pageSize"`
} `json:"pagination"`
}
var HttpError error = errors.New("http client error")
// NewCoinankClient new coinank http client for coinank openapi
+255
View File
@@ -0,0 +1,255 @@
package coinank
import (
"context"
"encoding/json"
"nofx/provider/coinank/coinank_enum"
"strconv"
)
// VisualScreener Visual Screener
func (c *CoinankClient) VisualScreener(ctx context.Context, interval coinank_enum.Interval) ([]VisualScreenerResponse, error) {
paramsMap := make(map[string]string, 1)
paramsMap["interval"] = string(interval)
resp, err := c.Get(ctx, "/api/instruments/visualScreener", paramsMap)
if err != nil {
return nil, err
}
var result CoinankResponse[[]VisualScreenerResponse]
err = json.Unmarshal([]byte(resp), &result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, HttpError
}
return result.Data, nil
}
// OiRank Open Interest Ranking
func (c *CoinankClient) OiRank(ctx context.Context, sortBy coinank_enum.InstrumentAggSortBy,
sortType coinank_enum.SortType, page int, size int) ([]OiRankResponse, error) {
resp, err := c.Get(ctx, "/api/instruments/oiRank", c.rankParam(sortBy, sortType, page, size))
if err != nil {
return nil, err
}
var result CoinankResponse[CoinankResponse[PageData[OiRankResponse]]]
err = json.Unmarshal([]byte(resp), &result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, HttpError
}
if !result.Data.Success {
return nil, HttpError
}
return result.Data.Data.List, nil
}
// LongShortRank longShortRatio Ranking
func (c *CoinankClient) LongShortRank(ctx context.Context, sortBy coinank_enum.InstrumentAggSortBy,
sortType coinank_enum.SortType, page int, size int) ([]LongShortRankResponse, error) {
resp, err := c.Get(ctx, "/api/instruments/longShortRank", c.rankParam(sortBy, sortType, page, size))
if err != nil {
return nil, err
}
var result CoinankResponse[CoinankResponse[PageData[LongShortRankResponse]]]
err = json.Unmarshal([]byte(resp), &result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, HttpError
}
if !result.Data.Success {
return nil, HttpError
}
return result.Data.Data.List, nil
}
// LiquidationRank Liquidation Ranking
func (c *CoinankClient) LiquidationRank(ctx context.Context, sortBy coinank_enum.InstrumentAggSortBy,
sortType coinank_enum.SortType, page int, size int) ([]LiquidationRankResponse, error) {
resp, err := c.Get(ctx, "/api/instruments/liquidationRank", c.rankParam(sortBy, sortType, page, size))
if err != nil {
return nil, err
}
var result CoinankResponse[CoinankResponse[PageData[LiquidationRankResponse]]]
err = json.Unmarshal([]byte(resp), &result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, HttpError
}
if !result.Data.Success {
return nil, HttpError
}
return result.Data.Data.List, nil
}
// PriceRank PriceChg Ranking
func (c *CoinankClient) PriceRank(ctx context.Context, sortBy coinank_enum.InstrumentAggSortBy,
sortType coinank_enum.SortType, page int, size int) ([]PriceRankResponse, error) {
resp, err := c.Get(ctx, "/api/instruments/priceRank", c.rankParam(sortBy, sortType, page, size))
if err != nil {
return nil, err
}
var result CoinankResponse[CoinankResponse[PageData[PriceRankResponse]]]
err = json.Unmarshal([]byte(resp), &result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, HttpError
}
if !result.Data.Success {
return nil, HttpError
}
return result.Data.Data.List, nil
}
// VolumeRank VolumeChg Ranking
func (c *CoinankClient) VolumeRank(ctx context.Context, sortBy coinank_enum.InstrumentAggSortBy,
sortType coinank_enum.SortType, page int, size int) ([]VolumeRankResponse, error) {
resp, err := c.Get(ctx, "/api/instruments/volumeRank", c.rankParam(sortBy, sortType, page, size))
if err != nil {
return nil, err
}
var result CoinankResponse[CoinankResponse[PageData[VolumeRankResponse]]]
err = json.Unmarshal([]byte(resp), &result)
if err != nil {
return nil, err
}
if !result.Success {
return nil, HttpError
}
if !result.Data.Success {
return nil, HttpError
}
return result.Data.Data.List, nil
}
func (c *CoinankClient) rankParam(sortBy coinank_enum.InstrumentAggSortBy,
sortType coinank_enum.SortType, page int, size int) map[string]string {
paramsMap := make(map[string]string, 4)
if page < 1 {
page = 1
}
if size < 1 {
size = 10
}
if sortBy == "" {
sortBy = coinank_enum.OpenInterest
}
if sortType == "" {
sortType = coinank_enum.Desc
}
paramsMap["page"] = strconv.Itoa(page)
paramsMap["size"] = strconv.Itoa(size)
paramsMap["sortBy"] = string(sortBy)
paramsMap["sortType"] = string(sortType)
return paramsMap
}
type VisualScreenerResponse struct {
BaseCoin string `json:"baseCoin"`
PriceChg float64 `json:"priceChg"`
OiChg float64 `json:"oiChg"`
VoChg float64 `json:"voChg"`
}
type LongShortRankResponse struct {
BaseCoin string `json:"baseCoin"`
CoinImage string `json:"coinImage"`
Price float64 `json:"price"`
LongShortPerson float64 `json:"longShortPerson"`
LsPersonChg5M float64 `json:"lsPersonChg5m"`
LsPersonChg15M float64 `json:"lsPersonChg15m"`
LsPersonChg30M float64 `json:"lsPersonChg30m"`
LsPersonChg1H float64 `json:"lsPersonChg1h"`
LsPersonChg4H float64 `json:"lsPersonChg4h"`
CirculatingSupply int `json:"circulatingSupply"`
Symbol string `json:"symbol"`
ExchangeName string `json:"exchangeName"`
SupportContract bool `json:"supportContract"`
}
type OiRankResponse struct {
BaseCoin string `json:"baseCoin"`
CoinImage string `json:"coinImage"`
Price float64 `json:"price"`
OpenInterest float64 `json:"openInterest"`
OpenInterestChM5 float64 `json:"openInterestChM5"`
OpenInterestChM15 float64 `json:"openInterestChM15"`
OpenInterestChM30 float64 `json:"openInterestChM30"`
OpenInterestCh1 float64 `json:"openInterestCh1"`
OpenInterestCh4 float64 `json:"openInterestCh4"`
OpenInterestCh24 float64 `json:"openInterestCh24"`
OpenInterestCh2D float64 `json:"openInterestCh2D"`
OpenInterestCh3D float64 `json:"openInterestCh3D"`
OpenInterestCh7D float64 `json:"openInterestCh7D"`
CirculatingSupply int `json:"circulatingSupply"`
Symbol string `json:"symbol"`
ExchangeName string `json:"exchangeName"`
SupportContract bool `json:"supportContract"`
Follow bool `json:"follow"`
}
type LiquidationRankResponse struct {
BaseCoin string `json:"baseCoin"`
CoinImage string `json:"coinImage"`
Price float64 `json:"price"`
PriceChangeH24 float64 `json:"priceChangeH24"`
LiquidationH1 float64 `json:"liquidationH1"`
LiquidationH1Long float64 `json:"liquidationH1Long"`
LiquidationH1Short float64 `json:"liquidationH1Short"`
LiquidationH4 float64 `json:"liquidationH4"`
LiquidationH4Long float64 `json:"liquidationH4Long"`
LiquidationH4Short float64 `json:"liquidationH4Short"`
LiquidationH12 float64 `json:"liquidationH12"`
LiquidationH12Long float64 `json:"liquidationH12Long"`
LiquidationH12Short float64 `json:"liquidationH12Short"`
LiquidationH24 float64 `json:"liquidationH24"`
LiquidationH24Long float64 `json:"liquidationH24Long"`
LiquidationH24Short float64 `json:"liquidationH24Short"`
CirculatingSupply int `json:"circulatingSupply"`
SupportContract bool `json:"supportContract"`
}
type PriceRankResponse struct {
BaseCoin string `json:"baseCoin"`
CoinImage string `json:"coinImage"`
Price float64 `json:"price"`
PriceChangeH24 float64 `json:"priceChangeH24"`
PriceChangeM5 float64 `json:"priceChangeM5"`
PriceChangeM15 float64 `json:"priceChangeM15"`
PriceChangeM30 float64 `json:"priceChangeM30"`
PriceChangeH1 float64 `json:"priceChangeH1"`
PriceChangeH2 float64 `json:"priceChangeH2"`
PriceChangeH4 float64 `json:"priceChangeH4"`
PriceChangeH6 float64 `json:"priceChangeH6"`
PriceChangeH8 float64 `json:"priceChangeH8"`
PriceChangeH12 float64 `json:"priceChangeH12"`
CirculatingSupply int `json:"circulatingSupply"`
Symbol string `json:"symbol"`
ExchangeName string `json:"exchangeName"`
SupportContract bool `json:"supportContract"`
}
type VolumeRankResponse struct {
BaseCoin string `json:"baseCoin"`
CoinImage string `json:"coinImage"`
Price float64 `json:"price"`
Turnover24H float64 `json:"turnover24h"`
TurnoverChg24H float64 `json:"turnoverChg24h"`
TurnoverChg4H float64 `json:"turnoverChg4h"`
TurnoverChg1H float64 `json:"turnoverChg1h"`
TurnoverChg30M float64 `json:"turnoverChg30m"`
TurnoverChg15M float64 `json:"turnoverChg15m"`
CirculatingSupply int `json:"circulatingSupply"`
Symbol string `json:"symbol"`
ExchangeName string `json:"exchangeName"`
SupportContract bool `json:"supportContract"`
}
@@ -0,0 +1,101 @@
package coinank
import (
"context"
"encoding/json"
"nofx/provider/coinank/coinank_enum"
"testing"
)
func TestVisualScreener(t *testing.T) {
client := NewCoinankClient(coinank_enum.MainUrl, TestApikey)
resp, err := client.VisualScreener(context.TODO(), coinank_enum.Minute15)
if err != nil {
t.Error(err)
}
res, err := json.Marshal(resp)
if err != nil {
t.Error(err)
}
t.Logf("%s", res)
}
func TestOiRank(t *testing.T) {
client := NewCoinankClient(coinank_enum.MainUrl, TestApikey)
resp, err := client.OiRank(context.TODO(), coinank_enum.OpenInterest, coinank_enum.Desc, 1, 10)
if err != nil {
t.Error(err)
}
if resp[0].BaseCoin != "BTC" {
t.Error("oi first not BTC")
}
res, err := json.Marshal(resp)
if err != nil {
t.Error(err)
}
t.Logf("%s", res)
}
func TestLongShortRank(t *testing.T) {
client := NewCoinankClient(coinank_enum.MainUrl, TestApikey)
resp, err := client.LongShortRank(context.TODO(), coinank_enum.LongShortRatio, coinank_enum.Desc, 1, 10)
if err != nil {
t.Error(err)
}
if resp[0].BaseCoin == "" {
t.Error("baseCoin is empty")
}
res, err := json.Marshal(resp)
if err != nil {
t.Error(err)
}
t.Logf("%s", res)
}
func TestLiquidationRank(t *testing.T) {
client := NewCoinankClient(coinank_enum.MainUrl, TestApikey)
resp, err := client.LiquidationRank(context.TODO(), coinank_enum.LiquidationH1, coinank_enum.Desc, 1, 10)
if err != nil {
t.Error(err)
}
if resp[0].BaseCoin == "" {
t.Error("baseCoin is empty")
}
res, err := json.Marshal(resp)
if err != nil {
t.Error(err)
}
t.Logf("%s", res)
}
func TestPriceRank(t *testing.T) {
client := NewCoinankClient(coinank_enum.MainUrl, TestApikey)
resp, err := client.PriceRank(context.TODO(), coinank_enum.Price, coinank_enum.Desc, 1, 10)
if err != nil {
t.Error(err)
}
if resp[0].BaseCoin == "" {
t.Error("baseCoin is empty")
}
res, err := json.Marshal(resp)
if err != nil {
t.Error(err)
}
t.Logf("%s", res)
}
func TestVolumeRank(t *testing.T) {
client := NewCoinankClient(coinank_enum.MainUrl, TestApikey)
resp, err := client.VolumeRank(context.TODO(), coinank_enum.Turnover24h, coinank_enum.Desc, 1, 10)
if err != nil {
t.Error(err)
}
if resp[0].BaseCoin == "" {
t.Error("baseCoin is empty")
}
res, err := json.Marshal(resp)
if err != nil {
t.Error(err)
}
t.Logf("%s", res)
}