mango-explorer/bin/marketmaker

155 lines
7.6 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env pyston3
import argparse
import logging
import os
import os.path
import rx
import sys
import threading
import typing
from decimal import Decimal
sys.path.insert(0, os.path.abspath(
os.path.join(os.path.dirname(__file__), "..")))
import mango # nopep8
import mango.marketmaking # nopep8
from mango.marketmaking.orderchain import chain # nopep8
from mango.marketmaking.orderchain import chainbuilder # nopep8
parser = argparse.ArgumentParser(description="Shows the on-chain data of a particular account.")
mango.ContextBuilder.add_command_line_parameters(parser)
mango.Wallet.add_command_line_parameters(parser)
chainbuilder.ChainBuilder.add_command_line_parameters(parser)
parser.add_argument("--market", type=str, required=True, help="market symbol to make market upon (e.g. ETH/USDC)")
parser.add_argument("--oracle-provider", type=str, required=True,
help="name of the price provider to use (e.g. pyth)")
parser.add_argument("--existing-order-tolerance", type=Decimal, default=Decimal("0.001"),
help="tolerance in price and quantity when matching existing orders or cancelling/replacing")
parser.add_argument("--pulse-interval", type=int, default=10,
help="number of seconds between each 'pulse' of the market maker")
parser.add_argument("--account-index", type=int, default=0,
help="index of the account to use, if more than one available")
parser.add_argument("--notify-errors", type=mango.parse_subscription_target, action="append", default=[],
help="The notification target for error events")
parser.add_argument("--dry-run", action="store_true", default=False,
help="runs as read-only and does not perform any transactions")
args = parser.parse_args()
logging.getLogger().setLevel(args.log_level)
logging.warning(mango.WARNING_DISCLAIMER_TEXT)
for notify in args.notify_errors:
handler = mango.NotificationHandler(notify)
handler.setLevel(logging.ERROR)
logging.getLogger().addHandler(handler)
def cleanup(context: mango.Context, wallet: mango.Wallet, account: mango.Account, market: mango.Market, dry_run: bool):
logging.info("Cleaning up.")
market_operations: mango.MarketOperations = mango.create_market_operations(
context, wallet, account, market, dry_run)
orders = market_operations.load_my_orders()
for order in orders:
market_operations.cancel_order(order)
logging.info("Settling.")
market_operations.crank()
market_operations.settle()
context = mango.ContextBuilder.from_command_line_parameters(args)
logging.info(f"{context}")
disposer = mango.DisposePropagator()
manager = mango.IndividualWebSocketSubscriptionManager(context)
disposer.add_disposable(manager)
health_check = mango.HealthCheck()
disposer.add_disposable(health_check)
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
group = mango.Group.load(context, context.group_id)
account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index)
market_symbol = args.market.upper()
market = context.market_lookup.find_by_symbol(market_symbol)
if market is None:
raise Exception(f"Could not find market {market_symbol}")
# The market index is also the index of the base token in the group's token list.
if market.quote != group.shared_quote_token.token:
raise Exception(
f"Group {group.name} uses shared quote token {group.shared_quote_token.token.symbol}/{group.shared_quote_token.token.mint}, but market {market.symbol} uses quote token {market.quote.symbol}/{market.quote.mint}.")
cleanup(context, wallet, account, market, args.dry_run)
latest_group_observer = mango.build_group_watcher(context, manager, health_check, group)
account_subscription, latest_account_observer = mango.build_account_watcher(
context, manager, health_check, account, latest_group_observer)
latest_price_observer = mango.build_price_watcher(
context, manager, health_check, disposer, args.oracle_provider, market)
market = mango.ensure_market_loaded(context, market)
market_instruction_builder: mango.MarketInstructionBuilder = mango.create_market_instruction_builder(
context, wallet, account, market, args.dry_run)
if isinstance(market, mango.SerumMarket):
inventory_watcher: mango.Watcher[mango.Inventory] = mango.build_serum_inventory_watcher(
context, manager, health_check, disposer, wallet, market)
latest_open_orders_observer: mango.Watcher[mango.PlacedOrdersContainer] = mango.build_serum_open_orders_watcher(
context, manager, health_check, market, wallet)
latest_bids_watcher: mango.Watcher[typing.Sequence[mango.Order]] = mango.build_serum_orderbook_side_watcher(
context, manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.BIDS)
latest_asks_watcher: mango.Watcher[typing.Sequence[mango.Order]] = mango.build_serum_orderbook_side_watcher(
context, manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.ASKS)
elif isinstance(market, mango.SpotMarket):
inventory_watcher = mango.SpotInventoryAccountWatcher(market, latest_account_observer)
latest_open_orders_observer = mango.build_spot_open_orders_watcher(
context, manager, health_check, wallet, account, group, market)
latest_bids_watcher = mango.build_serum_orderbook_side_watcher(
context, manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.BIDS)
latest_asks_watcher = mango.build_serum_orderbook_side_watcher(
context, manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.ASKS)
elif isinstance(market, mango.PerpMarket):
inventory_watcher = mango.PerpInventoryAccountWatcher(market, latest_account_observer, group)
latest_open_orders_observer = mango.build_perp_open_orders_watcher(
context, manager, health_check, market, account, group, account_subscription)
latest_bids_watcher = mango.build_perp_orderbook_side_watcher(
context, manager, health_check, market, mango.OrderBookSideType.BIDS)
latest_asks_watcher = mango.build_perp_orderbook_side_watcher(
context, manager, health_check, market, mango.OrderBookSideType.ASKS)
else:
raise Exception(f"Could not determine type of market {market.symbol}")
manager.open()
order_reconciler = mango.marketmaking.ToleranceOrderReconciler(
args.existing_order_tolerance, args.existing_order_tolerance)
desired_orders_chain: chain.Chain = chainbuilder.ChainBuilder.from_command_line_parameters(args)
market_maker = mango.marketmaking.MarketMaker(
wallet, market, market_instruction_builder, desired_orders_chain, order_reconciler)
model_state = mango.marketmaking.ModelState(market, latest_account_observer,
latest_group_observer, latest_price_observer,
latest_open_orders_observer, inventory_watcher,
latest_bids_watcher, latest_asks_watcher)
health_check.add("marketmaker_pulse", market_maker.pulse_complete)
2021-08-07 10:42:39 -07:00
logging.info(f"Current assets in account {account.address} (owner: {account.owner}):")
mango.TokenValue.report([asset for asset in account.net_assets if asset is not None], logging.info)
pulse_disposable = rx.interval(args.pulse_interval).subscribe(
on_next=lambda _: market_maker.pulse(context, model_state))
disposer.add_disposable(pulse_disposable)
# Wait - don't exit. Exiting will be handled by signals/interrupts.
waiter = threading.Event()
try:
waiter.wait()
except:
pass
logging.info("Shutting down...")
disposer.dispose()
cleanup(context, wallet, account, market, args.dry_run)
logging.info("Shutdown complete.")