Removed stale calculator cruft now that pandas is the main mechanism.
This commit is contained in:
parent
8eb308d9be
commit
5063f14a16
|
@ -4,6 +4,7 @@ import argparse
|
|||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import pandas
|
||||
import sys
|
||||
import typing
|
||||
|
||||
|
@ -74,75 +75,35 @@ mango.output(f"Total Value: {total_in_wallet}")
|
|||
|
||||
|
||||
mango_accounts = mango.Account.load_all_for_owner(context, address, group)
|
||||
account_value: mango.InstrumentValue = mango.InstrumentValue(
|
||||
group.shared_quote_token, Decimal(0)
|
||||
grand_total: mango.InstrumentValue = mango.InstrumentValue(
|
||||
group.shared_quote_token, total_in_wallet.value
|
||||
)
|
||||
quote_token_free_in_open_orders: mango.InstrumentValue = mango.InstrumentValue(
|
||||
group.shared_quote_token, Decimal(0)
|
||||
)
|
||||
quote_token_total_in_open_orders: mango.InstrumentValue = mango.InstrumentValue(
|
||||
group.shared_quote_token, Decimal(0)
|
||||
)
|
||||
grand_total: mango.InstrumentValue = total_in_wallet
|
||||
for account in mango_accounts:
|
||||
mango.output(
|
||||
"\n⚠ WARNING! ⚠ This is a work-in-progress and these figures may be wrong!\n"
|
||||
)
|
||||
mango.output(f"\nAccount Balances [{account.address}]:")
|
||||
at_least_one_output: bool = False
|
||||
pandas.set_option("display.max_columns", None)
|
||||
pandas.set_option("display.width", None)
|
||||
pandas.set_option("display.precision", 6)
|
||||
|
||||
open_orders: typing.Dict[str, mango.OpenOrders] = account.load_all_spot_open_orders(
|
||||
context
|
||||
)
|
||||
for asset in account.base_slots:
|
||||
if (
|
||||
(asset.deposit.value != 0)
|
||||
or (asset.borrow.value != 0)
|
||||
or (asset.net_value.value != 0)
|
||||
or ((asset.perp_account is not None) and not asset.perp_account.empty)
|
||||
):
|
||||
at_least_one_output = True
|
||||
report: mango.AccountInstrumentValues = (
|
||||
mango.AccountInstrumentValues.from_account_basket_base_token(
|
||||
asset, open_orders, group
|
||||
)
|
||||
)
|
||||
# mango.output(report)
|
||||
market_cache: mango.MarketCache = group.market_cache_from_cache(
|
||||
cache, report.base_token
|
||||
)
|
||||
price_from_cache: mango.InstrumentValue = group.token_price_from_cache(
|
||||
cache, report.base_token
|
||||
)
|
||||
priced_report: mango.AccountInstrumentValues = report.priced(market_cache)
|
||||
account_value += priced_report.net_value
|
||||
quote_token_free_in_open_orders += priced_report.quote_token_free
|
||||
quote_token_total_in_open_orders += priced_report.quote_token_total
|
||||
mango.output(priced_report)
|
||||
|
||||
quote_report = mango.AccountInstrumentValues(
|
||||
account.shared_quote_token,
|
||||
account.shared_quote_token,
|
||||
account.shared_quote.raw_deposit,
|
||||
account.shared_quote.deposit,
|
||||
account.shared_quote.raw_borrow,
|
||||
account.shared_quote.borrow,
|
||||
mango.InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
mango.InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
quote_token_free_in_open_orders,
|
||||
quote_token_total_in_open_orders,
|
||||
mango.InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
Decimal(0),
|
||||
Decimal(0),
|
||||
mango.InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
mango.InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
Decimal(0),
|
||||
Decimal(0),
|
||||
mango.NullLotSizeConverter(),
|
||||
)
|
||||
account_value += quote_report.net_value + quote_token_total_in_open_orders
|
||||
mango.output(quote_report)
|
||||
|
||||
mango.output(f"Account Total: {account_value}")
|
||||
frame: pandas.DataFrame = account.to_dataframe(group, open_orders, cache)
|
||||
account_value = account.total_value(frame)
|
||||
grand_total += account_value
|
||||
|
||||
numeric_columns = [
|
||||
"CurrentPrice",
|
||||
"SpotValue",
|
||||
"SpotOpenValue",
|
||||
"PerpNotionalSize",
|
||||
"PerpAsset",
|
||||
"PerpLiability",
|
||||
"UnsettledFunding",
|
||||
]
|
||||
for column_name in numeric_columns:
|
||||
frame[column_name] = pandas.to_numeric(frame[column_name])
|
||||
output_columns = ["Symbol", *numeric_columns]
|
||||
mango.output(frame[output_columns])
|
||||
if mango.output_formatter.format == mango.OutputFormat.TEXT:
|
||||
mango.output("Account Value:", account_value)
|
||||
|
||||
mango.output(f"\nGrand Total: {grand_total}")
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import typing
|
||||
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
import mango # nopep8
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Display the balances of all group tokens in the current wallet."
|
||||
)
|
||||
mango.ContextBuilder.add_command_line_parameters(parser)
|
||||
mango.Wallet.add_command_line_parameters(parser)
|
||||
parser.add_argument(
|
||||
"--address",
|
||||
type=PublicKey,
|
||||
help="Root address to check (if not provided, the wallet address is used)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json-filename",
|
||||
type=str,
|
||||
help="If specified, a file to write the balance information in JSON format",
|
||||
)
|
||||
args: argparse.Namespace = mango.parse_args(parser)
|
||||
|
||||
address: typing.Optional[PublicKey] = args.address
|
||||
if address is None:
|
||||
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
|
||||
address = wallet.address
|
||||
|
||||
context: mango.Context = mango.ContextBuilder.from_command_line_parameters(args)
|
||||
group: mango.Group = mango.Group.load(context)
|
||||
cache: mango.Cache = mango.Cache.load(context, group.cache)
|
||||
|
||||
valuation: mango.Valuation = mango.Valuation.from_wallet(context, group, cache, address)
|
||||
|
||||
mango.output(valuation)
|
||||
if args.json_filename is not None:
|
||||
with open(args.json_filename, "w") as json_file:
|
||||
json.dump(valuation.to_json_dict(), json_file, indent=4)
|
|
@ -1,69 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import mango.calculators.healthcalculator
|
||||
import argparse
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from solana.publickey import PublicKey
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
import mango # nopep8
|
||||
|
||||
parser = argparse.ArgumentParser(description="Shows health of a Mango account.")
|
||||
mango.ContextBuilder.add_command_line_parameters(parser)
|
||||
mango.Wallet.add_command_line_parameters(parser)
|
||||
parser.add_argument(
|
||||
"--address", type=PublicKey, required=False, help="address of the Mango account"
|
||||
)
|
||||
args: argparse.Namespace = mango.parse_args(parser)
|
||||
|
||||
context: mango.Context = mango.ContextBuilder.from_command_line_parameters(args)
|
||||
group: mango.Group = mango.Group.load(context, context.group_address)
|
||||
cache: mango.Cache = mango.Cache.load(context, group.cache)
|
||||
|
||||
address: PublicKey = args.address
|
||||
mango_account: mango.Account
|
||||
if address is None:
|
||||
wallet = mango.Wallet.from_command_line_parameters_or_raise(args)
|
||||
address = wallet.address
|
||||
mango_accounts = mango.Account.load_all_for_owner(context, address, group)
|
||||
mango_account = mango_accounts[0]
|
||||
else:
|
||||
mango_account = mango.Account.load(context, address, group)
|
||||
|
||||
health_calculator = mango.calculators.healthcalculator.HealthCalculator(
|
||||
context, mango.calculators.healthcalculator.HealthType.INITIAL
|
||||
)
|
||||
|
||||
spot_open_orders_addresses = list(
|
||||
[
|
||||
basket_token.spot_open_orders
|
||||
for basket_token in mango_account.slots
|
||||
if basket_token.spot_open_orders is not None
|
||||
]
|
||||
)
|
||||
spot_open_orders_account_infos = mango.AccountInfo.load_multiple(
|
||||
context, spot_open_orders_addresses
|
||||
)
|
||||
spot_open_orders_account_infos_by_address = {
|
||||
str(account_info.address): account_info
|
||||
for account_info in spot_open_orders_account_infos
|
||||
}
|
||||
spot_open_orders = {}
|
||||
for basket_token in mango_account.slots:
|
||||
if basket_token.spot_open_orders is not None:
|
||||
account_info = spot_open_orders_account_infos_by_address[
|
||||
str(basket_token.spot_open_orders)
|
||||
]
|
||||
oo = mango.OpenOrders.parse(
|
||||
account_info,
|
||||
basket_token.base_instrument.decimals,
|
||||
mango_account.shared_quote_token.decimals,
|
||||
)
|
||||
spot_open_orders[str(basket_token.spot_open_orders)] = oo
|
||||
|
||||
mango.output(
|
||||
"Health", health_calculator.calculate(mango_account, spot_open_orders, group, cache)
|
||||
)
|
|
@ -14,10 +14,6 @@ from .accountinfo import AccountInfo as AccountInfo
|
|||
from .accountinfoconverter import (
|
||||
build_account_info_converter as build_account_info_converter,
|
||||
)
|
||||
from .accountinstrumentvalues import AccountInstrumentValues as AccountInstrumentValues
|
||||
from .accountinstrumentvalues import (
|
||||
PricedAccountInstrumentValues as PricedAccountInstrumentValues,
|
||||
)
|
||||
from .accountliquidator import AccountLiquidator as AccountLiquidator
|
||||
from .accountliquidator import NullAccountLiquidator as NullAccountLiquidator
|
||||
from .accountscout import AccountScout as AccountScout
|
||||
|
@ -83,8 +79,7 @@ from .idl import IdlParser as IdlParser
|
|||
from .idl import lazy_load_cached_idl_parser as lazy_load_cached_idl_parser
|
||||
from .idsjsonmarketlookup import IdsJsonMarketLookup as IdsJsonMarketLookup
|
||||
from .inventory import Inventory as Inventory
|
||||
from .inventory import PerpInventoryAccountWatcher as PerpInventoryAccountWatcher
|
||||
from .inventory import SpotInventoryAccountWatcher as SpotInventoryAccountWatcher
|
||||
from .inventory import InventoryAccountWatcher as InventoryAccountWatcher
|
||||
from .instructions import (
|
||||
build_cancel_all_perp_orders_instructions as build_cancel_all_perp_orders_instructions,
|
||||
)
|
||||
|
@ -308,9 +303,6 @@ from .transactionscout import (
|
|||
from .transactionscout import (
|
||||
mango_instruction_from_response as mango_instruction_from_response,
|
||||
)
|
||||
from .valuation import AccountValuation as AccountValuation
|
||||
from .valuation import TokenValuation as TokenValuation
|
||||
from .valuation import Valuation as Valuation
|
||||
from .version import Version as Version
|
||||
from .wallet import Wallet as Wallet
|
||||
from .walletbalancer import FilterSmallChanges as FilterSmallChanges
|
||||
|
|
|
@ -1006,13 +1006,15 @@ class Account(AddressableAccount):
|
|||
|
||||
return assets, liabilities
|
||||
|
||||
def init_health(self, frame: pandas.DataFrame) -> Decimal:
|
||||
def init_health(self, frame: pandas.DataFrame) -> InstrumentValue:
|
||||
assets, liabilities = self.weighted_assets(frame, "Init")
|
||||
return assets + liabilities
|
||||
value: Decimal = assets + liabilities
|
||||
return InstrumentValue(self.shared_quote_token, value)
|
||||
|
||||
def maint_health(self, frame: pandas.DataFrame) -> Decimal:
|
||||
def maint_health(self, frame: pandas.DataFrame) -> InstrumentValue:
|
||||
assets, liabilities = self.weighted_assets(frame, "Maint")
|
||||
return assets + liabilities
|
||||
value: Decimal = assets + liabilities
|
||||
return InstrumentValue(self.shared_quote_token, value)
|
||||
|
||||
def init_health_ratio(self, frame: pandas.DataFrame) -> Decimal:
|
||||
assets, liabilities = self.weighted_assets(frame, "Init")
|
||||
|
@ -1028,10 +1030,11 @@ class Account(AddressableAccount):
|
|||
|
||||
return ((assets / -liabilities) - 1) * 100
|
||||
|
||||
def total_value(self, frame: pandas.DataFrame) -> Decimal:
|
||||
def total_value(self, frame: pandas.DataFrame) -> InstrumentValue:
|
||||
assets, liabilities = self.unweighted_assets(frame)
|
||||
|
||||
return assets + liabilities
|
||||
value: Decimal = assets + liabilities
|
||||
return InstrumentValue(self.shared_quote_token, value)
|
||||
|
||||
def is_liquidatable(self, frame: pandas.DataFrame) -> bool:
|
||||
if self.being_liquidated and self.init_health(frame) < 0:
|
||||
|
|
|
@ -1,388 +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://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,
|
||||
) -> None:
|
||||
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_slot: AccountSlot,
|
||||
open_orders_by_address: typing.Dict[str, OpenOrders],
|
||||
group: Group,
|
||||
) -> "AccountInstrumentValues":
|
||||
base_token: Instrument = account_slot.base_instrument
|
||||
quote_token: Token = Token.ensure(account_slot.quote_token_bank.token)
|
||||
perp_account: typing.Optional[PerpAccount] = account_slot.perp_account
|
||||
if perp_account is None:
|
||||
raise Exception(
|
||||
f"No perp account for basket token {account_slot.base_instrument.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_slot.spot_open_orders is not None:
|
||||
open_orders: typing.Sequence[OpenOrders] = [
|
||||
open_orders_by_address[str(account_slot.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_slot.raw_deposit,
|
||||
account_slot.deposit,
|
||||
account_slot.raw_borrow,
|
||||
account_slot.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"""« AccountInstrumentValues {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,
|
||||
) -> None:
|
||||
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
|
||||
)
|
||||
|
||||
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"""« PricedAccountInstrumentValues {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}"
|
|
@ -1,6 +0,0 @@
|
|||
# from .collateralcalculator import CollateralCalculator
|
||||
# from .healthcalculator import HealthType, HealthCalculator
|
||||
# from .perpcollateralcalculator import PerpCollateralCalculator
|
||||
# from .serumcollateralcalculator import SerumCollateralCalculator
|
||||
# from .spotcollateralcalculator import SpotCollateralCalculator
|
||||
# from .unsettledfundingcalculator import UnsettledFundingParams, calculate_unsettled_funding
|
|
@ -1,40 +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 abc
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from ..account import Account
|
||||
from ..cache import Cache
|
||||
from ..group import Group
|
||||
from ..instrumentvalue import InstrumentValue
|
||||
from ..openorders import OpenOrders
|
||||
|
||||
|
||||
class CollateralCalculator(metaclass=abc.ABCMeta):
|
||||
def __init__(self) -> None:
|
||||
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
def calculate(
|
||||
self,
|
||||
account: Account,
|
||||
all_open_orders: typing.Dict[str, OpenOrders],
|
||||
group: Group,
|
||||
cache: Cache,
|
||||
) -> InstrumentValue:
|
||||
raise NotImplementedError(
|
||||
"CollateralCalculator.calculate() is not implemented on the base type."
|
||||
)
|
|
@ -1,245 +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 enum
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from ..account import Account, AccountSlot
|
||||
from ..accountinstrumentvalues import (
|
||||
AccountInstrumentValues,
|
||||
PricedAccountInstrumentValues,
|
||||
)
|
||||
from ..cache import Cache, MarketCache
|
||||
from ..context import Context
|
||||
from ..group import GroupSlotSpotMarket, GroupSlotPerpMarket, GroupSlot, Group
|
||||
from ..instrumentvalue import InstrumentValue
|
||||
from ..lotsizeconverter import NullLotSizeConverter
|
||||
from ..openorders import OpenOrders
|
||||
from ..perpaccount import PerpAccount
|
||||
from ..token import Instrument
|
||||
|
||||
|
||||
# # 🥭 HealthType enum
|
||||
#
|
||||
# Is the health calculation Initial or Maintenance?
|
||||
#
|
||||
class HealthType(enum.Enum):
|
||||
# We use strings here so that argparse can work with these as parameters.
|
||||
INITIAL = "INITIAL"
|
||||
MAINTENANCE = "MAINTENANCE"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
||||
|
||||
|
||||
class HealthCalculator:
|
||||
def __init__(self, context: Context, health_type: HealthType) -> None:
|
||||
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
self.context: Context = context
|
||||
self.health_type: HealthType = health_type
|
||||
|
||||
def _calculate_pessimistic_spot_value(
|
||||
self, values: PricedAccountInstrumentValues
|
||||
) -> typing.Tuple[InstrumentValue, InstrumentValue]:
|
||||
# base total if all bids were executed
|
||||
if_all_bids_executed: InstrumentValue = (
|
||||
values.quote_token_locked + values.base_token_total
|
||||
)
|
||||
|
||||
# base total if all asks were executed
|
||||
if_all_asks_executed: InstrumentValue = values.base_token_free
|
||||
|
||||
base: InstrumentValue
|
||||
quote: InstrumentValue
|
||||
if if_all_bids_executed > if_all_asks_executed:
|
||||
base = values.net_value + if_all_bids_executed
|
||||
quote = values.quote_token_free
|
||||
return base, quote
|
||||
else:
|
||||
base = values.net_value + if_all_asks_executed
|
||||
quote = values.base_token_locked + values.quote_token_total
|
||||
return base, quote
|
||||
|
||||
def _calculate_pessimistic_perp_value(
|
||||
self, values: PricedAccountInstrumentValues
|
||||
) -> typing.Tuple[InstrumentValue, InstrumentValue]:
|
||||
return values.perp_base_position, values.perp_quote_position
|
||||
|
||||
def _calculate_perp_value(
|
||||
self,
|
||||
basket_token: AccountSlot,
|
||||
token_price: InstrumentValue,
|
||||
market_index: int,
|
||||
cache: Cache,
|
||||
unadjustment_factor: Decimal,
|
||||
) -> typing.Tuple[Decimal, Decimal]:
|
||||
if basket_token.perp_account is None or basket_token.perp_account.empty:
|
||||
return Decimal(0), Decimal(0)
|
||||
|
||||
perp_market_cache = cache.perp_market_cache[market_index]
|
||||
if perp_market_cache is None:
|
||||
raise Exception(
|
||||
f"Cache contains no perp market cache for market index {market_index}."
|
||||
)
|
||||
|
||||
perp_account: PerpAccount = basket_token.perp_account
|
||||
token: Instrument = basket_token.base_instrument
|
||||
base_lot_size: Decimal = perp_account.lot_size_converter.base_lot_size
|
||||
quote_lot_size: Decimal = perp_account.lot_size_converter.quote_lot_size
|
||||
|
||||
takerQuote: Decimal = perp_account.taker_quote * quote_lot_size
|
||||
base_position: Decimal = (
|
||||
perp_account.base_position + perp_account.taker_base
|
||||
) * base_lot_size
|
||||
bids_quantity: Decimal = perp_account.bids_quantity * base_lot_size
|
||||
asks_quantity: Decimal = perp_account.asks_quantity * base_lot_size
|
||||
if_all_bids_executed = (
|
||||
token.shift_to_decimals(base_position + bids_quantity) * unadjustment_factor
|
||||
)
|
||||
if_all_asks_executed = (
|
||||
token.shift_to_decimals(base_position - asks_quantity) * unadjustment_factor
|
||||
)
|
||||
|
||||
if abs(if_all_bids_executed) > abs(if_all_asks_executed):
|
||||
quote_position = (
|
||||
perp_account.quote_position
|
||||
- perp_account.unsettled_funding(perp_market_cache)
|
||||
)
|
||||
full_quote_position = (
|
||||
quote_position + takerQuote - (bids_quantity * token_price.value)
|
||||
)
|
||||
return if_all_bids_executed, full_quote_position
|
||||
else:
|
||||
quote_position = (
|
||||
perp_account.quote_position
|
||||
- perp_account.unsettled_funding(perp_market_cache)
|
||||
)
|
||||
full_quote_position = (
|
||||
quote_position + takerQuote + (asks_quantity * token_price.value)
|
||||
)
|
||||
return if_all_asks_executed, full_quote_position
|
||||
|
||||
def calculate(
|
||||
self,
|
||||
account: Account,
|
||||
open_orders_by_address: typing.Dict[str, OpenOrders],
|
||||
group: Group,
|
||||
cache: Cache,
|
||||
) -> Decimal:
|
||||
priced_reports: typing.List[PricedAccountInstrumentValues] = []
|
||||
for asset in account.base_slots:
|
||||
# if (asset.deposit.value != 0) or (asset.borrow.value != 0) or (asset.net_value.value != 0):
|
||||
report: AccountInstrumentValues = (
|
||||
AccountInstrumentValues.from_account_basket_base_token(
|
||||
asset, open_orders_by_address, group
|
||||
)
|
||||
)
|
||||
# print("report", report)
|
||||
# price: InstrumentValue = group.token_price_from_cache(cache, report.base_token)
|
||||
market_cache: MarketCache = group.market_cache_from_cache(
|
||||
cache, report.base_token
|
||||
)
|
||||
# print("Market cache", market_cache)
|
||||
priced_report: PricedAccountInstrumentValues = report.priced(market_cache)
|
||||
# print("priced_report", priced_report)
|
||||
priced_reports += [priced_report]
|
||||
|
||||
quote_token_free_in_open_orders: InstrumentValue = InstrumentValue(
|
||||
group.shared_quote_token, Decimal(0)
|
||||
)
|
||||
quote_token_total_in_open_orders: InstrumentValue = InstrumentValue(
|
||||
group.shared_quote_token, Decimal(0)
|
||||
)
|
||||
for priced_report in priced_reports:
|
||||
quote_token_free_in_open_orders += priced_report.quote_token_free
|
||||
quote_token_total_in_open_orders += priced_report.quote_token_total
|
||||
# print("quote_token_free_in_open_orders", quote_token_free_in_open_orders)
|
||||
# print("quote_token_total_in_open_orders", quote_token_total_in_open_orders)
|
||||
|
||||
quote_report: AccountInstrumentValues = AccountInstrumentValues(
|
||||
account.shared_quote_token,
|
||||
account.shared_quote_token,
|
||||
account.shared_quote.raw_deposit,
|
||||
account.shared_quote.deposit,
|
||||
account.shared_quote.raw_borrow,
|
||||
account.shared_quote.borrow,
|
||||
InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
quote_token_free_in_open_orders,
|
||||
quote_token_total_in_open_orders,
|
||||
InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
Decimal(0),
|
||||
Decimal(0),
|
||||
InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
InstrumentValue(group.shared_quote_token, Decimal(0)),
|
||||
Decimal(0),
|
||||
Decimal(0),
|
||||
NullLotSizeConverter(),
|
||||
)
|
||||
# print("quote_report", quote_report)
|
||||
|
||||
health: Decimal = quote_report.net_value.value
|
||||
# print("Health (start)", health)
|
||||
for priced_report in priced_reports:
|
||||
slot: GroupSlot = group.slot_by_instrument(priced_report.base_token)
|
||||
spot_health = Decimal(0)
|
||||
spot_market: typing.Optional[GroupSlotSpotMarket] = slot.spot_market
|
||||
if spot_market is not None:
|
||||
base_value, quote_value = self._calculate_pessimistic_spot_value(
|
||||
priced_report
|
||||
)
|
||||
|
||||
spot_weight = (
|
||||
spot_market.init_asset_weight
|
||||
if base_value > 0
|
||||
else spot_market.init_liab_weight
|
||||
)
|
||||
spot_health = base_value.value * spot_weight
|
||||
# print("Weights", base_value.value, "*", spot_weight, spot_health)
|
||||
|
||||
perp_base, perp_quote = priced_report.if_worst_execution()
|
||||
perp_market: typing.Optional[GroupSlotPerpMarket] = slot.perp_market
|
||||
perp_health: Decimal = Decimal(0)
|
||||
if perp_market is not None:
|
||||
perp_weight = (
|
||||
perp_market.init_asset_weight
|
||||
if perp_base > 0
|
||||
else perp_market.init_liab_weight
|
||||
)
|
||||
perp_health = perp_base.value * perp_weight
|
||||
|
||||
health += spot_health
|
||||
health += perp_health
|
||||
health += quote_value.value
|
||||
health += perp_quote.value
|
||||
health += priced_report.raw_perp_quote_position
|
||||
# print("Health (now)", health, spot_health, perp_health, quote_value.value,
|
||||
# perp_quote.value, priced_report.raw_perp_quote_position)
|
||||
|
||||
# print("Health (returning)", health)
|
||||
|
||||
return health
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"« HealthCalculator [{self.health_type}] »"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self}"
|
|
@ -1,95 +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 typing
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from ..account import Account
|
||||
from ..cache import Cache
|
||||
from ..group import GroupSlotSpotMarket, GroupSlotPerpMarket, GroupSlot, Group
|
||||
from ..instrumentvalue import InstrumentValue
|
||||
from ..openorders import OpenOrders
|
||||
|
||||
from .collateralcalculator import CollateralCalculator
|
||||
|
||||
|
||||
class PerpCollateralCalculator(CollateralCalculator):
|
||||
def __init__(self) -> None:
|
||||
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],
|
||||
group: Group,
|
||||
cache: Cache,
|
||||
) -> InstrumentValue:
|
||||
# Quote token calculation:
|
||||
# total_collateral = deposits[QUOTE_INDEX] * deposit_index - borrows[QUOTE_INDEX] * borrow_index
|
||||
# Note: the `AccountSlot` in the `Account` already factors the deposit and borrow index.
|
||||
total: Decimal = account.shared_quote.net_value.value
|
||||
collateral_description = [f"{total:,.8f} USDC"]
|
||||
for basket_token in account.base_slots:
|
||||
slot: GroupSlot = group.slot_by_instrument(basket_token.base_instrument)
|
||||
token_price = group.token_price_from_cache(
|
||||
cache, basket_token.base_instrument
|
||||
)
|
||||
|
||||
# Not using perp market asset weights yet - stick with spot.
|
||||
# perp_market: typing.Optional[GroupSlotPerpMarket] = group.perp_markets_by_index[index]
|
||||
# if perp_market is None:
|
||||
# raise Exception(
|
||||
# f"Could not read perp market of token {basket_token.token_bank.token.symbol} at index {index} of cache at {cache.address}")
|
||||
spot_market: typing.Optional[GroupSlotSpotMarket] = slot.spot_market
|
||||
init_asset_weight: Decimal
|
||||
init_liab_weight: Decimal
|
||||
if spot_market is not None:
|
||||
init_asset_weight = spot_market.init_asset_weight
|
||||
init_liab_weight = spot_market.init_liab_weight
|
||||
else:
|
||||
perp_market: typing.Optional[GroupSlotPerpMarket] = slot.perp_market
|
||||
if perp_market is None:
|
||||
raise Exception(
|
||||
f"Could not read spot or perp market of token {basket_token.base_instrument.symbol} at index {slot.index} of cache at {cache.address}"
|
||||
)
|
||||
init_asset_weight = perp_market.init_asset_weight
|
||||
init_liab_weight = perp_market.init_liab_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 `AccountSlot` in the `Account` already factors the deposit and borrow index.
|
||||
weighted: Decimal = token_price.value * (
|
||||
(basket_token.deposit.value * init_asset_weight)
|
||||
- (basket_token.borrow.value * init_liab_weight)
|
||||
)
|
||||
|
||||
if weighted != 0:
|
||||
collateral_description += [
|
||||
f"{weighted:,.8f} USDC from {basket_token.base_instrument.symbol}"
|
||||
]
|
||||
total += weighted
|
||||
|
||||
self._logger.debug(f"Weighted collateral: {', '.join(collateral_description)}")
|
||||
return InstrumentValue(group.shared_quote_token, total)
|
|
@ -1,40 +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 typing
|
||||
|
||||
from ..account import Account
|
||||
from ..cache import Cache
|
||||
from ..group import Group
|
||||
from ..instrumentvalue import InstrumentValue
|
||||
from ..openorders import OpenOrders
|
||||
|
||||
from .collateralcalculator import CollateralCalculator
|
||||
|
||||
|
||||
class SerumCollateralCalculator(CollateralCalculator):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
def calculate(
|
||||
self,
|
||||
account: Account,
|
||||
all_open_orders: typing.Dict[str, OpenOrders],
|
||||
group: Group,
|
||||
cache: Cache,
|
||||
) -> InstrumentValue:
|
||||
raise NotImplementedError(
|
||||
"SerumCollateralCalculator.calculate() is not implemented."
|
||||
)
|
|
@ -1,105 +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 typing
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from ..account import Account
|
||||
from ..cache import Cache
|
||||
from ..group import GroupSlotSpotMarket, GroupSlotPerpMarket, GroupSlot, Group
|
||||
from ..instrumentvalue import InstrumentValue
|
||||
from ..openorders import OpenOrders
|
||||
|
||||
from .collateralcalculator import CollateralCalculator
|
||||
|
||||
|
||||
class SpotCollateralCalculator(CollateralCalculator):
|
||||
def __init__(self) -> None:
|
||||
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],
|
||||
group: Group,
|
||||
cache: Cache,
|
||||
) -> InstrumentValue:
|
||||
# Quote token calculation:
|
||||
# total_collateral = deposits[QUOTE_INDEX] * deposit_index - borrows[QUOTE_INDEX] * borrow_index
|
||||
# Note: the `AccountSlot` in the `Account` already factors the deposit and borrow index.
|
||||
total: Decimal = account.shared_quote.net_value.value
|
||||
collateral_description = [f"{total:,.8f} USDC"]
|
||||
for basket_token in account.base_slots:
|
||||
slot: GroupSlot = group.slot_by_instrument(basket_token.base_instrument)
|
||||
token_price = group.token_price_from_cache(
|
||||
cache, basket_token.base_instrument
|
||||
)
|
||||
|
||||
spot_market: typing.Optional[GroupSlotSpotMarket] = slot.spot_market
|
||||
init_asset_weight: Decimal
|
||||
init_liab_weight: Decimal
|
||||
if spot_market is not None:
|
||||
init_asset_weight = spot_market.init_asset_weight
|
||||
init_liab_weight = spot_market.init_liab_weight
|
||||
else:
|
||||
perp_market: typing.Optional[GroupSlotPerpMarket] = slot.perp_market
|
||||
if perp_market is None:
|
||||
raise Exception(
|
||||
f"Could not read spot or perp market of token {basket_token.base_instrument.symbol} at index {slot.index} of cache at {cache.address}"
|
||||
)
|
||||
init_asset_weight = perp_market.init_asset_weight
|
||||
init_liab_weight = perp_market.init_liab_weight
|
||||
|
||||
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.value * 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 `AccountSlot` in the `Account` already factors the deposit and borrow index.
|
||||
weighted: Decimal = in_orders + (
|
||||
token_price.value
|
||||
* (
|
||||
(basket_token.deposit.value * init_asset_weight)
|
||||
- (basket_token.borrow.value * init_liab_weight)
|
||||
)
|
||||
)
|
||||
|
||||
if weighted != 0:
|
||||
collateral_description += [
|
||||
f"{weighted:,.8f} USDC from {basket_token.base_instrument.symbol}"
|
||||
]
|
||||
total += weighted
|
||||
|
||||
self._logger.debug(f"Weighted collateral: {', '.join(collateral_description)}")
|
||||
return InstrumentValue(group.shared_quote_token, total)
|
|
@ -1,44 +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)
|
||||
|
||||
from dataclasses import dataclass
|
||||
from decimal import Decimal
|
||||
|
||||
from ..instrumentvalue import InstrumentValue
|
||||
from ..token import Token
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnsettledFundingParams:
|
||||
quote_token: Token
|
||||
base_position: InstrumentValue
|
||||
long_funding: Decimal
|
||||
long_settled_funding: Decimal
|
||||
short_funding: Decimal
|
||||
short_settled_funding: Decimal
|
||||
|
||||
|
||||
def calculate_unsettled_funding(params: UnsettledFundingParams) -> InstrumentValue:
|
||||
result: Decimal
|
||||
if params.base_position > 0:
|
||||
result = params.base_position.value * (
|
||||
params.long_funding - params.long_settled_funding
|
||||
)
|
||||
else:
|
||||
result = params.base_position.value * (
|
||||
params.short_funding - params.short_settled_funding
|
||||
)
|
||||
|
||||
return InstrumentValue(params.quote_token, result)
|
|
@ -21,15 +21,10 @@ from decimal import Decimal
|
|||
|
||||
from .account import Account
|
||||
from .cache import Cache
|
||||
from .calculators.collateralcalculator import CollateralCalculator
|
||||
from .calculators.spotcollateralcalculator import SpotCollateralCalculator
|
||||
from .calculators.perpcollateralcalculator import PerpCollateralCalculator
|
||||
from .group import Group
|
||||
from .instrumentvalue import InstrumentValue
|
||||
from .market import InventorySource, Market
|
||||
from .openorders import OpenOrders
|
||||
from .perpmarket import PerpMarket
|
||||
from .token import Token
|
||||
from .watcher import Watcher
|
||||
|
||||
|
||||
|
@ -67,7 +62,7 @@ class Inventory:
|
|||
return f"{self}"
|
||||
|
||||
|
||||
class SpotInventoryAccountWatcher:
|
||||
class InventoryAccountWatcher:
|
||||
def __init__(
|
||||
self,
|
||||
market: Market,
|
||||
|
@ -94,7 +89,6 @@ class SpotInventoryAccountWatcher:
|
|||
account.net_values, market.quote.symbol
|
||||
)
|
||||
self.quote_index: int = account.net_values_by_index.index(quote_value)
|
||||
self.collateral_calculator: CollateralCalculator = SpotCollateralCalculator()
|
||||
|
||||
@property
|
||||
def latest(self) -> Inventory:
|
||||
|
@ -110,9 +104,9 @@ class SpotInventoryAccountWatcher:
|
|||
str(oo_watcher.latest.address): oo_watcher.latest
|
||||
for oo_watcher in self.all_open_orders_watchers
|
||||
}
|
||||
available_collateral: InstrumentValue = self.collateral_calculator.calculate(
|
||||
account, all_open_orders, group, cache
|
||||
)
|
||||
|
||||
frame = account.to_dataframe(group, all_open_orders, cache)
|
||||
available_collateral: InstrumentValue = account.init_health(frame)
|
||||
|
||||
base_value = account.net_values_by_index[self.base_index]
|
||||
if base_value is None:
|
||||
|
@ -132,56 +126,3 @@ class SpotInventoryAccountWatcher:
|
|||
base_value,
|
||||
quote_value,
|
||||
)
|
||||
|
||||
|
||||
class PerpInventoryAccountWatcher:
|
||||
def __init__(
|
||||
self,
|
||||
market: PerpMarket,
|
||||
account_watcher: Watcher[Account],
|
||||
group_watcher: Watcher[Group],
|
||||
cache_watcher: Watcher[Cache],
|
||||
group: Group,
|
||||
):
|
||||
self.market: PerpMarket = market
|
||||
self.account_watcher: Watcher[Account] = account_watcher
|
||||
self.group_watcher: Watcher[Group] = group_watcher
|
||||
self.cache_watcher: Watcher[Cache] = cache_watcher
|
||||
self.perp_account_index: int = group.slot_by_perp_market_address(
|
||||
market.address
|
||||
).index
|
||||
account: Account = account_watcher.latest
|
||||
quote_value = InstrumentValue.find_by_symbol(
|
||||
account.net_values, market.quote.symbol
|
||||
)
|
||||
self.quote_index: int = account.net_values_by_index.index(quote_value)
|
||||
self.collateral_calculator: CollateralCalculator = PerpCollateralCalculator()
|
||||
|
||||
@property
|
||||
def latest(self) -> Inventory:
|
||||
account: Account = self.account_watcher.latest
|
||||
group: Group = self.group_watcher.latest
|
||||
cache: Cache = self.cache_watcher.latest
|
||||
perp_account = account.perp_accounts_by_index[self.perp_account_index]
|
||||
if perp_account is None:
|
||||
raise Exception(
|
||||
f"Could not find perp account for {self.market.symbol} in account {account.address} at index {self.perp_account_index}."
|
||||
)
|
||||
|
||||
available_collateral: InstrumentValue = self.collateral_calculator.calculate(
|
||||
account, {}, group, cache
|
||||
)
|
||||
|
||||
base_lots = perp_account.base_position
|
||||
base_value = self.market.lot_size_converter.base_size_lots_to_number(base_lots)
|
||||
# TODO - what about ADA, when base isn't a Token?
|
||||
base_token_value = InstrumentValue(Token.ensure(self.market.base), base_value)
|
||||
quote_token_value = account.shared_quote.net_value
|
||||
|
||||
return Inventory(
|
||||
InventorySource.ACCOUNT,
|
||||
perp_account.mngo_accrued,
|
||||
available_collateral,
|
||||
base_token_value,
|
||||
quote_token_value,
|
||||
)
|
||||
|
|
|
@ -26,16 +26,11 @@ from ..instrumentvalue import InstrumentValue
|
|||
from ..modelstate import ModelState
|
||||
from ..token import Token
|
||||
|
||||
from ..calculators.collateralcalculator import CollateralCalculator
|
||||
from ..calculators.perpcollateralcalculator import PerpCollateralCalculator
|
||||
from ..calculators.spotcollateralcalculator import SpotCollateralCalculator
|
||||
|
||||
# # 🥭 ModelStateBuilder class
|
||||
#
|
||||
# Base class for building a `ModelState` through polling or websockets.
|
||||
#
|
||||
|
||||
|
||||
class ModelStateBuilder(metaclass=abc.ABCMeta):
|
||||
def __init__(self) -> None:
|
||||
self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)
|
||||
|
@ -286,8 +281,6 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
PublicKey
|
||||
] = all_open_orders_addresses
|
||||
|
||||
self.collateral_calculator: CollateralCalculator = SpotCollateralCalculator()
|
||||
|
||||
def poll(self, context: mango.Context) -> ModelState:
|
||||
addresses: typing.List[PublicKey] = [
|
||||
self.group_address,
|
||||
|
@ -348,9 +341,8 @@ class SpotPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
account.net_values, self.market.quote.symbol
|
||||
)
|
||||
|
||||
available_collateral: InstrumentValue = self.collateral_calculator.calculate(
|
||||
account, all_open_orders, group, cache
|
||||
)
|
||||
frame = account.to_dataframe(group, all_open_orders, cache)
|
||||
available_collateral: InstrumentValue = account.init_health(frame)
|
||||
inventory: mango.Inventory = mango.Inventory(
|
||||
mango.InventorySource.ACCOUNT,
|
||||
mngo_accrued,
|
||||
|
@ -395,6 +387,7 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
oracle: mango.Oracle,
|
||||
group_address: PublicKey,
|
||||
cache_address: PublicKey,
|
||||
all_open_orders_addresses: typing.Sequence[PublicKey],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.order_owner: PublicKey = order_owner
|
||||
|
@ -404,7 +397,9 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
self.group_address: PublicKey = group_address
|
||||
self.cache_address: PublicKey = cache_address
|
||||
|
||||
self.collateral_calculator: CollateralCalculator = PerpCollateralCalculator()
|
||||
self.all_open_orders_addresses: typing.Sequence[
|
||||
PublicKey
|
||||
] = all_open_orders_addresses
|
||||
|
||||
def poll(self, context: mango.Context) -> ModelState:
|
||||
addresses: typing.List[PublicKey] = [
|
||||
|
@ -414,6 +409,7 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
self.market.underlying_perp_market.bids,
|
||||
self.market.underlying_perp_market.asks,
|
||||
self.market.event_queue_address,
|
||||
*self.all_open_orders_addresses,
|
||||
]
|
||||
account_infos: typing.Sequence[
|
||||
mango.AccountInfo
|
||||
|
@ -422,6 +418,33 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
cache: mango.Cache = mango.Cache.parse(account_infos[1])
|
||||
account: mango.Account = mango.Account.parse(account_infos[2], group, cache)
|
||||
|
||||
# Update our stash of OpenOrders addresses for next time, in case new OpenOrders accounts were added
|
||||
self.all_open_orders_addresses = account.spot_open_orders
|
||||
|
||||
spot_open_orders_account_infos_by_address = {
|
||||
str(account_info.address): account_info
|
||||
for account_info in account_infos[6:]
|
||||
}
|
||||
|
||||
all_open_orders: typing.Dict[str, mango.OpenOrders] = {}
|
||||
for basket_token in account.slots:
|
||||
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.base_instrument.decimals,
|
||||
account.shared_quote_token.decimals,
|
||||
)
|
||||
all_open_orders[str(basket_token.spot_open_orders)] = open_orders
|
||||
|
||||
slot = group.slot_by_perp_market_address(self.market.address)
|
||||
perp_account = account.perp_accounts_by_index[slot.index]
|
||||
if perp_account is None:
|
||||
|
@ -434,9 +457,8 @@ class PerpPollingModelStateBuilder(PollingModelStateBuilder):
|
|||
base_value = self.market.lot_size_converter.base_size_lots_to_number(base_lots)
|
||||
base_token_value = mango.InstrumentValue(self.market.base, base_value)
|
||||
quote_token_value = account.shared_quote.net_value
|
||||
available_collateral: InstrumentValue = self.collateral_calculator.calculate(
|
||||
account, {}, group, cache
|
||||
)
|
||||
frame = account.to_dataframe(group, all_open_orders, cache)
|
||||
available_collateral: InstrumentValue = account.init_health(frame)
|
||||
inventory: mango.Inventory = mango.Inventory(
|
||||
mango.InventorySource.ACCOUNT,
|
||||
perp_account.mngo_accrued,
|
||||
|
|
|
@ -176,11 +176,48 @@ def _polling_perp_model_state_builder_factory(
|
|||
market: mango.PerpMarket,
|
||||
oracle: mango.Oracle,
|
||||
) -> ModelStateBuilder:
|
||||
all_open_orders_addresses: typing.Sequence[PublicKey] = account.spot_open_orders
|
||||
return PerpPollingModelStateBuilder(
|
||||
account.address, market, oracle, group.address, group.cache
|
||||
account.address,
|
||||
market,
|
||||
oracle,
|
||||
group.address,
|
||||
group.cache,
|
||||
all_open_orders_addresses,
|
||||
)
|
||||
|
||||
|
||||
def __load_all_openorders_watchers(
|
||||
context: mango.Context,
|
||||
wallet: mango.Wallet,
|
||||
account: mango.Account,
|
||||
group: mango.Group,
|
||||
websocket_manager: mango.WebSocketSubscriptionManager,
|
||||
health_check: mango.HealthCheck,
|
||||
) -> typing.Sequence[mango.Watcher[mango.OpenOrders]]:
|
||||
all_open_orders_watchers: typing.List[mango.Watcher[mango.OpenOrders]] = []
|
||||
for basket_token in account.base_slots:
|
||||
if basket_token.spot_open_orders is not None:
|
||||
spot_market_symbol: str = f"spot:{basket_token.base_instrument.symbol}/{account.shared_quote_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]
|
||||
|
||||
return all_open_orders_watchers
|
||||
|
||||
|
||||
def _websocket_model_state_builder_factory(
|
||||
context: mango.Context,
|
||||
disposer: mango.DisposePropagator,
|
||||
|
@ -251,32 +288,18 @@ def _websocket_model_state_builder_factory(
|
|||
account.spot_open_orders_by_index[market_index] or SYSTEM_PROGRAM_ADDRESS
|
||||
)
|
||||
|
||||
all_open_orders_watchers: typing.List[mango.Watcher[mango.OpenOrders]] = []
|
||||
for basket_token in account.base_slots:
|
||||
if basket_token.spot_open_orders is not None:
|
||||
spot_market_symbol: str = f"spot:{basket_token.base_instrument.symbol}/{account.shared_quote_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
|
||||
all_open_orders_watchers = __load_all_openorders_watchers(
|
||||
context, wallet, account, group, websocket_manager, health_check
|
||||
)
|
||||
latest_open_orders_observer = list(
|
||||
[
|
||||
oo_watcher
|
||||
for oo_watcher in all_open_orders_watchers
|
||||
if (market.base == market.base and market.quote == market.quote)
|
||||
]
|
||||
)[0]
|
||||
|
||||
inventory_watcher = mango.SpotInventoryAccountWatcher(
|
||||
inventory_watcher = mango.InventoryAccountWatcher(
|
||||
market,
|
||||
latest_account_observer,
|
||||
group_watcher,
|
||||
|
@ -291,9 +314,19 @@ def _websocket_model_state_builder_factory(
|
|||
)
|
||||
elif isinstance(market, mango.PerpMarket):
|
||||
order_owner = account.address
|
||||
inventory_watcher = mango.PerpInventoryAccountWatcher(
|
||||
market, latest_account_observer, group_watcher, cache_watcher, group
|
||||
|
||||
all_open_orders_watchers = __load_all_openorders_watchers(
|
||||
context, wallet, account, group, websocket_manager, health_check
|
||||
)
|
||||
|
||||
inventory_watcher = mango.InventoryAccountWatcher(
|
||||
market,
|
||||
latest_account_observer,
|
||||
group_watcher,
|
||||
all_open_orders_watchers,
|
||||
cache_watcher,
|
||||
)
|
||||
|
||||
latest_open_orders_observer = mango.build_perp_open_orders_watcher(
|
||||
context,
|
||||
websocket_manager,
|
||||
|
|
|
@ -1,280 +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://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 solana.publickey import PublicKey
|
||||
|
||||
from .account import Account
|
||||
from .accountinstrumentvalues import AccountInstrumentValues
|
||||
from .cache import Cache
|
||||
from .context import Context
|
||||
from .group import Group
|
||||
from .instrumentvalue import InstrumentValue
|
||||
from .openorders import OpenOrders
|
||||
from .token import Instrument, Token, SolToken
|
||||
|
||||
|
||||
class TokenValuation:
|
||||
def __init__(
|
||||
self,
|
||||
raw_token_value: InstrumentValue,
|
||||
price_token_value: InstrumentValue,
|
||||
value_token_value: InstrumentValue,
|
||||
) -> None:
|
||||
self.raw: InstrumentValue = raw_token_value
|
||||
self.price: InstrumentValue = price_token_value
|
||||
self.value: InstrumentValue = value_token_value
|
||||
|
||||
@staticmethod
|
||||
def from_json_dict(
|
||||
context: Context, json: typing.Dict[str, typing.Any]
|
||||
) -> "TokenValuation":
|
||||
symbol: str = json["symbol"]
|
||||
value_currency: str = json["valueCurrency"]
|
||||
balance: Decimal = Decimal(json["balance"])
|
||||
price: Decimal = Decimal(json["price"])
|
||||
value: Decimal = Decimal(json["value"])
|
||||
token = context.instrument_lookup.find_by_symbol(symbol)
|
||||
if token is None:
|
||||
raise Exception(f"Could not find token for symbol: {symbol}")
|
||||
currency_token = context.instrument_lookup.find_by_symbol(value_currency)
|
||||
if currency_token is None:
|
||||
raise Exception(
|
||||
f"Could not find token for currency symbol: {value_currency}"
|
||||
)
|
||||
raw_token_value: InstrumentValue = InstrumentValue(token, balance)
|
||||
price_token_value: InstrumentValue = InstrumentValue(currency_token, price)
|
||||
value_token_value: InstrumentValue = InstrumentValue(currency_token, value)
|
||||
return TokenValuation(raw_token_value, price_token_value, value_token_value)
|
||||
|
||||
@staticmethod
|
||||
def from_token_balance(
|
||||
context: Context, group: Group, cache: Cache, token_balance: InstrumentValue
|
||||
) -> "TokenValuation":
|
||||
token_to_lookup: Instrument = token_balance.token
|
||||
if token_balance.token == SolToken:
|
||||
token_to_lookup = context.instrument_lookup.find_by_symbol_or_raise("SOL")
|
||||
cached_token_price: InstrumentValue = group.token_price_from_cache(
|
||||
cache, token_to_lookup
|
||||
)
|
||||
balance_value: InstrumentValue = token_balance * cached_token_price
|
||||
return TokenValuation(token_balance, cached_token_price, balance_value)
|
||||
|
||||
@staticmethod
|
||||
def all_from_wallet(
|
||||
context: Context, group: Group, cache: Cache, address: PublicKey
|
||||
) -> typing.Sequence["TokenValuation"]:
|
||||
balances: typing.List[InstrumentValue] = []
|
||||
sol_balance = context.client.get_balance(address)
|
||||
balances += [InstrumentValue(SolToken, sol_balance)]
|
||||
|
||||
for slot_token_bank in group.tokens:
|
||||
if isinstance(slot_token_bank.token, Token):
|
||||
balance = InstrumentValue.fetch_total_value(
|
||||
context, address, slot_token_bank.token
|
||||
)
|
||||
balances += [balance]
|
||||
|
||||
wallet_tokens: typing.List[TokenValuation] = []
|
||||
for balance in balances:
|
||||
if balance.value != 0:
|
||||
wallet_tokens += [
|
||||
TokenValuation.from_token_balance(context, group, cache, balance)
|
||||
]
|
||||
|
||||
return wallet_tokens
|
||||
|
||||
def to_json_dict(self) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
"symbol": self.raw.token.symbol,
|
||||
"valueCurrency": self.price.token.symbol,
|
||||
"balance": f"{self.raw.value:.8f}",
|
||||
"price": f"{self.price.value:.8f}",
|
||||
"value": f"{self.value.value:.8f}",
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f" {self.raw:<45} worth {self.value}"
|
||||
|
||||
|
||||
class AccountValuation:
|
||||
def __init__(
|
||||
self, name: str, address: PublicKey, tokens: typing.Sequence[TokenValuation]
|
||||
) -> None:
|
||||
self.name: str = name
|
||||
self.address: PublicKey = address
|
||||
self.tokens: typing.Sequence[TokenValuation] = tokens
|
||||
|
||||
@property
|
||||
def value(self) -> InstrumentValue:
|
||||
return sum((t.value for t in self.tokens[1:]), start=self.tokens[0].value)
|
||||
|
||||
@staticmethod
|
||||
def from_json_dict(
|
||||
context: Context, json: typing.Dict[str, typing.Any]
|
||||
) -> "AccountValuation":
|
||||
name: str = json["name"]
|
||||
address: PublicKey = PublicKey(json["address"])
|
||||
tokens: typing.List[TokenValuation] = []
|
||||
for token_dict in json["tokens"]:
|
||||
token_valuation: TokenValuation = TokenValuation.from_json_dict(
|
||||
context, token_dict
|
||||
)
|
||||
tokens += [token_valuation]
|
||||
|
||||
return AccountValuation(name, address, tokens)
|
||||
|
||||
@staticmethod
|
||||
def from_account(
|
||||
context: Context, group: Group, account: Account, cache: Cache
|
||||
) -> "AccountValuation":
|
||||
open_orders: typing.Dict[str, OpenOrders] = account.load_all_spot_open_orders(
|
||||
context
|
||||
)
|
||||
token_values: typing.List[TokenValuation] = []
|
||||
for asset in account.base_slots:
|
||||
if (asset.net_value.value != 0) or (
|
||||
(asset.perp_account is not None) and not asset.perp_account.empty
|
||||
):
|
||||
report: AccountInstrumentValues = (
|
||||
AccountInstrumentValues.from_account_basket_base_token(
|
||||
asset, open_orders, group
|
||||
)
|
||||
)
|
||||
asset_valuation = TokenValuation.from_token_balance(
|
||||
context, group, cache, report.net_value
|
||||
)
|
||||
token_values += [asset_valuation]
|
||||
|
||||
quote_valuation = TokenValuation.from_token_balance(
|
||||
context, group, cache, account.shared_quote.net_value
|
||||
)
|
||||
token_values += [quote_valuation]
|
||||
|
||||
return AccountValuation(account.info, account.address, token_values)
|
||||
|
||||
def to_json_dict(self) -> typing.Dict[str, typing.Any]:
|
||||
value: InstrumentValue = self.value
|
||||
return {
|
||||
"name": self.name,
|
||||
"address": f"{self.address}",
|
||||
"value": f"{value.value:.8f}",
|
||||
"valueCurrency": value.token.symbol,
|
||||
"tokens": list([tok.to_json_dict() for tok in self.tokens]),
|
||||
}
|
||||
|
||||
|
||||
class Valuation:
|
||||
def __init__(
|
||||
self,
|
||||
timestamp: datetime,
|
||||
address: PublicKey,
|
||||
wallet_tokens: typing.Sequence[TokenValuation],
|
||||
accounts: typing.Sequence[AccountValuation],
|
||||
):
|
||||
self.timestamp: datetime = timestamp
|
||||
self.address: PublicKey = address
|
||||
self.wallet_tokens: typing.Sequence[TokenValuation] = wallet_tokens
|
||||
self.accounts: typing.Sequence[AccountValuation] = accounts
|
||||
|
||||
@property
|
||||
def value(self) -> InstrumentValue:
|
||||
wallet_tokens_value: InstrumentValue = sum(
|
||||
(t.value for t in self.wallet_tokens[1:]), start=self.wallet_tokens[0].value
|
||||
)
|
||||
return sum((acc.value for acc in self.accounts), start=wallet_tokens_value)
|
||||
|
||||
@staticmethod
|
||||
def from_json_dict(
|
||||
context: Context, json: typing.Dict[str, typing.Any]
|
||||
) -> "Valuation":
|
||||
timestamp: datetime = datetime.fromisoformat(json["timestamp"])
|
||||
address: PublicKey = PublicKey(json["address"])
|
||||
wallet_tokens: typing.List[TokenValuation] = []
|
||||
for token_dict in json["wallet"]["tokens"]:
|
||||
token_valuation: TokenValuation = TokenValuation.from_json_dict(
|
||||
context, token_dict
|
||||
)
|
||||
wallet_tokens += [token_valuation]
|
||||
|
||||
accounts: typing.List[AccountValuation] = []
|
||||
for account_dict in json["accounts"]:
|
||||
account_valuation: AccountValuation = AccountValuation.from_json_dict(
|
||||
context, account_dict
|
||||
)
|
||||
accounts += [account_valuation]
|
||||
|
||||
return Valuation(timestamp, address, wallet_tokens, accounts)
|
||||
|
||||
@staticmethod
|
||||
def from_wallet(
|
||||
context: Context, group: Group, cache: Cache, address: PublicKey
|
||||
) -> "Valuation":
|
||||
spl_tokens = TokenValuation.all_from_wallet(context, group, cache, address)
|
||||
|
||||
mango_accounts = Account.load_all_for_owner(context, address, group)
|
||||
account_valuations = []
|
||||
for account in mango_accounts:
|
||||
account_valuations += [
|
||||
AccountValuation.from_account(context, group, account, cache)
|
||||
]
|
||||
|
||||
return Valuation(datetime.now(), address, spl_tokens, account_valuations)
|
||||
|
||||
def to_json_dict(self) -> typing.Dict[str, typing.Any]:
|
||||
value: InstrumentValue = self.value
|
||||
wallet_value: InstrumentValue = sum(
|
||||
(t.value for t in self.wallet_tokens[1:]), start=self.wallet_tokens[0].value
|
||||
)
|
||||
return {
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"address": f"{self.address}",
|
||||
"value": f"{value.value:.8f}",
|
||||
"valueCurrency": value.token.symbol,
|
||||
"wallet": {
|
||||
"value": f"{wallet_value.value:.8f}",
|
||||
"valueCurrency": wallet_value.token.symbol,
|
||||
"tokens": list([tok.to_json_dict() for tok in self.wallet_tokens]),
|
||||
},
|
||||
"accounts": list([acc.to_json_dict() for acc in self.accounts]),
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
address: str = f"{self.address}:"
|
||||
wallet_total: InstrumentValue = sum(
|
||||
(t.value for t in self.wallet_tokens[1:]), start=self.wallet_tokens[0].value
|
||||
)
|
||||
accounts: typing.List[str] = []
|
||||
for account in self.accounts:
|
||||
account_tokens: str = "\n ".join(
|
||||
[f"{item}" for item in account.tokens]
|
||||
)
|
||||
accounts += [
|
||||
f"""Account '{account.name}' (total: {account.value}):
|
||||
{account_tokens}"""
|
||||
]
|
||||
|
||||
accounts_tokens: str = "\n ".join(accounts)
|
||||
wallet_tokens: str = "\n ".join(
|
||||
[f"{item}" for item in self.wallet_tokens]
|
||||
)
|
||||
return f"""« Valuation of {address:<47} {self.value}
|
||||
Wallet Tokens (total: {wallet_total}):
|
||||
{wallet_tokens}
|
||||
{accounts_tokens}
|
||||
»"""
|
|
@ -1,82 +0,0 @@
|
|||
from ..fakes import fake_context
|
||||
from ..data import load_data_from_directory
|
||||
|
||||
from decimal import Decimal
|
||||
from mango.calculators.healthcalculator import HealthType, HealthCalculator
|
||||
|
||||
|
||||
def test_empty() -> None:
|
||||
context = fake_context()
|
||||
group, cache, account, open_orders = load_data_from_directory(
|
||||
"tests/testdata/empty"
|
||||
)
|
||||
|
||||
actual = HealthCalculator(context, HealthType.INITIAL)
|
||||
health = actual.calculate(account, open_orders, group, cache)
|
||||
# Typescript says: 0
|
||||
assert health == Decimal("0")
|
||||
|
||||
|
||||
def test_1deposit() -> None:
|
||||
context = fake_context()
|
||||
group, cache, account, open_orders = load_data_from_directory(
|
||||
"tests/testdata/1deposit"
|
||||
)
|
||||
|
||||
actual = HealthCalculator(context, HealthType.INITIAL)
|
||||
health = actual.calculate(account, open_orders, group, cache)
|
||||
# Typescript says: 37904260000.05905822642118252475
|
||||
assert health == Decimal("37904.2600000591928892771752953600134")
|
||||
|
||||
|
||||
def test_account1() -> None:
|
||||
context = fake_context()
|
||||
group, cache, account, open_orders = load_data_from_directory(
|
||||
"tests/testdata/account1"
|
||||
)
|
||||
|
||||
actual = HealthCalculator(context, HealthType.INITIAL)
|
||||
health = actual.calculate(account, open_orders, group, cache)
|
||||
# Typescript says: 454884281.15520619643754685058
|
||||
# TODO: This is significantly different from Typescript answer
|
||||
assert health == Decimal("2578453.62435039273978679178827388534")
|
||||
|
||||
|
||||
def test_account2() -> None:
|
||||
context = fake_context()
|
||||
group, cache, account, open_orders = load_data_from_directory(
|
||||
"tests/testdata/account2"
|
||||
)
|
||||
|
||||
actual = HealthCalculator(context, HealthType.INITIAL)
|
||||
health = actual.calculate(account, open_orders, group, cache)
|
||||
# Typescript says: 7516159604.84918334545095675026
|
||||
# TODO: This is slightly different from Typescript answer
|
||||
assert health == Decimal("-34471.8822627460347363357247598728190")
|
||||
|
||||
|
||||
def test_account3() -> None:
|
||||
context = fake_context()
|
||||
group, cache, account, open_orders = load_data_from_directory(
|
||||
"tests/testdata/account3"
|
||||
)
|
||||
|
||||
actual = HealthCalculator(context, HealthType.INITIAL)
|
||||
health = actual.calculate(account, open_orders, group, cache)
|
||||
|
||||
# Typescript says: 341025333625.51856223547208912805
|
||||
# TODO: This is significantly different from Typescript answer
|
||||
assert health == Decimal("7036880.69722812395986194177339495613")
|
||||
|
||||
|
||||
def test_account4() -> None:
|
||||
context = fake_context()
|
||||
group, cache, account, open_orders = load_data_from_directory(
|
||||
"tests/testdata/account4"
|
||||
)
|
||||
|
||||
actual = HealthCalculator(context, HealthType.INITIAL)
|
||||
health = actual.calculate(account, open_orders, group, cache)
|
||||
# Typescript says: -848086876487.04950427436299875694
|
||||
# TODO: This is significantly different from Typescript answer
|
||||
assert health == Decimal("1100318.49506000114695611699892507857")
|
|
@ -10,10 +10,10 @@ def test_empty() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 0
|
||||
assert account.init_health(frame) == Decimal("0")
|
||||
assert account.init_health(frame).value == Decimal("0")
|
||||
|
||||
# Typescript says: 0
|
||||
assert account.maint_health(frame) == Decimal("0")
|
||||
assert account.maint_health(frame).value == Decimal("0")
|
||||
|
||||
# Typescript says: 100
|
||||
assert account.init_health_ratio(frame) == Decimal("100")
|
||||
|
@ -22,7 +22,7 @@ def test_empty() -> None:
|
|||
assert account.maint_health_ratio(frame) == Decimal("100")
|
||||
|
||||
# Typescript says: 0
|
||||
assert account.total_value(frame) == Decimal("0")
|
||||
assert account.total_value(frame).value == Decimal("0")
|
||||
|
||||
# Typescript says: 0
|
||||
assert account.leverage(frame) == Decimal("0")
|
||||
|
@ -37,12 +37,12 @@ def test_1deposit() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 37904260000.05905822642118252475
|
||||
assert account.init_health(frame) == Decimal(
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"37904.2600000591928892771752953600134"
|
||||
)
|
||||
|
||||
# Typescript says: 42642292500.06652466908819931746
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"42642.2925000665920004368222072800150"
|
||||
)
|
||||
|
||||
|
@ -53,7 +53,7 @@ def test_1deposit() -> None:
|
|||
assert account.maint_health_ratio(frame) == Decimal("100")
|
||||
|
||||
# Typescript says: 47380.32499999999999928946
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"47380.3250000739911115964691192000167"
|
||||
)
|
||||
|
||||
|
@ -70,10 +70,12 @@ def test_account1() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 454884281.15520619643754685058
|
||||
assert account.init_health(frame) == Decimal("454.88428115521887496581258174978975")
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"454.88428115521887496581258174978975"
|
||||
)
|
||||
|
||||
# Typescript says: 901472688.63722587052636470162
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"901.47268863723220971375597908220329"
|
||||
)
|
||||
|
||||
|
@ -88,7 +90,7 @@ def test_account1() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: 1348.25066158888197520582
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"1348.25066711924554446169937641461683"
|
||||
)
|
||||
|
||||
|
@ -105,10 +107,12 @@ def test_account2() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 7516159604.84918334545095675026
|
||||
assert account.init_health(frame) == Decimal("7516.1596048492430563556697582309637")
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"7516.1596048492430563556697582309637"
|
||||
)
|
||||
|
||||
# Typescript says: 9618709877.45119083596852505025
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"9618.7098774512206992595893853316522"
|
||||
)
|
||||
|
||||
|
@ -123,7 +127,7 @@ def test_account2() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: 11721.35669142618275273549
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"11721.3566920531983421635090124323407"
|
||||
)
|
||||
|
||||
|
@ -140,10 +144,12 @@ def test_account3() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 341025333625.51856223547208912805
|
||||
assert account.init_health(frame) == Decimal("341025.33362550396263557255539801613")
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"341025.33362550396263557255539801613"
|
||||
)
|
||||
|
||||
# Typescript says: 683477170424.20340250929429970483
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"683477.17042418393637609525383421613"
|
||||
)
|
||||
|
||||
|
@ -158,7 +164,7 @@ def test_account3() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: 1025929.00722205438034961844
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"1025929.00722286391011661795227041613"
|
||||
)
|
||||
|
||||
|
@ -175,12 +181,12 @@ def test_account4() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: -848086876487.04950427436299875694
|
||||
assert account.init_health(frame) == Decimal(
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"-848086.87648706716344229365643382143"
|
||||
)
|
||||
|
||||
# Typescript says: -433869053006.07361789143756070075
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"-433869.05300609716344867811565222143"
|
||||
)
|
||||
|
||||
|
@ -195,7 +201,9 @@ def test_account4() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: -19651.22952604663374742699
|
||||
assert account.total_value(frame) == Decimal("-19651.22952512716345506257487062143")
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"-19651.22952512716345506257487062143"
|
||||
)
|
||||
|
||||
# Typescript says: -421.56937094643044972031
|
||||
assert account.leverage(frame) == Decimal("-421.569370966155451390856326152441566")
|
||||
|
@ -210,12 +218,12 @@ def test_account5() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 15144959918141.09175135195858530324
|
||||
assert account.init_health(frame) == Decimal(
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"15144959.9181410924496111317578727438"
|
||||
)
|
||||
|
||||
# Typescript says: 15361719060997.68276021614036608298
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"15361719.0609976820704356689151633723"
|
||||
)
|
||||
|
||||
|
@ -230,7 +238,7 @@ def test_account5() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: 15578478.17337437202354522015
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"15578478.2038542716912602060724540009"
|
||||
)
|
||||
|
||||
|
@ -247,12 +255,12 @@ def test_account6() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 14480970069238.33686487450164648294
|
||||
assert account.init_health(frame) == Decimal(
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"14480970.0692383312566073701425189089"
|
||||
)
|
||||
|
||||
# Typescript says: 15030566.251990.17026082618337312624
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"15030566.2519901615291113644851708626"
|
||||
)
|
||||
|
||||
|
@ -267,7 +275,7 @@ def test_account6() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: 15580162.40781940827396567784
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"15580162.4347419918016153588278228163"
|
||||
)
|
||||
|
||||
|
@ -284,12 +292,12 @@ def test_account7() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 16272272.28055547965738014682
|
||||
assert account.init_health(frame) == Decimal(
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"16.2722722805554769752688793558604253"
|
||||
)
|
||||
|
||||
# Typescript says: 16649749.17384252860704663135
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"16.6497491738425205606631394095387232"
|
||||
)
|
||||
|
||||
|
@ -304,7 +312,7 @@ def test_account7() -> None:
|
|||
)
|
||||
|
||||
# Typescript says: 17.02722595090433088671
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"17.0272260671295641460573994632170211"
|
||||
)
|
||||
|
||||
|
@ -321,12 +329,12 @@ def test_account8() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 337240882.73863372865950083224
|
||||
assert account.init_health(frame) == Decimal(
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"337.240882738638250140157452733466665"
|
||||
)
|
||||
|
||||
# Typescript says: 496326340.62213476397751321656
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"496.326340622137024717841136284723945"
|
||||
)
|
||||
|
||||
|
@ -341,7 +349,7 @@ def test_account8() -> None:
|
|||
)
|
||||
|
||||
# # Typescript says: 655.41179779906788382959
|
||||
assert account.total_value(frame) == Decimal(
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"655.411798505635799295524819835981215"
|
||||
)
|
||||
|
||||
|
@ -358,10 +366,12 @@ def test_account9() -> None:
|
|||
frame = account.to_dataframe(group, open_orders, cache)
|
||||
|
||||
# Typescript says: 96257596.93294236504926786324
|
||||
assert account.init_health(frame) == Decimal("96.25759693294275991712014716369911")
|
||||
assert account.init_health(frame).value == Decimal(
|
||||
"96.25759693294275991712014716369911"
|
||||
)
|
||||
|
||||
# # Typescript says: 511619124.36291981710078502488
|
||||
assert account.maint_health(frame) == Decimal(
|
||||
assert account.maint_health(frame).value == Decimal(
|
||||
"511.61912436291544183007238034887025"
|
||||
)
|
||||
|
||||
|
@ -376,7 +386,9 @@ def test_account9() -> None:
|
|||
)
|
||||
|
||||
# # Typescript says: 926.98053240315212875089
|
||||
assert account.total_value(frame) == Decimal("926.98065179288812374302461353404140")
|
||||
assert account.total_value(frame).value == Decimal(
|
||||
"926.98065179288812374302461353404140"
|
||||
)
|
||||
|
||||
# # Typescript says: 3.91944283828893702548
|
||||
assert account.leverage(frame) == Decimal("3.91944232844987663175000830924077777")
|
||||
|
|
Loading…
Reference in New Issue