diff --git a/examples/create_market_order.py b/examples/create_market_order.py index 62be835..069d2db 100644 --- a/examples/create_market_order.py +++ b/examples/create_market_order.py @@ -5,11 +5,11 @@ logging.basicConfig(level=logging.DEBUG) # The API_KEY_PRIVATE_KEY provided belongs to a dummy account registered on Testnet. -# It was generated using the setup_system.py script, and servers as an example. +# It was generated using the setup_system.py script, and serves as an example. BASE_URL = "https://testnet.zklighter.elliot.ai" API_KEY_PRIVATE_KEY = "0xed636277f3753b6c0275f7a28c2678a7f3a95655e09deaebec15179b50c5da7f903152e50f594f7b" ACCOUNT_INDEX = 65 -API_KEY_INDEX = 1 +API_KEY_INDEX = 3 def trim_exception(e: Exception) -> str: @@ -28,7 +28,7 @@ async def main(): market_index=0, client_order_index=0, base_amount=1000, # 0.1 ETH - avg_execution_price=170000, # $1700 + avg_execution_price=170000, # $1700 -- worst acceptable price for the order is_ask=True, ) print("Create Order Tx:", tx) diff --git a/examples/create_market_order_max_slippage.py b/examples/create_market_order_max_slippage.py new file mode 100644 index 0000000..f219a22 --- /dev/null +++ b/examples/create_market_order_max_slippage.py @@ -0,0 +1,36 @@ +import asyncio +import logging +import lighter + +logging.basicConfig(level=logging.DEBUG) + +# The API_KEY_PRIVATE_KEY provided belongs to a dummy account registered on Testnet. +# It was generated using the setup_system.py script, and serves as an example. +BASE_URL = "https://testnet.zklighter.elliot.ai" +API_KEY_PRIVATE_KEY = "0xe0fa55e11d6b5575d54c0500bd2f3b240221ae90241e3b573f2307e27de20c04ea628de3f1936e56" +ACCOUNT_INDEX = 22 +API_KEY_INDEX = 3 + + +def trim_exception(e: Exception) -> str: + return str(e).strip().split("\n")[-1] + + +async def main(): + client = lighter.SignerClient( + url=BASE_URL, + private_key=API_KEY_PRIVATE_KEY, + account_index=ACCOUNT_INDEX, + api_key_index=API_KEY_INDEX, + ) + + # tx = await client.create_market_order_limited_slippage(market_index=0, client_order_index=0, base_amount=30000000, + # max_slippage=0.001, is_ask=True) + tx = await client.create_market_order_if_slippage(market_index=0, client_order_index=0, base_amount=30000000, + max_slippage=0.01, is_ask=True, ideal_price=300000) + print("Create Order Tx:", tx) + await client.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/lighter/signer_client.py b/lighter/signer_client.py index 12b81c5..a0d7ab1 100644 --- a/lighter/signer_client.py +++ b/lighter/signer_client.py @@ -190,6 +190,7 @@ def __init__( self.signer = _initialize_signer() self.api_client = lighter.ApiClient(configuration=Configuration(host=url)) self.tx_api = lighter.TransactionApi(self.api_client) + self.order_api = lighter.OrderApi(self.api_client) self.nonce_manager = nonce_manager.nonce_manager_factory( nonce_manager_type=nonce_management_type, account_index=account_index, @@ -618,6 +619,88 @@ async def create_market_order( api_key_index=api_key_index, ) + # will only do the amount such that the slippage is limited to the value provided + async def create_market_order_limited_slippage( + self, + market_index, + client_order_index, + base_amount, + max_slippage, + is_ask, + reduce_only: bool = False, + nonce=-1, + api_key_index=-1, + ideal_price=None + ) -> (CreateOrder, TxHash, str): + if ideal_price is None: + order_book_orders = await self.order_api.order_book_orders(market_index, 1) + logging.debug("Create market order limited slippage is doing an API call to get the current ideal price. You can also provide it yourself to avoid this.") + ideal_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", "")) + + acceptable_execution_price = round(ideal_price * (1 + max_slippage * (-1 if is_ask else 1))) + return await self.create_order( + market_index, + client_order_index, + base_amount, + price=acceptable_execution_price, + is_ask=is_ask, + order_type=self.ORDER_TYPE_MARKET, + time_in_force=self.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL, + order_expiry=self.DEFAULT_IOC_EXPIRY, + reduce_only=reduce_only, + nonce=nonce, + api_key_index=api_key_index, + ) + + # will only execute the order if it executes with slippage <= max_slippage + async def create_market_order_if_slippage( + self, + market_index, + client_order_index, + base_amount, + max_slippage, + is_ask, + reduce_only: bool = False, + nonce=-1, + api_key_index=-1, + ideal_price=None + ) -> (CreateOrder, TxHash, str): + order_book_orders = await self.order_api.order_book_orders(market_index, 100) + if ideal_price is None: + ideal_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", "")) + + matched_usd_amount, matched_size = 0, 0 + for order_book_order in (order_book_orders.bids if is_ask else order_book_orders.asks): + if matched_size == base_amount: + break + curr_order_price = int(order_book_order.price.replace(".", "")) + curr_order_size = int(order_book_order.remaining_base_amount.replace(".", "")) + to_be_used_order_size = min(base_amount - matched_size, curr_order_size) + matched_usd_amount += curr_order_price * to_be_used_order_size + matched_size += to_be_used_order_size + + potential_execution_price = matched_usd_amount / matched_size + acceptable_execution_price = ideal_price * (1 + max_slippage * (-1 if is_ask else 1)) + if (is_ask and potential_execution_price < acceptable_execution_price) or (not is_ask and potential_execution_price > acceptable_execution_price): + return None, None, "Excessive slippage" + + if matched_size < base_amount: + return None, None, "Cannot be sure slippage will be acceptable due to the high size" + + return await self.create_order( + market_index, + client_order_index, + base_amount, + price=round(acceptable_execution_price), + is_ask=is_ask, + order_type=self.ORDER_TYPE_MARKET, + time_in_force=self.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL, + order_expiry=self.DEFAULT_IOC_EXPIRY, + reduce_only=reduce_only, + nonce=nonce, + api_key_index=api_key_index, + ) + @process_api_key_and_nonce async def cancel_order(self, market_index, order_index, nonce=-1, api_key_index=-1) -> (CancelOrder, TxHash, str): tx_info, error = self.sign_cancel_order(market_index, order_index, nonce)