Removed stale calculator cruft now that pandas is the main mechanism.

This commit is contained in:
Geoff Taylor 2022-02-21 12:28:23 +00:00
parent 8eb308d9be
commit 5063f14a16
20 changed files with 181 additions and 1657 deletions

View File

@ -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}")

View File

@ -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)

View File

@ -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)
)

View File

@ -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

View File

@ -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:

View File

@ -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}"

View File

@ -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

View File

@ -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."
)

View File

@ -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}"

View File

@ -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)

View File

@ -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."
)

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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,

View File

@ -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,

View File

@ -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}
»"""

View File

@ -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")

View File

@ -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")