2021-12-10 16:44:55 +00:00
|
|
|
|
package kucoinapi
|
|
|
|
|
|
2021-12-29 18:17:03 +00:00
|
|
|
|
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data
|
2021-12-30 17:39:45 +00:00
|
|
|
|
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data
|
2021-12-29 18:17:03 +00:00
|
|
|
|
|
2021-12-10 16:44:55 +00:00
|
|
|
|
import (
|
2021-12-25 18:23:06 +00:00
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
2021-12-10 16:44:55 +00:00
|
|
|
|
"net/url"
|
2021-12-10 17:41:23 +00:00
|
|
|
|
"strconv"
|
2021-12-25 18:23:06 +00:00
|
|
|
|
"time"
|
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
"github.com/c9s/requestgen"
|
2021-12-25 18:23:06 +00:00
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"github.com/valyala/fastjson"
|
2021-12-10 16:44:55 +00:00
|
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
2021-12-10 17:03:17 +00:00
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
2021-12-10 16:44:55 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type MarketDataService struct {
|
|
|
|
|
client *RestClient
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-25 18:23:06 +00:00
|
|
|
|
func (s *MarketDataService) NewGetKLinesRequest() *GetKLinesRequest {
|
|
|
|
|
return &GetKLinesRequest{client: s.client}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-10 16:44:55 +00:00
|
|
|
|
type Symbol struct {
|
|
|
|
|
Symbol string `json:"symbol"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
BaseCurrency string `json:"baseCurrency"`
|
|
|
|
|
QuoteCurrency string `json:"quoteCurrency"`
|
|
|
|
|
FeeCurrency string `json:"feeCurrency"`
|
|
|
|
|
Market string `json:"market"`
|
|
|
|
|
BaseMinSize fixedpoint.Value `json:"baseMinSize"`
|
|
|
|
|
QuoteMinSize fixedpoint.Value `json:"quoteMinSize"`
|
|
|
|
|
BaseIncrement fixedpoint.Value `json:"baseIncrement"`
|
|
|
|
|
QuoteIncrement fixedpoint.Value `json:"quoteIncrement"`
|
|
|
|
|
PriceIncrement fixedpoint.Value `json:"priceIncrement"`
|
|
|
|
|
PriceLimitRate fixedpoint.Value `json:"priceLimitRate"`
|
|
|
|
|
IsMarginEnabled bool `json:"isMarginEnabled"`
|
|
|
|
|
EnableTrading bool `json:"enableTrading"`
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 18:22:33 +00:00
|
|
|
|
//go:generate GetRequest -type ListSymbolsRequest -url "/api/v1/symbols" -responseDataType []Symbol
|
2021-12-29 18:17:03 +00:00
|
|
|
|
type ListSymbolsRequest struct {
|
|
|
|
|
client requestgen.APIClient
|
|
|
|
|
market *string `param:"market"`
|
|
|
|
|
}
|
2021-12-10 16:44:55 +00:00
|
|
|
|
|
2021-12-29 18:22:33 +00:00
|
|
|
|
func (s *MarketDataService) NewListSymbolsRequest() *ListSymbolsRequest {
|
|
|
|
|
return &ListSymbolsRequest{client: s.client}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 18:17:03 +00:00
|
|
|
|
func (s *MarketDataService) ListSymbols(market ...string) ([]Symbol, error) {
|
2021-12-29 18:22:33 +00:00
|
|
|
|
req := s.NewListSymbolsRequest()
|
2021-12-10 16:44:55 +00:00
|
|
|
|
if len(market) == 1 {
|
2021-12-29 18:17:03 +00:00
|
|
|
|
req.Market(market[0])
|
2021-12-10 16:44:55 +00:00
|
|
|
|
} else if len(market) > 1 {
|
|
|
|
|
return nil, errors.New("symbols api only supports one market parameter")
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 18:17:03 +00:00
|
|
|
|
return req.Do(context.Background())
|
2021-12-10 16:44:55 +00:00
|
|
|
|
}
|
2021-12-10 17:03:17 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
//Get Ticker
|
|
|
|
|
{
|
|
|
|
|
"sequence": "1550467636704",
|
|
|
|
|
"bestAsk": "0.03715004",
|
|
|
|
|
"size": "0.17",
|
|
|
|
|
"price": "0.03715005",
|
|
|
|
|
"bestBidSize": "3.803",
|
|
|
|
|
"bestBid": "0.03710768",
|
|
|
|
|
"bestAskSize": "1.788",
|
|
|
|
|
"time": 1550653727731
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
type Ticker struct {
|
|
|
|
|
Sequence string `json:"sequence"`
|
|
|
|
|
Size fixedpoint.Value `json:"size"`
|
|
|
|
|
Price fixedpoint.Value `json:"price"`
|
|
|
|
|
BestAsk fixedpoint.Value `json:"bestAsk"`
|
|
|
|
|
BestBid fixedpoint.Value `json:"bestBid"`
|
|
|
|
|
BestBidSize fixedpoint.Value `json:"bestBidSize"`
|
|
|
|
|
Time types.MillisecondTimestamp `json:"time"`
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 18:22:33 +00:00
|
|
|
|
//go:generate GetRequest -type GetTickerRequest -url "/api/v1/market/orderbook/level1" -responseDataType Ticker
|
|
|
|
|
type GetTickerRequest struct {
|
|
|
|
|
client requestgen.APIClient
|
|
|
|
|
symbol string `param:"symbol,query"`
|
|
|
|
|
}
|
2021-12-10 17:03:17 +00:00
|
|
|
|
|
2021-12-29 18:22:33 +00:00
|
|
|
|
func (s *MarketDataService) NewGetTickerRequest(symbol string) *GetTickerRequest {
|
|
|
|
|
return &GetTickerRequest{client: s.client, symbol: symbol}
|
|
|
|
|
}
|
2021-12-10 17:03:17 +00:00
|
|
|
|
|
2021-12-29 18:22:33 +00:00
|
|
|
|
func (s *MarketDataService) GetTicker(symbol string) (*Ticker, error) {
|
|
|
|
|
req := s.NewGetTickerRequest(symbol)
|
|
|
|
|
return req.Do(context.Background())
|
2021-12-10 17:03:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
{
|
|
|
|
|
"time":1602832092060,
|
|
|
|
|
"ticker":[
|
|
|
|
|
{
|
|
|
|
|
"symbol": "BTC-USDT", // symbol
|
2021-12-21 17:26:00 +00:00
|
|
|
|
"symbolName":"BTC-USDT", // SymbolName of trading pairs, it would change after renaming
|
2021-12-10 17:03:17 +00:00
|
|
|
|
"buy": "11328.9", // bestAsk
|
|
|
|
|
"sell": "11329", // bestBid
|
|
|
|
|
"changeRate": "-0.0055", // 24h change rate
|
|
|
|
|
"changePrice": "-63.6", // 24h change price
|
|
|
|
|
"high": "11610", // 24h highest price
|
|
|
|
|
"low": "11200", // 24h lowest price
|
|
|
|
|
"vol": "2282.70993217", // 24h volume,the aggregated trading volume in BTC
|
|
|
|
|
"volValue": "25984946.157790431", // 24h total, the trading volume in quote currency of last 24 hours
|
|
|
|
|
"last": "11328.9", // last price
|
|
|
|
|
"averagePrice": "11360.66065903", // 24h average transaction price yesterday
|
|
|
|
|
"takerFeeRate": "0.001", // Basic Taker Fee
|
|
|
|
|
"makerFeeRate": "0.001", // Basic Maker Fee
|
|
|
|
|
"takerCoefficient": "1", // Taker Fee Coefficient
|
|
|
|
|
"makerCoefficient": "1" // Maker Fee Coefficient
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
*/
|
2021-12-10 17:03:17 +00:00
|
|
|
|
|
|
|
|
|
type Ticker24H struct {
|
2021-12-10 17:41:23 +00:00
|
|
|
|
Symbol string `json:"symbol"`
|
2021-12-21 17:26:00 +00:00
|
|
|
|
SymbolName string `json:"symbolName"`
|
2021-12-10 17:41:23 +00:00
|
|
|
|
Buy fixedpoint.Value `json:"buy"`
|
|
|
|
|
Sell fixedpoint.Value `json:"sell"`
|
|
|
|
|
ChangeRate fixedpoint.Value `json:"changeRate"`
|
|
|
|
|
ChangePrice fixedpoint.Value `json:"changePrice"`
|
|
|
|
|
High fixedpoint.Value `json:"high"`
|
|
|
|
|
Low fixedpoint.Value `json:"low"`
|
|
|
|
|
Last fixedpoint.Value `json:"last"`
|
2021-12-10 17:03:17 +00:00
|
|
|
|
AveragePrice fixedpoint.Value `json:"averagePrice"`
|
2021-12-10 17:41:23 +00:00
|
|
|
|
Volume fixedpoint.Value `json:"vol"` // base volume
|
|
|
|
|
VolumeValue fixedpoint.Value `json:"volValue"` // quote volume
|
2021-12-10 17:03:17 +00:00
|
|
|
|
|
|
|
|
|
TakerFeeRate fixedpoint.Value `json:"takerFeeRate"`
|
|
|
|
|
MakerFeeRate fixedpoint.Value `json:"makerFeeRate"`
|
|
|
|
|
|
|
|
|
|
TakerCoefficient fixedpoint.Value `json:"takerCoefficient"`
|
|
|
|
|
MakerCoefficient fixedpoint.Value `json:"makerCoefficient"`
|
2021-12-21 17:26:00 +00:00
|
|
|
|
|
|
|
|
|
Time types.MillisecondTimestamp `json:"time"`
|
2021-12-10 17:03:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AllTickers struct {
|
2021-12-10 17:41:23 +00:00
|
|
|
|
Time types.MillisecondTimestamp `json:"time"`
|
|
|
|
|
Ticker []Ticker24H `json:"ticker"`
|
2021-12-10 17:03:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 18:33:07 +00:00
|
|
|
|
//go:generate GetRequest -type GetAllTickersRequest -url "/api/v1/market/allTickers" -responseDataType AllTickers
|
|
|
|
|
type GetAllTickersRequest struct {
|
|
|
|
|
client requestgen.APIClient
|
|
|
|
|
}
|
2021-12-10 17:03:17 +00:00
|
|
|
|
|
2021-12-29 18:33:07 +00:00
|
|
|
|
func (s *MarketDataService) ListTickers() (*AllTickers, error) {
|
|
|
|
|
req := &GetAllTickersRequest{client: s.client}
|
|
|
|
|
return req.Do(context.Background())
|
2021-12-10 17:03:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-21 17:26:00 +00:00
|
|
|
|
func (s *MarketDataService) GetTicker24HStat(symbol string) (*Ticker24H, error) {
|
|
|
|
|
var params = url.Values{}
|
|
|
|
|
params.Add("symbol", symbol)
|
|
|
|
|
|
2021-12-29 14:06:21 +00:00
|
|
|
|
req, err := s.client.NewRequest(context.Background(), "GET", "/api/v1/market/stats", params, nil)
|
2021-12-21 17:26:00 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-22 15:52:44 +00:00
|
|
|
|
response, err := s.client.SendRequest(req)
|
2021-12-21 17:26:00 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var apiResponse struct {
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
Message string `json:"msg"`
|
|
|
|
|
Data *Ticker24H `json:"data"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return apiResponse.Data, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-10 17:41:23 +00:00
|
|
|
|
/*
|
|
|
|
|
{
|
|
|
|
|
"sequence": "3262786978",
|
|
|
|
|
"time": 1550653727731,
|
|
|
|
|
"bids": [["6500.12", "0.45054140"],
|
|
|
|
|
["6500.11", "0.45054140"]], //[price,size]
|
|
|
|
|
"asks": [["6500.16", "0.57753524"],
|
|
|
|
|
["6500.15", "0.57753524"]]
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
type OrderBook struct {
|
2021-12-25 11:34:27 +00:00
|
|
|
|
Sequence string `json:"sequence,omitempty"`
|
2021-12-10 17:41:23 +00:00
|
|
|
|
Time types.MillisecondTimestamp `json:"time"`
|
2021-12-25 11:34:27 +00:00
|
|
|
|
Bids types.PriceVolumeSlice `json:"bids,omitempty"`
|
|
|
|
|
Asks types.PriceVolumeSlice `json:"asks,omitempty"`
|
2021-12-10 17:41:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
//go:generate GetRequest -type GetOrderBookLevel2Depth20Request -url "/api/v1/market/orderbook/level2_20" -responseDataType .OrderBook
|
|
|
|
|
type GetOrderBookLevel2Depth20Request struct {
|
|
|
|
|
client requestgen.APIClient
|
|
|
|
|
symbol string `param:"symbol,query"`
|
|
|
|
|
}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
//go:generate GetRequest -type GetOrderBookLevel2Depth100Request -url "/api/v1/market/orderbook/level2_100" -responseDataType .OrderBook
|
|
|
|
|
type GetOrderBookLevel2Depth100Request struct {
|
|
|
|
|
client requestgen.APIClient
|
|
|
|
|
symbol string `param:"symbol,query"`
|
|
|
|
|
}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
//go:generate GetRequest -type GetOrderBookLevel2DepthAllRequest -url "/api/v3/market/orderbook/level2" -responseDataType .OrderBook
|
|
|
|
|
type GetOrderBookLevel2DepthAllRequest struct {
|
|
|
|
|
client requestgen.AuthenticatedAPIClient
|
|
|
|
|
symbol string `param:"symbol,query"`
|
|
|
|
|
}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
type OrderBookRequest interface {
|
|
|
|
|
Do(ctx context.Context) (*OrderBook, error)
|
|
|
|
|
}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
func (s *MarketDataService) NewGetOrderBookRequest(symbol string, depth int) OrderBookRequest {
|
|
|
|
|
switch depth {
|
|
|
|
|
case 20:
|
|
|
|
|
return &GetOrderBookLevel2Depth20Request{client: s.client, symbol: symbol}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
case 100:
|
|
|
|
|
return &GetOrderBookLevel2Depth100Request{client: s.client, symbol: symbol}
|
2021-12-10 17:41:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 17:15:19 +00:00
|
|
|
|
return &GetOrderBookLevel2DepthAllRequest{client: s.client, symbol: symbol}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *MarketDataService) GetOrderBook(symbol string, depth int) (*OrderBook, error) {
|
|
|
|
|
req := s.NewGetOrderBookRequest(symbol, depth)
|
|
|
|
|
return req.Do(context.Background())
|
2021-12-10 17:41:23 +00:00
|
|
|
|
}
|
2021-12-25 18:23:06 +00:00
|
|
|
|
|
|
|
|
|
//go:generate requestgen -type GetKLinesRequest
|
|
|
|
|
type GetKLinesRequest struct {
|
|
|
|
|
client *RestClient
|
|
|
|
|
|
|
|
|
|
symbol string `param:"symbol"`
|
|
|
|
|
|
2021-12-25 18:39:44 +00:00
|
|
|
|
interval string `param:"type" validValues:"1min,3min,5min,15min,30min,1hour,2hour,4hour,6hour,8hour,12hour,1day,1week"`
|
2021-12-25 18:23:06 +00:00
|
|
|
|
|
|
|
|
|
startAt *time.Time `param:"startAt,seconds"`
|
|
|
|
|
|
|
|
|
|
endAt *time.Time `param:"endAt,seconds"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type KLine struct {
|
|
|
|
|
Symbol string
|
|
|
|
|
Interval string
|
|
|
|
|
StartTime time.Time
|
|
|
|
|
Open fixedpoint.Value
|
|
|
|
|
High fixedpoint.Value
|
|
|
|
|
Low fixedpoint.Value
|
|
|
|
|
Close fixedpoint.Value
|
|
|
|
|
Volume, QuoteVolume fixedpoint.Value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *GetKLinesRequest) Do(ctx context.Context) ([]KLine, error) {
|
|
|
|
|
params, err := r.GetParametersQuery()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-29 14:06:21 +00:00
|
|
|
|
req, err := r.client.NewRequest(ctx, "GET", "/api/v1/market/candles", params, nil)
|
2021-12-25 18:23:06 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response, err := r.client.SendRequest(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var apiResponse struct {
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
Message string `json:"msg"`
|
|
|
|
|
Data json.RawMessage `json:"data"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if apiResponse.Data == nil {
|
|
|
|
|
return nil, errors.New("api error: [" + apiResponse.Code + "] " + apiResponse.Message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parseKLines(apiResponse.Data, r.symbol, r.interval)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseKLines(b []byte, symbol, interval string) (klines []KLine, err error) {
|
|
|
|
|
s, err := fastjson.ParseBytes(b)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, v := range s.GetArray() {
|
|
|
|
|
arr := v.GetArray()
|
|
|
|
|
ts, err := strconv.ParseInt(string(arr[0].GetStringBytes()), 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o, err := fixedpoint.NewFromString(string(arr[1].GetStringBytes()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c, err := fixedpoint.NewFromString(string(arr[2].GetStringBytes()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h, err := fixedpoint.NewFromString(string(arr[3].GetStringBytes()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
l, err := fixedpoint.NewFromString(string(arr[4].GetStringBytes()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vv, err := fixedpoint.NewFromString(string(arr[5].GetStringBytes()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qv, err := fixedpoint.NewFromString(string(arr[6].GetStringBytes()))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
klines = append(klines, KLine{
|
|
|
|
|
Symbol: symbol,
|
|
|
|
|
Interval: interval,
|
|
|
|
|
StartTime: time.Unix(ts, 0),
|
|
|
|
|
Open: o,
|
|
|
|
|
High: h,
|
|
|
|
|
Low: l,
|
|
|
|
|
Close: c,
|
|
|
|
|
Volume: vv,
|
|
|
|
|
QuoteVolume: qv,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return klines, err
|
|
|
|
|
}
|