mango-explorer/mango/accountinstrumentvalues.py

251 lines
14 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://markets/) support is available at:
# [Docs](https://docs.markets/)
# [Discord](https://discord.gg/67jySBhxrg)
# [Twitter](https://twitter.com/mangomarkets)
# [Github](https://github.com/blockworks-foundation)
# [Email](mailto:hello@blockworks.foundation)
import typing
from datetime import datetime
from decimal import Decimal
from .account import AccountSlot
from .cache import PerpMarketCache, MarketCache, RootBankCache
from .calculators.unsettledfundingcalculator import calculate_unsettled_funding, UnsettledFundingParams
from .group import Group
from .instrumentvalue import InstrumentValue
from .lotsizeconverter import LotSizeConverter
from .openorders import OpenOrders
from .perpaccount import PerpAccount
from .token import Instrument, Token
# # 🥭 _token_values_from_open_orders function
#
# `_token_values_from_open_orders()` builds InstrumentValue objects from an OpenOrders object.
#
def _token_values_from_open_orders(base_token: Token, quote_token: Token, spot_open_orders: typing.Sequence[OpenOrders]) -> typing.Tuple[InstrumentValue, InstrumentValue, InstrumentValue, InstrumentValue]:
base_token_free: Decimal = Decimal(0)
base_token_total: Decimal = Decimal(0)
quote_token_free: Decimal = Decimal(0)
quote_token_total: Decimal = Decimal(0)
for open_orders in spot_open_orders:
base_token_free += open_orders.base_token_free
base_token_total += open_orders.base_token_total
quote_token_free += open_orders.quote_token_free
quote_token_total += open_orders.quote_token_total
return (InstrumentValue(base_token, base_token_free),
InstrumentValue(base_token, base_token_total),
InstrumentValue(quote_token, quote_token_free),
InstrumentValue(quote_token, quote_token_total))
# # 🥭 AccountInstrumentValues class
#
# `AccountInstrumentValues` gathers basket items together instead of separate arrays.
#
class AccountInstrumentValues:
def __init__(self, base_token: Instrument, quote_token: Token, raw_deposit: Decimal, deposit: InstrumentValue, raw_borrow: Decimal, borrow: InstrumentValue, base_token_free: InstrumentValue, base_token_total: InstrumentValue, quote_token_free: InstrumentValue, quote_token_total: InstrumentValue, perp_base_position: InstrumentValue, raw_perp_quote_position: Decimal, raw_taker_quote: Decimal, bids_quantity: InstrumentValue, asks_quantity: InstrumentValue, long_settled_funding: Decimal, short_settled_funding: Decimal, lot_size_converter: LotSizeConverter):
self.base_token: Instrument = base_token
self.quote_token: Token = quote_token
self.raw_deposit: Decimal = raw_deposit
self.deposit: InstrumentValue = deposit
self.raw_borrow: Decimal = raw_borrow
self.borrow: InstrumentValue = borrow
self.base_token_free: InstrumentValue = base_token_free
self.base_token_total: InstrumentValue = base_token_total
self.quote_token_free: InstrumentValue = quote_token_free
self.quote_token_total: InstrumentValue = quote_token_total
self.perp_base_position: InstrumentValue = perp_base_position
self.raw_perp_quote_position: Decimal = raw_perp_quote_position
self.raw_taker_quote: Decimal = raw_taker_quote
self.bids_quantity: InstrumentValue = bids_quantity
self.asks_quantity: InstrumentValue = asks_quantity
self.long_settled_funding: Decimal = long_settled_funding
self.short_settled_funding: Decimal = short_settled_funding
self.lot_size_converter: LotSizeConverter = lot_size_converter
@property
def net_value(self) -> InstrumentValue:
return self.deposit - self.borrow + self.base_token_total
@property
def base_token_locked(self) -> InstrumentValue:
return self.base_token_total - self.base_token_free
@property
def quote_token_locked(self) -> InstrumentValue:
return self.quote_token_total - self.quote_token_free
@property
def if_all_bids_executed(self) -> InstrumentValue:
return self.perp_base_position + self.bids_quantity
@property
def if_all_asks_executed(self) -> InstrumentValue:
return self.perp_base_position - self.asks_quantity
def priced(self, market_cache: MarketCache) -> "PricedAccountInstrumentValues":
# We can only price actual SPL tokens
if isinstance(self.base_token, Token):
return PricedAccountInstrumentValues(self, market_cache)
null_root_bank = RootBankCache(Decimal(1), Decimal(1), datetime.now())
market_cache_with_null_root_bank = MarketCache(market_cache.price, null_root_bank, market_cache.perp_market)
return PricedAccountInstrumentValues(self, market_cache_with_null_root_bank)
@staticmethod
def from_account_basket_base_token(account_basket_token: AccountSlot, open_orders_by_address: typing.Dict[str, OpenOrders], group: Group) -> "AccountInstrumentValues":
base_token: Instrument = account_basket_token.token_info.token
quote_token: Token = Token.ensure(account_basket_token.quote_token_info.token)
perp_account: typing.Optional[PerpAccount] = account_basket_token.perp_account
if perp_account is None:
raise Exception(f"No perp account for basket token {account_basket_token.token_info.token.symbol}")
base_token_free: InstrumentValue = InstrumentValue(base_token, Decimal(0))
base_token_total: InstrumentValue = InstrumentValue(base_token, Decimal(0))
quote_token_free: InstrumentValue = InstrumentValue(quote_token, Decimal(0))
quote_token_total: InstrumentValue = InstrumentValue(quote_token, Decimal(0))
if account_basket_token.spot_open_orders is not None:
open_orders: typing.Sequence[OpenOrders] = [
open_orders_by_address[str(account_basket_token.spot_open_orders)]]
base_token_free, base_token_total, quote_token_free, quote_token_total = _token_values_from_open_orders(
Token.ensure(base_token), Token.ensure(quote_token), open_orders)
lot_size_converter: LotSizeConverter = perp_account.lot_size_converter
perp_base_position: InstrumentValue = perp_account.base_token_value
perp_quote_position: Decimal = perp_account.quote_position_raw
long_settled_funding: Decimal = perp_account.long_settled_funding / lot_size_converter.quote_lot_size
short_settled_funding: Decimal = perp_account.short_settled_funding / lot_size_converter.quote_lot_size
taker_quote: Decimal = perp_account.taker_quote * lot_size_converter.quote_lot_size
bids_quantity: InstrumentValue = InstrumentValue(base_token, base_token.shift_to_decimals(
perp_account.bids_quantity * lot_size_converter.base_lot_size))
asks_quantity: InstrumentValue = InstrumentValue(base_token, base_token.shift_to_decimals(
perp_account.asks_quantity * lot_size_converter.base_lot_size))
return AccountInstrumentValues(base_token, quote_token, account_basket_token.raw_deposit, account_basket_token.deposit, account_basket_token.raw_borrow, account_basket_token.borrow, base_token_free, base_token_total, quote_token_free, quote_token_total, perp_base_position, perp_quote_position, taker_quote, bids_quantity, asks_quantity, long_settled_funding, short_settled_funding, lot_size_converter)
def __str__(self) -> str:
return f"""« 𝙰𝚌𝚌𝚘𝚞𝚗𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚖𝚎𝚗𝚝𝚅𝚊𝚕𝚞𝚎𝚜 {self.base_token.symbol}
Deposited : {self.deposit}
Borrowed : {self.borrow}
Unsettled:
Base : {self.base_token_total} ({self.base_token_free} free)
Quote : {self.quote_token_total} ({self.quote_token_free} free)
Perp:
Base : {self.perp_base_position}
Quote : {self.raw_perp_quote_position}
If Executed:
All Bids : {self.if_all_bids_executed}
All Asks : {self.if_all_asks_executed}
Net Value : {self.net_value}
»"""
def __repr__(self) -> str:
return f"{self}"
class PricedAccountInstrumentValues(AccountInstrumentValues):
def __init__(self, original_account_token_values: AccountInstrumentValues, market_cache: MarketCache):
price: InstrumentValue = market_cache.adjusted_price(
original_account_token_values.base_token, original_account_token_values.quote_token)
if market_cache.root_bank is None:
raise Exception(f"No root bank for token {original_account_token_values.base_token} in {market_cache}")
deposit_value: Decimal = original_account_token_values.raw_deposit * market_cache.root_bank.deposit_index * price.value
shifted_deposit_value: Decimal = original_account_token_values.quote_token.shift_to_decimals(deposit_value)
deposit: InstrumentValue = InstrumentValue(original_account_token_values.quote_token, shifted_deposit_value)
borrow_value: Decimal = original_account_token_values.raw_borrow * market_cache.root_bank.borrow_index * price.value
shifted_borrow_value: Decimal = original_account_token_values.quote_token.shift_to_decimals(borrow_value)
borrow: InstrumentValue = InstrumentValue(original_account_token_values.quote_token, shifted_borrow_value)
base_token_free: InstrumentValue = original_account_token_values.base_token_free * price
base_token_total: InstrumentValue = original_account_token_values.base_token_total * price
perp_base_position: InstrumentValue = original_account_token_values.perp_base_position * price
super().__init__(original_account_token_values.base_token, original_account_token_values.quote_token,
original_account_token_values.raw_deposit, deposit,
original_account_token_values.raw_borrow, borrow, base_token_free, base_token_total,
original_account_token_values.quote_token_free,
original_account_token_values.quote_token_total,
perp_base_position, original_account_token_values.raw_perp_quote_position,
original_account_token_values.raw_taker_quote,
original_account_token_values.bids_quantity, original_account_token_values.asks_quantity,
original_account_token_values.long_settled_funding, original_account_token_values.short_settled_funding,
original_account_token_values.lot_size_converter)
self.original_account_token_values: AccountInstrumentValues = original_account_token_values
self.price: InstrumentValue = price
self.perp_market_cache: typing.Optional[PerpMarketCache] = market_cache.perp_market
perp_quote_position: InstrumentValue = InstrumentValue(
original_account_token_values.quote_token, original_account_token_values.raw_perp_quote_position)
if market_cache.perp_market is not None:
original: AccountInstrumentValues = original_account_token_values
long_funding: Decimal = market_cache.perp_market.long_funding / original.lot_size_converter.quote_lot_size
short_funding: Decimal = market_cache.perp_market.short_funding / original.lot_size_converter.quote_lot_size
unsettled_funding: InstrumentValue = calculate_unsettled_funding(UnsettledFundingParams(
quote_token=original.quote_token,
base_position=original.perp_base_position,
long_funding=long_funding,
long_settled_funding=original.long_settled_funding,
short_funding=short_funding,
short_settled_funding=original.short_settled_funding
))
perp_quote_position -= unsettled_funding
self.perp_quote_position: InstrumentValue = perp_quote_position
@property
def if_all_bids_executed(self) -> InstrumentValue:
return self.perp_base_position + (self.bids_quantity * self.price)
@property
def if_all_asks_executed(self) -> InstrumentValue:
return self.perp_base_position - (self.asks_quantity * self.price)
def if_worst_execution(self) -> typing.Tuple[InstrumentValue, InstrumentValue]:
taker_quote: InstrumentValue = InstrumentValue(self.perp_quote_position.token, self.raw_taker_quote)
# print("Quote calc", self.perp_quote_position, taker_quote, self.bids_quantity, self.price)
if abs(self.if_all_bids_executed.value) > abs(self.if_all_asks_executed.value):
base_position = self.if_all_bids_executed
quote_position = self.perp_quote_position + taker_quote - (self.bids_quantity * self.price)
else:
base_position = self.if_all_asks_executed
quote_position = self.perp_quote_position + taker_quote + (self.asks_quantity * self.price)
return base_position, quote_position
def __str__(self) -> str:
return f"""« 𝙿𝚛𝚒𝚌𝚎𝚍𝙰𝚌𝚌𝚘𝚞𝚗𝚝𝙸𝚗𝚜𝚝𝚛𝚞𝚖𝚎𝚗𝚝𝚅𝚊𝚕𝚞𝚎𝚜 {self.base_token.symbol} priced in {self.quote_token.symbol}
Deposited : {self.original_account_token_values.deposit:<45} worth {self.deposit}
Borrowed : {self.original_account_token_values.borrow:<45} worth {self.borrow}
Unsettled:
Base : {self.original_account_token_values.base_token_total:<45} worth {self.base_token_total}
Quote : {self.original_account_token_values.quote_token_total:<45} worth {self.quote_token_total}
Perp:
Base : {self.original_account_token_values.perp_base_position:<45} worth {self.perp_base_position}
Quote : {self.perp_quote_position:<45} worth {self.perp_quote_position}
If Executed:
All Bids : {self.original_account_token_values.if_all_bids_executed:<45} worth {self.if_all_bids_executed}
All Asks : {self.original_account_token_values.if_all_asks_executed:<45} worth {self.if_all_asks_executed}
Net Value : {self.original_account_token_values.net_value:<45} worth {self.net_value}
»"""
def __repr__(self) -> str:
return f"{self}"