place order post example

This commit is contained in:
wuhewuhe 2023-11-05 17:33:56 +01:00
parent 793440ae1c
commit 8695a773c9
7 changed files with 457 additions and 58 deletions

View File

@ -5,23 +5,33 @@ import (
"context" "context"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
handlers "github.com/wuhewuhe/bybit.go.api/handlers" "github.com/wuhewuhe/bybit.go.api/handlers"
"io" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv"
"time" "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 // TimeInForceType define time in force type of order
type TimeInForceType string type TimeInForceType string
// Client define API client // Client define API client
type Client struct { type Client struct {
APIKey string APIKey string
SecretKey string APISecret string
BaseURL string BaseURL string
HTTPClient *http.Client HTTPClient *http.Client
Debug bool Debug bool
@ -39,29 +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 { func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) { return func(c *Client) {
c.BaseURL = baseURL 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"`
Result interface{} `json:"result"`
RetExtInfo struct{} `json:"retExtInfo"`
Time int64 `json:"time"`
}
func PrettyPrint(i interface{}) string { func PrettyPrint(i interface{}) string {
s, _ := json.MarshalIndent(i, "", "\t") s, _ := json.MarshalIndent(i, "", "\t")
return string(s) return string(s)
@ -74,11 +68,11 @@ func (c *Client) debug(format string, v ...interface{}) {
} }
// NewBybitHttpClient NewClient Create client function for initialising new Bybit client // 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{ c := &Client{
APIKey: apiKey, APIKey: apiKey,
SecretKey: secretKey, APISecret: APISecret,
BaseURL: "https://api.bybit.com", BaseURL: MAINNET,
HTTPClient: http.DefaultClient, HTTPClient: http.DefaultClient,
Logger: log.New(os.Stderr, Name, log.LstdFlags), Logger: log.New(os.Stderr, Name, log.LstdFlags),
} }
@ -104,49 +98,47 @@ func (c *Client) parseRequest(r *request, opts ...RequestOption) (err error) {
fullURL := fmt.Sprintf("%s%s", c.BaseURL, r.endpoint) fullURL := fmt.Sprintf("%s%s", c.BaseURL, r.endpoint)
queryString := r.query.Encode() queryString := r.query.Encode()
body := &bytes.Buffer{}
bodyString := r.form.Encode()
header := http.Header{} header := http.Header{}
body := &bytes.Buffer{}
if r.params != nil {
body = bytes.NewBuffer(r.params)
}
if r.header != nil { if r.header != nil {
header = r.header.Clone() header = r.header.Clone()
} }
header.Set("User-Agent", fmt.Sprintf("%s/%s", Name, Version)) 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 { if r.secType == secTypeSigned {
now := time.Now()
unixNano := now.UnixNano()
timeStamp := unixNano / 1000000
header.Set(signTypeKey, "2") header.Set(signTypeKey, "2")
header.Set(apiRequestKey, c.APIKey) header.Set(apiRequestKey, c.APIKey)
header.Set(timestampKey, fmt.Sprintf("%d", currentTimestamp())) header.Set(timestampKey, strconv.FormatInt(timeStamp, 10))
if r.recvWindow == "" { if r.recvWindow == "" {
header.Set(recvWindowKey, "5000") r.recvWindow = "5000"
} else { }
header.Set(recvWindowKey, r.recvWindow) header.Set(recvWindowKey, r.recvWindow)
}
var signatureBase string var signatureBase []byte
if r.method == "GET" { if r.method == "POST" {
signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, queryString) header.Set("Content-Type", "application/json")
} else if r.method == "POST" { signatureBase = []byte(strconv.FormatInt(timeStamp, 10) + c.APIKey + r.recvWindow + string(r.params[:]))
signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, bodyString) } else {
signatureBase = []byte(strconv.FormatInt(timeStamp, 10) + c.APIKey + r.recvWindow + queryString)
} }
hmac256 := hmac.New(sha256.New, []byte(c.APISecret))
mac := hmac.New(sha256.New, []byte(c.SecretKey)) hmac256.Write(signatureBase)
_, err = mac.Write([]byte(signatureBase)) signature := hex.EncodeToString(hmac256.Sum(nil))
if err != nil { header.Set(signatureKey, signature)
return err
}
signatureValue := fmt.Sprintf("%x", mac.Sum(nil))
header.Set(signatureKey, signatureValue)
} }
if queryString != "" { if queryString != "" {
fullURL = fmt.Sprintf("%s?%s", fullURL, 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.fullURL = fullURL
r.header = header
r.body = body r.body = body
r.header = header
return nil return nil
} }
@ -200,3 +192,26 @@ func (c *Client) callAPI(ctx context.Context, r *request, opts ...RequestOption)
func (c *Client) NewServerTimeService() *ServerTime { func (c *Client) NewServerTimeService() *ServerTime {
return &ServerTime{c: c} 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,
}
}
// 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,
}
}

View File

@ -3,6 +3,10 @@ package bybit_connector
const ( const (
Name = "bybit.api.go" Name = "bybit.api.go"
Version = "1.0.0" 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 // WebSocket public channel - Mainnet
SPOT_MAINNET = "wss://stream.bybit.com/v5/public/spot" SPOT_MAINNET = "wss://stream.bybit.com/v5/public/spot"
LINEAR_MAINNET = "wss://stream.bybit.com/v5/public/linear" LINEAR_MAINNET = "wss://stream.bybit.com/v5/public/linear"
@ -29,5 +33,5 @@ const (
signatureKey = "X-BAPI-SIGN" signatureKey = "X-BAPI-SIGN"
apiRequestKey = "X-BAPI-API-KEY" apiRequestKey = "X-BAPI-API-KEY"
recvWindowKey = "X-BAPI-RECV-WINDOW" recvWindowKey = "X-BAPI-RECV-WINDOW"
signTypeKey = "2" signTypeKey = "X-BAPI-SIGN-TYPE"
) )

View File

@ -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))
}

View File

@ -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))
}

View File

@ -34,3 +34,80 @@ func (s *ServerTime) Do(ctx context.Context, opts ...RequestOption) (res *Server
} }
return res, nil return res, nil
} }
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"`
}
// 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
}
// Do Send the request
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
}

View File

@ -1,8 +1,10 @@
package bybit_connector package bybit_connector
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/url" "net/url"
) )
@ -21,12 +23,12 @@ type request struct {
method string method string
endpoint string endpoint string
query url.Values query url.Values
form url.Values
recvWindow string recvWindow string
secType secType secType secType
header http.Header header http.Header
body io.Reader params []byte
fullURL string fullURL string
body io.Reader
} }
// addParam add param with key/value to query string // addParam add param with key/value to query string
@ -47,11 +49,20 @@ func (r *request) setParam(key string, value interface{}) *request {
return r 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 { func (r *request) setParams(m params) *request {
if r.method == http.MethodGet {
for k, v := range m { for k, v := range m {
r.setParam(k, v) 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 return r
} }
@ -59,13 +70,10 @@ func (r *request) validate() (err error) {
if r.query == nil { if r.query == nil {
r.query = url.Values{} r.query = url.Values{}
} }
if r.form == nil {
r.form = url.Values{}
}
return nil 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 { func WithRecvWindow(recvWindow string) RequestOption {
return func(r *request) { return func(r *request) {
r.recvWindow = recvWindow r.recvWindow = recvWindow

250
trade.go
View File

@ -1 +1,251 @@
package bybit_connector package bybit_connector
import (
"context"
"encoding/json"
"net/http"
)
type OrderResult struct {
OrderId string `json:"orderId"`
OrderLinkId string `json:"orderLinkId"`
}
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 (o *Order) TimeInForce(tif string) *Order {
o.timeInForce = &tif
return o
}
func (s *Order) IsLeverage(isLeverage int) *Order {
s.isLeverage = &isLeverage
return s
}
func (s *Order) TriggerPrice(triggerPrice string) *Order {
s.triggerPrice = &triggerPrice
return s
}
func (s *Order) OrderLinkId(orderLinkId string) *Order {
s.orderLinkId = &orderLinkId
return s
}
func (o *Order) Price(price string) *Order {
o.price = &price
return o
}
func (o *Order) TriggerDirection(direction int) *Order {
o.triggerDirection = &direction
return o
}
func (o *Order) OrderFilter(filter string) *Order {
o.orderFilter = &filter
return o
}
func (o *Order) TriggerBy(triggerBy string) *Order {
o.triggerBy = &triggerBy
return o
}
func (o *Order) OrderIv(iv string) *Order {
o.orderIv = &iv
return o
}
func (o *Order) PositionIdx(idx int) *Order {
o.positionIdx = &idx
return o
}
func (o *Order) TakeProfit(profit string) *Order {
o.takeProfit = &profit
return o
}
func (o *Order) StopLoss(loss string) *Order {
o.stopLoss = &loss
return o
}
func (o *Order) TpTriggerBy(triggerBy string) *Order {
o.tpTriggerBy = &triggerBy
return o
}
func (o *Order) SlTriggerBy(triggerBy string) *Order {
o.slTriggerBy = &triggerBy
return o
}
func (o *Order) ReduceOnly(reduce bool) *Order {
o.reduceOnly = &reduce
return o
}
func (o *Order) CloseOnTrigger(close bool) *Order {
o.closeOnTrigger = &close
return o
}
func (o *Order) SmpType(smp string) *Order {
o.smpType = &smp
return o
}
func (o *Order) Mmp(mmp bool) *Order {
o.mmp = &mmp
return o
}
func (o *Order) TpslMode(mode string) *Order {
o.tpslMode = &mode
return o
}
func (o *Order) TpLimitPrice(price string) *Order {
o.tpLimitPrice = &price
return o
}
func (o *Order) SlLimitPrice(price string) *Order {
o.slLimitPrice = &price
return o
}
func (o *Order) TpOrderType(orderType string) *Order {
o.tpOrderType = &orderType
return o
}
func (o *Order) SlOrderType(orderType string) *Order {
o.slOrderType = &orderType
return o
}
func (s *Order) Do(ctx context.Context, opts ...RequestOption) (*ServerResponse, error) {
r := &request{
method: http.MethodPost,
endpoint: "/v5/order/create",
secType: secTypeSigned,
}
m := params{
"category": s.category,
"symbol": s.symbol,
"side": s.side,
"orderType": s.orderType,
"qty": s.qty,
}
if s.price != nil {
m["price"] = *s.price
}
if s.triggerDirection != nil {
m["triggerDirection"] = *s.triggerDirection
}
if s.orderFilter != nil {
m["orderFilter"] = *s.orderFilter
}
if s.triggerPrice != nil {
m["triggerPrice"] = *s.triggerPrice
}
if s.triggerBy != nil {
m["triggerBy"] = *s.triggerBy
}
if s.orderIv != nil {
m["orderIv"] = *s.orderIv
}
if s.timeInForce != nil {
m["timeInForce"] = *s.timeInForce
}
if s.positionIdx != nil {
m["positionIdx"] = *s.positionIdx
}
if s.orderLinkId != nil {
m["orderLinkId"] = *s.orderLinkId
}
if s.takeProfit != nil {
m["takeProfit"] = *s.takeProfit
}
if s.stopLoss != nil {
m["stopLoss"] = *s.stopLoss
}
if s.tpTriggerBy != nil {
m["tpTriggerBy"] = *s.tpTriggerBy
}
if s.slTriggerBy != nil {
m["slTriggerBy"] = *s.slTriggerBy
}
if s.reduceOnly != nil {
m["reduceOnly"] = *s.reduceOnly
}
if s.closeOnTrigger != nil {
m["closeOnTrigger"] = *s.closeOnTrigger
}
if s.smpType != nil {
m["smpType"] = *s.smpType
}
if s.mmp != nil {
m["mmp"] = *s.mmp
}
if s.tpslMode != nil {
m["tpslMode"] = *s.tpslMode
}
if s.tpLimitPrice != nil {
m["tpLimitPrice"] = *s.tpLimitPrice
}
if s.slLimitPrice != nil {
m["slLimitPrice"] = *s.slLimitPrice
}
if s.tpOrderType != nil {
m["tpOrderType"] = *s.tpOrderType
}
if s.slOrderType != nil {
m["slOrderType"] = *s.slOrderType
}
r.setParams(m)
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
}