Added DesiredOrdersBuilder and a couple of basic implementations.
This commit is contained in:
parent
80886c106c
commit
a00738a67c
|
@ -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}"
|
|
@ -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"« 𝙽𝚞𝚕𝚕𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛𝚜𝙱𝚞𝚒𝚕𝚍𝚎𝚛 »"
|
|
@ -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} »"
|
|
@ -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()}")
|
||||
|
||||
|
|
Loading…
Reference in New Issue