Updated SpotCollateralCalculator to take tokens in OpenOrders into account.
This commit is contained in:
parent
bf36c672c0
commit
8c42537572
|
@ -21,6 +21,7 @@ from decimal import Decimal
|
|||
|
||||
from .account import Account
|
||||
from .cache import Cache, PriceCache
|
||||
from .openorders import OpenOrders
|
||||
from .spotmarketinfo import SpotMarketInfo
|
||||
from .tokenvalue import TokenValue
|
||||
|
||||
|
@ -29,7 +30,7 @@ class CollateralCalculator(metaclass=abc.ABCMeta):
|
|||
def __init__(self):
|
||||
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
def calculate(self, account: Account, cache: Cache) -> TokenValue:
|
||||
def calculate(self, account: Account, all_open_orders: typing.Dict[str, OpenOrders], cache: Cache) -> TokenValue:
|
||||
raise NotImplementedError("CollateralCalculator.calculate() is not implemented on the base type.")
|
||||
|
||||
|
||||
|
@ -37,7 +38,7 @@ class SerumCollateralCalculator(CollateralCalculator):
|
|||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def calculate(self, account: Account, cache: Cache) -> TokenValue:
|
||||
def calculate(self, account: Account, all_open_orders: typing.Dict[str, OpenOrders], cache: Cache) -> TokenValue:
|
||||
raise NotImplementedError("SerumCollateralCalculator.calculate() is not implemented.")
|
||||
|
||||
|
||||
|
@ -54,7 +55,7 @@ class SpotCollateralCalculator(CollateralCalculator):
|
|||
# Also from Daffy, same thread, when I said there were two `init_asset_weights`, one for spot and one for perp (https://discord.com/channels/791995070613159966/807051268304273408/882030633940054056):
|
||||
# yes I think we ignore perps
|
||||
#
|
||||
def calculate(self, account: Account, cache: Cache) -> TokenValue:
|
||||
def calculate(self, account: Account, all_open_orders: typing.Dict[str, OpenOrders], cache: Cache) -> TokenValue:
|
||||
# Quote token calculation:
|
||||
# total_collateral = deposits[QUOTE_INDEX] * deposit_index - borrows[QUOTE_INDEX] * borrow_index
|
||||
# Note: the `AccountBasketToken` in the `Account` already factors the deposit and borrow index.
|
||||
|
@ -70,12 +71,18 @@ class SpotCollateralCalculator(CollateralCalculator):
|
|||
raise Exception(
|
||||
f"Could not read spot market of token {basket_token.token_info.token.symbol} at index {index} of cache at {cache.address}")
|
||||
|
||||
in_orders: Decimal = Decimal(0)
|
||||
if basket_token.spot_open_orders is not None and str(basket_token.spot_open_orders) in all_open_orders:
|
||||
open_orders: OpenOrders = all_open_orders[str(basket_token.spot_open_orders)]
|
||||
in_orders = open_orders.quote_token_total + \
|
||||
(open_orders.base_token_total * token_price.price * spot_market.init_asset_weight)
|
||||
|
||||
# Base token calculations:
|
||||
# total_collateral += prices[i] * (init_asset_weights[i] * deposits[i] * deposit_index - init_liab_weights[i] * borrows[i] * borrow_index)
|
||||
# Note: the `AccountBasketToken` in the `Account` already factors the deposit and borrow index.
|
||||
weighted: Decimal = token_price.price * ((
|
||||
weighted: Decimal = in_orders + (token_price.price * ((
|
||||
basket_token.deposit.value * spot_market.init_asset_weight) - (
|
||||
basket_token.borrow.value * spot_market.init_liab_weight))
|
||||
basket_token.borrow.value * spot_market.init_liab_weight)))
|
||||
total += weighted
|
||||
|
||||
return TokenValue(account.group.shared_quote_token.token, total)
|
||||
|
@ -94,7 +101,7 @@ class PerpCollateralCalculator(CollateralCalculator):
|
|||
# Also from Daffy, same thread, when I said there were two `init_asset_weights`, one for spot and one for perp (https://discord.com/channels/791995070613159966/807051268304273408/882030633940054056):
|
||||
# yes I think we ignore perps
|
||||
#
|
||||
def calculate(self, account: Account, cache: Cache) -> TokenValue:
|
||||
def calculate(self, account: Account, all_open_orders: typing.Dict[str, OpenOrders], cache: Cache) -> TokenValue:
|
||||
# Quote token calculation:
|
||||
# total_collateral = deposits[QUOTE_INDEX] * deposit_index - borrows[QUOTE_INDEX] * borrow_index
|
||||
# Note: the `AccountBasketToken` in the `Account` already factors the deposit and borrow index.
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -23,6 +24,7 @@ from .cache import Cache
|
|||
from .collateralcalculator import CollateralCalculator, SpotCollateralCalculator, PerpCollateralCalculator
|
||||
from .group import Group
|
||||
from .market import InventorySource, Market
|
||||
from .openorders import OpenOrders
|
||||
from .perpmarket import PerpMarket
|
||||
from .tokenvalue import TokenValue
|
||||
from .watcher import Watcher
|
||||
|
@ -56,8 +58,9 @@ class Inventory:
|
|||
|
||||
|
||||
class SpotInventoryAccountWatcher:
|
||||
def __init__(self, market: Market, account_watcher: Watcher[Account], cache_watcher: Watcher[Cache]):
|
||||
def __init__(self, market: Market, account_watcher: Watcher[Account], all_open_orders_watchers: typing.Sequence[Watcher[OpenOrders]], cache_watcher: Watcher[Cache]):
|
||||
self.account_watcher: Watcher[Account] = account_watcher
|
||||
self.all_open_orders_watchers: typing.Sequence[Watcher[OpenOrders]] = all_open_orders_watchers
|
||||
self.cache_watcher: Watcher[Cache] = cache_watcher
|
||||
account: Account = account_watcher.latest
|
||||
base_value = TokenValue.find_by_symbol(account.net_assets, market.base.symbol)
|
||||
|
@ -75,7 +78,9 @@ class SpotInventoryAccountWatcher:
|
|||
mngo = account.group.find_token_info_by_symbol("MNGO").token
|
||||
mngo_accrued: TokenValue = TokenValue(mngo, Decimal(0))
|
||||
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, cache)
|
||||
all_open_orders: typing.Dict[str, OpenOrders] = {
|
||||
str(oo_watcher.latest.address): oo_watcher.latest for oo_watcher in self.all_open_orders_watchers}
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, all_open_orders, cache)
|
||||
|
||||
base_value = account.net_assets[self.base_index]
|
||||
if base_value is None:
|
||||
|
@ -109,7 +114,7 @@ class PerpInventoryAccountWatcher:
|
|||
raise Exception(
|
||||
f"Could not find perp account for {self.market.symbol} in account {account.address} at index {self.perp_account_index}.")
|
||||
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, cache)
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, {}, cache)
|
||||
|
||||
base_lots = perp_account.base_position
|
||||
base_value = self.market.lot_size_converter.quantity_lots_to_value(base_lots)
|
||||
|
|
|
@ -185,7 +185,8 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
group_address: PublicKey,
|
||||
cache_address: PublicKey,
|
||||
account_address: PublicKey,
|
||||
open_orders_address: PublicKey
|
||||
open_orders_address: PublicKey,
|
||||
all_open_orders_addresses: typing.Sequence[PublicKey]
|
||||
):
|
||||
super().__init__()
|
||||
self.order_owner: PublicKey = order_owner
|
||||
|
@ -196,6 +197,7 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
self.cache_address: PublicKey = cache_address
|
||||
self.account_address: PublicKey = account_address
|
||||
self.open_orders_address: PublicKey = open_orders_address
|
||||
self.all_open_orders_addresses: typing.Sequence[PublicKey] = all_open_orders_addresses
|
||||
|
||||
self.collateral_calculator: mango.CollateralCalculator = mango.SpotCollateralCalculator()
|
||||
|
||||
|
@ -204,16 +206,33 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
self.group_address,
|
||||
self.cache_address,
|
||||
self.account_address,
|
||||
self.open_orders_address,
|
||||
self.market.underlying_serum_market.state.bids(),
|
||||
self.market.underlying_serum_market.state.asks()
|
||||
self.market.underlying_serum_market.state.asks(),
|
||||
*self.all_open_orders_addresses
|
||||
]
|
||||
account_infos: typing.Sequence[mango.AccountInfo] = mango.AccountInfo.load_multiple(context, addresses)
|
||||
group: mango.Group = mango.Group.parse(context, account_infos[0])
|
||||
cache: mango.Cache = mango.Cache.parse(account_infos[1])
|
||||
account: mango.Account = mango.Account.parse(account_infos[2], group)
|
||||
placed_orders_container: mango.PlacedOrdersContainer = mango.OpenOrders.parse(
|
||||
account_infos[3], self.market.base.decimals, self.market.quote.decimals)
|
||||
|
||||
# Update our stash of OpenOrders addresses for next time, in case new OpenOrders accounts were added
|
||||
self.all_open_orders_addresses = list([oo for oo in account.spot_open_orders if oo is not None])
|
||||
|
||||
spot_open_orders_account_infos_by_address = {
|
||||
str(account_info.address): account_info for account_info in account_infos[5:]}
|
||||
|
||||
all_open_orders: typing.Dict[str, mango.OpenOrders] = {}
|
||||
for basket_token in account.basket:
|
||||
if basket_token.spot_open_orders is not None and str(basket_token.spot_open_orders) in spot_open_orders_account_infos_by_address:
|
||||
account_info: mango.AccountInfo = spot_open_orders_account_infos_by_address[str(
|
||||
basket_token.spot_open_orders)]
|
||||
open_orders: mango.OpenOrders = mango.OpenOrders.parse(
|
||||
account_info,
|
||||
basket_token.token_info.decimals,
|
||||
account.shared_quote_token.token_info.token.decimals)
|
||||
all_open_orders[str(basket_token.spot_open_orders)] = open_orders
|
||||
|
||||
placed_orders_container: mango.PlacedOrdersContainer = all_open_orders[str(self.open_orders_address)]
|
||||
|
||||
# Spot markets don't accrue MNGO liquidity incentives
|
||||
mngo = account.group.find_token_info_by_symbol("MNGO").token
|
||||
|
@ -222,7 +241,7 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
base_value = mango.TokenValue.find_by_symbol(account.net_assets, self.market.base.symbol)
|
||||
quote_value = mango.TokenValue.find_by_symbol(account.net_assets, self.market.quote.symbol)
|
||||
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, cache)
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, all_open_orders, cache)
|
||||
inventory: mango.Inventory = mango.Inventory(mango.InventorySource.ACCOUNT,
|
||||
mngo_accrued,
|
||||
available_collateral,
|
||||
|
@ -230,9 +249,9 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
quote_value)
|
||||
|
||||
bids: typing.Sequence[mango.Order] = mango.parse_account_info_to_orders(
|
||||
account_infos[4], self.market.underlying_serum_market)
|
||||
account_infos[3], self.market.underlying_serum_market)
|
||||
asks: typing.Sequence[mango.Order] = mango.parse_account_info_to_orders(
|
||||
account_infos[5], self.market.underlying_serum_market)
|
||||
account_infos[4], self.market.underlying_serum_market)
|
||||
|
||||
price: mango.Price = self.oracle.fetch_price(context)
|
||||
|
||||
|
@ -289,7 +308,7 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
base_value = self.market.lot_size_converter.quantity_lots_to_value(base_lots)
|
||||
base_token_value = mango.TokenValue(self.market.base, base_value)
|
||||
quote_token_value = mango.TokenValue.find_by_symbol(account.net_assets, self.market.quote.symbol)
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, cache)
|
||||
available_collateral: TokenValue = self.collateral_calculator.calculate(account, {}, cache)
|
||||
inventory: mango.Inventory = mango.Inventory(mango.InventorySource.ACCOUNT,
|
||||
perp_account.mngo_accrued,
|
||||
available_collateral,
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
# [Email](mailto:hello@blockworks.foundation)
|
||||
|
||||
import enum
|
||||
from mango.constants import SYSTEM_PROGRAM_ADDRESS
|
||||
import mango
|
||||
import typing
|
||||
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
from ..constants import SYSTEM_PROGRAM_ADDRESS
|
||||
from .modelstate import ModelState
|
||||
from .modelstatebuilder import ModelStateBuilder, WebsocketModelStateBuilder, SerumPollingModelStateBuilder, SpotPollingModelStateBuilder, PerpPollingModelStateBuilder
|
||||
|
||||
|
@ -89,11 +89,13 @@ def _polling_spot_model_state_builder_factory(group: mango.Group, account: mango
|
|||
oracle: mango.Oracle) -> ModelStateBuilder:
|
||||
market_index: int = group.find_spot_market_index(market.address)
|
||||
open_orders_address: typing.Optional[PublicKey] = account.spot_open_orders[market_index]
|
||||
all_open_orders_addresses: typing.Sequence[PublicKey] = list(
|
||||
[oo for oo in account.spot_open_orders if oo is not None])
|
||||
if open_orders_address is None:
|
||||
raise Exception(
|
||||
f"Could not find spot openorders in account {account.address} for market {market.symbol}.")
|
||||
return SpotPollingModelStateBuilder(
|
||||
open_orders_address, 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, all_open_orders_addresses)
|
||||
|
||||
|
||||
def _polling_perp_model_state_builder_factory(group: mango.Group, account: mango.Account, market: mango.PerpMarket,
|
||||
|
@ -137,9 +139,24 @@ def _websocket_model_state_builder_factory(context: mango.Context, disposer: man
|
|||
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)
|
||||
inventory_watcher = mango.SpotInventoryAccountWatcher(market, latest_account_observer, cache_watcher)
|
||||
latest_open_orders_observer = mango.build_spot_open_orders_watcher(
|
||||
context, websocket_manager, health_check, wallet, account, group, market)
|
||||
|
||||
all_open_orders_watchers: typing.List[mango.Watcher[mango.OpenOrders]] = []
|
||||
for basket_token in account.basket:
|
||||
if basket_token.spot_open_orders is not None:
|
||||
spot_market_symbol: str = f"spot:{basket_token.token_info.token.symbol}/{account.shared_quote_token.token_info.token.symbol}"
|
||||
spot_market = context.market_lookup.find_by_symbol(spot_market_symbol)
|
||||
if spot_market is None:
|
||||
raise Exception(f"Could not find spot market {spot_market_symbol}")
|
||||
if not isinstance(spot_market, mango.SpotMarket):
|
||||
raise Exception(f"Market {spot_market_symbol} is not a spot market")
|
||||
oo_watcher = mango.build_spot_open_orders_watcher(
|
||||
context, websocket_manager, health_check, wallet, account, group, spot_market)
|
||||
all_open_orders_watchers += [oo_watcher]
|
||||
if market.base == spot_market.base and market.quote == spot_market.quote:
|
||||
latest_open_orders_observer = oo_watcher
|
||||
|
||||
inventory_watcher = mango.SpotInventoryAccountWatcher(
|
||||
market, latest_account_observer, all_open_orders_watchers, cache_watcher)
|
||||
latest_bids_watcher = mango.build_serum_orderbook_side_watcher(
|
||||
context, websocket_manager, health_check, market.underlying_serum_market, mango.OrderBookSideType.BIDS)
|
||||
latest_asks_watcher = mango.build_serum_orderbook_side_watcher(
|
||||
|
|
|
@ -81,7 +81,7 @@ def build_cache_watcher(context: Context, manager: WebSocketSubscriptionManager,
|
|||
return latest_cache_observer
|
||||
|
||||
|
||||
def build_spot_open_orders_watcher(context: Context, manager: WebSocketSubscriptionManager, health_check: HealthCheck, wallet: Wallet, account: Account, group: Group, spot_market: SpotMarket) -> Watcher[PlacedOrdersContainer]:
|
||||
def build_spot_open_orders_watcher(context: Context, manager: WebSocketSubscriptionManager, health_check: HealthCheck, wallet: Wallet, account: Account, group: Group, spot_market: SpotMarket) -> Watcher[OpenOrders]:
|
||||
market_index = group.find_spot_market_index(spot_market.address)
|
||||
open_orders_address = account.spot_open_orders[market_index]
|
||||
if open_orders_address is None:
|
||||
|
@ -97,7 +97,7 @@ def build_spot_open_orders_watcher(context: Context, manager: WebSocketSubscript
|
|||
manager.add(spot_open_orders_subscription)
|
||||
initial_spot_open_orders = OpenOrders.load(
|
||||
context, open_orders_address, spot_market.base.decimals, spot_market.quote.decimals)
|
||||
latest_open_orders_observer = LatestItemObserverSubscriber[PlacedOrdersContainer](
|
||||
latest_open_orders_observer = LatestItemObserverSubscriber[OpenOrders](
|
||||
initial_spot_open_orders)
|
||||
spot_open_orders_subscription.publisher.subscribe(latest_open_orders_observer)
|
||||
health_check.add("open_orders_subscription", spot_open_orders_subscription.publisher)
|
||||
|
|
Loading…
Reference in New Issue