diff --git a/bybit_api_client.go b/bybit_api_client.go index d9b807d..76581b3 100644 --- a/bybit_api_client.go +++ b/bybit_api_client.go @@ -5,23 +5,33 @@ import ( "context" "crypto/hmac" "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" - handlers "github.com/wuhewuhe/bybit.go.api/handlers" + "github.com/wuhewuhe/bybit.go.api/handlers" "io" "log" "net/http" "os" + "strconv" "time" ) +type ServerResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result interface{} `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + // TimeInForceType define time in force type of order type TimeInForceType string // Client define API client type Client struct { APIKey string - SecretKey string + APISecret string BaseURL string HTTPClient *http.Client Debug bool @@ -39,21 +49,13 @@ func WithDebug(debug bool) ClientOption { } } +// WithBaseURL is a client option to set the base URL of the Bybit HTTP client. func WithBaseURL(baseURL string) ClientOption { return func(c *Client) { c.BaseURL = baseURL } } -func currentTimestamp() int64 { - return FormatTimestamp(time.Now()) -} - -// FormatTimestamp formats a time into Unix timestamp in milliseconds, as requested by Bybit. -func FormatTimestamp(t time.Time) int64 { - return t.UnixNano() / int64(time.Millisecond) -} - type ServerResponse struct { RetCode int `json:"retCode"` RetMsg string `json:"retMsg"` @@ -73,12 +75,19 @@ func (c *Client) debug(format string, v ...interface{}) { } } +func GetCurrentTime() int64 { + now := time.Now() + unixNano := now.UnixNano() + timeStamp := unixNano / int64(time.Millisecond) + return timeStamp +} + // NewBybitHttpClient NewClient Create client function for initialising new Bybit client -func NewBybitHttpClient(apiKey string, secretKey string, options ...ClientOption) *Client { +func NewBybitHttpClient(apiKey string, APISecret string, options ...ClientOption) *Client { c := &Client{ APIKey: apiKey, - SecretKey: secretKey, - BaseURL: "https://api.bybit.com", + APISecret: APISecret, + BaseURL: MAINNET, HTTPClient: http.DefaultClient, Logger: log.New(os.Stderr, Name, log.LstdFlags), } @@ -104,49 +113,45 @@ func (c *Client) parseRequest(r *request, opts ...RequestOption) (err error) { fullURL := fmt.Sprintf("%s%s", c.BaseURL, r.endpoint) queryString := r.query.Encode() - body := &bytes.Buffer{} - bodyString := r.form.Encode() header := http.Header{} + body := &bytes.Buffer{} + if r.params != nil { + body = bytes.NewBuffer(r.params) + } if r.header != nil { header = r.header.Clone() } header.Set("User-Agent", fmt.Sprintf("%s/%s", Name, Version)) - if bodyString != "" { - header.Set("Content-Type", "application/json") - body = bytes.NewBufferString(bodyString) - } + if r.secType == secTypeSigned { + timeStamp := GetCurrentTime() header.Set(signTypeKey, "2") header.Set(apiRequestKey, c.APIKey) - header.Set(timestampKey, fmt.Sprintf("%d", currentTimestamp())) + header.Set(timestampKey, strconv.FormatInt(timeStamp, 10)) if r.recvWindow == "" { - header.Set(recvWindowKey, "5000") + r.recvWindow = "5000" + } + header.Set(recvWindowKey, r.recvWindow) + + var signatureBase []byte + if r.method == "POST" { + header.Set("Content-Type", "application/json") + signatureBase = []byte(strconv.FormatInt(timeStamp, 10) + c.APIKey + r.recvWindow + string(r.params[:])) } else { - header.Set(recvWindowKey, r.recvWindow) + signatureBase = []byte(strconv.FormatInt(timeStamp, 10) + c.APIKey + r.recvWindow + queryString) } - - var signatureBase string - if r.method == "GET" { - signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, queryString) - } else if r.method == "POST" { - signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, bodyString) - } - - mac := hmac.New(sha256.New, []byte(c.SecretKey)) - _, err = mac.Write([]byte(signatureBase)) - if err != nil { - return err - } - signatureValue := fmt.Sprintf("%x", mac.Sum(nil)) - header.Set(signatureKey, signatureValue) + hmac256 := hmac.New(sha256.New, []byte(c.APISecret)) + hmac256.Write(signatureBase) + signature := hex.EncodeToString(hmac256.Sum(nil)) + header.Set(signatureKey, signature) } if queryString != "" { fullURL = fmt.Sprintf("%s?%s", fullURL, queryString) } - c.debug("full url: %s, body: %s", fullURL, bodyString) + c.debug("full url: %s, body: %s", fullURL, body) r.fullURL = fullURL - r.header = header r.body = body + r.header = header return nil } @@ -196,7 +201,53 @@ func (c *Client) callAPI(ctx context.Context, r *request, opts ...RequestOption) return data, nil } -// NewServerTimeService Market Endpoints -func (c *Client) NewServerTimeService() *ServerTime { - return &ServerTime{c: c} +// NewMarketKlineService Market Endpoints +func (c *Client) NewMarketKlineService(klineType, category, symbol, interval string) *Klines { + return &Klines{ + c: c, + category: category, + symbol: symbol, + interval: interval, + klineType: klineType, + } +} + +func (c *Client) NewMarketKLinesService(klineType string, params map[string]interface{}) *MarketClient { + return &MarketClient{ + c: c, + klineType: klineType, + params: params, + } +} + +func (c *Client) NewMarketInfoServiceNoParams() *MarketClient { + return &MarketClient{ + c: c, + } +} + +func (c *Client) NewMarketInfoService(params map[string]interface{}) *MarketClient { + return &MarketClient{ + c: c, + params: params, + } +} + +// NewPlaceOrderService Trade Endpoints +func (c *Client) NewPlaceOrderService(category, symbol, side, orderType, qty string) *Order { + return &Order{ + c: c, + category: category, + symbol: symbol, + side: side, + orderType: orderType, + qty: qty, + } +} + +func (c *Client) NewPlaceTradeService(params map[string]interface{}) *Trade { + return &Trade{ + c: c, + params: params, + } } diff --git a/consts.go b/consts.go index e8f1889..02028cc 100644 --- a/consts.go +++ b/consts.go @@ -3,6 +3,10 @@ package bybit_connector const ( Name = "bybit.api.go" Version = "1.0.0" + // Https + MAINNET = "https://api.bybit.com" + MAINNET_BACKT = "https://api.bytick.com" + TESTNET = "https://api-testnet.bybit.com" // WebSocket public channel - Mainnet SPOT_MAINNET = "wss://stream.bybit.com/v5/public/spot" LINEAR_MAINNET = "wss://stream.bybit.com/v5/public/linear" @@ -29,5 +33,5 @@ const ( signatureKey = "X-BAPI-SIGN" apiRequestKey = "X-BAPI-API-KEY" recvWindowKey = "X-BAPI-RECV-WINDOW" - signTypeKey = "2" + signTypeKey = "X-BAPI-SIGN-TYPE" ) diff --git a/examples/Trade/place_order.go b/examples/Trade/place_order.go new file mode 100644 index 0000000..e640850 --- /dev/null +++ b/examples/Trade/place_order.go @@ -0,0 +1,21 @@ +package main + +import ( + "context" + "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" +) + +func main() { + PlaceOrder() +} + +func PlaceOrder() { + client := bybit.NewBybitHttpClient("8wYkmpLsMg10eNQyPm", "Ouxc34myDnXvei54XsBZgoQzfGxO4bkr2Zsj", bybit.WithBaseURL(bybit.TESTNET)) + orderResult, err := client.NewPlaceOrderService("linear", "XRPUSDT", "Buy", "Market", "10").Do(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(bybit.PrettyPrint(orderResult)) +} diff --git a/examples/Trade/place_trade.go b/examples/Trade/place_trade.go new file mode 100644 index 0000000..4da2bb4 --- /dev/null +++ b/examples/Trade/place_trade.go @@ -0,0 +1,22 @@ +package main + +import ( + "context" + "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" +) + +func main() { + PlaceTrade() +} + +func PlaceTrade() { + client := bybit.NewBybitHttpClient("8wYkmpLsMg10eNQyPm", "Ouxc34myDnXvei54XsBZgoQzfGxO4bkr2Zsj", bybit.WithBaseURL(bybit.TESTNET)) + params := map[string]interface{}{"category": "linear", "symbol": "BTCUSDT", "side": "Buy", "positionIdx": 0, "orderType": "Limit", "qty": "0.001", "price": "10000", "timeInForce": "GTC"} + orderResult, err := client.NewPlaceTradeService(params).Do(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(bybit.PrettyPrint(orderResult)) +} diff --git a/examples/market/Instrument_info.go b/examples/market/Instrument_info.go new file mode 100644 index 0000000..a65f0d8 --- /dev/null +++ b/examples/market/Instrument_info.go @@ -0,0 +1,25 @@ +package main + +import ( + "context" + "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" +) + +func main() { + InstrumentInfo() +} + +func InstrumentInfo() { + + client := bybit.NewBybitHttpClient("", "") + + // NewServerTimeService + params := map[string]interface{}{"category": "linear", "symbol": "BTCUSDT", "status": "Trading"} + marketKline, err := client.NewMarketInfoService(params).GetInstrumentInfo(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(bybit.PrettyPrint(marketKline)) +} diff --git a/examples/market/index_kline.go b/examples/market/index_kline.go new file mode 100644 index 0000000..725475e --- /dev/null +++ b/examples/market/index_kline.go @@ -0,0 +1,25 @@ +package main + +import ( + "context" + "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" +) + +func main() { + IndexKline() +} + +func IndexKline() { + + client := bybit.NewBybitHttpClient("", "") + + // NewServerTimeService + params := map[string]interface{}{"category": "linear", "symbol": "BTCUSDT", "interval": "1", "Limit": 2} + marketKline, err := client.NewMarketKLinesService("index-price-kline", params).GetMarketKline(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(bybit.PrettyPrint(marketKline)) +} diff --git a/examples/market/market_kline.go b/examples/market/market_kline.go new file mode 100644 index 0000000..579b9fe --- /dev/null +++ b/examples/market/market_kline.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" +) + +func main() { + MarketKline() +} + +func MarketKline() { + + client := bybit.NewBybitHttpClient("", "") + + // NewServerTimeService + marketKline, err := client.NewMarketKlineService("premium-index-price-kline", "linear", "BTCUSDT", "1").Limit(10).Do(context.Background()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(bybit.PrettyPrint(marketKline)) +} diff --git a/examples/market/server_time.go b/examples/market/server_time.go index 054cb5f..22ccabe 100644 --- a/examples/market/server_time.go +++ b/examples/market/server_time.go @@ -15,7 +15,7 @@ func ServerTime() { client := bybit.NewBybitHttpClient("", "") // NewServerTimeService - serverTime, err := client.NewServerTimeService().Do(context.Background()) + serverTime, err := client.NewMarketInfoServiceNoParams().GetServerTime(context.Background()) if err != nil { fmt.Println(err) return diff --git a/examples/websocket/private_ws.go b/examples/websocket/private_ws.go index b0bf487..fe00528 100644 --- a/examples/websocket/private_ws.go +++ b/examples/websocket/private_ws.go @@ -1,8 +1,8 @@ package main import ( - bybit "bybit.go.api" "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" ) func main() { diff --git a/examples/websocket/public_ws.go b/examples/websocket/public_ws.go index 30e23ef..4524bd5 100644 --- a/examples/websocket/public_ws.go +++ b/examples/websocket/public_ws.go @@ -1,8 +1,8 @@ package main import ( - bybit "bybit.go.api" "fmt" + bybit "github.com/wuhewuhe/bybit.go.api" ) func main() { diff --git a/handlers/params_verification.go b/handlers/params_verification.go new file mode 100644 index 0000000..6ced07b --- /dev/null +++ b/handlers/params_verification.go @@ -0,0 +1,23 @@ +package handlers + +import ( + "fmt" +) + +func ValidateParams(params map[string]interface{}) error { + seenKeys := make(map[string]bool) + + for key, value := range params { + if key == "" { + return fmt.Errorf("empty key found in parameters") + } + if seenKeys[key] { + return fmt.Errorf("duplicate key found in parameters: %s", key) + } + if value == nil { + return fmt.Errorf("parameter for key '%s' is nil", key) + } + seenKeys[key] = true + } + return nil +} diff --git a/market.go b/market.go index aba27bd..55f7996 100644 --- a/market.go +++ b/market.go @@ -3,21 +3,17 @@ package bybit_connector import ( "context" "encoding/json" + "github.com/wuhewuhe/bybit.go.api/handlers" "net/http" ) -type ServerTimeResult struct { - TimeSecond string `json:"timeSecond"` - TimeNano string `json:"timeNano"` +type MarketClient struct { + c *Client + klineType string + params map[string]interface{} } -// ServerTime Binance Check Server Time endpoint (GET /v5/market/time) -type ServerTime struct { - c *Client -} - -// Do Send the request -func (s *ServerTime) Do(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { +func (s *MarketClient) GetServerTime(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { r := &request{ method: http.MethodGet, endpoint: "/v5/market/time", @@ -34,3 +30,267 @@ func (s *ServerTime) Do(ctx context.Context, opts ...RequestOption) (res *Server } return res, nil } + +func (s *MarketClient) GetMarketKline(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/" + s.klineType, + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetInstrumentInfo(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/instruments-info", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetOrderBookInfo(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/orderbook", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetMarketTickers(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/tickers", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetFundingRates(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/tickers", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetPublicRecentTrades(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/recent-trade", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetOpenInterests(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/open-interest", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetHistoricalVolatility(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/historical-volatility", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetInsuranceInfo(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/insurance", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetRiskLimit(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/risk-limit", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetDeliveryPrice(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/delivery-price", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *MarketClient) GetMarketLSRatio(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/account-ratio", + secType: secTypeNone, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/market_klines.go b/market_klines.go new file mode 100644 index 0000000..c0929d8 --- /dev/null +++ b/market_klines.go @@ -0,0 +1,67 @@ +package bybit_connector + +import ( + "context" + "encoding/json" + "net/http" +) + +// Klines Market Kline (GET /v5/market/kline) +type Klines struct { + c *Client + klineType string + category string + symbol string + interval string + limit *int + start *uint64 + end *uint64 +} + +// Limit set limit +func (s *Klines) Limit(limit int) *Klines { + s.limit = &limit + return s +} + +// Start set startTime +func (s *Klines) Start(startTime uint64) *Klines { + s.start = &startTime + return s +} + +// End set endTime +func (s *Klines) End(endTime uint64) *Klines { + s.end = &endTime + return s +} + +func (s *Klines) Do(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/" + s.klineType, + secType: secTypeNone, + } + r.setParam("category", s.category) + r.setParam("symbol", s.symbol) + r.setParam("interval", s.interval) + if s.limit != nil { + r.setParam("limit", *s.limit) + } + if s.start != nil { + r.setParam("start", *s.start) + } + if s.end != nil { + r.setParam("end", *s.end) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/models/marketResponse.go b/models/marketResponse.go new file mode 100644 index 0000000..65e7a0c --- /dev/null +++ b/models/marketResponse.go @@ -0,0 +1,197 @@ +package models + +type ServerTimeResult struct { + TimeSecond string `json:"timeSecond"` + TimeNano string `json:"timeNano"` +} + +type MarketKlineCandle struct { + StartTime string `json:"startTime"` + OpenPrice string `json:"openPrice"` + HighPrice string `json:"highPrice"` + LowPrice string `json:"lowPrice"` + ClosePrice string `json:"closePrice"` + Volume string `json:"volume"` + Turnover string `json:"turnover"` +} + +type MarketKlineResponse struct { + Category string `json:"category"` + Symbol string `json:"symbol"` + List []MarketKlineCandle `json:"list"` +} + +type InstrumentInfo struct { + Category string `json:"category"` + NextPageCursor string `json:"nextPageCursor"` + List []Instrument `json:"list"` +} + +type Instrument struct { + Symbol string `json:"symbol"` + ContractType string `json:"contractType"` + Status string `json:"status"` + BaseCoin string `json:"baseCoin"` + QuoteCoin string `json:"quoteCoin"` + LaunchTime string `json:"launchTime"` + DeliveryTime string `json:"deliveryTime"` + DeliveryFeeRate string `json:"deliveryFeeRate"` + PriceScale string `json:"priceScale"` + LeverageFilter LeverageFilter `json:"leverageFilter"` + PriceFilter PriceFilter `json:"priceFilter"` + LotSizeFilter LotSizeFilter `json:"lotSizeFilter"` + UnifiedMarginTrade bool `json:"unifiedMarginTrade"` + FundingInterval int `json:"fundingInterval"` + SettleCoin string `json:"settleCoin"` + CopyTrading string `json:"copyTrading"` +} + +type LeverageFilter struct { + MinLeverage string `json:"minLeverage"` + MaxLeverage string `json:"maxLeverage"` + LeverageStep string `json:"leverageStep"` +} + +type PriceFilter struct { + MinPrice string `json:"minPrice"` + MaxPrice string `json:"maxPrice"` + TickSize string `json:"tickSize"` +} + +type LotSizeFilter struct { + MaxOrderQty string `json:"maxOrderQty"` + MinOrderQty string `json:"minOrderQty"` + QtyStep string `json:"qtyStep"` + PostOnlyMaxOrderQty string `json:"postOnlyMaxOrderQty"` +} + +type OrderBookEntry struct { + Price string `json:"0"` + Size string `json:"1"` +} + +type OrderBookInfo struct { + Symbol string `json:"s"` + Bids []OrderBookEntry `json:"b"` + Asks []OrderBookEntry `json:"a"` + Timestamp int64 `json:"ts"` + UpdateID int64 `json:"u"` +} + +type TickerInfo struct { + Symbol string `json:"symbol"` + LastPrice string `json:"lastPrice"` + IndexPrice string `json:"indexPrice"` + MarkPrice string `json:"markPrice"` + PrevPrice24h string `json:"prevPrice24h"` + Price24hPcnt string `json:"price24hPcnt"` + HighPrice24h string `json:"highPrice24h"` + LowPrice24h string `json:"lowPrice24h"` + PrevPrice1h string `json:"prevPrice1h"` + OpenInterest string `json:"openInterest"` + OpenInterestValue string `json:"openInterestValue"` + Turnover24h string `json:"turnover24h"` + Volume24h string `json:"volume24h"` + FundingRate string `json:"fundingRate"` + NextFundingTime string `json:"nextFundingTime"` + PredictedDeliveryPrice string `json:"predictedDeliveryPrice"` + BasisRate string `json:"basisRate"` + Basis string `json:"basis"` + DeliveryFeeRate string `json:"deliveryFeeRate"` + DeliveryTime string `json:"deliveryTime"` + Ask1Size string `json:"ask1Size"` + Bid1Price string `json:"bid1Price"` + Ask1Price string `json:"ask1Price"` + Bid1Size string `json:"bid1Size"` +} + +type MarketTickers struct { + Category string `json:"category"` + List []TickerInfo `json:"list"` +} + +type FundingRateInfo struct { + Symbol string `json:"symbol"` + FundingRate string `json:"fundingRate"` + FundingRateTimestamp string `json:"fundingRateTimestamp"` +} + +type FundingRate struct { + Category string `json:"category"` + List []FundingRateInfo `json:"list"` +} + +type TradeInfo struct { + ExecId string `json:"execId"` + Symbol string `json:"symbol"` + Price string `json:"price"` + Size string `json:"size"` + Side string `json:"side"` + Time string `json:"time"` + IsBlockTrade bool `json:"isBlockTrade"` +} + +type PublicRecentTradeHistory struct { + Category string `json:"category"` + List []TradeInfo `json:"list"` +} + +type VolatilityData struct { + Period int `json:"period"` + Value string `json:"value"` + Time string `json:"time"` +} + +type HistoricalVolatilityInfo struct { + Category string `json:"category"` + List []VolatilityData `json:"list"` +} + +type InsuranceData struct { + Coin string `json:"coin"` + Balance string `json:"balance"` + Value string `json:"value"` +} + +type MarketInsuranceInfo struct { + UpdatedTime string `json:"updatedTime"` + List []InsuranceData `json:"list"` +} + +type RiskLimitData struct { + Id int `json:"id"` + Symbol string `json:"symbol"` + RiskLimitValue string `json:"riskLimitValue"` + MaintenanceMargin float64 `json:"maintenanceMargin"` + InitialMargin float64 `json:"initialMargin"` + IsLowestRisk int `json:"isLowestRisk"` + MaxLeverage string `json:"maxLeverage"` +} + +type MarketRiskLimitInfo struct { + Category string `json:"category"` + List []RiskLimitData `json:"list"` +} + +type DeliveryPriceData struct { + Symbol string `json:"symbol"` + DeliveryPrice string `json:"deliveryPrice"` + DeliveryTime string `json:"deliveryTime"` +} + +type DeliveryPriceInfo struct { + Category string `json:"category"` + List []DeliveryPriceData `json:"list"` + NextPageCursor string `json:"nextPageCursor"` +} + +type LongShortRatioData struct { + Symbol string `json:"symbol"` + BuyRatio string `json:"buyRatio"` + SellRatio string `json:"sellRatio"` + Timestamp string `json:"timestamp"` +} + +type MarketLongShortRatioInfo struct { + List []LongShortRatioData `json:"list"` +} diff --git a/request.go b/request.go index da54f9a..49ecd9e 100644 --- a/request.go +++ b/request.go @@ -1,8 +1,10 @@ package bybit_connector import ( + "encoding/json" "fmt" "io" + "log" "net/http" "net/url" ) @@ -21,12 +23,12 @@ type request struct { method string endpoint string query url.Values - form url.Values recvWindow string secType secType header http.Header - body io.Reader + params []byte fullURL string + body io.Reader } // addParam add param with key/value to query string @@ -47,11 +49,20 @@ func (r *request) setParam(key string, value interface{}) *request { return r } -// setParams set params with key/values to query string +// setParams set params with key/values to query string or body func (r *request) setParams(m params) *request { - for k, v := range m { - r.setParam(k, v) + if r.method == http.MethodGet { + for k, v := range m { + r.setParam(k, v) + } + } else if r.method == http.MethodPost { + jsonData, err := json.Marshal(m) + if err != nil { + log.Fatal(err) + } + r.params = jsonData } + return r } @@ -59,13 +70,10 @@ func (r *request) validate() (err error) { if r.query == nil { r.query = url.Values{} } - if r.form == nil { - r.form = url.Values{} - } return nil } -// Append `WithRecvWindow(insert_recvwindow)` to request to modify the default recvWindow value +// WithRecvWindow Append `WithRecvWindow(insert_recvWindow)` to request to modify the default recvWindow value func WithRecvWindow(recvWindow string) RequestOption { return func(r *request) { r.recvWindow = recvWindow diff --git a/trade.go b/trade.go index 06c9e41..0d89be2 100644 --- a/trade.go +++ b/trade.go @@ -1 +1,279 @@ package bybit_connector + +import ( + "context" + "encoding/json" + "github.com/wuhewuhe/bybit.go.api/handlers" + "net/http" +) + +type OrderResult struct { + OrderId string `json:"orderId"` + OrderLinkId string `json:"orderLinkId"` +} + +type Trade struct { + c *Client + params map[string]interface{} +} + +type Order struct { + c *Client + category string + symbol string + isLeverage *int + side string + orderType string + qty string + price *string + triggerDirection *int + orderFilter *string + triggerPrice *string + triggerBy *string + orderIv *string + timeInForce *string + positionIdx *int + orderLinkId *string + takeProfit *string + stopLoss *string + tpTriggerBy *string + slTriggerBy *string + reduceOnly *bool + closeOnTrigger *bool + smpType *string + mmp *bool + tpslMode *string + tpLimitPrice *string + slLimitPrice *string + tpOrderType *string + slOrderType *string +} + +func (order *Order) TimeInForce(tif string) *Order { + order.timeInForce = &tif + return order +} + +func (order *Order) IsLeverage(isLeverage int) *Order { + order.isLeverage = &isLeverage + return order +} + +func (order *Order) TriggerPrice(triggerPrice string) *Order { + order.triggerPrice = &triggerPrice + return order +} + +func (order *Order) OrderLinkId(orderLinkId string) *Order { + order.orderLinkId = &orderLinkId + return order +} + +func (order *Order) Price(price string) *Order { + order.price = &price + return order +} + +func (order *Order) TriggerDirection(direction int) *Order { + order.triggerDirection = &direction + return order +} + +func (order *Order) OrderFilter(filter string) *Order { + order.orderFilter = &filter + return order +} + +func (order *Order) TriggerBy(triggerBy string) *Order { + order.triggerBy = &triggerBy + return order +} + +func (order *Order) OrderIv(iv string) *Order { + order.orderIv = &iv + return order +} + +func (order *Order) PositionIdx(idx int) *Order { + order.positionIdx = &idx + return order +} + +func (order *Order) TakeProfit(profit string) *Order { + order.takeProfit = &profit + return order +} + +func (order *Order) StopLoss(loss string) *Order { + order.stopLoss = &loss + return order +} + +func (order *Order) TpTriggerBy(triggerBy string) *Order { + order.tpTriggerBy = &triggerBy + return order +} + +func (order *Order) SlTriggerBy(triggerBy string) *Order { + order.slTriggerBy = &triggerBy + return order +} + +func (order *Order) ReduceOnly(reduce bool) *Order { + order.reduceOnly = &reduce + return order +} + +func (order *Order) CloseOnTrigger(close bool) *Order { + order.closeOnTrigger = &close + return order +} + +func (order *Order) SmpType(smp string) *Order { + order.smpType = &smp + return order +} + +func (order *Order) Mmp(mmp bool) *Order { + order.mmp = &mmp + return order +} + +func (order *Order) TpslMode(mode string) *Order { + order.tpslMode = &mode + return order +} + +func (order *Order) TpLimitPrice(price string) *Order { + order.tpLimitPrice = &price + return order +} + +func (order *Order) SlLimitPrice(price string) *Order { + order.slLimitPrice = &price + return order +} + +func (order *Order) TpOrderType(orderType string) *Order { + order.tpOrderType = &orderType + return order +} + +func (order *Order) SlOrderType(orderType string) *Order { + order.slOrderType = &orderType + return order +} + +func (order *Order) Do(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + r := &request{ + method: http.MethodPost, + endpoint: "/v5/order/create", + secType: secTypeSigned, + } + m := params{ + "category": order.category, + "symbol": order.symbol, + "side": order.side, + "orderType": order.orderType, + "qty": order.qty, + } + if order.price != nil { + m["price"] = *order.price + } + if order.triggerDirection != nil { + m["triggerDirection"] = *order.triggerDirection + } + if order.orderFilter != nil { + m["orderFilter"] = *order.orderFilter + } + if order.triggerPrice != nil { + m["triggerPrice"] = *order.triggerPrice + } + if order.triggerBy != nil { + m["triggerBy"] = *order.triggerBy + } + if order.orderIv != nil { + m["orderIv"] = *order.orderIv + } + if order.timeInForce != nil { + m["timeInForce"] = *order.timeInForce + } + if order.positionIdx != nil { + m["positionIdx"] = *order.positionIdx + } + if order.orderLinkId != nil { + m["orderLinkId"] = *order.orderLinkId + } + if order.takeProfit != nil { + m["takeProfit"] = *order.takeProfit + } + if order.stopLoss != nil { + m["stopLoss"] = *order.stopLoss + } + if order.tpTriggerBy != nil { + m["tpTriggerBy"] = *order.tpTriggerBy + } + if order.slTriggerBy != nil { + m["slTriggerBy"] = *order.slTriggerBy + } + if order.reduceOnly != nil { + m["reduceOnly"] = *order.reduceOnly + } + if order.closeOnTrigger != nil { + m["closeOnTrigger"] = *order.closeOnTrigger + } + if order.smpType != nil { + m["smpType"] = *order.smpType + } + if order.mmp != nil { + m["mmp"] = *order.mmp + } + if order.tpslMode != nil { + m["tpslMode"] = *order.tpslMode + } + if order.tpLimitPrice != nil { + m["tpLimitPrice"] = *order.tpLimitPrice + } + if order.slLimitPrice != nil { + m["slLimitPrice"] = *order.slLimitPrice + } + if order.tpOrderType != nil { + m["tpOrderType"] = *order.tpOrderType + } + if order.slOrderType != nil { + m["slOrderType"] = *order.slOrderType + } + r.setParams(m) + data, err := order.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +func (s *Trade) Do(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { + if err = handlers.ValidateParams(s.params); err != nil { + return nil, err + } + r := &request{ + method: http.MethodPost, + endpoint: "/v5/order/create", + secType: secTypeSigned, + } + r.setParams(s.params) + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ServerResponse) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +}