Improved Serum fill event details and output - now calculates price and quantity with proper decimals.
This commit is contained in:
parent
dbd76cb5f5
commit
510c53035d
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue