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()
)
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

View File

@ -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)

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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)