Kraken: Various futures bug fixes (#806)

* Kraken futures: fix requests signature calculation

* Kraken futures: fix error return of SendOrder

* Kraken futures: fix order id json field name

* Kraken Futures: ensure uppercase instrument names

* Kraken Futures: add support for immediate or cancel type orders
This commit is contained in:
Luis Rascão
2021-10-20 01:34:33 +01:00
committed by GitHub
parent cb5fd8100c
commit a4d792f0c5
4 changed files with 57 additions and 35 deletions

View File

@@ -316,7 +316,7 @@ type FuturesEditedOrderData struct {
// FuturesSendOrderData stores send order data
type FuturesSendOrderData struct {
SendStatus struct {
OrderID string `json:"orderId"`
OrderID string `json:"order_id"`
Status string `json:"status"`
ReceivedTime string `json:"receivedTime"`
OrderEvents []struct {
@@ -528,7 +528,7 @@ type FuturesRecentOrdersData struct {
// BatchOrderData stores batch order data
type BatchOrderData struct {
Result bool `json:"result"`
Result string `json:"result"`
ServerTime string `json:"serverTime"`
BatchStatus []struct {
Status string `json:"status"`

View File

@@ -62,8 +62,8 @@ func (k *Kraken) GetFuturesTradeHistory(ctx context.Context, symbol currency.Pai
}
// FuturesBatchOrder places a batch order for futures
func (k *Kraken) FuturesBatchOrder(ctx context.Context, data []PlaceBatchOrderData) (FuturesAccountsData, error) {
var resp FuturesAccountsData
func (k *Kraken) FuturesBatchOrder(ctx context.Context, data []PlaceBatchOrderData) (BatchOrderData, error) {
var resp BatchOrderData
for x := range data {
unformattedPair, err := currency.NewPairFromString(data[x].Symbol)
if err != nil {
@@ -80,9 +80,18 @@ func (k *Kraken) FuturesBatchOrder(ctx context.Context, data []PlaceBatchOrderDa
}
data[x].Symbol = formattedPair.String()
}
req := make(map[string]interface{})
req["batchOrder"] = data
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresBatchOrder, nil, req, &resp)
jsonData, err := json.Marshal(req)
if err != nil {
return resp, err
}
params := url.Values{}
params.Set("json", string(jsonData))
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresBatchOrder, params, &resp)
}
// FuturesEditOrder edits a futures order
@@ -98,13 +107,19 @@ func (k *Kraken) FuturesEditOrder(ctx context.Context, orderID, clientOrderID st
params.Set("size", strconv.FormatFloat(size, 'f', -1, 64))
params.Set("limitPrice", strconv.FormatFloat(limitPrice, 'f', -1, 64))
params.Set("stopPrice", strconv.FormatFloat(stopPrice, 'f', -1, 64))
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresEditOrder, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresEditOrder, params, &resp)
}
// FuturesSendOrder sends a futures order
func (k *Kraken) FuturesSendOrder(ctx context.Context, orderType order.Type, symbol currency.Pair, side, triggerSignal, clientOrderID, reduceOnly string,
ioc bool,
size, limitPrice, stopPrice float64) (FuturesSendOrderData, error) {
var resp FuturesSendOrderData
if ioc {
orderType = order.ImmediateOrCancel
}
oType, ok := validOrderTypes[orderType]
if !ok {
return resp, errors.New("invalid orderType")
@@ -140,7 +155,7 @@ func (k *Kraken) FuturesSendOrder(ctx context.Context, orderType order.Type, sym
if stopPrice != 0 {
params.Set("stopPrice", strconv.FormatFloat(stopPrice, 'f', -1, 64))
}
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresSendOrder, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresSendOrder, params, &resp)
}
// FuturesCancelOrder cancels an order
@@ -153,7 +168,7 @@ func (k *Kraken) FuturesCancelOrder(ctx context.Context, orderID, clientOrderID
if clientOrderID != "" {
params.Set("cliOrdId", clientOrderID)
}
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresCancelOrder, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresCancelOrder, params, &resp)
}
// FuturesGetFills gets order fills for futures
@@ -163,7 +178,7 @@ func (k *Kraken) FuturesGetFills(ctx context.Context, lastFillTime time.Time) (F
if !lastFillTime.IsZero() {
params.Set("lastFillTime", lastFillTime.UTC().Format("2006-01-02T15:04:05.999Z"))
}
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresOrderFills, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresOrderFills, params, &resp)
}
// FuturesTransfer transfers funds between accounts
@@ -174,19 +189,19 @@ func (k *Kraken) FuturesTransfer(ctx context.Context, fromAccount, toAccount, un
req["toAccount"] = toAccount
req["unit"] = unit
req["amount"] = amount
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresTransfer, nil, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresTransfer, nil, &resp)
}
// FuturesGetOpenPositions gets futures platform's notifications
func (k *Kraken) FuturesGetOpenPositions(ctx context.Context) (FuturesOpenPositions, error) {
var resp FuturesOpenPositions
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresOpenPositions, nil, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresOpenPositions, nil, &resp)
}
// FuturesNotifications gets futures notifications
func (k *Kraken) FuturesNotifications(ctx context.Context) (FuturesNotificationData, error) {
var resp FuturesNotificationData
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresNotifications, nil, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresNotifications, nil, &resp)
}
// FuturesCancelAllOrders cancels all futures orders for a given symbol or all symbols
@@ -200,7 +215,7 @@ func (k *Kraken) FuturesCancelAllOrders(ctx context.Context, symbol currency.Pai
}
params.Set("symbol", symbolValue)
}
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresCancelAllOrders, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresCancelAllOrders, params, &resp)
}
// FuturesCancelAllOrdersAfter cancels all futures orders for all symbols after a period of time (timeout measured in seconds)
@@ -208,13 +223,13 @@ func (k *Kraken) FuturesCancelAllOrdersAfter(ctx context.Context, timeout int64)
var resp CancelOrdersAfterData
params := url.Values{}
params.Set("timeout", strconv.FormatInt(timeout, 10))
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresCancelOrdersAfter, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresCancelOrdersAfter, params, &resp)
}
// FuturesOpenOrders gets all futures open orders
func (k *Kraken) FuturesOpenOrders(ctx context.Context) (FuturesOpenOrdersData, error) {
var resp FuturesOpenOrdersData
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresOpenOrders, nil, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresOpenOrders, nil, &resp)
}
// FuturesRecentOrders gets recent futures orders for a symbol or all symbols
@@ -228,7 +243,7 @@ func (k *Kraken) FuturesRecentOrders(ctx context.Context, symbol currency.Pair)
}
params.Set("symbol", symbolValue)
}
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresRecentOrders, nil, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresRecentOrders, nil, &resp)
}
// FuturesWithdrawToSpotWallet withdraws currencies from futures wallet to spot wallet
@@ -237,7 +252,7 @@ func (k *Kraken) FuturesWithdrawToSpotWallet(ctx context.Context, currency strin
params := url.Values{}
params.Set("currency", currency)
params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresWithdraw, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodPost, futuresWithdraw, params, &resp)
}
// FuturesGetTransfers withdraws currencies from futures wallet to spot wallet
@@ -247,13 +262,13 @@ func (k *Kraken) FuturesGetTransfers(ctx context.Context, lastTransferTime time.
if !lastTransferTime.IsZero() {
params.Set("lastTransferTime", lastTransferTime.UTC().Format(time.RFC3339))
}
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresTransfers, params, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresTransfers, params, &resp)
}
// GetFuturesAccountData gets account data for futures
func (k *Kraken) GetFuturesAccountData(ctx context.Context) (FuturesAccountsData, error) {
var resp FuturesAccountsData
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresAccountData, nil, nil, &resp)
return resp, k.SendFuturesAuthRequest(ctx, http.MethodGet, futuresAccountData, nil, &resp)
}
func (k *Kraken) signFuturesRequest(endpoint, nonce, data string) (string, error) {
@@ -272,27 +287,25 @@ func (k *Kraken) signFuturesRequest(endpoint, nonce, data string) (string, error
}
// SendFuturesAuthRequest will send an auth req
func (k *Kraken) SendFuturesAuthRequest(ctx context.Context, method, path string, postData url.Values, data map[string]interface{}, result interface{}) error {
func (k *Kraken) SendFuturesAuthRequest(ctx context.Context, method, path string, data url.Values, result interface{}) error {
if !k.AllowAuthenticatedRequest() {
return fmt.Errorf("%s %w", k.Name, exchange.ErrAuthenticatedRequestWithoutCredentialsSet)
}
if postData == nil {
postData = url.Values{}
if data == nil {
data = url.Values{}
}
dataToSign := data.Encode()
// when json payloads are requested, signing needs to the unendecoded data
if data.Has("json") {
dataToSign = "json=" + data.Get("json")
}
interim := json.RawMessage{}
newRequest := func() (*request.Item, error) {
nonce := strconv.FormatInt(time.Now().UnixMilli(), 10)
reqData := ""
if len(data) > 0 {
temp, err := json.Marshal(data)
if err != nil {
return nil, err
}
postData.Set("json", string(temp))
reqData = "json=" + string(temp)
}
sig, err := k.signFuturesRequest(path, nonce, reqData)
sig, err := k.signFuturesRequest(path, nonce, dataToSign)
if err != nil {
return nil, err
}
@@ -304,7 +317,7 @@ func (k *Kraken) SendFuturesAuthRequest(ctx context.Context, method, path string
return &request.Item{
Method: method,
Path: futuresURL + common.EncodeURLValues(path, postData),
Path: futuresURL + common.EncodeURLValues(path, data),
Headers: headers,
Result: &interim,
AuthRequest: true,

View File

@@ -200,7 +200,7 @@ func TestFuturesSendOrder(t *testing.T) {
t.Error(err)
}
_, err = k.FuturesSendOrder(context.Background(),
order.Limit, cp, "buy", "", "", "", 1, 1, 0.9)
order.Limit, cp, "buy", "", "", "", true, 1, 1, 0.9)
if err != nil {
t.Error(err)
}

View File

@@ -606,7 +606,7 @@ func (k *Kraken) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a
for name := range bal.Accounts {
for code := range bal.Accounts[name].Balances {
balances = append(balances, account.Balance{
CurrencyName: currency.NewCode(code),
CurrencyName: currency.NewCode(code).Upper(),
TotalValue: bal.Accounts[name].Balances[code],
})
}
@@ -749,6 +749,7 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (order.Submit
"",
s.ClientOrderID,
"",
s.ImmediateOrCancel,
s.Amount,
s.Price,
0,
@@ -756,6 +757,14 @@ func (k *Kraken) SubmitOrder(ctx context.Context, s *order.Submit) (order.Submit
if err != nil {
return submitOrderResponse, err
}
// check the status, anything that is not placed we error out
if order.SendStatus.Status != "placed" {
return submitOrderResponse,
fmt.Errorf("submit order failed: %s",
order.SendStatus.Status)
}
submitOrderResponse.OrderID = order.SendStatus.OrderID
submitOrderResponse.IsOrderPlaced = true
default: