Improved Group and Account output. Tied OpenOrders and PerpOpenOrders together with a PlacedOrdersContainer protocol.

This commit is contained in:
Geoff Taylor 2021-07-29 16:19:52 +01:00
parent 5b6d5f2291
commit e721d6c8e8
11 changed files with 233 additions and 94 deletions

View File

@ -82,7 +82,7 @@ def build_latest_account_observer(context: mango.Context, account: mango.Account
return account_subscription, latest_account_observer
def build_latest_spot_open_orders_observer(manager: mango.WebSocketSubscriptionManager, disposer: mango.DisposePropagator, spot_market: mango.SpotMarket) -> mango.LatestItemObserverSubscriber[mango.OpenOrders]:
def build_latest_spot_open_orders_observer(manager: mango.WebSocketSubscriptionManager, disposer: mango.DisposePropagator, spot_market: mango.SpotMarket) -> mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer]:
market_index = group.find_spot_market_index(spot_market.address)
spot_open_orders_address = account.spot_open_orders[market_index]
if spot_open_orders_address is None:
@ -93,13 +93,14 @@ def build_latest_spot_open_orders_observer(manager: mango.WebSocketSubscriptionM
manager.add(spot_open_orders_subscription)
initial_spot_open_orders = mango.OpenOrders.load(
context, spot_open_orders_address, spot_market.base.decimals, spot_market.quote.decimals)
latest_open_orders_observer = mango.LatestItemObserverSubscriber(initial_spot_open_orders)
latest_open_orders_observer = mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer](
initial_spot_open_orders)
spot_open_orders_subscription.publisher.subscribe(latest_open_orders_observer)
add_file_health("open_orders_subscription", spot_open_orders_subscription.publisher, disposer)
return latest_open_orders_observer
def build_latest_serum_open_orders_observer(manager: mango.WebSocketSubscriptionManager, disposer: mango.DisposePropagator, serum_market: mango.SerumMarket, context: mango.Context, wallet: mango.Wallet) -> mango.LatestItemObserverSubscriber[mango.OpenOrders]:
def build_latest_serum_open_orders_observer(manager: mango.WebSocketSubscriptionManager, disposer: mango.DisposePropagator, serum_market: mango.SerumMarket, context: mango.Context, wallet: mango.Wallet) -> mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer]:
all_open_orders = mango.OpenOrders.load_for_market_and_owner(
context, serum_market.address, wallet.address, context.dex_program_id, serum_market.base.decimals, serum_market.quote.decimals)
if len(all_open_orders) > 0:
@ -124,24 +125,19 @@ def build_latest_serum_open_orders_observer(manager: mango.WebSocketSubscription
manager.add(serum_open_orders_subscription)
latest_serum_open_orders_observer = mango.LatestItemObserverSubscriber(initial_serum_open_orders)
latest_serum_open_orders_observer = mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer](
initial_serum_open_orders)
serum_open_orders_subscription.publisher.subscribe(latest_serum_open_orders_observer)
add_file_health("open_orders_subscription", serum_open_orders_subscription.publisher, disposer)
return latest_serum_open_orders_observer
def build_latest_perp_open_orders_observer(disposer: mango.DisposePropagator, perp_market: mango.PerpMarket, account: mango.Account, account_subscription: mango.WebSocketSubscription[mango.Account]) -> mango.LatestItemObserverSubscriber[mango.OpenOrders]:
def build_latest_perp_open_orders_observer(disposer: mango.DisposePropagator, perp_market: mango.PerpMarket, account: mango.Account, account_subscription: mango.WebSocketSubscription[mango.Account]) -> mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer]:
index = group.find_perp_market_index(perp_market.address)
def _build_open_orders_from_account(account: mango.Account) -> mango.OpenOrders:
perp_account = account.perp_accounts[index]
perp_open_orders = perp_account.open_orders
return mango.OpenOrders.from_perps_account_layout(context, account, perp_market, perp_open_orders)
initial_open_orders = _build_open_orders_from_account(account)
latest_open_orders_observer = mango.LatestItemObserverSubscriber(initial_open_orders)
initial_open_orders = account.perp_accounts[index].open_orders
latest_open_orders_observer = mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer](initial_open_orders)
account_subscription.publisher.subscribe(
on_next=lambda account: latest_open_orders_observer.on_next(_build_open_orders_from_account(account)))
on_next=lambda updated_account: latest_open_orders_observer.on_next(updated_account.perp_accounts[index].open_orders))
add_file_health("open_orders_subscription", account_subscription.publisher, disposer)
return latest_open_orders_observer

View File

@ -30,18 +30,21 @@ from .marketoperations import MarketOperations, NullMarketOperations
from .metadata import Metadata
from .notification import NotificationTarget, TelegramNotificationTarget, DiscordNotificationTarget, MailjetNotificationTarget, CsvFileNotificationTarget, FilteringNotificationTarget, NotificationHandler, parse_subscription_target
from .observables import DisposePropagator, NullObserverSubscriber, PrintingObserverSubscriber, TimestampedPrintingObserverSubscriber, CollectingObserverSubscriber, LatestItemObserverSubscriber, CaptureFirstItem, FunctionObserver, create_backpressure_skipping_observer, debug_print_item, log_subscription_error, observable_pipeline_error_reporter, EventSource, FileToucherObserver
from .openorders import OpenOrders, PlacedOrder
from .openorders import OpenOrders
from .oracle import OracleSource, Price, Oracle, OracleProvider, SupportedOracleFeature
from .orderbookside import OrderBookSide
from .orders import Order, OrderType, Side
from .ownedtokenvalue import OwnedTokenValue
from .oraclefactory import create_oracle_provider
from .perpaccount import PerpAccount
from .perpeventqueue import PerpEvent, PerpFillEvent, PerpOutEvent, PerpUnknownEvent, PerpEventQueue, UnseenPerpEventChangesTracker
from .perpmarket import PerpMarket, PerpMarketStub
from .perpmarketdetails import PerpMarketDetails
from .perpmarketinfo import PerpMarketInfo
from .perpmarketinstructionbuilder import PerpMarketInstructionBuilder
from .perpmarketoperations import PerpMarketOperations
from .perpopenorders import PerpOpenOrders
from .placedorder import PlacedOrder, PlacedOrdersContainer
from .reconnectingwebsocket import ReconnectingWebsocket
from .retrier import RetryWithPauses, retry_context
from .rootbank import NodeBank, RootBank

View File

@ -26,6 +26,7 @@ from .encoding import encode_key
from .group import Group
from .layouts import layouts
from .metadata import Metadata
from .perpaccount import PerpAccount
from .tokenvalue import TokenValue
from .version import Version
@ -40,7 +41,7 @@ class Account(AddressableAccount):
meta_data: Metadata, group: Group, owner: PublicKey, in_margin_basket: typing.Sequence[Decimal],
deposits: typing.Sequence[typing.Optional[TokenValue]], borrows: typing.Sequence[typing.Optional[TokenValue]],
net_assets: typing.Sequence[typing.Optional[TokenValue]], spot_open_orders: typing.Sequence[PublicKey],
perp_accounts: typing.Sequence[typing.Any], msrm_amount: Decimal, being_liquidated: bool,
perp_accounts: typing.Sequence[PerpAccount], msrm_amount: Decimal, being_liquidated: bool,
is_bankrupt: bool):
super().__init__(account_info)
self.version: Version = version
@ -53,7 +54,7 @@ class Account(AddressableAccount):
self.borrows: typing.Sequence[typing.Optional[TokenValue]] = borrows
self.net_assets: typing.Sequence[typing.Optional[TokenValue]] = net_assets
self.spot_open_orders: typing.Sequence[PublicKey] = spot_open_orders
self.perp_accounts: typing.Sequence[layouts.PERP_ACCOUNT] = perp_accounts
self.perp_accounts: typing.Sequence[PerpAccount] = perp_accounts
self.msrm_amount: Decimal = msrm_amount
self.being_liquidated: bool = being_liquidated
self.is_bankrupt: bool = is_bankrupt
@ -81,10 +82,10 @@ class Account(AddressableAccount):
net_assets += [None]
spot_open_orders: typing.Sequence[PublicKey] = layout.spot_open_orders
perp_accounts: typing.Sequence[typing.Any] = layout.perp_accounts
perp_accounts: typing.Sequence[PerpAccount] = list(map(PerpAccount.from_layout, layout.perp_accounts))
msrm_amount: Decimal = layout.msrm_amount
being_liquidated: bool = layout.being_liquidated
is_bankrupt: bool = layout.is_bankrupt
being_liquidated: bool = bool(layout.being_liquidated)
is_bankrupt: bool = bool(layout.is_bankrupt)
return Account(account_info, version, meta_data, group, owner, in_margin_basket, deposits, borrows, net_assets, spot_open_orders, perp_accounts, msrm_amount, being_liquidated, is_bankrupt)
@ -96,7 +97,7 @@ class Account(AddressableAccount):
f"Account data length ({len(data)}) does not match expected size ({layouts.MANGO_ACCOUNT.sizeof()})")
layout = layouts.MANGO_ACCOUNT.parse(data)
return Account.from_layout(layout, account_info, Version.V1, group)
return Account.from_layout(layout, account_info, Version.V3, group)
@staticmethod
def load(context: Context, address: PublicKey, group: Group) -> "Account":
@ -138,24 +139,34 @@ class Account(AddressableAccount):
return Account.load_all_for_owner(context, owner, group)[0]
def __str__(self):
deposits = "\n ".join(
[f"{deposit}" for deposit in self.deposits if deposit is not None and deposit.value != Decimal(0)] or ["None"])
borrows = "\n ".join(
[f"{borrow}" for borrow in self.borrows if borrow is not None and borrow.value != Decimal(0)] or ["None"])
net_assets = "\n ".join(
[f"{net_asset}" for net_asset in self.net_assets if net_asset is not None and net_asset.value != Decimal(0)] or ["None"])
def _render_list(items, stub):
rendered = []
for index, item in enumerate(items):
rendered += [f"{index}: {(item or stub)}".replace("\n", "\n ")]
return rendered
available_deposit_count = len([deposit for deposit in self.deposits if deposit is not None])
deposits = "\n ".join(_render_list(self.deposits, "« No Deposit »"))
available_borrow_count = len([borrow for borrow in self.borrows if borrow is not None])
borrows = "\n ".join(_render_list(self.borrows, "« No Borrow »"))
net_assets = "\n ".join(_render_list(self.net_assets, "« No Net Assets »"))
spot_open_orders = ", ".join([f"{oo}" for oo in self.spot_open_orders if oo is not None])
perp_accounts = ", ".join(
[f"{perp}".replace("\n", "\n ") for perp in self.perp_accounts if perp.open_orders.free_slot_bits != 0xFFFFFFFF])
indices_in_basket = []
for index, value in enumerate(self.in_margin_basket):
if value != 0:
indices_in_basket += [index]
in_margin_basket = ", ".join([f"{self.group.tokens[index].token.symbol}" for index in indices_in_basket])
return f"""« 𝙰𝚌𝚌𝚘𝚞𝚗𝚝 {self.version} [{self.address}]
{self.meta_data}
Bankrupt? {self.is_bankrupt}
Being Liquidated? {self.being_liquidated}
Owner: {self.owner}
Group: « 𝙶𝚛𝚘𝚞𝚙 '{self.group.name}' {self.group.version} [{self.group.address}] »
Deposits:
In Basket: {in_margin_basket}
Bankrupt? {self.is_bankrupt}
Being Liquidated? {self.being_liquidated}
Deposits [{available_deposit_count} available]:
{deposits}
Borrows:
Borrows [{available_borrow_count} available]:
{borrows}
Net Assets:
{net_assets}

View File

@ -113,7 +113,7 @@ class Group(AddressableAccount):
layout = layouts.GROUP.parse(data)
name = context.lookup_group_name(account_info.address)
return Group.from_layout(context, layout, name, account_info, Version.V1, context.token_lookup, context.market_lookup)
return Group.from_layout(context, layout, name, account_info, Version.V3, context.token_lookup, context.market_lookup)
@staticmethod
def load(context: Context, address: typing.Optional[PublicKey] = None) -> "Group":
@ -156,9 +156,6 @@ class Group(AddressableAccount):
return balances
def __str__(self):
print("Token 14", self.tokens[14])
print("Token 14 is None", self.tokens[14] is None)
def _render_list(items, stub):
rendered = []
for index, item in enumerate(items):

View File

@ -646,10 +646,6 @@ def build_spot_place_order_instructions(context: Context, wallet: Wallet, group:
relevant_open_orders += [AccountMeta(is_signer=False, is_writable=False,
pubkey=oo_address or SYSTEM_PROGRAM_ADDRESS)]
print("root_bank.address", root_bank.address)
print("node_bank.address", node_bank.address)
print("open_orders_address", open_orders_address)
print("account.address", account.address)
fee_discount_address_meta: typing.List[AccountMeta] = []
if fee_discount_address is not None:
fee_discount_address_meta = [AccountMeta(is_signer=False, is_writable=False, pubkey=fee_discount_address)]

View File

@ -30,7 +30,7 @@ class ModelState:
group_watcher: mango.LatestItemObserverSubscriber[mango.Group],
price_watcher: mango.LatestItemObserverSubscriber[mango.Price],
perp_market_watcher: typing.Optional[mango.LatestItemObserverSubscriber[mango.PerpMarketDetails]],
open_orders_watcher: mango.LatestItemObserverSubscriber[mango.OpenOrders]
placed_orders_container_watcher: mango.LatestItemObserverSubscriber[mango.PlacedOrdersContainer]
):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.market: mango.Market = market
@ -39,7 +39,8 @@ class ModelState:
self.price_watcher: mango.LatestItemObserverSubscriber[mango.Price] = price_watcher
self.perp_market_watcher: typing.Optional[mango.LatestItemObserverSubscriber[mango.PerpMarketDetails]
] = perp_market_watcher
self.open_orders_watcher: mango.LatestItemObserverSubscriber[mango.OpenOrders] = open_orders_watcher
self.placed_orders_container_watcher: mango.LatestItemObserverSubscriber[
mango.PlacedOrdersContainer] = placed_orders_container_watcher
@property
def group(self) -> mango.Group:
@ -55,17 +56,13 @@ class ModelState:
return None
return self.perp_market_watcher.latest
@property
def open_orders(self) -> mango.OpenOrders:
return self.open_orders_watcher.latest
@property
def price(self) -> mango.Price:
return self.price_watcher.latest
@property
def placed_orders(self) -> typing.Sequence[mango.PlacedOrder]:
return self.open_orders.placed_orders
def existing_orders(self) -> typing.Sequence[mango.PlacedOrder]:
return self.placed_orders_container_watcher.latest.placed_orders
def __str__(self) -> str:
return f"""« 𝙼𝚘𝚍𝚎𝚕𝚂𝚝𝚊𝚝𝚎 for market '{self.market.symbol}' »"""

View File

@ -37,17 +37,17 @@ class OrderTracker:
def existing_orders(self, model_state: ModelState) -> typing.Sequence[mango.Order]:
live_orders: typing.List[mango.Order] = []
for placed_order in model_state.placed_orders:
details = self._find_tracked(placed_order.client_id)
for existing_order in model_state.existing_orders:
details = self._find_tracked(existing_order.client_id)
if details is None:
self.logger.warning(f"Could not find existing order with client ID {placed_order.client_id}")
self.logger.warning(f"Could not find existing order with client ID {existing_order.client_id}")
# Return a stub order so that the Reconciler has the chance to cancel it.
stub = mango.Order.from_ids(placed_order.id, placed_order.client_id, placed_order.side)
stub = mango.Order.from_ids(existing_order.id, existing_order.client_id, existing_order.side)
live_orders += [stub]
else:
if details.id != placed_order.id:
if details.id != existing_order.id:
self.tracked.remove(details)
details = details.with_id(placed_order.id)
details = details.with_id(existing_order.id)
self.tracked += [details]
live_orders += [details]

View File

@ -21,7 +21,6 @@ from pyserum.open_orders_account import OpenOrdersAccount
from solana.publickey import PublicKey
from solana.rpc.types import MemcmpOpts
from .account import Account
from .accountflags import AccountFlags
from .accountinfo import AccountInfo
from .addressableaccount import AddressableAccount
@ -29,36 +28,9 @@ from .context import Context
from .encoding import encode_key
from .group import Group
from .layouts import layouts
from .orders import Side
from .perpmarket import PerpMarket
from .placedorder import PlacedOrder
from .version import Version
class PlacedOrder(typing.NamedTuple):
id: int
client_id: int
side: Side
@staticmethod
def build_from_open_orders_data(free_slot_bits: Decimal, is_bid_bits: Decimal, order_ids: typing.Sequence[Decimal], client_order_ids: typing.Sequence[Decimal]):
int_free_slot_bits = int(free_slot_bits)
int_is_bid_bits = int(is_bid_bits)
placed_orders: typing.List[PlacedOrder] = []
for index in range(len(order_ids)):
if not (int_free_slot_bits & (1 << index)):
order_id = int(order_ids[index])
client_id = int(client_order_ids[index])
side = Side.BUY if int_is_bid_bits & (1 << index) else Side.SELL
placed_orders += [PlacedOrder(id=order_id, client_id=client_id, side=side)]
return placed_orders
def __repr__(self) -> str:
return f"{self}"
def __str__(self) -> str:
return f"« 𝙿𝚕𝚊𝚌𝚎𝚍𝙾𝚛𝚍𝚎𝚛 {self.side} [{self.id}] {self.client_id} »"
# # 🥭 OpenOrders class
#
@ -107,18 +79,6 @@ class OpenOrders(AddressableAccount):
layout.owner, base_token_free, base_token_total, quote_token_free,
quote_token_total, placed_orders, layout.referrer_rebate_accrued)
@staticmethod
def from_perps_account_layout(context: Context, account: Account, perp_market: PerpMarket, perp_open_orders: layouts.PERP_OPEN_ORDERS) -> "OpenOrders":
account_flags = AccountFlags(Version.UNSPECIFIED, True, False,
True, False, False, False, False, False)
placed_orders = PlacedOrder.build_from_open_orders_data(
perp_open_orders.free_slot_bits, perp_open_orders.is_bid_bits, perp_open_orders.orders, perp_open_orders.client_order_ids)
open_orders = OpenOrders(account.account_info, Version.V1, context.program_id,
account_flags, perp_market.address, account.address,
Decimal(0), Decimal(0), Decimal(0), Decimal(0),
placed_orders, Decimal(0))
return open_orders
@staticmethod
def parse(account_info: AccountInfo, base_decimals: Decimal, quote_decimals: Decimal) -> "OpenOrders":
data = account_info.data

59
mango/perpaccount.py Normal file
View File

@ -0,0 +1,59 @@
# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Mango Markets](https://mango.markets/) support is available at:
# [Docs](https://docs.mango.markets/)
# [Discord](https://discord.gg/67jySBhxrg)
# [Twitter](https://twitter.com/mangomarkets)
# [Github](https://github.com/blockworks-foundation)
# [Email](mailto:hello@blockworks.foundation)
from decimal import Decimal
from .layouts import layouts
from .perpopenorders import PerpOpenOrders
# # 🥭 PerpAccount class
#
# Perp accounts aren't directly addressable. They exist as a sub-object of a full Mango `Account` object.
#
class PerpAccount:
def __init__(self, base_position: Decimal, quote_position: Decimal, long_settled_funding: Decimal,
short_settled_funding: Decimal, mngo_accrued: Decimal, open_orders: PerpOpenOrders):
self.base_position: Decimal = base_position
self.quote_position: Decimal = quote_position
self.long_settled_funding: Decimal = long_settled_funding
self.short_settled_funding: Decimal = short_settled_funding
self.mngo_accrued: Decimal = mngo_accrued
self.open_orders: PerpOpenOrders = open_orders
@staticmethod
def from_layout(layout: layouts.PERP_ACCOUNT) -> "PerpAccount":
base_position: Decimal = layout.base_position
quote_position: Decimal = layout.quote_position
long_settled_funding: Decimal = layout.long_settled_funding
short_settled_funding: Decimal = layout.short_settled_funding
mngo_accrued: Decimal = layout.mngo_accrued
open_orders: PerpOpenOrders = PerpOpenOrders.from_layout(layout.open_orders)
return PerpAccount(base_position, quote_position, long_settled_funding, short_settled_funding, mngo_accrued, open_orders)
def __str__(self) -> str:
open_orders = f"{self.open_orders}".replace("\n", "\n ")
return f"""« 𝙿𝚎𝚛𝚙𝙰𝚌𝚌𝚘𝚞𝚗𝚝
Base Position: {self.base_position}
Quote Position: {self.quote_position}
Long Settled Funding: {self.long_settled_funding}
Short Settled Funding: {self.short_settled_funding}
MNGO Accrued: {self.mngo_accrued}
OpenOrders:
{open_orders}
»"""

55
mango/perpopenorders.py Normal file
View File

@ -0,0 +1,55 @@
# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Mango Markets](https://mango.markets/) support is available at:
# [Docs](https://docs.mango.markets/)
# [Discord](https://discord.gg/67jySBhxrg)
# [Twitter](https://twitter.com/mangomarkets)
# [Github](https://github.com/blockworks-foundation)
# [Email](mailto:hello@blockworks.foundation)
import typing
from decimal import Decimal
from .layouts import layouts
from .openorders import PlacedOrder
# # 🥭 PerpOpenOrders class
#
class PerpOpenOrders:
def __init__(self, bids_quantity: Decimal, asks_quantity: Decimal, free_slot_bits: Decimal,
is_bid_bits: Decimal, placed_orders: typing.Sequence[PlacedOrder]):
self.bids_quantity: Decimal = bids_quantity
self.asks_quantity: Decimal = asks_quantity
self.free_slot_bits: Decimal = free_slot_bits
self.is_bid_bits: Decimal = is_bid_bits
self.placed_orders: typing.Sequence[PlacedOrder] = placed_orders
@staticmethod
def from_layout(layout: layouts.PERP_OPEN_ORDERS) -> "PerpOpenOrders":
bids_quantity: Decimal = layout.bids_quantity
asks_quantity: Decimal = layout.asks_quantity
free_slot_bits: Decimal = layout.free_slot_bits
is_bid_bits: Decimal = layout.is_bid_bits
placed_orders = PlacedOrder.build_from_open_orders_data(
layout.free_slot_bits, layout.is_bid_bits, layout.orders, layout.client_order_ids)
return PerpOpenOrders(bids_quantity, asks_quantity, free_slot_bits, is_bid_bits, placed_orders)
def __str__(self) -> str:
placed_orders = "\n ".join(map(str, self.placed_orders)) or "None"
return f"""« 𝙿𝚎𝚛𝚙𝙾𝚙𝚎𝚗𝙾𝚛𝚍𝚎𝚛𝚜
Bids Quantity: {self.bids_quantity}
Asks Quantity: {self.asks_quantity}
Orders:
{placed_orders}
»"""

65
mango/placedorder.py Normal file
View File

@ -0,0 +1,65 @@
# # ⚠ Warning
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# [🥭 Mango Markets](https://mango.markets/) support is available at:
# [Docs](https://docs.mango.markets/)
# [Discord](https://discord.gg/67jySBhxrg)
# [Twitter](https://twitter.com/mangomarkets)
# [Github](https://github.com/blockworks-foundation)
# [Email](mailto:hello@blockworks.foundation)
import typing
from decimal import Decimal
from .orders import Side
# # 🥭 PlacedOrder tuple
#
# A `PlacedOrder` is a representation of all the data available from an Open Orders account pertaining to a
# particular order.
#
# The information is usually split across 3 collections - 'is bid', 'orders' and 'client ID's. That can be a
# little awkward to use, so this tuple packages it all together, per order.
#
class PlacedOrder(typing.NamedTuple):
id: int
client_id: int
side: Side
@staticmethod
def build_from_open_orders_data(free_slot_bits: Decimal, is_bid_bits: Decimal, order_ids: typing.Sequence[Decimal], client_order_ids: typing.Sequence[Decimal]):
int_free_slot_bits = int(free_slot_bits)
int_is_bid_bits = int(is_bid_bits)
placed_orders: typing.List[PlacedOrder] = []
for index in range(len(order_ids)):
if not (int_free_slot_bits & (1 << index)):
order_id = int(order_ids[index])
client_id = int(client_order_ids[index])
side = Side.BUY if int_is_bid_bits & (1 << index) else Side.SELL
placed_orders += [PlacedOrder(id=order_id, client_id=client_id, side=side)]
return placed_orders
def __repr__(self) -> str:
return f"{self}"
def __str__(self) -> str:
return f"« 𝙿𝚕𝚊𝚌𝚎𝚍𝙾𝚛𝚍𝚎𝚛 {self.side} [{self.id}] {self.client_id} »"
# # 🥭 PlacedOrdersContainer protocol
#
# The `PlacedOrdersContainer` protocol exposes commonality between the regular Serum `OpenOrders` class and the
# internally-different `PerpOpenOrders` class. Both have their own `placed_orders` member, but are otherwise
# different enough that a common abstract base class would be a bit kludgy.
#
class PlacedOrdersContainer(typing.Protocol):
placed_orders: typing.Sequence[PlacedOrder]