Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/create_market_order_max_slippage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async def main():
base_amount=1000, # 0.1 ETH
max_slippage=0.01, # 1%
is_ask=True,
ideal_price=300000 # $3000
# ideal_price=300000 # $3000
)

print("Create Order Tx:", tx)
Expand Down
38 changes: 38 additions & 0 deletions examples/create_market_order_quote_amount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import asyncio
from utils import default_example_setup


async def main():
client, api_client, _ = default_example_setup()
client.check_client()

market_index = 0
margin = 100 # usdc
leverage = 7

tx, tx_hash, err = await client.update_leverage(
market_index=market_index,
leverage=leverage,
margin_mode=client.CROSS_MARGIN_MODE
)
quote_amount = margin * leverage
print(f"Update Leverage {tx=} {tx_hash=} {err=}")

# Note: this also works for spot
tx, tx_hash, err = await client.create_market_order_quote_amount(
market_index=market_index,
client_order_index=0,
quote_amount=quote_amount,
max_slippage=0.001,
is_ask=False
)
print(f"Create Order {tx=} {tx_hash=} {err=}")
if err is not None:
raise Exception(err)

await client.close()
await api_client.close()


if __name__ == "__main__":
asyncio.run(main())
103 changes: 87 additions & 16 deletions lighter/signer_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ctypes
from fractions import Fraction
from functools import wraps
import inspect
import json
Expand Down Expand Up @@ -585,8 +586,83 @@ async def create_market_order(
market_index,
client_order_index,
base_amount,
avg_execution_price,
price=avg_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,
)

# returns best price as integer
async def get_best_price(self, market_index, is_ask, ob_orders=None) -> int:
if ob_orders is None:
ob_orders = await self.order_api.order_book_orders(market_index, 1)
ideal_price = int((ob_orders.bids[0].price if is_ask else ob_orders.asks[0].price).replace(".", ""))
return ideal_price

async def get_potential_execution_price(self, market_index, amount, is_ask, is_amount_base=True, ob_orders=None) -> (float, int):
if ob_orders is None:
ob_orders = await self.order_api.order_book_orders(market_index, 100)
matched_usd_amount, matched_size = 0, 0
for ob_order in (ob_orders.bids if is_ask else ob_orders.asks):
if (is_amount_base and matched_size == amount) or (not is_amount_base and matched_usd_amount == amount):
break
curr_order_price = int(ob_order.price.replace(".", ""))
curr_order_size = int(ob_order.remaining_base_amount.replace(".", ""))
max_possible_order_size = amount - matched_size if is_amount_base else Fraction(amount - matched_usd_amount, curr_order_price)

to_be_used_order_size = min(max_possible_order_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

return potential_execution_price, (matched_size if is_amount_base else matched_usd_amount)

async def create_market_order_quote_amount(
self,
market_index,
client_order_index,
quote_amount,
max_slippage,
is_ask,
reduce_only: bool = False,
nonce: int = DEFAULT_NONCE,
api_key_index: int = DEFAULT_API_KEY_INDEX,
ideal_price=None
):
quote_amount = int(quote_amount * 1e6)
ob_orders = await self.order_api.order_book_orders(market_index, 100)
if ideal_price is None:
logging.debug(
"Doing an API call to get the current ideal price. You can also provide it yourself to avoid this.")
ideal_price = await self.get_best_price(market_index, is_ask, ob_orders=ob_orders)
acceptable_execution_price = round(ideal_price * (1 + max_slippage * (-1 if is_ask else 1)))

potential_execution_price, matched_usd_amount = await self.get_potential_execution_price(
market_index,
quote_amount,
is_ask,
is_amount_base=False,
ob_orders=ob_orders
)

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_usd_amount < quote_amount:
return None, None, "Cannot be sure slippage will be acceptable due to the high size"

# one can choose between int or round depending on purpose, doesn't really much
base_amount = int(quote_amount / potential_execution_price)
return await self.create_order(
market_index,
client_order_index,
base_amount,
price=round(acceptable_execution_price), # just in case, limits size for slippage
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,
Expand All @@ -609,10 +685,9 @@ async def create_market_order_limited_slippage(
ideal_price=None
) -> Union[Tuple[CreateOrder, RespSendTx, None], Tuple[None, None, 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(".", ""))
ideal_price = await self.get_best_price(market_index, is_ask)

acceptable_execution_price = round(ideal_price * (1 + max_slippage * (-1 if is_ask else 1)))
return await self.create_order(
Expand Down Expand Up @@ -642,21 +717,17 @@ async def create_market_order_if_slippage(
api_key_index: int = DEFAULT_API_KEY_INDEX,
ideal_price=None
) -> Union[Tuple[CreateOrder, RespSendTx, None], Tuple[None, None, str]]:
order_book_orders = await self.order_api.order_book_orders(market_index, 100)
ob_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
ideal_price = await self.get_best_price(market_index, is_ask, ob_orders)
potential_execution_price, matched_size = await self.get_potential_execution_price(
market_index,
base_amount,
is_ask,
is_amount_base=True,
ob_orders=ob_orders
)

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"
Expand Down