Added a RoundToLotSizeElement element to marketmaker chain.

This commit is contained in:
Geoff Taylor 2021-08-31 12:36:23 +01:00
parent befb86bcfb
commit b57ef58d63
5 changed files with 75 additions and 6 deletions

View File

@ -13,7 +13,6 @@
# [Github](https://github.com/blockworks-foundation)
# [Email](mailto:hello@blockworks.foundation)
from decimal import Decimal
from .token import Token
@ -46,6 +45,18 @@ class LotSizeConverter():
def quantity_lots_to_value(self, quantity_lots: Decimal) -> Decimal:
return (quantity_lots * self.base_lot_size) / (10 ** self.base.decimals)
def round_base(self, quantity: Decimal) -> Decimal:
base_factor: Decimal = 10 ** self.base.decimals
rounded: int = round(quantity * base_factor)
return Decimal(rounded) / base_factor
def round_quote(self, price: Decimal) -> Decimal:
quote_factor: Decimal = 10 ** self.base.decimals
base_factor: Decimal = 10 ** self.base.decimals
lots: Decimal = (price * quote_factor * self.base_lot_size) / (base_factor * self.quote_lot_size)
rounded: int = round(lots)
return Decimal(rounded) / self.quote_lot_size
def __str__(self) -> str:
return f"« 𝙻𝚘𝚝𝚂𝚒𝚣𝚎𝙲𝚘𝚗𝚟𝚎𝚛𝚝𝚎𝚛 [base lot size: {self.base_lot_size}, quote lot size: {self.quote_lot_size}] »"

View File

@ -25,6 +25,7 @@ from .confidenceintervalspreadelement import ConfidenceIntervalSpreadElement
from .element import Element
from .minimumchargeelement import MinimumChargeElement
from .preventpostonlycrossingbookelement import PreventPostOnlyCrossingBookElement
from .roundtolotsizeelement import RoundToLotSizeElement
# # 🥭 ChainBuilder class
@ -61,7 +62,8 @@ class ChainBuilder:
ConfidenceIntervalSpreadElement(args.position_size_ratio, confidence_interval_levels, args.order_type),
BiasQuoteOnPositionElement(args.quote_position_bias),
MinimumChargeElement(args.minimum_charge_ratio),
PreventPostOnlyCrossingBookElement()
PreventPostOnlyCrossingBookElement(),
RoundToLotSizeElement()
]
return Chain(elements)

View File

@ -40,16 +40,16 @@ class PreventPostOnlyCrossingBookElement(Element):
new_buy: mango.Order = order.with_price(new_buy_price)
self.logger.debug(
f"""Order change - would cross the orderbook {top_bid.price if top_bid else None} / {top_ask.price}:
Old: {order}
New: {new_buy}""")
Old: {order}
New: {new_buy}""")
new_orders += [new_buy]
elif order.side == mango.Side.SELL and top_bid is not None and top_ask is not None and order.price <= top_bid.price:
new_sell_price: Decimal = 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 {top_bid.price} / {top_ask.price if top_ask else None}:
Old: {order}
New: {new_sell}""")
Old: {order}
New: {new_sell}""")
new_orders += [new_sell]
else:

View File

@ -0,0 +1,51 @@
# # ⚠ 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
# # 🥭 RoundToLotSizeElement class
#
# May modifiy an `Order`s price or quantity to ensure it's exactly aligned to the market's lot sizes.
#
class RoundToLotSizeElement(Element):
def __init__(self):
super().__init__()
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:
new_price: Decimal = model_state.market.lot_size_converter.round_quote(order.price)
new_quantity: Decimal = model_state.market.lot_size_converter.round_base(order.quantity)
new_order: mango.Order = order
if (new_order.price != new_price) or (new_order.quantity != new_quantity):
new_order = new_order.with_price(new_price).with_quantity(new_quantity)
self.logger.debug(f"""Order change - price is now aligned to lot size:
Old: {order}
New: {new_order}""")
new_orders += [new_order]
return new_orders
def __str__(self) -> str:
return "« 𝚁𝚘𝚞𝚗𝚍𝚃𝚘𝙻𝚘𝚝𝚂𝚒𝚣𝚎𝙴𝚕𝚎𝚖𝚎𝚗𝚝 »"

View File

@ -121,6 +121,11 @@ class Order(typing.NamedTuple):
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)
# Returns an identical order with the quantity changed.
def with_quantity(self, quantity: Decimal) -> "Order":
return Order(id=self.id, side=self.side, price=self.price, quantity=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)