From 510c53035d2c1e20e49f5b3429b1416c81718db2 Mon Sep 17 00:00:00 2001 From: Geoff Taylor Date: Fri, 25 Mar 2022 09:58:13 +0000 Subject: [PATCH] Improved Serum fill event details and output - now calculates price and quantity with proper decimals. --- mango/accountinfoconverter.py | 16 ++++- mango/marketmaking/modelstatebuilder.py | 8 ++- mango/serumeventqueue.py | 94 ++++++++++++++++++------- mango/serummarket.py | 10 ++- mango/spotmarket.py | 10 ++- mango/watchers.py | 14 ++-- 6 files changed, 113 insertions(+), 39 deletions(-) diff --git a/mango/accountinfoconverter.py b/mango/accountinfoconverter.py index b4c1089..79d52dd 100644 --- a/mango/accountinfoconverter.py +++ b/mango/accountinfoconverter.py @@ -111,7 +111,11 @@ def build_account_info_converter( account_info, __FakePerpMarketDetails() ) elif account_type_upper == "SERUMEVENTQUEUE": - return lambda account_info: SerumEventQueue.parse(account_info) + return lambda account_info: SerumEventQueue.parse( + account_info, + Token("UNKNOWNBASE", "Unknown Base", Decimal(0), PublicKey(0)), + Token("UNKNOWNQUOTE", "Unknown Quote", Decimal(0), PublicKey(0)), + ) elif account_type_upper == "SERUMEVENTS": serum_splitter: typing.Optional[UnseenSerumEventChangesTracker] = None @@ -121,12 +125,18 @@ def build_account_info_converter( nonlocal serum_splitter if serum_splitter is None: initial_serum_event_queue: SerumEventQueue = SerumEventQueue.parse( - account_info + account_info, + Token("UNKNOWNBASE", "Unknown Base", Decimal(0), PublicKey(0)), + Token("UNKNOWNQUOTE", "Unknown Quote", Decimal(0), PublicKey(0)), ) serum_splitter = UnseenSerumEventChangesTracker( initial_serum_event_queue ) - serum_event_queue: SerumEventQueue = SerumEventQueue.parse(account_info) + serum_event_queue: SerumEventQueue = SerumEventQueue.parse( + account_info, + Token("UNKNOWNBASE", "Unknown Base", Decimal(0), PublicKey(0)), + Token("UNKNOWNQUOTE", "Unknown Quote", Decimal(0), PublicKey(0)), + ) return serum_splitter.unseen(serum_event_queue) return __split_serum_events diff --git a/mango/marketmaking/modelstatebuilder.py b/mango/marketmaking/modelstatebuilder.py index c7d1fb6..468273e 100644 --- a/mango/marketmaking/modelstatebuilder.py +++ b/mango/marketmaking/modelstatebuilder.py @@ -216,7 +216,9 @@ class SerumPollingModelStateBuilder(PollingModelStateBuilder): account_infos[6], account_infos[7] ) - event_queue: mango.EventQueue = mango.SerumEventQueue.parse(account_infos[8]) + event_queue: mango.EventQueue = mango.SerumEventQueue.parse( + account_infos[8], self.base_token, self.quote_token + ) price: mango.Price = self.oracle.fetch_price(context) @@ -353,7 +355,9 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder): account_infos[3], account_infos[4] ) - event_queue: mango.EventQueue = mango.SerumEventQueue.parse(account_infos[5]) + event_queue: mango.EventQueue = mango.SerumEventQueue.parse( + account_infos[5], self.market.base, self.market.quote + ) price: mango.Price = self.oracle.fetch_price(context) diff --git a/mango/serumeventqueue.py b/mango/serumeventqueue.py index 8fb2ed7..17e677b 100644 --- a/mango/serumeventqueue.py +++ b/mango/serumeventqueue.py @@ -24,6 +24,7 @@ from .addressableaccount import AddressableAccount from .context import Context from .layouts import layouts from .observables import Disposable +from .tokens import Token from .version import Version from .websocketsubscription import ( WebSocketAccountSubscription, @@ -77,6 +78,8 @@ class SerumEvent: self, version: Version, event_flags: SerumEventFlags, + base: Token, + quote: Token, open_order_slot: Decimal, fee_tier: Decimal, native_quantity_released: Decimal, @@ -88,6 +91,8 @@ class SerumEvent: ) -> None: self.version: Version = version self.event_flags: SerumEventFlags = event_flags + self.base: Token = base + self.quote: Token = quote self.open_order_slot: Decimal = open_order_slot self.fee_tier: Decimal = fee_tier self.native_quantity_released: Decimal = native_quantity_released @@ -102,12 +107,32 @@ class SerumEvent: def accounts_to_crank(self) -> typing.Sequence[PublicKey]: return [self.public_key] + @property + def price(self) -> Decimal: + if self.event_flags.bid: + return ( + self.native_quantity_paid + self.native_fee_or_rebate + ) / self.native_quantity_released + else: + return ( + self.native_quantity_released + self.native_fee_or_rebate + ) / self.native_quantity_paid + + @property + def quantity(self) -> Decimal: + if self.event_flags.bid: + return self.quote.shift_to_decimals(self.native_quantity_released) + else: + return self.quote.shift_to_decimals(self.native_quantity_paid) + @staticmethod - def from_layout(layout: typing.Any) -> "SerumEvent": + def from_layout(layout: typing.Any, base: Token, quote: Token) -> "SerumEvent": event_flags: SerumEventFlags = SerumEventFlags.from_layout(layout.event_flags) return SerumEvent( Version.UNSPECIFIED, event_flags, + base, + quote, layout.open_order_slot, layout.fee_tier, layout.native_quantity_released, @@ -119,16 +144,17 @@ class SerumEvent: ) def __str__(self) -> str: - return f"""« SerumEvent {self.event_flags} - Original Index: {self.original_index} - Order ID: {self.order_id} - Client Order ID: {self.client_order_id} - Public Key: {self.public_key} - OpenOrder Slot: {self.open_order_slot} - Native Quantity Released: {self.native_quantity_released} - Native Quantity Paid: {self.native_quantity_paid} - Native Fee Or Rebate: {self.native_fee_or_rebate} + return f"""« SerumEvent {self.quantity:,.8f} {self.base.symbol} @ {self.price:,.8f} {self.quote.symbol} + {self.event_flags} + ID: {self.order_id} / {self.client_order_id} + Index: {self.original_index} + Owner: {self.public_key} Fee Tier: {self.fee_tier} + OpenOrder Slot: {self.open_order_slot} + Native + Quantity Released: {self.native_quantity_released} + Quantity Paid: {self.native_quantity_paid} + Fee Or Rebate: {self.native_fee_or_rebate} »""" def __repr__(self) -> str: @@ -150,6 +176,8 @@ class SerumEventQueue(AddressableAccount): self, account_info: AccountInfo, version: Version, + base: Token, + quote: Token, account_flags: AccountFlags, head: Decimal, count: Decimal, @@ -160,6 +188,9 @@ class SerumEventQueue(AddressableAccount): super().__init__(account_info) self.version: Version = version + self.base: Token = base + self.quote: Token = quote + self.account_flags: AccountFlags = account_flags self.head: Decimal = head self.count: Decimal = count @@ -169,20 +200,23 @@ class SerumEventQueue(AddressableAccount): @staticmethod def from_layout( - layout: typing.Any, account_info: AccountInfo, version: Version + layout: typing.Any, + account_info: AccountInfo, + version: Version, + base: Token, + quote: Token, ) -> "SerumEventQueue": account_flags: AccountFlags = AccountFlags.from_layout(layout.account_flags) head: Decimal = layout.head count: Decimal = layout.count seq_num: Decimal = layout.next_seq_num - events: typing.List[SerumEvent] = list( - map( - SerumEvent.from_layout, - [evt for evt in layout.events if evt is not None], - ) - ) - for index, event in enumerate(events): - event.original_index = Decimal(index) + + events: typing.List[SerumEvent] = [] + for index, evt in enumerate(layout.events): + if evt is not None: + event = SerumEvent.from_layout(evt, base, quote) + event.original_index = Decimal(index) + events += [event] # Events are stored in a ringbuffer, and the oldest is overwritten when a new event arrives. # Make it a bit simpler to use by splitting at the insertion point and swapping the two pieces @@ -197,6 +231,8 @@ class SerumEventQueue(AddressableAccount): return SerumEventQueue( account_info, version, + base, + quote, account_flags, head, count, @@ -206,17 +242,23 @@ class SerumEventQueue(AddressableAccount): ) @staticmethod - def parse(account_info: AccountInfo) -> "SerumEventQueue": + def parse( + account_info: AccountInfo, base: Token, quote: Token + ) -> "SerumEventQueue": # Data length isn't fixed so can't check we get the right value the way we normally do. layout = layouts.SERUM_EVENT_QUEUE.parse(account_info.data) - return SerumEventQueue.from_layout(layout, account_info, Version.V1) + return SerumEventQueue.from_layout( + layout, account_info, Version.V1, base, quote + ) @staticmethod - def load(context: Context, address: PublicKey) -> "SerumEventQueue": + def load( + context: Context, address: PublicKey, base: Token, quote: Token + ) -> "SerumEventQueue": account_info = AccountInfo.load(context, address) if account_info is None: raise Exception(f"SerumEventQueue account not found at address '{address}'") - return SerumEventQueue.parse(account_info) + return SerumEventQueue.parse(account_info, base, quote) @property def accounts_to_crank(self) -> typing.Sequence[PublicKey]: @@ -257,7 +299,11 @@ class SerumEventQueue(AddressableAccount): callback: typing.Callable[["SerumEventQueue"], None], ) -> Disposable: subscription = WebSocketAccountSubscription( - context, self.address, SerumEventQueue.parse + context, + self.address, + lambda account_info: SerumEventQueue.parse( + account_info, self.base, self.quote + ), ) websocketmanager.add(subscription) subscription.publisher.subscribe(on_next=callback) # type: ignore[call-arg] diff --git a/mango/serummarket.py b/mango/serummarket.py index 61ef9fb..0537ff4 100644 --- a/mango/serummarket.py +++ b/mango/serummarket.py @@ -123,7 +123,7 @@ class SerumMarket(LoadedMarket): def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]: event_queue: SerumEventQueue = SerumEventQueue.load( - context, self.event_queue_address + context, self.event_queue_address, self.base, self.quote ) return event_queue.unprocessed_events @@ -157,14 +157,18 @@ class SerumMarket(LoadedMarket): disposer = Disposable() event_queue_address = self.event_queue_address initial: SerumEventQueue = SerumEventQueue.load( - context, self.event_queue_address + context, self.event_queue_address, self.base, self.quote ) splitter: UnseenSerumEventChangesTracker = UnseenSerumEventChangesTracker( initial ) event_queue_subscription = WebSocketAccountSubscription( - context, event_queue_address, SerumEventQueue.parse + context, + event_queue_address, + lambda account_info: SerumEventQueue.parse( + account_info, self.base, self.quote + ), ) disposer.add_disposable(event_queue_subscription) diff --git a/mango/spotmarket.py b/mango/spotmarket.py index 22b9322..da6781a 100644 --- a/mango/spotmarket.py +++ b/mango/spotmarket.py @@ -126,7 +126,7 @@ class SpotMarket(LoadedMarket): def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]: event_queue: SerumEventQueue = SerumEventQueue.load( - context, self.event_queue_address + context, self.event_queue_address, self.base, self.quote ) return event_queue.unprocessed_events @@ -162,14 +162,18 @@ class SpotMarket(LoadedMarket): disposer = Disposable() event_queue_address = self.event_queue_address initial: SerumEventQueue = SerumEventQueue.load( - context, self.event_queue_address + context, self.event_queue_address, self.base, self.quote ) splitter: UnseenSerumEventChangesTracker = UnseenSerumEventChangesTracker( initial ) event_queue_subscription = WebSocketAccountSubscription( - context, event_queue_address, SerumEventQueue.parse + context, + event_queue_address, + lambda account_info: SerumEventQueue.parse( + account_info, self.base, self.quote + ), ) disposer.add_disposable(event_queue_subscription) diff --git a/mango/watchers.py b/mango/watchers.py index ecc391a..d9e754d 100644 --- a/mango/watchers.py +++ b/mango/watchers.py @@ -418,12 +418,14 @@ def build_serum_event_queue_watcher( serum_market: SerumMarket, ) -> Watcher[EventQueue]: initial: EventQueue = SerumEventQueue.load( - context, serum_market.event_queue_address + context, serum_market.event_queue_address, serum_market.base, serum_market.quote ) subscription = WebSocketAccountSubscription[EventQueue]( context, serum_market.event_queue_address, - lambda account_info: SerumEventQueue.parse(account_info), + lambda account_info: SerumEventQueue.parse( + account_info, serum_market.base, serum_market.quote + ), ) manager.add(subscription) latest_observer = LatestItemObserverSubscriber[EventQueue](initial) @@ -438,11 +440,15 @@ def build_spot_event_queue_watcher( health_check: HealthCheck, spot_market: SpotMarket, ) -> Watcher[EventQueue]: - initial: EventQueue = SerumEventQueue.load(context, spot_market.event_queue_address) + initial: EventQueue = SerumEventQueue.load( + context, spot_market.event_queue_address, spot_market.base, spot_market.quote + ) subscription = WebSocketAccountSubscription[EventQueue]( context, spot_market.event_queue_address, - lambda account_info: SerumEventQueue.parse(account_info), + lambda account_info: SerumEventQueue.parse( + account_info, spot_market.base, spot_market.quote + ), ) manager.add(subscription) latest_observer = LatestItemObserverSubscriber[EventQueue](initial)