Added a streaming watcher for some account types.
This commit is contained in:
parent
f8e8480085
commit
817b31f8ea
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env pyston3
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import rx
|
||||
import rx.operators
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from decimal import Decimal
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
sys.path.insert(0, os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..")))
|
||||
import mango # nopep8
|
||||
import mango.layouts # nopep8
|
||||
import mango.marketmaking.fixedratiosdesiredordersbuilder # nopep8
|
||||
import mango.marketmaking.marketmaker # nopep8
|
||||
import mango.marketmaking.modelstate # nopep8
|
||||
import mango.marketmaking.toleranceorderreconciler # nopep8
|
||||
|
||||
parser = argparse.ArgumentParser(description="Shows the on-chain data of a particular account.")
|
||||
mango.Context.add_command_line_parameters(parser)
|
||||
mango.Wallet.add_command_line_parameters(parser)
|
||||
parser.add_argument("--address", type=PublicKey, required=True, help="Address of the Solana account to watch")
|
||||
parser.add_argument("--account-type", type=str, required=True,
|
||||
help="Underlying object type of the data in the AccountInfo")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
logging.warning(mango.WARNING_DISCLAIMER_TEXT)
|
||||
|
||||
context = mango.Context.from_command_line_parameters(args)
|
||||
|
||||
disposer = mango.DisposePropagator()
|
||||
manager = mango.WebSocketSubscriptionManager()
|
||||
disposer.add_disposable(manager)
|
||||
|
||||
|
||||
def build_converter(context: mango.Context, account_type):
|
||||
account_type_upper = account_type.upper()
|
||||
if account_type_upper == "ACCOUNTINFO":
|
||||
return lambda account_info: account_info
|
||||
elif account_type_upper == "GROUP":
|
||||
return lambda account_info: mango.Group.parse(context, account_info)
|
||||
elif account_type_upper == "ACCOUNT":
|
||||
account_info = mango.AccountInfo.load(context, args.address)
|
||||
if account_info is None:
|
||||
raise Exception(f"No account at {args.address}.")
|
||||
layout_account = mango.layouts.MANGO_ACCOUNT.parse(account_info.data)
|
||||
group_address = layout_account.group
|
||||
group: mango.Group = mango.Group.load(context, group_address)
|
||||
return lambda account_info: mango.Account.parse(context, account_info, group)
|
||||
elif account_type_upper == "OPENORDERS":
|
||||
return lambda account_info: mango.OpenOrders.parse(account_info, Decimal(6), Decimal(6))
|
||||
elif account_type_upper == "PERPEVENTQUEUE":
|
||||
return lambda account_info: mango.PerpEventQueue.parse(account_info)
|
||||
elif account_type_upper == "PERPEVENTS":
|
||||
class EventSplitter:
|
||||
def __init__(self):
|
||||
self.seq_num = Decimal(0)
|
||||
|
||||
def split(self, item: mango.PerpEventQueue) -> None:
|
||||
events = item.events[int(self.seq_num):int(item.sequence_number)]
|
||||
self.seq_num = item.sequence_number
|
||||
return events
|
||||
|
||||
splitter: EventSplitter = EventSplitter()
|
||||
|
||||
def _split_events(account_info: mango.AccountInfo):
|
||||
perp_event_queue = mango.PerpEventQueue.parse(account_info)
|
||||
return splitter.split(perp_event_queue)
|
||||
|
||||
return _split_events
|
||||
raise Exception(f"Could not find AccountInfo converter for type {account_type}.")
|
||||
|
||||
|
||||
subscription = mango.WebSocketSubscription[mango.AccountInfo](
|
||||
context, args.address, lambda account_info: account_info)
|
||||
manager.add(subscription)
|
||||
|
||||
subscription.publisher.pipe(
|
||||
rx.operators.flat_map(build_converter(context, args.account_type))
|
||||
).subscribe(mango.PrintingObserverSubscriber(False))
|
||||
|
||||
websocket_url = context.cluster_url.replace("https", "ws", 1)
|
||||
ws: mango.ReconnectingWebsocket = mango.ReconnectingWebsocket(websocket_url, manager.open_handler, manager.on_item)
|
||||
ws.ping_interval = 10
|
||||
ws.open()
|
||||
|
||||
# Wait - don't exit. Exiting will be handled by signals/interrupts.
|
||||
waiter = threading.Event()
|
||||
try:
|
||||
waiter.wait()
|
||||
except:
|
||||
pass
|
||||
|
||||
logging.info("Shutting down...")
|
||||
ws.close()
|
||||
disposer.dispose()
|
||||
logging.info("Shutdown complete.")
|
|
@ -31,6 +31,7 @@ from .orders import Order, OrderType, Side
|
|||
from .ownedtokenvalue import OwnedTokenValue
|
||||
from .oracle import OracleSource, Price, Oracle, OracleProvider
|
||||
from .oraclefactory import create_oracle_provider
|
||||
from .perpeventqueue import Event, PerpEventQueue
|
||||
from .perpmarket import PerpMarket
|
||||
from .perpmarketinfo import PerpMarketInfo
|
||||
from .perpmarketinstructionbuilder import PerpMarketInstructionBuilder
|
||||
|
|
|
@ -942,7 +942,7 @@ OUT_EVENT = construct.Struct(
|
|||
assert OUT_EVENT.sizeof() == _EVENT_SIZE
|
||||
|
||||
UNKNOWN_EVENT = construct.Struct(
|
||||
"flags" / construct.Bytes(1),
|
||||
"event_type" / construct.Bytes(1),
|
||||
construct.Padding(7),
|
||||
"owner" / PublicKeyAdapter(),
|
||||
construct.Padding(_EVENT_SIZE - 40)
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
# # ⚠ 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 datetime import datetime
|
||||
from decimal import Decimal
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .accountinfo import AccountInfo
|
||||
from .addressableaccount import AddressableAccount
|
||||
from .context import Context
|
||||
from .layouts import layouts
|
||||
from .metadata import Metadata
|
||||
from .orders import Side
|
||||
from .version import Version
|
||||
|
||||
|
||||
# # 🥭 PerpEventQueue class
|
||||
#
|
||||
# `PerpEventQueue` stores details of how to reach `PerpEventQueue```.
|
||||
#
|
||||
|
||||
|
||||
class Event:
|
||||
def __init__(self, event_type: int):
|
||||
self.event_type: int = event_type
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
||||
|
||||
class FillEvent(Event):
|
||||
def __init__(self, event_type: int, timestamp: datetime, side: Side, price: Decimal, quantity: Decimal,
|
||||
best_initial: Decimal, maker_slot: Decimal, maker_out: bool,
|
||||
maker: PublicKey, maker_order_id: Decimal, maker_client_order_id: Decimal,
|
||||
taker: PublicKey, taker_order_id: Decimal, taker_client_order_id: Decimal):
|
||||
super().__init__(event_type)
|
||||
self.timestamp: datetime = timestamp
|
||||
self.side: Side = side
|
||||
self.price: Decimal = price
|
||||
self.quantity: Decimal = quantity
|
||||
|
||||
self.best_initial: Decimal = best_initial
|
||||
self.maker_slot: Decimal = maker_slot
|
||||
self.maker_out: bool = maker_out
|
||||
|
||||
self.maker: PublicKey = maker
|
||||
self.maker_order_id: Decimal = maker_order_id
|
||||
self.maker_client_order_id: Decimal = maker_client_order_id
|
||||
|
||||
self.taker: PublicKey = taker
|
||||
self.taker_order_id: Decimal = taker_order_id
|
||||
self.taker_client_order_id: Decimal = taker_client_order_id
|
||||
|
||||
def __str__(self):
|
||||
return f"""« 𝙵𝚒𝚕𝚕𝙴𝚟𝚎𝚗𝚝 [{self.timestamp}] {self.side} {self.quantity} at {self.price}
|
||||
Maker: {self.maker}, {self.maker_order_id} / {self.maker_client_order_id}
|
||||
Taker: {self.taker}, {self.taker_order_id} / {self.taker_client_order_id}
|
||||
Best Initial: {self.best_initial}
|
||||
Maker Slot: {self.maker_slot}
|
||||
Maker Out: {self.maker_out}
|
||||
»"""
|
||||
|
||||
|
||||
class OutEvent(Event):
|
||||
def __init__(self, event_type: int, owner: PublicKey, side: Side, quantity: Decimal, slot: Decimal):
|
||||
super().__init__(event_type)
|
||||
self.owner: PublicKey = owner
|
||||
self.side: Side = side
|
||||
self.slot: Decimal = slot
|
||||
self.quantity: Decimal = quantity
|
||||
|
||||
def __str__(self):
|
||||
return f"""« 𝙾𝚞𝚝𝙴𝚟𝚎𝚗𝚝 [{self.owner}] {self.side} {self.quantity}, slot: {self.slot} »"""
|
||||
|
||||
|
||||
class UnknownEvent(Event):
|
||||
def __init__(self, event_type: int, owner: PublicKey):
|
||||
super().__init__(event_type)
|
||||
self.owner: PublicKey = owner
|
||||
|
||||
def __str__(self):
|
||||
return f"« 𝚄𝚗𝚔𝚗𝚘𝚠𝚗𝙴𝚟𝚎𝚗𝚝 [{self.owner}] »"
|
||||
|
||||
|
||||
def event_builder(event_layout) -> Event:
|
||||
if event_layout.event_type == b'\x00':
|
||||
return FillEvent(event_layout.event_type, event_layout.timestamp, event_layout.side,
|
||||
event_layout.price, event_layout.quantity, event_layout.best_initial,
|
||||
event_layout.maker_slot, event_layout.maker_out, event_layout.maker,
|
||||
event_layout.maker_order_id, event_layout.maker_client_order_id,
|
||||
event_layout.taker, event_layout.taker_order_id,
|
||||
event_layout.taker_client_order_id)
|
||||
elif event_layout.event_type == b'\x01':
|
||||
return OutEvent(event_layout.event_type, event_layout.owner, event_layout.side, event_layout.quantity, event_layout.slot)
|
||||
else:
|
||||
return UnknownEvent(event_layout.event_type, event_layout.owner)
|
||||
|
||||
|
||||
class PerpEventQueue(AddressableAccount):
|
||||
def __init__(self, account_info: AccountInfo, version: Version, meta_data: Metadata,
|
||||
head: Decimal, count: Decimal, sequence_number: Decimal, events: typing.Sequence[Event]):
|
||||
super().__init__(account_info)
|
||||
self.version: Version = version
|
||||
|
||||
self.meta_data: Metadata = meta_data
|
||||
self.head: Decimal = head
|
||||
self.count: Decimal = count
|
||||
self.sequence_number: Decimal = sequence_number
|
||||
self.events: typing.Sequence[Event] = events
|
||||
|
||||
@staticmethod
|
||||
def from_layout(layout: layouts.PERP_EVENT_QUEUE, account_info: AccountInfo, version: Version) -> "PerpEventQueue":
|
||||
meta_data: Metadata = layout.meta_data
|
||||
head: Decimal = layout.head
|
||||
count: Decimal = layout.count
|
||||
seq_num: Decimal = layout.seq_num
|
||||
events: typing.Sequence[Event] = list(map(event_builder, layout.events))
|
||||
|
||||
return PerpEventQueue(account_info, version, meta_data, head, count, seq_num, events)
|
||||
|
||||
@staticmethod
|
||||
def parse(account_info: AccountInfo) -> "PerpEventQueue":
|
||||
# Data length isn't fixed so can't check we get the right value the way we normally do.
|
||||
layout = layouts.PERP_EVENT_QUEUE.parse(account_info.data)
|
||||
return PerpEventQueue.from_layout(layout, account_info, Version.V1)
|
||||
|
||||
@staticmethod
|
||||
def load(context: Context, address: PublicKey) -> "PerpEventQueue":
|
||||
account_info = AccountInfo.load(context, address)
|
||||
if account_info is None:
|
||||
raise Exception(f"PerpEventQueue account not found at address '{address}'")
|
||||
return PerpEventQueue.parse(account_info)
|
||||
|
||||
def __str__(self):
|
||||
events = "\n ".join([f"{event}".replace("\n", "\n ")
|
||||
for event in self.events if event is not None]) or "None"
|
||||
return f"""« 𝙿𝚎𝚛𝚙𝙴𝚟𝚎𝚗𝚝𝚀𝚞𝚎𝚞𝚎 [{self.version}] {self.address}
|
||||
{self.meta_data}
|
||||
Head: {self.head}
|
||||
Count: {self.count}
|
||||
Sequence Number: {self.sequence_number}
|
||||
Events:
|
||||
{events}
|
||||
»"""
|
|
@ -29,7 +29,7 @@ from .version import Version
|
|||
|
||||
# # 🥭 NodeBank class
|
||||
#
|
||||
# `NodeBank` stores details of how to reach `NodeBank```.
|
||||
# `NodeBank` stores details of deposits/borrows and vault.
|
||||
#
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue