Moved marketmaker to an 'orderchain' model.
This commit is contained in:
parent
b56d114a4a
commit
3419c89033
|
@ -15,25 +15,18 @@ sys.path.insert(0, os.path.abspath(
|
|||
os.path.join(os.path.dirname(__file__), "..")))
|
||||
import mango # nopep8
|
||||
import mango.marketmaking # nopep8
|
||||
from mango.marketmaking.orderchain import chain # nopep8
|
||||
from mango.marketmaking.orderchain import chainbuilder # nopep8
|
||||
|
||||
parser = argparse.ArgumentParser(description="Shows the on-chain data of a particular account.")
|
||||
mango.ContextBuilder.add_command_line_parameters(parser)
|
||||
mango.Wallet.add_command_line_parameters(parser)
|
||||
chainbuilder.ChainBuilder.add_command_line_parameters(parser)
|
||||
parser.add_argument("--market", type=str, required=True, help="market symbol to make market upon (e.g. ETH/USDC)")
|
||||
parser.add_argument("--oracle-provider", type=str, required=True,
|
||||
help="name of the price provider to use (e.g. pyth)")
|
||||
parser.add_argument("--position-size-ratio", type=Decimal, required=True,
|
||||
help="fraction of the token inventory to be bought or sold in each order")
|
||||
parser.add_argument("--quote-position-bias", type=Decimal, default=Decimal(0),
|
||||
help="bias to apply to quotes based on inventory position")
|
||||
parser.add_argument("--existing-order-tolerance", type=Decimal, default=Decimal("0.001"),
|
||||
help="tolerance in price and quantity when matching existing orders or cancelling/replacing")
|
||||
parser.add_argument("--minimum-charge-ratio", type=Decimal, default=Decimal("0.0005"),
|
||||
help="minimum fraction of the price to be accept as a spread")
|
||||
parser.add_argument("--confidence-interval-level", type=Decimal, action="append",
|
||||
help="the levels of weighting to apply to the confidence interval from the oracle: e.g. 1 - use the oracle confidence interval as the spread, 2 (risk averse, default) - multiply the oracle confidence interval by 2 to get the spread, 0.5 (aggressive) halve the oracle confidence interval to get the spread (can be specified multiple times to give multiple levels)")
|
||||
parser.add_argument("--order-type", type=mango.OrderType, default=mango.OrderType.POST_ONLY,
|
||||
choices=list(mango.OrderType), help="Order type: LIMIT, IOC or POST_ONLY")
|
||||
parser.add_argument("--pulse-interval", type=int, default=10,
|
||||
help="number of seconds between each 'pulse' of the market maker")
|
||||
parser.add_argument("--account-index", type=int, default=0,
|
||||
|
@ -130,13 +123,10 @@ manager.open()
|
|||
|
||||
order_reconciler = mango.marketmaking.ToleranceOrderReconciler(
|
||||
args.existing_order_tolerance, args.existing_order_tolerance)
|
||||
confidence_interval_levels = args.confidence_interval_level
|
||||
if len(confidence_interval_levels) == 0:
|
||||
confidence_interval_levels = [Decimal(2)]
|
||||
desired_orders_builder = mango.marketmaking.ConfidenceIntervalDesiredOrdersBuilder(
|
||||
args.position_size_ratio, args.minimum_charge_ratio, confidence_interval_levels, args.order_type, args.quote_position_bias)
|
||||
|
||||
desired_orders_chain: chain.Chain = chainbuilder.ChainBuilder.from_command_line_parameters(args)
|
||||
market_maker = mango.marketmaking.MarketMaker(
|
||||
wallet, market, market_instruction_builder, desired_orders_builder, order_reconciler)
|
||||
wallet, market, market_instruction_builder, desired_orders_chain, order_reconciler)
|
||||
model_state = mango.marketmaking.ModelState(market, latest_account_observer,
|
||||
latest_group_observer, latest_price_observer,
|
||||
latest_open_orders_observer, inventory_watcher,
|
||||
|
|
|
@ -14,11 +14,6 @@ from solana.publickey import PublicKey
|
|||
sys.path.insert(0, os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..")))
|
||||
import mango # nopep8
|
||||
import mango.layouts # nopep8
|
||||
import mango.marketmaking.fixedratiosdesiredordersbuilder # nopep8
|
||||
import mango.marketmaking.marketmaker # nopep8
|
||||
import mango.marketmaking.modelstate # nopep8
|
||||
import mango.marketmaking.toleranceorderreconciler # nopep8
|
||||
|
||||
parser = argparse.ArgumentParser(description="Shows the on-chain data of a particular account.")
|
||||
mango.ContextBuilder.add_command_line_parameters(parser)
|
||||
|
|
|
@ -17,11 +17,6 @@ from solana.publickey import PublicKey
|
|||
sys.path.insert(0, os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..")))
|
||||
import mango # nopep8
|
||||
import mango.layouts # nopep8
|
||||
import mango.marketmaking.fixedratiosdesiredordersbuilder # nopep8
|
||||
import mango.marketmaking.marketmaker # nopep8
|
||||
import mango.marketmaking.modelstate # nopep8
|
||||
import mango.marketmaking.toleranceorderreconciler # nopep8
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Watches one or many accounts (via a websocket) and sends a notification if the SOL balance falls below the --minimum-sol-balance threshold.")
|
||||
|
|
|
@ -120,3 +120,8 @@ logging.setLogRecordFactory(emojified_record_factory)
|
|||
logging.basicConfig(level=logging.INFO,
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
format="%(asctime)s %(level_emoji)s %(name)-12.12s %(message)s")
|
||||
|
||||
# Stop libraries outputting lots of information unless it's a warning or worse.
|
||||
logging.getLogger("requests").setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
logging.getLogger("solanaweb3").setLevel(logging.WARNING)
|
||||
|
|
|
@ -18,10 +18,9 @@ from decimal import Decimal
|
|||
|
||||
from .token import Token
|
||||
|
||||
|
||||
# # 🥭 LotSizeConverter class
|
||||
#
|
||||
|
||||
|
||||
class LotSizeConverter():
|
||||
def __init__(self, base: Token, base_lot_size: Decimal, quote: Token, quote_lot_size: Decimal):
|
||||
self.base: Token = base
|
||||
|
@ -29,6 +28,10 @@ class LotSizeConverter():
|
|||
self.quote: Token = quote
|
||||
self.quote_lot_size: Decimal = quote_lot_size
|
||||
|
||||
@property
|
||||
def tick_size(self) -> Decimal:
|
||||
return self.price_lots_to_value(Decimal(1))
|
||||
|
||||
def price_lots_to_native(self, price_lots: Decimal) -> Decimal:
|
||||
return (price_lots * self.quote_lot_size) / self.base_lot_size
|
||||
|
||||
|
@ -52,8 +55,6 @@ class LotSizeConverter():
|
|||
|
||||
# # 🥭 NullLotSizeConverter class
|
||||
#
|
||||
|
||||
|
||||
class NullLotSizeConverter(LotSizeConverter):
|
||||
def __init__(self):
|
||||
super().__init__(None, Decimal(1), None, Decimal(1))
|
||||
|
@ -72,3 +73,29 @@ class NullLotSizeConverter(LotSizeConverter):
|
|||
|
||||
def __str__(self) -> str:
|
||||
return "« 𝙽𝚞𝚕𝚕𝙻𝚘𝚝𝚂𝚒𝚣𝚎𝙲𝚘𝚗𝚟𝚎𝚛𝚝𝚎𝚛 »"
|
||||
|
||||
|
||||
# # 🥭 RaisingLotSizeConverter class
|
||||
#
|
||||
class RaisingLotSizeConverter(LotSizeConverter):
|
||||
def __init__(self):
|
||||
super().__init__(None, Decimal(-1), None, Decimal(-1))
|
||||
|
||||
def price_lots_to_native(self, price_lots: Decimal) -> Decimal:
|
||||
raise NotImplementedError(
|
||||
"RaisingLotSizeConverter.price_lots_to_native() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.")
|
||||
|
||||
def quantity_lots_to_native(self, quantity_lots: Decimal) -> Decimal:
|
||||
raise NotImplementedError(
|
||||
"RaisingLotSizeConverter.quantity_lots_to_native() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.")
|
||||
|
||||
def price_lots_to_value(self, price_lots: Decimal) -> Decimal:
|
||||
raise NotImplementedError(
|
||||
"RaisingLotSizeConverter.price_lots_to_value() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.")
|
||||
|
||||
def quantity_lots_to_value(self, quantity_lots: Decimal) -> Decimal:
|
||||
raise NotImplementedError(
|
||||
"RaisingLotSizeConverter.quantity_lots_to_value() is not implemented. RaisingLotSizeConverter is a stub used where no LotSizeConverter members should be called.")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "« 𝙽𝚞𝚕𝚕𝙻𝚘𝚝𝚂𝚒𝚣𝚎𝙲𝚘𝚗𝚟𝚎𝚛𝚝𝚎𝚛 »"
|
||||
|
|
|
@ -20,6 +20,7 @@ import logging
|
|||
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from .lotsizeconverter import LotSizeConverter
|
||||
from .token import Token
|
||||
|
||||
|
||||
|
@ -40,13 +41,14 @@ class InventorySource(enum.Enum):
|
|||
#
|
||||
|
||||
class Market(metaclass=abc.ABCMeta):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, inventory_source: InventorySource, base: Token, quote: Token):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, inventory_source: InventorySource, base: Token, quote: Token, lot_size_converter: LotSizeConverter):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.program_id: PublicKey = program_id
|
||||
self.address: PublicKey = address
|
||||
self.inventory_source: InventorySource = inventory_source
|
||||
self.base: Token = base
|
||||
self.quote: Token = quote
|
||||
self.lot_size_converter: LotSizeConverter = lot_size_converter
|
||||
|
||||
@property
|
||||
def symbol(self) -> str:
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from .confidenceintervaldesiredordersbuilder import ConfidenceIntervalDesiredOrdersBuilder
|
||||
from .desiredordersbuilder import DesiredOrdersBuilder, NullDesiredOrdersBuilder
|
||||
from .fixedratiosdesiredordersbuilder import FixedRatiosDesiredOrdersBuilder
|
||||
from .marketmaker import MarketMaker
|
||||
from .modelstate import ModelState
|
||||
from .orderreconciler import OrderReconciler, NullOrderReconciler
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
# # ⚠ 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 .desiredordersbuilder import DesiredOrdersBuilder
|
||||
from .modelstate import ModelState
|
||||
|
||||
|
||||
# # 🥭 ConfidenceIntervalDesiredOrdersBuilder class
|
||||
#
|
||||
# Builds orders using a fixed position size ratio but with a spread based on the confidence in the oracle price.
|
||||
#
|
||||
class ConfidenceIntervalDesiredOrdersBuilder(DesiredOrdersBuilder):
|
||||
def __init__(self, position_size_ratio: Decimal, min_price_ratio: Decimal, confidence_interval_levels: typing.Sequence[Decimal] = [Decimal(2)], order_type: mango.OrderType = mango.OrderType.POST_ONLY, quote_position_bias: Decimal = Decimal(0)):
|
||||
super().__init__()
|
||||
self.position_size_ratio: Decimal = position_size_ratio
|
||||
self.min_price_ratio: Decimal = min_price_ratio
|
||||
self.confidence_interval_levels: typing.Sequence[Decimal] = confidence_interval_levels
|
||||
self.order_type: mango.OrderType = order_type
|
||||
self.quote_position_bias: Decimal = quote_position_bias
|
||||
|
||||
def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]:
|
||||
price: mango.Price = model_state.price
|
||||
if price.source.supports & mango.SupportedOracleFeature.CONFIDENCE == 0:
|
||||
raise Exception(f"Price does not support confidence interval: {price}")
|
||||
|
||||
base_tokens: mango.TokenValue = model_state.inventory.base
|
||||
quote_tokens: mango.TokenValue = model_state.inventory.quote
|
||||
|
||||
total = (base_tokens.value * price.mid_price) + quote_tokens.value
|
||||
quote_value_to_risk = total * self.position_size_ratio
|
||||
position_size = quote_value_to_risk / price.mid_price
|
||||
|
||||
orders: typing.List[mango.Order] = []
|
||||
|
||||
# From Daffy on 20th August 2021:
|
||||
# Formula to adjust price might look like this `pyth_price * (1 + (curr_pos / size) * pos_lean)`
|
||||
# where pos_lean is a negative number
|
||||
#
|
||||
# size is the standard size you're quoting which I believe comes from the position-size-ratio
|
||||
#
|
||||
# So if my standard size I'm quoting is 0.0002 BTC, my current position is +0.0010 BTC, and pos_lean
|
||||
# is -0.0001, you would move your quotes down by 0.0005 (or 5bps)
|
||||
# (Private chat link: https://discord.com/channels/@me/832570058861314048/878343278523723787)
|
||||
quote_position_bias = self.quote_position_bias * -1
|
||||
bias = (1 + (model_state.inventory.base.value / position_size) * quote_position_bias)
|
||||
|
||||
for confidence_interval_level in self.confidence_interval_levels:
|
||||
# From Daffy on 26th July 2021: max(pyth_conf * 2, price * min_charge)
|
||||
# (Private chat link: https://discord.com/channels/@me/832570058861314048/869208592648134666)
|
||||
charge = max(price.confidence * confidence_interval_level, price.mid_price * self.min_price_ratio)
|
||||
bid: Decimal = (price.mid_price - charge) * bias
|
||||
ask: Decimal = (price.mid_price + charge) * bias
|
||||
|
||||
orders += [
|
||||
mango.Order.from_basic_info(mango.Side.BUY, price=bid,
|
||||
quantity=position_size, order_type=self.order_type),
|
||||
mango.Order.from_basic_info(mango.Side.SELL, price=ask,
|
||||
quantity=position_size, order_type=self.order_type)
|
||||
]
|
||||
|
||||
return orders
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙲𝚘𝚗𝚏𝚒𝚍𝚎𝚗𝚌𝚎𝙸𝚗𝚝𝚎𝚛𝚟𝚊𝚕𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛𝚜𝙱𝚞𝚒𝚕𝚍𝚎𝚛 {self.order_type} - position size: {self.position_size_ratio}, min charge: {self.min_price_ratio}, confidence interval levels: {self.confidence_interval_levels} »"
|
|
@ -21,28 +21,26 @@ import typing
|
|||
|
||||
from datetime import datetime
|
||||
|
||||
from .desiredordersbuilder import DesiredOrdersBuilder
|
||||
from .modelstate import ModelState
|
||||
from ..observables import EventSource
|
||||
from .orderreconciler import OrderReconciler
|
||||
from .ordertracker import OrderTracker
|
||||
from .orderchain.chain import Chain
|
||||
|
||||
|
||||
# # 🥭 MarketMaker class
|
||||
#
|
||||
# An event-driven market-maker.
|
||||
#
|
||||
|
||||
class MarketMaker:
|
||||
def __init__(self, wallet: mango.Wallet, market: mango.Market,
|
||||
market_instruction_builder: mango.MarketInstructionBuilder,
|
||||
desired_orders_builder: DesiredOrdersBuilder,
|
||||
order_reconciler: OrderReconciler):
|
||||
desired_orders_chain: Chain, order_reconciler: OrderReconciler):
|
||||
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.desired_orders_builder: DesiredOrdersBuilder = desired_orders_builder
|
||||
self.desired_orders_chain: Chain = desired_orders_chain
|
||||
self.order_reconciler: OrderReconciler = order_reconciler
|
||||
self.order_tracker: OrderTracker = OrderTracker()
|
||||
|
||||
|
@ -56,7 +54,7 @@ class MarketMaker:
|
|||
try:
|
||||
payer = mango.CombinableInstructions.from_wallet(self.wallet)
|
||||
|
||||
desired_orders = self.desired_orders_builder.build(context, model_state)
|
||||
desired_orders = self.desired_orders_chain.process(context, model_state)
|
||||
existing_orders = self.order_tracker.existing_orders(model_state)
|
||||
reconciled = self.order_reconciler.reconcile(model_state, existing_orders, desired_orders)
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ import logging
|
|||
import mango
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
# # 🥭 ModelState class
|
||||
#
|
||||
|
@ -73,6 +75,18 @@ class ModelState:
|
|||
def asks(self) -> typing.Sequence[mango.Order]:
|
||||
return self.asks_watcher.latest
|
||||
|
||||
@property
|
||||
def top_bid(self) -> mango.Order:
|
||||
return self.bids_watcher.latest[0]
|
||||
|
||||
@property
|
||||
def top_ask(self) -> mango.Order:
|
||||
return self.asks_watcher.latest[0]
|
||||
|
||||
@property
|
||||
def spread(self) -> Decimal:
|
||||
return self.top_ask.price - self.top_bid.price
|
||||
|
||||
@property
|
||||
def existing_orders(self) -> typing.Sequence[mango.PlacedOrder]:
|
||||
return self.placed_orders_container_watcher.latest.placed_orders
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
# # ⚠ 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
|
||||
|
||||
|
||||
# # 🥭 BiasQuoteOnPositionElement class
|
||||
#
|
||||
# Modifies an `Order`s price based on current inventory. Uses `quote_position_bias` to shift the price to sell
|
||||
# more (if too much inventory) or buy more (if too little inventory).
|
||||
#
|
||||
class BiasQuoteOnPositionElement(Element):
|
||||
def __init__(self, quote_position_bias: Decimal = Decimal(0)):
|
||||
super().__init__()
|
||||
self.quote_position_bias: Decimal = quote_position_bias
|
||||
|
||||
def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]:
|
||||
if self.quote_position_bias == 0:
|
||||
# Zero bias results in no changes to orders.
|
||||
return orders
|
||||
|
||||
# From Daffy on 20th August 2021:
|
||||
# Formula to adjust price might look like this `pyth_price * (1 + (curr_pos / size) * pos_lean)`
|
||||
# where pos_lean is a negative number
|
||||
#
|
||||
# size is the standard size you're quoting which I believe comes from the position-size-ratio
|
||||
#
|
||||
# So if my standard size I'm quoting is 0.0002 BTC, my current position is +0.0010 BTC, and pos_lean
|
||||
# is -0.0001, you would move your quotes down by 0.0005 (or 5bps)
|
||||
# (Private chat link: https://discord.com/channels/@me/832570058861314048/878343278523723787)
|
||||
quote_position_bias = self.quote_position_bias * -1
|
||||
new_orders: typing.List[mango.Order] = []
|
||||
for order in orders:
|
||||
bias = (1 + (model_state.inventory.base.value / order.quantity) * quote_position_bias)
|
||||
new_price: Decimal = order.price * bias
|
||||
new_order: mango.Order = order.with_price(new_price)
|
||||
self.logger.debug(f"""Order change - quote_position_bias {self.quote_position_bias} creates a bias factor of {bias}:
|
||||
Old: {order}
|
||||
New: {new_order}""")
|
||||
new_orders += [new_order]
|
||||
|
||||
return new_orders
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙱𝚒𝚊𝚜𝚀𝚞𝚘𝚝𝚎𝙾𝚗𝙿𝚘𝚜𝚒𝚝𝚒𝚘𝚗𝙴𝚕𝚎𝚖𝚎𝚗𝚝 - bias: {self.quote_position_bias} »"
|
|
@ -0,0 +1,48 @@
|
|||
# # ⚠ 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 ..modelstate import ModelState
|
||||
from .element import Element
|
||||
|
||||
|
||||
# # 🥭 Chain class
|
||||
#
|
||||
# A `Chain` object takes a series of `Element`s and calls them in sequence to build a list of
|
||||
# desired `Order`s.
|
||||
#
|
||||
# Only `Order`s returned from `process()` method are used as the final list of 'desired orders' for
|
||||
# reconciling and possibly adding to the orderbook.
|
||||
#
|
||||
class Chain:
|
||||
def __init__(self, elements: typing.Sequence[Element]):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.elements: typing.Sequence[Element] = elements
|
||||
|
||||
def process(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]:
|
||||
orders: typing.Sequence[mango.Order] = []
|
||||
for element in self.elements:
|
||||
orders = element.process(context, model_state, orders)
|
||||
return orders
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝙲𝚑𝚊𝚒𝚗 of {len(self.elements)} elements »"""
|
|
@ -0,0 +1,67 @@
|
|||
# # ⚠ 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 typing
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from ...orders import OrderType
|
||||
from .biasquoteonpositionelement import BiasQuoteOnPositionElement
|
||||
from .chain import Chain
|
||||
from .confidenceintervalspreadelement import ConfidenceIntervalSpreadElement
|
||||
from .element import Element
|
||||
from .minimumchargeelement import MinimumChargeElement
|
||||
from .preventpostonlycrossingbookelement import PreventPostOnlyCrossingBookElement
|
||||
|
||||
|
||||
# # 🥭 ChainBuilder class
|
||||
#
|
||||
# A `ChainBuilder` class to allow building a `Chain`, keeping parameter and constructor complexities all in
|
||||
# one place.
|
||||
#
|
||||
class ChainBuilder:
|
||||
@staticmethod
|
||||
def add_command_line_parameters(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("--order-type", type=OrderType, default=OrderType.POST_ONLY,
|
||||
choices=list(OrderType), help="Order type: LIMIT, IOC or POST_ONLY")
|
||||
parser.add_argument("--position-size-ratio", type=Decimal, required=True,
|
||||
help="fraction of the token inventory to be bought or sold in each order")
|
||||
parser.add_argument("--confidence-interval-level", type=Decimal, action="append",
|
||||
help="the levels of weighting to apply to the confidence interval from the oracle: e.g. 1 - use the oracle confidence interval as the spread, 2 (risk averse, default) - multiply the oracle confidence interval by 2 to get the spread, 0.5 (aggressive) halve the oracle confidence interval to get the spread (can be specified multiple times to give multiple levels)")
|
||||
parser.add_argument("--quote-position-bias", type=Decimal, default=Decimal(0),
|
||||
help="bias to apply to quotes based on inventory position")
|
||||
parser.add_argument("--minimum-charge-ratio", type=Decimal, default=Decimal("0.0005"),
|
||||
help="minimum fraction of the price to be accept as a spread")
|
||||
|
||||
# This function is the converse of `add_command_line_parameters()` - it takes
|
||||
# an argument of parsed command-line parameters and expects to see the ones it added
|
||||
# to that collection in the `add_command_line_parameters()` call.
|
||||
#
|
||||
# It then uses those parameters to create a properly-configured `Chain` object.
|
||||
#
|
||||
@staticmethod
|
||||
def from_command_line_parameters(args: argparse.Namespace) -> Chain:
|
||||
confidence_interval_levels: typing.Sequence[Decimal] = args.confidence_interval_level
|
||||
if len(confidence_interval_levels) == 0:
|
||||
confidence_interval_levels = [Decimal(2)]
|
||||
elements: typing.List[Element] = [
|
||||
ConfidenceIntervalSpreadElement(args.position_size_ratio, confidence_interval_levels, args.order_type),
|
||||
BiasQuoteOnPositionElement(args.quote_position_bias),
|
||||
MinimumChargeElement(args.minimum_charge_ratio),
|
||||
PreventPostOnlyCrossingBookElement()
|
||||
]
|
||||
|
||||
return Chain(elements)
|
|
@ -0,0 +1,70 @@
|
|||
# # ⚠ 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
|
||||
|
||||
|
||||
# # 🥭 ConfidenceIntervalSpreadElement class
|
||||
#
|
||||
# Ignores any input `Order`s (so probably best at the head of the chain). Builds orders using a fixed position
|
||||
# size ratio but with a spread based on the confidence in the oracle price.
|
||||
#
|
||||
class ConfidenceIntervalSpreadElement(Element):
|
||||
def __init__(self, position_size_ratio: Decimal, confidence_interval_levels: typing.Sequence[Decimal] = [Decimal(2)], order_type: mango.OrderType = mango.OrderType.POST_ONLY):
|
||||
super().__init__()
|
||||
self.position_size_ratio: Decimal = position_size_ratio
|
||||
self.confidence_interval_levels: typing.Sequence[Decimal] = confidence_interval_levels
|
||||
self.order_type: mango.OrderType = order_type
|
||||
|
||||
def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]:
|
||||
price: mango.Price = model_state.price
|
||||
if price.source.supports & mango.SupportedOracleFeature.CONFIDENCE == 0:
|
||||
raise Exception(f"Price does not support confidence interval: {price}")
|
||||
|
||||
base_tokens: mango.TokenValue = model_state.inventory.base
|
||||
quote_tokens: mango.TokenValue = model_state.inventory.quote
|
||||
|
||||
total = (base_tokens.value * price.mid_price) + quote_tokens.value
|
||||
quote_value_to_risk = total * self.position_size_ratio
|
||||
position_size = quote_value_to_risk / price.mid_price
|
||||
|
||||
new_orders: typing.List[mango.Order] = []
|
||||
for confidence_interval_level in self.confidence_interval_levels:
|
||||
charge = price.confidence * confidence_interval_level
|
||||
bid: Decimal = price.mid_price - charge
|
||||
ask: Decimal = price.mid_price + charge
|
||||
|
||||
new_orders += [
|
||||
mango.Order.from_basic_info(mango.Side.BUY, price=bid,
|
||||
quantity=position_size, order_type=self.order_type),
|
||||
mango.Order.from_basic_info(mango.Side.SELL, price=ask,
|
||||
quantity=position_size, order_type=self.order_type)
|
||||
]
|
||||
|
||||
new_orders.sort(key=lambda ord: ord.price, reverse=True)
|
||||
order_text = "\n ".join([f"{order}" for order in new_orders])
|
||||
self.logger.debug(f"""Initial desired orders - spread {model_state.spread} ({model_state.top_bid.price} / {model_state.top_ask.price}):
|
||||
{order_text}""")
|
||||
return new_orders
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙲𝚘𝚗𝚏𝚒𝚍𝚎𝚗𝚌𝚎𝙸𝚗𝚝𝚎𝚛𝚟𝚊𝚕𝚂𝚙𝚛𝚎𝚊𝚍𝙴𝚕𝚎𝚖𝚎𝚗𝚝 {self.order_type} - position size: {self.position_size_ratio}, confidence interval levels: {self.confidence_interval_levels} »"
|
|
@ -19,39 +19,26 @@ import logging
|
|||
import mango
|
||||
import typing
|
||||
|
||||
from .modelstate import ModelState
|
||||
from ..modelstate import ModelState
|
||||
|
||||
|
||||
# # 🥭 DesiredOrdersBuilder class
|
||||
# # 🥭 Element class
|
||||
#
|
||||
# A builder that builds a list of orders we'd like to be on the orderbook.
|
||||
# A base class for a part of a chain that can take in a sequence of elements and process them, changing
|
||||
# them as desired.
|
||||
#
|
||||
# The logic of what orders to create will be implemented in a derived class.
|
||||
# Only `Order`s returned from `process()` method are passed to the next element of the chain.
|
||||
#
|
||||
|
||||
class DesiredOrdersBuilder(metaclass=abc.ABCMeta):
|
||||
class Element(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[mango.Order]:
|
||||
raise NotImplementedError("DesiredOrdersBuilder.build() is not implemented on the base type.")
|
||||
def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]:
|
||||
raise NotImplementedError("Element.process() 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[mango.Order]:
|
||||
return []
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "« 𝙽𝚞𝚕𝚕𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛𝚜𝙱𝚞𝚒𝚕𝚍𝚎𝚛 »"
|
||||
return """« 𝙴𝚕𝚎𝚖𝚎𝚗𝚝 »"""
|
|
@ -20,16 +20,16 @@ import typing
|
|||
|
||||
from decimal import Decimal
|
||||
|
||||
from .desiredordersbuilder import DesiredOrdersBuilder
|
||||
from .modelstate import ModelState
|
||||
from .element import Element
|
||||
from ..modelstate import ModelState
|
||||
|
||||
|
||||
# # 🥭 FixedRatiosDesiredOrdersBuilder class
|
||||
#
|
||||
# Builds orders using a fixed spread ratio and a fixed position size ratio.
|
||||
# Ignores any input `Order`s (so probably best at the head of the chain). Builds orders using a fixed spread
|
||||
# ratio and a fixed position size ratio.
|
||||
#
|
||||
|
||||
class FixedRatiosDesiredOrdersBuilder(DesiredOrdersBuilder):
|
||||
class FixedRatiosElement(Element):
|
||||
def __init__(self, spread_ratios: typing.Sequence[Decimal], position_size_ratios: typing.Sequence[Decimal], order_type: mango.OrderType = mango.OrderType.POST_ONLY):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
if len(spread_ratios) != len(position_size_ratios):
|
||||
|
@ -39,14 +39,14 @@ class FixedRatiosDesiredOrdersBuilder(DesiredOrdersBuilder):
|
|||
self.position_size_ratios: typing.Sequence[Decimal] = position_size_ratios
|
||||
self.order_type: mango.OrderType = order_type
|
||||
|
||||
def build(self, context: mango.Context, model_state: ModelState) -> typing.Sequence[mango.Order]:
|
||||
def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]:
|
||||
price: mango.Price = model_state.price
|
||||
base_tokens: mango.TokenValue = model_state.inventory.base
|
||||
quote_tokens: mango.TokenValue = model_state.inventory.quote
|
||||
|
||||
total = (base_tokens.value * price.mid_price) + quote_tokens.value
|
||||
|
||||
orders: typing.List[mango.Order] = []
|
||||
new_orders: typing.List[mango.Order] = []
|
||||
for counter in range(len(self.spread_ratios)):
|
||||
position_size_ratio = self.position_size_ratios[counter]
|
||||
quote_value_to_risk = total * position_size_ratio
|
||||
|
@ -56,14 +56,14 @@ class FixedRatiosDesiredOrdersBuilder(DesiredOrdersBuilder):
|
|||
bid: Decimal = price.mid_price - (price.mid_price * spread_ratio)
|
||||
ask: Decimal = price.mid_price + (price.mid_price * spread_ratio)
|
||||
|
||||
orders += [
|
||||
new_orders += [
|
||||
mango.Order.from_basic_info(mango.Side.BUY, price=bid,
|
||||
quantity=base_position_size, order_type=self.order_type),
|
||||
mango.Order.from_basic_info(mango.Side.SELL, price=ask,
|
||||
quantity=base_position_size, order_type=self.order_type)
|
||||
]
|
||||
|
||||
return orders
|
||||
return new_orders
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙵𝚒𝚡𝚎𝚍𝚁𝚊𝚝𝚒𝚘𝙳𝚎𝚜𝚒𝚛𝚎𝚍𝙾𝚛𝚍𝚎𝚛𝚜𝙱𝚞𝚒𝚕𝚍𝚎𝚛 using ratios - spread: {self.spread_ratios}, position size: {self.position_size_ratios} »"
|
||||
return f"« 𝙵𝚒𝚡𝚎𝚍𝚁𝚊𝚝𝚒𝚘𝚜𝙴𝚕𝚎𝚖𝚎𝚗𝚝 using ratios - spread: {self.spread_ratios}, position size: {self.position_size_ratios} »"
|
|
@ -0,0 +1,60 @@
|
|||
# # ⚠ 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
|
||||
|
||||
|
||||
# # 🥭 MinimumChargeElement class
|
||||
#
|
||||
# May modifiy an `Order`s price if it would result in too small a difference from the mid-price, meaning less
|
||||
# of a charge if that `Order` is filled.
|
||||
#
|
||||
class MinimumChargeElement(Element):
|
||||
def __init__(self, minimum_charge_ratio: Decimal = Decimal(0)):
|
||||
super().__init__()
|
||||
self.minimum_charge_ratio: Decimal = minimum_charge_ratio
|
||||
|
||||
def process(self, context: mango.Context, model_state: ModelState, orders: typing.Sequence[mango.Order]) -> typing.Sequence[mango.Order]:
|
||||
# From Daffy on 26th July 2021: max(pyth_conf * 2, price * min_charge)
|
||||
# (Private chat link: https://discord.com/channels/@me/832570058861314048/869208592648134666)
|
||||
new_orders: typing.List[mango.Order] = []
|
||||
for order in orders:
|
||||
minimum_charge = model_state.price.mid_price * self.minimum_charge_ratio
|
||||
current_charge = (model_state.price.mid_price - order.price).copy_abs()
|
||||
if current_charge > minimum_charge:
|
||||
# All OK with current order
|
||||
new_orders += [order]
|
||||
else:
|
||||
if order.side == mango.Side.BUY:
|
||||
new_price: Decimal = model_state.price.mid_price - minimum_charge
|
||||
else:
|
||||
new_price = model_state.price.mid_price + minimum_charge
|
||||
new_order = order.with_price(new_price)
|
||||
self.logger.debug(f"""Order change - price is less than minimum charge:
|
||||
Old: {order}
|
||||
New: {new_order}""")
|
||||
new_orders += [new_order]
|
||||
|
||||
return new_orders
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« 𝙼𝚒𝚗𝚒𝚖𝚞𝚖𝙲𝚑𝚊𝚛𝚐𝚎𝙴𝚕𝚎𝚖𝚎𝚗𝚝 - minimum charge ratio: {self.minimum_charge_ratio} »"
|
|
@ -0,0 +1,60 @@
|
|||
# # ⚠ 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
|
||||
|
||||
|
||||
# # 🥭 PreventPostOnlyCrossingBookElement class
|
||||
#
|
||||
# May modifiy an `Order`s price if it would result in too small a difference from the mid-price, meaning less
|
||||
# of a charge if that `Order` is filled.
|
||||
#
|
||||
class PreventPostOnlyCrossingBookElement(Element):
|
||||
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.order_type == mango.OrderType.POST_ONLY:
|
||||
if order.side == mango.Side.BUY and order.price >= model_state.top_bid.price:
|
||||
new_buy_price: Decimal = model_state.top_bid.price - model_state.market.lot_size_converter.tick_size
|
||||
new_buy: mango.Order = order.with_price(new_buy_price)
|
||||
self.logger.debug(f"""Order change - would cross the orderbook {model_state.top_bid.price} / {model_state.top_ask.price}:
|
||||
Old: {order}
|
||||
New: {new_buy}""")
|
||||
new_orders += [new_buy]
|
||||
elif order.side == mango.Side.SELL and order.price <= model_state.top_ask.price:
|
||||
new_sell_price: Decimal = model_state.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 {model_state.top_bid.price} / {model_state.top_ask.price}:
|
||||
Old: {order}
|
||||
New: {new_sell}""")
|
||||
new_orders += [new_sell]
|
||||
else:
|
||||
# All OK with current order
|
||||
new_orders += [order]
|
||||
else:
|
||||
# Only change POST_ONLY orders.
|
||||
new_orders += [order]
|
||||
|
||||
return new_orders
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "« 𝙿𝚛𝚎𝚟𝚎𝚗𝚝𝙿𝚘𝚜𝚝𝙾𝚗𝚕𝚢𝙲𝚛𝚘𝚜𝚜𝚒𝚗𝚐𝙱𝚘𝚘𝚔𝙴𝚕𝚎𝚖𝚎𝚗𝚝 »"
|
|
@ -39,7 +39,6 @@ from .liquidationevent import LiquidationEvent
|
|||
#
|
||||
# Derived classes should not override `send()` since that is the interface outside classes call and it's used to ensure `NotificationTarget`s don't throw an exception when sending.
|
||||
#
|
||||
|
||||
class NotificationTarget(metaclass=abc.ABCMeta):
|
||||
def __init__(self):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
|
@ -76,8 +75,6 @@ class NotificationTarget(metaclass=abc.ABCMeta):
|
|||
#
|
||||
# The [Telegram instructions to create a bot](https://core.telegram.org/bots#creating-a-new-bot)
|
||||
# show you how to create the bot token.
|
||||
|
||||
|
||||
class TelegramNotificationTarget(NotificationTarget):
|
||||
def __init__(self, address):
|
||||
super().__init__()
|
||||
|
@ -99,8 +96,6 @@ class TelegramNotificationTarget(NotificationTarget):
|
|||
#
|
||||
# The `DiscordNotificationTarget` sends messages to Discord.
|
||||
#
|
||||
|
||||
|
||||
class DiscordNotificationTarget(NotificationTarget):
|
||||
def __init__(self, address):
|
||||
super().__init__()
|
||||
|
@ -168,8 +163,6 @@ class DiscordNotificationTarget(NotificationTarget):
|
|||
# ]
|
||||
# }'
|
||||
# ```
|
||||
|
||||
|
||||
class MailjetNotificationTarget(NotificationTarget):
|
||||
def __init__(self, encoded_parameters):
|
||||
super().__init__()
|
||||
|
@ -224,8 +217,6 @@ class MailjetNotificationTarget(NotificationTarget):
|
|||
# columns to the output. Token changes may arrive in different orders, so ordering of token
|
||||
# changes is not guaranteed to be consistent from transaction to transaction.
|
||||
#
|
||||
|
||||
|
||||
class CsvFileNotificationTarget(NotificationTarget):
|
||||
def __init__(self, filename):
|
||||
super().__init__()
|
||||
|
@ -257,8 +248,6 @@ class CsvFileNotificationTarget(NotificationTarget):
|
|||
# This class takes a `NotificationTarget` and a filter function, and only calls the
|
||||
# `NotificationTarget` if the filter function returns `True` for the notification item.
|
||||
#
|
||||
|
||||
|
||||
class FilteringNotificationTarget(NotificationTarget):
|
||||
def __init__(self, inner_notifier: NotificationTarget, filter_func: typing.Callable[[typing.Any], bool]):
|
||||
super().__init__()
|
||||
|
@ -279,17 +268,12 @@ class FilteringNotificationTarget(NotificationTarget):
|
|||
# `NotificationTarget` to be plugged in to the `logging` subsystem to receive log messages
|
||||
# and notify however it chooses.
|
||||
#
|
||||
|
||||
|
||||
class NotificationHandler(logging.StreamHandler):
|
||||
def __init__(self, target: NotificationTarget):
|
||||
logging.StreamHandler.__init__(self)
|
||||
self.target = target
|
||||
|
||||
def emit(self, record):
|
||||
# Don't send error logging from solanaweb3
|
||||
if record.name == "solanaweb3.rpc.httprpc.HTTPClient":
|
||||
return
|
||||
message = self.format(record)
|
||||
self.target.send_notification(message)
|
||||
|
||||
|
@ -302,8 +286,6 @@ class NotificationHandler(logging.StreamHandler):
|
|||
# This is most likely used when parsing command-line arguments - this function can be used
|
||||
# in the `type` parameter of an `add_argument()` call.
|
||||
#
|
||||
|
||||
|
||||
def parse_subscription_target(target):
|
||||
protocol, destination = target.split(":", 1)
|
||||
|
||||
|
|
|
@ -122,6 +122,11 @@ class Order(typing.NamedTuple):
|
|||
return Order(id=self.id, side=self.side, price=self.price, quantity=self.quantity,
|
||||
client_id=client_id, owner=self.owner, order_type=self.order_type)
|
||||
|
||||
# Returns an identical order with the price changed.
|
||||
def with_price(self, price: Decimal) -> "Order":
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
def from_serum_order(serum_order: PySerumOrder) -> "Order":
|
||||
price = Decimal(serum_order.info.price)
|
||||
|
|
|
@ -20,7 +20,7 @@ from solana.publickey import PublicKey
|
|||
from .accountinfo import AccountInfo
|
||||
from .context import Context
|
||||
from .group import Group
|
||||
from .lotsizeconverter import LotSizeConverter
|
||||
from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter
|
||||
from .market import Market, InventorySource
|
||||
from .orderbookside import PerpOrderBookSide
|
||||
from .orders import Order
|
||||
|
@ -35,7 +35,7 @@ from .token import Token
|
|||
#
|
||||
class PerpMarket(Market):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_perp_market: PerpMarketDetails):
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote)
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
|
||||
self.underlying_perp_market: PerpMarketDetails = underlying_perp_market
|
||||
self.lot_size_converter: LotSizeConverter = LotSizeConverter(
|
||||
base, underlying_perp_market.base_lot_size, quote, underlying_perp_market.quote_lot_size)
|
||||
|
@ -92,7 +92,7 @@ class PerpMarket(Market):
|
|||
#
|
||||
class PerpMarketStub(Market):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey):
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote)
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
|
||||
self.group_address: PublicKey = group_address
|
||||
|
||||
def load(self, context: Context, group: typing.Optional[Group] = None) -> PerpMarket:
|
||||
|
|
|
@ -22,6 +22,7 @@ from solana.publickey import PublicKey
|
|||
|
||||
from .accountinfo import AccountInfo
|
||||
from .context import Context
|
||||
from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter
|
||||
from .market import Market, InventorySource
|
||||
from .orders import Order
|
||||
from .serumeventqueue import SerumEvent, SerumEventQueue
|
||||
|
@ -34,8 +35,10 @@ from .token import Token
|
|||
#
|
||||
class SerumMarket(Market):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, underlying_serum_market: PySerumMarket):
|
||||
super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote)
|
||||
super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter())
|
||||
self.underlying_serum_market: PySerumMarket = underlying_serum_market
|
||||
self.lot_size_converter: LotSizeConverter = LotSizeConverter(
|
||||
base, underlying_serum_market.state.base_lot_size, quote, underlying_serum_market.state.quote_lot_size)
|
||||
|
||||
def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]:
|
||||
event_queue: SerumEventQueue = SerumEventQueue.load(context, self.underlying_serum_market.state.event_queue())
|
||||
|
@ -67,7 +70,7 @@ class SerumMarket(Market):
|
|||
#
|
||||
class SerumMarketStub(Market):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token):
|
||||
super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote)
|
||||
super().__init__(program_id, address, InventorySource.SPL_TOKENS, base, quote, RaisingLotSizeConverter())
|
||||
|
||||
def load(self, context: Context) -> SerumMarket:
|
||||
underlying_serum_market: PySerumMarket = PySerumMarket.load(
|
||||
|
|
|
@ -23,6 +23,7 @@ from solana.publickey import PublicKey
|
|||
from .accountinfo import AccountInfo
|
||||
from .context import Context
|
||||
from .group import Group
|
||||
from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter
|
||||
from .market import Market, InventorySource
|
||||
from .orders import Order
|
||||
from .serumeventqueue import SerumEvent, SerumEventQueue
|
||||
|
@ -35,9 +36,11 @@ from .token import Token
|
|||
#
|
||||
class SpotMarket(Market):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group: Group, underlying_serum_market: PySerumMarket):
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote)
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
|
||||
self.group: Group = group
|
||||
self.underlying_serum_market: PySerumMarket = underlying_serum_market
|
||||
self.lot_size_converter: LotSizeConverter = LotSizeConverter(
|
||||
base, underlying_serum_market.state.base_lot_size, quote, underlying_serum_market.state.quote_lot_size)
|
||||
|
||||
def unprocessed_events(self, context: Context) -> typing.Sequence[SerumEvent]:
|
||||
event_queue: SerumEventQueue = SerumEventQueue.load(context, self.underlying_serum_market.state.event_queue())
|
||||
|
@ -71,7 +74,7 @@ class SpotMarket(Market):
|
|||
|
||||
class SpotMarketStub(Market):
|
||||
def __init__(self, program_id: PublicKey, address: PublicKey, base: Token, quote: Token, group_address: PublicKey):
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote)
|
||||
super().__init__(program_id, address, InventorySource.ACCOUNT, base, quote, RaisingLotSizeConverter())
|
||||
self.group_address: PublicKey = group_address
|
||||
|
||||
def load(self, context: Context, group: typing.Optional[Group]) -> SpotMarket:
|
||||
|
|
Loading…
Reference in New Issue