mango-explorer/mango/marketmaking/modelstatebuilderfactory.py

371 lines
12 KiB
Python

# # ⚠ 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 enum
import mango
import typing
from solana.publickey import PublicKey
from ..constants import SYSTEM_PROGRAM_ADDRESS
from ..modelstate import ModelState
from .modelstatebuilder import (
ModelStateBuilder,
WebsocketModelStateBuilder,
SerumPollingModelStateBuilder,
SpotPollingModelStateBuilder,
PerpPollingModelStateBuilder,
)
class ModelUpdateMode(enum.Enum):
# We use strings here so that argparse can work with these as parameters.
WEBSOCKET = "WEBSOCKET"
POLL = "POLL"
def __str__(self) -> str:
return self.value
def __repr__(self) -> str:
return f"{self}"
# # 🥭 ModelStateBuilder class
#
# Base class for building a `ModelState` through polling or websockets.
#
def model_state_builder_factory(
mode: ModelUpdateMode,
context: mango.Context,
disposer: mango.Disposable,
websocket_manager: mango.WebSocketSubscriptionManager,
health_check: mango.HealthCheck,
wallet: mango.Wallet,
group: mango.Group,
account: mango.Account,
market: mango.LoadedMarket,
oracle: mango.Oracle,
) -> ModelStateBuilder:
if mode == ModelUpdateMode.WEBSOCKET:
return _websocket_model_state_builder_factory(
context,
disposer,
websocket_manager,
health_check,
wallet,
group,
account,
market,
oracle,
)
else:
return _polling_model_state_builder_factory(
context, wallet, group, account, market, oracle
)
def _polling_model_state_builder_factory(
context: mango.Context,
wallet: mango.Wallet,
group: mango.Group,
account: mango.Account,
market: mango.Market,
oracle: mango.Oracle,
) -> ModelStateBuilder:
if mango.SerumMarket.isa(market):
return _polling_serum_model_state_builder_factory(
context, wallet, group, account, mango.SerumMarket.ensure(market), oracle
)
elif mango.SpotMarket.isa(market):
return _polling_spot_model_state_builder_factory(
group, account, mango.SpotMarket.ensure(market), oracle
)
elif mango.PerpMarket.isa(market):
return _polling_perp_model_state_builder_factory(
group, account, mango.PerpMarket.ensure(market), oracle
)
else:
raise Exception(
f"Could not determine type of market {market.fully_qualified_symbol}: {market}"
)
def _polling_serum_model_state_builder_factory(
context: mango.Context,
wallet: mango.Wallet,
group: mango.Group,
account: mango.Account,
market: mango.SerumMarket,
oracle: mango.Oracle,
) -> ModelStateBuilder:
base_account = mango.TokenAccount.fetch_largest_for_owner_and_token(
context, wallet.address, market.base
)
if base_account is None:
raise Exception(
f"Could not find token account owned by {wallet.address} for base token {market.base}."
)
quote_account = mango.TokenAccount.fetch_largest_for_owner_and_token(
context, wallet.address, market.quote
)
if quote_account is None:
raise Exception(
f"Could not find token account owned by {wallet.address} for quote token {market.quote}."
)
all_open_orders = mango.OpenOrders.load_for_market_and_owner(
context,
market.address,
wallet.address,
context.serum_program_address,
market.base,
market.quote,
)
if len(all_open_orders) == 0:
raise Exception(
f"Could not find serum openorders account owned by {wallet.address} for market {market.fully_qualified_symbol}."
)
return SerumPollingModelStateBuilder(
all_open_orders[0].address,
market,
oracle,
group.address,
group.cache,
account.address,
all_open_orders[0].address,
base_account,
quote_account,
)
def _polling_spot_model_state_builder_factory(
group: mango.Group,
account: mango.Account,
market: mango.SpotMarket,
oracle: mango.Oracle,
) -> ModelStateBuilder:
market_index: int = group.slot_by_spot_market_address(market.address).index
open_orders_address: typing.Optional[PublicKey] = account.spot_open_orders_by_index[
market_index
]
all_open_orders_addresses: typing.Sequence[PublicKey] = account.spot_open_orders
if open_orders_address is None:
raise Exception(
f"Could not find spot openorders in account {account.address} for market {market.fully_qualified_symbol}."
)
return SpotPollingModelStateBuilder(
open_orders_address,
market,
oracle,
group.address,
group.cache,
account.address,
open_orders_address,
all_open_orders_addresses,
)
def _polling_perp_model_state_builder_factory(
group: mango.Group,
account: mango.Account,
market: mango.PerpMarket,
oracle: mango.Oracle,
) -> ModelStateBuilder:
all_open_orders_addresses: typing.Sequence[PublicKey] = account.spot_open_orders
return PerpPollingModelStateBuilder(
account.address,
market,
oracle,
group.address,
group.cache,
all_open_orders_addresses,
)
def __load_all_openorders_watchers(
context: mango.Context,
wallet: mango.Wallet,
account: mango.Account,
group: mango.Group,
websocket_manager: mango.WebSocketSubscriptionManager,
health_check: mango.HealthCheck,
) -> typing.Sequence[mango.Watcher[mango.OpenOrders]]:
all_open_orders_watchers: typing.List[mango.Watcher[mango.OpenOrders]] = []
for basket_token in account.base_slots:
if basket_token.spot_open_orders is not None:
spot_market_symbol: str = f"spot:{basket_token.base_instrument.symbol}/{account.shared_quote_token.symbol}"
spot_market = mango.SpotMarket.ensure(
mango.market(context, spot_market_symbol)
)
oo_watcher = mango.build_spot_open_orders_watcher(
context,
websocket_manager,
health_check,
wallet,
account,
group,
spot_market,
)
all_open_orders_watchers += [oo_watcher]
return all_open_orders_watchers
def _websocket_model_state_builder_factory(
context: mango.Context,
disposer: mango.Disposable,
websocket_manager: mango.WebSocketSubscriptionManager,
health_check: mango.HealthCheck,
wallet: mango.Wallet,
group: mango.Group,
account: mango.Account,
market: mango.LoadedMarket,
oracle: mango.Oracle,
) -> ModelStateBuilder:
group_watcher = mango.build_group_watcher(
context, websocket_manager, health_check, group
)
cache = mango.Cache.load(context, group.cache)
cache_watcher = mango.build_cache_watcher(
context, websocket_manager, health_check, cache, group
)
account_subscription, latest_account_observer = mango.build_account_watcher(
context, websocket_manager, health_check, account, group_watcher, cache_watcher
)
initial_price = oracle.fetch_price(context)
price_feed = oracle.to_streaming_observable(context)
latest_price_observer = mango.LatestItemObserverSubscriber(initial_price)
price_disposable = price_feed.subscribe(latest_price_observer)
disposer.add_disposable(price_disposable)
health_check.add("price_subscription", price_feed)
if mango.SerumMarket.isa(market):
serum_market = mango.SerumMarket.ensure(market)
order_owner: PublicKey = (
serum_market.find_openorders_address_for_owner(context, wallet.address)
or SYSTEM_PROGRAM_ADDRESS
)
price_watcher: mango.Watcher[mango.Price] = mango.build_price_watcher(
context, websocket_manager, health_check, disposer, "market", serum_market
)
inventory_watcher: mango.Watcher[
mango.Inventory
] = mango.build_serum_inventory_watcher(
context,
websocket_manager,
health_check,
disposer,
wallet,
serum_market,
price_watcher,
)
latest_open_orders_observer: mango.Watcher[
mango.PlacedOrdersContainer
] = mango.build_serum_open_orders_watcher(
context, websocket_manager, health_check, serum_market, wallet
)
latest_orderbook_watcher: mango.Watcher[
mango.OrderBook
] = mango.build_orderbook_watcher(
context, websocket_manager, health_check, serum_market
)
latest_event_queue_watcher: mango.Watcher[
mango.EventQueue
] = mango.build_serum_event_queue_watcher(
context, websocket_manager, health_check, serum_market
)
elif mango.SpotMarket.isa(market):
spot_market = mango.SpotMarket.ensure(market)
market_index: int = group.slot_by_spot_market_address(spot_market.address).index
order_owner = (
account.spot_open_orders_by_index[market_index] or SYSTEM_PROGRAM_ADDRESS
)
all_open_orders_watchers = __load_all_openorders_watchers(
context, wallet, account, group, websocket_manager, health_check
)
latest_open_orders_observer = list(
[
oo_watcher
for oo_watcher in all_open_orders_watchers
if (
spot_market.base == spot_market.base
and spot_market.quote == spot_market.quote
)
]
)[0]
inventory_watcher = mango.InventoryAccountWatcher(
spot_market,
latest_account_observer,
group_watcher,
all_open_orders_watchers,
cache_watcher,
)
latest_orderbook_watcher = mango.build_orderbook_watcher(
context, websocket_manager, health_check, spot_market
)
latest_event_queue_watcher = mango.build_spot_event_queue_watcher(
context, websocket_manager, health_check, spot_market
)
elif mango.PerpMarket.isa(market):
perp_market = mango.PerpMarket.ensure(market)
order_owner = account.address
all_open_orders_watchers = __load_all_openorders_watchers(
context, wallet, account, group, websocket_manager, health_check
)
inventory_watcher = mango.InventoryAccountWatcher(
perp_market,
latest_account_observer,
group_watcher,
all_open_orders_watchers,
cache_watcher,
)
latest_open_orders_observer = mango.build_perp_open_orders_watcher(
context,
websocket_manager,
health_check,
perp_market,
account,
group,
account_subscription,
)
latest_orderbook_watcher = mango.build_orderbook_watcher(
context, websocket_manager, health_check, perp_market
)
latest_event_queue_watcher = mango.build_perp_event_queue_watcher(
context, websocket_manager, health_check, perp_market
)
else:
raise Exception(
f"Could not determine type of market {market.fully_qualified_symbol} - {market}"
)
model_state = ModelState(
order_owner,
market,
group_watcher,
latest_account_observer,
latest_price_observer,
latest_open_orders_observer,
inventory_watcher,
latest_orderbook_watcher,
latest_event_queue_watcher,
)
return WebsocketModelStateBuilder(model_state)