diff --git a/bin/cancel-my-orders b/bin/cancel-my-orders index 8e18d43..34087c1 100755 --- a/bin/cancel-my-orders +++ b/bin/cancel-my-orders @@ -14,6 +14,10 @@ parser = argparse.ArgumentParser(description="Cancels all orders on a market fro mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.add_command_line_parameters(parser) parser.add_argument("--market", type=str, required=True, help="market symbol where orders are placed (e.g. ETH/USDC)") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -21,13 +25,15 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) orders = market_operations.load_my_orders() for order in orders: print("Cancelling:", order) diff --git a/bin/cancel-order b/bin/cancel-order index 9e41a4e..bf73de3 100755 --- a/bin/cancel-order +++ b/bin/cancel-order @@ -20,6 +20,10 @@ parser.add_argument("--client-id", type=int, help="client ID of the order to cancel (either --client-id must be specified, or both --id and --side must be specified") parser.add_argument("--side", type=mango.Side, default=mango.Side.BUY, choices=list(mango.Side), help="whether the order to cancel is a BUY or a SELL (either --client-id must be specified, or both --id and --side must be specified") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -27,13 +31,15 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) order = mango.Order.from_ids(id=args.id, client_id=args.client_id, side=args.side) cancellation = market_operations.cancel_order(order) diff --git a/bin/crank-market b/bin/crank-market index 978c376..6150354 100755 --- a/bin/crank-market +++ b/bin/crank-market @@ -17,6 +17,10 @@ mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.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("--limit", type=Decimal, default=Decimal(32), help="maximum number of events to be processed") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -24,6 +28,8 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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) logging.info(f"Context: {context}") logging.info(f"Wallet address: {wallet.address}") @@ -33,7 +39,7 @@ market = context.market_lookup.find_by_symbol(market_symbol) if market is None: raise Exception(f"Could not find market {market_symbol}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) crank = market_operations.crank(args.limit) print(crank) diff --git a/bin/hedger b/bin/hedger index 9da7848..aafd421 100755 --- a/bin/hedger +++ b/bin/hedger @@ -22,12 +22,12 @@ parser = argparse.ArgumentParser( mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.add_command_line_parameters(parser) parser.add_argument("--market", type=str, required=True, help="perp market symbol to hedge (e.g. ETH-PERP)") -parser.add_argument("--account-index", type=int, default=0, - help="index of the account to use, if more than one available") parser.add_argument("--max-price-slippage-factor", type=Decimal, default=Decimal("0.05"), help="the maximum value the IOC hedging order price can slip by (default is 0.05 for 5%)") parser.add_argument("--notify-errors", type=mango.parse_subscription_target, action="append", default=[], help="The notification target for error events") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") 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() @@ -44,10 +44,7 @@ logger: logging.Logger = logging.getLogger("Hedger") context = mango.ContextBuilder.from_command_line_parameters(args) wallet = mango.Wallet.from_command_line_parameters_or_raise(args) group = mango.Group.load(context, context.group_id) -accounts = mango.Account.load_all_for_owner(context, wallet.address, group) -if len(accounts) == 0: - raise Exception(f"No mango account found for root address '{wallet.address}'.") -account = accounts[args.account_index] +account = mango.Account.load_for_owner_by_index(context, wallet.address, group, args.account_index) disposer = mango.DisposePropagator() manager = mango.WebSocketSubscriptionManager() @@ -76,7 +73,7 @@ if not isinstance(hedging_market, mango.SpotMarket): raise Exception(f"Market {hedging_market_symbol} is not a spot market.") hedging_market_operations: mango.MarketOperations = mango.create_market_operations( - context, wallet, args.dry_run, hedging_market) + context, wallet, account, hedging_market, args.dry_run) initial: mango.PerpEventQueue = mango.PerpEventQueue.load( context, watched_market.underlying_perp_market.event_queue, watched_market.lot_size_converter) diff --git a/bin/marketmaker b/bin/marketmaker index 924274e..4f02759 100755 --- a/bin/marketmaker +++ b/bin/marketmaker @@ -21,8 +21,6 @@ parser = argparse.ArgumentParser(description="Shows the on-chain data of a parti mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.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("--account-index", type=int, default=0, - help="index of the account to use, if more than one available") parser.add_argument("--oracle-provider", type=str, required=True, help="name of the price provider to use (e.g. pyth)") parser.add_argument("--position-size-ratio", type=Decimal, required=True, @@ -37,6 +35,8 @@ parser.add_argument("--order-type", type=mango.OrderType, default=mango.OrderTyp choices=list(mango.OrderType), help="Order type: LIMIT, IOC or POST_ONLY") 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("--dry-run", action="store_true", default=False, help="runs as read-only and does not perform any transactions") args = parser.parse_args() @@ -45,9 +45,10 @@ logging.getLogger().setLevel(args.log_level) logging.warning(mango.WARNING_DISCLAIMER_TEXT) -def cleanup(context: mango.Context, wallet: mango.Wallet, dry_run: bool, market: mango.Market): +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, dry_run, market) + 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) @@ -184,10 +185,7 @@ disposer.add_disposable(manager) wallet = mango.Wallet.from_command_line_parameters_or_raise(args) group = mango.Group.load(context, context.group_id) -accounts = mango.Account.load_all_for_owner(context, wallet.address, group) -if len(accounts) == 0: - raise Exception(f"No mango account found for root address '{wallet.address}'.") -account = accounts[args.account_index] +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) @@ -199,7 +197,7 @@ 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, args.dry_run, market) +cleanup(context, wallet, account, market, args.dry_run) latest_group_observer = build_latest_group_observer(context, manager, disposer, group) account_subscription, latest_account_observer = build_latest_account_observer( @@ -208,7 +206,7 @@ latest_price_observer = build_latest_price_observer(context, disposer, args.orac market = mango.ensure_market_loaded(context, market) market_instruction_builder: mango.MarketInstructionBuilder = mango.create_market_instruction_builder( - context, wallet, args.dry_run, market, account) + context, wallet, account, market, args.dry_run) if isinstance(market, mango.SerumMarket): latest_perp_market_observer = None latest_open_orders_observer = build_latest_serum_open_orders_observer(manager, disposer, market, context, wallet) @@ -260,5 +258,5 @@ except: logging.info("Shutting down...") ws.close() disposer.dispose() -cleanup(context, wallet, args.dry_run, market) +cleanup(context, wallet, account, market, args.dry_run) logging.info("Shutdown complete.") diff --git a/bin/place-order b/bin/place-order index 39786d0..aae84db 100755 --- a/bin/place-order +++ b/bin/place-order @@ -21,6 +21,10 @@ parser.add_argument("--price", type=Decimal, required=True, help="price to BUY o parser.add_argument("--side", type=mango.Side, required=True, choices=list(mango.Side), help="side: BUY or SELL") parser.add_argument("--order-type", type=mango.OrderType, required=True, choices=list(mango.OrderType), help="Order type: LIMIT, IOC or POST_ONLY") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -28,13 +32,15 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) order: mango.Order = mango.Order.from_basic_info(args.side, args.price, args.quantity, args.order_type) placed = market_operations.place_order(order) print(placed) diff --git a/bin/settle-market b/bin/settle-market index 3764a61..4f54f51 100755 --- a/bin/settle-market +++ b/bin/settle-market @@ -16,6 +16,10 @@ parser = argparse.ArgumentParser(description="Settles all openorders transaction mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.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("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -23,6 +27,8 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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) logging.info(f"Context: {context}") logging.info(f"Wallet address: {wallet.address}") @@ -32,7 +38,7 @@ market = context.market_lookup.find_by_symbol(market_symbol) if market is None: raise Exception(f"Could not find market {market_symbol}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) settle = market_operations.settle() print(settle) diff --git a/bin/show-my-orders b/bin/show-my-orders index 84e9b08..0740ebe 100755 --- a/bin/show-my-orders +++ b/bin/show-my-orders @@ -14,6 +14,10 @@ parser = argparse.ArgumentParser(description="Shows all orders on the given mark mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.add_command_line_parameters(parser) parser.add_argument("--market", type=str, required=True, help="market symbol to buy (e.g. ETH/USDC)") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -21,13 +25,15 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) orders = market_operations.load_my_orders() print(f"{len(orders)} order(s) to show.") for order in orders: diff --git a/bin/show-orders b/bin/show-orders index 3d2b75c..f01790b 100755 --- a/bin/show-orders +++ b/bin/show-orders @@ -14,6 +14,10 @@ parser = argparse.ArgumentParser(description="Shows all orders on a market.") mango.ContextBuilder.add_command_line_parameters(parser) mango.Wallet.add_command_line_parameters(parser) parser.add_argument("--market", type=str, required=True, help="market symbol to buy (e.g. ETH/USDC)") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") +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) @@ -21,13 +25,15 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) context = mango.ContextBuilder.from_command_line_parameters(args) 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}") -market_operations = mango.create_market_operations(context, wallet, False, market) +market_operations = mango.create_market_operations(context, wallet, account, market, args.dry_run) orders = market_operations.load_orders() print(f"{len(orders)} order(s) to show.") for order in orders: diff --git a/bin/simple-marketmaker b/bin/simple-marketmaker index fd8159e..de3f43d 100755 --- a/bin/simple-marketmaker +++ b/bin/simple-marketmaker @@ -31,6 +31,8 @@ parser.add_argument("--pause-duration", type=int, default=10, help="number of seconds to pause between placing orders and cancelling them") parser.add_argument("--oracle-provider", type=str, default="serum", help="name of the oracle service providing the prices") +parser.add_argument("--account-index", type=int, default=0, + help="index of the account to use, if more than one available") 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() @@ -41,13 +43,16 @@ logging.warning(mango.WARNING_DISCLAIMER_TEXT) try: context = mango.ContextBuilder.from_command_line_parameters(args) 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 spot market {market_symbol}") - market_operations: mango.MarketOperations = mango.create_market_operations(context, wallet, args.dry_run, market) + market_operations: mango.MarketOperations = mango.create_market_operations( + context, wallet, account, market, args.dry_run) oracle_provider: mango.OracleProvider = mango.create_oracle_provider(context, args.oracle_provider) oracle = oracle_provider.oracle_for_market(context, market) diff --git a/mango/account.py b/mango/account.py index 532ada9..060244e 100644 --- a/mango/account.py +++ b/mango/account.py @@ -134,9 +134,13 @@ class Account(AddressableAccount): return accounts @staticmethod - def load_primary_for_owner(context: Context, owner: PublicKey, group: Group) -> "Account": - # Don't try to do anything smart (yet). Just return the first account. Might need to be smarter in the future. - return Account.load_all_for_owner(context, owner, group)[0] + def load_for_owner_by_index(context: Context, owner: PublicKey, group: Group, account_index: int) -> "Account": + accounts: typing.Sequence[Account] = Account.load_all_for_owner(context, owner, group) + if len(accounts) == 0: + raise Exception(f"Could not find any Mango accounts for owner '{owner}'.") + if account_index >= len(accounts): + raise Exception(f"Could not find Mango account at index {account_index} for owner '{owner}'.") + return accounts[account_index] def __str__(self): def _render_list(items, stub): diff --git a/mango/createmarketinstructionbuilder.py b/mango/createmarketinstructionbuilder.py index 6b0f31b..ae49efc 100644 --- a/mango/createmarketinstructionbuilder.py +++ b/mango/createmarketinstructionbuilder.py @@ -14,8 +14,6 @@ # [Email](mailto:hello@blockworks.foundation) -import typing - from .account import Account from .context import Context from .ensuremarketloaded import ensure_market_loaded @@ -34,7 +32,7 @@ from .wallet import Wallet # This function deals with the creation of a `MarketInstructionBuilder` object for a given `Market`. -def create_market_instruction_builder(context: Context, wallet: Wallet, dry_run: bool, market: Market, account: typing.Optional[Account] = None) -> MarketInstructionBuilder: +def create_market_instruction_builder(context: Context, wallet: Wallet, account: Account, market: Market, dry_run: bool = False) -> MarketInstructionBuilder: if dry_run: return NullMarketInstructionBuilder(market.symbol) @@ -42,11 +40,9 @@ def create_market_instruction_builder(context: Context, wallet: Wallet, dry_run: if isinstance(loaded_market, SerumMarket): return SerumMarketInstructionBuilder.load(context, wallet, loaded_market) elif isinstance(loaded_market, SpotMarket): - spot_account: Account = account or Account.load_primary_for_owner(context, wallet.address, loaded_market.group) - return SpotMarketInstructionBuilder.load(context, wallet, loaded_market.group, spot_account, loaded_market) + return SpotMarketInstructionBuilder.load(context, wallet, loaded_market.group, account, loaded_market) elif isinstance(loaded_market, PerpMarket): - perp_account = account or Account.load_primary_for_owner(context, wallet.address, loaded_market.group) return PerpMarketInstructionBuilder.load( - context, wallet, loaded_market.group, perp_account, loaded_market) + context, wallet, loaded_market.group, account, loaded_market) else: raise Exception(f"Could not find market instructions builder for market {market.symbol}") diff --git a/mango/createmarketoperations.py b/mango/createmarketoperations.py index fe32f5a..5e4d39f 100644 --- a/mango/createmarketoperations.py +++ b/mango/createmarketoperations.py @@ -35,7 +35,7 @@ from .wallet import Wallet # This function deals with the creation of a `MarketOperations` object for a given `Market`. -def create_market_operations(context: Context, wallet: Wallet, dry_run: bool, market: Market) -> MarketOperations: +def create_market_operations(context: Context, wallet: Wallet, account: Account, market: Market, dry_run: bool = False) -> MarketOperations: if dry_run: return NullMarketOperations(market.symbol) @@ -45,12 +45,10 @@ def create_market_operations(context: Context, wallet: Wallet, dry_run: bool, ma context, wallet, loaded_market) return SerumMarketOperations(context, wallet, loaded_market, serum_market_instruction_builder) elif isinstance(loaded_market, SpotMarket): - account: Account = Account.load_primary_for_owner(context, wallet.address, loaded_market.group) spot_market_instruction_builder: SpotMarketInstructionBuilder = SpotMarketInstructionBuilder.load( context, wallet, loaded_market.group, account, loaded_market) return SpotMarketOperations(context, wallet, loaded_market.group, account, loaded_market, spot_market_instruction_builder) elif isinstance(loaded_market, PerpMarket): - account = Account.load_primary_for_owner(context, wallet.address, loaded_market.group) perp_market_instruction_builder: PerpMarketInstructionBuilder = PerpMarketInstructionBuilder.load( context, wallet, loaded_market.underlying_perp_market.group, account, loaded_market) return PerpMarketOperations(loaded_market.symbol, context, wallet, perp_market_instruction_builder, account, loaded_market) diff --git a/mango/ensuremarketloaded.py b/mango/ensuremarketloaded.py index cc2fa4b..af1ac7b 100644 --- a/mango/ensuremarketloaded.py +++ b/mango/ensuremarketloaded.py @@ -21,9 +21,12 @@ from .perpmarket import PerpMarketStub from .serummarket import SerumMarketStub from .spotmarket import SpotMarketStub -# # 🥭 create_market_instruction_builder +# # 🥭 ensure_market_loaded function # -# This function deals with the creation of a `MarketInstructionBuilder` object for a given `Market`. +# This function ensures that a `Market` is 'loaded' and not a 'stub'. Stubs are handy for laoding in +# bulk, for instance in a market lookup, but real processing usually requires a fully loaded `Market`. +# +# This function simplifies turning a stub into a fully-loaded, usable market. def ensure_market_loaded(context: Context, market: Market) -> Market: