diff --git a/bin/cancel-my-orders b/bin/cancel-my-orders index 6808624..3e07340 100755 --- a/bin/cancel-my-orders +++ b/bin/cancel-my-orders @@ -53,7 +53,9 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: ) orders = market_operations.load_my_orders(include_expired=True) if len(orders) == 0: - mango.output(f"No open orders on {market_operations.market.symbol}") + mango.output( + f"No open orders on {market_operations.market.fully_qualified_symbol}" + ) else: if mango.PerpMarket.isa(market_operations.market): cancel_all = mango.PerpMarketOperations.ensure( diff --git a/bin/marketmaker b/bin/marketmaker index ff38020..18296e2 100755 --- a/bin/marketmaker +++ b/bin/marketmaker @@ -181,10 +181,12 @@ def cleanup( dry_run: bool, ) -> None: market_operations: mango.MarketOperations = mango.operations( - context, wallet, account, market.symbol, dry_run + context, wallet, account, market.fully_qualified_symbol, dry_run ) market_instruction_builder: mango.MarketInstructionBuilder = ( - mango.instruction_builder(context, wallet, account, market.symbol, dry_run) + mango.instruction_builder( + context, wallet, account, market.fully_qualified_symbol, dry_run + ) ) cancels: mango.CombinableInstructions = mango.CombinableInstructions.empty() orders = market_operations.load_my_orders(include_expired=True) @@ -221,7 +223,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: # The market index is also the index of the base token in the group's token list. if market.quote != group.shared_quote_token: raise Exception( - f"Group {group.name} uses shared quote token {group.shared_quote_token.symbol}/{group.shared_quote_token.mint}, but market {market.symbol} uses quote token {market.quote.symbol}/{market.quote.mint}." + f"Group {group.name} uses shared quote token {group.shared_quote_token.symbol}/{group.shared_quote_token.mint}, but market {market.fully_qualified_symbol} uses quote token {market.quote.symbol}/{market.quote.mint}." ) cleanup(context, wallet, account, market, args.dry_run) @@ -230,7 +232,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: if args.hedging_market is not None: if not mango.PerpMarket.isa(market): raise Exception( - f"Cannot hedge - market {market.symbol} is not a perp market." + f"Cannot hedge - market {market.fully_qualified_symbol} is not a perp market." ) underlying_market = mango.PerpMarket.ensure(market) @@ -243,7 +245,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: f"MarketOperations for {args.hedging_market} is not a SpotMarketOperations." ) - logging.info(f"Hedging on {hedging_ops.market.symbol}") + logging.info(f"Hedging on {hedging_ops.market.fully_qualified_symbol}") target_balance: typing.Optional[ mango.TargetBalance @@ -287,7 +289,9 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: logging.info(f"Desired orders chain: {desired_orders_chain}") market_instruction_builder: mango.MarketInstructionBuilder = ( - mango.instruction_builder(context, wallet, account, market.symbol, args.dry_run) + mango.instruction_builder( + context, wallet, account, market.fully_qualified_symbol, args.dry_run + ) ) market_maker = mango.marketmaking.MarketMaker( @@ -310,7 +314,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: oracle = oracle_provider.oracle_for_market(context, oracle_market) if oracle is None: raise Exception( - f"Could not find oracle for market {oracle_market.symbol} from provider {args.oracle_provider}." + f"Could not find oracle for market {oracle_market.fully_qualified_symbol} from provider {args.oracle_provider}." ) model_state_builder: mango.marketmaking.ModelStateBuilder = ( diff --git a/bin/show-model-state b/bin/show-model-state index 6d2e9a9..27ad4bd 100755 --- a/bin/show-model-state +++ b/bin/show-model-state @@ -50,7 +50,7 @@ with mango.ContextBuilder.from_command_line_parameters( oracle = oracle_provider.oracle_for_market(context, market) if oracle is None: raise Exception( - f"Could not find oracle for market {market.symbol} from provider {args.oracle_provider}." + f"Could not find oracle for market {market.fully_qualified_symbol} from provider {args.oracle_provider}." ) health_check = mango.HealthCheck() diff --git a/bin/show-price b/bin/show-price index 25248b0..f51e760 100755 --- a/bin/show-price +++ b/bin/show-price @@ -38,7 +38,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: oracle = oracle_provider.oracle_for_market(context, market) if oracle is None: mango.output( - f"Could not find oracle for market {market.symbol} from provider {args.provider}." + f"Could not find oracle for market {market.fully_qualified_symbol} from provider {args.provider}." ) else: if not args.stream: diff --git a/bin/show-serum-open-orders b/bin/show-serum-open-orders index a9427c6..89aab71 100755 --- a/bin/show-serum-open-orders +++ b/bin/show-serum-open-orders @@ -41,7 +41,7 @@ with mango.ContextBuilder.from_command_line_parameters(args) as context: market.quote, ) mango.output( - f"Found {len(all_open_orders_for_market)} Serum OpenOrders account(s) for market {market.symbol}." + f"Found {len(all_open_orders_for_market)} Serum OpenOrders account(s) for market {market.fully_qualified_symbol}." ) for open_orders in all_open_orders_for_market: mango.output(open_orders) diff --git a/mango/hedging/perptospothedger.py b/mango/hedging/perptospothedger.py index ebf5f93..5e0b6cb 100644 --- a/mango/hedging/perptospothedger.py +++ b/mango/hedging/perptospothedger.py @@ -45,14 +45,14 @@ def __init__( underlying_market.quote != hedging_market.quote ): raise Exception( - f"Market {hedging_market.symbol} cannot be used to hedge market {underlying_market.symbol}." + f"Market {hedging_market.fully_qualified_symbol} cannot be used to hedge market {underlying_market.fully_qualified_symbol}." ) if not mango.Instrument.symbols_match( target_balance.symbol, hedging_market.base.symbol ): raise Exception( - f"Cannot target {target_balance.symbol} when hedging on {hedging_market.symbol}" + f"Cannot target {target_balance.symbol} when hedging on {hedging_market.fully_qualified_symbol}" ) self.underlying_market: mango.PerpMarket = underlying_market @@ -124,7 +124,7 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: perp_position_rounded + token_balance_rounded - self.target_balance ) self._logger.debug( - f"Delta from {self.underlying_market.symbol} to {self.hedging_market.symbol} is {delta:,.8f} {basket_token.base_instrument.symbol}, action threshold is: {self.action_threshold}" + f"Delta from {self.underlying_market.fully_qualified_symbol} to {self.hedging_market.fully_qualified_symbol} is {delta:,.8f} {basket_token.base_instrument.symbol}, action threshold is: {self.action_threshold}" ) if abs(delta) > self.action_threshold: @@ -151,14 +151,14 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: side, adjusted_price, quantity, mango.OrderType.IOC ) self._logger.info( - f"Hedging perp position {perp_position} and token balance {token_balance} with {side} of {quantity:,.8f} at {up_or_down} ({model_state.price}) {adjusted_price:,.8f} on {self.hedging_market.symbol}\n\t{order}" + f"Hedging perp position {perp_position} and token balance {token_balance} with {side} of {quantity:,.8f} at {up_or_down} ({model_state.price}) {adjusted_price:,.8f} on {self.hedging_market.fully_qualified_symbol}\n\t{order}" ) try: self.market_operations.place_order(order) self.pause_counter = 0 except Exception: self._logger.error( - f"[{context.name}] Failed to hedge on {self.hedging_market.symbol} using order {order} - {traceback.format_exc()}" + f"[{context.name}] Failed to hedge on {self.hedging_market.fully_qualified_symbol} using order {order} - {traceback.format_exc()}" ) raise @@ -181,4 +181,4 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: self.pulse_error.on_next(exception) def __str__(self) -> str: - return f"« PerpToSpotHedger for underlying '{self.underlying_market.symbol}', hedging on '{self.hedging_market.symbol}' »" + return f"« PerpToSpotHedger for underlying '{self.underlying_market.fully_qualified_symbol}', hedging on '{self.hedging_market.fully_qualified_symbol}' »" diff --git a/mango/marketmaking/marketmaker.py b/mango/marketmaking/marketmaker.py index a15260c..8550631 100644 --- a/mango/marketmaking/marketmaker.py +++ b/mango/marketmaking/marketmaker.py @@ -81,7 +81,7 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: existing_orders = model_state.current_orders() self._logger.debug( - f"""Before reconciliation: all owned orders on current orderbook [{model_state.market.symbol}]: + f"""Before reconciliation: all owned orders on current orderbook [{model_state.market.fully_qualified_symbol}]: {mango.indent_collection_as_str(existing_orders)}""" ) reconciled = self.order_reconciler.reconcile( @@ -106,14 +106,16 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: ): ids = [f"{ord.id} / {ord.client_id}" for ord in reconciled.to_cancel] self._logger.info( - f"Cancelling all orders on {self.market.symbol} - currently {len(ids)}: {ids}" + f"Cancelling all orders on {self.market.fully_qualified_symbol} - currently {len(ids)}: {ids}" ) cancellations = ( self.market_instruction_builder.build_cancel_all_orders_instructions() ) else: for to_cancel in reconciled.to_cancel: - self._logger.info(f"Cancelling {self.market.symbol} {to_cancel}") + self._logger.info( + f"Cancelling {self.market.fully_qualified_symbol} {to_cancel}" + ) cancel = ( self.market_instruction_builder.build_cancel_order_instructions( to_cancel, ok_if_missing=True @@ -129,7 +131,7 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: ) self._logger.info( - f"Placing {self.market.symbol} {to_place_with_client_id}" + f"Placing {self.market.fully_qualified_symbol} {to_place_with_client_id}" ) place_order = ( self.market_instruction_builder.build_place_order_instructions( @@ -138,8 +140,14 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: ) place_orders += place_order + accounts_to_crank = list(model_state.accounts_to_crank) + if self.market_instruction_builder.open_orders_address is not None: + accounts_to_crank += [ + self.market_instruction_builder.open_orders_address + ] + crank = self.market_instruction_builder.build_crank_instructions( - model_state.accounts_to_crank + accounts_to_crank ) settle = self.market_instruction_builder.build_settle_instructions() @@ -176,7 +184,7 @@ def pulse(self, context: mango.Context, model_state: mango.ModelState) -> None: self.pulse_error.on_next(exception) def __str__(self) -> str: - return f"""« MarketMaker for market '{self.market.symbol}' »""" + return f"""« MarketMaker for market '{self.market.fully_qualified_symbol}' »""" def __repr__(self) -> str: return f"{self}" diff --git a/mango/marketmaking/modelstatebuilder.py b/mango/marketmaking/modelstatebuilder.py index 088aa4c..c7d1fb6 100644 --- a/mango/marketmaking/modelstatebuilder.py +++ b/mango/marketmaking/modelstatebuilder.py @@ -61,7 +61,7 @@ def build(self, context: mango.Context) -> ModelState: return self.model_state def __str__(self) -> str: - return f"« WebsocketModelStateBuilder for market '{self.model_state.market.symbol}' »" + return f"« WebsocketModelStateBuilder for market '{self.model_state.market.fully_qualified_symbol}' »" # # 🥭 PollingModelStateBuilder class @@ -247,9 +247,7 @@ def poll(self, context: mango.Context) -> ModelState: ) def __str__(self) -> str: - return ( - f"""« SerumPollingModelStateBuilder for market '{self.market.symbol}' »""" - ) + return f"""« SerumPollingModelStateBuilder for market '{self.market.fully_qualified_symbol}' »""" # # 🥭 SpotPollingModelStateBuilder class @@ -372,7 +370,7 @@ def poll(self, context: mango.Context) -> ModelState: ) def __str__(self) -> str: - return f"""« SpotPollingModelStateBuilder for market '{self.market.symbol}' »""" + return f"""« SpotPollingModelStateBuilder for market '{self.market.fully_qualified_symbol}' »""" # # 🥭 PerpPollingModelStateBuilder class @@ -490,4 +488,4 @@ def poll(self, context: mango.Context) -> ModelState: ) def __str__(self) -> str: - return f"""« PerpPollingModelStateBuilder for market '{self.market.symbol}' »""" + return f"""« PerpPollingModelStateBuilder for market '{self.market.fully_qualified_symbol}' »""" diff --git a/mango/marketmaking/modelstatebuilderfactory.py b/mango/marketmaking/modelstatebuilderfactory.py index 04fbbcc..59c7971 100644 --- a/mango/marketmaking/modelstatebuilderfactory.py +++ b/mango/marketmaking/modelstatebuilderfactory.py @@ -97,7 +97,9 @@ def _polling_model_state_builder_factory( group, account, mango.PerpMarket.ensure(market), oracle ) else: - raise Exception(f"Could not determine type of market {market.symbol}") + raise Exception( + f"Could not determine type of market {market.fully_qualified_symbol}: {market}" + ) def _polling_serum_model_state_builder_factory( @@ -132,7 +134,7 @@ def _polling_serum_model_state_builder_factory( ) if len(all_open_orders) == 0: raise Exception( - f"Could not find serum openorders account owned by {wallet.address} for market {market.symbol}." + f"Could not find serum openorders account owned by {wallet.address} for market {market.fully_qualified_symbol}." ) return SerumPollingModelStateBuilder( all_open_orders[0].address, @@ -160,7 +162,7 @@ def _polling_spot_model_state_builder_factory( 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.symbol}." + f"Could not find spot openorders in account {account.address} for market {market.fully_qualified_symbol}." ) return SpotPollingModelStateBuilder( open_orders_address, @@ -350,7 +352,9 @@ def _websocket_model_state_builder_factory( context, websocket_manager, health_check, perp_market ) else: - raise Exception(f"Could not determine type of market {market.symbol}") + raise Exception( + f"Could not determine type of market {market.fully_qualified_symbol} - {market}" + ) model_state = ModelState( order_owner, diff --git a/mango/marketoperations.py b/mango/marketoperations.py index 822cdd4..9cdcb0a 100644 --- a/mango/marketoperations.py +++ b/mango/marketoperations.py @@ -60,6 +60,7 @@ class MarketInstructionBuilder(metaclass=abc.ABCMeta): def __init__(self) -> None: self._logger: logging.Logger = logging.getLogger(self.__class__.__name__) + self.open_orders_address: typing.Optional[PublicKey] = None @abc.abstractmethod def build_cancel_order_instructions( @@ -120,7 +121,7 @@ def __init__(self, market: LoadedMarket) -> None: @property def symbol(self) -> str: - return self.market.symbol + return self.market.fully_qualified_symbol @property def inventory_source(self) -> InventorySource: @@ -269,7 +270,9 @@ def place_order( return [] def load_orderbook(self) -> OrderBook: - return OrderBook(self.market.symbol, NullLotSizeConverter(), [], []) + return OrderBook( + self.market.fully_qualified_symbol, NullLotSizeConverter(), [], [] + ) def load_my_orders(self, include_expired: bool = False) -> typing.Sequence[Order]: return [] @@ -287,4 +290,4 @@ def ensure_openorders(self) -> PublicKey: return SYSTEM_PROGRAM_ADDRESS def __str__(self) -> str: - return f"""« NullMarketOperations [{self.market.symbol}] »""" + return f"""« NullMarketOperations [{self.market.fully_qualified_symbol}] »""" diff --git a/mango/markets.py b/mango/markets.py index 97a54aa..a0ee934 100644 --- a/mango/markets.py +++ b/mango/markets.py @@ -83,6 +83,13 @@ def symbol_matches(self, symbol: str) -> bool: def symbol(self) -> str: return f"{self.base.symbol}/{self.quote.symbol}" + @property + @abc.abstractproperty + def fully_qualified_symbol(self) -> str: + raise NotImplementedError( + "Market.fully_qualified_symbol is not implemented on the base type." + ) + def __str__(self) -> str: return f"« Market {self.symbol} »" diff --git a/mango/modelstate.py b/mango/modelstate.py index 95d0849..962a361 100644 --- a/mango/modelstate.py +++ b/mango/modelstate.py @@ -140,7 +140,7 @@ def current_orders(self) -> typing.Sequence[Order]: ) def __str__(self) -> str: - return f"""« ModelState for market '{self.market.symbol}' + return f"""« ModelState for market '{self.market.fully_qualified_symbol}' Group: {self.group_watcher.latest.address} Account: {self.account_watcher.latest.address} Price: {self.price_watcher.latest} diff --git a/mango/oracle.py b/mango/oracle.py index 6162f1e..94a5b89 100644 --- a/mango/oracle.py +++ b/mango/oracle.py @@ -23,7 +23,7 @@ from decimal import Decimal from .context import Context -from .markets import Market +from .loadedmarket import LoadedMarket # # 🥭 Oracles @@ -56,7 +56,7 @@ def __init__( provider_name: str, source_name: str, supports: SupportedOracleFeature, - market: Market, + market: LoadedMarket, ) -> None: self.provider_name = provider_name self.source_name = source_name @@ -64,7 +64,7 @@ def __init__( self.market = market def __str__(self) -> str: - return f"« OracleSource '{self.source_name}' from '{self.provider_name}' for market '{self.market.symbol}' [{self.supports}] »" + return f"« OracleSource '{self.source_name}' from '{self.provider_name}' for market '{self.market.fully_qualified_symbol}' [{self.supports}] »" def __repr__(self) -> str: return f"{self}" @@ -79,7 +79,7 @@ def __init__( self, source: OracleSource, timestamp: datetime, - market: Market, + market: LoadedMarket, top_bid: Decimal, mid_price: Decimal, top_ask: Decimal, @@ -87,7 +87,7 @@ def __init__( ) -> None: self.source: OracleSource = source self.timestamp: datetime = timestamp - self.market: Market = market + self.market: LoadedMarket = market self.top_bid: Decimal = top_bid self.mid_price: Decimal = mid_price self.top_ask: Decimal = top_ask @@ -101,7 +101,7 @@ def __str__(self) -> str: confidence = "" if self.source.supports & SupportedOracleFeature.CONFIDENCE: confidence = f" +/- {self.confidence:,.8f}" - return f"« Price [{self.source.provider_name}] {self.market.symbol} at {self.timestamp}: {self.mid_price:,.8f}{confidence} »" + return f"« Price [{self.source.provider_name}] {self.market.fully_qualified_symbol} at {self.timestamp}: {self.mid_price:,.8f}{confidence} »" def __repr__(self) -> str: return f"{self}" @@ -112,7 +112,7 @@ def __repr__(self) -> str: # Derived versions of this class can fetch prices for a specific market. # class Oracle(metaclass=abc.ABCMeta): - def __init__(self, name: str, market: Market) -> None: + def __init__(self, name: str, market: LoadedMarket) -> None: self._logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.name = name self.market = market @@ -136,7 +136,7 @@ def to_streaming_observable( ) def __str__(self) -> str: - return f"« Oracle {self.name} [{self.market.symbol}] »" + return f"« Oracle {self.name} [{self.market.fully_qualified_symbol}] »" def __repr__(self) -> str: return f"{self}" @@ -152,7 +152,7 @@ def __init__(self, name: str) -> None: @abc.abstractmethod def oracle_for_market( - self, context: Context, market: Market + self, context: Context, market: LoadedMarket ) -> typing.Optional[Oracle]: raise NotImplementedError( "OracleProvider.create_oracle_for_market() is not implemented on the base type." diff --git a/mango/oracles/ftx/ftx.py b/mango/oracles/ftx/ftx.py index 5bea5a8..66c58d0 100644 --- a/mango/oracles/ftx/ftx.py +++ b/mango/oracles/ftx/ftx.py @@ -25,7 +25,7 @@ from ...context import Context from ...datetimes import utc_now, datetime_from_timestamp -from ...markets import Market +from ...loadedmarket import LoadedMarket from ...observables import Disposable, DisposeWrapper from ...oracle import ( Oracle, @@ -61,10 +61,10 @@ def _ftx_get_from_url(url: str) -> typing.Any: # Implements the `Oracle` abstract base class specialised to the Ftx Network. # class FtxOracle(Oracle): - def __init__(self, market: Market, ftx_symbol: str) -> None: - name = f"Ftx Oracle for {market.symbol} / {ftx_symbol}" + def __init__(self, market: LoadedMarket, ftx_symbol: str) -> None: + name = f"Ftx Oracle for {market.fully_qualified_symbol} / {ftx_symbol}" super().__init__(name, market) - self.market: Market = market + self.market: LoadedMarket = market self.ftx_symbol: str = ftx_symbol features: SupportedOracleFeature = ( SupportedOracleFeature.MID_PRICE | SupportedOracleFeature.TOP_BID_AND_OFFER @@ -146,7 +146,7 @@ def __init__(self) -> None: super().__init__("Ftx Oracle Factory") def oracle_for_market( - self, context: Context, market: Market + self, context: Context, market: LoadedMarket ) -> typing.Optional[Oracle]: symbol = self._market_symbol_to_ftx_symbol(market.symbol) return FtxOracle(market, symbol) diff --git a/mango/oracles/market/market.py b/mango/oracles/market/market.py index 55fa14c..3761b1b 100644 --- a/mango/oracles/market/market.py +++ b/mango/oracles/market/market.py @@ -23,7 +23,6 @@ from ...context import Context from ...datetimes import utc_now from ...loadedmarket import LoadedMarket -from ...markets import Market from ...observables import observable_pipeline_error_reporter from ...oracle import ( Oracle, @@ -33,7 +32,6 @@ SupportedOracleFeature, ) from ...orders import OrderBook -from ...porcelain import market as porcelain_market # # 🥭 Market @@ -58,7 +56,7 @@ # class MarketOracle(Oracle): def __init__(self, market: LoadedMarket): - name = f"Market Oracle for {market.symbol}" + name = f"Market Oracle for {market.fully_qualified_symbol}" super().__init__(name, market) self.loaded_market: LoadedMarket = market features: SupportedOracleFeature = SupportedOracleFeature.TOP_BID_AND_OFFER @@ -116,10 +114,9 @@ def __init__(self) -> None: super().__init__("Market Oracle Factory") def oracle_for_market( - self, context: Context, market: Market + self, context: Context, market: LoadedMarket ) -> typing.Optional[Oracle]: - loaded_market: LoadedMarket = porcelain_market(context, market.symbol) - return MarketOracle(loaded_market) + return MarketOracle(market) def all_available_symbols(self, context: Context) -> typing.Sequence[str]: all_markets = context.market_lookup.all_markets() diff --git a/mango/oracles/pythnetwork/pythnetwork.py b/mango/oracles/pythnetwork/pythnetwork.py index dfa48dd..aa16336 100644 --- a/mango/oracles/pythnetwork/pythnetwork.py +++ b/mango/oracles/pythnetwork/pythnetwork.py @@ -25,7 +25,7 @@ from ...accountinfo import AccountInfo from ...context import Context from ...datetimes import utc_now -from ...markets import Market +from ...loadedmarket import LoadedMarket from ...observables import observable_pipeline_error_reporter from ...oracle import ( Oracle, @@ -74,11 +74,13 @@ class PythOracle(Oracle): - def __init__(self, context: Context, market: Market, product_data: typing.Any): - name = f"Pyth Oracle for {market.symbol}" + def __init__( + self, context: Context, market: LoadedMarket, product_data: typing.Any + ): + name = f"Pyth Oracle for {market.fully_qualified_symbol}" super().__init__(name, market) self.context: Context = context - self.market: Market = market + self.market: LoadedMarket = market self.product_data: typing.Any = product_data self.address: PublicKey = product_data.address features: SupportedOracleFeature = ( @@ -153,7 +155,9 @@ def __init__(self, context: Context) -> None: super().__init__(f"Pyth Oracle Factory [{self.address}]") self.context: Context = context - def oracle_for_market(self, _: Context, market: Market) -> typing.Optional[Oracle]: + def oracle_for_market( + self, _: Context, market: LoadedMarket + ) -> typing.Optional[Oracle]: pyth_symbol = self._market_symbol_to_pyth_symbol(market.symbol) products = self._fetch_all_pyth_products(self.context, self.address) for product in products: diff --git a/mango/oracles/stub/stub.py b/mango/oracles/stub/stub.py index 8e2d822..5dad29f 100644 --- a/mango/oracles/stub/stub.py +++ b/mango/oracles/stub/stub.py @@ -25,7 +25,6 @@ from ...context import Context from ...datetimes import utc_now from ...loadedmarket import LoadedMarket -from ...markets import Market from ...observables import observable_pipeline_error_reporter from ...oracle import ( Oracle, @@ -35,7 +34,6 @@ SupportedOracleFeature, ) from ...perpmarket import PerpMarket -from ...porcelain import market as porcelain_market from ...spotmarket import SpotMarket @@ -60,8 +58,10 @@ class StubOracle(Oracle): - def __init__(self, market: Market, index: int, cache_address: PublicKey) -> None: - name = f"Stub Oracle for {market.symbol}" + def __init__( + self, market: LoadedMarket, index: int, cache_address: PublicKey + ) -> None: + name = f"Stub Oracle for {market.fully_qualified_symbol}" super().__init__(name, market) self.index: int = index self.cache_address: PublicKey = cache_address @@ -116,19 +116,18 @@ def __init__(self) -> None: super().__init__("Stub Oracle Factory") def oracle_for_market( - self, context: Context, market: Market + self, context: Context, market: LoadedMarket ) -> typing.Optional[Oracle]: - loaded_market: LoadedMarket = porcelain_market(context, market.symbol) - if SpotMarket.isa(loaded_market): - spot_market = SpotMarket.ensure(loaded_market) + if SpotMarket.isa(market): + spot_market = SpotMarket.ensure(market) spot_index: int = spot_market.group.slot_by_spot_market_address( - loaded_market.address + market.address ).index return StubOracle(spot_market, spot_index, spot_market.group.cache) - elif PerpMarket.isa(loaded_market): - perp_market = PerpMarket.ensure(loaded_market) + elif PerpMarket.isa(market): + perp_market = PerpMarket.ensure(market) perp_index: int = perp_market.group.slot_by_perp_market_address( - loaded_market.address + market.address ).index return StubOracle(perp_market, perp_index, perp_market.group.cache) diff --git a/mango/perpmarket.py b/mango/perpmarket.py index 90f3b69..0ffb9ab 100644 --- a/mango/perpmarket.py +++ b/mango/perpmarket.py @@ -158,13 +158,19 @@ def isa(market: Market) -> bool: @staticmethod def ensure(market: Market) -> "PerpMarket": if not PerpMarket.isa(market): - raise Exception(f"Market for {market.symbol} is not a Perp market") + raise Exception( + f"Market for {market.fully_qualified_symbol} is not a Perp market" + ) return typing.cast(PerpMarket, market) @property def symbol(self) -> str: return f"{self.base.symbol}-PERP" + @property + def fully_qualified_symbol(self) -> str: + return f"perp:{self.symbol}" + @property def group(self) -> Group: return self.underlying_perp_market.group @@ -286,10 +292,6 @@ def load( def build_cancel_order_instructions( self, order: Order, ok_if_missing: bool = False ) -> CombinableInstructions: - if self.perp_market.underlying_perp_market is None: - raise Exception( - f"PerpMarket {self.perp_market.symbol} has not been loaded." - ) return build_perp_cancel_order_instructions( self.context, self.wallet, @@ -300,10 +302,6 @@ def build_cancel_order_instructions( ) def build_place_order_instructions(self, order: Order) -> CombinableInstructions: - if self.perp_market.underlying_perp_market is None: - raise Exception( - f"PerpMarket {self.perp_market.symbol} has not been loaded." - ) return build_perp_place_order_instructions( self.context, self.wallet, @@ -327,11 +325,6 @@ def build_settle_instructions(self) -> CombinableInstructions: def build_crank_instructions( self, addresses: typing.Sequence[PublicKey], limit: Decimal = Decimal(32) ) -> CombinableInstructions: - if self.perp_market.underlying_perp_market is None: - raise Exception( - f"PerpMarket {self.perp_market.symbol} has not been loaded." - ) - distinct_addresses: typing.List[PublicKey] = [self.account.address] for address in addresses: if address not in distinct_addresses: @@ -371,10 +364,6 @@ def build_redeem_instructions(self) -> CombinableInstructions: def build_cancel_all_orders_instructions( self, limit: Decimal = Decimal(32) ) -> CombinableInstructions: - if self.perp_market.underlying_perp_market is None: - raise Exception( - f"PerpMarket {self.perp_market.symbol} has not been loaded." - ) return build_perp_cancel_all_orders_instructions( self.context, self.wallet, @@ -419,14 +408,10 @@ def ensure(market_ops: MarketOperations) -> "PerpMarketOperations": def perp_market(self) -> PerpMarket: return self.market_instruction_builder.perp_market - @property - def market_name(self) -> str: - return self.perp_market.symbol - def cancel_order( self, order: Order, ok_if_missing: bool = False ) -> typing.Sequence[str]: - self._logger.info(f"Cancelling {self.market_name} order {order}.") + self._logger.info(f"Cancelling {self.symbol} order {order}.") signers: CombinableInstructions = CombinableInstructions.from_wallet( self.wallet ) @@ -447,7 +432,7 @@ def place_order( self.wallet ) order_with_client_id: Order = order.with_update(client_id=client_id) - self._logger.info(f"Placing {self.market_name} order {order_with_client_id}.") + self._logger.info(f"Placing {self.symbol} order {order_with_client_id}.") place: CombinableInstructions = ( self.market_instruction_builder.build_place_order_instructions( order_with_client_id @@ -507,7 +492,7 @@ def _build_crank( ) def __str__(self) -> str: - return f"""« PerpMarketOperations [{self.market_name}] »""" + return f"""« PerpMarketOperations [{self.symbol}] »""" # # 🥭 PerpMarketStub class @@ -534,6 +519,10 @@ def __init__( ) self.group_address: PublicKey = group_address + @property + def fully_qualified_symbol(self) -> str: + return f"perp:{self.symbol}" + def load( self, context: Context, group: typing.Optional[Group] = None ) -> PerpMarket: diff --git a/mango/porcelain.py b/mango/porcelain.py index 4ea3404..828e50f 100644 --- a/mango/porcelain.py +++ b/mango/porcelain.py @@ -120,7 +120,7 @@ def instruction_builder( ) -> MarketInstructionBuilder: loaded_market: LoadedMarket = market(context, symbol) if dry_run: - return NullMarketInstructionBuilder(loaded_market.symbol) + return NullMarketInstructionBuilder(loaded_market.fully_qualified_symbol) if SerumMarket.isa(loaded_market): return SerumMarketInstructionBuilder.load( diff --git a/mango/serummarket.py b/mango/serummarket.py index 3e76172..d663540 100644 --- a/mango/serummarket.py +++ b/mango/serummarket.py @@ -89,9 +89,15 @@ def isa(market: Market) -> bool: @staticmethod def ensure(market: Market) -> "SerumMarket": if not SerumMarket.isa(market): - raise Exception(f"Market for {market.symbol} is not a Serum market") + raise Exception( + f"Market for {market.fully_qualified_symbol} is not a Serum market" + ) return typing.cast(SerumMarket, market) + @property + def fully_qualified_symbol(self) -> str: + return f"serum:{self.symbol}" + @property def bids_address(self) -> PublicKey: return self.underlying_serum_market.state.bids() @@ -426,7 +432,9 @@ def serum_market(self) -> SerumMarket: def cancel_order( self, order: Order, ok_if_missing: bool = False ) -> typing.Sequence[str]: - self._logger.info(f"Cancelling {self.serum_market.symbol} order {order}.") + self._logger.info( + f"Cancelling {self.serum_market.fully_qualified_symbol} order {order}." + ) signers: CombinableInstructions = CombinableInstructions.from_wallet( self.wallet ) @@ -463,7 +471,7 @@ def place_order( order_type=order.order_type, ) self._logger.info( - f"Placing {self.serum_market.symbol} order {order_with_client_id}." + f"Placing {self.serum_market.fully_qualified_symbol} order {order_with_client_id}." ) place: CombinableInstructions = ( self.market_instruction_builder.build_place_order_instructions( @@ -546,7 +554,7 @@ def _build_crank( ) def __str__(self) -> str: - return f"""« SerumMarketOperations [{self.serum_market.symbol}] »""" + return f"""« SerumMarketOperations [{self.serum_market.fully_qualified_symbol}] »""" # # 🥭 SerumMarketStub class @@ -573,6 +581,10 @@ def __init__( self.base: Token = base self.quote: Token = quote + @property + def fully_qualified_symbol(self) -> str: + return f"serum:{self.symbol}" + def load(self, context: Context) -> SerumMarket: underlying_serum_market: PySerumMarket = PySerumMarket.load( context.client.compatible_client, diff --git a/mango/simplemarketmaking/simplemarketmaker.py b/mango/simplemarketmaking/simplemarketmaker.py index 22c683d..4022ec5 100644 --- a/mango/simplemarketmaking/simplemarketmaker.py +++ b/mango/simplemarketmaking/simplemarketmaker.py @@ -252,7 +252,7 @@ def update_health_on_successful_iteration(self) -> None: ) def __str__(self) -> str: - return f"""« SimpleMarketMaker for market '{self.market.symbol}' »""" + return f"""« SimpleMarketMaker for market '{self.market.fully_qualified_symbol}' »""" def __repr__(self) -> str: return f"{self}" diff --git a/mango/spotmarket.py b/mango/spotmarket.py index 31977e2..d16f0a9 100644 --- a/mango/spotmarket.py +++ b/mango/spotmarket.py @@ -93,9 +93,15 @@ def isa(market: Market) -> bool: @staticmethod def ensure(market: Market) -> "SpotMarket": if not SpotMarket.isa(market): - raise Exception(f"Market for {market.symbol} is not a Spot market") + raise Exception( + f"Market for {market.fully_qualified_symbol} is not a Spot market" + ) return typing.cast(SpotMarket, market) + @property + def fully_qualified_symbol(self) -> str: + return f"spot:{self.symbol}" + @property def bids_address(self) -> PublicKey: return self.underlying_serum_market.state.bids() @@ -410,7 +416,7 @@ def build_create_openorders_instructions(self) -> CombinableInstructions: ) def __str__(self) -> str: - return f"« SpotMarketInstructionBuilder [{self.spot_market.symbol}] »" + return f"« SpotMarketInstructionBuilder [{self.spot_market.fully_qualified_symbol}] »" # # 🥭 SpotMarketOperations class @@ -459,7 +465,9 @@ def group(self) -> Group: def cancel_order( self, order: Order, ok_if_missing: bool = False ) -> typing.Sequence[str]: - self._logger.info(f"Cancelling {self.spot_market.symbol} order {order}.") + self._logger.info( + f"Cancelling {self.spot_market.fully_qualified_symbol} order {order}." + ) signers: CombinableInstructions = CombinableInstructions.from_wallet( self.wallet ) @@ -486,7 +494,9 @@ def place_order( order_with_client_id: Order = order.with_update( client_id=client_id ).with_update(owner=self.open_orders_address or SYSTEM_PROGRAM_ADDRESS) - self._logger.info(f"Placing {self.spot_market.symbol} order {order}.") + self._logger.info( + f"Placing {self.spot_market.fully_qualified_symbol} order {order}." + ) place: CombinableInstructions = ( self.market_instruction_builder.build_place_order_instructions( order_with_client_id @@ -577,7 +587,7 @@ def _build_crank( ) def __str__(self) -> str: - return f"« SpotMarketOperations [{self.spot_market.symbol}] »" + return f"« SpotMarketOperations [{self.spot_market.fully_qualified_symbol}] »" # # 🥭 SpotMarketStub class @@ -606,6 +616,10 @@ def __init__( self.quote: Token = quote self.group_address: PublicKey = group_address + @property + def fully_qualified_symbol(self) -> str: + return f"spot:{self.symbol}" + def load(self, context: Context, group: typing.Optional[Group]) -> SpotMarket: actual_group: Group = group or Group.load(context, self.group_address) underlying_serum_market: PySerumMarket = PySerumMarket.load( diff --git a/mango/watchers.py b/mango/watchers.py index 4a3b316..ecc391a 100644 --- a/mango/watchers.py +++ b/mango/watchers.py @@ -31,7 +31,7 @@ from .instrumentvalue import InstrumentValue from .inventory import Inventory from .loadedmarket import LoadedMarket -from .markets import Market, InventorySource +from .markets import InventorySource from .modelstate import EventQueue from .observables import Disposable, LatestItemObserverSubscriber from .openorders import OpenOrders @@ -137,7 +137,7 @@ def build_spot_open_orders_watcher( ) open_orders_address = market_operations.create_openorders() logging.info( - f"Created {spot_market.symbol} OpenOrders at: {open_orders_address}" + f"Created {spot_market.fully_qualified_symbol} OpenOrders at: {open_orders_address}" ) spot_open_orders_subscription = WebSocketAccountSubscription[OpenOrders]( @@ -193,7 +193,7 @@ def build_serum_open_orders_watcher( open_orders_address = create_open_orders.signers[0].public_key logging.info( - f"Creating OpenOrders account for market {serum_market.symbol} at {open_orders_address}." + f"Creating OpenOrders account for market {serum_market.fully_qualified_symbol} at {open_orders_address}." ) signers: CombinableInstructions = CombinableInstructions.from_wallet(wallet) transaction_ids = (signers + create_open_orders).execute(context) @@ -262,13 +262,13 @@ def build_price_watcher( health_check: HealthCheck, disposer: Disposable, provider_name: str, - market: Market, + market: LoadedMarket, ) -> LatestItemObserverSubscriber[Price]: oracle_provider: OracleProvider = create_oracle_provider(context, provider_name) oracle = oracle_provider.oracle_for_market(context, market) if oracle is None: raise Exception( - f"Could not find oracle for market {market.symbol} from provider {provider_name}." + f"Could not find oracle for market {market.fully_qualified_symbol} from provider {provider_name}." ) initial_price = oracle.fetch_price(context) @@ -373,7 +373,7 @@ def build_orderbook_watcher( or orderbook_infos[1] is None ): raise Exception( - f"Could not find {market.symbol} order book at addresses {orderbook_addresses}." + f"Could not find {market.fully_qualified_symbol} order book at addresses {orderbook_addresses}." ) initial_orderbook: OrderBook = market.parse_account_infos_to_orderbook( diff --git a/tests/fakes.py b/tests/fakes.py index d2c4e66..2e1854b 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -182,6 +182,10 @@ def fake_loaded_market( base_lot_size: Decimal = Decimal(1), quote_lot_size: Decimal = Decimal(1) ) -> mango.LoadedMarket: class FakeLoadedMarket(mango.LoadedMarket): + @property + def fully_qualified_symbol(self) -> str: + return "full:MARKET/SYMBOL" + @property def bids_address(self) -> PublicKey: return fake_seeded_public_key("bids_address") @@ -270,7 +274,7 @@ def fake_order_id(index: int, price: int) -> int: def fake_price( - market: mango.Market = fake_loaded_market(), + market: mango.LoadedMarket = fake_loaded_market(), price: Decimal = Decimal(100), bid: Decimal = Decimal(99), ask: Decimal = Decimal(101), diff --git a/tests/test_marketlookup.py b/tests/test_marketlookup.py index c930c11..b92918f 100644 --- a/tests/test_marketlookup.py +++ b/tests/test_marketlookup.py @@ -10,7 +10,9 @@ def test_market_symbol_matching() -> None: assert mango.Market.symbols_match("eth/usdc", "eth/usdc") assert mango.Market.symbols_match("btc/usdc", "BTC/USDC") assert mango.Market.symbols_match("ETH/USDC", "eth/usdc") + assert mango.Market.symbols_match("serum:ETH/USDC", "serum:eth/usdc") assert not mango.Market.symbols_match("ETH/USDC", "BTC/USDC") + assert not mango.Market.symbols_match("serum:ETH/USDC", "spot:eth/usdc") def test_serum_market_lookup() -> None: