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 mango
import traceback
import typing
from decimal import Decimal
from mango.marketmaking.modelstate import ModelState
from .desiredordersbuilder import DesiredOrdersBuilder
from .modelstate import ModelState
# # 🥭 MarketMaker class
@ -32,45 +33,20 @@ from mango.marketmaking.modelstate import ModelState
class MarketMaker:
def __init__(self, wallet: mango.Wallet, market: mango.Market,
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.wallet: mango.Wallet = wallet
self.market: mango.Market = market
self.market_instruction_builder: mango.MarketInstructionBuilder = market_instruction_builder
self.spread_ratio: Decimal = spread_ratio
self.position_size_ratio: Decimal = position_size_ratio
self.desired_orders_builder: DesiredOrdersBuilder = desired_orders_builder
self.buy_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):
try:
bid, ask = self.calculate_order_prices(model_state)
buy_size, sell_size = self.calculate_order_sizes(model_state)
desired_orders = self.desired_orders_builder.build(context, model_state)
payer = mango.CombinableInstructions.from_wallet(self.wallet)
cancellations = mango.CombinableInstructions.empty()
@ -83,22 +59,24 @@ class MarketMaker:
cancel = self.market_instruction_builder.build_cancel_order_instructions(order)
cancellations += cancel
buy_client_id = context.random_client_id()
self.buy_client_ids += [buy_client_id]
self.logger.info(f"Placing BUY order for {buy_size} at price {bid} with client ID: {buy_client_id}")
buy = self.market_instruction_builder.build_place_order_instructions(
mango.Side.BUY, mango.OrderType.POST_ONLY, bid, buy_size, buy_client_id)
place_orders = mango.CombinableInstructions.empty()
for desired_order in desired_orders:
client_id = context.random_client_id()
if desired_order.side == mango.Side.BUY:
self.buy_client_ids += [client_id]
else:
self.sell_client_ids += [client_id]
sell_client_id = context.random_client_id()
self.sell_client_ids += [sell_client_id]
self.logger.info(f"Placing SELL order for {sell_size} at price {ask} with client ID: {sell_client_id}")
sell = self.market_instruction_builder.build_place_order_instructions(
mango.Side.SELL, mango.OrderType.POST_ONLY, ask, sell_size, sell_client_id)
self.logger.info(
f"Placing {desired_order.side} order for {desired_order.quantity} at price {desired_order.price} with client ID: {client_id}")
place_order = self.market_instruction_builder.build_place_order_instructions(
desired_order.side, desired_order.order_type, desired_order.price, desired_order.quantity, client_id)
place_orders += place_order
settle = self.market_instruction_builder.build_settle_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:
self.logger.error(f"Market-maker error on pulse: {exception} - {traceback.format_exc()}")