Added QuoteSingleSideElement for marketmaker.

This commit is contained in:
Geoff Taylor 2021-10-14 09:42:09 +01:00
parent c6a9069f77
commit b46d6bd0d2
4 changed files with 166 additions and 1 deletions

View File

@ -179,6 +179,17 @@ The default is to perform calculations based on the mid price. The `--minimumcha
This ensures that POST_ONLY orders that would corss the orderbook (and so be cancelled instead of put on the book) are placed *just* inside the spread by 1 tick.
### `QuoteSingleSideElement`
> Specified using: `--chain quotesingleside`
> Accepts parameter: `--quotesingleside-side`
Sometimes you may only want your marketmaker to place SELL orders. Or only place BUY orders. The `QuoteSingleSideElement` allows you to specify a single side, and will filter out all desired orders that are not for that side.
For example, you can set the 'order chain' to have a `RatiosElement` asking for orders with spreads at 0.5%, 0.7%, and 0.9% (which would produce 6 orders - 3 BUYs and 3 SELLs), and then use a `QuoteSingleSideElement` with a `side` of `SELL` which would remove the 3 BUYs from the desired orders leaving only the 3 SELLs.
### `RatiosElement`
> Specified using: `--chain ratios`

View File

@ -22,11 +22,12 @@ from .chain import Chain
from .confidenceintervalelement import ConfidenceIntervalElement
from .element import Element
from .fixedpositionsizeelement import FixedPositionSizeElement
from .ratioselement import RatiosElement
from .fixedspreadelement import FixedSpreadElement
from .minimumchargeelement import MinimumChargeElement
from .preventpostonlycrossingbookelement import PreventPostOnlyCrossingBookElement
from .quotesinglesideelement import QuoteSingleSideElement
from .roundtolotsizeelement import RoundToLotSizeElement
from .ratioselement import RatiosElement
_DEFAULT_CHAIN = [
"confidenceinterval",
@ -57,6 +58,7 @@ class ChainBuilder:
FixedPositionSizeElement.add_command_line_parameters(parser)
MinimumChargeElement.add_command_line_parameters(parser)
PreventPostOnlyCrossingBookElement.add_command_line_parameters(parser)
QuoteSingleSideElement.add_command_line_parameters(parser)
RatiosElement.add_command_line_parameters(parser)
RoundToLotSizeElement.add_command_line_parameters(parser)
@ -96,6 +98,8 @@ class ChainBuilder:
return MinimumChargeElement.from_command_line_parameters(args)
elif proper_name == "PREVENTPOSTONLYCROSSINGBOOK":
return PreventPostOnlyCrossingBookElement.from_command_line_parameters(args)
elif proper_name == "QUOTESINGLESIDE":
return QuoteSingleSideElement.from_command_line_parameters(args)
elif proper_name == "RATIOS":
return RatiosElement.from_command_line_parameters(args)
elif proper_name == "ROUNDTOLOTSIZE":

View File

@ -0,0 +1,57 @@
# # ⚠ 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 mango
import typing
from .element import Element
from ...modelstate import ModelState
# # 🥭 QuoteSingleSideElement class
#
# Only allows orders from one side of the book to progress to the next element of the chain.
#
class QuoteSingleSideElement(Element):
def __init__(self, side: mango.Side):
super().__init__()
self.allowed: mango.Side = side
@staticmethod
def add_command_line_parameters(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--quotesingleside-side", type=mango.Side,
help="the single side to quote on - if BUY, all SELLs will be removed from desired orders, if SELL, all BUYs will be removed.")
@staticmethod
def from_command_line_parameters(args: argparse.Namespace) -> "QuoteSingleSideElement":
side: mango.Side = args.quotesingleside_side
return QuoteSingleSideElement(side)
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.side == self.allowed:
self.logger.debug(f"""Allowing {order.side} order [allowed: {self.allowed}]:
Allowed: {order}""")
new_orders += [order]
else:
self.logger.debug(f"""Removing {order.side} order [allowed: {self.allowed}]:
Removed: {order}""")
return new_orders
def __str__(self) -> str:
return "« 𝚀𝚞𝚘𝚝𝚎𝚂𝚒𝚗𝚐𝚕𝚎𝚂𝚒𝚍𝚎𝙴𝚕𝚎𝚖𝚎𝚗𝚝 »"

View File

@ -0,0 +1,93 @@
import argparse
from ...context import mango
from ...fakes import fake_context, fake_model_state, fake_order
from mango.marketmaking.orderchain.quotesinglesideelement import QuoteSingleSideElement
def test_from_args():
args: argparse.Namespace = argparse.Namespace(quotesingleside_side=mango.Side.BUY)
actual: QuoteSingleSideElement = QuoteSingleSideElement.from_command_line_parameters(args)
assert actual is not None
assert isinstance(actual, QuoteSingleSideElement)
def test_allow_single_buy():
context = fake_context()
model_state = fake_model_state()
order: mango.Order = fake_order(side=mango.Side.BUY)
actual: QuoteSingleSideElement = QuoteSingleSideElement(mango.Side.BUY)
result = actual.process(context, model_state, [order])
assert result == [order]
def test_prevent_single_buy():
context = fake_context()
model_state = fake_model_state()
order: mango.Order = fake_order(side=mango.Side.BUY)
actual: QuoteSingleSideElement = QuoteSingleSideElement(mango.Side.SELL)
result = actual.process(context, model_state, [order])
assert result == []
def test_allow_all_buys_and_no_sells():
context = fake_context()
model_state = fake_model_state()
orders = [
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL)
]
actual: QuoteSingleSideElement = QuoteSingleSideElement(mango.Side.BUY)
result = actual.process(context, model_state, orders)
assert len(result) == 3
assert result == [orders[0], orders[2], orders[4]]
def test_allow_all_sells_and_no_buys():
context = fake_context()
model_state = fake_model_state()
orders = [
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL)
]
actual: QuoteSingleSideElement = QuoteSingleSideElement(mango.Side.SELL)
result = actual.process(context, model_state, orders)
assert len(result) == 3
assert result == [orders[1], orders[3], orders[5]]
def test_allow_all_buys_and_no_sells_different_pattern():
# This is just to check it isn't allowing every other order.
context = fake_context()
model_state = fake_model_state()
orders = [
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.BUY),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.SELL),
fake_order(side=mango.Side.BUY)
]
actual: QuoteSingleSideElement = QuoteSingleSideElement(mango.Side.BUY)
result = actual.process(context, model_state, orders)
assert len(result) == 3
assert result == [orders[0], orders[1], orders[5]]