Skip to content

Commit 082d4e1

Browse files
authored
market order wrt quote amount (#120)
* market order wrt quote amount * int vs round
1 parent 039c6a9 commit 082d4e1

3 files changed

Lines changed: 126 additions & 17 deletions

File tree

examples/create_market_order_max_slippage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ async def main():
1313
base_amount=1000, # 0.1 ETH
1414
max_slippage=0.01, # 1%
1515
is_ask=True,
16-
ideal_price=300000 # $3000
16+
# ideal_price=300000 # $3000
1717
)
1818

1919
print("Create Order Tx:", tx)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import asyncio
2+
from utils import default_example_setup
3+
4+
5+
async def main():
6+
client, api_client, _ = default_example_setup()
7+
client.check_client()
8+
9+
market_index = 0
10+
margin = 100 # usdc
11+
leverage = 7
12+
13+
tx, tx_hash, err = await client.update_leverage(
14+
market_index=market_index,
15+
leverage=leverage,
16+
margin_mode=client.CROSS_MARGIN_MODE
17+
)
18+
quote_amount = margin * leverage
19+
print(f"Update Leverage {tx=} {tx_hash=} {err=}")
20+
21+
# Note: this also works for spot
22+
tx, tx_hash, err = await client.create_market_order_quote_amount(
23+
market_index=market_index,
24+
client_order_index=0,
25+
quote_amount=quote_amount,
26+
max_slippage=0.001,
27+
is_ask=False
28+
)
29+
print(f"Create Order {tx=} {tx_hash=} {err=}")
30+
if err is not None:
31+
raise Exception(err)
32+
33+
await client.close()
34+
await api_client.close()
35+
36+
37+
if __name__ == "__main__":
38+
asyncio.run(main())

lighter/signer_client.py

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ctypes
2+
from fractions import Fraction
23
from functools import wraps
34
import inspect
45
import json
@@ -591,8 +592,83 @@ async def create_market_order(
591592
market_index,
592593
client_order_index,
593594
base_amount,
594-
avg_execution_price,
595+
price=avg_execution_price,
596+
is_ask=is_ask,
597+
order_type=self.ORDER_TYPE_MARKET,
598+
time_in_force=self.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
599+
order_expiry=self.DEFAULT_IOC_EXPIRY,
600+
reduce_only=reduce_only,
601+
nonce=nonce,
602+
api_key_index=api_key_index,
603+
)
604+
605+
# returns best price as integer
606+
async def get_best_price(self, market_index, is_ask, ob_orders=None) -> int:
607+
if ob_orders is None:
608+
ob_orders = await self.order_api.order_book_orders(market_index, 1)
609+
ideal_price = int((ob_orders.bids[0].price if is_ask else ob_orders.asks[0].price).replace(".", ""))
610+
return ideal_price
611+
612+
async def get_potential_execution_price(self, market_index, amount, is_ask, is_amount_base=True, ob_orders=None) -> (float, int):
613+
if ob_orders is None:
614+
ob_orders = await self.order_api.order_book_orders(market_index, 100)
615+
matched_usd_amount, matched_size = 0, 0
616+
for ob_order in (ob_orders.bids if is_ask else ob_orders.asks):
617+
if (is_amount_base and matched_size == amount) or (not is_amount_base and matched_usd_amount == amount):
618+
break
619+
curr_order_price = int(ob_order.price.replace(".", ""))
620+
curr_order_size = int(ob_order.remaining_base_amount.replace(".", ""))
621+
max_possible_order_size = amount - matched_size if is_amount_base else Fraction(amount - matched_usd_amount, curr_order_price)
622+
623+
to_be_used_order_size = min(max_possible_order_size, curr_order_size)
624+
matched_usd_amount += curr_order_price * to_be_used_order_size
625+
matched_size += to_be_used_order_size
626+
627+
potential_execution_price = matched_usd_amount / matched_size
628+
629+
return potential_execution_price, (matched_size if is_amount_base else matched_usd_amount)
630+
631+
async def create_market_order_quote_amount(
632+
self,
633+
market_index,
634+
client_order_index,
635+
quote_amount,
636+
max_slippage,
637+
is_ask,
638+
reduce_only: bool = False,
639+
nonce: int = DEFAULT_NONCE,
640+
api_key_index: int = DEFAULT_API_KEY_INDEX,
641+
ideal_price=None
642+
):
643+
quote_amount = int(quote_amount * 1e6)
644+
ob_orders = await self.order_api.order_book_orders(market_index, 100)
645+
if ideal_price is None:
646+
logging.debug(
647+
"Doing an API call to get the current ideal price. You can also provide it yourself to avoid this.")
648+
ideal_price = await self.get_best_price(market_index, is_ask, ob_orders=ob_orders)
649+
acceptable_execution_price = round(ideal_price * (1 + max_slippage * (-1 if is_ask else 1)))
650+
651+
potential_execution_price, matched_usd_amount = await self.get_potential_execution_price(
652+
market_index,
653+
quote_amount,
595654
is_ask,
655+
is_amount_base=False,
656+
ob_orders=ob_orders
657+
)
658+
659+
if (is_ask and potential_execution_price < acceptable_execution_price) or (not is_ask and potential_execution_price > acceptable_execution_price):
660+
return None, None, "Excessive slippage"
661+
if matched_usd_amount < quote_amount:
662+
return None, None, "Cannot be sure slippage will be acceptable due to the high size"
663+
664+
# one can choose between int or round depending on purpose, doesn't really much
665+
base_amount = int(quote_amount / potential_execution_price)
666+
return await self.create_order(
667+
market_index,
668+
client_order_index,
669+
base_amount,
670+
price=round(acceptable_execution_price), # just in case, limits size for slippage
671+
is_ask=is_ask,
596672
order_type=self.ORDER_TYPE_MARKET,
597673
time_in_force=self.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
598674
order_expiry=self.DEFAULT_IOC_EXPIRY,
@@ -615,10 +691,9 @@ async def create_market_order_limited_slippage(
615691
ideal_price=None
616692
) -> Union[Tuple[CreateOrder, RespSendTx, None], Tuple[None, None, str]]:
617693
if ideal_price is None:
618-
order_book_orders = await self.order_api.order_book_orders(market_index, 1)
619694
logging.debug(
620695
"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.")
621-
ideal_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", ""))
696+
ideal_price = await self.get_best_price(market_index, is_ask)
622697

623698
acceptable_execution_price = round(ideal_price * (1 + max_slippage * (-1 if is_ask else 1)))
624699
return await self.create_order(
@@ -648,21 +723,17 @@ async def create_market_order_if_slippage(
648723
api_key_index: int = DEFAULT_API_KEY_INDEX,
649724
ideal_price=None
650725
) -> Union[Tuple[CreateOrder, RespSendTx, None], Tuple[None, None, str]]:
651-
order_book_orders = await self.order_api.order_book_orders(market_index, 100)
726+
ob_orders = await self.order_api.order_book_orders(market_index, 100)
652727
if ideal_price is None:
653-
ideal_price = int((order_book_orders.bids[0].price if is_ask else order_book_orders.asks[0].price).replace(".", ""))
654-
655-
matched_usd_amount, matched_size = 0, 0
656-
for order_book_order in (order_book_orders.bids if is_ask else order_book_orders.asks):
657-
if matched_size == base_amount:
658-
break
659-
curr_order_price = int(order_book_order.price.replace(".", ""))
660-
curr_order_size = int(order_book_order.remaining_base_amount.replace(".", ""))
661-
to_be_used_order_size = min(base_amount - matched_size, curr_order_size)
662-
matched_usd_amount += curr_order_price * to_be_used_order_size
663-
matched_size += to_be_used_order_size
728+
ideal_price = await self.get_best_price(market_index, is_ask, ob_orders)
729+
potential_execution_price, matched_size = await self.get_potential_execution_price(
730+
market_index,
731+
base_amount,
732+
is_ask,
733+
is_amount_base=True,
734+
ob_orders=ob_orders
735+
)
664736

665-
potential_execution_price = matched_usd_amount / matched_size
666737
acceptable_execution_price = ideal_price * (1 + max_slippage * (-1 if is_ask else 1))
667738
if (is_ask and potential_execution_price < acceptable_execution_price) or (not is_ask and potential_execution_price > acceptable_execution_price):
668739
return None, None, "Excessive slippage"

0 commit comments

Comments
 (0)