Added order placement API. (#31)
Added order placement API including finding the open order account logic.
This commit is contained in:
parent
969c14f3e1
commit
e2351f5134
112
src/market.py
112
src/market.py
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from typing import Any, Iterable, List, NamedTuple, Tuple
|
from typing import Any, Iterable, List, NamedTuple
|
||||||
|
|
||||||
from solana.account import Account
|
from solana.account import Account
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
|
@ -12,11 +12,14 @@ from solana.transaction import Transaction, TransactionInstruction
|
||||||
|
|
||||||
from ._layouts.account_flags import ACCOUNT_FLAGS_LAYOUT
|
from ._layouts.account_flags import ACCOUNT_FLAGS_LAYOUT
|
||||||
from ._layouts.market import MARKET_LAYOUT, MINT_LAYOUT
|
from ._layouts.market import MARKET_LAYOUT, MINT_LAYOUT
|
||||||
|
from ._layouts.open_orders import OPEN_ORDERS_LAYOUT
|
||||||
from ._layouts.slab import Slab
|
from ._layouts.slab import Slab
|
||||||
from .enums import Side
|
from .enums import OrderType, Side
|
||||||
from .instructions import DEFAULT_DEX_PROGRAM_ID, CancelOrderParams, MatchOrdersParams, NewOrderParams
|
from .instructions import DEFAULT_DEX_PROGRAM_ID, CancelOrderParams, MatchOrdersParams, NewOrderParams
|
||||||
from .instructions import cancel_order as cancel_order_inst
|
from .instructions import cancel_order as cancel_order_inst
|
||||||
from .instructions import match_orders as match_order_inst
|
from .instructions import match_orders as match_order_inst
|
||||||
|
from .instructions import new_order as new_order_inst
|
||||||
|
from .open_order_account import OpenOrderAccount, make_create_account_instruction
|
||||||
from .queue_ import decode_event_queue, decode_request_queue
|
from .queue_ import decode_event_queue, decode_request_queue
|
||||||
from .utils import load_bytes_data
|
from .utils import load_bytes_data
|
||||||
|
|
||||||
|
@ -91,6 +94,22 @@ class Market:
|
||||||
"""Returns quote mint address."""
|
"""Returns quote mint address."""
|
||||||
return PublicKey(self._decode.quote_mint)
|
return PublicKey(self._decode.quote_mint)
|
||||||
|
|
||||||
|
def base_vault_address(self) -> PublicKey:
|
||||||
|
"""Returns base vault address."""
|
||||||
|
return PublicKey(self._decode.base_vault)
|
||||||
|
|
||||||
|
def quote_vault_address(self) -> PublicKey:
|
||||||
|
"""Returns quote vault address."""
|
||||||
|
return PublicKey(self._decode.quote_vault)
|
||||||
|
|
||||||
|
def request_queue(self) -> PublicKey:
|
||||||
|
"""Returns quote vault address."""
|
||||||
|
return PublicKey(self._decode.request_queue)
|
||||||
|
|
||||||
|
def event_queue(self) -> PublicKey:
|
||||||
|
"""Returns quote vault address."""
|
||||||
|
return PublicKey(self._decode.event_queue)
|
||||||
|
|
||||||
def __base_spl_token_multiplier(self) -> int:
|
def __base_spl_token_multiplier(self) -> int:
|
||||||
return 10 ** self._base_spl_token_decimals
|
return 10 ** self._base_spl_token_decimals
|
||||||
|
|
||||||
|
@ -109,7 +128,12 @@ class Market:
|
||||||
)
|
)
|
||||||
|
|
||||||
def price_number_to_lots(self, price: float) -> int:
|
def price_number_to_lots(self, price: float) -> int:
|
||||||
raise NotImplementedError("price_number_to_lots is not implemented")
|
return int(
|
||||||
|
round(
|
||||||
|
(price * 10 ** self.__quote_spl_token_multiplier() * self._decode.base_lot_size)
|
||||||
|
/ (10 ** self.__base_spl_token_multiplier() * self._decode.quote_lot_size)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def base_size_lots_to_number(self, size: int) -> float:
|
def base_size_lots_to_number(self, size: int) -> float:
|
||||||
return float(size * self._decode.base_lot_size) / self.__base_spl_token_multiplier()
|
return float(size * self._decode.base_lot_size) / self.__base_spl_token_multiplier()
|
||||||
|
@ -183,14 +207,84 @@ class Market:
|
||||||
fee_cost=event.native_fee_or_rebate * (1 if event.event_flags.maker else -1),
|
fee_cost=event.native_fee_or_rebate * (1 if event.event_flags.maker else -1),
|
||||||
)
|
)
|
||||||
|
|
||||||
def place_order(self, order_params: NewOrderParams):
|
def place_order(
|
||||||
pass
|
self,
|
||||||
|
payer: PublicKey,
|
||||||
|
owner: Account,
|
||||||
|
order_type: OrderType,
|
||||||
|
side: Side,
|
||||||
|
limit_price: int,
|
||||||
|
max_quantity: int,
|
||||||
|
client_id: int = 0,
|
||||||
|
):
|
||||||
|
transaction = Transaction()
|
||||||
|
signers: List[Account] = [owner]
|
||||||
|
open_order_accounts = self.find_open_orders_accounts_for_owner(owner.public_key())
|
||||||
|
if not open_order_accounts:
|
||||||
|
new_open_order_account = Account()
|
||||||
|
transaction.add(
|
||||||
|
make_create_account_instruction(
|
||||||
|
owner.public_key(),
|
||||||
|
new_open_order_account.public_key(),
|
||||||
|
Client(self._endpoint).get_minimum_balance_for_rent_exemption(OPEN_ORDERS_LAYOUT.sizeof())[
|
||||||
|
"result"
|
||||||
|
],
|
||||||
|
self._program_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
signers.append(new_open_order_account)
|
||||||
|
|
||||||
def make_place_order_transaction(self, order_params: NewOrderParams) -> Tuple[Transaction, List[PublicKey]]:
|
transaction.add(
|
||||||
pass
|
self.make_place_order_instruction(
|
||||||
|
payer,
|
||||||
|
owner,
|
||||||
|
order_type,
|
||||||
|
side,
|
||||||
|
limit_price,
|
||||||
|
max_quantity,
|
||||||
|
client_id,
|
||||||
|
open_order_accounts[0].address if open_order_accounts else new_open_order_account.public_key(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self._send_transaction(transaction, *signers)
|
||||||
|
|
||||||
def find_open_orders_accounts_for_owner(self, owner_address: PublicKey):
|
def make_place_order_instruction(
|
||||||
pass
|
self,
|
||||||
|
payer: PublicKey,
|
||||||
|
owner: Account,
|
||||||
|
order_type: OrderType,
|
||||||
|
side: Side,
|
||||||
|
limit_price: int,
|
||||||
|
max_quantity: int,
|
||||||
|
client_id: int,
|
||||||
|
open_order_account: PublicKey,
|
||||||
|
) -> TransactionInstruction:
|
||||||
|
if self.base_size_number_to_lots(max_quantity) < 0:
|
||||||
|
raise Exception("Size lot %d is too small." % max_quantity)
|
||||||
|
if self.price_number_to_lots(limit_price) < 0:
|
||||||
|
raise Exception("Price lot %d is too small." % limit_price)
|
||||||
|
return new_order_inst(
|
||||||
|
NewOrderParams(
|
||||||
|
market=self.address(),
|
||||||
|
open_orders=open_order_account,
|
||||||
|
payer=payer,
|
||||||
|
owner=owner.public_key(),
|
||||||
|
request_queue=self.request_queue(),
|
||||||
|
base_vault=self.base_vault_address(),
|
||||||
|
quote_vault=self.quote_vault_address(),
|
||||||
|
side=side,
|
||||||
|
limit_price=limit_price,
|
||||||
|
max_quantity=max_quantity,
|
||||||
|
order_type=order_type,
|
||||||
|
client_id=client_id,
|
||||||
|
program_id=self._program_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def find_open_orders_accounts_for_owner(self, owner_address: PublicKey) -> List[OpenOrderAccount]:
|
||||||
|
return OpenOrderAccount.find_for_market_and_owner(
|
||||||
|
self._endpoint, self.address(), owner_address, self._program_id
|
||||||
|
)
|
||||||
|
|
||||||
def cancel_order_by_client_id(self, owner: str) -> str:
|
def cancel_order_by_client_id(self, owner: str) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
import base64
|
||||||
|
from typing import List, NamedTuple
|
||||||
|
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
from solana.rpc.api import Client
|
from solana.rpc.api import Client, MemcmpOpt
|
||||||
|
from solana.system_program import CreateAccountParams, create_account
|
||||||
|
from solana.transaction import TransactionInstruction
|
||||||
|
|
||||||
from ._layouts.open_orders import OPEN_ORDERS_LAYOUT
|
from ._layouts.open_orders import OPEN_ORDERS_LAYOUT
|
||||||
|
from .instructions import DEFAULT_DEX_PROGRAM_ID
|
||||||
from .utils import load_bytes_data
|
from .utils import load_bytes_data
|
||||||
|
|
||||||
|
|
||||||
|
class ProgramAccount(NamedTuple):
|
||||||
|
public_key: PublicKey
|
||||||
|
data: bytes
|
||||||
|
is_executablable: bool
|
||||||
|
lamports: int
|
||||||
|
owner: PublicKey
|
||||||
|
|
||||||
|
|
||||||
class OpenOrderAccount:
|
class OpenOrderAccount:
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
@ -41,6 +53,9 @@ class OpenOrderAccount:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bytes(address: PublicKey, data_bytes: bytes) -> OpenOrderAccount:
|
def from_bytes(address: PublicKey, data_bytes: bytes) -> OpenOrderAccount:
|
||||||
open_order_decoded = OPEN_ORDERS_LAYOUT.parse(data_bytes)
|
open_order_decoded = OPEN_ORDERS_LAYOUT.parse(data_bytes)
|
||||||
|
if not open_order_decoded.account_flags.open_orders or not open_order_decoded.account_flags.initialized:
|
||||||
|
raise Exception("Not an open order account or not initialized.")
|
||||||
|
|
||||||
return OpenOrderAccount(
|
return OpenOrderAccount(
|
||||||
address=address,
|
address=address,
|
||||||
market=PublicKey(open_order_decoded.market),
|
market=PublicKey(open_order_decoded.market),
|
||||||
|
@ -56,11 +71,56 @@ class OpenOrderAccount:
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_for_market_and_owner(connection: Client, market: PublicKey, owner: PublicKey):
|
def find_for_market_and_owner(
|
||||||
pass
|
endpoint: str, market: PublicKey, owner: PublicKey, program_id: PublicKey
|
||||||
|
) -> List[OpenOrderAccount]:
|
||||||
|
filters = [
|
||||||
|
MemcmpOpt(
|
||||||
|
offset=5 + 8, # 5 bytes of padding, 8 bytes of account flag
|
||||||
|
bytes=str(market),
|
||||||
|
),
|
||||||
|
MemcmpOpt(
|
||||||
|
offset=5 + 8 + 32, # 5 bytes of padding, 8 bytes of account flag, 32 bytes of market public key
|
||||||
|
bytes=str(owner),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
resp = Client(endpoint).get_program_accounts(
|
||||||
|
program_id, encoding="base64", memcmp_opts=filters, data_size=OPEN_ORDERS_LAYOUT.sizeof()
|
||||||
|
)
|
||||||
|
accounts = []
|
||||||
|
for account in resp["result"]:
|
||||||
|
account_details = account["account"]
|
||||||
|
accounts.append(
|
||||||
|
ProgramAccount(
|
||||||
|
public_key=PublicKey(account["pubkey"]),
|
||||||
|
data=base64.decodebytes(account_details["data"][0].encode("ascii")),
|
||||||
|
is_executablable=bool(account_details["executable"]),
|
||||||
|
owner=PublicKey(account_details["owner"]),
|
||||||
|
lamports=int(account_details["lamports"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return [OpenOrderAccount.from_bytes(account.public_key, account.data) for account in accounts]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(endpoint: str, address: str) -> OpenOrderAccount:
|
def load(endpoint: str, address: str) -> OpenOrderAccount:
|
||||||
addr_pub_key = PublicKey(address)
|
addr_pub_key = PublicKey(address)
|
||||||
bytes_data = load_bytes_data(addr_pub_key, endpoint)
|
bytes_data = load_bytes_data(addr_pub_key, endpoint)
|
||||||
return OpenOrderAccount.from_bytes(addr_pub_key, bytes_data)
|
return OpenOrderAccount.from_bytes(addr_pub_key, bytes_data)
|
||||||
|
|
||||||
|
|
||||||
|
def make_create_account_instruction(
|
||||||
|
owner_address: PublicKey,
|
||||||
|
new_account_address: PublicKey,
|
||||||
|
lamports: int,
|
||||||
|
program_id: PublicKey = DEFAULT_DEX_PROGRAM_ID,
|
||||||
|
) -> TransactionInstruction:
|
||||||
|
return create_account(
|
||||||
|
CreateAccountParams(
|
||||||
|
from_pubkey=owner_address,
|
||||||
|
new_account_pubkey=new_account_address,
|
||||||
|
lamports=lamports,
|
||||||
|
space=OPEN_ORDERS_LAYOUT.sizeof(),
|
||||||
|
program_id=program_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -13,12 +13,12 @@ def _decode_queue(header_layout: Any, node_layout: Any, buffer: bytes, history:
|
||||||
for i in range(min(history, alloc_len)):
|
for i in range(min(history, alloc_len)):
|
||||||
node_index = (header.head + header.count + alloc_len - 1 - i) % alloc_len
|
node_index = (header.head + header.count + alloc_len - 1 - i) % alloc_len
|
||||||
offset = header_layout.sizeof() + node_index * node_layout.sizeof()
|
offset = header_layout.sizeof() + node_index * node_layout.sizeof()
|
||||||
nodes.append(node_layout.parse(buffer[offset : offset + node_layout.sizeof()])) # noqa: E203 # noqa: E203
|
nodes.append(node_layout.parse(buffer[offset : offset + node_layout.sizeof()])) # noqa: E203
|
||||||
else:
|
else:
|
||||||
for i in range(header.count):
|
for i in range(header.count):
|
||||||
node_index = (header.head + i) % alloc_len
|
node_index = (header.head + i) % alloc_len
|
||||||
offset = header_layout.sizeof() + node_index * node_layout.sizeof()
|
offset = header_layout.sizeof() + node_index * node_layout.sizeof()
|
||||||
nodes.append(node_layout.parse(buffer[offset : offset + node_layout.sizeof()])) # noqa: E203 # noqa: E203
|
nodes.append(node_layout.parse(buffer[offset : offset + node_layout.sizeof()])) # noqa: E203
|
||||||
return header, nodes
|
return header, nodes
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
c2VydW0FAAAAAAAAADklQaAz6ADd3zS9MiJs/L7mmunSOSZtNjcUVqTd25yTY3mmdwHMScQ6cpqR7agJJnqKc3TW62zaypJkFqLsl+cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAs27bCDgAAAPj///////////////////8HAAAAAAAAAAAAAAAAAAAA///////////0AQAAAAAAAP3/////////COIBAAAAAAD8/////////9IEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcGFkZGluZw==
|
|
@ -1,3 +1,4 @@
|
||||||
_BINARY_DIR_PATH = "tests/binary/"
|
_BINARY_DIR_PATH = "tests/binary/"
|
||||||
ASK_ORDER_BIN_PATH = _BINARY_DIR_PATH + "ask_order_binary.bin"
|
ASK_ORDER_BIN_PATH = _BINARY_DIR_PATH + "ask_order_binary.bin"
|
||||||
EVENT_QUEUE_BIN_PATH = _BINARY_DIR_PATH + "event_queue_binary.bin"
|
EVENT_QUEUE_BIN_PATH = _BINARY_DIR_PATH + "event_queue_binary.bin"
|
||||||
|
OPEN_ORDER_ACCOUNT_BIN_PATH = _BINARY_DIR_PATH + "open_order_account_binary.bin"
|
||||||
|
|
|
@ -4,6 +4,7 @@ from solana.account import Account
|
||||||
from solana.publickey import PublicKey
|
from solana.publickey import PublicKey
|
||||||
from solana.rpc.api import Client
|
from solana.rpc.api import Client
|
||||||
|
|
||||||
|
from src.enums import OrderType, Side
|
||||||
from src.market import Market
|
from src.market import Market
|
||||||
|
|
||||||
from .utils import confirm_transaction
|
from .utils import confirm_transaction
|
||||||
|
@ -77,3 +78,57 @@ def test_match_order(bootstrapped_market: Market, stubbed_payer: Account, http_c
|
||||||
# There should be no ask order.
|
# There should be no ask order.
|
||||||
asks = bootstrapped_market.load_asks()
|
asks = bootstrapped_market.load_asks()
|
||||||
assert sum(1 for _ in asks) == 0
|
assert sum(1 for _ in asks) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
def test_new_order(
|
||||||
|
bootstrapped_market: Market,
|
||||||
|
stubbed_payer: Account,
|
||||||
|
http_client: Client,
|
||||||
|
stubbed_quote_wallet: Account,
|
||||||
|
stubbed_base_wallet: Account,
|
||||||
|
):
|
||||||
|
initial_request_len = len(bootstrapped_market.load_request_queue())
|
||||||
|
sig = bootstrapped_market.place_order(
|
||||||
|
payer=stubbed_quote_wallet.public_key(),
|
||||||
|
owner=stubbed_payer,
|
||||||
|
side=Side.Buy,
|
||||||
|
order_type=OrderType.Limit,
|
||||||
|
limit_price=1000,
|
||||||
|
max_quantity=3000,
|
||||||
|
)
|
||||||
|
confirm_transaction(http_client, sig)
|
||||||
|
|
||||||
|
request_queue = bootstrapped_market.load_request_queue()
|
||||||
|
# 0 request after matching.
|
||||||
|
assert len(request_queue) == initial_request_len + 1
|
||||||
|
|
||||||
|
# There should be no bid order.
|
||||||
|
bids = bootstrapped_market.load_bids()
|
||||||
|
assert sum(1 for _ in bids) == 0
|
||||||
|
|
||||||
|
# There should be no ask order.
|
||||||
|
asks = bootstrapped_market.load_asks()
|
||||||
|
assert sum(1 for _ in asks) == 0
|
||||||
|
|
||||||
|
sig = bootstrapped_market.place_order(
|
||||||
|
payer=stubbed_base_wallet.public_key(),
|
||||||
|
owner=stubbed_payer,
|
||||||
|
side=Side.Sell,
|
||||||
|
order_type=OrderType.Limit,
|
||||||
|
limit_price=1500,
|
||||||
|
max_quantity=3000,
|
||||||
|
)
|
||||||
|
confirm_transaction(http_client, sig)
|
||||||
|
|
||||||
|
# The two order shouldn't get executed since there is a price difference of 1
|
||||||
|
sig = bootstrapped_market.match_orders(stubbed_payer, 2)
|
||||||
|
confirm_transaction(http_client, sig)
|
||||||
|
|
||||||
|
# There should be 1 bid order that we sent earlier.
|
||||||
|
bids = bootstrapped_market.load_bids()
|
||||||
|
assert sum(1 for _ in bids) == 1
|
||||||
|
|
||||||
|
# There should be 1 ask order that we sent earlier.
|
||||||
|
asks = bootstrapped_market.load_asks()
|
||||||
|
assert sum(1 for _ in asks) == 1
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from solana.publickey import PublicKey
|
||||||
|
|
||||||
|
from src.open_order_account import OPEN_ORDERS_LAYOUT, OpenOrderAccount
|
||||||
|
|
||||||
|
from .binary_file_path import OPEN_ORDER_ACCOUNT_BIN_PATH
|
||||||
|
|
||||||
|
|
||||||
|
def test_decode_open_order_account_layout():
|
||||||
|
"""Test decode event queue."""
|
||||||
|
with open(OPEN_ORDER_ACCOUNT_BIN_PATH, "r") as input_file:
|
||||||
|
base64_res = input_file.read()
|
||||||
|
data = base64.decodebytes(base64_res.encode("ascii"))
|
||||||
|
open_order_account = OPEN_ORDERS_LAYOUT.parse(data)
|
||||||
|
assert open_order_account.account_flags.open_orders
|
||||||
|
assert open_order_account.account_flags.initialized
|
||||||
|
assert PublicKey(open_order_account.market) == PublicKey("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
|
||||||
|
assert PublicKey(open_order_account.owner) == PublicKey("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
|
||||||
|
# if there is no order the byte returned here will be all 0. In this case we have three orders.
|
||||||
|
assert len([order for order in open_order_account.orders if int.from_bytes(order, "little") != 0]) == 3
|
||||||
|
# the first three order are bid order
|
||||||
|
assert int.from_bytes(open_order_account.is_bid_bits, "little") == 0b111
|
||||||
|
|
||||||
|
|
||||||
|
def test_decode_open_order_account():
|
||||||
|
"""Test decode event queue."""
|
||||||
|
with open(OPEN_ORDER_ACCOUNT_BIN_PATH, "r") as input_file:
|
||||||
|
base64_res = input_file.read()
|
||||||
|
data = base64.decodebytes(base64_res.encode("ascii"))
|
||||||
|
open_order_account = OpenOrderAccount.from_bytes(PublicKey(1), data)
|
||||||
|
assert open_order_account.market == PublicKey("4r5Bw3HxmxAzPQ2ATUvgF2nFe3B6G1Z2Nq2Nwu77wWc2")
|
||||||
|
assert open_order_account.owner == PublicKey("7hJx7QMiVfjZSSADQ18oNKzqifJPMu18djYLkh4aYh5Q")
|
||||||
|
assert len([order for order in open_order_account.orders if order != 0]) == 3
|
||||||
|
# the first three order are bid order
|
||||||
|
assert open_order_account.is_bid_bits == 0b111
|
Loading…
Reference in New Issue