# # ⚠ 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)