From 42e85ae1c5bab7816073506fb2c164f6d09a10e1 Mon Sep 17 00:00:00 2001 From: Mihail Lavric Date: Sun, 24 Aug 2025 17:36:18 +0300 Subject: [PATCH 1/3] market order with slippage params --- examples/create_market_order.py | 6 +- examples/create_market_order_max_slippage.py | 36 +++++++++ lighter/signer_client.py | 77 ++++++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 examples/create_market_order_max_slippage.py diff --git a/examples/create_market_order.py b/examples/create_market_order.py index 62be835..e864a7c 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 = 2 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..0d9dcea --- /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 = "0x321ec32c490960eb83df619e2701b5948e32da808fc0b6fd99550ce92af130f1c4088a37a0793972" +ACCOUNT_INDEX = 46 +API_KEY_INDEX = 2 + + +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) + 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..3c0ad52 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,82 @@ 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, + ) -> (CreateOrder, TxHash, str): + order_book_orders = await self.order_api.order_book_orders(market_index, 1) + best_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", "")) + acceptable_execution_price = round(best_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, + ) -> (CreateOrder, TxHash, str): + order_book_orders = await self.order_api.order_book_orders(market_index, 100) + best_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 = best_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) From 74d7eed18a84d31c1147d83fe150ab3be119391b Mon Sep 17 00:00:00 2001 From: Mihail Lavric Date: Tue, 26 Aug 2025 15:52:45 +0300 Subject: [PATCH 2/3] add option to set ideal_price yourself (slippage) --- examples/create_market_order_max_slippage.py | 8 ++++---- lighter/signer_client.py | 16 +++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/create_market_order_max_slippage.py b/examples/create_market_order_max_slippage.py index 0d9dcea..f219a22 100644 --- a/examples/create_market_order_max_slippage.py +++ b/examples/create_market_order_max_slippage.py @@ -7,9 +7,9 @@ # 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 = "0x321ec32c490960eb83df619e2701b5948e32da808fc0b6fd99550ce92af130f1c4088a37a0793972" -ACCOUNT_INDEX = 46 -API_KEY_INDEX = 2 +API_KEY_PRIVATE_KEY = "0xe0fa55e11d6b5575d54c0500bd2f3b240221ae90241e3b573f2307e27de20c04ea628de3f1936e56" +ACCOUNT_INDEX = 22 +API_KEY_INDEX = 3 def trim_exception(e: Exception) -> str: @@ -27,7 +27,7 @@ async def main(): # 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) + max_slippage=0.01, is_ask=True, ideal_price=300000) print("Create Order Tx:", tx) await client.close() diff --git a/lighter/signer_client.py b/lighter/signer_client.py index 3c0ad52..a0d7ab1 100644 --- a/lighter/signer_client.py +++ b/lighter/signer_client.py @@ -630,10 +630,14 @@ async def create_market_order_limited_slippage( 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, 1) - best_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", "")) - acceptable_execution_price = round(best_price * (1 + max_slippage * (-1 if is_ask else 1))) + 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, @@ -659,9 +663,11 @@ async def create_market_order_if_slippage( 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) - best_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", "")) + 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): @@ -674,7 +680,7 @@ async def create_market_order_if_slippage( matched_size += to_be_used_order_size potential_execution_price = matched_usd_amount / matched_size - acceptable_execution_price = best_price * (1 + max_slippage * (-1 if is_ask else 1)) + 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" From 91adddf810d83c78078c4809c7ea0469c1fa410a Mon Sep 17 00:00:00 2001 From: Mihail Lavric Date: Tue, 26 Aug 2025 15:54:23 +0300 Subject: [PATCH 3/3] api key index 3 --- examples/create_market_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/create_market_order.py b/examples/create_market_order.py index e864a7c..069d2db 100644 --- a/examples/create_market_order.py +++ b/examples/create_market_order.py @@ -9,7 +9,7 @@ BASE_URL = "https://testnet.zklighter.elliot.ai" API_KEY_PRIVATE_KEY = "0xed636277f3753b6c0275f7a28c2678a7f3a95655e09deaebec15179b50c5da7f903152e50f594f7b" ACCOUNT_INDEX = 65 -API_KEY_INDEX = 2 +API_KEY_INDEX = 3 def trim_exception(e: Exception) -> str: