ModelState now tracks current orders on the orderbook directly, rather than using OpenOrders.
This commit is contained in:
parent
39c591fc6f
commit
1d4e24ea38
|
@ -4,6 +4,5 @@ from .modelstate import ModelState
|
|||
from .modelstatebuilder import ModelStateBuilder, WebsocketModelStateBuilder, PollingModelStateBuilder, SerumPollingModelStateBuilder, SpotPollingModelStateBuilder, PerpPollingModelStateBuilder
|
||||
from .modelstatebuilderfactory import ModelUpdateMode, model_state_builder_factory
|
||||
from .orderreconciler import OrderReconciler, NullOrderReconciler
|
||||
from .ordertracker import OrderTracker
|
||||
from .reconciledorders import ReconciledOrders
|
||||
from .toleranceorderreconciler import ToleranceOrderReconciler
|
||||
|
|
|
@ -25,7 +25,6 @@ from decimal import Decimal
|
|||
from .modelstate import ModelState
|
||||
from ..observables import EventSource
|
||||
from .orderreconciler import OrderReconciler
|
||||
from .ordertracker import OrderTracker
|
||||
from .orderchain.chain import Chain
|
||||
|
||||
|
||||
|
@ -46,7 +45,6 @@ class MarketMaker:
|
|||
self.order_reconciler: OrderReconciler = order_reconciler
|
||||
self.redeem_threshold: typing.Optional[Decimal] = redeem_threshold
|
||||
|
||||
self.order_tracker: OrderTracker = OrderTracker()
|
||||
self.pulse_complete: EventSource[datetime] = EventSource[datetime]()
|
||||
self.pulse_error: EventSource[Exception] = EventSource[Exception]()
|
||||
|
||||
|
@ -69,7 +67,7 @@ class MarketMaker:
|
|||
self.logger.info(f"[{context.name}] Market-maker not quoting - model_state.not_quoting is set.")
|
||||
return
|
||||
|
||||
existing_orders = self.order_tracker.existing_orders(model_state)
|
||||
existing_orders = model_state.current_orders()
|
||||
reconciled = self.order_reconciler.reconcile(model_state, existing_orders, desired_orders)
|
||||
|
||||
cancellations = mango.CombinableInstructions.empty()
|
||||
|
@ -82,7 +80,6 @@ class MarketMaker:
|
|||
for to_place in reconciled.to_place:
|
||||
desired_client_id: int = context.random_client_id()
|
||||
to_place_with_client_id = to_place.with_client_id(desired_client_id)
|
||||
self.order_tracker.track(to_place_with_client_id)
|
||||
|
||||
self.logger.info(f"Placing {self.market.symbol} {to_place_with_client_id}")
|
||||
place_order = self.market_instruction_builder.build_place_order_instructions(to_place_with_client_id)
|
||||
|
|
|
@ -20,13 +20,17 @@ import typing
|
|||
|
||||
from decimal import Decimal
|
||||
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
|
||||
# # 🥭 ModelState class
|
||||
#
|
||||
# Provides simple access to the latest state of market and account data.
|
||||
#
|
||||
class ModelState:
|
||||
def __init__(self, market: mango.Market,
|
||||
def __init__(self,
|
||||
order_owner: PublicKey,
|
||||
market: mango.Market,
|
||||
group_watcher: mango.Watcher[mango.Group],
|
||||
account_watcher: mango.Watcher[mango.Account],
|
||||
price_watcher: mango.Watcher[mango.Price],
|
||||
|
@ -36,6 +40,7 @@ class ModelState:
|
|||
asks: mango.Watcher[typing.Sequence[mango.Order]]
|
||||
):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.order_owner: PublicKey = order_owner
|
||||
self.market: mango.Market = market
|
||||
self.group_watcher: mango.Watcher[mango.Group] = group_watcher
|
||||
self.account_watcher: mango.Watcher[mango.Account] = account_watcher
|
||||
|
@ -103,9 +108,9 @@ class ModelState:
|
|||
else:
|
||||
return top_ask.price - top_bid.price
|
||||
|
||||
@property
|
||||
def existing_orders(self) -> typing.Sequence[mango.PlacedOrder]:
|
||||
return self.placed_orders_container_watcher.latest.placed_orders
|
||||
def current_orders(self) -> typing.Sequence[mango.Order]:
|
||||
all_orders = [*self.bids_watcher.latest, *self.asks_watcher.latest]
|
||||
return list([o for o in all_orders if o.owner == self.order_owner])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝙼𝚘𝚍𝚎𝚕𝚂𝚝𝚊𝚝𝚎 for market '{self.market.symbol}'
|
||||
|
|
|
@ -80,9 +80,10 @@ class PollingModelStateBuilder(ModelStateBuilder):
|
|||
def poll(self, context: mango.Context) -> ModelState:
|
||||
raise NotImplementedError("PollingModelStateBuilder.poll() is not implemented on the base type.")
|
||||
|
||||
def from_values(self, market: mango.Market, group: mango.Group, account: mango.Account, price: mango.Price,
|
||||
placed_orders_container: mango.PlacedOrdersContainer, inventory: mango.Inventory,
|
||||
bids: typing.Sequence[mango.Order], asks: typing.Sequence[mango.Order]) -> ModelState:
|
||||
def from_values(self, order_owner: PublicKey, market: mango.Market, group: mango.Group, account: mango.Account,
|
||||
price: mango.Price, placed_orders_container: mango.PlacedOrdersContainer,
|
||||
inventory: mango.Inventory, bids: typing.Sequence[mango.Order],
|
||||
asks: typing.Sequence[mango.Order]) -> ModelState:
|
||||
group_watcher: mango.ManualUpdateWatcher[mango.Group] = mango.ManualUpdateWatcher(group)
|
||||
account_watcher: mango.ManualUpdateWatcher[mango.Account] = mango.ManualUpdateWatcher(account)
|
||||
price_watcher: mango.ManualUpdateWatcher[mango.Price] = mango.ManualUpdateWatcher(price)
|
||||
|
@ -92,7 +93,7 @@ class PollingModelStateBuilder(ModelStateBuilder):
|
|||
bids_watcher: mango.ManualUpdateWatcher[typing.Sequence[mango.Order]] = mango.ManualUpdateWatcher(bids)
|
||||
asks_watcher: mango.ManualUpdateWatcher[typing.Sequence[mango.Order]] = mango.ManualUpdateWatcher(asks)
|
||||
|
||||
return ModelState(market, group_watcher, account_watcher, price_watcher,
|
||||
return ModelState(order_owner, market, group_watcher, account_watcher, price_watcher,
|
||||
placed_orders_container_watcher, inventory_watcher, bids_watcher, asks_watcher)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
@ -105,6 +106,7 @@ class PollingModelStateBuilder(ModelStateBuilder):
|
|||
#
|
||||
class SerumPollingModelStateBuilder(PollingModelStateBuilder):
|
||||
def __init__(self,
|
||||
order_owner: PublicKey,
|
||||
market: mango.SerumMarket,
|
||||
oracle: mango.Oracle,
|
||||
group_address: PublicKey,
|
||||
|
@ -114,6 +116,7 @@ class SerumPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
quote_inventory_token_account: mango.TokenAccount,
|
||||
):
|
||||
super().__init__()
|
||||
self.order_owner: PublicKey = order_owner
|
||||
self.market: mango.SerumMarket = market
|
||||
self.oracle: mango.Oracle = oracle
|
||||
|
||||
|
@ -164,7 +167,7 @@ class SerumPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
base_inventory_token_account.value,
|
||||
quote_inventory_token_account.value)
|
||||
|
||||
return self.from_values(self.market, group, account, price, placed_orders_container, inventory, bids, asks)
|
||||
return self.from_values(self.order_owner, self.market, group, account, price, placed_orders_container, inventory, bids, asks)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝚂𝚎𝚛𝚞𝚖𝙿𝚘𝚕𝚕𝚒𝚗𝚐𝙼𝚘𝚍𝚎𝚕𝚂𝚝𝚊𝚝𝚎𝙱𝚞𝚒𝚕𝚍𝚎𝚛 for market '{self.market.symbol}' »"""
|
||||
|
@ -176,6 +179,7 @@ class SerumPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
#
|
||||
class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
||||
def __init__(self,
|
||||
order_owner: PublicKey,
|
||||
market: mango.SpotMarket,
|
||||
oracle: mango.Oracle,
|
||||
group_address: PublicKey,
|
||||
|
@ -184,6 +188,7 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
open_orders_address: PublicKey
|
||||
):
|
||||
super().__init__()
|
||||
self.order_owner: PublicKey = order_owner
|
||||
self.market: mango.SpotMarket = market
|
||||
self.oracle: mango.Oracle = oracle
|
||||
|
||||
|
@ -231,7 +236,7 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
|
||||
price: mango.Price = self.oracle.fetch_price(context)
|
||||
|
||||
return self.from_values(self.market, group, account, price, placed_orders_container, inventory, bids, asks)
|
||||
return self.from_values(self.order_owner, self.market, group, account, price, placed_orders_container, inventory, bids, asks)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝚂𝚙𝚘𝚝𝙿𝚘𝚕𝚕𝚒𝚗𝚐𝙼𝚘𝚍𝚎𝚕𝚂𝚝𝚊𝚝𝚎𝙱𝚞𝚒𝚕𝚍𝚎𝚛 for market '{self.market.symbol}' »"""
|
||||
|
@ -243,6 +248,7 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
#
|
||||
class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
||||
def __init__(self,
|
||||
order_owner: PublicKey,
|
||||
market: mango.PerpMarket,
|
||||
oracle: mango.Oracle,
|
||||
group_address: PublicKey,
|
||||
|
@ -250,6 +256,7 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
account_address: PublicKey
|
||||
):
|
||||
super().__init__()
|
||||
self.order_owner: PublicKey = order_owner
|
||||
self.market: mango.PerpMarket = market
|
||||
self.oracle: mango.Oracle = oracle
|
||||
|
||||
|
@ -296,7 +303,7 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
|
||||
price: mango.Price = self.oracle.fetch_price(context)
|
||||
|
||||
return self.from_values(self.market, group, account, price, placed_orders_container, inventory, bids.orders(), asks.orders())
|
||||
return self.from_values(self.order_owner, self.market, group, account, price, placed_orders_container, inventory, bids.orders(), asks.orders())
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝙿𝚎𝚛𝚙𝙿𝚘𝚕𝚕𝚒𝚗𝚐𝙼𝚘𝚍𝚎𝚕𝚂𝚝𝚊𝚝𝚎𝙱𝚞𝚒𝚕𝚍𝚎𝚛 for market '{self.market.symbol}' »"""
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# [Email](mailto:hello@blockworks.foundation)
|
||||
|
||||
import enum
|
||||
from mango.constants import SYSTEM_PROGRAM_ADDRESS
|
||||
import mango
|
||||
import typing
|
||||
|
||||
|
@ -81,7 +82,7 @@ def _polling_serum_model_state_builder_factory(context: mango.Context, wallet: m
|
|||
raise Exception(
|
||||
f"Could not find serum openorders account owned by {wallet.address} for market {market.symbol}.")
|
||||
return SerumPollingModelStateBuilder(
|
||||
market, oracle, group.address, account.address, all_open_orders[0].address, base_account, quote_account)
|
||||
all_open_orders[0].address, market, oracle, group.address, account.address, all_open_orders[0].address, base_account, quote_account)
|
||||
|
||||
|
||||
def _polling_spot_model_state_builder_factory(group: mango.Group, account: mango.Account, market: mango.SpotMarket,
|
||||
|
@ -92,12 +93,12 @@ def _polling_spot_model_state_builder_factory(group: mango.Group, account: mango
|
|||
raise Exception(
|
||||
f"Could not find spot openorders in account {account.address} for market {market.symbol}.")
|
||||
return SpotPollingModelStateBuilder(
|
||||
market, oracle, group.address, group.cache, account.address, open_orders_address)
|
||||
open_orders_address, market, oracle, group.address, group.cache, account.address, open_orders_address)
|
||||
|
||||
|
||||
def _polling_perp_model_state_builder_factory(group: mango.Group, account: mango.Account, market: mango.PerpMarket,
|
||||
oracle: mango.Oracle) -> ModelStateBuilder:
|
||||
return PerpPollingModelStateBuilder(market, oracle, group.address, group.cache, account.address)
|
||||
return PerpPollingModelStateBuilder(account.address, market, oracle, group.address, group.cache, account.address)
|
||||
|
||||
|
||||
def _websocket_model_state_builder_factory(context: mango.Context, disposer: mango.DisposePropagator,
|
||||
|
@ -118,6 +119,8 @@ def _websocket_model_state_builder_factory(context: mango.Context, disposer: man
|
|||
|
||||
market = mango.ensure_market_loaded(context, market)
|
||||
if isinstance(market, mango.SerumMarket):
|
||||
order_owner: PublicKey = market.find_openorders_address_for_owner(
|
||||
context, wallet.address) or SYSTEM_PROGRAM_ADDRESS
|
||||
price_watcher: mango.Watcher[mango.Price] = mango.build_price_watcher(
|
||||
context, websocket_manager, health_check, disposer, "serum", market)
|
||||
inventory_watcher: mango.Watcher[mango.Inventory] = mango.build_serum_inventory_watcher(
|
||||
|
@ -129,6 +132,8 @@ def _websocket_model_state_builder_factory(context: mango.Context, disposer: man
|
|||
latest_asks_watcher: mango.Watcher[typing.Sequence[mango.Order]] = mango.build_serum_orderbook_side_watcher(
|
||||
context, websocket_manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.ASKS)
|
||||
elif isinstance(market, mango.SpotMarket):
|
||||
market_index: int = group.find_spot_market_index(market.address)
|
||||
order_owner = account.spot_open_orders[market_index] or SYSTEM_PROGRAM_ADDRESS
|
||||
cache: mango.Cache = mango.Cache.load(context, group.cache)
|
||||
cache_watcher: mango.Watcher[mango.Cache] = mango.build_cache_watcher(
|
||||
context, websocket_manager, health_check, cache, group)
|
||||
|
@ -140,6 +145,7 @@ def _websocket_model_state_builder_factory(context: mango.Context, disposer: man
|
|||
latest_asks_watcher = mango.build_serum_orderbook_side_watcher(
|
||||
context, websocket_manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.ASKS)
|
||||
elif isinstance(market, mango.PerpMarket):
|
||||
order_owner = account.address
|
||||
cache = mango.Cache.load(context, group.cache)
|
||||
cache_watcher = mango.build_cache_watcher(context, websocket_manager, health_check, cache, group)
|
||||
inventory_watcher = mango.PerpInventoryAccountWatcher(market, latest_account_observer, cache_watcher, group)
|
||||
|
@ -152,7 +158,7 @@ def _websocket_model_state_builder_factory(context: mango.Context, disposer: man
|
|||
else:
|
||||
raise Exception(f"Could not determine type of market {market.symbol}")
|
||||
|
||||
model_state = ModelState(market, latest_group_observer, latest_account_observer,
|
||||
model_state = ModelState(order_owner, market, latest_group_observer, latest_account_observer,
|
||||
latest_price_observer, latest_open_orders_observer,
|
||||
inventory_watcher, latest_bids_watcher, latest_asks_watcher)
|
||||
return WebsocketModelStateBuilder(model_state)
|
||||
|
|
|
@ -1,67 +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 logging
|
||||
import mango
|
||||
import typing
|
||||
|
||||
from collections import deque
|
||||
|
||||
from .modelstate import ModelState
|
||||
|
||||
|
||||
# # 🥭 OrderTracker class
|
||||
#
|
||||
# Maintains a history of orders that were placed (or at least an attempt was made).
|
||||
#
|
||||
class OrderTracker:
|
||||
def __init__(self, max_history: int = 20):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.tracked: typing.Deque[mango.Order] = deque(maxlen=max_history)
|
||||
|
||||
def track(self, order: mango.Order):
|
||||
self.tracked += [order]
|
||||
|
||||
def existing_orders(self, model_state: ModelState) -> typing.Sequence[mango.Order]:
|
||||
live_orders: typing.List[mango.Order] = []
|
||||
for existing_order in model_state.existing_orders:
|
||||
details = self._find_tracked(existing_order.client_id)
|
||||
if details is None:
|
||||
self.logger.warning(f"Could not find existing order with client ID {existing_order.client_id}")
|
||||
# Return a stub order so that the Reconciler has the chance to cancel it.
|
||||
stub = mango.Order.from_ids(existing_order.id, existing_order.client_id, existing_order.side)
|
||||
live_orders += [stub]
|
||||
else:
|
||||
if details.id != existing_order.id:
|
||||
self.tracked.remove(details)
|
||||
details = details.with_id(existing_order.id)
|
||||
self.tracked += [details]
|
||||
|
||||
live_orders += [details]
|
||||
|
||||
return live_orders
|
||||
|
||||
def _find_tracked(self, client_id_to_find: int) -> typing.Optional[mango.Order]:
|
||||
for tracked in self.tracked:
|
||||
if tracked.client_id == client_id_to_find:
|
||||
return tracked
|
||||
return None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return """« 𝙾𝚛𝚍𝚎𝚛𝚁𝚎𝚌𝚘𝚗𝚌𝚒𝚕𝚎𝚛 »"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
|
@ -25,6 +25,7 @@ from .accountinfo import AccountInfo
|
|||
from .context import Context
|
||||
from .lotsizeconverter import LotSizeConverter, RaisingLotSizeConverter
|
||||
from .market import Market, InventorySource
|
||||
from .openorders import OpenOrders
|
||||
from .orders import Order
|
||||
from .serumeventqueue import SerumEvent, SerumEventQueue
|
||||
from .token import Token
|
||||
|
@ -55,6 +56,13 @@ class SerumMarket(Market):
|
|||
|
||||
return list(map(Order.from_serum_order, itertools.chain(bids_orderbook.orders(), asks_orderbook.orders())))
|
||||
|
||||
def find_openorders_address_for_owner(self, context: Context, owner: PublicKey) -> typing.Optional[PublicKey]:
|
||||
all_open_orders = OpenOrders.load_for_market_and_owner(
|
||||
context, self.address, owner, context.serum_program_address, self.base.decimals, self.quote.decimals)
|
||||
if len(all_open_orders) == 0:
|
||||
return None
|
||||
return all_open_orders[0].address
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""« 𝚂𝚎𝚛𝚞𝚖𝙼𝚊𝚛𝚔𝚎𝚝 {self.symbol} {self.address} [{self.program_address}]
|
||||
Event Queue: {self.underlying_serum_market.state.event_queue()}
|
||||
|
|
|
@ -142,7 +142,8 @@ def fake_asks():
|
|||
return None
|
||||
|
||||
|
||||
def fake_model_state(market: typing.Optional[mango.Market] = None,
|
||||
def fake_model_state(order_owner: typing.Optional[PublicKey] = None,
|
||||
market: typing.Optional[mango.Market] = None,
|
||||
group: typing.Optional[mango.Group] = None,
|
||||
account: typing.Optional[mango.Account] = None,
|
||||
price: typing.Optional[mango.Price] = None,
|
||||
|
@ -150,6 +151,7 @@ def fake_model_state(market: typing.Optional[mango.Market] = None,
|
|||
inventory: typing.Optional[mango.Inventory] = None,
|
||||
bids: typing.Optional[typing.Sequence[mango.Order]] = None,
|
||||
asks: typing.Optional[typing.Sequence[mango.Order]] = None) -> mango.marketmaking.ModelState:
|
||||
order_owner = order_owner or fake_seeded_public_key("order owner")
|
||||
market = market or fake_loaded_market()
|
||||
group = group or fake_group()
|
||||
account = account or fake_account()
|
||||
|
@ -167,5 +169,6 @@ def fake_model_state(market: typing.Optional[mango.Market] = None,
|
|||
bids_watcher: mango.ManualUpdateWatcher[typing.Sequence[mango.Order]] = mango.ManualUpdateWatcher(bids)
|
||||
asks_watcher: mango.ManualUpdateWatcher[typing.Sequence[mango.Order]] = mango.ManualUpdateWatcher(asks)
|
||||
|
||||
return mango.marketmaking.ModelState(market, group_watcher, account_watcher, price_watcher,
|
||||
placed_orders_container_watcher, inventory_watcher, bids_watcher, asks_watcher)
|
||||
return mango.marketmaking.ModelState(order_owner, market, group_watcher,
|
||||
account_watcher, price_watcher, placed_orders_container_watcher,
|
||||
inventory_watcher, bids_watcher, asks_watcher)
|
||||
|
|
Loading…
Reference in New Issue