diff --git a/examples/basic_tpsl.py b/examples/basic_tpsl.py index fccb37db..d4e85fef 100644 --- a/examples/basic_tpsl.py +++ b/examples/basic_tpsl.py @@ -10,16 +10,21 @@ def main(): parser.add_argument("--is_buy", action="store_true") args = parser.parse_args() - address, info, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True) + _, _, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True) is_buy = args.is_buy - # Place an order that should execute by setting the price very aggressively - order_result = exchange.order("ETH", is_buy, 0.02, 2500 if is_buy else 1500, {"limit": {"tif": "Gtc"}}) + coin = "ETH" + sz = 0.02 + px = 3500 if is_buy else 2500 + trigger_px = 2600 if is_buy else 3400 + sl_px = 2500 if is_buy else 3500 + # Place an order that should execute by setting the price very aggressively, the above prices were set when ETH was at 3000 + order_result = exchange.order(coin, is_buy, sz, px, {"limit": {"tif": "Gtc"}}) print(order_result) # Place a stop order - stop_order_type = {"trigger": {"triggerPx": 1600 if is_buy else 2400, "isMarket": True, "tpsl": "sl"}} - stop_result = exchange.order("ETH", not is_buy, 0.02, 1500 if is_buy else 2500, stop_order_type, reduce_only=True) + stop_order_type = {"trigger": {"triggerPx": trigger_px, "isMarket": True, "tpsl": "sl"}} + stop_result = exchange.order("ETH", not is_buy, sz, sl_px, stop_order_type, reduce_only=True) print(stop_result) # Cancel the order @@ -30,8 +35,8 @@ def main(): print(cancel_result) # Place a tp order - tp_order_type = {"trigger": {"triggerPx": 1600 if is_buy else 2400, "isMarket": True, "tpsl": "tp"}} - tp_result = exchange.order("ETH", not is_buy, 0.02, 2500 if is_buy else 1500, tp_order_type, reduce_only=True) + tp_order_type = {"trigger": {"triggerPx": px, "isMarket": True, "tpsl": "tp"}} + tp_result = exchange.order("ETH", not is_buy, sz, px, tp_order_type, reduce_only=True) print(tp_result) # Cancel the order @@ -41,6 +46,49 @@ def main(): cancel_result = exchange.cancel("ETH", status["resting"]["oid"]) print(cancel_result) + # Alternatively use grouping to place the parent order and child TP/SL in a single action + orders = [ + { + "coin": "ETH", + "is_buy": is_buy, + "sz": sz, + "limit_px": px, + "order_type": {"limit": {"tif": "Gtc"}}, + "reduce_only": False, + }, + { + "coin": "ETH", + "is_buy": not is_buy, + "sz": sz, + "limit_px": px, + "order_type": { + "trigger": { + "isMarket": True, + "triggerPx": px, + "tpsl": "tp", + } + }, + "reduce_only": True, + }, + { + "coin": coin, + "is_buy": not is_buy, + "sz": sz, + "limit_px": sl_px, + "order_type": { + "trigger": { + "isMarket": True, + "triggerPx": trigger_px, + "tpsl": "sl", + } + }, + "reduce_only": True, + }, + ] + + resp = exchange.bulk_orders(orders, grouping="normalTpsl") + print(resp) + if __name__ == "__main__": main() diff --git a/hyperliquid/exchange.py b/hyperliquid/exchange.py index f3c7a33b..cc539fa1 100644 --- a/hyperliquid/exchange.py +++ b/hyperliquid/exchange.py @@ -11,6 +11,7 @@ from hyperliquid.utils.signing import ( CancelByCloidRequest, CancelRequest, + Grouping, ModifyRequest, OidOrCloid, OrderRequest, @@ -48,6 +49,10 @@ ) +def _get_dex(coin: str) -> str: + return coin.split(":")[0] if ":" in coin else "" + + class Exchange(API): # Default Max Slippage for Market Orders 5% DEFAULT_SLIPPAGE = 0.05 @@ -91,7 +96,7 @@ def _slippage_price( coin = self.info.name_to_coin[name] if not px: # Get midprice - dex = coin.split(":")[0] if ":" in coin else "" + dex = _get_dex(coin) px = float(self.info.all_mids(dex)[coin]) asset = self.info.coin_to_asset[coin] @@ -132,7 +137,9 @@ def order( order["cloid"] = cloid return self.bulk_orders([order], builder) - def bulk_orders(self, order_requests: List[OrderRequest], builder: Optional[BuilderInfo] = None) -> Any: + def bulk_orders( + self, order_requests: List[OrderRequest], builder: Optional[BuilderInfo] = None, grouping: Grouping = "na" + ) -> Any: order_wires: List[OrderWire] = [ order_request_to_order_wire(order, self.info.name_to_asset(order["coin"])) for order in order_requests ] @@ -140,7 +147,7 @@ def bulk_orders(self, order_requests: List[OrderRequest], builder: Optional[Buil if builder: builder["b"] = builder["b"].lower() - order_action = order_wires_to_order_action(order_wires, builder) + order_action = order_wires_to_order_action(order_wires, builder, grouping) signature = sign_l1_action( self.wallet, @@ -243,7 +250,8 @@ def market_close( address = self.account_address if self.vault_address: address = self.vault_address - positions = self.info.user_state(address)["assetPositions"] + dex = _get_dex(coin) + positions = self.info.user_state(address, dex)["assetPositions"] for position in positions: item = position["position"] if coin != item["coin"]: diff --git a/hyperliquid/utils/signing.py b/hyperliquid/utils/signing.py index 578f05d3..55e8192e 100644 --- a/hyperliquid/utils/signing.py +++ b/hyperliquid/utils/signing.py @@ -1,3 +1,5 @@ +from typing import Any + import time from decimal import Decimal @@ -496,11 +498,11 @@ def order_request_to_order_wire(order: OrderRequest, asset: int) -> OrderWire: return order_wire -def order_wires_to_order_action(order_wires, builder=None): +def order_wires_to_order_action(order_wires: list[OrderWire], builder: Any = None, grouping: Grouping = "na") -> Any: action = { "type": "order", "orders": order_wires, - "grouping": "na", + "grouping": grouping, } if builder: action["builder"] = builder diff --git a/pyproject.toml b/pyproject.toml index aae20142..db31aca2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "hyperliquid-python-sdk" -version = "0.20.1" +version = "0.21.0" description = "SDK for Hyperliquid API trading with Python." readme = "README.md" authors = ["Hyperliquid "]