diff --git a/bin/marketmaker b/bin/marketmaker index 68d02c4..cbb711e 100755 --- a/bin/marketmaker +++ b/bin/marketmaker @@ -15,25 +15,18 @@ 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("--position-size-ratio", type=Decimal, required=True, - help="fraction of the token inventory to be bought or sold in each order") -parser.add_argument("--quote-position-bias", type=Decimal, default=Decimal(0), - help="bias to apply to quotes based on inventory position") 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("--minimum-charge-ratio", type=Decimal, default=Decimal("0.0005"), - help="minimum fraction of the price to be accept as a spread") -parser.add_argument("--confidence-interval-level", type=Decimal, action="append", - help="the levels of weighting to apply to the confidence interval from the oracle: e.g. 1 - use the oracle confidence interval as the spread, 2 (risk averse, default) - multiply the oracle confidence interval by 2 to get the spread, 0.5 (aggressive) halve the oracle confidence interval to get the spread (can be specified multiple times to give multiple levels)") -parser.add_argument("--order-type", type=mango.OrderType, default=mango.OrderType.POST_ONLY, - 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, @@ -130,13 +123,10 @@ manager.open() order_reconciler = mango.marketmaking.ToleranceOrderReconciler( args.existing_order_tolerance, args.existing_order_tolerance) -confidence_interval_levels = args.confidence_interval_level -if len(confidence_interval_levels) == 0: - confidence_interval_levels = [Decimal(2)] -desired_orders_builder = mango.marketmaking.ConfidenceIntervalDesiredOrdersBuilder( - args.position_size_ratio, args.minimum_charge_ratio, confidence_interval_levels, args.order_type, args.quote_position_bias) + +desired_orders_chain: chain.Chain = chainbuilder.ChainBuilder.from_command_line_parameters(args) market_maker = mango.marketmaking.MarketMaker( - wallet, market, market_instruction_builder, desired_orders_builder, order_reconciler) + 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, diff --git a/bin/watch-address b/bin/watch-address index f321c3f..e2cd1d6 100755 --- a/bin/watch-address +++ b/bin/watch-address @@ -14,11 +14,6 @@ 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.ContextBuilder.add_command_line_parameters(parser) diff --git a/bin/watch-minimum-balances b/bin/watch-minimum-balances index 2dae5f9..c119954 100755 --- a/bin/watch-minimum-balances +++ b/bin/watch-minimum-balances @@ -17,11 +17,6 @@ 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="Watches one or many accounts (via a websocket) and sends a notification if the SOL balance falls below the --minimum-sol-balance threshold.") diff --git a/mango/__init__.py b/mango/__init__.py index b561705..86f79bc 100644 --- a/mango/__init__.py +++ b/mango/__init__.py @@ -120,3 +120,8 @@ logging.setLogRecordFactory(emojified_record_factory) logging.basicConfig(level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S", format="%(asctime)s %(level_emoji)s %(name)-12.12s %(message)s") + +# Stop libraries outputting lots of information unless it's a warning or worse. +logging.getLogger("requests").setLevel(logging.WARNING) +logging.getLogger("urllib3").setLevel(logging.WARNING) +logging.getLogger("solanaweb3").setLevel(logging.WARNING) diff --git a/mango/lotsizeconverter.py b/mango/lotsizeconverter.py index 38f81d3..928ec61 100644 --- a/mango/lotsizeconverter.py +++ b/mango/lotsizeconverter.py @@ -18,10 +18,9 @@ from decimal import Decimal from .token import Token + # # πŸ₯­ LotSizeConverter class # - - class LotSizeConverter(): def __init__(self, base: Token, base_lot_size: Decimal, quote: Token, quote_lot_size: Decimal): self.base: Token = base @@ -29,6 +28,10 @@ class LotSizeConverter(): self.quote: Token = quote self.quote_lot_size: Decimal = quote_lot_size + @property + def tick_size(self) -> Decimal: + return self.price_lots_to_value(Decimal(1)) + def price_lots_to_native(self, price_lots: Decimal) -> Decimal: return (price_lots * self.quote_lot_size) / self.base_lot_size @@ -52,8 +55,6 @@ class LotSizeConverter(): # # πŸ₯­ NullLotSizeConverter class # - - class NullLotSizeConverter(LotSizeConverter): def __init__(self): super().__init__(None, Decimal(1), None, Decimal(1)) @@ -72,3 +73,29 @@ class NullLotSizeConverter(LotSizeConverter): def __str__(self) -> str: return "Β« π™½πšžπš•πš•π™»πš˜πšπš‚πš’πš£πšŽπ™²πš˜πš—πšŸπšŽπš›πšπšŽπš› Β»" + + +# # πŸ₯­ RaisingLotSizeConverter class +# +class RaisingLotSizeConverter(LotSizeConverter): + def __init__(self): + super().__init__(None, Decimal(-1), None, Decimal(-1)) + + def price_lots_to_native(self, price_lots: Decimal) -> Decimal: + raise NotImplementedError( + "RaisingLotSizeConverter.price_lots_to_native() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.") + + def quantity_lots_to_native(self, quantity_lots: Decimal) -> Decimal: + raise NotImplementedError( + "RaisingLotSizeConverter.quantity_lots_to_native() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.") + + def price_lots_to_value(self, price_lots: Decimal) -> Decimal: + raise NotImplementedError( + "RaisingLotSizeConverter.price_lots_to_value() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.") + + def quantity_lots_to_value(self, quantity_lots: Decimal) -> Decimal: + raise NotImplementedError( + "RaisingLotSizeConverter.quantity_lots_to_value() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.") + + def __str__(self) -> str: + return "Β« π™½πšžπš•πš•π™»πš˜πšπš‚πš’πš£πšŽπ™²πš˜πš—πšŸπšŽπš›πšπšŽπš› Β»" diff --git a/mango/market.py b/mango/market.py index aa1888c..8fa0713 100644 --- a/mango/market.py +++ b/mango/market.py @@ -20,6 +20,7 @@ import logging from solana.publickey import PublicKey +from .lotsizeconverter import LotSizeConverter from .token import Token @@ -40,13 +41,14 @@ class InventorySource(enum.Enum): # class Market(metaclass=abc.ABCMeta): - def __init__(self, program_id: PublicKey, address: PublicKey, inventory_source: InventorySource, base: Token, quote: Token): + def __init__(self, program_id: PublicKey, address: PublicKey, inventory_source: InventorySource, base: Token, quote: Token, lot_size_converter: LotSizeConverter): self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.program_id: PublicKey = program_id self.address: PublicKey = address self.inventory_source: InventorySource = inventory_source self.base: Token = base self.quote: Token = quote + self.lot_size_converter: LotSizeConverter = lot_size_converter @property def symbol(self) -> str: diff --git a/mango/marketmaking/__init__.py b/mango/marketmaking/__init__.py index e667ba3..def8d1f 100644 --- a/mango/marketmaking/__init__.py +++ b/mango/marketmaking/__init__.py @@ -1,6 +1,3 @@ -from .confidenceintervaldesiredordersbuilder import ConfidenceIntervalDesiredOrdersBuilder -from .desiredordersbuilder import DesiredOrdersBuilder, NullDesiredOrdersBuilder -from .fixedratiosdesiredordersbuilder import FixedRatiosDesiredOrdersBuilder from .marketmaker import MarketMaker from .modelstate import ModelState from .orderreconciler import OrderReconciler, NullOrderReconciler diff --git a/mango/marketmaking/confidenceintervaldesiredordersbuilder.py b/mango/marketmaking/confidenceintervaldesiredordersbuilder.py deleted file mode 100644 index 7c8798d..0000000 --- a/mango/marketmaking/confidenceintervaldesiredordersbuilder.py +++ /dev/null @@ -1,82 +0,0 @@ -# # ⚠ 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 mango -import typing - -from decimal import Decimal - -from .desiredordersbuilder import DesiredOrdersBuilder -from .modelstate import ModelState - - -# # πŸ₯­ ConfidenceIntervalDesiredOrdersBuilder class -# -# Builds orders using a fixed position size ratio but with a spread based on the confidence in the oracle price. -# -class ConfidenceIntervalDesiredOrdersBuilder(DesiredOrdersBuilder): - def __init__(self, position_size_ratio: Decimal, min_price_ratio: Decimal, confidence_interval_levels: typing.Sequence[Decimal] = [Decimal(2)], order_type: mango.OrderType = mango.OrderType.POST_ONLY, quote_position_bias: Decimal = Decimal(0)): - super().__init__() - self.position_size_ratio: Decimal = position_size_ratio - self.min_price_ratio: Decimal = min_price_ratio - self.confidence_interval_levels: typing.Sequence[Decimal] = confidence_interval_levels - self.order_type: mango.OrderType = order_type - self.quote_position_bias: Decimal = quote_position_bias - - def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]: - price: mango.Price = model_state.price - if price.source.supports & mango.SupportedOracleFeature.CONFIDENCE == 0: - raise Exception(f"Price does not support confidence interval: {price}") - - base_tokens: mango.TokenValue = model_state.inventory.base - quote_tokens: mango.TokenValue = model_state.inventory.quote - - total = (base_tokens.value * price.mid_price) + quote_tokens.value - quote_value_to_risk = total * self.position_size_ratio - position_size = quote_value_to_risk / price.mid_price - - orders: typing.List[mango.Order] = [] - - # From Daffy on 20th August 2021: - # Formula to adjust price might look like this `pyth_price * (1 + (curr_pos / size) * pos_lean)` - # where pos_lean is a negative number - # - # size is the standard size you're quoting which I believe comes from the position-size-ratio - # - # So if my standard size I'm quoting is 0.0002 BTC, my current position is +0.0010 BTC, and pos_lean - # is -0.0001, you would move your quotes down by 0.0005 (or 5bps) - # (Private chat link: https://discord.com/channels/@me/832570058861314048/878343278523723787) - quote_position_bias = self.quote_position_bias * -1 - bias = (1 + (model_state.inventory.base.value / position_size) * quote_position_bias) - - for confidence_interval_level in self.confidence_interval_levels: - # From Daffy on 26th July 2021: max(pyth_conf * 2, price * min_charge) - # (Private chat link: https://discord.com/channels/@me/832570058861314048/869208592648134666) - charge = max(price.confidence * confidence_interval_level, price.mid_price * self.min_price_ratio) - bid: Decimal = (price.mid_price - charge) * bias - ask: Decimal = (price.mid_price + charge) * bias - - orders += [ - mango.Order.from_basic_info(mango.Side.BUY, price=bid, - quantity=position_size, order_type=self.order_type), - mango.Order.from_basic_info(mango.Side.SELL, price=ask, - quantity=position_size, order_type=self.order_type) - ] - - return orders - - def __str__(self) -> str: - return f"Β« π™²πš˜πš—πšπš’πšπšŽπš—πšŒπšŽπ™Έπš—πšπšŽπš›πšŸπšŠπš•π™³πšŽπšœπš’πš›πšŽπšπ™Ύπš›πšπšŽπš›πšœπ™±πšžπš’πš•πšπšŽπš› {self.order_type} - position size: {self.position_size_ratio}, min charge: {self.min_price_ratio}, confidence interval levels: {self.confidence_interval_levels} Β»" diff --git a/mango/marketmaking/marketmaker.py b/mango/marketmaking/marketmaker.py index 7afd108..4545840 100644 --- a/mango/marketmaking/marketmaker.py +++ b/mango/marketmaking/marketmaker.py @@ -21,28 +21,26 @@ import typing from datetime import datetime -from .desiredordersbuilder import DesiredOrdersBuilder from .modelstate import ModelState from ..observables import EventSource from .orderreconciler import OrderReconciler from .ordertracker import OrderTracker +from .orderchain.chain import Chain # # πŸ₯­ MarketMaker class # # An event-driven market-maker. # - class MarketMaker: def __init__(self, wallet: mango.Wallet, market: mango.Market, market_instruction_builder: mango.MarketInstructionBuilder, - desired_orders_builder: DesiredOrdersBuilder, - order_reconciler: OrderReconciler): + desired_orders_chain: Chain, order_reconciler: OrderReconciler): self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.wallet: mango.Wallet = wallet self.market: mango.Market = market self.market_instruction_builder: mango.MarketInstructionBuilder = market_instruction_builder - self.desired_orders_builder: DesiredOrdersBuilder = desired_orders_builder + self.desired_orders_chain: Chain = desired_orders_chain self.order_reconciler: OrderReconciler = order_reconciler self.order_tracker: OrderTracker = OrderTracker() @@ -56,7 +54,7 @@ class MarketMaker: try: payer = mango.CombinableInstructions.from_wallet(self.wallet) - desired_orders = self.desired_orders_builder.build(context, model_state) + desired_orders = self.desired_orders_chain.process(context, model_state) existing_orders = self.order_tracker.existing_orders(model_state) reconciled = self.order_reconciler.reconcile(model_state, existing_orders, desired_orders) diff --git a/mango/marketmaking/modelstate.py b/mango/marketmaking/modelstate.py index 82b6d96..9d8723d 100644 --- a/mango/marketmaking/modelstate.py +++ b/mango/marketmaking/modelstate.py @@ -18,6 +18,8 @@ import logging import mango import typing +from decimal import Decimal + # # πŸ₯­ ModelState class # @@ -73,6 +75,18 @@ class ModelState: def asks(self) -> typing.Sequence[mango.Order]: return self.asks_watcher.latest + @property + def top_bid(self) -> mango.Order: + return self.bids_watcher.latest[0] + + @property + def top_ask(self) -> mango.Order: + return self.asks_watcher.latest[0] + + @property + def spread(self) -> Decimal: + return self.top_ask.price - self.top_bid.price + @property def existing_orders(self) -> typing.Sequence[mango.PlacedOrder]: return self.placed_orders_container_watcher.latest.placed_orders diff --git a/mango/marketmaking/orderchain/__init__.py b/mango/marketmaking/orderchain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mango/marketmaking/orderchain/biasquoteonpositionelement.py b/mango/marketmaking/orderchain/biasquoteonpositionelement.py new file mode 100644 index 0000000..5aa9e2a --- /dev/null +++ b/mango/marketmaking/orderchain/biasquoteonpositionelement.py @@ -0,0 +1,64 @@ +# # ⚠ 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 mango +import typing + +from decimal import Decimal + +from .element import Element +from ..modelstate import ModelState + + +# # πŸ₯­ BiasQuoteOnPositionElement class +# +# Modifies an `Order`s price based on current inventory. Uses `quote_position_bias` to shift the price to sell +# more (if too much inventory) or buy more (if too little inventory). +# +class BiasQuoteOnPositionElement(Element): + def __init__(self, quote_position_bias: Decimal = Decimal(0)): + super().__init__() + self.quote_position_bias: Decimal = quote_position_bias + + def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]: + if self.quote_position_bias == 0: + # Zero bias results in no changes to orders. + return orders + + # From Daffy on 20th August 2021: + # Formula to adjust price might look like this `pyth_price * (1 + (curr_pos / size) * pos_lean)` + # where pos_lean is a negative number + # + # size is the standard size you're quoting which I believe comes from the position-size-ratio + # + # So if my standard size I'm quoting is 0.0002 BTC, my current position is +0.0010 BTC, and pos_lean + # is -0.0001, you would move your quotes down by 0.0005 (or 5bps) + # (Private chat link: https://discord.com/channels/@me/832570058861314048/878343278523723787) + quote_position_bias = self.quote_position_bias * -1 + new_orders: typing.List[mango.Order] = [] + for order in orders: + bias = (1 + (model_state.inventory.base.value / order.quantity) * quote_position_bias) + new_price: Decimal = order.price * bias + new_order: mango.Order = order.with_price(new_price) + self.logger.debug(f"""Order change - quote_position_bias {self.quote_position_bias} creates a bias factor of {bias}: + Old: {order} + New: {new_order}""") + new_orders += [new_order] + + return new_orders + + def __str__(self) -> str: + return f"Β« π™±πš’πšŠπšœπš€πšžπš˜πšπšŽπ™Ύπš—π™Ώπš˜πšœπš’πšπš’πš˜πš—π™΄πš•πšŽπš–πšŽπš—πš - bias: {self.quote_position_bias} Β»" diff --git a/mango/marketmaking/orderchain/chain.py b/mango/marketmaking/orderchain/chain.py new file mode 100644 index 0000000..ad3a3a1 --- /dev/null +++ b/mango/marketmaking/orderchain/chain.py @@ -0,0 +1,48 @@ +# # ⚠ 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 logging +import mango +import typing + +from ..modelstate import ModelState +from .element import Element + + +# # πŸ₯­ Chain class +# +# A `Chain` object takes a series of `Element`s and calls them in sequence to build a list of +# desired `Order`s. +# +# Only `Order`s returned from `process()` method are used as the final list of 'desired orders' for +# reconciling and possibly adding to the orderbook. +# +class Chain: + def __init__(self, elements: typing.Sequence[Element]): + self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) + self.elements: typing.Sequence[Element] = elements + + def process(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]: + orders: typing.Sequence[mango.Order] = [] + for element in self.elements: + orders = element.process(context, model_state, orders) + return orders + + def __repr__(self) -> str: + return f"{self}" + + def __str__(self) -> str: + return f"""Β« π™²πš‘πšŠπš’πš— of {len(self.elements)} elements Β»""" diff --git a/mango/marketmaking/orderchain/chainbuilder.py b/mango/marketmaking/orderchain/chainbuilder.py new file mode 100644 index 0000000..dc8effa --- /dev/null +++ b/mango/marketmaking/orderchain/chainbuilder.py @@ -0,0 +1,67 @@ +# # ⚠ 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 argparse +import typing + +from decimal import Decimal + +from ...orders import OrderType +from .biasquoteonpositionelement import BiasQuoteOnPositionElement +from .chain import Chain +from .confidenceintervalspreadelement import ConfidenceIntervalSpreadElement +from .element import Element +from .minimumchargeelement import MinimumChargeElement +from .preventpostonlycrossingbookelement import PreventPostOnlyCrossingBookElement + + +# # πŸ₯­ ChainBuilder class +# +# A `ChainBuilder` class to allow building a `Chain`, keeping parameter and constructor complexities all in +# one place. +# +class ChainBuilder: + @staticmethod + def add_command_line_parameters(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--order-type", type=OrderType, default=OrderType.POST_ONLY, + choices=list(OrderType), help="Order type: LIMIT, IOC or POST_ONLY") + parser.add_argument("--position-size-ratio", type=Decimal, required=True, + help="fraction of the token inventory to be bought or sold in each order") + parser.add_argument("--confidence-interval-level", type=Decimal, action="append", + help="the levels of weighting to apply to the confidence interval from the oracle: e.g. 1 - use the oracle confidence interval as the spread, 2 (risk averse, default) - multiply the oracle confidence interval by 2 to get the spread, 0.5 (aggressive) halve the oracle confidence interval to get the spread (can be specified multiple times to give multiple levels)") + parser.add_argument("--quote-position-bias", type=Decimal, default=Decimal(0), + help="bias to apply to quotes based on inventory position") + parser.add_argument("--minimum-charge-ratio", type=Decimal, default=Decimal("0.0005"), + help="minimum fraction of the price to be accept as a spread") + + # This function is the converse of `add_command_line_parameters()` - it takes + # an argument of parsed command-line parameters and expects to see the ones it added + # to that collection in the `add_command_line_parameters()` call. + # + # It then uses those parameters to create a properly-configured `Chain` object. + # + @staticmethod + def from_command_line_parameters(args: argparse.Namespace) -> Chain: + confidence_interval_levels: typing.Sequence[Decimal] = args.confidence_interval_level + if len(confidence_interval_levels) == 0: + confidence_interval_levels = [Decimal(2)] + elements: typing.List[Element] = [ + ConfidenceIntervalSpreadElement(args.position_size_ratio, confidence_interval_levels, args.order_type), + BiasQuoteOnPositionElement(args.quote_position_bias), + MinimumChargeElement(args.minimum_charge_ratio), + PreventPostOnlyCrossingBookElement() + ] + + return Chain(elements) diff --git a/mango/marketmaking/orderchain/confidenceintervalspreadelement.py b/mango/marketmaking/orderchain/confidenceintervalspreadelement.py new file mode 100644 index 0000000..97570a9 --- /dev/null +++ b/mango/marketmaking/orderchain/confidenceintervalspreadelement.py @@ -0,0 +1,70 @@ +# # ⚠ 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 mango +import typing + +from decimal import Decimal + +from .element import Element +from ..modelstate import ModelState + + +# # πŸ₯­ ConfidenceIntervalSpreadElement class +# +# Ignores any input `Order`s (so probably best at the head of the chain). Builds orders using a fixed position +# size ratio but with a spread based on the confidence in the oracle price. +# +class ConfidenceIntervalSpreadElement(Element): + def __init__(self, position_size_ratio: Decimal, confidence_interval_levels: typing.Sequence[Decimal] = [Decimal(2)], order_type: mango.OrderType = mango.OrderType.POST_ONLY): + super().__init__() + self.position_size_ratio: Decimal = position_size_ratio + self.confidence_interval_levels: typing.Sequence[Decimal] = confidence_interval_levels + self.order_type: mango.OrderType = order_type + + def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]: + price: mango.Price = model_state.price + if price.source.supports & mango.SupportedOracleFeature.CONFIDENCE == 0: + raise Exception(f"Price does not support confidence interval: {price}") + + base_tokens: mango.TokenValue = model_state.inventory.base + quote_tokens: mango.TokenValue = model_state.inventory.quote + + total = (base_tokens.value * price.mid_price) + quote_tokens.value + quote_value_to_risk = total * self.position_size_ratio + position_size = quote_value_to_risk / price.mid_price + + new_orders: typing.List[mango.Order] = [] + for confidence_interval_level in self.confidence_interval_levels: + charge = price.confidence * confidence_interval_level + bid: Decimal = price.mid_price - charge + ask: Decimal = price.mid_price + charge + + new_orders += [ + mango.Order.from_basic_info(mango.Side.BUY, price=bid, + quantity=position_size, order_type=self.order_type), + mango.Order.from_basic_info(mango.Side.SELL, price=ask, + quantity=position_size, order_type=self.order_type) + ] + + new_orders.sort(key=lambda ord: ord.price, reverse=True) + order_text = "\n ".join([f"{order}" for order in new_orders]) + self.logger.debug(f"""Initial desired orders - spread {model_state.spread} ({model_state.top_bid.price} / {model_state.top_ask.price}): + {order_text}""") + return new_orders + + def __str__(self) -> str: + return f"Β« π™²πš˜πš—πšπš’πšπšŽπš—πšŒπšŽπ™Έπš—πšπšŽπš›πšŸπšŠπš•πš‚πš™πš›πšŽπšŠπšπ™΄πš•πšŽπš–πšŽπš—πš {self.order_type} - position size: {self.position_size_ratio}, confidence interval levels: {self.confidence_interval_levels} Β»" diff --git a/mango/marketmaking/desiredordersbuilder.py b/mango/marketmaking/orderchain/element.py similarity index 52% rename from mango/marketmaking/desiredordersbuilder.py rename to mango/marketmaking/orderchain/element.py index c76bae1..8b80371 100644 --- a/mango/marketmaking/desiredordersbuilder.py +++ b/mango/marketmaking/orderchain/element.py @@ -19,39 +19,26 @@ import logging import mango import typing -from .modelstate import ModelState +from ..modelstate import ModelState -# # πŸ₯­ DesiredOrdersBuilder class +# # πŸ₯­ Element class # -# A builder that builds a list of orders we'd like to be on the orderbook. +# A base class for a part of a chain that can take in a sequence of elements and process them, changing +# them as desired. # -# The logic of what orders to create will be implemented in a derived class. +# Only `Order`s returned from `process()` method are passed to the next element of the chain. # - -class DesiredOrdersBuilder(metaclass=abc.ABCMeta): +class Element(metaclass=abc.ABCMeta): def __init__(self): self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) @abc.abstractmethod - def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]: - raise NotImplementedError("DesiredOrdersBuilder.build() is not implemented on the base type.") + def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]: + raise NotImplementedError("Element.process() is not implemented on the base type.") def __repr__(self) -> str: return f"{self}" - -# # πŸ₯­ NullDesiredOrdersBuilder class -# -# A no-op implementation of the `DesiredOrdersBuilder` that will never ask to create orders. -# - -class NullDesiredOrdersBuilder(DesiredOrdersBuilder): - def __init__(self): - super().__init__() - - def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]: - return [] - def __str__(self) -> str: - return "Β« π™½πšžπš•πš•π™³πšŽπšœπš’πš›πšŽπšπ™Ύπš›πšπšŽπš›πšœπ™±πšžπš’πš•πšπšŽπš› Β»" + return """Β« π™΄πš•πšŽπš–πšŽπš—πš Β»""" diff --git a/mango/marketmaking/fixedratiosdesiredordersbuilder.py b/mango/marketmaking/orderchain/fixedratioselement.py similarity index 79% rename from mango/marketmaking/fixedratiosdesiredordersbuilder.py rename to mango/marketmaking/orderchain/fixedratioselement.py index 962ddb3..db671f4 100644 --- a/mango/marketmaking/fixedratiosdesiredordersbuilder.py +++ b/mango/marketmaking/orderchain/fixedratioselement.py @@ -20,16 +20,16 @@ import typing from decimal import Decimal -from .desiredordersbuilder import DesiredOrdersBuilder -from .modelstate import ModelState +from .element import Element +from ..modelstate import ModelState # # πŸ₯­ FixedRatiosDesiredOrdersBuilder class # -# Builds orders using a fixed spread ratio and a fixed position size ratio. +# Ignores any input `Order`s (so probably best at the head of the chain). Builds orders using a fixed spread +# ratio and a fixed position size ratio. # - -class FixedRatiosDesiredOrdersBuilder(DesiredOrdersBuilder): +class FixedRatiosElement(Element): def __init__(self, spread_ratios: typing.Sequence[Decimal], position_size_ratios: typing.Sequence[Decimal], order_type: mango.OrderType = mango.OrderType.POST_ONLY): self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) if len(spread_ratios) != len(position_size_ratios): @@ -39,14 +39,14 @@ class FixedRatiosDesiredOrdersBuilder(DesiredOrdersBuilder): self.position_size_ratios: typing.Sequence[Decimal] = position_size_ratios self.order_type: mango.OrderType = order_type - def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]: + def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]: price: mango.Price = model_state.price base_tokens: mango.TokenValue = model_state.inventory.base quote_tokens: mango.TokenValue = model_state.inventory.quote total = (base_tokens.value * price.mid_price) + quote_tokens.value - orders: typing.List[mango.Order] = [] + new_orders: typing.List[mango.Order] = [] for counter in range(len(self.spread_ratios)): position_size_ratio = self.position_size_ratios[counter] quote_value_to_risk = total * position_size_ratio @@ -56,14 +56,14 @@ class FixedRatiosDesiredOrdersBuilder(DesiredOrdersBuilder): bid: Decimal = price.mid_price - (price.mid_price * spread_ratio) ask: Decimal = price.mid_price + (price.mid_price * spread_ratio) - orders += [ + new_orders += [ mango.Order.from_basic_info(mango.Side.BUY, price=bid, quantity=base_position_size, order_type=self.order_type), mango.Order.from_basic_info(mango.Side.SELL, price=ask, quantity=base_position_size, order_type=self.order_type) ] - return orders + return new_orders def __str__(self) -> str: - return f"Β« π™΅πš’πš‘πšŽπšπšπšŠπšπš’πš˜π™³πšŽπšœπš’πš›πšŽπšπ™Ύπš›πšπšŽπš›πšœπ™±πšžπš’πš•πšπšŽπš› using ratios - spread: {self.spread_ratios}, position size: {self.position_size_ratios} Β»" + return f"Β« π™΅πš’πš‘πšŽπšπšπšŠπšπš’πš˜πšœπ™΄πš•πšŽπš–πšŽπš—πš using ratios - spread: {self.spread_ratios}, position size: {self.position_size_ratios} Β»" diff --git a/mango/marketmaking/orderchain/minimumchargeelement.py b/mango/marketmaking/orderchain/minimumchargeelement.py new file mode 100644 index 0000000..2d1aedd --- /dev/null +++ b/mango/marketmaking/orderchain/minimumchargeelement.py @@ -0,0 +1,60 @@ +# # ⚠ 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 mango +import typing + +from decimal import Decimal + +from .element import Element +from ..modelstate import ModelState + + +# # πŸ₯­ MinimumChargeElement class +# +# May modifiy an `Order`s price if it would result in too small a difference from the mid-price, meaning less +# of a charge if that `Order` is filled. +# +class MinimumChargeElement(Element): + def __init__(self, minimum_charge_ratio: Decimal = Decimal(0)): + super().__init__() + self.minimum_charge_ratio: Decimal = minimum_charge_ratio + + def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]: + # From Daffy on 26th July 2021: max(pyth_conf * 2, price * min_charge) + # (Private chat link: https://discord.com/channels/@me/832570058861314048/869208592648134666) + new_orders: typing.List[mango.Order] = [] + for order in orders: + minimum_charge = model_state.price.mid_price * self.minimum_charge_ratio + current_charge = (model_state.price.mid_price - order.price).copy_abs() + if current_charge > minimum_charge: + # All OK with current order + new_orders += [order] + else: + if order.side == mango.Side.BUY: + new_price: Decimal = model_state.price.mid_price - minimum_charge + else: + new_price = model_state.price.mid_price + minimum_charge + new_order = order.with_price(new_price) + self.logger.debug(f"""Order change - price is less than minimum charge: + Old: {order} + New: {new_order}""") + new_orders += [new_order] + + return new_orders + + def __str__(self) -> str: + return f"Β« π™Όπš’πš—πš’πš–πšžπš–π™²πš‘πšŠπš›πšπšŽπ™΄πš•πšŽπš–πšŽπš—πš - minimum charge ratio: {self.minimum_charge_ratio} Β»" diff --git a/mango/marketmaking/orderchain/preventpostonlycrossingbookelement.py b/mango/marketmaking/orderchain/preventpostonlycrossingbookelement.py new file mode 100644 index 0000000..5100f56 --- /dev/null +++ b/mango/marketmaking/orderchain/preventpostonlycrossingbookelement.py @@ -0,0 +1,60 @@ +# # ⚠ 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 mango +import typing + +from decimal import Decimal + +from .element import Element +from ..modelstate import ModelState + + +# # πŸ₯­ PreventPostOnlyCrossingBookElement class +# +# May modifiy an `Order`s price if it would result in too small a difference from the mid-price, meaning less +# of a charge if that `Order` is filled. +# +class PreventPostOnlyCrossingBookElement(Element): + def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]: + new_orders: typing.List[mango.Order] = [] + for order in orders: + if order.order_type == mango.OrderType.POST_ONLY: + if order.side == mango.Side.BUY and order.price >= model_state.top_bid.price: + new_buy_price: Decimal = model_state.top_bid.price - model_state.market.lot_size_converter.tick_size + new_buy: mango.Order = order.with_price(new_buy_price) + self.logger.debug(f"""Order change - would cross the orderbook {model_state.top_bid.price} / {model_state.top_ask.price}: + Old: {order} + New: {new_buy}""") + new_orders += [new_buy] + elif order.side == mango.Side.SELL and order.price <= model_state.top_ask.price: + new_sell_price: Decimal = model_state.top_ask.price + model_state.market.lot_size_converter.tick_size + new_sell: mango.Order = order.with_price(new_sell_price) + self.logger.debug(f"""Order change - would cross the orderbook {model_state.top_bid.price} / {model_state.top_ask.price}: + Old: {order} + New: {new_sell}""") + new_orders += [new_sell] + else: + # All OK with current order + new_orders += [order] + else: + # Only change POST_ONLY orders. + new_orders += [order] + + return new_orders + + def __str__(self) -> str: + return "Β« π™Ώπš›πšŽπšŸπšŽπš—πšπ™Ώπš˜πšœπšπ™Ύπš—πš•πš’π™²πš›πš˜πšœπšœπš’πš—πšπ™±πš˜πš˜πš”π™΄πš•πšŽπš–πšŽπš—πš Β»" diff --git a/mango/notification.py b/mango/notification.py index 1f43ced..a405641 100644 --- a/mango/notification.py +++ b/mango/notification.py @@ -39,7 +39,6 @@ from .liquidationevent import LiquidationEvent # # Derived classes should not override `send()` since that is the interface outside classes call and it's used to ensure `NotificationTarget`s don't throw an exception when sending. # - class NotificationTarget(metaclass=abc.ABCMeta): def __init__(self): self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) @@ -76,8 +75,6 @@ class NotificationTarget(metaclass=abc.ABCMeta): # # The [Telegram instructions to create a bot](https://core.telegram.org/bots#creating-a-new-bot) # show you how to create the bot token. - - class TelegramNotificationTarget(NotificationTarget): def __init__(self, address): super().__init__() @@ -99,8 +96,6 @@ class TelegramNotificationTarget(NotificationTarget): # # The `DiscordNotificationTarget` sends messages to Discord. # - - class DiscordNotificationTarget(NotificationTarget): def __init__(self, address): super().__init__() @@ -168,8 +163,6 @@ class DiscordNotificationTarget(NotificationTarget): # ] # }' # ``` - - class MailjetNotificationTarget(NotificationTarget): def __init__(self, encoded_parameters): super().__init__() @@ -224,8 +217,6 @@ class MailjetNotificationTarget(NotificationTarget): # columns to the output. Token changes may arrive in different orders, so ordering of token # changes is not guaranteed to be consistent from transaction to transaction. # - - class CsvFileNotificationTarget(NotificationTarget): def __init__(self, filename): super().__init__() @@ -257,8 +248,6 @@ class CsvFileNotificationTarget(NotificationTarget): # This class takes a `NotificationTarget` and a filter function, and only calls the # `NotificationTarget` if the filter function returns `True` for the notification item. # - - class FilteringNotificationTarget(NotificationTarget): def __init__(self, inner_notifier: NotificationTarget, filter_func: typing.Callable[[typing.Any], bool]): super().__init__() @@ -279,17 +268,12 @@ class FilteringNotificationTarget(NotificationTarget): # `NotificationTarget` to be plugged in to the `logging` subsystem to receive log messages # and notify however it chooses. # - - class NotificationHandler(logging.StreamHandler): def __init__(self, target: NotificationTarget): logging.StreamHandler.__init__(self) self.target = target def emit(self, record): - # Don't send error logging from solanaweb3 - if record.name == "solanaweb3.rpc.httprpc.HTTPClient": - return message = self.format(record) self.target.send_notification(message) @@ -302,8 +286,6 @@ class NotificationHandler(logging.StreamHandler): # This is most likely used when parsing command-line arguments - this function can be used # in the `type` parameter of an `add_argument()` call. # - - def parse_subscription_target(target): protocol, destination = target.split(":", 1) diff --git a/mango/orders.py b/mango/orders.py index b832297..acbb5b0 100644 --- a/mango/orders.py +++ b/mango/orders.py @@ -122,6 +122,11 @@ class Order(typing.NamedTuple): return Order(id=self.id, side=self.side, price=self.price, quantity=self.quantity, client_id=client_id, owner=self.owner, order_type=self.order_type) + # Returns an identical order with the price changed. + def with_price(self, price: Decimal) -> "Order": + return Order(id=self.id, side=self.side, price=price, quantity=self.quantity, + client_id=self.client_id, owner=self.owner, order_type=self.order_type) + @staticmethod def from_serum_order(serum_order: PySerumOrder) -> "Order": price = Decimal(serum_order.info.price) diff --git a/mango/perpmarket.py b/mango/perpmarket.py index e3eac3f..c44a459 100644 --- a/mango/perpmarket.py +++ b/mango/perpmarket.py @@ -20,7 +20,7 @@ from solana.publickey import PublicKey from .accountinfo import AccountInfo from .context import Context from .group import Group -from .lotsizeconverter import LotSizeConverter +from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter from .market import Market, InventorySource from .orderbookside import PerpOrderBookSide from .orders import Order @@ -35,7 +35,7 @@ from .token import Token # class PerpMarket(Market): def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_perp_market: PerpMarketDetails): - super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote) + super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter()) self.underlying_perp_market: PerpMarketDetails = underlying_perp_market self.lot_size_converter: LotSizeConverter = LotSizeConverter( base, underlying_perp_market.base_lot_size, quote, underlying_perp_market.quote_lot_size) @@ -92,7 +92,7 @@ class PerpMarket(Market): # class PerpMarketStub(Market): def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey): - super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote) + super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter()) self.group_address: PublicKey = group_address def load(self, context: Context, group: typing.Optional[Group] = None) -> PerpMarket: diff --git a/mango/serummarket.py b/mango/serummarket.py index f27be8c..2a1d81e 100644 --- a/mango/serummarket.py +++ b/mango/serummarket.py @@ -22,6 +22,7 @@ from solana.publickey import PublicKey from .accountinfo import AccountInfo from .context import Context +from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter from .market import Market, InventorySource from .orders import Order from .serumeventqueue import SerumEvent, SerumEventQueue @@ -34,8 +35,10 @@ from .token import Token # class SerumMarket(Market): def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_serum_market: PySerumMarket): - super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote) + super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter()) self.underlying_serum_market: PySerumMarket = underlying_serum_market + self.lot_size_converter: LotSizeConverter = LotSizeConverter( + base, underlying_serum_market.state.base_lot_size, quote, underlying_serum_market.state.quote_lot_size) def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]: event_queue: SerumEventQueue = SerumEventQueue.load(context, self.underlying_serum_market.state.event_queue()) @@ -67,7 +70,7 @@ class SerumMarket(Market): # class SerumMarketStub(Market): def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token): - super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote) + super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter()) def load(self, context: Context) -> SerumMarket: underlying_serum_market: PySerumMarket = PySerumMarket.load( diff --git a/mango/spotmarket.py b/mango/spotmarket.py index 90e86d8..bdc1f27 100644 --- a/mango/spotmarket.py +++ b/mango/spotmarket.py @@ -23,6 +23,7 @@ from solana.publickey import PublicKey from .accountinfo import AccountInfo from .context import Context from .group import Group +from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter from .market import Market, InventorySource from .orders import Order from .serumeventqueue import SerumEvent, SerumEventQueue @@ -35,9 +36,11 @@ from .token import Token # class SpotMarket(Market): def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group: Group, underlying_serum_market: PySerumMarket): - super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote) + super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter()) self.group: Group = group self.underlying_serum_market: PySerumMarket = underlying_serum_market + self.lot_size_converter: LotSizeConverter = LotSizeConverter( + base, underlying_serum_market.state.base_lot_size, quote, underlying_serum_market.state.quote_lot_size) def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]: event_queue: SerumEventQueue = SerumEventQueue.load(context, self.underlying_serum_market.state.event_queue()) @@ -71,7 +74,7 @@ class SpotMarket(Market): class SpotMarketStub(Market): def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey): - super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote) + super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter()) self.group_address: PublicKey = group_address def load(self, context: Context, group: typing.Optional[Group]) -> SpotMarket: