Skip to content

Commit

Permalink
frank/misc for perp filler (#88)
Browse files Browse the repository at this point in the history
* fix: misc math fixes, dlob fixes

* more misc perp filler stuff

* chore: increase usermap timeout

* wip misc fixes

* fix: more misc fixes

* more misc perp filler fixes and additions

* remove debug prints so i don't forget

* fix: misc fixes

* make get perp with lp settle pythonic

* pr comments
  • Loading branch information
soundsonacid authored Jan 31, 2024
1 parent 9e243ea commit 9ab1e01
Show file tree
Hide file tree
Showing 13 changed files with 421 additions and 105 deletions.
1 change: 1 addition & 0 deletions src/driftpy/accounts/get_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ async def get_user_account(
) -> UserAccount:
return (await get_user_account_and_slot(program, user_public_key)).data


async def get_perp_market_account_and_slot(
program: Program, market_index: int
) -> Optional[DataAndSlot[PerpMarketAccount]]:
Expand Down
2 changes: 1 addition & 1 deletion src/driftpy/accounts/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def decode_pyth_price_info(
confidence=convert_pyth_price(pyth_price_info.confidence_interval, scale),
twap=convert_pyth_price(twap, scale),
twap_confidence=convert_pyth_price(twac, scale),
has_sufficient_number_of_datapoints=True,
has_sufficient_number_of_data_points=True,
)


Expand Down
100 changes: 89 additions & 11 deletions src/driftpy/dlob/dlob.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import copy
from typing import Callable, Dict, Generator, List, Optional, Union
from typing import Callable, Dict, Generator, List, Optional, Tuple, Union
from solders.pubkey import Pubkey

from driftpy.constants.numeric_constants import (
Expand Down Expand Up @@ -825,9 +825,8 @@ def find_taking_nodes_to_fill(
taking_order_generator = self.get_taking_asks(
market_index, market_type, slot, oracle_price_data
)
maker_node_generator_fn = lambda: self.get_resting_limit_bids(
market_index, slot, market_type, oracle_price_data
)
maker_node_generator_fn = self.get_resting_limit_bids

does_cross = (
lambda taker_price, maker_price: (
taker_price is None or taker_price <= maker_price
Expand Down Expand Up @@ -872,9 +871,8 @@ def does_cross(price: Optional[int] = None) -> bool:
taking_order_generator = self.get_taking_bids(
market_index, market_type, slot, oracle_price_data
)
maker_node_generator_fn = lambda: self.get_resting_limit_asks(
market_index, slot, market_type, oracle_price_data
)
maker_node_generator_fn = self.get_resting_limit_asks

does_cross = (
lambda taker_price, maker_price: (
taker_price is None or taker_price >= maker_price
Expand Down Expand Up @@ -917,6 +915,86 @@ def does_cross(price: Optional[int] = None) -> bool:

return nodes_to_fill

def determine_maker_and_taker(
self, ask: DLOBNode, bid: DLOBNode
) -> Union[Tuple[DLOBNode, DLOBNode], None]:
ask_slot = ask.order.slot + ask.order.auction_duration
bid_slot = bid.order.slot + bid.order.auction_duration

if bid.order.post_only and ask.order.post_only:
return None
elif bid.order.post_only:
return (ask, bid)
elif ask.order.post_only:
return (bid, ask)
elif ask_slot <= bid_slot:
return (bid, ask)
else:
return (ask, bid)

def find_crossing_resting_limit_orders(
self,
market_index: int,
slot: int,
market_type: MarketType,
oracle_price_data: OraclePriceData,
) -> List[NodeToFill]:
nodes_to_fill: List[NodeToFill] = []

for ask in self.get_resting_limit_asks(
market_index, slot, market_type, oracle_price_data
):
bid_generator = self.get_resting_limit_bids(
market_index, slot, market_type, oracle_price_data
)

for bid in bid_generator:
bid_price = bid.get_price(oracle_price_data, slot)
ask_price = ask.get_price(oracle_price_data, slot)

# don't cross
if bid_price < ask_price:
break

bid_order = bid.order
ask_order = ask.order

# can't match from same user
if bid.user_account == ask.user_account:
break

maker_and_taker = self.determine_maker_and_taker(ask, bid)

# unable to match maker and taker due to post only or slot
if not maker_and_taker:
continue

taker, maker = maker_and_taker

bid_base_remaining = (
bid_order.base_asset_amount - bid_order.base_asset_amount_filled
)
ask_base_remaining = (
ask_order.base_asset_amount - ask_order.base_asset_amount_filled
)

base_filled = min(bid_base_remaining, ask_base_remaining)

new_bid = copy.deepcopy(bid_order)
new_bid.base_asset_amount_filled += base_filled
self.get_list_for_order(new_bid, slot).update(new_bid, bid.user_account)

new_ask = copy.deepcopy(ask_order)
new_ask.base_asset_amount_filled += base_filled
self.get_list_for_order(new_ask, slot).update(new_ask, ask.user_account)

nodes_to_fill.append(NodeToFill(taker, [maker]))

if new_ask.base_asset_amount == new_ask.base_asset_amount_filled:
break

return nodes_to_fill

def find_resting_limit_order_nodes_to_fill(
self,
market_index: int,
Expand Down Expand Up @@ -1014,7 +1092,7 @@ def find_expired_nodes_to_fill(

for ask_generator in ask_generators:
for ask in ask_generator:
if is_order_expired(bid.order, ts, True):
if is_order_expired(ask.order, ts, True):
nodes_to_fill.append(NodeToFill(ask, []))

return nodes_to_fill
Expand All @@ -1040,9 +1118,9 @@ def merge_nodes_to_fill_helper(nodes_to_fill_list):
node_to_fill.node, []
)

if node_to_fill.maker_nodes is not None:
merged_nodes_to_fill[node_signature].maker_nodes.extend(
node_to_fill.maker_nodes
if node_to_fill.maker is not None:
merged_nodes_to_fill[node_signature].maker.extend(
node_to_fill.maker
)

merge_nodes_to_fill_helper(resting_limit_order_nodes_to_fill)
Expand Down
187 changes: 187 additions & 0 deletions src/driftpy/drift_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from solders.instruction import Instruction
from solders.system_program import ID
from solders.sysvar import RENT
from solders.signature import Signature
from solders.address_lookup_table_account import AddressLookupTableAccount
from solana.rpc.async_api import AsyncClient
from solana.rpc.types import TxOpts
Expand Down Expand Up @@ -1613,6 +1614,17 @@ def get_settle_pnl_ix(

return instruction

def get_settle_pnl_ixs(
self, users: dict[Pubkey, UserAccount], market_indexes: list[int]
) -> list[Instruction]:
ixs: list[Instruction] = []
for pubkey, account in users.items():
for market_index in market_indexes:
ix = self.get_settle_pnl_ix(pubkey, account, market_index)
ixs.append(ix)

return ixs

async def resolve_spot_bankruptcy(
self,
user_authority: Pubkey,
Expand Down Expand Up @@ -2040,6 +2052,181 @@ def get_initialize_insurance_fund_stake_ix(
),
)

async def get_fill_perp_order_ix(
self,
user_account_pubkey: Pubkey,
user_account: UserAccount,
order: Order,
maker_info: Optional[Union[MakerInfo, list[MakerInfo]]],
referrer_info: Optional[ReferrerInfo],
) -> Instruction:
user_stats_pubkey = get_user_stats_account_public_key(
self.program.program_id, user_account.authority
)

filler_pubkey = self.get_user_account_public_key()
filler_stats_pubkey = self.get_user_stats_public_key()

market_index = (
order.market_index
if order
else next(
(
order.market_index
for order in user_account.orders
if order.order_id == user_account.next_order_id - 1
),
None,
)
)

maker_info = (
maker_info
if isinstance(maker_info, list)
else [maker_info]
if maker_info
else []
)

user_accounts = [user_account]
for maker in maker_info:
user_accounts.append(maker.maker_user_account)

remaining_accounts = self.get_remaining_accounts(user_accounts, [market_index])

for maker in maker_info:
remaining_accounts.append(
AccountMeta(pubkey=maker.maker, is_writable=True, is_signer=False)
)
remaining_accounts.append(
AccountMeta(pubkey=maker.maker_stats, is_writable=True, is_signer=False)
)

if referrer_info:
referrer_is_maker = any(
maker.maker == referrer_info.referrer for maker in maker_info
)
if not referrer_is_maker:
remaining_accounts.append(
AccountMeta(
pubkey=referrer_info.referrer, is_writable=True, is_signer=False
)
)
remaining_accounts.append(
AccountMeta(
pubkey=referrer_info.referrer_stats,
is_writable=True,
is_signer=False,
)
)

order_id = order.order_id
return self.program.instruction["fill_perp_order"](
order_id,
None,
ctx=Context(
accounts={
"state": self.get_state_public_key(),
"filler": filler_pubkey,
"filler_stats": filler_stats_pubkey,
"user": user_account_pubkey,
"user_stats": user_stats_pubkey,
"authority": self.authority,
},
remaining_accounts=remaining_accounts,
),
)

def get_revert_fill_ix(self):
filler_pubkey = self.get_user_account_public_key()
filler_stats_pubkey = self.get_user_stats_public_key()

return self.program.instruction["revert_fill"](
ctx=Context(
accounts={
"state": self.get_state_public_key(),
"filler": filler_pubkey,
"filler_stats": filler_stats_pubkey,
"authority": self.authority,
}
)
)

def get_trigger_order_ix(
self,
user_account_pubkey: Pubkey,
user_account: UserAccount,
order: Order,
filler_pubkey: Optional[Pubkey] = None,
):
filler = filler_pubkey or self.get_user_account_public_key()

if is_variant(order.market_type, "Perp"):
remaining_accounts = self.get_remaining_accounts(
user_accounts=[user_account],
writable_perp_market_indexes=[order.market_index],
)
else:
remaining_accounts = self.get_remaining_accounts(
user_accounts=[user_account],
writable_spot_market_indexes=[
order.market_index,
QUOTE_SPOT_MARKET_INDEX,
],
)

return self.program.instruction["trigger_order"](
order.order_id,
ctx=Context(
accounts={
"state": self.get_state_public_key(),
"filler": filler,
"user": user_account_pubkey,
"authority": self.authority,
},
remaining_accounts=remaining_accounts,
),
)

async def force_cancel_orders(
self,
user_account_pubkey: Pubkey,
user_account: UserAccount,
filler_pubkey: Optional[Pubkey] = None,
) -> Signature:
tx_sig_and_slot = await self.send_ixs(
self.get_force_cancel_orders_ix(
user_account_pubkey, user_account, filler_pubkey
)
)

return tx_sig_and_slot.tx_sig

def get_force_cancel_orders_ix(
self,
user_account_pubkey: Pubkey,
user_account: UserAccount,
filler_pubkey: Optional[Pubkey] = None,
):
filler = filler_pubkey or self.get_user_account_public_key()

remaining_accounts = self.get_remaining_accounts(
user_accounts=[user_account],
writable_spot_market_indexes=[QUOTE_SPOT_MARKET_INDEX],
)

return self.program.instruction["force_cancel_orders"](
ctx=Context(
accounts={
"state": self.get_state_public_key(),
"filler": filler,
"user": user_account_pubkey,
"authority": self.authority,
},
remaining_accounts=remaining_accounts,
)
)

@deprecated
async def open_position(
self,
Expand Down
Loading

0 comments on commit 9ab1e01

Please sign in to comment.