Added DesiredOrdersBuilder and a couple of basic implementations.

This commit is contained in:
Geoff Taylor 2021-07-12 20:09:36 +01:00
parent 80886c106c
commit a00738a67c
4 changed files with 182 additions and 43 deletions

View File

@ -0,0 +1,38 @@
# # ⚠ 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
from decimal import Decimal
# # 🥭 DesiredOrder class
#
# Encapsulates a single order we want to be present on the orderbook.
#
class DesiredOrder:
def __init__(self, side: mango.Side, order_type: mango.OrderType, price: Decimal, quantity: Decimal):
self.side: mango.Side = side
self.order_type: mango.OrderType = order_type
self.price: Decimal = price
self.quantity: Decimal = quantity
def __str__(self) -> str:
return f"""« 𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛: {self.order_type} - {self.side} {self.quantity} at {self.price} »"""
def __repr__(self) -> str:
return f"{self}"

View File

@ -0,0 +1,58 @@
# # ⚠ 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 abc
import logging
import mango
import typing
from .desiredorder import DesiredOrder
from .modelstate import ModelState
# # 🥭 DesiredOrdersBuilder class
#
# A builder that builds a list of orders we'd like to be on the orderbook.
#
# The logic of what orders to create will be implemented in a derived class.
#
class DesiredOrdersBuilder(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[DesiredOrder]:
raise NotImplementedError("DesiredOrdersBuilder.build() 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[DesiredOrder]:
return []
def __str__(self) -> str:
return f"« 𝙽𝚞𝚕𝚕𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛𝚜𝙱𝚞𝚒𝚕𝚍𝚎𝚛 »"

View File

@ -0,0 +1,65 @@
# # ⚠ 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 decimal import Decimal
from .desiredorder import DesiredOrder
from .desiredordersbuilder import DesiredOrdersBuilder
from .modelstate import ModelState
# # 🥭 FixedRatioDesiredOrdersBuilder class
#
# Builds orders using a fixed spread ratio and a fixed position size ratio.
#
class FixedRatioDesiredOrdersBuilder(DesiredOrdersBuilder):
def __init__(self, spread_ratio: Decimal, position_size_ratio: Decimal):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.spread_ratio: Decimal = spread_ratio
self.position_size_ratio: Decimal = position_size_ratio
def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[DesiredOrder]:
price: mango.Price = model_state.price
inventory: typing.Sequence[typing.Optional[mango.TokenValue]] = model_state.account.net_assets
base_tokens: typing.Optional[mango.TokenValue] = mango.TokenValue.find_by_token(inventory, price.market.base)
if base_tokens is None:
raise Exception(f"Could not find market-maker base token {price.market.base.symbol} in inventory.")
quote_tokens: typing.Optional[mango.TokenValue] = mango.TokenValue.find_by_token(inventory, price.market.quote)
if quote_tokens is None:
raise Exception(f"Could not find market-maker quote token {price.market.quote.symbol} in inventory.")
total = (base_tokens.value * price.mid_price) + quote_tokens.value
position_size = total * self.position_size_ratio
buy_size: Decimal = position_size / price.mid_price
sell_size: Decimal = position_size / price.mid_price
bid: Decimal = price.mid_price - (price.mid_price * self.spread_ratio)
ask: Decimal = price.mid_price + (price.mid_price * self.spread_ratio)
return [
DesiredOrder(mango.Side.BUY, mango.OrderType.POST_ONLY, bid, buy_size),
DesiredOrder(mango.Side.SELL, mango.OrderType.POST_ONLY, ask, sell_size)
]
def __str__(self) -> str:
return f"« 𝙵𝚒𝚡𝚎𝚍𝚁𝚊𝚝𝚒𝚘𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛𝚜𝙱𝚞𝚒𝚕𝚍𝚎𝚛 using ratios - spread: {self.spread_ratio}, position size: {self.position_size_ratio} »"

View File

@ -15,13 +15,14 @@
import logging import logging
import mango import mango
import traceback import traceback
import typing import typing
from decimal import Decimal from decimal import Decimal
from mango.marketmaking.modelstate import ModelState
from .desiredordersbuilder import DesiredOrdersBuilder
from .modelstate import ModelState
# # 🥭 MarketMaker class # # 🥭 MarketMaker class
@ -32,45 +33,20 @@ from mango.marketmaking.modelstate import ModelState
class MarketMaker: class MarketMaker:
def __init__(self, wallet: mango.Wallet, market: mango.Market, def __init__(self, wallet: mango.Wallet, market: mango.Market,
market_instruction_builder: mango.MarketInstructionBuilder, market_instruction_builder: mango.MarketInstructionBuilder,
spread_ratio: Decimal, position_size_ratio: Decimal): desired_orders_builder: DesiredOrdersBuilder):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
self.wallet: mango.Wallet = wallet self.wallet: mango.Wallet = wallet
self.market: mango.Market = market self.market: mango.Market = market
self.market_instruction_builder: mango.MarketInstructionBuilder = market_instruction_builder self.market_instruction_builder: mango.MarketInstructionBuilder = market_instruction_builder
self.spread_ratio: Decimal = spread_ratio self.desired_orders_builder: DesiredOrdersBuilder = desired_orders_builder
self.position_size_ratio: Decimal = position_size_ratio
self.buy_client_ids: typing.List[int] = [] self.buy_client_ids: typing.List[int] = []
self.sell_client_ids: typing.List[int] = [] self.sell_client_ids: typing.List[int] = []
def calculate_order_prices(self, model_state: ModelState) -> typing.Tuple[Decimal, Decimal]:
price: mango.Price = model_state.price
bid: Decimal = price.mid_price - (price.mid_price * self.spread_ratio)
ask: Decimal = price.mid_price + (price.mid_price * self.spread_ratio)
return (bid, ask)
def calculate_order_sizes(self, model_state: ModelState) -> typing.Tuple[Decimal, Decimal]:
price: mango.Price = model_state.price
inventory: typing.Sequence[typing.Optional[mango.TokenValue]] = model_state.account.net_assets
base_tokens: typing.Optional[mango.TokenValue] = mango.TokenValue.find_by_token(inventory, price.market.base)
if base_tokens is None:
raise Exception(f"Could not find market-maker base token {price.market.base.symbol} in inventory.")
quote_tokens: typing.Optional[mango.TokenValue] = mango.TokenValue.find_by_token(inventory, price.market.quote)
if quote_tokens is None:
raise Exception(f"Could not find market-maker quote token {price.market.quote.symbol} in inventory.")
total = (base_tokens.value * price.mid_price) + quote_tokens.value
position_size = total * self.position_size_ratio
buy_size: Decimal = position_size / price.mid_price
sell_size: Decimal = position_size / price.mid_price
return (buy_size, sell_size)
def pulse(self, context: mango.Context, model_state: ModelState): def pulse(self, context: mango.Context, model_state: ModelState):
try: try:
bid, ask = self.calculate_order_prices(model_state) desired_orders = self.desired_orders_builder.build(context, model_state)
buy_size, sell_size = self.calculate_order_sizes(model_state)
payer = mango.CombinableInstructions.from_wallet(self.wallet) payer = mango.CombinableInstructions.from_wallet(self.wallet)
cancellations = mango.CombinableInstructions.empty() cancellations = mango.CombinableInstructions.empty()
@ -83,22 +59,24 @@ class MarketMaker:
cancel = self.market_instruction_builder.build_cancel_order_instructions(order) cancel = self.market_instruction_builder.build_cancel_order_instructions(order)
cancellations += cancel cancellations += cancel
buy_client_id = context.random_client_id() place_orders = mango.CombinableInstructions.empty()
self.buy_client_ids += [buy_client_id] for desired_order in desired_orders:
self.logger.info(f"Placing BUY order for {buy_size} at price {bid} with client ID: {buy_client_id}") client_id = context.random_client_id()
buy = self.market_instruction_builder.build_place_order_instructions( if desired_order.side == mango.Side.BUY:
mango.Side.BUY, mango.OrderType.POST_ONLY, bid, buy_size, buy_client_id) self.buy_client_ids += [client_id]
else:
self.sell_client_ids += [client_id]
sell_client_id = context.random_client_id() self.logger.info(
self.sell_client_ids += [sell_client_id] f"Placing {desired_order.side} order for {desired_order.quantity} at price {desired_order.price} with client ID: {client_id}")
self.logger.info(f"Placing SELL order for {sell_size} at price {ask} with client ID: {sell_client_id}") place_order = self.market_instruction_builder.build_place_order_instructions(
sell = self.market_instruction_builder.build_place_order_instructions( desired_order.side, desired_order.order_type, desired_order.price, desired_order.quantity, client_id)
mango.Side.SELL, mango.OrderType.POST_ONLY, ask, sell_size, sell_client_id) place_orders += place_order
settle = self.market_instruction_builder.build_settle_instructions() settle = self.market_instruction_builder.build_settle_instructions()
crank = self.market_instruction_builder.build_crank_instructions() crank = self.market_instruction_builder.build_crank_instructions()
(payer + cancellations + buy + sell + settle + crank).execute(context) (payer + cancellations + place_orders + settle + crank).execute(context)
except Exception as exception: except Exception as exception:
self.logger.error(f"Market-maker error on pulse: {exception} - {traceback.format_exc()}") self.logger.error(f"Market-maker error on pulse: {exception} - {traceback.format_exc()}")