diff --git a/hyperliquid/consts.go b/hyperliquid/consts.go index 6df7155..f51569f 100644 --- a/hyperliquid/consts.go +++ b/hyperliquid/consts.go @@ -1,6 +1,6 @@ package hyperliquid -const GLOBAL_DEBUG = false // Defualt debug that is used in all tests +const GLOBAL_DEBUG = false // Default debug that is used in all tests // Execution constants const DEFAULT_SLIPPAGE = 0.005 // 0.5% default slippage diff --git a/hyperliquid/convert.go b/hyperliquid/convert.go index 7553154..fc9f600 100644 --- a/hyperliquid/convert.go +++ b/hyperliquid/convert.go @@ -60,6 +60,7 @@ func OrderRequestToWire(req OrderRequest, meta map[string]AssetInfo, isSpot bool SizePx: FloatToWire(req.Sz, maxDecimals, info.SzDecimals), ReduceOnly: req.ReduceOnly, OrderType: OrderTypeToWire(req.OrderType), + Cloid: req.Cloid, } } diff --git a/hyperliquid/exchange_service.go b/hyperliquid/exchange_service.go index 96461e1..b132b5f 100644 --- a/hyperliquid/exchange_service.go +++ b/hyperliquid/exchange_service.go @@ -14,11 +14,12 @@ type IExchangeAPI interface { // Open orders BulkOrders(requests []OrderRequest, grouping Grouping) (*PlaceOrderResponse, error) Order(request OrderRequest, grouping Grouping) (*PlaceOrderResponse, error) - MarketOrder(coin string, size float64, slippage *float64) (*PlaceOrderResponse, error) - LimitOrder(orderType string, coin string, size float64, px float64, isBuy bool, reduceOnly bool) (*PlaceOrderResponse, error) + MarketOrder(coin string, size float64, slippage *float64, clientOID ...string) (*PlaceOrderResponse, error) + LimitOrder(orderType string, coin string, size float64, px float64, isBuy bool, reduceOnly bool, clientOID ...string) (*PlaceOrderResponse, error) // Order management CancelOrderByOID(coin string, orderID int) (any, error) + CancelOrderByCloid(coin string, clientOID string) (any, error) BulkCancelOrders(cancels []CancelOidWire) (any, error) CancelAllOrdersByCoin(coin string) (any, error) CancelAllOrders() (any, error) @@ -98,7 +99,7 @@ func (api *ExchangeAPI) SlippagePriceSpot(coin string, isBuy bool, slippage floa // MarketOrder("BTC", 0.1, nil) // Buy 0.1 BTC // MarketOrder("BTC", -0.1, nil) // Sell 0.1 BTC // MarketOrder("BTC", 0.1, &slippage) // Buy 0.1 BTC with slippage -func (api *ExchangeAPI) MarketOrder(coin string, size float64, slippage *float64) (*PlaceOrderResponse, error) { +func (api *ExchangeAPI) MarketOrder(coin string, size float64, slippage *float64, clientOID ...string) (*PlaceOrderResponse, error) { slpg := GetSlippage(slippage) isBuy := IsBuy(size) finalPx := api.SlippagePrice(coin, isBuy, slpg) @@ -115,6 +116,9 @@ func (api *ExchangeAPI) MarketOrder(coin string, size float64, slippage *float64 OrderType: orderType, ReduceOnly: false, } + if len(clientOID) > 0 { + orderRequest.Cloid = clientOID[0] + } return api.Order(orderRequest, GroupingNa) } @@ -150,7 +154,7 @@ func (api *ExchangeAPI) MarketOrderSpot(coin string, size float64, slippage *flo // Order type can be Gtc, Ioc, Alo. // Size determines the amount of the coin to buy/sell. // See the constants TifGtc, TifIoc, TifAlo. -func (api *ExchangeAPI) LimitOrder(orderType string, coin string, size float64, px float64, reduceOnly bool) (*PlaceOrderResponse, error) { +func (api *ExchangeAPI) LimitOrder(orderType string, coin string, size float64, px float64, reduceOnly bool, clientOID ...string) (*PlaceOrderResponse, error) { // check if the order type is valid if orderType != TifGtc && orderType != TifIoc && orderType != TifAlo { return nil, APIError{Message: fmt.Sprintf("Invalid order type: %s. Available types: %s, %s, %s", orderType, TifGtc, TifIoc, TifAlo)} @@ -168,6 +172,9 @@ func (api *ExchangeAPI) LimitOrder(orderType string, coin string, size float64, OrderType: orderTypeZ, ReduceOnly: reduceOnly, } + if len(clientOID) > 0 { + orderRequest.Cloid = clientOID[0] + } return api.Order(orderRequest, GroupingNa) } @@ -304,6 +311,33 @@ func (api *ExchangeAPI) CancelOrderByOID(coin string, orderID int64) (*CancelOrd return api.BulkCancelOrders([]CancelOidWire{{Asset: api.meta[coin].AssetId, Oid: int(orderID)}}) } +// Cancel exact order by Client Order Id +// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid +func (api *ExchangeAPI) CancelOrderByCloid(coin string, clientOID string) (*CancelOrderResponse, error) { + timestamp := GetNonce() + action := CancelCloidOrderAction{ + Type: "cancelByCloid", + Cancels: []CancelCloidWire{ + { + Asset: api.meta[coin].AssetId, + Cloid: clientOID, + }, + }, + } + v, r, s, err := api.SignL1Action(action, timestamp) + if err != nil { + api.debug("Error signing L1 action: %s", err) + return nil, err + } + request := ExchangeRequest{ + Action: action, + Nonce: timestamp, + Signature: ToTypedSig(r, s, v), + VaultAddress: nil, + } + return MakeUniversalRequest[CancelOrderResponse](api, request) +} + // Cancel all orders for a given coin func (api *ExchangeAPI) CancelAllOrdersByCoin(coin string) (*CancelOrderResponse, error) { orders, err := api.infoAPI.GetOpenOrders(api.AccountAddress()) diff --git a/hyperliquid/exchange_types.go b/hyperliquid/exchange_types.go index 4845763..1334c13 100644 --- a/hyperliquid/exchange_types.go +++ b/hyperliquid/exchange_types.go @@ -28,6 +28,7 @@ type OrderRequest struct { LimitPx float64 `json:"limit_px"` OrderType OrderType `json:"order_type"` ReduceOnly bool `json:"reduce_only"` + Cloid string `json:"cloid,omitempty"` } type OrderType struct { @@ -145,6 +146,16 @@ type CancelOidWire struct { Oid int `msgpack:"o" json:"o"` } +type CancelCloidWire struct { + Asset int `msgpack:"asset" json:"asset"` + Cloid string `msgpack:"cloid" json:"cloid"` +} + +type CancelCloidOrderAction struct { + Type string `msgpack:"type" json:"type"` + Cancels []CancelCloidWire `msgpack:"cancels" json:"cancels"` +} + type CancelOrderResponse struct { Status string `json:"status"` Response InnerCancelResponse `json:"response"` @@ -160,7 +171,8 @@ type CancelResponseStatuses struct { } type RestingStatus struct { - OrderId int `json:"oid"` + OrderId int `json:"oid"` + Cloid string `json:"cloid,omitempty"` } type CloseRequest struct { @@ -175,6 +187,7 @@ type FilledStatus struct { OrderId int `json:"oid"` AvgPx float64 `json:"avgPx,string"` TotalSz float64 `json:"totalSz,string"` + Cloid string `json:"cloid,omitempty"` } type Liquidation struct { diff --git a/hyperliquid/hyperliquid_test.go b/hyperliquid/hyperliquid_test.go index 0ce2d37..3624241 100644 --- a/hyperliquid/hyperliquid_test.go +++ b/hyperliquid/hyperliquid_test.go @@ -70,38 +70,46 @@ func TestHyperliquid_MakeSomeTradingLogic(t *testing.T) { } t.Logf("LimitOrder(TifGtc, ETH, -0.01, 5000.1, true): %v", res3) + res4, err := client.LimitOrder(TifGtc, "ETH", 0.01, 1234.1, false, "0x1234567890abcdef1234567890abcdef") + if err != nil { + if err != nil { + t.Errorf("Error: %v", err) + } + } + t.Logf("LimitOrder(TifIoc, ETH, 0.01, 1234.1, false, 0x1234567890abcdef1234567890abcdef): %v", res4) + // Get all ordres - res4, err := client.GetAccountOpenOrders() + res5, err := client.GetAccountOpenOrders() if err != nil { t.Errorf("Error: %v", err) } - t.Logf("GetAccountOpenOrders(): %v", res4) + t.Logf("GetAccountOpenOrders(): %v", res5) // Close all orders - res5, err := client.CancelAllOrders() + res6, err := client.CancelAllOrders() if err != nil { t.Errorf("Error: %v", err) } - t.Logf("CancelAllOrders(): %v", res5) + t.Logf("CancelAllOrders(): %v", res6) // Make market order - res6, err := client.MarketOrder("ETH", 0.01, nil) + res7, err := client.MarketOrder("ETH", 0.01, nil) if err != nil { t.Errorf("Error: %v", err) } - t.Logf("MarketOrder(ETH, 0.01, nil): %v", res6) + t.Logf("MarketOrder(ETH, 0.01, nil): %v", res7) // Close position - res7, err := client.ClosePosition("ETH") + res8, err := client.ClosePosition("ETH") if err != nil { t.Errorf("Error: %v", err) } - t.Logf("ClosePosition(ETH): %v", res7) + t.Logf("ClosePosition(ETH): %v", res8) // Get account balance - res8, err := client.GetAccountState() + res9, err := client.GetAccountState() if err != nil { t.Errorf("Error: %v", err) } - t.Logf("GetAccountState(): %v", res8) + t.Logf("GetAccountState(): %v", res9) }