2021-12-10 18:44:57 +00:00
|
|
|
package kucoinapi
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net/url"
|
2021-12-11 11:40:53 +00:00
|
|
|
"strconv"
|
|
|
|
"time"
|
2021-12-10 18:44:57 +00:00
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
2021-12-11 11:40:53 +00:00
|
|
|
"github.com/google/uuid"
|
2021-12-10 18:44:57 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type TradeService struct {
|
|
|
|
client *RestClient
|
|
|
|
}
|
|
|
|
|
|
|
|
type OrderResponse struct {
|
|
|
|
OrderID string `json:"orderId"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TradeService) NewPlaceOrderRequest() *PlaceOrderRequest {
|
|
|
|
return &PlaceOrderRequest{
|
|
|
|
client: c.client,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TradeService) NewBatchPlaceOrderRequest() *BatchPlaceOrderRequest {
|
|
|
|
return &BatchPlaceOrderRequest{
|
|
|
|
client: c.client,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TradeService) NewCancelOrderRequest() *CancelOrderRequest {
|
|
|
|
return &CancelOrderRequest{
|
|
|
|
client: c.client,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TradeService) NewCancelAllOrderRequest() *CancelAllOrderRequest {
|
|
|
|
return &CancelAllOrderRequest{
|
|
|
|
client: c.client,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:40:53 +00:00
|
|
|
type ListOrdersRequest struct {
|
|
|
|
client *RestClient
|
|
|
|
|
|
|
|
status *string
|
|
|
|
|
|
|
|
symbol *string
|
|
|
|
|
|
|
|
side *SideType
|
|
|
|
|
|
|
|
orderType *OrderType
|
|
|
|
|
|
|
|
tradeType *TradeType
|
|
|
|
|
|
|
|
startAt *time.Time
|
|
|
|
|
|
|
|
endAt *time.Time
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
2021-12-11 11:40:53 +00:00
|
|
|
func (r *ListOrdersRequest) Status(status string) {
|
|
|
|
r.status = &status
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
2021-12-11 11:40:53 +00:00
|
|
|
func (r *ListOrdersRequest) Symbol(symbol string) {
|
|
|
|
r.symbol = &symbol
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ListOrdersRequest) Side(side SideType) {
|
|
|
|
r.side = &side
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ListOrdersRequest) OrderType(orderType OrderType) {
|
|
|
|
r.orderType = &orderType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ListOrdersRequest) StartAt(startAt time.Time) {
|
|
|
|
r.startAt = &startAt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ListOrdersRequest) EndAt(endAt time.Time) {
|
|
|
|
r.endAt = &endAt
|
|
|
|
}
|
|
|
|
|
|
|
|
type Order struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Symbol string `json:"symbol"`
|
|
|
|
OperationType string `json:"opType"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Side string `json:"side"`
|
|
|
|
Price fixedpoint.Value `json:"price"`
|
|
|
|
Size fixedpoint.Value `json:"size"`
|
|
|
|
Funds fixedpoint.Value `json:"funds"`
|
|
|
|
DealFunds fixedpoint.Value `json:"dealFunds"`
|
|
|
|
DealSize fixedpoint.Value `json:"dealSize"`
|
|
|
|
Fee fixedpoint.Value `json:"fee"`
|
|
|
|
FeeCurrency string `json:"feeCurrency"`
|
|
|
|
StopType string `json:"stop"`
|
|
|
|
StopTriggerred bool `json:"stopTriggered"`
|
|
|
|
StopPrice fixedpoint.Value `json:"stopPrice"`
|
|
|
|
TimeInForce TimeInForceType `json:"timeInForce"`
|
|
|
|
PostOnly bool `json:"postOnly"`
|
|
|
|
Hidden bool `json:"hidden"`
|
|
|
|
Iceberg bool `json:"iceberg"`
|
|
|
|
Channel string `json:"channel"`
|
|
|
|
ClientOrderID string `json:"clientOid"`
|
|
|
|
Remark string `json:"remark"`
|
|
|
|
IsActive bool `json:"isActive"`
|
|
|
|
CancelExist bool `json:"cancelExist"`
|
|
|
|
CreatedAt types.MillisecondTimestamp `json:"createdAt"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type OrderListPage struct {
|
|
|
|
CurrentPage int `json:"currentPage"`
|
|
|
|
PageSize int `json:"pageSize"`
|
|
|
|
TotalNumber int `json:"totalNum"`
|
|
|
|
TotalPage int `json:"totalPage"`
|
|
|
|
Items []Order `json:"items"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *ListOrdersRequest) Do(ctx context.Context) (*OrderListPage, error) {
|
|
|
|
var params = url.Values{}
|
|
|
|
|
|
|
|
if r.status != nil {
|
|
|
|
params["status"] = []string{*r.status}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.symbol != nil {
|
|
|
|
params["symbol"] = []string{*r.symbol}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.side != nil {
|
|
|
|
params["side"] = []string{string(*r.side)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.orderType != nil {
|
|
|
|
params["type"] = []string{string(*r.orderType)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.tradeType != nil {
|
|
|
|
params["tradeType"] = []string{string(*r.tradeType)}
|
|
|
|
} else {
|
|
|
|
params["tradeType"] = []string{"TRADE"}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.startAt != nil {
|
|
|
|
params["startAt"] = []string{strconv.FormatInt(r.startAt.UnixNano()/int64(time.Millisecond), 10)}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.endAt != nil {
|
|
|
|
params["endAt"] = []string{strconv.FormatInt(r.endAt.UnixNano()/int64(time.Millisecond), 10)}
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := r.client.newAuthenticatedRequest("GET", "/api/v1/orders", params, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := r.client.sendRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var orderResponse struct {
|
|
|
|
Code string `json:"code"`
|
|
|
|
Message string `json:"msg"`
|
|
|
|
Data *OrderListPage `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := response.DecodeJSON(&orderResponse); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if orderResponse.Data == nil {
|
|
|
|
return nil, errors.New("api error: [" + orderResponse.Code + "] " + orderResponse.Message)
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
2021-12-11 11:40:53 +00:00
|
|
|
|
|
|
|
return orderResponse.Data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *TradeService) NewListOrdersRequest() *ListOrdersRequest {
|
|
|
|
return &ListOrdersRequest{client: c.client}
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
//go:generate requestgen -type PlaceOrderRequest
|
2021-12-10 18:44:57 +00:00
|
|
|
type PlaceOrderRequest struct {
|
|
|
|
client *RestClient
|
|
|
|
|
|
|
|
// A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 32 characters.
|
2021-12-12 17:11:15 +00:00
|
|
|
clientOrderID *string `param:"clientOid,required"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
symbol string `param:"symbol,required"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
|
|
|
// A combination of case-sensitive alphanumerics, all numbers, or all letters of up to 8 characters.
|
2021-12-12 17:11:15 +00:00
|
|
|
tag *string `param:"tag"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
|
|
|
// "buy" or "sell"
|
2021-12-12 17:11:15 +00:00
|
|
|
side SideType `param:"side"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
ordType OrderType `param:"ordType"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
|
|
|
// limit order parameters
|
2021-12-12 17:11:15 +00:00
|
|
|
size string `param:"size,required"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
price *string `param:"price"`
|
2021-12-10 18:44:57 +00:00
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
timeInForce *TimeInForceType `param:"timeInForce,required"`
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PlaceOrderRequest) Do(ctx context.Context) (*OrderResponse, error) {
|
2021-12-11 11:40:53 +00:00
|
|
|
payload, err := r.getParameters()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
if _, ok := payload["clientOid"] ; !ok {
|
|
|
|
payload["clientOid"] = uuid.New().String()
|
|
|
|
}
|
|
|
|
|
2021-12-10 18:44:57 +00:00
|
|
|
req, err := r.client.newAuthenticatedRequest("POST", "/api/v1/orders", nil, payload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := r.client.sendRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var orderResponse struct {
|
|
|
|
Code string `json:"code"`
|
|
|
|
Message string `json:"msg"`
|
|
|
|
Data *OrderResponse `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := response.DecodeJSON(&orderResponse); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:40:53 +00:00
|
|
|
if orderResponse.Data == nil {
|
|
|
|
return nil, errors.New("api error: [" + orderResponse.Code + "] " + orderResponse.Message)
|
|
|
|
}
|
|
|
|
|
2021-12-10 18:44:57 +00:00
|
|
|
return orderResponse.Data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type CancelOrderRequest struct {
|
|
|
|
client *RestClient
|
|
|
|
|
|
|
|
orderID *string
|
|
|
|
clientOrderID *string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *CancelOrderRequest) OrderID(orderID string) *CancelOrderRequest {
|
|
|
|
r.orderID = &orderID
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *CancelOrderRequest) ClientOrderID(clientOrderID string) *CancelOrderRequest {
|
|
|
|
r.clientOrderID = &clientOrderID
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
type CancelOrderResponse struct {
|
|
|
|
CancelledOrderIDs []string `json:"cancelledOrderIds,omitempty"`
|
|
|
|
|
|
|
|
// used when using client order id for canceling order
|
2021-12-11 11:40:53 +00:00
|
|
|
CancelledOrderId string `json:"cancelledOrderId,omitempty"`
|
|
|
|
ClientOrderID string `json:"clientOid,omitempty"`
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *CancelOrderRequest) Do(ctx context.Context) (*CancelOrderResponse, error) {
|
2021-12-11 12:07:32 +00:00
|
|
|
if r.orderID == nil && r.clientOrderID == nil {
|
2021-12-10 18:44:57 +00:00
|
|
|
return nil, errors.New("either orderID or clientOrderID is required for canceling order")
|
|
|
|
}
|
|
|
|
|
|
|
|
var refURL string
|
|
|
|
|
|
|
|
if r.orderID != nil {
|
|
|
|
refURL = "/api/v1/orders/" + *r.orderID
|
|
|
|
} else if r.clientOrderID != nil {
|
|
|
|
refURL = "/api/v1/order/client-order/" + *r.clientOrderID
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := r.client.newAuthenticatedRequest("DELETE", refURL, nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := r.client.sendRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:44:07 +00:00
|
|
|
var apiResponse struct {
|
2021-12-10 18:44:57 +00:00
|
|
|
Code string `json:"code"`
|
|
|
|
Message string `json:"msg"`
|
|
|
|
Data *CancelOrderResponse `json:"data"`
|
|
|
|
}
|
2021-12-11 11:44:07 +00:00
|
|
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
2021-12-10 18:44:57 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:44:07 +00:00
|
|
|
if apiResponse.Data == nil {
|
|
|
|
return nil, errors.New("api error: [" + apiResponse.Code + "] " + apiResponse.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
return apiResponse.Data, nil
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CancelAllOrderRequest struct {
|
|
|
|
client *RestClient
|
|
|
|
|
|
|
|
symbol *string
|
|
|
|
|
|
|
|
// tradeType string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *CancelAllOrderRequest) Symbol(symbol string) *CancelAllOrderRequest {
|
|
|
|
r.symbol = &symbol
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *CancelAllOrderRequest) Do(ctx context.Context) (*CancelOrderResponse, error) {
|
|
|
|
req, err := r.client.newAuthenticatedRequest("DELETE", "/api/v1/orders", nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := r.client.sendRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:44:07 +00:00
|
|
|
var apiResponse struct {
|
2021-12-11 11:40:53 +00:00
|
|
|
Code string `json:"code"`
|
|
|
|
Message string `json:"msg"`
|
2021-12-10 18:44:57 +00:00
|
|
|
Data *CancelOrderResponse `json:"data"`
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:44:07 +00:00
|
|
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
2021-12-10 18:44:57 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:44:07 +00:00
|
|
|
if apiResponse.Data == nil {
|
|
|
|
return nil, errors.New("api error: [" + apiResponse.Code + "] " + apiResponse.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
return apiResponse.Data, nil
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Request via this endpoint to place 5 orders at the same time.
|
|
|
|
// The order type must be a limit order of the same symbol.
|
|
|
|
// The interface currently only supports spot trading
|
|
|
|
type BatchPlaceOrderRequest struct {
|
|
|
|
client *RestClient
|
|
|
|
|
|
|
|
symbol string
|
2021-12-11 11:40:53 +00:00
|
|
|
reqs []*PlaceOrderRequest
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *BatchPlaceOrderRequest) Symbol(symbol string) *BatchPlaceOrderRequest {
|
|
|
|
r.symbol = symbol
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *BatchPlaceOrderRequest) Add(reqs ...*PlaceOrderRequest) *BatchPlaceOrderRequest {
|
|
|
|
r.reqs = append(r.reqs, reqs...)
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *BatchPlaceOrderRequest) Do(ctx context.Context) ([]OrderResponse, error) {
|
|
|
|
var orderList []map[string]interface{}
|
|
|
|
for _, req := range r.reqs {
|
2021-12-11 11:40:53 +00:00
|
|
|
params, err := req.getParameters()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-12 17:11:15 +00:00
|
|
|
if _, ok := params["clientOid"] ; !ok {
|
|
|
|
params["clientOid"] = uuid.New().String()
|
|
|
|
}
|
|
|
|
|
2021-12-10 18:44:57 +00:00
|
|
|
orderList = append(orderList, params)
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload = map[string]interface{}{
|
2021-12-11 11:40:53 +00:00
|
|
|
"symbol": r.symbol,
|
2021-12-10 18:44:57 +00:00
|
|
|
"orderList": orderList,
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := r.client.newAuthenticatedRequest("POST", "/api/v1/orders/multi", nil, payload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := r.client.sendRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 12:02:35 +00:00
|
|
|
var apiResponse struct {
|
2021-12-10 18:44:57 +00:00
|
|
|
Code string `json:"code"`
|
|
|
|
Message string `json:"msg"`
|
|
|
|
Data []OrderResponse `json:"data"`
|
|
|
|
}
|
2021-12-11 12:02:35 +00:00
|
|
|
|
|
|
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
2021-12-10 18:44:57 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-11 12:02:35 +00:00
|
|
|
if apiResponse.Data == nil {
|
|
|
|
return nil, errors.New("api error: [" + apiResponse.Code + "] " + apiResponse.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
return apiResponse.Data, nil
|
2021-12-10 18:44:57 +00:00
|
|
|
}
|