Improved Serum fill event details and output - now calculates price and quantity with proper decimals.

This commit is contained in:
Geoff Taylor 2022-03-25 09:58:13 +00:00
parent dbd76cb5f5
commit 510c53035d
6 changed files with 113 additions and 39 deletions

View File

@ -111,7 +111,11 @@ def build_account_info_converter(
account_info, __FakePerpMarketDetails() account_info, __FakePerpMarketDetails()
) )
elif account_type_upper == "SERUMEVENTQUEUE": 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": elif account_type_upper == "SERUMEVENTS":
serum_splitter: typing.Optional[UnseenSerumEventChangesTracker] = None serum_splitter: typing.Optional[UnseenSerumEventChangesTracker] = None
@ -121,12 +125,18 @@ def build_account_info_converter(
nonlocal serum_splitter nonlocal serum_splitter
if serum_splitter is None: if serum_splitter is None:
initial_serum_event_queue: SerumEventQueue = SerumEventQueue.parse( 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( serum_splitter = UnseenSerumEventChangesTracker(
initial_serum_event_queue 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 serum_splitter.unseen(serum_event_queue)
return __split_serum_events return __split_serum_events

View File

@ -216,7 +216,9 @@ class SerumPollingModelStateBuilder(PollingModelStateBuilder):
account_infos[6], account_infos[7] 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) price: mango.Price = self.oracle.fetch_price(context)
@ -353,7 +355,9 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
account_infos[3], account_infos[4] 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) price: mango.Price = self.oracle.fetch_price(context)

View File

@ -24,6 +24,7 @@ from .addressableaccount import AddressableAccount
from .context import Context from .context import Context
from .layouts import layouts from .layouts import layouts
from .observables import Disposable from .observables import Disposable
from .tokens import Token
from .version import Version from .version import Version
from .websocketsubscription import ( from .websocketsubscription import (
WebSocketAccountSubscription, WebSocketAccountSubscription,
@ -77,6 +78,8 @@ class SerumEvent:
self, self,
version: Version, version: Version,
event_flags: SerumEventFlags, event_flags: SerumEventFlags,
base: Token,
quote: Token,
open_order_slot: Decimal, open_order_slot: Decimal,
fee_tier: Decimal, fee_tier: Decimal,
native_quantity_released: Decimal, native_quantity_released: Decimal,
@ -88,6 +91,8 @@ class SerumEvent:
) -> None: ) -> None:
self.version: Version = version self.version: Version = version
self.event_flags: SerumEventFlags = event_flags self.event_flags: SerumEventFlags = event_flags
self.base: Token = base
self.quote: Token = quote
self.open_order_slot: Decimal = open_order_slot self.open_order_slot: Decimal = open_order_slot
self.fee_tier: Decimal = fee_tier self.fee_tier: Decimal = fee_tier
self.native_quantity_released: Decimal = native_quantity_released self.native_quantity_released: Decimal = native_quantity_released
@ -102,12 +107,32 @@ class SerumEvent:
def accounts_to_crank(self) -> typing.Sequence[PublicKey]: def accounts_to_crank(self) -> typing.Sequence[PublicKey]:
return [self.public_key] 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 @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) event_flags: SerumEventFlags = SerumEventFlags.from_layout(layout.event_flags)
return SerumEvent( return SerumEvent(
Version.UNSPECIFIED, Version.UNSPECIFIED,
event_flags, event_flags,
base,
quote,
layout.open_order_slot, layout.open_order_slot,
layout.fee_tier, layout.fee_tier,
layout.native_quantity_released, layout.native_quantity_released,
@ -119,16 +144,17 @@ class SerumEvent:
) )
def __str__(self) -> str: def __str__(self) -> str:
return f"""« SerumEvent {self.event_flags} return f"""« SerumEvent {self.quantity:,.8f} {self.base.symbol} @ {self.price:,.8f} {self.quote.symbol}
Original Index: {self.original_index} {self.event_flags}
Order ID: {self.order_id} ID: {self.order_id} / {self.client_order_id}
Client Order ID: {self.client_order_id} Index: {self.original_index}
Public Key: {self.public_key} Owner: {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}
Fee Tier: {self.fee_tier} 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: def __repr__(self) -> str:
@ -150,6 +176,8 @@ class SerumEventQueue(AddressableAccount):
self, self,
account_info: AccountInfo, account_info: AccountInfo,
version: Version, version: Version,
base: Token,
quote: Token,
account_flags: AccountFlags, account_flags: AccountFlags,
head: Decimal, head: Decimal,
count: Decimal, count: Decimal,
@ -160,6 +188,9 @@ class SerumEventQueue(AddressableAccount):
super().__init__(account_info) super().__init__(account_info)
self.version: Version = version self.version: Version = version
self.base: Token = base
self.quote: Token = quote
self.account_flags: AccountFlags = account_flags self.account_flags: AccountFlags = account_flags
self.head: Decimal = head self.head: Decimal = head
self.count: Decimal = count self.count: Decimal = count
@ -169,20 +200,23 @@ class SerumEventQueue(AddressableAccount):
@staticmethod @staticmethod
def from_layout( def from_layout(
layout: typing.Any, account_info: AccountInfo, version: Version layout: typing.Any,
account_info: AccountInfo,
version: Version,
base: Token,
quote: Token,
) -> "SerumEventQueue": ) -> "SerumEventQueue":
account_flags: AccountFlags = AccountFlags.from_layout(layout.account_flags) account_flags: AccountFlags = AccountFlags.from_layout(layout.account_flags)
head: Decimal = layout.head head: Decimal = layout.head
count: Decimal = layout.count count: Decimal = layout.count
seq_num: Decimal = layout.next_seq_num seq_num: Decimal = layout.next_seq_num
events: typing.List[SerumEvent] = list(
map( events: typing.List[SerumEvent] = []
SerumEvent.from_layout, for index, evt in enumerate(layout.events):
[evt for evt in layout.events if evt is not None], if evt is not None:
) event = SerumEvent.from_layout(evt, base, quote)
) event.original_index = Decimal(index)
for index, event in enumerate(events): events += [event]
event.original_index = Decimal(index)
# Events are stored in a ringbuffer, and the oldest is overwritten when a new event arrives. # 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 # 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( return SerumEventQueue(
account_info, account_info,
version, version,
base,
quote,
account_flags, account_flags,
head, head,
count, count,
@ -206,17 +242,23 @@ class SerumEventQueue(AddressableAccount):
) )
@staticmethod @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. # 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) 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 @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) account_info = AccountInfo.load(context, address)
if account_info is None: if account_info is None:
raise Exception(f"SerumEventQueue account not found at address '{address}'") raise Exception(f"SerumEventQueue account not found at address '{address}'")
return SerumEventQueue.parse(account_info) return SerumEventQueue.parse(account_info, base, quote)
@property @property
def accounts_to_crank(self) -> typing.Sequence[PublicKey]: def accounts_to_crank(self) -> typing.Sequence[PublicKey]:
@ -257,7 +299,11 @@ class SerumEventQueue(AddressableAccount):
callback: typing.Callable[["SerumEventQueue"], None], callback: typing.Callable[["SerumEventQueue"], None],
) -> Disposable: ) -> Disposable:
subscription = WebSocketAccountSubscription( subscription = WebSocketAccountSubscription(
context, self.address, SerumEventQueue.parse context,
self.address,
lambda account_info: SerumEventQueue.parse(
account_info, self.base, self.quote
),
) )
websocketmanager.add(subscription) websocketmanager.add(subscription)
subscription.publisher.subscribe(on_next=callback) # type: ignore[call-arg] subscription.publisher.subscribe(on_next=callback) # type: ignore[call-arg]

View File

@ -123,7 +123,7 @@ class SerumMarket(LoadedMarket):
def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]: def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]:
event_queue: SerumEventQueue = SerumEventQueue.load( event_queue: SerumEventQueue = SerumEventQueue.load(
context, self.event_queue_address context, self.event_queue_address, self.base, self.quote
) )
return event_queue.unprocessed_events return event_queue.unprocessed_events
@ -157,14 +157,18 @@ class SerumMarket(LoadedMarket):
disposer = Disposable() disposer = Disposable()
event_queue_address = self.event_queue_address event_queue_address = self.event_queue_address
initial: SerumEventQueue = SerumEventQueue.load( initial: SerumEventQueue = SerumEventQueue.load(
context, self.event_queue_address context, self.event_queue_address, self.base, self.quote
) )
splitter: UnseenSerumEventChangesTracker = UnseenSerumEventChangesTracker( splitter: UnseenSerumEventChangesTracker = UnseenSerumEventChangesTracker(
initial initial
) )
event_queue_subscription = WebSocketAccountSubscription( 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) disposer.add_disposable(event_queue_subscription)

View File

@ -126,7 +126,7 @@ class SpotMarket(LoadedMarket):
def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]: def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]:
event_queue: SerumEventQueue = SerumEventQueue.load( event_queue: SerumEventQueue = SerumEventQueue.load(
context, self.event_queue_address context, self.event_queue_address, self.base, self.quote
) )
return event_queue.unprocessed_events return event_queue.unprocessed_events
@ -162,14 +162,18 @@ class SpotMarket(LoadedMarket):
disposer = Disposable() disposer = Disposable()
event_queue_address = self.event_queue_address event_queue_address = self.event_queue_address
initial: SerumEventQueue = SerumEventQueue.load( initial: SerumEventQueue = SerumEventQueue.load(
context, self.event_queue_address context, self.event_queue_address, self.base, self.quote
) )
splitter: UnseenSerumEventChangesTracker = UnseenSerumEventChangesTracker( splitter: UnseenSerumEventChangesTracker = UnseenSerumEventChangesTracker(
initial initial
) )
event_queue_subscription = WebSocketAccountSubscription( 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) disposer.add_disposable(event_queue_subscription)

View File

@ -418,12 +418,14 @@ def build_serum_event_queue_watcher(
serum_market: SerumMarket, serum_market: SerumMarket,
) -> Watcher[EventQueue]: ) -> Watcher[EventQueue]:
initial: EventQueue = SerumEventQueue.load( 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]( subscription = WebSocketAccountSubscription[EventQueue](
context, context,
serum_market.event_queue_address, 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) manager.add(subscription)
latest_observer = LatestItemObserverSubscriber[EventQueue](initial) latest_observer = LatestItemObserverSubscriber[EventQueue](initial)
@ -438,11 +440,15 @@ def build_spot_event_queue_watcher(
health_check: HealthCheck, health_check: HealthCheck,
spot_market: SpotMarket, spot_market: SpotMarket,
) -> Watcher[EventQueue]: ) -> 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]( subscription = WebSocketAccountSubscription[EventQueue](
context, context,
spot_market.event_queue_address, 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) manager.add(subscription)
latest_observer = LatestItemObserverSubscriber[EventQueue](initial) latest_observer = LatestItemObserverSubscriber[EventQueue](initial)