mango-explorer/mango/collateralcalculator.py

135 lines
7.6 KiB
Python

# # ⚠ 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 abc
import logging
import typing
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
class CollateralCalculator(metaclass=abc.ABCMeta):
def __init__(self):
self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)
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.")
class SerumCollateralCalculator(CollateralCalculator):
def __init__(self):
super().__init__()
def calculate(self, account: Account, all_open_orders: typing.Dict[str, OpenOrders], cache: Cache) -> TokenValue:
raise NotImplementedError("SerumCollateralCalculator.calculate() is not implemented.")
class SpotCollateralCalculator(CollateralCalculator):
def __init__(self):
super().__init__()
# From Daffy in Discord, 30th August 2021 (https://discord.com/channels/791995070613159966/807051268304273408/882029587914182666)
# I think the correct calculation is
# total_collateral = deposits[QUOTE_INDEX] * deposit_index - borrows[QUOTE_INDEX] * borrow_index
# for i in num_oracles:
# total_collateral += prices[i] * (init_asset_weights[i] * deposits[i] * deposit_index - init_liab_weights[i] * borrows[i] * borrow_index)
#
# 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, 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.
total: Decimal = account.shared_quote_token.net_value.value
for basket_token in account.basket:
index = account.group.find_base_token_market_index(basket_token.token_info)
token_price: typing.Optional[PriceCache] = cache.price_cache[index]
if token_price is None:
raise Exception(
f"Could not read price of token {basket_token.token_info.token.symbol} at index {index} of cache at {cache.address}")
spot_market: typing.Optional[SpotMarketInfo] = account.group.spot_markets[index]
if spot_market is None:
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 = in_orders + (token_price.price * ((
basket_token.deposit.value * spot_market.init_asset_weight) - (
basket_token.borrow.value * spot_market.init_liab_weight)))
total += weighted
return TokenValue(account.group.shared_quote_token.token, total)
class PerpCollateralCalculator(CollateralCalculator):
def __init__(self):
super().__init__()
# From Daffy in Discord, 30th August 2021 (https://discord.com/channels/791995070613159966/807051268304273408/882029587914182666)
# I think the correct calculation is
# total_collateral = deposits[QUOTE_INDEX] * deposit_index - borrows[QUOTE_INDEX] * borrow_index
# for i in num_oracles:
# total_collateral += prices[i] * (init_asset_weights[i] * deposits[i] * deposit_index - init_liab_weights[i] * borrows[i] * borrow_index)
#
# 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, 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.
total: Decimal = account.shared_quote_token.net_value.value
for basket_token in account.basket:
index = account.group.find_base_token_market_index(basket_token.token_info)
token_price: typing.Optional[PriceCache] = cache.price_cache[index]
if token_price is None:
raise Exception(
f"Could not read price of token {basket_token.token_info.token.symbol} at index {index} of cache at {cache.address}")
# Not using perp market asset weights yet - stick with spot.
# perp_market: typing.Optional[PerpMarketInfo] = account.group.perp_markets[index]
# if perp_market is None:
# raise Exception(
# f"Could not read perp market of token {basket_token.token_info.token.symbol} at index {index} of cache at {cache.address}")
spot_market: typing.Optional[SpotMarketInfo] = account.group.spot_markets[index]
if spot_market is None:
raise Exception(
f"Could not read spot market of token {basket_token.token_info.token.symbol} at index {index} of cache at {cache.address}")
# 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 * ((
basket_token.deposit.value * spot_market.init_asset_weight) - (
basket_token.borrow.value * spot_market.init_liab_weight))
total += weighted
return TokenValue(account.group.shared_quote_token.token, total)